From 7c3f1075be630bb38f7b1f1484f0dcff196c660d Mon Sep 17 00:00:00 2001 From: liyd <1419499670@qq.com> Date: Sat, 3 Dec 2022 17:07:30 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/generator/domain/ColumnInfo.java | 78 ++++ .../common/generator/domain/GenConfig.java | 55 +++ .../common/generator/domain/vo/TableInfo.java | 33 ++ .../generator/rest/GenConfigController.java | 37 ++ .../generator/rest/GeneratorController.java | 94 ++++ .../generator/service/GenConfigService.java | 25 ++ .../generator/service/GeneratorService.java | 116 +++++ .../service/impl/GenConfigServiceImpl.java | 55 +++ .../service/impl/GeneratorServiceImpl.java | 242 ++++++++++ .../common/generator/utils/ColUtil.java | 40 ++ .../common/generator/utils/GenUtil.java | 420 ++++++++++++++++++ .../common/generator/wql/Generator.wql | 82 ++++ .../java/org/nl/modules/system/wql/sys.xls | Bin 246272 -> 254464 bytes .../template/generator/admin/Controller.ftl | 3 +- .../template/generator/admin/Dto.ftl | 35 +- .../template/generator/admin/Service.ftl | 80 ++-- .../template/generator/admin/ServiceImpl.ftl | 131 +++--- .../template/generator/front/index.ftl | 14 +- nladmin-ui/src/views/generator/config.vue | 47 +- nladmin-ui/src/views/generator/index.vue | 14 +- 20 files changed, 1447 insertions(+), 154 deletions(-) create mode 100644 nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/domain/ColumnInfo.java create mode 100644 nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/domain/GenConfig.java create mode 100644 nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/domain/vo/TableInfo.java create mode 100644 nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/rest/GenConfigController.java create mode 100644 nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/rest/GeneratorController.java create mode 100644 nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/service/GenConfigService.java create mode 100644 nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/service/GeneratorService.java create mode 100644 nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/service/impl/GenConfigServiceImpl.java create mode 100644 nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/service/impl/GeneratorServiceImpl.java create mode 100644 nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/utils/ColUtil.java create mode 100644 nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/utils/GenUtil.java create mode 100644 nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/wql/Generator.wql diff --git a/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/domain/ColumnInfo.java b/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/domain/ColumnInfo.java new file mode 100644 index 0000000..f53d6b1 --- /dev/null +++ b/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/domain/ColumnInfo.java @@ -0,0 +1,78 @@ +package org.nl.modules.common.generator.domain; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.nl.modules.common.generator.utils.GenUtil; + +import java.io.Serializable; + +/** + * @author: lyd + * @description: + * @Date: 2022/12/2 + */ +@Data +public class ColumnInfo implements Serializable { + + /** 防止精度丢失 */ + @JsonSerialize(using= ToStringSerializer.class) + private Long column_id; + + @ApiModelProperty(value = "表名") + private String table_name; + + @ApiModelProperty(value = "数据库字段名称") + private String column_name; + + @ApiModelProperty(value = "数据库字段类型") + private String column_type; + + @ApiModelProperty(value = "数据库字段键类型") + private String key_type; + + @ApiModelProperty(value = "字段额外的参数") + private String extra; + + @ApiModelProperty(value = "数据库字段描述") + private String remark; + + @ApiModelProperty(value = "是否必填") + private String not_null; + + @ApiModelProperty(value = "是否在列表显示") + private String list_show; + + @ApiModelProperty(value = "是否表单显示") + private String form_show; + + @ApiModelProperty(value = "表单类型") + private String form_type; + + @ApiModelProperty(value = "查询 1:模糊 2:精确") + private String query_type; + + @ApiModelProperty(value = "字典名称") + private String dict_name; + + @ApiModelProperty(value = "日期注解") + private String date_annotation; + + public ColumnInfo(String tableName, Long column_id, String columnName, Boolean notNull, String columnType, String remark, String keyType, String extra) { + this.table_name = tableName; + this.column_id = column_id; + this.column_name = columnName; + this.column_type = columnType; + this.key_type = keyType; + this.extra = extra; + this.not_null = notNull?"1":"0"; + if(GenUtil.PK.equalsIgnoreCase(keyType) && GenUtil.EXTRA.equalsIgnoreCase(extra)){ + this.not_null = "0"; + } + this.remark = remark; + this.list_show = "1"; + this.form_show = "1"; + } +} + diff --git a/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/domain/GenConfig.java b/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/domain/GenConfig.java new file mode 100644 index 0000000..ed720c5 --- /dev/null +++ b/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/domain/GenConfig.java @@ -0,0 +1,55 @@ +package org.nl.modules.common.generator.domain; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import java.io.Serializable; + +/** + * @author: lyd + * @description: code_gen_config + * @Date: 2022/12/2 + */ +@Data +public class GenConfig implements Serializable { + public GenConfig(String tableName) { + this.table_name = tableName; + } + /** 防止精度丢失 */ + @JsonSerialize(using= ToStringSerializer.class) + private Long config_id; + + @NotBlank + @ApiModelProperty(value = "表名") + private String table_name; + + @ApiModelProperty(value = "接口名称") + private String api_alias; + + @NotBlank + @ApiModelProperty(value = "包路径") + private String pack; + + @NotBlank + @ApiModelProperty(value = "模块名") + private String module_name; + + @NotBlank + @ApiModelProperty(value = "前端文件路径") + private String path; + + @ApiModelProperty(value = "前端文件路径") + private String api_path; + + @ApiModelProperty(value = "作者") + private String author; + + @ApiModelProperty(value = "表前缀") + private String prefix; + + @ApiModelProperty(value = "是否覆盖") + private Boolean cover = false; +} diff --git a/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/domain/vo/TableInfo.java b/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/domain/vo/TableInfo.java new file mode 100644 index 0000000..d28fb5b --- /dev/null +++ b/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/domain/vo/TableInfo.java @@ -0,0 +1,33 @@ +package org.nl.modules.common.generator.domain.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author: lyd + * @description: 表的数据信息 + * @Date: 2022/12/2 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class TableInfo { + + /** 表名称 */ + private Object tableName; + + /** 创建日期 */ + private Object createTime; + + /** 数据库引擎 */ + private Object engine; + + /** 编码集 */ + private Object coding; + + /** 备注 */ + private Object remark; + + +} diff --git a/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/rest/GenConfigController.java b/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/rest/GenConfigController.java new file mode 100644 index 0000000..a259648 --- /dev/null +++ b/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/rest/GenConfigController.java @@ -0,0 +1,37 @@ +package org.nl.modules.common.generator.rest; + +import com.alibaba.fastjson.JSONObject; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import org.nl.modules.common.generator.domain.GenConfig; +import org.nl.modules.common.generator.service.GenConfigService; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** + * @author: lyd + * @description: 代码生成器配置管理 + * @Date: 2022/12/2 + */ +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/genConfig") +@Api(tags = "系统:代码生成器配置管理") +public class GenConfigController { + private final GenConfigService genConfigService; + + @ApiOperation("查询") + @GetMapping(value = "/{tableName}") + public ResponseEntity query(@PathVariable String tableName){ + return new ResponseEntity<>(genConfigService.find(tableName), HttpStatus.OK); + } + + @ApiOperation("修改") + @PutMapping + public ResponseEntity update(@Validated @RequestBody JSONObject genConfig){ + return new ResponseEntity<>(genConfigService.update(genConfig),HttpStatus.OK); + } +} diff --git a/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/rest/GeneratorController.java b/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/rest/GeneratorController.java new file mode 100644 index 0000000..448e595 --- /dev/null +++ b/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/rest/GeneratorController.java @@ -0,0 +1,94 @@ +package org.nl.modules.common.generator.rest; + +import com.alibaba.fastjson.JSONArray; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import org.nl.modules.common.exception.BadRequestException; +import org.nl.modules.common.generator.domain.ColumnInfo; +import org.nl.modules.common.generator.service.GenConfigService; +import org.nl.modules.common.generator.service.GeneratorService; +import org.nl.modules.common.utils.PageUtil; +import org.nl.modules.system.service.dto.UserQueryCriteria; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * @author: lyd + * @description: 代码生成管理 + * @Date: 2022/12/2 + */ +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/generator") +@Api(tags = "系统:代码生成管理") +public class GeneratorController { + private final GeneratorService generatorService; + private final GenConfigService genConfigService; + + @Value("${generator.enabled}") + private Boolean generatorEnabled; + + @ApiOperation("查询数据库数据") + @GetMapping(value = "/tables/all") + public ResponseEntity queryTables(){ + return new ResponseEntity<>(generatorService.getTables(), HttpStatus.OK); + } + + @ApiOperation("查询数据库数据") + @GetMapping(value = "/tables") + public ResponseEntity queryTables(@RequestParam(defaultValue = "") String name, + Pageable pageable){ + return new ResponseEntity<>(generatorService.getTables(name,pageable), HttpStatus.OK); + } + + @ApiOperation("查询字段数据") + @GetMapping(value = "/columns") + public ResponseEntity queryColumns(@RequestParam String tableName){ + return new ResponseEntity<>(generatorService.getColumns(tableName), HttpStatus.OK); + } + + @ApiOperation("保存字段数据") + @PutMapping + public ResponseEntity save(@RequestBody JSONArray columnInfos){ + generatorService.save(columnInfos); + return new ResponseEntity<>(HttpStatus.OK); + } + + @ApiOperation("同步字段数据") + @PostMapping(value = "sync") + public ResponseEntity sync(@RequestBody List tables){ + for (String table : tables) { + generatorService.sync(generatorService.getColumns(table), generatorService.query(table)); + } + return new ResponseEntity<>(HttpStatus.OK); + } + + @ApiOperation("生成代码") + @PostMapping(value = "/{tableName}/{type}") + public ResponseEntity generator(@PathVariable String tableName, @PathVariable Integer type, HttpServletRequest request, HttpServletResponse response){ + if(!generatorEnabled && type == 0){ + throw new BadRequestException("此环境不允许生成代码,请选择预览或者下载查看!"); + } + switch (type){ + // 生成代码 + case 0: generatorService.generator(genConfigService.find(tableName), generatorService.getColumns(tableName)); + break; + // 预览 + case 1: return generatorService.preview(genConfigService.find(tableName), generatorService.getColumns(tableName)); + // 下载 + case 2: generatorService.download(genConfigService.find(tableName), generatorService.getColumns(tableName), request, response); + break; + default: throw new BadRequestException("没有这个选项"); + } + return new ResponseEntity<>(HttpStatus.OK); + } + +} diff --git a/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/service/GenConfigService.java b/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/service/GenConfigService.java new file mode 100644 index 0000000..a370fce --- /dev/null +++ b/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/service/GenConfigService.java @@ -0,0 +1,25 @@ +package org.nl.modules.common.generator.service; + +import com.alibaba.fastjson.JSONObject; +import org.nl.modules.common.generator.domain.GenConfig; + +/** + * @author: lyd + * @description: + * @Date: 2022/12/2 + */ +public interface GenConfigService { + /** + * 查询表配置 + * @param tableName 表名 + * @return 表配置 + */ + JSONObject find(String tableName); + + /** + * 更新表配置 + * @param genConfig 表配置 + * @return 表配置 + */ + JSONObject update(JSONObject genConfig); +} diff --git a/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/service/GeneratorService.java b/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/service/GeneratorService.java new file mode 100644 index 0000000..e1d8bae --- /dev/null +++ b/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/service/GeneratorService.java @@ -0,0 +1,116 @@ +package org.nl.modules.common.generator.service; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import org.nl.modules.common.generator.domain.ColumnInfo; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.scheduling.annotation.Async; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * @author: lyd + * @description: + * @Date: 2022/12/2 + */ +public interface GeneratorService { + + /** + * 查询数据库元数据 + * @param name 表名 + * @param pageable 分页参数 + * @return / + */ + Object getTables(String name, Pageable pageable); + + /** + * 得到数据表的元数据 + * @param name 表名 + * @return / + */ + JSONObject getColumns(String name); + + /** + * 同步表数据 + * @param columnInfos / content + * @param columnInfoList / + */ + @Async + void sync(JSONObject columnInfos, JSONArray columnInfoList); + + /** + * 保持数据 + * @param columnInfos / + */ +// void save(List columnInfos); + + /** + * 获取所有table + * @return / + */ + Object getTables(); + + /** + * 代码生成 + * @param genConfig 配置信息 + * @param columns 字段信息 + */ +// void generator(GenConfig genConfig, List columns); + + /** + * 预览 + * @param genConfig 配置信息 + * @param columns 字段信息 + * @return / + */ +// ResponseEntity preview(GenConfig genConfig, List columns); + + /** + * 打包下载 + * @param genConfig 配置信息 + * @param columns 字段信息 + * @param request / + * @param response / + */ +// void download(GenConfig genConfig, List columns, HttpServletRequest request, HttpServletResponse response); + + /** + * 查询数据库的表字段数据数据 + * @param table / + * @return / + */ + JSONArray query(String table); + + /** + * 保存字段 + * @param columnInfos + */ + void save(JSONArray columnInfos); + + /** + * 预览 + * @param genConfig 配置信息 + * @param columns 字段信息 数组存在content + * @return / + */ + ResponseEntity preview(JSONObject genConfig, JSONObject columns); + + /** + * 代码生成 + * @param genConfig 配置信息 + * @param columns 字段信息 数组存在content + */ + void generator(JSONObject genConfig, JSONObject columns); + + /** + * 打包下载 + * @param genConfig 配置信息 + * @param columns 字段信息 数组存在content + * @param request / + * @param response / + */ + void download(JSONObject genConfig, JSONObject columns, HttpServletRequest request, HttpServletResponse response); +} diff --git a/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/service/impl/GenConfigServiceImpl.java b/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/service/impl/GenConfigServiceImpl.java new file mode 100644 index 0000000..2a51ee6 --- /dev/null +++ b/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/service/impl/GenConfigServiceImpl.java @@ -0,0 +1,55 @@ +package org.nl.modules.common.generator.service.impl; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import lombok.RequiredArgsConstructor; +import org.nl.modules.common.generator.domain.GenConfig; +import org.nl.modules.common.generator.service.GenConfigService; +import org.nl.modules.wql.core.bean.WQLObject; +import org.springframework.stereotype.Service; + +/** + * @author: lyd + * @description: + * @Date: 2022/12/2 + */ +@Service +@RequiredArgsConstructor +public class GenConfigServiceImpl implements GenConfigService { + /** + * 查询表配置 + * + * @param tableName 表名 + * @return 表配置 + */ + @Override + public JSONObject find(String tableName) { + WQLObject genTab = WQLObject.getWQLObject("code_gen_config"); + JSONObject jsonObject = genTab.query("table_name = '" + tableName + "'").uniqueResult(0); + if (ObjectUtil.isEmpty(jsonObject)) + return JSONObject.parseObject(JSONObject.toJSONString(new GenConfig(tableName))); + return jsonObject; + } + + /** + * 更新表配置 + * + * @param genConfig 表名 + 表配置 + * @return 表配置 + */ + @Override + public JSONObject update(JSONObject genConfig) { + WQLObject genTab = WQLObject.getWQLObject("code_gen_config"); + if (ObjectUtil.isEmpty(genConfig.getString("config_id"))) { + // 创建 + genConfig.put("config_id", IdUtil.getSnowflake(1, 1).nextId()); + genTab.insert(genConfig); + } else { + // 更新 + genTab.update(genConfig); + } + return genConfig; + } +} diff --git a/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/service/impl/GeneratorServiceImpl.java b/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/service/impl/GeneratorServiceImpl.java new file mode 100644 index 0000000..7989ac5 --- /dev/null +++ b/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/service/impl/GeneratorServiceImpl.java @@ -0,0 +1,242 @@ +package org.nl.modules.common.generator.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.ZipUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import lombok.RequiredArgsConstructor; +import org.nl.modules.common.exception.BadRequestException; +import org.nl.modules.common.generator.domain.ColumnInfo; +import org.nl.modules.common.generator.service.GeneratorService; +import org.nl.modules.common.generator.utils.GenUtil; +import org.nl.modules.common.utils.FileUtil; +import org.nl.modules.wql.WQL; +import org.nl.modules.wql.core.bean.WQLObject; +import org.nl.modules.wql.util.WqlUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; + +/** + * @author: lyd + * @description: + * @Date: 2022/12/2 + */ +@Service +@RequiredArgsConstructor +public class GeneratorServiceImpl implements GeneratorService { + private static final Logger log = LoggerFactory.getLogger(GeneratorServiceImpl.class); + + @PersistenceContext + private EntityManager em; + /** + * 查询数据库元数据 + * + * @param name 表名 + * @param pageable 分页参数 + * @return / + */ + @Override + public Object getTables(String name, Pageable pageable) { + JSONObject map = new JSONObject(); + map.put("flag", "1"); + map.put("table_name", name); + JSONObject json = WQL.getWO("Generator").addParamMap(map).pageQuery(WqlUtil.getHttpContext(pageable), "create_time desc"); + return json; + } + + /** + * 得到数据表的元数据 + * + * @param name 表名 + * @return / + */ + @Override + public JSONObject getColumns(String name) { + WQLObject colTab = WQLObject.getWQLObject("code_column_config"); + JSONObject content = new JSONObject(); + JSONArray array = colTab.query("table_name = '" + name + "'").getResultJSONArray(0); + if (ObjectUtil.isEmpty(array)) { + array = query(name); + // 保存 + for (int i = 0; i < array.size(); i++) { + JSONObject jsonObject = array.getJSONObject(i); + colTab.insert(jsonObject); + } + } + content.put("content", array); + return content; + } + + /** + * 同步表数据 + * + * @param content / + * @param columnInfoList / + */ + @Override + public void sync(JSONObject content, JSONArray columnInfoList) { + WQLObject colTab = WQLObject.getWQLObject("code_column_config"); + JSONArray columnInfos = content.getJSONArray("content"); + // 第一种情况,数据库类字段改变或者新增字段 + for (int i = 0; i < columnInfoList.size(); i++) { + JSONObject columnInfoObj = columnInfoList.getJSONObject(i); + JSONArray columns = new JSONArray(); + for (int j = 0; j < columnInfos.size(); j++) { + JSONObject columnInfosObj = columnInfos.getJSONObject(j); + if (columnInfoObj.getString("column_name").equals(columnInfosObj.getString("column_name"))) { + columns.add(columnInfosObj); + } + } + // 如果能找到,就修改部分可能被字段 + if (CollectionUtil.isNotEmpty(columns)) { + JSONObject column = columns.getJSONObject(0); + column.put("column_type", columnInfoObj.getString("column_type")); + column.put("extra", columnInfoObj.getString("extra")); + column.put("key_type", columnInfoObj.getString("key_type")); + if (StrUtil.isEmpty(column.getString("remark"))) { + column.put("remark", columnInfoObj.getString("remark")); + } + colTab.update(column); + } else { + // 如果找不到,则保存新字段信息 + colTab.insert(columnInfoObj); + } + } + // 第二种情况,数据库字段删除了 + for (int k = 0; k < columnInfos.size(); k++) { + JSONArray columns = new JSONArray(); + JSONObject columnInfo = columnInfos.getJSONObject(k); + for (int l = 0; l < columnInfoList.size(); l++) { + JSONObject c = columnInfoList.getJSONObject(l); + if (columnInfo.getString("column_name").equals(c.getString("column_name"))) { + columns.add(c); + } + } + if (ObjectUtil.isEmpty(columnInfo)) colTab.delete(columnInfo); + } + } + + @Override + public JSONArray query(String tableName) { + JSONObject map = new JSONObject(); + map.put("flag", "2"); + map.put("table_name", tableName); + JSONArray generator = WQL.getWO("Generator").addParamMap(map).process().getResultJSONArray(0); + JSONArray columnInfos = new JSONArray(); + for (int i = 0; i < generator.size(); i++) { + JSONObject obj = generator.getJSONObject(i); + columnInfos.add( + JSONObject.parseObject(JSON.toJSONString(new ColumnInfo( + tableName, + IdUtil.getSnowflake(1,1).nextId(), + obj.getString("column_name"), + "NO".equals(obj.getString("is_nullable")), + obj.getString("data_type"), + ObjectUtil.isNotEmpty(obj.getString("column_comment")) ? obj.getString("column_comment") : null, + ObjectUtil.isNotEmpty(obj.getString("column_key")) ? obj.getString("column_key") : null, + ObjectUtil.isNotEmpty(obj.getString("extra")) ? obj.getString("extra") : null + ))) + ); + } + return columnInfos; + } + + /** + * 保存字段 + * + * @param columnInfos + */ + @Override + public void save(JSONArray columnInfos) { + WQLObject colTab = WQLObject.getWQLObject("code_column_config"); + for (int i = 0; i < columnInfos.size(); i++) { + JSONObject object = columnInfos.getJSONObject(i); + colTab.update(object); + } + } + + /** + * 预览 + * + * @param genConfig 配置信息 + * @param columns 字段信息 数组存在content + * @return / + */ + @Override + public ResponseEntity preview(JSONObject genConfig, JSONObject columns) { + JSONArray column = columns.getJSONArray("content"); + if (ObjectUtil.isEmpty(genConfig.getString("config_id"))) throw new BadRequestException("请先配置生成器"); + JSONArray genList = GenUtil.preview(column, genConfig); + return new ResponseEntity<>(genList, HttpStatus.OK); + } + + /** + * 代码生成 + * + * @param genConfig 配置信息 + * @param columns 字段信息 数组存在content + */ + @Override + public void generator(JSONObject genConfig, JSONObject columns) { + JSONArray column = columns.getJSONArray("content"); + if (ObjectUtil.isEmpty(genConfig.getString("config_id"))) throw new BadRequestException("请先配置生成器"); + try { + GenUtil.generatorCode(column, genConfig); + } catch (IOException e) { + log.error(e.getMessage(), e); + throw new BadRequestException("生成失败,请手动处理已生成的文件"); + } + } + + /** + * 打包下载 + * + * @param genConfig 配置信息 + * @param columns 字段信息 数组存在content + * @param request / + * @param response / + */ + @Override + public void download(JSONObject genConfig, JSONObject columns, HttpServletRequest request, HttpServletResponse response) { + JSONArray column = columns.getJSONArray("content"); + if (ObjectUtil.isEmpty(genConfig.getString("config_id"))) throw new BadRequestException("请先配置生成器"); + try { + File file = new File(GenUtil.download(column, genConfig)); + String zipPath = file.getPath() + ".zip"; + ZipUtil.zip(file.getPath(), zipPath); + FileUtil.downloadFile(request, response, new File(zipPath), true); + } catch (IOException e) { + throw new BadRequestException("下载失败"); + } + } + + /** + * 获取所有table + * + * @return / + */ + @Override + public Object getTables() { + // 使用预编译防止sql注入 + JSONObject map = new JSONObject(); + map.put("flag", "1"); + JSONArray generator = WQL.getWO("Generator").addParamMap(map).process().getResultJSONArray(0); + return generator; + } + +} diff --git a/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/utils/ColUtil.java b/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/utils/ColUtil.java new file mode 100644 index 0000000..7cd0cb0 --- /dev/null +++ b/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/utils/ColUtil.java @@ -0,0 +1,40 @@ +package org.nl.modules.common.generator.utils; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author: lyd + * @description: sql字段转java + * @Date: 2022/12/3 + */ +public class ColUtil { + private static final Logger log = LoggerFactory.getLogger(ColUtil.class); + + /** + * 转换mysql数据类型为java数据类型 + * + * @param type 数据库字段类型 + * @return String + */ + static String cloToJava(String type) { + Configuration config = getConfig(); + assert config != null; + return config.getString(type, "unknowType"); + } + + /** + * 获取配置信息 + */ + public static PropertiesConfiguration getConfig() { + try { + return new PropertiesConfiguration("generator.properties"); + } catch (ConfigurationException e) { + log.error(e.getMessage(), e); + } + return null; + } +} diff --git a/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/utils/GenUtil.java b/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/utils/GenUtil.java new file mode 100644 index 0000000..fa49734 --- /dev/null +++ b/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/utils/GenUtil.java @@ -0,0 +1,420 @@ +package org.nl.modules.common.generator.utils; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.template.*; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.nl.modules.common.utils.FileUtil; +import org.nl.modules.common.utils.StringUtils; +import org.springframework.util.ObjectUtils; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.nl.modules.common.utils.FileUtil.SYS_TEM_DIR; + +/** + * @author: lyd + * @description: 代码生成 + * @Date: 2022/12/2 + */ +@Slf4j +@SuppressWarnings({"unchecked", "all"}) +public class GenUtil { + private static final String TIMESTAMP = "Timestamp"; + + private static final String Date = "Date"; + + private static final String BIGDECIMAL = "BigDecimal"; + + public static final String PK = "PRI"; + + public static final String EXTRA = "auto_increment"; + + /** + * 获取后端代码模板名称 + * + * @return List + */ + private static List getAdminTemplateNames() { + List templateNames = new ArrayList<>(); + // templateNames.add("Entity"); + templateNames.add("Dto"); + // templateNames.add("Mapper"); + templateNames.add("Controller"); + //templateNames.add("QueryCriteria"); + templateNames.add("Service"); + templateNames.add("ServiceImpl"); + // templateNames.add("Repository"); + return templateNames; + } + + /** + * 获取前端代码模板名称 + * + * @return List + */ + private static List getFrontTemplateNames() { + List templateNames = new ArrayList<>(); + templateNames.add("index"); + templateNames.add("api"); + return templateNames; + } + + public static JSONArray preview(JSONArray columns, JSONObject genConfig) { + JSONObject genMap = getGenMap(columns, genConfig); + JSONArray genList = new JSONArray(); + // 获取后端模版 + List templates = getAdminTemplateNames(); + TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH)); + for (String templateName : templates) { + Map map = new HashMap<>(1); + Template template = engine.getTemplate("generator/admin/" + templateName + ".ftl"); + map.put("content", template.render(genMap)); + map.put("name", templateName); + genList.add(map); + } + // 获取前端模版 + templates = getFrontTemplateNames(); + for (String templateName : templates) { + Map map = new HashMap<>(1); + Template template = engine.getTemplate("generator/front/" + templateName + ".ftl"); + map.put(templateName, template.render(genMap)); + map.put("content", template.render(genMap)); + map.put("name", templateName); + genList.add(map); + } + return genList; + } + + public static void generatorCode(JSONArray columns, JSONObject genConfig) throws IOException { + JSONObject genMap = getGenMap(columns, genConfig); + TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH)); + // 生成后端代码 + List templates = getAdminTemplateNames(); + for (String templateName : templates) { + Template template = engine.getTemplate("generator/admin/" + templateName + ".ftl"); + String filePath = getAdminFilePath(templateName, genConfig, genMap.get("className").toString(), System.getProperty("user.dir")); + + assert filePath != null; + File file = new File(filePath); + + // 如果非覆盖生成 + if (!genConfig.getString("cover").equals("1") && FileUtil.exist(file)) { + continue; + } + // 生成代码 + genFile(file, template, genMap); + } + // 生成前端代码 + templates = getFrontTemplateNames(); + for (String templateName : templates) { + Template template = engine.getTemplate("generator/front/" + templateName + ".ftl"); + // api 和 vue文件都放一起,所以这里不需要传api_path + String filePath = getFrontFilePath(templateName, genConfig.getString("path"), genConfig.getString("path"), genMap.get("changeClassName").toString()); + + assert filePath != null; + File file = new File(filePath); + + // 如果非覆盖生成 + if (!genConfig.getString("cover").equals("1") && FileUtil.exist(file)) { + continue; + } + // 生成代码 + genFile(file, template, genMap); + } + } + + // 下载 + public static String download(JSONArray columns, JSONObject genConfig) throws IOException { + // 拼接的路径:/tmpnladmin-gen-temp/,这个路径在Linux下需要root用户才有权限创建,非root用户会权限错误而失败,更改为: /tmp/nladmin-gen-temp/ + // String tempPath =SYS_TEM_DIR + "nladmin-gen-temp" + File.separator + genConfig.getTableName() + File.separator; + String tempPath = SYS_TEM_DIR + "nladmin-gen-temp" + File.separator + genConfig.getString("table_name") + File.separator; + JSONObject genMap = getGenMap(columns, genConfig); + TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH)); + // 生成后端代码 + List templates = getAdminTemplateNames(); + for (String templateName : templates) { + Template template = engine.getTemplate("generator/admin/" + templateName + ".ftl"); + String filePath = getAdminFilePath(templateName, genConfig, genMap.get("className").toString(), tempPath + "eladmin" + File.separator); + assert filePath != null; + File file = new File(filePath); + // 如果非覆盖生成 + if (!genConfig.getString("cover").equals("1") && FileUtil.exist(file)) { + continue; + } + // 生成代码 + genFile(file, template, genMap); + } + // 生成前端代码 + templates = getFrontTemplateNames(); + for (String templateName : templates) { + Template template = engine.getTemplate("generator/front/" + templateName + ".ftl"); + // api 和 vue文件都放一起,所以这里不需要传api_path + String filePath = getFrontFilePath(templateName, genConfig.getString("path"), genConfig.getString("path"), genMap.get("changeClassName").toString()); + + assert filePath != null; + File file = new File(filePath); + + // 如果非覆盖生成 + if (!genConfig.getString("cover").equals("1") && FileUtil.exist(file)) { + continue; + } + // 生成代码 + genFile(file, template, genMap); + } + return tempPath; + } + + /** + * 定义后端文件路径以及名称 + */ + private static String getAdminFilePath(String templateName, JSONObject genConfig, String className, String rootPath) { + // projectPath: rootPath(项目绝对路径到项目名)+File.separator(\)+genConfig.getString("module_name")(前端输入的模块名) + // eg: D:\code\work\nl-sso-server\nladmin-system\nlsso-server + String projectPath = rootPath + File.separator + genConfig.getString("module_name"); + // packagePath: 包路径 + // eg:D:\code\work\nl-sso-server\nladmin-system\nlsso-server\src\main\java\输入的包名 + String packagePath = projectPath + File.separator + "src" + File.separator + "main" + File.separator + "java" + File.separator; + if (!ObjectUtils.isEmpty(genConfig.getString("pack"))) { // 将点转成\ + packagePath += genConfig.getString("pack").replace(".", File.separator) + File.separator; + } + if ("Entity".equals(templateName)) { + return packagePath + "domain" + File.separator + className + ".java"; + } + + if ("Controller".equals(templateName)) { + return packagePath + "rest" + File.separator + className + "Controller.java"; + } + + if ("Service".equals(templateName)) { + return packagePath + "service" + File.separator + className + "Service.java"; + } + + if ("ServiceImpl".equals(templateName)) { + return packagePath + "service" + File.separator + "impl" + File.separator + className + "ServiceImpl.java"; + } + + if ("Dto".equals(templateName)) { + return packagePath + "service" + File.separator + "dto" + File.separator + className + "Dto.java"; + } + + if ("QueryCriteria".equals(templateName)) { + return packagePath + "service" + File.separator + "dto" + File.separator + className + "QueryCriteria.java"; + } + + if ("Mapper".equals(templateName)) { + return packagePath + "service" + File.separator + "mapstruct" + File.separator + className + "Mapper.java"; + } + + if ("Repository".equals(templateName)) { + return packagePath + "repository" + File.separator + className + "Repository.java"; + } + return null; + } + /** + * 定义前端文件路径以及名称 + */ + private static String getFrontFilePath(String templateName, String apiPath, String path, String apiName) { + if ("api".equals(templateName)) { + return apiPath + File.separator + apiName + ".js"; + } + + if ("index".equals(templateName)) { + return path + File.separator + "index.vue"; + } + + return null; + } + + + // 获取模版数据 + private static JSONObject getGenMap(JSONArray columnInfos, JSONObject genConfig) { + // 存储模版字段数据 + JSONObject genMap = new JSONObject(16); + // 接口别名 + genMap.put("apiAlias", genConfig.getString("api_alias")); + // 包名称 + genMap.put("package", genConfig.getString("pack")); + // 模块名称 + genMap.put("moduleName", genConfig.getString("module_name")); + // 作者 + genMap.put("author", genConfig.getString("author")); + // 创建日期 + genMap.put("date", LocalDate.now().toString()); + // 表名 + genMap.put("tableName", genConfig.getString("table_name")); + // 大写开头的类名 + String className = StringUtils.toCapitalizeCamelCase(genConfig.getString("table_name")); + // 小写开头的类名 + String changeClassName = StringUtils.toCamelCase(genConfig.getString("table_name")); + // 判断是否去除表前缀 + if (StrUtil.isNotEmpty(genConfig.getString("prefix"))) { + className = StringUtils.toCapitalizeCamelCase(StrUtil.removePrefix(genConfig.getString("table_name"), genConfig.getString("prefix"))); + changeClassName = StringUtils.toCamelCase(StrUtil.removePrefix(genConfig.getString("table_name"), genConfig.getString("prefix"))); + } + // 保存类名 + genMap.put("className", className); + // 保存小写开头的类名 + genMap.put("changeClassName", changeClassName); + // 存在 Timestamp 字段 + genMap.put("hasTimestamp", false); + // 查询类中存在 Timestamp 字段 + genMap.put("queryHasTimestamp", false); + // 存在 BigDecimal 字段 + genMap.put("hasBigDecimal", false); + // 查询类中存在 BigDecimal 字段 + genMap.put("queryHasBigDecimal", false); + // 是否需要创建查询 + genMap.put("hasQuery", false); + // 自增主键 + genMap.put("auto", false); + // 存在字典 + genMap.put("hasDict", false); + // 存在日期注解 + genMap.put("hasDateAnnotation", false); + // 日期包 + genMap.put("hasDate", false); + // 保存字段信息 + List> columns = new ArrayList<>(); + // 保存查询字段的信息 + List> queryColumns = new ArrayList<>(); + // 存储字典信息 + List dicts = new ArrayList<>(); + // 存储 between 信息 + List> betweens = new ArrayList<>(); + // 存储不为空的字段信息 + List> isNotNullColumns = new ArrayList<>(); + + for (int i = 0; i < columnInfos.size(); i++) { + JSONObject column = columnInfos.getJSONObject(i); + JSONObject listMap = new JSONObject(16); + // 字段描述 + listMap.put("remark", column.getString("remark")); + // 字段类型 + listMap.put("columnKey", column.getString("key_type")); + // 主键类型 + String colType = ColUtil.cloToJava(column.getString("column_type")); + // 小写开头的字段名 + //String changeColumnName = StringUtils.toCamelCase(column.getColumnName()); + String changeColumnName = column.getString("column_name"); + // 大写开头的字段名 + String capitalColumnName = StringUtils.toCapitalizeCamelCase(column.getString("column_name")); + if (PK.equals(column.getString("key_type"))) { + // 存储主键类型 + genMap.put("pkColumnType", colType); + // 存储小写开头的字段名 + genMap.put("pkChangeColName", changeColumnName); + // 存储大写开头的字段名 + genMap.put("pkCapitalColName", capitalColumnName); + } + // 是否存在 Timestamp 类型的字段 + if (TIMESTAMP.equals(colType)) { + genMap.put("hasTimestamp", true); + } + // 是否存在 Timestamp 类型的字段 + if (Date.equals(colType)) { + genMap.put("hasDate", true); + } + // 是否存在 BigDecimal 类型的字段 + if (BIGDECIMAL.equals(colType)) { + genMap.put("hasBigDecimal", true); + } + // 主键是否自增 + if (EXTRA.equals(column.getString("extra"))) { + genMap.put("auto", true); + } + // 主键存在字典 + if (StrUtil.isNotEmpty(column.getString("dict_name"))) { + genMap.put("hasDict", true); + dicts.add(column.getString("dict_name")); + } + + // 存储字段类型 + listMap.put("columnType", colType); + // 存储字原始段名称 + listMap.put("columnName", column.getString("column_name")); + // 不为空 + listMap.put("istNotNull", (column.getString("not_null").equals("1"))?true:false); + // 字段列表显示 + listMap.put("columnShow", (column.getString("list_show").equals("1"))?true:false); + // 表单显示 + listMap.put("formShow", (column.getString("form_show").equals("1"))?true:false); + // 表单组件类型 + listMap.put("formType", StrUtil.isNotEmpty(column.getString("form_type")) ? column.getString("form_type") : "Input"); + // 小写开头的字段名称 + listMap.put("changeColumnName", changeColumnName); + //大写开头的字段名称 + listMap.put("capitalColumnName", capitalColumnName); + // 字典名称 + listMap.put("dictName", column.getString("dict_name")); + // 日期注解 + listMap.put("dateAnnotation", column.getString("date_annotation")); + if (StrUtil.isNotEmpty(column.getString("date_annotation"))) { + genMap.put("hasDateAnnotation", true); + } + // 添加非空字段信息 + if (column.getString("not_null").equals("1")) { + isNotNullColumns.add(listMap); + } + // 判断是否有查询,如有则把查询的字段set进columnQuery + if (!StrUtil.isEmpty(column.getString("query_type"))) { + // 查询类型 + listMap.put("queryType", column.getString("query_type")); + // 是否存在查询 + genMap.put("hasQuery", true); + if (TIMESTAMP.equals(colType)) { + // 查询中存储 Timestamp 类型 + genMap.put("queryHasTimestamp", true); + } + if (BIGDECIMAL.equals(colType)) { + // 查询中存储 BigDecimal 类型 + genMap.put("queryHasBigDecimal", true); + } + if ("between".equalsIgnoreCase(column.getString("query_type"))) { + betweens.add(listMap); + } else { + // 添加到查询列表中 + queryColumns.add(listMap); + } + } + // 添加到字段列表中 + columns.add(listMap); + } + // 保存字段列表 + genMap.put("columns", columns); + // 保存查询列表 + genMap.put("queryColumns", queryColumns); + // 保存字段列表 + genMap.put("dicts", dicts); + // 保存查询列表 + genMap.put("betweens", betweens); + // 保存非空字段信息 + genMap.put("isNotNullColumns", isNotNullColumns); + return genMap; + } + + // 生成文件 + private static void genFile(File file, Template template, Map map) throws IOException { + // 生成目标文件 + Writer writer = null; + try { + FileUtil.touch(file); + writer = new FileWriter(file); + template.render(map, writer); + } catch (TemplateException | IOException e) { + throw new RuntimeException(e); + } finally { + assert writer != null; + writer.close(); + } + } +} diff --git a/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/wql/Generator.wql b/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/wql/Generator.wql new file mode 100644 index 0000000..797916c --- /dev/null +++ b/nladmin-system/nlsso-server/src/main/java/org/nl/modules/common/generator/wql/Generator.wql @@ -0,0 +1,82 @@ +[交易说明] + 交易名: 任务分页查询 + 所属模块: + 功能简述: + 版权所有: + 表引用: + 版本经历: + +[数据库] + --指定数据库,为空采用默认值,默认为db.properties中列出的第一个库 + +[IO定义] + ################################################# + ## 表字段对应输入参数 + ################################################# + 输入.flag TYPEAS s_string + 输入.table_name TYPEAS s_string + +[临时表] + --这边列出来的临时表就会在运行期动态创建 + +[临时变量] + --所有中间过程变量均可在此处定义 + +[业务过程] + + ########################################## + # 1、输入输出检查 # + ########################################## + + + ########################################## + # 2、主过程前处理 # + ########################################## + + + ########################################## + # 3、业务主过程 # + ########################################## + + IF 输入.flag = "1" + PAGEQUERY + SELECT + table_name, + DATE_FORMAT(now(),"%Y-%m-%d %T") create_time, + ENGINE, + table_collation as coding, + table_comment as remark + FROM + information_schema.TABLES + WHERE + table_schema = (SELECT DATABASE()) + OPTION 输入.table_name <> "" + table_name like "%" 输入.table_name "%" + ENDOPTION + ENDSELECT + ENDPAGEQUERY + ENDIF + + IF 输入.flag = "2" + PAGEQUERY + SELECT + column_name, + is_nullable, + data_type, + column_comment, + column_key, + extra + FROM + information_schema.COLUMNS + WHERE + table_schema = ( + SELECT DATABASE + ()) + OPTION 输入.table_name <> "" + table_name = 输入.table_name + ENDOPTION + ORDER BY + ordinal_position + ENDSELECT + ENDPAGEQUERY + ENDIF diff --git a/nladmin-system/nlsso-server/src/main/java/org/nl/modules/system/wql/sys.xls b/nladmin-system/nlsso-server/src/main/java/org/nl/modules/system/wql/sys.xls index 14f01c110f8b74c5aa8a87aaa4567d4b07b5af21..9c7e6d37bb832f7a9b5f729ce53cd61a19a2e1b4 100644 GIT binary patch delta 51622 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 DuIxt+ delta 47157 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 +import java.sql.Timestamp; + <#if hasDate> - import java.util.Date; +import java.util.Date; <#if hasBigDecimal> - import java.math.BigDecimal; +import java.math.BigDecimal; import java.io.Serializable; <#if !auto && pkColumnType = 'Long'> - import com.fasterxml.jackson.databind.annotation.JsonSerialize; - import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; /** @@ -21,18 +24,18 @@ import java.io.Serializable; @Data public class ${className}Dto implements Serializable { <#if columns??> - <#list columns as column> +<#list columns as column> - <#if column.remark != ''> - /** ${column.remark} */ - - <#if column.columnKey = 'PRI'> - <#if !auto && pkColumnType = 'Long'> - /** 防止精度丢失 */ - @JsonSerialize(using= ToStringSerializer.class) - - - private ${column.columnType} ${column.changeColumnName}; - + <#if column.remark != ''> + /** ${column.remark} */ + + <#if column.columnKey = 'PRI'> + <#if !auto && pkColumnType = 'Long'> + /** 防止精度丢失 */ + @JsonSerialize(using= ToStringSerializer.class) + + + private ${column.columnType} ${column.changeColumnName}; + } diff --git a/nladmin-system/nlsso-server/src/main/resources/template/generator/admin/Service.ftl b/nladmin-system/nlsso-server/src/main/resources/template/generator/admin/Service.ftl index bb49bfd..b7c7785 100644 --- a/nladmin-system/nlsso-server/src/main/resources/template/generator/admin/Service.ftl +++ b/nladmin-system/nlsso-server/src/main/resources/template/generator/admin/Service.ftl @@ -1,6 +1,6 @@ - package ${package}.service; +import ${package}.service.dto.${className}Dto; import org.springframework.data.domain.Pageable; import java.util.Map; import java.util.List; @@ -14,51 +14,51 @@ import javax.servlet.http.HttpServletResponse; **/ public interface ${className}Service { -/** -* 查询数据分页 -* @param whereJson 条件 -* @param page 分页参数 -* @return Map -*/ -Map queryAll(Map whereJson, Pageable page); + /** + * 查询数据分页 + * @param whereJson 条件 + * @param page 分页参数 + * @return Map + */ + Map queryAll(Map whereJson, Pageable page); -/** -* 查询所有数据不分页 -* @param whereJson 条件参数 -* @return List<${className}Dto> + /** + * 查询所有数据不分页 + * @param whereJson 条件参数 + * @return List<${className}Dto> */ List<${className}Dto> queryAll(Map whereJson); - /** - * 根据ID查询 - * @param ${pkChangeColName} ID - * @return ${className} - */ - ${className}Dto findById(${pkColumnType} ${pkChangeColName}); + /** + * 根据ID查询 + * @param ${pkChangeColName} ID + * @return ${className} + */ + ${className}Dto findById(${pkColumnType} ${pkChangeColName}); - /** - * 根据编码查询 - * @param code code - * @return ${className} - */ - ${className}Dto findByCode(String code); + /** + * 根据编码查询 + * @param code code + * @return ${className} + */ + ${className}Dto findByCode(String code); - /** - * 创建 - * @param dto / - */ - void create(${className}Dto dto); + /** + * 创建 + * @param dto / + */ + void create(${className}Dto dto); - /** - * 编辑 - * @param dto / - */ - void update(${className}Dto dto); + /** + * 编辑 + * @param dto / + */ + void update(${className}Dto dto); - /** - * 多选删除 - * @param ids / - */ - void deleteAll(${pkColumnType}[] ids); - } + /** + * 多选删除 + * @param ids / + */ + void deleteAll(${pkColumnType}[] ids); +} diff --git a/nladmin-system/nlsso-server/src/main/resources/template/generator/admin/ServiceImpl.ftl b/nladmin-system/nlsso-server/src/main/resources/template/generator/admin/ServiceImpl.ftl index 6d4cfc7..49869f0 100644 --- a/nladmin-system/nlsso-server/src/main/resources/template/generator/admin/ServiceImpl.ftl +++ b/nladmin-system/nlsso-server/src/main/resources/template/generator/admin/ServiceImpl.ftl @@ -1,9 +1,12 @@ package ${package}.service.impl; - +import ${package}.service.${className}Service; +import ${package}.service.dto.${className}Dto; +import com.alibaba.fastjson.JSON; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.nl.modules.common.exception.BadRequestException; import org.springframework.transaction.annotation.Transactional; import org.springframework.data.domain.Pageable; @@ -31,98 +34,98 @@ import cn.hutool.core.util.ObjectUtil; @Slf4j public class ${className}ServiceImpl implements ${className}Service { -@Override -public Map queryAll(Map whereJson, Pageable page){ -WQLObject wo = WQLObject.getWQLObject("${tableName}"); -ResultBean rb = wo.pagequery(WqlUtil.getHttpContext(page), "1=1", "update_time desc"); -final JSONObject json = rb.pageResult(); -return json; -} + @Override + public Map queryAll(Map whereJson, Pageable page){ + WQLObject wo = WQLObject.getWQLObject("${tableName}"); + ResultBean rb = wo.pagequery(WqlUtil.getHttpContext(page), "1=1", "update_time desc"); + final JSONObject json = rb.pageResult(); + return json; + } -@Override -public List<${className}Dto> queryAll(Map whereJson){ - WQLObject wo = WQLObject.getWQLObject("${tableName}"); - JSONArray arr = wo.query().getResultJSONArray(0); - if (ObjectUtil.isNotEmpty(arr)) return arr.toJavaList(${className}Dto.class); - return null; + @Override + public List<${className}Dto> queryAll(Map whereJson){ + WQLObject wo = WQLObject.getWQLObject("${tableName}"); + JSONArray arr = wo.query().getResultJSONArray(0); + if (ObjectUtil.isNotEmpty(arr)) return arr.toJavaList(${className}Dto.class); + return null; } @Override public ${className}Dto findById(${pkColumnType} ${pkChangeColName}) { - WQLObject wo = WQLObject.getWQLObject("${tableName}"); - JSONObject json = wo.query("${pkChangeColName} = '" + ${pkChangeColName} + "'").uniqueResult(0); - if (ObjectUtil.isNotEmpty(json)){ - return json.toJavaObject( ${className}Dto.class); - } - return null; + WQLObject wo = WQLObject.getWQLObject("${tableName}"); + JSONObject json = wo.query("${pkChangeColName} = '" + ${pkChangeColName} + "'").uniqueResult(0); + if (ObjectUtil.isNotEmpty(json)){ + return json.toJavaObject( ${className}Dto.class); + } + return null; } @Override public ${className}Dto findByCode(String code) { - WQLObject wo = WQLObject.getWQLObject("${tableName}"); - JSONObject json = wo.query("code ='" + code + "'").uniqueResult(0); - if (ObjectUtil.isNotEmpty(json)){ - return json.toJavaObject( ${className}Dto.class); - } - return null; + WQLObject wo = WQLObject.getWQLObject("${tableName}"); + JSONObject json = wo.query("code ='" + code + "'").uniqueResult(0); + if (ObjectUtil.isNotEmpty(json)){ + return json.toJavaObject( ${className}Dto.class); + } + return null; } @Override @Transactional(rollbackFor = Exception.class) public void create(${className}Dto dto) { - Long currentUserId = SecurityUtils.getCurrentUserId(); - String nickName = SecurityUtils.getCurrentNickName(); - String now = DateUtil.now(); + Long currentUserId = SecurityUtils.getCurrentUserId(); + String nickName = SecurityUtils.getCurrentNickName(); + String now = DateUtil.now(); - dto.set${pkChangeColName ? cap_first }(IdUtil.getSnowflake(1, 1).nextId()); - dto.setCreate_id(currentUserId); - dto.setCreate_name(nickName); - dto.setUpdate_optid(currentUserId); - dto.setUpdate_optname(nickName); - dto.setUpdate_time(now); - dto.setCreate_time(now); + dto.set${pkChangeColName ? cap_first }(IdUtil.getSnowflake(1, 1).nextId()); + dto.setCreate_id(currentUserId); + dto.setCreate_name(nickName); + dto.setUpdate_optid(currentUserId); + dto.setUpdate_optname(nickName); + dto.setUpdate_time(now); + dto.setCreate_time(now); - WQLObject wo = WQLObject.getWQLObject("${tableName}"); - JSONObject json = JSONObject.parseObject(JSON.toJSONString(dto)); - wo.insert(json); + WQLObject wo = WQLObject.getWQLObject("${tableName}"); + JSONObject json = JSONObject.parseObject(JSON.toJSONString(dto)); + wo.insert(json); } @Override @Transactional(rollbackFor = Exception.class) public void update(${className}Dto dto) { - ${className}Dto entity = this.findById(dto.get${pkChangeColName ? cap_first }()); - if (entity == null) throw new BadRequestException("被删除或无权限,操作失败!"); + ${className}Dto entity = this.findById(dto.get${pkChangeColName ? cap_first }()); + if (entity == null) throw new BadRequestException("被删除或无权限,操作失败!"); - Long currentUserId = SecurityUtils.getCurrentUserId(); - String nickName = SecurityUtils.getCurrentNickName(); + Long currentUserId = SecurityUtils.getCurrentUserId(); + String nickName = SecurityUtils.getCurrentNickName(); - String now = DateUtil.now(); - dto.setUpdate_time(now); - dto.setUpdate_optid(currentUserId); - dto.setUpdate_optname(nickName); + String now = DateUtil.now(); + dto.setUpdate_time(now); + dto.setUpdate_optid(currentUserId); + dto.setUpdate_optname(nickName); - WQLObject wo = WQLObject.getWQLObject("${tableName}"); - JSONObject json = JSONObject.parseObject(JSON.toJSONString(dto)); - wo.update(json); + WQLObject wo = WQLObject.getWQLObject("${tableName}"); + JSONObject json = JSONObject.parseObject(JSON.toJSONString(dto)); + wo.update(json); } @Override @Transactional(rollbackFor = Exception.class) public void deleteAll(${pkColumnType}[] ids) { - Long currentUserId = SecurityUtils.getCurrentUserId(); - String nickName = SecurityUtils.getCurrentNickName(); - String now = DateUtil.now(); + Long currentUserId = SecurityUtils.getCurrentUserId(); + String nickName = SecurityUtils.getCurrentNickName(); + String now = DateUtil.now(); - WQLObject wo = WQLObject.getWQLObject("${tableName}"); - for (${pkColumnType} ${pkChangeColName}: ids) { - JSONObject param = new JSONObject(); - param.put("${pkChangeColName}", String.valueOf(${pkChangeColName})); - param.put("is_delete", "1"); - param.put("update_optid", currentUserId); - param.put("update_optname", nickName); - param.put("update_time", now); - wo.update(param); - } + WQLObject wo = WQLObject.getWQLObject("${tableName}"); + for (${pkColumnType} ${pkChangeColName}: ids) { + JSONObject param = new JSONObject(); + param.put("${pkChangeColName}", String.valueOf(${pkChangeColName})); + param.put("is_delete", "1"); + param.put("update_optid", currentUserId); + param.put("update_optname", nickName); + param.put("update_time", now); + wo.update(param); + } } - } +} diff --git a/nladmin-system/nlsso-server/src/main/resources/template/generator/front/index.ftl b/nladmin-system/nlsso-server/src/main/resources/template/generator/front/index.ftl index 7fe7842..e886a91 100644 --- a/nladmin-system/nlsso-server/src/main/resources/template/generator/front/index.ftl +++ b/nladmin-system/nlsso-server/src/main/resources/template/generator/front/index.ftl @@ -113,14 +113,14 @@