0%

前言

Jetcache缓存可以使用的 CacheTypeREMOTE, LOCAL, BOTH

注解 @Cached 在使用时,可以 CacheType,如果不指定,则默认为 REMOTE

平台中的缓存注解在各个项目运行时,是无法修改的,并且平台中的缓存注解没有指定 CacheType

场景

满足不同项目对平台缓存存储方式的自定义配置。

场景一:没有缓存中间件,使用 caffeine 本地缓存。

场景二:有缓存中间件,比如Redis,使用 remote.**.type=REDIS

目的

为了满足不同场景的需要。

实现以下功能:

  1. 在注解CacheType=REMOTE 的情况下,实现默认缓存可以由 REMOTE 改为 LOCALBOTH

风险

风险一

在用户需要把缓存注解默认为 CacheType=REMOTE 的数据,改为 BOTH,并且需要主动指定缓存注解 CacheType=REMOTE 的数据,只存到 REMOTE 中。

解决风险

风险一

当前是实现了多个缓存系统(LOCALREMOTE),所以需要自定义的缓存中,指定缓存的 area (默认为 default)。

下面是 area 描述的原话:

1
2
3
4
5
/**
* If you want to use multi backend cache system, you can setup multi "cache area" in configuration,
* this attribute specifies the name of the "cache area" you want to use.
* @return the name of cache area
*/

因为主要目的是修改平台默认的缓存方案,并不是项目中默认的缓存方案,所以只需要处理平台缓存 area 下的 CacheType

当平台缓存的 area 与 项目中缓存的 area 区分开来时,那么平台的默认缓存就很容易修改了。

实现

实现方案

  1. 重写 jetcache下的ConfigMap类,处理 CacheType
  2. 建议修改平台缓存的 area(为了适配一个项目多种缓存系统)。

具体实现

平台中的缓存注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Service
public class CategoryItemServiceImpl extends ServiceImpl<CategoryItemMapper, CategoryItem> implements ICategoryItemService {

@Override
@Cached(name = "categoryItemList:parentId:",key = "#parentId+''",area="systemo_demo")
public List<CategoryItemDTO> loadCategoryItemListByParentId(Long parentId) {
LambdaQueryWrapper<CategoryItem> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.select(
CategoryItem::getId,
CategoryItem::getParentId,
CategoryItem::getItemCode,
CategoryItem::getName)
.eq(CategoryItem::getParentId,parentId)
.eq(CategoryItem::getDelStatus,BaseConstant.NO)
.orderByAsc(CategoryItem::getSortIndex);
List list = this.list(queryWrapper);
return categoryConvert.convertToCategoryItemDTOList(list);
}
}

systemo_demo是平台内的系统常量

ConfigMap处理默认缓存方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class CustomCacheConfigMap extends ConfigMap {
@Override
public void putByMethodInfo(String key, CacheInvokeConfig config) {
methodInfoMap.put(key, config);
CachedAnnoConfig cac = config.getCachedAnnoConfig();
if (cac != null && !CacheConsts.isUndefined(cac.getName())) {
/*自定义校验使用的 CacheType,这里需要注意,只需要判断默认情况下的缓存类型,非默认情况下的默认为开发者主动修改*/
if (CacheConsts.DEFAULT_CACHE_TYPE.equals(cac.getCacheType())
&& "system.demo".equals(cac.getArea())
/*当前配置的 cacheType 不为 null*/
&& cacheConfig.getCacheType() != null) {

cac.setCacheType(cacheConfig.getCacheType());
}
cacheNameMap.put(cac.getArea() + "_" + cac.getName(), config);
}
}
}

本文地址: https://github.com/maxzhao-it/blog/post/b2db72d0/

实现方案

  1. 重写 jetcache下的ConfigMap类,处理 CacheType
  2. 建议修改平台缓存的 area(为了适配一个项目多种缓存系统)。

具体实现

平台中的缓存注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

@Service
public class CategoryItemServiceImpl extends ServiceImpl<CategoryItemMapper, CategoryItem> implements ICategoryItemService {

@Override
@Cached(name = "categoryItemList:parentId:", key = "#parentId+''", area = "systemo_demo")
public List<CategoryItemDTO> loadCategoryItemListByParentId(Long parentId) {
LambdaQueryWrapper<CategoryItem> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.select(
CategoryItem::getId,
CategoryItem::getParentId,
CategoryItem::getItemCode,
CategoryItem::getName)
.eq(CategoryItem::getParentId, parentId)
.eq(CategoryItem::getDelStatus, BaseConstant.NO)
.orderByAsc(CategoryItem::getSortIndex);
List list = this.list(queryWrapper);
return categoryConvert.convertToCategoryItemDTOList(list);
}
}

systemo_demo是平台内的系统常量

ConfigMap处理默认缓存方式

具体实现请看附录中的 CustomCacheConfigMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class CustomCacheConfigMap extends ConfigMap {
@Override
public void putByMethodInfo(String key, CacheInvokeConfig config) {
methodInfoMap.put(key, config);
CachedAnnoConfig cac = config.getCachedAnnoConfig();
if (cac != null && !CacheConsts.isUndefined(cac.getName())) {
/*自定义校验使用的 CacheType,这里需要注意,只需要判断默认情况下的缓存类型,非默认情况下的默认为开发者主动修改*/
if (CacheConsts.DEFAULT_CACHE_TYPE.equals(cac.getCacheType())
&& "system.demo".equals(cac.getArea())
/*当前配置的 cacheType 不为 null*/
&& cacheConfig.getCacheType() != null) {

cac.setCacheType(cacheConfig.getCacheType());
}
cacheNameMap.put(cac.getArea() + "_" + cac.getName(), config);
}
}
}

自定义的 CustomCacheConfigMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
* 自定义缓存注解存储类<br>
* 替代 jetcache 的默认 ConfigMap<br>
* 实现了全局配置默认缓存方式<br>
*
* @author maxzhao
* @date 2021-09-24 15:30
*/
public class CustomCacheConfigMap extends ConfigMap {
/**
* 自定义缓存配置
*/
private final BootCacheProperties bootCacheProperties;

public CustomCacheConfigMap(BootCacheProperties bootCacheProperties) {
this.bootCacheProperties = bootCacheProperties;
}

/**
* 所有方法
*/
private final ConcurrentHashMap<String, CacheInvokeConfig> methodInfoMap = new ConcurrentHashMap<>();
/**
* 注解后的方法
*/
private final ConcurrentHashMap<String, CacheInvokeConfig> cacheNameMap = new ConcurrentHashMap<>();

@Override
public void putByMethodInfo(String key, CacheInvokeConfig config) {
methodInfoMap.put(key, config);
CachedAnnoConfig cac = config.getCachedAnnoConfig();
Map<String, CacheType> autoAreaCacheType = Optional.ofNullable(bootCacheProperties.getAutoAreaCacheType()).orElse(new HashMap<>(0));

if (cac != null && !CacheConsts.isUndefined(cac.getName())) {
/*自定义校验使用的 CacheType,这里需要注意,只需要判断默认情况下的缓存类型,非默认情况下的默认为开发者主动修改*/
CacheType cacheType = autoAreaCacheType.get(cac.getArea());
if (config.getCachedAnnoConfig() != null
&& CacheConsts.DEFAULT_CACHE_TYPE.equals(config.getCachedAnnoConfig().getCacheType())
/*当前配置的 cacheType 不为 null*/
&& cacheType != null) {
config.getCachedAnnoConfig().setCacheType(cacheType);
}
cacheNameMap.put(cac.getArea() + "_" + cac.getName(), config);
}
}

@Override
public CacheInvokeConfig getByMethodInfo(String key) {
return methodInfoMap.get(key);
}

@Override
public CacheInvokeConfig getByCacheName(String area, String cacheName) {
return cacheNameMap.get(area + "_" + cacheName);
}
}

BootCacheProperties 配置

配置

1
2
3
4
5
6
7
8
9
10
gt:
boot:
# 缓存配置
cache:
# 不同域的默认缓存方式 JetCache默认方式为 remote
auto-area-cache-type:
default: REMOTE
otherArea: REMOTE
# 对不同名称的缓存,进行自定义的缓存配置
autoConfig: { user: 60ms }

实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

@Data
public class BootCacheProperties {

/**
* 不同域的默认缓存方式<br>
* JetCache默认方式为 remote,
* 这里需要根据 area 划分,统一替换 单个 area 的 CacheType
*/
private Map<String, CacheType> autoAreaCacheType = new HashMap<>(0);
/**
* 自定义配置 cacheName 与 过期时间
*/
private Map<String, Duration> autoConfig = new HashMap<>(0);

}

注册 Bean

在配置类中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/*激活 Cached*/
@EnableMethodCache(basePackages = "com.gt")
/*激活 CreateCache注解*/
@EnableCreateCacheAnnotation
@EnableCaching(mode = AdviceMode.PROXY)
@Configuration
public class StartCacheConfig {


/**
* CacheProperties 的 bean 没有默认加载,所有要手动加载一下
*
* @return
*/
@Bean
@ConfigurationProperties(prefix = "spring.cache")
public CacheProperties getCacheProperties() {
return new CacheProperties();
}

/**
* 自定义的缓存配置
*
* @return BootCacheProperties
*/
@Bean
@ConfigurationProperties(prefix = "gt.boot.cache")
public BootCacheProperties bootCacheProperties() {
return new BootCacheProperties();
}

@Bean
public RedisCacheConfiguration cacheConfiguration() {
return CacheConfigurationUtil.getRedisCacheConfigurationWithTtl(Duration.ZERO);
}

/**
* 解决 @Cache 方法上注解的默认 CacheType
* @param bootCacheProperties 自定义参数
* @return ConfigMap 对象
*/
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public ConfigMap jetcacheConfigMap(BootCacheProperties bootCacheProperties) {
return new CustomCacheConfigMap(bootCacheProperties);
}
}

本文地址: https://github.com/maxzhao-it/blog/post/784e58da/

前言

MyBatisPlus 通过自定义生成器、模板引擎、模板,可以生成许多简化开发的类、方法等等。

这里把生成的文件,导出到ZIP文件。

主要目的是方便统一管理代码生成器。

需要了解自定义代码生成器

参考:MyBatisPlus代码生成器自定义FreemarkerEngine

配置

pom.xml配置

1
2
3
4
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>

自定义模板引擎类

CustomFreemarkerTemplateEngine

AutoGenerator配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MpGeneratorDemo {
public void generator() {
AutoGenerator mpg = new AutoGenerator();
/**省略其他配置*/
CustomFreemarkerTemplateEngine templateEngine = new CustomFreemarkerTemplateEngine(config);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ZipOutputStream zip = new ZipOutputStream(outputStream);
templateEngine.setZipOutputStream(zip);
/*配置模板引擎的属性,比如自定义的config,构造时传参*/
mpg.setTemplateEngine(templateEngine);
/*待输出的的文件流*/
outputStream.toByteArray();
}
}

FreemarkerEngine

为什么把生成文件写在模板引擎里?

