搭配 MybatisPlusIEnum 类型,优化枚举显示

修改前

修改前

修改后

修改后

EnumConverter

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
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ReflectUtil;
import com.baomidou.mybatisplus.annotation.IEnum;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.core.converter.AnnotatedType;
import io.swagger.v3.core.converter.ModelConverter;
import io.swagger.v3.core.converter.ModelConverterContext;
import io.swagger.v3.oas.models.media.Schema;
import lombok.RequiredArgsConstructor;
import org.springdoc.core.providers.ObjectMapperProvider;
import org.springframework.stereotype.Component;

import java.util.Iterator;

@Component
@RequiredArgsConstructor
public class EnumConverter implements ModelConverter {

private final ObjectMapperProvider objectMapperProvider;

@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public Schema<?> resolve(AnnotatedType type, ModelConverterContext context, Iterator<ModelConverter> chain) {
Schema<?> nextSchema = chain.hasNext() ? chain.next().resolve(type, context, chain) : null;
ObjectMapper objectMapper = objectMapperProvider.jsonMapper();
JavaType javaType = objectMapper.constructType(type.getType());
if (javaType != null && javaType.isEnumType()) {
Class<Enum> enumClass = (Class<Enum>) javaType.getRawClass();
Enum[] enums = enumClass.getEnumConstants();
StringBuilder builder = new StringBuilder();
for (Enum en : enums) {
if (en instanceof IEnum) {
var value = ReflectUtil.getFieldValue(en, "value");
var description = ReflectUtil.getFieldValue(en, "description");
builder.append(String.format("- %s: %s\n", Convert.toStr(value), Convert.toStr(description)));
}
}
if (nextSchema != null && !builder.isEmpty()) {
nextSchema.setTitle(nextSchema.getDescription());
nextSchema.setDescription(builder.toString());
}
}
return nextSchema;
}

}

因为 mp 只能在传入 Entity 的情况下自动填充字段。

MybatisMetaAspect

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
import cn.hutool.core.util.ReflectUtil;
import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class MybatisMetaAspect {

private final ConcurrentMap<Class<?>, Object> entityMap = new ConcurrentHashMap<>();

@Pointcut("execution(* com.baomidou.mybatisplus.core.mapper.BaseMapper.update(com.baomidou.mybatisplus.core.conditions.Wrapper))")
public void update() {
}

@Around("update()")
public Object aroundUpdate(ProceedingJoinPoint pjp) {
try {
Object result = invoke(pjp);
if (result != null) {
return result;
}
return pjp.proceed(pjp.getArgs());
} catch (Throwable e) {
log.error("mybatis around update error", e);
}
return null;
}

private Object invoke(ProceedingJoinPoint pjp) {
Object[] args = pjp.getArgs();
if (args == null || args.length != 1) {
return null;
}
Object arg = args[0];
if (arg instanceof AbstractWrapper<?, ?, ?> wrapper) {
Class<?> entityClass = wrapper.getEntityClass();
if (entityClass == null) {
log.warn("Detected that the entity is empty, which will cause automatic filling to fail. Please use `new UpdateWrapper<>(new T())` or `new UpdateWrapper<>(T.class)` instead.");
return null;
}
Object entity = entityMap.get(entityClass);
if (entity == null) {
entity = ReflectUtil.newInstance(entityClass);
entityMap.put(entityClass, entity);
}
return ReflectUtil.invoke(pjp.getThis(), "update", entity, arg);
}
return null;
}

}

RedisAppender

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
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.ThrowableProxyUtil;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.api.async.RedisAsyncCommands;
import lombok.Getter;
import lombok.Setter;

import java.time.Duration;
import java.util.Date;