因为模板引擎里有装配好的文件路径,比较直观。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class CustomFreemarkerTemplateEngine extends AbstractTemplateEngine {
/*****************/
/**
* zip 输出流
*/
@Setter
private ZipOutputStream zipOutputStream;
@Override
public void writer(Map<String, Object> objectMap, String templatePath, String outputFile) throws Exception {
Template template = configuration.getTemplate(templatePath);
try (FileOutputStream fileOutputStream = new FileOutputStream(outputFile)) {
template.process(objectMap, new OutputStreamWriter(fileOutputStream, ConstVal.UTF8));
}
log.debug("模板:" + templatePath + "; 文件:" + outputFile);
if (config.getUseZip()) {
InputStream inputStream = null;
BufferedInputStream bufferedInputStream = null;
zipOutputStream.putNextEntry(new ZipEntry(outputFile.replace(config.getOutPutDir(), "")));
byte[] buff = new byte[4096];
int len;
try {
inputStream = new FileInputStream(outputFile);
/*读取文件*/
bufferedInputStream = new BufferedInputStream(inputStream);
/*判断:当前已经传递的数据大小+当前已有的长度,小于总长度时,*/
while ((len = bufferedInputStream.read(buff)) != -1) {
zipOutputStream.write(buff, 0, len);
}
} catch (Exception e) {
log.error("代码生成器:输出ZIP错误:模板 :{}; 文件:{}", templatePath, outputFile);
log.error("代码生成器:输出ZIP错误", e);
} finally {
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(bufferedInputStream);
}
}
}
}

附录

CodeGeneratorFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
package com.gt.code.generator.factory;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.converts.OracleTypeConvert;
import com.baomidou.mybatisplus.generator.config.po.TableFill;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.IColumnType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.gt.code.generator.config.CodeGeneratorConfig;
import com.gt.code.generator.config.CodeGeneratorDbConfig;
import com.gt.code.generator.config.CodeGeneratorEnumVal;
import com.gt.code.generator.config.CodeGeneratorVal;
import com.gt.code.generator.util.template.CustomFreemarkerTemplateEngine;
import org.apache.commons.io.IOUtils;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.*;
import java.util.zip.ZipOutputStream;

/**
* 生成代码工厂<br>
*
* @author maxzhao
* @date 2021-08-27
*/
public class CodeGeneratorFactory {
/**
* 自动填充
*/
private static final List<TableFill> TABLE_FILL_LIST = Arrays.asList(
new TableFill("createTime", FieldFill.INSERT),
new TableFill("updateTime", FieldFill.UPDATE),
new TableFill("delStatus", FieldFill.INSERT_UPDATE));

private CodeGeneratorFactory() {
}

public static byte[] generate(CodeGeneratorConfig config) {
AutoGenerator mpg = new AutoGenerator();
CodeGeneratorDbConfig dbConfig = config.getDbConfig();
/*输出路径*/
String outPutDir = config.getFullOutPutDir();
//region 注入配置
mpg.setCfg(configInjection(config));
//endregion
//region 数据源配置
DataSourceConfig dataSource = dbConfig.generatorDataSourceConfig();
if (dataSource.getDbType() == DbType.ORACLE) {
dataSource.setTypeConvert(new OracleTypeConvert() {
@Override
public IColumnType processTypeConvert(GlobalConfig globalConfig, String fieldType) {
if (fieldType.toLowerCase(Locale.ROOT).matches("")) {

}
return super.processTypeConvert(globalConfig, fieldType);
}
});
}
mpg.setDataSource(dataSource);
//endregion
//region 表策略配置
StrategyConfig strategy = new StrategyConfig();
/*驼峰*/
strategy.setNaming(NamingStrategy.underline_to_camel);
/*驼峰*/
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
/*使用 lombok*/
strategy.setEntityLombokModel(true);
/*默认使用 restfull风格*/
strategy.setRestControllerStyle(true);
/*标*/
strategy.setInclude(config.getTablesNames());
/*驼峰转连字符串*/
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix(config.getTablePrefix());
/*字段添加 @TableField*/
strategy.setEntityTableFieldAnnotationEnable(true);
strategy.setTableFillList(TABLE_FILL_LIST);
mpg.setStrategy(strategy);
//endregion
//region 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName(config.getModuleName());
pc.setParent(config.getParentPackage());
/*设置 controller 文件 包名*/
pc.setController(CodeGeneratorEnumVal.CONTROLLER.getPackageName());
/*设置 xml 文件 包名*/
pc.setXml("mapper");
/*设置 entity 文件 包名*/
pc.setEntity("model.entity");
pc.setXml("mapper");
mpg.setPackageInfo(pc);
//endregion
//region 模板配置
TemplateConfig templateConfig = new TemplateConfig()
.setEntity(CodeGeneratorEnumVal.ENTITY.getTemplate())
.setController(CodeGeneratorEnumVal.CONTROLLER.getTemplate())
.setService(CodeGeneratorEnumVal.SERVICE.getTemplate())
.setServiceImpl(CodeGeneratorEnumVal.SERVICE_IMPL.getTemplate())
.setMapper(CodeGeneratorEnumVal.MAPPER.getTemplate())
.setXml(CodeGeneratorEnumVal.TEMPLATE_XML.getTemplate());
mpg.setTemplate(templateConfig);
//endregion
//region 全局配置
/*默认使用雪花算法*/
GlobalConfig gc = new GlobalConfig();
gc.setOutputDir(outPutDir);
gc.setAuthor(config.getAuthor());
gc.setOpen(false);
/*默认不覆盖,如果文件存在,将不会再生成,配置true就是覆盖*/
gc.setFileOverride(true);
/*配置 swagger2*/
gc.setSwagger2(config.getSwagger2());
/*开启 ActiveRecord 模式*/
gc.setActiveRecord(config.getActiveRecord());
mpg.setGlobalConfig(gc);

//endregion
//region 模板引擎
// 选择 freemarker 引擎需要指定如下加,注意 pom 依赖必须有!
CustomFreemarkerTemplateEngine templateEngine = new CustomFreemarkerTemplateEngine(config);
ByteArrayOutputStream outputStream = null;
ZipOutputStream zip = null;
if (config.getUseZip()) {
/*下载zip文件 字节输出流*/
outputStream = new ByteArrayOutputStream();
zip = new ZipOutputStream(outputStream);
templateEngine.setZipOutputStream(zip);
/*下载zip ,会覆盖文件*/
gc.setFileOverride(true);
}
mpg.setTemplateEngine(templateEngine);
//endregion
mpg.execute();
if (config.getUseZip()) {
/*获取结果*/
//等同于自己写判断然后关闭
IOUtils.closeQuietly(zip);
return outputStream != null ? outputStream.toByteArray() : null;
}
return null;
}

/**
* 添加自动填充
*
* @param tableFill 新的自动填充
*/
public static void addTableFill(TableFill tableFill) {
TABLE_FILL_LIST.add(tableFill);
}

/**
* 配置自定义参数/属性
*
* @param config 配置信息
*/
private static InjectionConfig configInjection(CodeGeneratorConfig config) {
// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
String controllerPackage = this.getConfig().getPackageInfo().get("Controller");
this.getConfig().getPackageInfo().put("Dto", controllerPackage + ".dto");
this.getConfig().getPackageInfo().put("Params", controllerPackage + ".Params");
this.getConfig().getPackageInfo().put("Vo", controllerPackage + ".vo");
this.getConfig().getPackageInfo().put("Convert", controllerPackage.substring(0, controllerPackage.lastIndexOf(CodeGeneratorEnumVal.CONTROLLER.getPackageName())) + CodeGeneratorEnumVal.CONVERT.getPackageName());
String xmlPath = this.getConfig().getPathInfo().get("xml_path");
/*修改 xml 生成路径*/
this.getConfig().getPathInfo().put("xml_path", xmlPath.replace(CodeGeneratorVal.JAVA_SOURCE_PATH, CodeGeneratorVal.XML_SOURCE_PATH));
Map<String, Object> map = new HashMap<>(4);
/*自定义cfg引用*/
this.setMap(map);
}
};
List<FileOutConfig> focList = new ArrayList<>();
/*dto 模板*/
focList.add(convertFileOutConfig(config, CodeGeneratorEnumVal.DTO));
/*params 模板*/
focList.add(convertFileOutConfig(config, CodeGeneratorEnumVal.PARAMS));
/*vo 模板*/
focList.add(convertFileOutConfig(config, CodeGeneratorEnumVal.VO));
/*判断是否使用 mapstruct convert*/
if ((config.getUseConvert() != null && config.getUseConvert())
/*增删查改时使用*/
|| (config.getActiveCrud() != null && config.getActiveCrud())) {
/*convert 模板*/
focList.add(convertFileOutConfig(config, CodeGeneratorEnumVal.CONVERT));
}
/*使用增删查改*/
if (config.getActiveCrud() != null && config.getActiveCrud()) {
/*exception 模板*/
focList.add(convertFileOutConfig(config, CodeGeneratorEnumVal.EXCEPTION));
/*exception enum 模板*/
focList.add(convertFileOutConfig(config, CodeGeneratorEnumVal.EXCEPTION_ENUM));
/*exception handler 模板*/
focList.add(convertFileOutConfig(config, CodeGeneratorEnumVal.EXCEPTION_HANDLER));
}
cfg.setFileOutConfigList(focList);
return cfg;
}

/**
* 加载文件输出配置
*
* @param config 配置
* @param enumVal 模板
* @return mybatis plus 文件输出配置
*/
private static FileOutConfig convertFileOutConfig(CodeGeneratorConfig config, CodeGeneratorEnumVal enumVal) {
return new FileOutConfig(enumVal.getTemplate()) {
@Override
public String outputFile(TableInfo tableInfo) {
/*指定模板生,自定义生成文件到哪个地方*/
return config.getModuleOutPutDir() + File.separator +
enumVal.getPackageName().replace(".", File.separator) +
File.separator +
String.format(enumVal.getClassName(), tableInfo.getEntityName()) + StringPool.DOT_JAVA;
}
};
}
}

CustomFreemarkerTemplateEngine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package com.gt.code.generator.util.template;

import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.config.ConstVal;
import com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.engine.AbstractTemplateEngine;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import com.gt.code.generator.config.CodeGeneratorConfig;
import com.gt.code.generator.config.CodeGeneratorEnumVal;
import com.gt.code.generator.util.CustomTableInfo;
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;

import java.io.*;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
* 自定义 Freemarker 模板引擎
*
* @author maxzhao
* @date 2021-08-28 08:36
*/
@Slf4j
public class CustomFreemarkerTemplateEngine extends AbstractTemplateEngine {
/**
* freemarker 配置
*/
private Configuration configuration;
/**
* zip 输出流
*/
@Setter
private ZipOutputStream zipOutputStream;
/**
* 自定义配置
*/
private final CodeGeneratorConfig config;

/**
* 初始化
*
* @param config 自定义配置
*/
public CustomFreemarkerTemplateEngine(CodeGeneratorConfig config) {
this.config = config;
}

@Override
public CustomFreemarkerTemplateEngine init(ConfigBuilder configBuilder) {
super.init(configBuilder);
configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
configuration.setDefaultEncoding(ConstVal.UTF8);
configuration.setClassForTemplateLoading(FreemarkerTemplateEngine.class, StringPool.SLASH);
return this;
}


@Override
public void writer(Map<String, Object> objectMap, String templatePath, String outputFile) throws Exception {
Template template = configuration.getTemplate(templatePath);
try (FileOutputStream fileOutputStream = new FileOutputStream(outputFile)) {
template.process(objectMap, new OutputStreamWriter(fileOutputStream, ConstVal.UTF8));
}
log.debug("模板:" + templatePath + "; 文件:" + outputFile);
if (config.getUseZip()) {
InputStream inputStream = null;
BufferedInputStream bufferedInputStream = null;
zipOutputStream.putNextEntry(new ZipEntry(outputFile.replace(config.getOutPutDir(), "")));
byte[] buff = new byte[4096];
int len;
try {
inputStream = new FileInputStream(outputFile);
/*读取文件*/
bufferedInputStream = new BufferedInputStream(inputStream);
/*判断:当前已经传递的数据大小+当前已有的长度,小于总长度时,*/
while ((len = bufferedInputStream.read(buff)) != -1) {
zipOutputStream.write(buff, 0, len);
}
} catch (Exception e) {
log.error("代码生成器:输出ZIP错误:模板 :{}; 文件:{}", templatePath, outputFile);
log.error("代码生成器:输出ZIP错误", e);
} finally {
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(bufferedInputStream);
}
}
}


/**
* 获取模板路径<br>
* 在配置时,一般不配置 .ftl 文件,这是 Freemarker 模板的习惯,所以这里要在路径上添加 .ftl
*
* @param filePath 模板路径
* @return 模板真实路径
*/
@Override
public String templateFilePath(String filePath) {
return filePath + ".ftl";
}

@Override
public Map<String, Object> getObjectMap(TableInfo tableInfo) {
Map<String, Object> objectMap = super.getObjectMap(tableInfo);
/*获取类 Package*/
Map<String, String> packageInfo = super.getConfigBuilder().getPackageInfo();
String controllerPackage = packageInfo.get("Controller");
/*基础 package*/
String basePackage = controllerPackage.substring(0, controllerPackage.lastIndexOf(CodeGeneratorEnumVal.CONTROLLER.getPackageName()));
packageInfo.put("basePackage", basePackage);
/*自定义表数据*/
CustomTableInfo customTableInfo = CustomTableInfo.getInstance(tableInfo);
/*使用增删改查*/
objectMap.put("activeCrud", config.getActiveCrud());
if (config.getActiveCrud() != null && config.getActiveCrud()) {
/*使用增删改查时,添加代码引入*/
packageInfo.put("ExceptionHandler", basePackage + "exception.handler");
packageInfo.put("Exception", basePackage + "exception");
packageInfo.put("ExceptionEnum", basePackage + "exception");
customTableInfo.initImportPackage(packageInfo);
}

/*每个类的 package*/
objectMap.put("packageInfo", packageInfo);
/*自定义表数据*/
objectMap.put("table", customTableInfo);
return objectMap;
}
}

CustomTableInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package com.gt.code.generator.util;

import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.config.po.TableField;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.gt.code.generator.config.CodeGeneratorEnumVal;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.springframework.util.StringUtils;

import java.util.*;


/**
* 自定义表配置
*
* @author maxzhao
* @date 2021-08-29 10:47
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
public class CustomTableInfo extends TableInfo {
/**
* 主键类型,默认为 String
*/
private String idTypeClassName;
private String dtoName;
private String paramsName;
private String voName;
private String convertName;
private String serviceBeanName;
private String mapperBeanName;
private String convertBeanName;
/**
* 异常类名
*/
private String exceptionName;
/**
* 异常枚举
*/
private String exceptionEnumName;
/**
* 异常处理类名
*/
private String exceptionHandlerName;
/**
* 使用 crud 时的 CONTROLLER 引入
*/
private final Set<String> apiImportPackages = new HashSet<>();
/**
* 使用 crud 时的 SERVICE 接口引入
*/
private final Set<String> serviceImportPackages = new HashSet<>();
/**
* 使用 crud 时的 SERVICE 实现引入
*/
private final Set<String> serviceImplImportPackages = new HashSet<>();
/**
* 使用 crud 时的 通用 实现引入
*/
private final Set<String> crudImplImportPackages = new HashSet<>();

public CustomTableInfo(TableInfo tableInfo) {
this.setConvert(tableInfo.isConvert());
this.setEntityName(tableInfo.getEntityName());
List<TableField> fields = tableInfo.getFields();
this.setFields(fields);
for (TableField field : fields) {
if (field.isKeyFlag()) {
this.idTypeClassName = field.getColumnType().getType();
}
}
if (!StringUtils.hasText(this.idTypeClassName)) {
this.idTypeClassName = "String";
}
this.getImportPackages().addAll(tableInfo.getImportPackages());
this.setName(tableInfo.getName());
this.setComment(tableInfo.getComment());
this.setEntityName(tableInfo.getEntityName());
this.setMapperName(tableInfo.getMapperName());
this.setXmlName(tableInfo.getXmlName());
this.setServiceName(tableInfo.getServiceName());
this.setServiceImplName(tableInfo.getServiceImplName());
this.setControllerName(tableInfo.getControllerName());
this.setCommonFields(tableInfo.getCommonFields());
this.setFieldNames(tableInfo.getFieldNames());


}


public static CustomTableInfo getInstance(TableInfo tableInfo) {
CustomTableInfo customTableInfo = new CustomTableInfo(tableInfo);

customTableInfo.setDtoName(String.format(CodeGeneratorEnumVal.DTO.getClassName(), tableInfo.getEntityName()));
customTableInfo.setParamsName(String.format(CodeGeneratorEnumVal.PARAMS.getClassName(), tableInfo.getEntityName()));
customTableInfo.setVoName(String.format(CodeGeneratorEnumVal.VO.getClassName(), tableInfo.getEntityName()));
customTableInfo.setConvertName(String.format(CodeGeneratorEnumVal.CONVERT.getClassName(), tableInfo.getEntityName()));

String serviceBeanName = customTableInfo.getServiceName().substring(1);
serviceBeanName = serviceBeanName.substring(0, 1).toLowerCase() + serviceBeanName.substring(1);
customTableInfo.setServiceBeanName(serviceBeanName);
String mapperBeanName = customTableInfo.getMapperName();
mapperBeanName = mapperBeanName.substring(0, 1).toLowerCase() + mapperBeanName.substring(1);
customTableInfo.setMapperBeanName(mapperBeanName);
customTableInfo.setConvertBeanName(customTableInfo.getConvertName().substring(0, 1).toLowerCase(Locale.ROOT) +
customTableInfo.getConvertName().substring(1));

/*异常类名 */
customTableInfo.setExceptionName(String.format(CodeGeneratorEnumVal.EXCEPTION.getClassName(), tableInfo.getEntityName()));
customTableInfo.setExceptionEnumName(String.format(CodeGeneratorEnumVal.EXCEPTION_ENUM.getClassName(), tableInfo.getEntityName()));
customTableInfo.setExceptionHandlerName(String.format(CodeGeneratorEnumVal.EXCEPTION_HANDLER.getClassName(), tableInfo.getEntityName()));
return customTableInfo;
}

/**
* 初始化导入依赖
*
* @param packageInfo 所有的 package
*/
public void initImportPackage(Map<String, String> packageInfo) {
apiImportPackages.add(java.util.List.class.getCanonicalName());
apiImportPackages.add(org.springframework.http.ResponseEntity.class.getCanonicalName());

serviceImportPackages.add(java.util.List.class.getCanonicalName());
serviceImplImportPackages.add(java.util.List.class.getCanonicalName());
serviceImplImportPackages.add(packageInfo.get("Convert") + StringPool.DOT + this.getConvertName());
serviceImplImportPackages.add(packageInfo.get("Exception") + StringPool.DOT + this.getExceptionName());
serviceImplImportPackages.add(packageInfo.get("ExceptionEnum") + StringPool.DOT + this.getExceptionEnumName());
serviceImplImportPackages.add(packageInfo.get("ExceptionHandler") + StringPool.DOT + this.getExceptionHandlerName());
serviceImplImportPackages.add(com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper.class.getCanonicalName());


crudImplImportPackages.add(com.baomidou.mybatisplus.core.metadata.IPage.class.getCanonicalName());
crudImplImportPackages.add(com.baomidou.mybatisplus.extension.plugins.pagination.Page.class.getCanonicalName());
crudImplImportPackages.add(packageInfo.get("Dto") + StringPool.DOT + this.dtoName);
crudImplImportPackages.add(packageInfo.get("Params") + StringPool.DOT + this.paramsName);
crudImplImportPackages.add(packageInfo.get("Vo") + StringPool.DOT + this.voName);
crudImplImportPackages.add(packageInfo.get("Entity") + StringPool.DOT + this.getEntityName());

}
}

本文地址: https://github.com/maxzhao-it/blog/post/15752/

前言

MyBatisPlus:AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller
等各个模块的代码,极大的提升了开发效率。

很多时候上面生成的代码不能满足开发需求,在自定义模板的同时,我们也会自定义模板引擎,这里使用 Freemarker模板引擎。

MyBatisPlus代码生成器官方简介

配置

pom.xml配置

1
2
3
4
5

<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>

自定义模板引擎类

CustomFreemarkerTemplateEngine

AutoGenerator配置

1
2
3
4
5
6
7
8
9
public class MpGeneratorDemo {
public void generator() {
AutoGenerator mpg = new AutoGenerator();
/**省略其他配置*/
CustomFreemarkerTemplateEngine templateEngine = new CustomFreemarkerTemplateEngine(config);
/*配置模板引擎的属性,比如自定义的config,构造时传参*/
mpg.setTemplateEngine(templateEngine);
}
}

FreemarkerEngine

继承 AbstractTemplateEngine

这里有三个属性

  1. configurationfreemarker配置,可以获取模板并操作模板
  2. zipOutputStream:导出ZIP文件的输出流
  3. config:自定义配置,可以控制是否输出ZIP文件、控制自定义模板属性等等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

@Slf4j
public class CustomFreemarkerTemplateEngine extends AbstractTemplateEngine {
/**
* freemarker 配置
*/
private Configuration configuration;
/**
* zip 输出流
*/
@Setter
private ZipOutputStream zipOutputStream;
/**
* 自定义配置
*/
private final CodeGeneratorConfig config;

/**
* 初始化
*
* @param config 自定义配置
*/
public CustomFreemarkerTemplateEngine(CodeGeneratorConfig config) {
this.config = config;
}

@Override
public CustomFreemarkerTemplateEngine init(ConfigBuilder configBuilder) {
super.init(configBuilder);
configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
configuration.setDefaultEncoding(ConstVal.UTF8);
configuration.setClassForTemplateLoading(FreemarkerTemplateEngine.class, StringPool.SLASH);
return this;
}

@Override
public void writer(Map<String, Object> objectMap, String templatePath, String outputFile) throws Exception {
Template template = configuration.getTemplate(templatePath);
/**生成模板操作***************************/
}

/**
* 获取模板路径<br>
* 在配置时,一般不配置 .ftl 文件,这是 Freemarker 模板的习惯,所以这里要在路径上添加 .ftl
*
* @param filePath 模板路径
* @return 模板真实路径
*/
@Override
public String templateFilePath(String filePath) {
return filePath + ".ftl";
}

@Override
public Map<String, Object> getObjectMap(TableInfo tableInfo) {
Map<String, Object> objectMap = super.getObjectMap(tableInfo);
/*****************************/
return objectMap;
}
}