@Getter
@Setter
public class RedisAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {

private String host;
private Integer port;
private String password;
private String key;

private RedisClient redisClient;
private RedisAsyncCommands<String, String> async;

@Override
protected void append(ILoggingEvent event) {
if (redisClient == null) {
return;
}
LoggerEntity entity = new LoggerEntity();
entity.setTimestamp(new Date(event.getTimeStamp()));
entity.setLevel(event.getLevel().toString());
entity.setCaller(event.getLoggerName());
entity.setThread(event.getThreadName());
entity.setMessage(event.getFormattedMessage());
IThrowableProxy throwableProxy = event.getThrowableProxy();
if (throwableProxy != null) {
entity.setThrowable(ThrowableProxyUtil.asString(throwableProxy));
}
async.rpush(key, JSONUtil.toJsonStr(entity));
}

@Override
public void start() {
super.start();
initRedis();
}

private void initRedis() {
if (StrUtil.isEmpty(host) || StrUtil.isEmpty(key)) {
return;
}
try {
RedisURI uri = RedisURI
.builder()
.withHost(host)
.withPort(port)
.withPassword(password.toCharArray())
.withTimeout(Duration.ofSeconds(10))
.build();
redisClient = RedisClient.create(uri);
async = redisClient.connect().async();
} catch (Exception e) {
System.out.printf("Initialize Logger Redis Exception: %s\n", e.getMessage());
}
}

@Override
public void stop() {
closeRedis();
super.stop();
}

private void closeRedis() {
if (redisClient != null) {
redisClient.close();
}
}

}

logback-spring.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>
<configuration>

<include resource="org/springframework/boot/logging/logback/base.xml"/>

<appender name="REDIS" class="xxx.RedisAppender">
<host>${LOGGER_REDIS_HOST:-}</host>
<port>${LOGGER_REDIS_PORT:-6379}</port>
<password>${LOGGER_REDIS_PASSWORD:-}</password>
<key>${LOGGER_REDIS_KEY:-log:test}</key>
</appender>

<root level="info">
<appender-ref ref="CONSOLE"/>
<!-- <appender-ref ref="FILE"/> -->
<appender-ref ref="REDIS"/>
</root>

</configuration>

logstash

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
input {
redis {
id => "test"
host => "xxxx"
port => "xxxx"
password => "p1ssw0rd"
data_type => "list"
threads => 4
key => "log:test"
type => "test"
}
}

filter {
date {
match => ["ts", "UNIX", "UNIX_MS", "ISO8601"]
timezone => "Asia/Shanghai"
target => "@timestamp"
remove_field => ["ts"]
}
ruby {
code => '
if event.get("msg") and event.get("msg").length > 512*1024
event.set("msg", "IGNORED LARGE MSG")
end
'
}
}

output {
elasticsearch {
id => "log"
hosts => "es-master:9200"
user => "logstash_internal"
password => "${LOGSTASH_INTERNAL_PASSWORD}"
index => "logstash-%{type}-%{+YYYY.MM.dd}"
}
}

/etc/systemd/system/my-application.service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[Unit]
Description=ApplicationName

[Service]
StartLimitInterval=5
StartLimitBurst=10
Restart=always
RestartSec=120
StandardOutput=append:/data/project/logs/stdout.log
StandardError=append:/data/project/logs/stderr.log
WorkingDirectory=/opt/project
ExecStart=/usr/local/java/jdk-21.0.3+9-jre/bin/java \
-XX:+ExitOnOutOfMemoryError \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/data/project/dumps/oom.bin \
-Dspring.profiles.active= \
-Dspring.config.location=classpath:application.yml,/opt/project/application.yml \
-Dlogging.config=/opt/project/logback.xml \
-jar /opt/project/project.jar

[Install]
WantedBy=multi-user.target
1
2
3
4
5
systemctl daemon-reload

systemctl status my-application.service
# 开机自启动
systemctl enable my-application.service

build.gradle

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
plugins {
id 'java'
id 'maven-publish'
id 'org.springframework.boot' version '3.3.1'
id 'io.spring.dependency-management' version '1.1.5'
id 'org.springframework.boot.experimental.thin-launcher' version '1.0.31.RELEASE'
}

bootJar {
archiveFileName = "${project.name}.jar"
}

thinJar {
archiveFileName = "${project.name}-thin.jar"
}

thinResolvePrepare {
delete file('thin')
into file('thin')
}

publishing {
publications {
maven(MavenPublication) {
from components.java
}
}
}

generatePomFileForMavenPublication {
destination = file('pom.xml')
}

生成 pom.xml

1
./gradlew generatePomFileForMavenPublication

编译生成 thin jar 包

1
./gradlew clean thinResolvePrepare

缓存 maven 依赖

1
./mvnw dependency:resolve

生成 thin jar 依赖结构

1
java -Dthin.dryrun=true -Dthin.root=thin -Dthin.repo=https://maven.aliyun.com/repository/public -jar thin/${project.name}-thin.jar

可能需要的命令

1
2
3
rsync -avzh thin/repository/ server:/opt/project/repository/

scp thin/${project.name}-thin.jar server:/opt/project/${project.name}.jar