writer模板生成方法

当前方法有三个参数:

  1. 模板的参数Map
    • 当前属性是 AbstractTemplateEngine中配置了一部分
    • 自定义模板引擎中自定义一部分
  2. 模板路径
  3. 输出文件路径

模板生成方法主要做了3件事:

  1. 根据模板路径获取模板
    • 模板路径是由配置路径和templateFilePath方法创建的
    • templateFilePath方法是参入配置路径,返回实际模板路径
  2. 生成模板
  3. 写入ZIP文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class CustomFreemarkerTemplateEngine extends AbstractTemplateEngine {
/************************************/
@Override
public void writer(Map<String, Object> objectMap, String templatePath, String outputFile) throws Exception {
Template template = configuration.getTemplate(templatePath);
try (FileOutputStream fileOutputStream = new FileOutputStream(outputFile)) {
template.process(objectMap, new OutputStreamWriter(fileOutputStream, ConstVal.UTF8));
}
log.debug("模板:" + templatePath + "; 文件:" + outputFile);
if (config.getUseZip()) {
InputStream inputStream = null;
BufferedInputStream bufferedInputStream = null;
zipOutputStream.putNextEntry(new ZipEntry(outputFile.replace(config.getOutPutDir(), "")));
byte[] buff = new byte[4096];
int len;
try {
inputStream = new FileInputStream(outputFile);
/*读取文件*/
bufferedInputStream = new BufferedInputStream(inputStream);
/*判断:当前已经传递的数据大小+当前已有的长度,小于总长度时,*/
while ((len = bufferedInputStream.read(buff)) != -1) {
zipOutputStream.write(buff, 0, len);
}
} catch (Exception e) {
log.error("代码生成器:输出ZIP错误:模板 :{}; 文件:{}", templatePath, outputFile);
log.error("代码生成器:输出ZIP错误", e);
} finally {
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(bufferedInputStream);
}
}
}
/************************************/
}

getObjectMap获取模板参数方法

  1. 当前方法只有一个参数TableInfo,顾名思义,就是单表的信息。
  2. TableInfo主要包括表结构、主要的Class Entity、Mapper、Mapper XML、Service、Controller
  3. 如果在 AutoGenerator 中配置了策略,这里就会得到执行策略后的名字。

我在这里配置了一个自己的CustomTableInfo(附录),除了自带的信息外,好包含了 dtoName、voName、convertName等等。

下面是一段配置的作用是,传入模板参数 activeCrud,当前参数可以在模板中判断是否生成 crud代码(需要模板中有相关代码):

1
2
3
4
5
6
7
8
9
  public class CustomFreemarkerTemplateEngine extends AbstractTemplateEngine {
@Override
public Map<String, Object> getObjectMap(TableInfo tableInfo) {
Map<String, Object> objectMap = super.getObjectMap(tableInfo);
/*使用增删改查*/
objectMap.put("activeCrud", config.getActiveCrud());
return objectMap;
}
}

我在 freemarker自定义模板中,就可以使用

附录

CodeGeneratorFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
package com.gt.code.generator.factory;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.converts.OracleTypeConvert;
import com.baomidou.mybatisplus.generator.config.po.TableFill;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.IColumnType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.gt.code.generator.config.CodeGeneratorConfig;
import com.gt.code.generator.config.CodeGeneratorDbConfig;
import com.gt.code.generator.config.CodeGeneratorEnumVal;
import com.gt.code.generator.config.CodeGeneratorVal;
import com.gt.code.generator.util.template.CustomFreemarkerTemplateEngine;
import org.apache.commons.io.IOUtils;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.*;
import java.util.zip.ZipOutputStream;

/**
* 生成代码工厂<br>
*
* @author maxzhao
* @date 2021-08-27
*/
public class CodeGeneratorFactory {
/**
* 自动填充
*/
private static final List<TableFill> TABLE_FILL_LIST = Arrays.asList(
new TableFill("createTime", FieldFill.INSERT),
new TableFill("updateTime", FieldFill.UPDATE),
new TableFill("delStatus", FieldFill.INSERT_UPDATE));

private CodeGeneratorFactory() {
}

public static byte[] generate(CodeGeneratorConfig config) {
AutoGenerator mpg = new AutoGenerator();
CodeGeneratorDbConfig dbConfig = config.getDbConfig();
/*输出路径*/
String outPutDir = config.getFullOutPutDir();
//region 注入配置
mpg.setCfg(configInjection(config));
//endregion
//region 数据源配置
DataSourceConfig dataSource = dbConfig.generatorDataSourceConfig();
if (dataSource.getDbType() == DbType.ORACLE) {
dataSource.setTypeConvert(new OracleTypeConvert() {
@Override
public IColumnType processTypeConvert(GlobalConfig globalConfig, String fieldType) {
if (fieldType.toLowerCase(Locale.ROOT).matches("")) {

}
return super.processTypeConvert(globalConfig, fieldType);
}
});
}
mpg.setDataSource(dataSource);
//endregion
//region 表策略配置
StrategyConfig strategy = new StrategyConfig();
/*驼峰*/
strategy.setNaming(NamingStrategy.underline_to_camel);
/*驼峰*/
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
/*使用 lombok*/
strategy.setEntityLombokModel(true);
/*默认使用 restfull风格*/
strategy.setRestControllerStyle(true);
/*标*/
strategy.setInclude(config.getTablesNames());
/*驼峰转连字符串*/
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix(config.getTablePrefix());
/*字段添加 @TableField*/
strategy.setEntityTableFieldAnnotationEnable(true);
strategy.setTableFillList(TABLE_FILL_LIST);
mpg.setStrategy(strategy);
//endregion
//region 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName(config.getModuleName());
pc.setParent(config.getParentPackage());
/*设置 controller 文件 包名*/
pc.setController(CodeGeneratorEnumVal.CONTROLLER.getPackageName());
/*设置 xml 文件 包名*/
pc.setXml("mapper");
/*设置 entity 文件 包名*/
pc.setEntity("model.entity");
pc.setXml("mapper");
mpg.setPackageInfo(pc);
//endregion
//region 模板配置
TemplateConfig templateConfig = new TemplateConfig()
.setEntity(CodeGeneratorEnumVal.ENTITY.getTemplate())
.setController(CodeGeneratorEnumVal.CONTROLLER.getTemplate())
.setService(CodeGeneratorEnumVal.SERVICE.getTemplate())
.setServiceImpl(CodeGeneratorEnumVal.SERVICE_IMPL.getTemplate())
.setMapper(CodeGeneratorEnumVal.MAPPER.getTemplate())
.setXml(CodeGeneratorEnumVal.TEMPLATE_XML.getTemplate());
mpg.setTemplate(templateConfig);
//endregion
//region 全局配置
/*默认使用雪花算法*/
GlobalConfig gc = new GlobalConfig();
gc.setOutputDir(outPutDir);
gc.setAuthor(config.getAuthor());
gc.setOpen(false);
/*默认不覆盖,如果文件存在,将不会再生成,配置true就是覆盖*/
gc.setFileOverride(true);
/*配置 swagger2*/
gc.setSwagger2(config.getSwagger2());
/*开启 ActiveRecord 模式*/
gc.setActiveRecord(config.getActiveRecord());
mpg.setGlobalConfig(gc);

//endregion
//region 模板引擎
// 选择 freemarker 引擎需要指定如下加,注意 pom 依赖必须有!
CustomFreemarkerTemplateEngine templateEngine = new CustomFreemarkerTemplateEngine(config);
ByteArrayOutputStream outputStream = null;
ZipOutputStream zip = null;
if (config.getUseZip()) {
/*下载zip文件 字节输出流*/
outputStream = new ByteArrayOutputStream();
zip = new ZipOutputStream(outputStream);
templateEngine.setZipOutputStream(zip);
/*下载zip ,会覆盖文件*/
gc.setFileOverride(true);
}
mpg.setTemplateEngine(templateEngine);
//endregion
mpg.execute();
if (config.getUseZip()) {
/*获取结果*/
//等同于自己写判断然后关闭
IOUtils.closeQuietly(zip);
return outputStream != null ? outputStream.toByteArray() : null;
}
return null;
}

/**
* 添加自动填充
*
* @param tableFill 新的自动填充
*/
public static void addTableFill(TableFill tableFill) {
TABLE_FILL_LIST.add(tableFill);
}

/**
* 配置自定义参数/属性
*
* @param config 配置信息
*/
private static InjectionConfig configInjection(CodeGeneratorConfig config) {
// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
String controllerPackage = this.getConfig().getPackageInfo().get("Controller");
this.getConfig().getPackageInfo().put("Dto", controllerPackage + ".dto");
this.getConfig().getPackageInfo().put("Params", controllerPackage + ".Params");
this.getConfig().getPackageInfo().put("Vo", controllerPackage + ".vo");
this.getConfig().getPackageInfo().put("Convert", controllerPackage.substring(0, controllerPackage.lastIndexOf(CodeGeneratorEnumVal.CONTROLLER.getPackageName())) + CodeGeneratorEnumVal.CONVERT.getPackageName());
String xmlPath = this.getConfig().getPathInfo().get("xml_path");
/*修改 xml 生成路径*/
this.getConfig().getPathInfo().put("xml_path", xmlPath.replace(CodeGeneratorVal.JAVA_SOURCE_PATH, CodeGeneratorVal.XML_SOURCE_PATH));
Map<String, Object> map = new HashMap<>(4);
/*自定义cfg引用*/
this.setMap(map);
}
};
List<FileOutConfig> focList = new ArrayList<>();
/*dto 模板*/
focList.add(convertFileOutConfig(config, CodeGeneratorEnumVal.DTO));
/*params 模板*/
focList.add(convertFileOutConfig(config, CodeGeneratorEnumVal.PARAMS));
/*vo 模板*/
focList.add(convertFileOutConfig(config, CodeGeneratorEnumVal.VO));
/*判断是否使用 mapstruct convert*/
if ((config.getUseConvert() != null && config.getUseConvert())
/*增删查改时使用*/
|| (config.getActiveCrud() != null && config.getActiveCrud())) {
/*convert 模板*/
focList.add(convertFileOutConfig(config, CodeGeneratorEnumVal.CONVERT));
}
/*使用增删查改*/
if (config.getActiveCrud() != null && config.getActiveCrud()) {
/*exception 模板*/
focList.add(convertFileOutConfig(config, CodeGeneratorEnumVal.EXCEPTION));
/*exception enum 模板*/
focList.add(convertFileOutConfig(config, CodeGeneratorEnumVal.EXCEPTION_ENUM));
/*exception handler 模板*/
focList.add(convertFileOutConfig(config, CodeGeneratorEnumVal.EXCEPTION_HANDLER));
}
cfg.setFileOutConfigList(focList);
return cfg;
}

/**
* 加载文件输出配置
*
* @param config 配置
* @param enumVal 模板
* @return mybatis plus 文件输出配置
*/
private static FileOutConfig convertFileOutConfig(CodeGeneratorConfig config, CodeGeneratorEnumVal enumVal) {
return new FileOutConfig(enumVal.getTemplate()) {
@Override
public String outputFile(TableInfo tableInfo) {
/*指定模板生,自定义生成文件到哪个地方*/
return config.getModuleOutPutDir() + File.separator +
enumVal.getPackageName().replace(".", File.separator) +
File.separator +
String.format(enumVal.getClassName(), tableInfo.getEntityName()) + StringPool.DOT_JAVA;
}
};
}
}

CustomFreemarkerTemplateEngine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package com.gt.code.generator.util.template;

import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.config.ConstVal;
import com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.engine.AbstractTemplateEngine;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import com.gt.code.generator.config.CodeGeneratorConfig;
import com.gt.code.generator.config.CodeGeneratorEnumVal;
import com.gt.code.generator.util.CustomTableInfo;
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;

import java.io.*;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
* 自定义 Freemarker 模板引擎
*
* @author maxzhao
* @date 2021-08-28 08:36
*/
@Slf4j
public class CustomFreemarkerTemplateEngine extends AbstractTemplateEngine {
/**
* freemarker 配置
*/
private Configuration configuration;
/**
* zip 输出流
*/
@Setter
private ZipOutputStream zipOutputStream;
/**
* 自定义配置
*/
private final CodeGeneratorConfig config;

/**
* 初始化
*
* @param config 自定义配置
*/
public CustomFreemarkerTemplateEngine(CodeGeneratorConfig config) {
this.config = config;
}

@Override
public CustomFreemarkerTemplateEngine init(ConfigBuilder configBuilder) {
super.init(configBuilder);
configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
configuration.setDefaultEncoding(ConstVal.UTF8);
configuration.setClassForTemplateLoading(FreemarkerTemplateEngine.class, StringPool.SLASH);
return this;
}


@Override
public void writer(Map<String, Object> objectMap, String templatePath, String outputFile) throws Exception {
Template template = configuration.getTemplate(templatePath);
try (FileOutputStream fileOutputStream = new FileOutputStream(outputFile)) {
template.process(objectMap, new OutputStreamWriter(fileOutputStream, ConstVal.UTF8));
}
log.debug("模板:" + templatePath + "; 文件:" + outputFile);
if (config.getUseZip()) {
InputStream inputStream = null;
BufferedInputStream bufferedInputStream = null;
zipOutputStream.putNextEntry(new ZipEntry(outputFile.replace(config.getOutPutDir(), "")));
byte[] buff = new byte[4096];
int len;
try {
inputStream = new FileInputStream(outputFile);
/*读取文件*/
bufferedInputStream = new BufferedInputStream(inputStream);
/*判断:当前已经传递的数据大小+当前已有的长度,小于总长度时,*/
while ((len = bufferedInputStream.read(buff)) != -1) {
zipOutputStream.write(buff, 0, len);
}
} catch (Exception e) {
log.error("代码生成器:输出ZIP错误:模板 :{}; 文件:{}", templatePath, outputFile);
log.error("代码生成器:输出ZIP错误", e);
} finally {
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(bufferedInputStream);
}
}
}


/**
* 获取模板路径<br>
* 在配置时,一般不配置 .ftl 文件,这是 Freemarker 模板的习惯,所以这里要在路径上添加 .ftl
*
* @param filePath 模板路径
* @return 模板真实路径
*/
@Override
public String templateFilePath(String filePath) {
return filePath + ".ftl";
}

@Override
public Map<String, Object> getObjectMap(TableInfo tableInfo) {
Map<String, Object> objectMap = super.getObjectMap(tableInfo);
/*获取类 Package*/
Map<String, String> packageInfo = super.getConfigBuilder().getPackageInfo();
String controllerPackage = packageInfo.get("Controller");
/*基础 package*/
String basePackage = controllerPackage.substring(0, controllerPackage.lastIndexOf(CodeGeneratorEnumVal.CONTROLLER.getPackageName()));
packageInfo.put("basePackage", basePackage);
/*自定义表数据*/
CustomTableInfo customTableInfo = CustomTableInfo.getInstance(tableInfo);
/*使用增删改查*/
objectMap.put("activeCrud", config.getActiveCrud());
if (config.getActiveCrud() != null && config.getActiveCrud()) {
/*使用增删改查时,添加代码引入*/
packageInfo.put("ExceptionHandler", basePackage + "exception.handler");
packageInfo.put("Exception", basePackage + "exception");
packageInfo.put("ExceptionEnum", basePackage + "exception");
customTableInfo.initImportPackage(packageInfo);
}

/*每个类的 package*/
objectMap.put("packageInfo", packageInfo);
/*自定义表数据*/
objectMap.put("table", customTableInfo);
return objectMap;
}
}

CustomTableInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package com.gt.code.generator.util;

import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.config.po.TableField;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.gt.code.generator.config.CodeGeneratorEnumVal;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.springframework.util.StringUtils;

import java.util.*;


/**
* 自定义表配置
*
* @author maxzhao
* @date 2021-08-29 10:47
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
public class CustomTableInfo extends TableInfo {
/**
* 主键类型,默认为 String
*/
private String idTypeClassName;
private String dtoName;
private String paramsName;
private String voName;
private String convertName;
private String serviceBeanName;
private String mapperBeanName;
private String convertBeanName;
/**
* 异常类名
*/
private String exceptionName;
/**
* 异常枚举
*/
private String exceptionEnumName;
/**
* 异常处理类名
*/
private String exceptionHandlerName;
/**
* 使用 crud 时的 CONTROLLER 引入
*/
private final Set<String> apiImportPackages = new HashSet<>();
/**
* 使用 crud 时的 SERVICE 接口引入
*/
private final Set<String> serviceImportPackages = new HashSet<>();
/**
* 使用 crud 时的 SERVICE 实现引入
*/
private final Set<String> serviceImplImportPackages = new HashSet<>();
/**
* 使用 crud 时的 通用 实现引入
*/
private final Set<String> crudImplImportPackages = new HashSet<>();

public CustomTableInfo(TableInfo tableInfo) {
this.setConvert(tableInfo.isConvert());
this.setEntityName(tableInfo.getEntityName());
List<TableField> fields = tableInfo.getFields();
this.setFields(fields);
for (TableField field : fields) {
if (field.isKeyFlag()) {
this.idTypeClassName = field.getColumnType().getType();
}
}
if (!StringUtils.hasText(this.idTypeClassName)) {
this.idTypeClassName = "String";
}
this.getImportPackages().addAll(tableInfo.getImportPackages());
this.setName(tableInfo.getName());
this.setComment(tableInfo.getComment());
this.setEntityName(tableInfo.getEntityName());
this.setMapperName(tableInfo.getMapperName());
this.setXmlName(tableInfo.getXmlName());
this.setServiceName(tableInfo.getServiceName());
this.setServiceImplName(tableInfo.getServiceImplName());
this.setControllerName(tableInfo.getControllerName());
this.setCommonFields(tableInfo.getCommonFields());
this.setFieldNames(tableInfo.getFieldNames());


}


public static CustomTableInfo getInstance(TableInfo tableInfo) {
CustomTableInfo customTableInfo = new CustomTableInfo(tableInfo);

customTableInfo.setDtoName(String.format(CodeGeneratorEnumVal.DTO.getClassName(), tableInfo.getEntityName()));
customTableInfo.setParamsName(String.format(CodeGeneratorEnumVal.PARAMS.getClassName(), tableInfo.getEntityName()));
customTableInfo.setVoName(String.format(CodeGeneratorEnumVal.VO.getClassName(), tableInfo.getEntityName()));
customTableInfo.setConvertName(String.format(CodeGeneratorEnumVal.CONVERT.getClassName(), tableInfo.getEntityName()));

String serviceBeanName = customTableInfo.getServiceName().substring(1);
serviceBeanName = serviceBeanName.substring(0, 1).toLowerCase() + serviceBeanName.substring(1);
customTableInfo.setServiceBeanName(serviceBeanName);
String mapperBeanName = customTableInfo.getMapperName();
mapperBeanName = mapperBeanName.substring(0, 1).toLowerCase() + mapperBeanName.substring(1);
customTableInfo.setMapperBeanName(mapperBeanName);
customTableInfo.setConvertBeanName(customTableInfo.getConvertName().substring(0, 1).toLowerCase(Locale.ROOT) +
customTableInfo.getConvertName().substring(1));

/*异常类名 */
customTableInfo.setExceptionName(String.format(CodeGeneratorEnumVal.EXCEPTION.getClassName(), tableInfo.getEntityName()));
customTableInfo.setExceptionEnumName(String.format(CodeGeneratorEnumVal.EXCEPTION_ENUM.getClassName(), tableInfo.getEntityName()));
customTableInfo.setExceptionHandlerName(String.format(CodeGeneratorEnumVal.EXCEPTION_HANDLER.getClassName(), tableInfo.getEntityName()));
return customTableInfo;
}

/**
* 初始化导入依赖
*
* @param packageInfo 所有的 package
*/
public void initImportPackage(Map<String, String> packageInfo) {
apiImportPackages.add(java.util.List.class.getCanonicalName());
apiImportPackages.add(org.springframework.http.ResponseEntity.class.getCanonicalName());

serviceImportPackages.add(java.util.List.class.getCanonicalName());
serviceImplImportPackages.add(java.util.List.class.getCanonicalName());
serviceImplImportPackages.add(packageInfo.get("Convert") + StringPool.DOT + this.getConvertName());
serviceImplImportPackages.add(packageInfo.get("Exception") + StringPool.DOT + this.getExceptionName());
serviceImplImportPackages.add(packageInfo.get("ExceptionEnum") + StringPool.DOT + this.getExceptionEnumName());
serviceImplImportPackages.add(packageInfo.get("ExceptionHandler") + StringPool.DOT + this.getExceptionHandlerName());
serviceImplImportPackages.add(com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper.class.getCanonicalName());


crudImplImportPackages.add(com.baomidou.mybatisplus.core.metadata.IPage.class.getCanonicalName());
crudImplImportPackages.add(com.baomidou.mybatisplus.extension.plugins.pagination.Page.class.getCanonicalName());
crudImplImportPackages.add(packageInfo.get("Dto") + StringPool.DOT + this.dtoName);
crudImplImportPackages.add(packageInfo.get("Params") + StringPool.DOT + this.paramsName);
crudImplImportPackages.add(packageInfo.get("Vo") + StringPool.DOT + this.voName);
crudImplImportPackages.add(packageInfo.get("Entity") + StringPool.DOT + this.getEntityName());

}
}

本文地址: https://github.com/maxzhao-it/blog/post/15751/

配置版权信息

打开setting-Editor-Copyright-CopyrightProfiles

变量配置

名称 类型 说明
$today DateInfo 当前日期时间对象
$file.fileName String 当前文件的名称
$file.pathName String 当前文件的完整路径
$file.className String 当前文件的类名
$file.qualifiedClassName String 当前文件的权限定名
$file.lastModified DateInfo 上一次修改的日期时间对象
$project.name String 当前项目名
$module.name String 当前 Module 名
$username String 当前用户名(系统用户名)

DateInfo配置信息

名称 类型 说明
year int 当前年份
month int 当前月份
day int 当前日期(1-31)
hour int 当前小时(0-11)
hour24 int 当前小时(0-23)
minute int 当前分钟(0-59)
second int 当前秒数(0-59)
format(String format) String 时间日期格式化

配置 Scope

打开setting-Editor-Copyright

image-20220615110322148

快速引入

java文件的类上方按Alt + Ins

本文地址: https://github.com/maxzhao-it/blog/post/8bda86f41/

前言

Java 操作图片,实现图片的裁剪

裁剪图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class ImageUtil {
/**
* 获取人脸图片数据
*
* @param pictureBytes 照片数据
* @param contentType 请求内容类型
* @param faceName 人脸模型名称
* @return 人脸图片数据
*/
private byte[] getFaceHead(String faceName, byte[] pictureBytes, MediaType contentType) {
/*获取人脸位置与大小*/
double[] faceDetectResult = this.getFaceDetectResult(faceName, pictureBytes, contentType);
if (faceDetectResult[0] == 0
&& faceDetectResult[1] == 0
&& (faceDetectResult[2] == 0
|| faceDetectResult[3] == 0)) {
log.warn("图片无法裁剪,使用原图片");
return pictureBytes;
}
/*裁剪图片*/
try {
/*获取图片 buffer*/
BufferedImage pictureImage = ImageIO.read(new ByteArrayInputStream(pictureBytes));
final int width = pictureImage.getWidth();
final int height = pictureImage.getHeight();
/*图片裁剪*/
int x = (int) (faceDetectResult[0] * width);
/*坐标 - 10*/
x = Math.max((x - 10), 0);
int y = (int) (faceDetectResult[1] * height);
/*坐标 - 10*/
y = Math.max((y - 10), 0);
int w = (int) (faceDetectResult[2] * width);
/*宽度 + 10*/
w = Math.min((w + 10), width - x);
int h = (int) (faceDetectResult[3] * height);
/*高度 + 10*/
h = Math.min((h + 10), height - y);
BufferedImage headImage = pictureImage.getSubimage(x, y, w, h);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ImageIO.write(headImage, "jpg", out);
return out.toByteArray();
} catch (IOException e) {
log.warn("裁剪图片失败使用原照片 {}", e.getMessage());
throw new RuntimeException("裁剪图片失败");
}
}
}

附录

1 图片操作类

  1. Image
  2. BufferedImage
    1. getSubimage 图片裁剪
  3. ImageIO
    1. read 读取图片
    2. wirte 写出图片

本文地址: https://github.com/maxzhao-it/blog/post/8bda86f4/

前言

Java 操作图片,实现图片的翻转

裁剪图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class ImageUtil {
/**
* 测试
*/
public void test() {
try {
FileInputStream in = new FileInputStream("D:\\Pictures\\temp\\1.jpg");
byte[] bytes = new byte[in.available()];
int read = in.read(bytes);
in.close();
bytes = getFaceHead(getFaceHead);
OutputStream outputStream = new FileOutputStream("D:\\Pictures\\temp\\" + System.currentTimeMillis() + ".jpg");
outputStream.write(bytes);
outputStream.close();
} catch (Exception e) {
throw new RuntimeException("翻转图片失败");
}
}

/**
* 水平翻转图片
*
* @param pictureBytes 照片数据
* @return 人脸图片数据
*/
private byte[] getFaceHead(byte[] pictureBytes) {
/*翻转图片*/
try {
/*获取图片 buffer*/
BufferedImage pictureImage = ImageIO.read(new ByteArrayInputStream(pictureBytes));
final int width = pictureImage.getWidth();
final int height = pictureImage.getHeight();
/*读取所有的像素*/
int[] rgbs = pictureImage.getRGB(0, 0, width, height, null, 0, width);
/*水平切换像素*/
int temp;
for (int row = 0; row < height; row++) {
for (int col = 0; col < width / 2; col++) {
temp = rgbs[row * width + col];
rgbs[row * width + col] = rgbs[row * width + (width - 1 - col)];
rgbs[row * width + (width - 1 - col)] = temp;
}
}
/*写入像素*/
pictureImage.setRGB(0, 0, width, height, rgbs, 0, width);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ImageIO.write(pictureImage, "jpg", out);
return out.toByteArray();
} catch (IOException e) {
throw new RuntimeException("翻转图片失败");
}
}
}

附录

1 图片操作类

  1. Image
  2. BufferedImage
    1. getSubimage 图片裁剪
    2. getRGB 获取像素
  3. ImageIO
    1. read 读取图片
    2. wirte 写出图片

本文地址: https://github.com/maxzhao-it/blog/post/1a9b93f6/

前言

JS 实现图片的裁剪

JS 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<html>

<body>
<div id="images" style="width:100px;height:200px;"></div>
<img src="3.jpg"/>

</body>
<script>
var loadTimer;
var imgObject = new Image();
imgObject.src = '3.jpg';
imgObject.onLoad = onImgLoaded();
console.log('imgObject.naturalHeight==============>', imgObject.naturalHeight);
console.log('imgObject.naturalWidth==============>', imgObject.naturalWidth);

function onImgLoaded() {
if (loadTimer != null) clearTimeout(loadTimer);
if (!imgObject.complete) {
loadTimer = setTimeout(function () {
onImgLoaded();
}, 3);
} else {
onPreloadComplete();
}
}

function onPreloadComplete() {
//call the methods that will create a 64-bit version of thumbnail here.
var newImg = getImagePortion(imgObject,
0.17443209886550903* imgObject.naturalWidth,
0.1213589534163475 * imgObject.naturalHeight,
0.44079777598381042 * imgObject.naturalWidth,
0.44744855165481567 * imgObject.naturalHeight,
2);
console.log("newImg", newImg);
//place image in appropriate div
document.getElementById("images").innerHTML = "<img alt=\"\" src=\"" + newImg + "\" />";
}

function getImagePortion(imgObj, newWidth, newHeight, startX, startY, ratio) {
console.log('newWidth', newWidth);
console.log('newHeight', newHeight);
console.log('startX', startX);
console.log('startY', startY);

// newWidth = Math.floor(newWidth);
// newHeight = Math.floor(newHeight);
// startX = Math.floor(startX);
// startY = Math.floor(startY);
/* the parameters: - the image element - the new width - the new height - the x point we start taking pixels - the y point we start taking pixels - the ratio */
//set up canvas for thumbnail
var tnCanvas = document.createElement('canvas');
var tnCanvasContext = tnCanvas.getContext('2d');
tnCanvas.width = newWidth;
tnCanvas.height = newHeight;

/* use the sourceCanvas to duplicate the entire image. This step was crucial for iOS4 and under devices. Follow the link at the end of this post to see what happens when you don’t do this */
var bufferCanvas = document.createElement('canvas');
var bufferContext = bufferCanvas.getContext('2d');
bufferCanvas.width = imgObj.width;
bufferCanvas.height = imgObj.height;
bufferContext.drawImage(imgObj, 0, 0);

/* now we use the drawImage method to take the pixels from our bufferCanvas and draw them into our thumbnail canvas */
tnCanvasContext.drawImage(bufferCanvas, startX, startY, newWidth * ratio, newHeight * ratio, 0, 0, newWidth, newHeight);
return tnCanvas.toDataURL();
}

setTimeout(function () {

}, 1000);
</script>

</html>

本文地址: https://github.com/maxzhao-it/blog/post/5949bf48/

前言

yum快速安装

官方介绍

Citus简介

Citus以插件的方式扩展到postgresql中,独立于postgresql内核,所以能很快的跟上pg主版本的更新,部署也比较简单,是现在非常流行的分布式方案。Citus在苏宁有大规模应用,微软也提供citus的商业支持。下面是citus的架构:

2018042723003371

Citus
节点主要分为协调节点和工作节点,协调节点不存储真实数据,只存储数据分布的元信息,实际的数据被分成若干分片,打散到不同worker节点中,应用连接协调节点,协调节点进行sql解析,生成分布式执行计划,下发到worker节点执行,cn将结果汇总返回客户端。

来自 PostgreSQL的几种分布式架构对比

其它特性:

● PostgreSQL兼容

● 水平扩展

● 实时并发查

● 快速数据加载

● 实时增删改查

● 持分布式事务

● 支持常用DDL

交互

  1. 客户端应用访问数据时只和CN节点交互。
  2. CN收到SQL请求后,生成分布式执行计划,并将各个子任务下发到相应的Worker节点,之后收集Worker的结果,经过处理后返回最终结果给客户端。

服务器

  • 192.168.2.100:5432 master
  • 192.168.2.101:5432 worker101
  • 192.168.2.102:5432 worker102
  • 192.168.2.103:5432 worker103
  • 192.168.2.104:5432 worker104

安装

全部节点上的操作

安装 citus

1
2
3
4
5
curl https://install.citusdata.com/community/rpm.sh | sudo bash
# 查看所有版本
yum list|grep citus
# 安装与自己PG对应的版本
yum install -y citus83_10

查看 PG 配置文件路径

1
psql -U postgres -c 'SHOW config_file'

预加载 citus 扩展

1
2
3
4
# preload citus extension
echo "shared_preload_libraries = 'citus'" | sudo tee -a /var/lib/pgsql/10/data/postgresql.conf
# 重启
systemctl restart postgresql-10

加载待所有的数据库中

1
sudo -i -u postgres psql -c "CREATE EXTENSION citus;"

成功输出

1
CREATE EXTENSION

Worker节点上执行

1
2
3
echo "host    all             all             192.168.2.100/32            trust" >> /var/lib/pgsql/10/data/pg_hba.conf
# 重启
systemctl restart postgresql-10

在主节点上执行

添加 worker 节点

1
2
3
4
5
# 新版:citus_add_node  旧版:master_add_node
sudo -i -u postgres psql -c "SELECT * from master_add_node('192.168.2.102', 5432);"
sudo -i -u postgres psql -c "SELECT * from master_add_node('192.168.2.102', 5432);"
sudo -i -u postgres psql -c "SELECT * from master_add_node('192.168.2.103', 5432);"
sudo -i -u postgres psql -c "SELECT * from master_add_node('192.168.2.104', 5432);"

查看节点

1
2
# 新版:citus_get_active_worker_nodes  旧版:master_get_active_worker_nodes
sudo -i -u postgres psql -c "SELECT * FROM master_get_active_worker_nodes();"
1
2
3
4
5
6
   node_name   | node_port
---------------+-----------
192.168.2.101 | 5432
192.168.2.103 | 5432
192.168.2.102 | 5432
192.168.2.104 | 5432

使用

1
sudo -i -u postgres psql

这个,有点复杂。

本文地址: https://github.com/maxzhao-it/blog/post/8e2fc2ac/

前言

MyBatisPlus:AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller
等各个模块的代码,极大的提升了开发效率。

但是,自带的模板还是有点差强人意,这里就需要自定义模板来实现。

需要了解自定义代码生成器

参考:MyBatisPlus代码生成器自定义FreemarkerEngine

这里以新建一个 Dto 举例。

配置

配置模板信息

创建枚举类CodeGeneratorEnumVal(详见附录)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* 常用配置信息<br>
* 自定义模板需要添加 .ftl<br>
*
* @author maxzhao
* @date 2021-08-26 23:01
*/
@Getter
public enum CodeGeneratorEnumVal {
/**
* controller<br>
* * 非自定义,模板路径不需要 .ftl
*/
CONTROLLER("templates/api/z.controller.java", "api", "%sController"),
/**
* dto
*/
DTO("templates/api/dto/z.dto.java.ftl", "api.dto", "%sDto"),
/*********/
/**
* 模板
*/
private final String template;
/**
* 模板
*/
private final String packageName;
/**
* 模板
*/
private final String className;
}

配置 AutoGenerator

1
2
3
4
5
6
7
8
9
public class CodeGeneratorFactory {
public static byte[] generate(CodeGeneratorConfig config) {
CodeGeneratorDbConfig dbConfig = config.getDbConfig();
//region 注入配置
mpg.setCfg(configInjection(config));
/*************/
return null;
}
}

configInjection方法

这里配置的是

  1. 自定义模板的路径( map cfg)
  2. 输出文件的路径 (PathInfo)
  3. 通用模板参数
    • 包路径 (packageInfo)
    • 基础的 Entity、Mapper、Mapper XML、Service、Controller生成路径
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class CodeGeneratorFactory {
private static InjectionConfig configInjection(CodeGeneratorConfig config) {
// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
String controllerPackage = this.getConfig().getPackageInfo().get("Controller");
this.getConfig().getPackageInfo().put("Dto", controllerPackage + ".dto");

/**************************/
String xmlPath = this.getConfig().getPathInfo().get("xml_path");
/*修改 xml 生成路径*/
this.getConfig().getPathInfo().put("xml_path", xmlPath.replace(CodeGeneratorVal.JAVA_SOURCE_PATH, CodeGeneratorVal.XML_SOURCE_PATH));
Map<String, Object> map = new HashMap<>(4);
/*自定义cfg引用*/
this.setMap(map);
}
};
List<FileOutConfig> focList = new ArrayList<>();
/*dto 模板*/
focList.add(convertFileOutConfig(config, CodeGeneratorEnumVal.DTO));
/**************************/
cfg.setFileOutConfigList(focList);
return cfg;
}
/**
* 加载文件输出配置
*
* @param config 配置
* @param enumVal 模板
* @return mybatis plus 文件输出配置
*/
private static FileOutConfig convertFileOutConfig(CodeGeneratorConfig config, CodeGeneratorEnumVal enumVal) {
return new FileOutConfig(enumVal.getTemplate()) {
@Override
public String outputFile(TableInfo tableInfo) {
/*指定模板生,自定义生成文件到哪个地方*/
return config.getModuleOutPutDir() + File.separator +
enumVal.getPackageName().replace(".", File.separator) +
File.separator +
String.format(enumVal.getClassName(), tableInfo.getEntityName()) + StringPool.DOT_JAVA;
}
};
}
}

模板文件z.dto.java.ftl

resources/templates/api/dto/z.dto.java.ftl 下创建文件,dto模板文件与 entity文件是比较相似的,所以直接从 entity.java.ftl拷贝一下,然后修改自己的自定义模板逻辑。

主要修改点

1
2
3
4
5
package ${package.Dto};
/**去掉 @Table @TableId @TableField 等Mybatis 标签***/
public class ${table.dtoName} implements Serializable {
/*****/
}

附录

CodeGeneratorEnumVal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
package com.gt.code.generator.config;

import lombok.Getter;

/**
* 常用配置信息<br>
* 自定义模板需要添加 .ftl<br>
*
* @author maxzhao
* @date 2021-08-26 23:01
*/
@Getter
public enum CodeGeneratorEnumVal {
/**
* controller<br>
* * 非自定义,模板路径不需要 .ftl
*/
CONTROLLER("templates/api/z.controller.java", "api", "%sDto"),
/**
* dto
*/
DTO("templates/api/dto/z.dto.java.ftl", "api.dto", "%sDto"),

/**
* params
*/
PARAMS("templates/api/params/z.params.java.ftl", "api.params", "%sParams"),

/**
* vo
*/
VO("templates/api/vo/z.vo.java.ftl", "api.vo", "%sVo"),


/**
* entity<br>
* * 非自定义,模板路径不需要 .ftl
*/
ENTITY("templates/model/entity/z.entity.java", "model.entity", "%"),
/**
* service<br>
* 非自定义,模板路径不需要 .ftl
*/
SERVICE("templates/service/z.service.java", "service", "%"),
/**
* service impl<br>
* * 非自定义,模板路径不需要 .ftl
*/
SERVICE_IMPL("templates/service/impl/z.serviceImpl.java", "service.impl", "%"),
/**
* mapper<br>
* * 非自定义,模板路径不需要 .ftl
*/
MAPPER("templates/mapper/z.mapper.java", "mapper", "%Mapper"),
/**
* mapper xml<br>
* * 非自定义,模板路径不需要 .ftl
*/
TEMPLATE_XML("templates/mapper/z.mapper.xml", "mapper", "%Mapper"),

/**
* convert
*/
CONVERT("templates/util/convert/z.convert.java.ftl", "util.convert", "%sConvert"),

/**
* 异常
*/
EXCEPTION("templates/exception/z.exception.java.ftl", "exception", "%sException"),

/**
* 异常类型
*/
EXCEPTION_ENUM("templates/exception/z.exceptionEnum.java.ftl", "exception", "%sExceptionEnum"),

/**
* 异常类型
*/
EXCEPTION_HANDLER("templates/exception/handler/z.exceptionHandler.java.ftl", "exception.handler", "%sExceptionHandler");

/**
* 模板
*/
private final String template;
/**
* 模板
*/
private final String packageName;
/**
* 模板
*/
private final String className;

CodeGeneratorEnumVal(String template, String packageName, String className) {
this.template = template;
this.packageName = packageName;
this.className = className;
}

@Override
public String toString() {
return super.toString();
}
}

CodeGeneratorFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
package com.gt.code.generator.factory;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.converts.OracleTypeConvert;
import com.baomidou.mybatisplus.generator.config.po.TableFill;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.IColumnType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.gt.code.generator.config.CodeGeneratorConfig;
import com.gt.code.generator.config.CodeGeneratorDbConfig;
import com.gt.code.generator.config.CodeGeneratorEnumVal;
import com.gt.code.generator.config.CodeGeneratorVal;
import com.gt.code.generator.util.template.CustomFreemarkerTemplateEngine;
import org.apache.commons.io.IOUtils;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.*;
import java.util.zip.ZipOutputStream;

/**
* 生成代码工厂<br>
*
* @author maxzhao
* @date 2021-08-27
*/
public class CodeGeneratorFactory {
/**
* 自动填充
*/
private static final List<TableFill> TABLE_FILL_LIST = Arrays.asList(
new TableFill("createTime", FieldFill.INSERT),
new TableFill("updateTime", FieldFill.UPDATE),
new TableFill("delStatus", FieldFill.INSERT_UPDATE));

private CodeGeneratorFactory() {
}

public static byte[] generate(CodeGeneratorConfig config) {
AutoGenerator mpg = new AutoGenerator();
CodeGeneratorDbConfig dbConfig = config.getDbConfig();
/*输出路径*/
String outPutDir = config.getFullOutPutDir();
//region 注入配置
mpg.setCfg(configInjection(config));
//endregion
//region 数据源配置
DataSourceConfig dataSource = dbConfig.generatorDataSourceConfig();
if (dataSource.getDbType() == DbType.ORACLE) {
dataSource.setTypeConvert(new OracleTypeConvert() {
@Override
public IColumnType processTypeConvert(GlobalConfig globalConfig, String fieldType) {
if (fieldType.toLowerCase(Locale.ROOT).matches("")) {

}
return super.processTypeConvert(globalConfig, fieldType);
}
});
}
mpg.setDataSource(dataSource);
//endregion
//region 表策略配置
StrategyConfig strategy = new StrategyConfig();
/*驼峰*/
strategy.setNaming(NamingStrategy.underline_to_camel);
/*驼峰*/
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
/*使用 lombok*/
strategy.setEntityLombokModel(true);
/*默认使用 restfull风格*/
strategy.setRestControllerStyle(true);
/*标*/
strategy.setInclude(config.getTablesNames());
/*驼峰转连字符串*/
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix(config.getTablePrefix());
/*字段添加 @TableField*/
strategy.setEntityTableFieldAnnotationEnable(true);
strategy.setTableFillList(TABLE_FILL_LIST);
mpg.setStrategy(strategy);
//endregion
//region 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName(config.getModuleName());
pc.setParent(config.getParentPackage());
/*设置 controller 文件 包名*/
pc.setController(CodeGeneratorEnumVal.CONTROLLER.getPackageName());
/*设置 xml 文件 包名*/
pc.setXml("mapper");
/*设置 entity 文件 包名*/
pc.setEntity("model.entity");
pc.setXml("mapper");
mpg.setPackageInfo(pc);
//endregion
//region 模板配置
TemplateConfig templateConfig = new TemplateConfig()
.setEntity(CodeGeneratorEnumVal.ENTITY.getTemplate())
.setController(CodeGeneratorEnumVal.CONTROLLER.getTemplate())
.setService(CodeGeneratorEnumVal.SERVICE.getTemplate())
.setServiceImpl(CodeGeneratorEnumVal.SERVICE_IMPL.getTemplate())
.setMapper(CodeGeneratorEnumVal.MAPPER.getTemplate())
.setXml(CodeGeneratorEnumVal.TEMPLATE_XML.getTemplate());
mpg.setTemplate(templateConfig);
//endregion
//region 全局配置
/*默认使用雪花算法*/
GlobalConfig gc = new GlobalConfig();
gc.setOutputDir(outPutDir);
gc.setAuthor(config.getAuthor());
gc.setOpen(false);
/*默认不覆盖,如果文件存在,将不会再生成,配置true就是覆盖*/
gc.setFileOverride(true);
/*配置 swagger2*/
gc.setSwagger2(config.getSwagger2());
/*开启 ActiveRecord 模式*/
gc.setActiveRecord(config.getActiveRecord());
mpg.setGlobalConfig(gc);

//endregion
//region 模板引擎
// 选择 freemarker 引擎需要指定如下加,注意 pom 依赖必须有!
CustomFreemarkerTemplateEngine templateEngine = new CustomFreemarkerTemplateEngine(config);
ByteArrayOutputStream outputStream = null;
ZipOutputStream zip = null;
if (config.getUseZip()) {
/*下载zip文件 字节输出流*/
outputStream = new ByteArrayOutputStream();
zip = new ZipOutputStream(outputStream);
templateEngine.setZipOutputStream(zip);
/*下载zip ,会覆盖文件*/
gc.setFileOverride(true);
}
mpg.setTemplateEngine(templateEngine);
//endregion
mpg.execute();
if (config.getUseZip()) {
/*获取结果*/
//等同于自己写判断然后关闭
IOUtils.closeQuietly(zip);
return outputStream != null ? outputStream.toByteArray() : null;
}
return null;
}

/**
* 添加自动填充
*
* @param tableFill 新的自动填充
*/
public static void addTableFill(TableFill tableFill) {
TABLE_FILL_LIST.add(tableFill);
}

/**
* 配置自定义参数/属性
*
* @param config 配置信息
*/
private static InjectionConfig configInjection(CodeGeneratorConfig config) {
// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
String controllerPackage = this.getConfig().getPackageInfo().get("Controller");
this.getConfig().getPackageInfo().put("Dto", controllerPackage + ".dto");
this.getConfig().getPackageInfo().put("Params", controllerPackage + ".params");
this.getConfig().getPackageInfo().put("Vo", controllerPackage + ".vo");
this.getConfig().getPackageInfo().put("Convert", controllerPackage.substring(0, controllerPackage.lastIndexOf(CodeGeneratorEnumVal.CONTROLLER.getPackageName())) + CodeGeneratorEnumVal.CONVERT.getPackageName());
String xmlPath = this.getConfig().getPathInfo().get("xml_path");
/*修改 xml 生成路径*/
this.getConfig().getPathInfo().put("xml_path", xmlPath.replace(CodeGeneratorVal.JAVA_SOURCE_PATH, CodeGeneratorVal.XML_SOURCE_PATH));
Map<String, Object> map = new HashMap<>(4);
/*自定义cfg引用*/
this.setMap(map);
}
};
List<FileOutConfig> focList = new ArrayList<>();
/*dto 模板*/
focList.add(convertFileOutConfig(config, CodeGeneratorEnumVal.DTO));
/*params 模板*/
focList.add(convertFileOutConfig(config, CodeGeneratorEnumVal.PARAMS));
/*vo 模板*/
focList.add(convertFileOutConfig(config, CodeGeneratorEnumVal.VO));
/*判断是否使用 mapstruct convert*/
if ((config.getUseConvert() != null && config.getUseConvert())
/*增删查改时使用*/
|| (config.getActiveCrud() != null && config.getActiveCrud())) {
/*convert 模板*/
focList.add(convertFileOutConfig(config, CodeGeneratorEnumVal.CONVERT));
}
/*使用增删查改*/
if (config.getActiveCrud() != null && config.getActiveCrud()) {
/*exception 模板*/
focList.add(convertFileOutConfig(config, CodeGeneratorEnumVal.EXCEPTION));
/*exception enum 模板*/
focList.add(convertFileOutConfig(config, CodeGeneratorEnumVal.EXCEPTION_ENUM));
/*exception handler 模板*/
focList.add(convertFileOutConfig(config, CodeGeneratorEnumVal.EXCEPTION_HANDLER));
}
cfg.setFileOutConfigList(focList);
return cfg;
}

/**
* 加载文件输出配置
*
* @param config 配置
* @param enumVal 模板
* @return mybatis plus 文件输出配置
*/
private static FileOutConfig convertFileOutConfig(CodeGeneratorConfig config, CodeGeneratorEnumVal enumVal) {
return new FileOutConfig(enumVal.getTemplate()) {
@Override
public String outputFile(TableInfo tableInfo) {
/*指定模板生,自定义生成文件到哪个地方*/
return config.getModuleOutPutDir() + File.separator +
enumVal.getPackageName().replace(".", File.separator) +
File.separator +
String.format(enumVal.getClassName(), tableInfo.getEntityName()) + StringPool.DOT_JAVA;
}
};
}

}

CodeGeneratorConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
package com.gt.code.generator.config;

import io.swagger.annotations.ApiModelProperty;
import lombok.AccessLevel;
import lombok.Data;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.springframework.util.StringUtils;

import java.io.File;
import java.util.Optional;

/**
* 代码生成器基础配置<br>
* Bean 注入 <br>
* 静态注入 <br>
*
* @author maxzhao
* @date 2021-08-26 22:16
*/
@Data
@Accessors
public class CodeGeneratorConfig {
/**
* 输出路径,默认当前项目路径
*/
private String outPutDir;
/**
* 输出 Java 文件的起始路径<br>
* 只读
*/
@Setter(AccessLevel.NONE)
private String fullOutPutDir;
/**
* 输出具体模块代码的文件的起始路径<br>
* {@link #fullOutPutDir} + {@link #parentPackage} + {@link #moduleName} <br>
* 只读
*/
@Setter(AccessLevel.NONE)
private String moduleOutPutDir;

/**
* 输出路径,默认当前项目路径
*
* @param outPutDir 输出路径
*/
public void setOutPutDir(String outPutDir) {
this.outPutDir = StringUtils.hasText(outPutDir)
? outPutDir
: System.getProperty("user.dir");
}

/**
* 输出 Java 文件的起始路径
*
* @return 输出全路径 包含 src.main.java
*/
public String getFullOutPutDir() {
if (this.fullOutPutDir != null) {
return this.fullOutPutDir;
}
/*获取当前的项目路径*/
this.fullOutPutDir = this.outPutDir;
fullOutPutDir += File.separator + CodeGeneratorVal.JAVA_SOURCE_PATH;
fullOutPutDir = fullOutPutDir.replace("\\", File.separator)
.replace("/", File.separator)
.replace(File.separator + File.separator, File.separator);
return fullOutPutDir;
}

/**
* 获取输出模块的路径
*
* @return 输出全路径 包含 src.main.java
*/
public String getModuleOutPutDir() {
if (this.moduleOutPutDir != null) {
return this.moduleOutPutDir;
}
/*获取java 文件的输出路径*/
this.moduleOutPutDir = getFullOutPutDir();
this.moduleOutPutDir = (this.moduleOutPutDir + File.separator +
this.parentPackage + File.separator + this.moduleName)
.replace(".", File.separator)
.replace(File.separator + File.separator, File.separator);
return this.moduleOutPutDir;
}

/**
* 基础包名(默认 gt.maxzhao.boot)<br>
* default {@link CodeGeneratorVal#DEFAULT_PARENT_PACKAGE}
*/
private String parentPackage;

/**
* 基础包名(默认 gt.maxzhao.boot)
*
* @param parentPackage 基础包名
*/
public void setParentPackage(String parentPackage) {
this.parentPackage = StringUtils.hasText(parentPackage)
? parentPackage
: CodeGeneratorVal.DEFAULT_PARENT_PACKAGE;
}

/**
* 模块名(默认为null,格式为 xx.xxx)
*/
private String moduleName;
/**
* 作者
*/
private String author;
/**
* 表前缀
*/
private String[] tablePrefix;
/**
* 表名数组
*/
private String[] tablesNames;
/**
* 使用 mapstruct 的 convert,true 使用 ;false 不使用<br>
* default false
*/
private Boolean useConvert = false;

/**
* 使用 mapstruct 的 convert,true 使用 ;false 不使用
*
* @param useConvert true 使用 ;false 不使用
*/
public void setUseConvert(Boolean useConvert) {
this.useConvert = Optional.ofNullable(useConvert).orElse(false);
}

/**
* 开启 ActiveRecord 模式<br>
* default false
*/
private Boolean activeRecord = false;

/**
* 开启 ActiveRecord 模式<br>
* default false
*
* @param activeRecord true 使用 ;false 不使用
*/
public void setActiveRecord(Boolean activeRecord) {
this.activeRecord = Optional.ofNullable(activeRecord).orElse(false);
}

/**
* 使用 swagger2,true 使用 ;false 不使用<br>
* default true
*/
private Boolean swagger2 = true;

/**
* 使用 swagger2,true 使用 ;false 不使用<br>
* default true
*
* @param swagger2 true 使用 ;false 不使用
*/
public void setSwagger2(Boolean swagger2) {
this.swagger2 = Optional.ofNullable(swagger2).orElse(true);
}

/**
* 数据库配置
*/
private CodeGeneratorDbConfig dbConfig = null;
/**
* 使用zip文件下载<br>
* http 可以直接下载 zip 文件
*/
private Boolean useZip = false;
/**
* 自动生成crud<br>
* 默认使用
*/
private Boolean activeCrud = true;
/**
* 自动生成 exception<br>
* 默认不使用
*/
private Boolean activeException = true;
}

z.dto.java.ftl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package ${package.Dto};

<#list table.importPackages as pkg>
<#if pkg?contains("com.baomidou.mybatisplus.annotation")>
<#else>
import ${pkg};
</#if>
</#list>
<#if swagger2>
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
</#if>
<#if entityLombokModel>
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
</#if>
import org.springframework.format.annotation.DateTimeFormat;

/**
* <p>${table.comment!'TODO 待补充注解'}</p>
* <p>接口、服务等接收参数</p>
*
* @author ${author}
* @date ${date}
*/
<#if entityLombokModel>
@Data
<#if superEntityClass??>
@EqualsAndHashCode(callSuper = true)
<#else>
@EqualsAndHashCode(callSuper = false)
</#if>
@Accessors(chain = true)
</#if>
<#if swagger2>
@ApiModel(value="${table.dtoName}对象", description="${table.comment!'TODO 待补充注解'}")
</#if>
public class ${table.dtoName} implements Serializable {

<#if entitySerialVersionUID>
private static final long serialVersionUID = 1L;
</#if>
<#-- ---------- BEGIN 字段循环遍历 ---------->
<#list table.fields as field>
<#if field.keyFlag>
<#assign keyPropertyName="${field.propertyName}"/>
</#if>
/**
* ${field.comment!'TODO 待补充注解'}
*/
<#if field.comment!?length gt 0>
<#if swagger2>
@ApiModelProperty(value = "${field.comment!'TODO 待补充注解'}")
</#if>
</#if>
<#-- DateTimeFormat -->
<#if "LocalDateTime" == field.propertyType>
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
<#elseif "LocalDate" == field.propertyType>
@DateTimeFormat(pattern = "yyyy-MM-dd")
</#if>
private ${field.propertyType} ${field.propertyName};
</#list>
<#------------ END 字段循环遍历 ---------->

<#if !entityLombokModel>
<#list table.fields as field>
<#if field.propertyType == "boolean">
<#assign getprefix="is"/>
<#else>
<#assign getprefix="get"/>
</#if>
public ${field.propertyType} ${getprefix}${field.capitalName}() {
return ${field.propertyName};
}

<#if entityBuilderModel>
public ${entity} set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
<#else>
public void set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
</#if>
this.${field.propertyName} = ${field.propertyName};
<#if entityBuilderModel>
return this;
</#if>
}
</#list>
</#if>

<#if entityColumnConstant>
<#list table.fields as field>
public static final String ${field.name?upper_case} = "${field.name}";

</#list>
</#if>
}

本文地址: https://github.com/maxzhao-it/blog/post/49280/