完整 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- Spring Boot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.4</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>springboot-demo</artifactId>
<version>1.0.0</version>
<name>springboot-demo</name>
<description>Spring Boot 3.5.4 Demo</description>
<properties>
<java.version>17</java.version>
<!-- MyBatis-Plus -->
<mybatis-plus.version>3.5.12</mybatis-plus.version>
<!-- Sa-Token -->
<sa-token.version>1.44.0</sa-token.version>
<!-- SpringDoc -->
<springdoc.version>2.8.9</springdoc.version>
<!-- RocketMQ -->
<rocketmq.version>2.3.4</rocketmq.version>
<!-- Elasticsearch -->
<elasticsearch.version>8.18.3</elasticsearch.version>
<!-- MinIO -->
<minio.version>8.5.17</minio.version>
<!-- Hutool -->
<hutool.version>5.8.39</hutool.version>
<!-- MapStruct -->
<mapstruct.version>1.6.3</mapstruct.version>
<!-- Druid -->
<druid.version>1.2.27</druid.version>
</properties>
<dependencies>
<!-- ================================================= -->
<!-- Web -->
<!-- ================================================= -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 参数校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- ================================================= -->
<!-- MySQL -->
<!-- ================================================= -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- ================================================= -->
<!-- MyBatis-Plus -->
<!-- ================================================= -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- 分页 / SQL Parser -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-jsqlparser</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- ================================================= -->
<!-- Redis -->
<!-- ================================================= -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Spring Cache -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- ================================================= -->
<!-- Sa-Token -->
<!-- ================================================= -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- Sa-Token Redis -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-template</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- ================================================= -->
<!-- RocketMQ -->
<!-- ================================================= -->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>${rocketmq.version}</version>
</dependency>
<!-- ================================================= -->
<!-- Elasticsearch -->
<!-- ================================================= -->
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>${elasticsearch.version}</version>
</dependency>
<!-- ================================================= -->
<!-- MinIO -->
<!-- ================================================= -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>${minio.version}</version>
</dependency>
<!-- ================================================= -->
<!-- Swagger / OpenAPI -->
<!-- ================================================= -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<!-- ================================================= -->
<!-- Lombok -->
<!-- ================================================= -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- ================================================= -->
<!-- MapStruct -->
<!-- ================================================= -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
<scope>provided</scope>
</dependency>
<!-- ================================================= -->
<!-- Hutool -->
<!-- ================================================= -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- ================================================= -->
<!-- Druid -->
<!-- ================================================= -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- ================================================= -->
<!-- Actuator -->
<!-- ================================================= -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- ================================================= -->
<!-- Test -->
<!-- ================================================= -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- ===================================================== -->
<!-- Build -->
<!-- ===================================================== -->
<build>
<plugins>
<!-- Spring Boot -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Spring Boot 3.5.4 + Java 17 + MySQL + MyBatis-Plus + Redis + Sa-Token + RocketMQ + Elasticsearch + MinIO + OpenAPI + Lombok + MapStruct + Hutool + AOP + Validation + Actuator + Test
aplication.yml
server:
port: 8080
spring:
application:
name: springboot-demo
# ==================== 数据库 ====================
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false
username: root
password: 123456
druid:
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 60000
# ==================== Redis ====================
data:
redis:
host: 127.0.0.1
port: 6379
database: 0
password:
timeout: 5s
lettuce:
pool:
max-active: 20
max-idle: 10
min-idle: 5
max-wait: 5s
# ==================== Cache ====================
cache:
type: redis
# ==================================================
# MyBatis-Plus
# ==================================================
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
# ==================================================
# Sa-Token
# ==================================================
sa-token:
# Token 名称
token-name: satoken
# Token 有效期,单位:秒
timeout: 2592000
# Token 最低活跃频率
active-timeout: 1800
# 是否允许同一账号多地同时登录
is-concurrent: true
# 同一账号最大登录数量
max-login-count: 2
# Token 风格
token-style: uuid-simple
# 是否打印日志
is-log: true
# ==================================================
# RocketMQ
# ==================================================
rocketmq:
name-server: 127.0.0.1:9876
producer:
group: ${spring.application.name}-producer
send-message-timeout: 3000
consumer:
group: ${spring.application.name}-consumer
# ==================================================
# Elasticsearch
# ==================================================
elasticsearch:
url: http://127.0.0.1:9200
username: elastic
password: 123456
# ==================================================
# MinIO
# ==================================================
minio:
endpoint: http://127.0.0.1:9000
access-key: minioadmin
secret-key: minioadmin
bucket-name: demo
# ==================================================
# SpringDoc
# ==================================================
springdoc:
api-docs:
path: /v3/api-docs
swagger-ui:
path: /swagger-ui.html
# ==================================================
# Actuator
# ==================================================
management:
endpoints:
web:
exposure:
include: health,info,metrics
endpoint:
health:
show-details: always
RedisConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
/**
* RedisTemplate<String, Object>
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(
RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template =
new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// Key 使用 String
StringRedisSerializer stringSerializer =
new StringRedisSerializer();
// Value 使用 JSON
GenericJackson2JsonRedisSerializer jsonSerializer =
new GenericJackson2JsonRedisSerializer();
// Key
template.setKeySerializer(stringSerializer);
// Value
template.setValueSerializer(jsonSerializer);
// Hash Key
template.setHashKeySerializer(stringSerializer);
// Hash Value
template.setHashValueSerializer(jsonSerializer);
template.afterPropertiesSet();
return template;
}
}
RedisService
@Service
@RequiredArgsConstructor
public class RedisService {
private final RedisTemplate<String, Object> redisTemplate;
// =====================================================
// String
// =====================================================
/**
* 设置值
*/
public void set(String key, Object value) {
redisTemplate.opsForValue()
.set(key, value);
}
/**
* 设置值 + 过期时间
*/
public void set(
String key,
Object value,
long timeout,
TimeUnit unit) {
redisTemplate.opsForValue()
.set(key, value, timeout, unit);
}
/**
* 设置值 + Duration
*/
public void set(
String key,
Object value,
Duration timeout) {
redisTemplate.opsForValue()
.set(key, value, timeout);
}
/**
* 获取值
*/
public Object get(String key) {
return redisTemplate.opsForValue()
.get(key);
}
/**
* 获取指定类型
*/
public <T> T get(
String key,
Class<T> clazz) {
Object value = get(key);
if (value == null) {
return null;
}
return clazz.cast(value);
}
/**
* 删除
*/
public Boolean delete(String key) {
return redisTemplate.delete(key);
}
/**
* 批量删除
*/
public Long delete(Collection<String> keys) {
return redisTemplate.delete(keys);
}
/**
* 判断 Key 是否存在
*/
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
/**
* 设置过期时间
*/
public Boolean expire(
String key,
long timeout,
TimeUnit unit) {
return redisTemplate.expire(
key,
timeout,
unit
);
}
/**
* 获取剩余过期时间
*/
public Long getExpire(
String key,
TimeUnit unit) {
return redisTemplate.getExpire(
key,
unit
);
}
// =====================================================
// Hash
// =====================================================
/**
* Hash 设置
*/
public void hSet(
String key,
String hashKey,
Object value) {
redisTemplate.opsForHash()
.put(key, hashKey, value);
}
/**
* Hash 获取
*/
public Object hGet(
String key,
String hashKey) {
return redisTemplate.opsForHash()
.get(key, hashKey);
}
/**
* Hash 删除
*/
public Long hDelete(
String key,
Object... hashKeys) {
return redisTemplate.opsForHash()
.delete(key, hashKeys);
}
/**
* Hash 是否存在
*/
public Boolean hHasKey(
String key,
String hashKey) {
return redisTemplate.opsForHash()
.hasKey(key, hashKey);
}
// =====================================================
// List
// =====================================================
/**
* 左侧添加
*/
public Long leftPush(
String key,
Object value) {
return redisTemplate.opsForList()
.leftPush(key, value);
}
/**
* 右侧添加
*/
public Long rightPush(
String key,
Object value) {
return redisTemplate.opsForList()
.rightPush(key, value);
}
/**
* 获取 List
*/
public List<Object> range(
String key,
long start,
long end) {
return redisTemplate.opsForList()
.range(key, start, end);
}
// =====================================================
// Set
// =====================================================
public Long sAdd(
String key,
Object... values) {
return redisTemplate.opsForSet()
.add(key, values);
}
public Set<Object> sMembers(String key) {
return redisTemplate.opsForSet()
.members(key);
}
public Boolean sIsMember(
String key,
Object value) {
return redisTemplate.opsForSet()
.isMember(key, value);
}
// =====================================================
// Increment
// =====================================================
/**
* 自增
*/
public Long increment(String key) {
return redisTemplate.opsForValue()
.increment(key);
}
/**
* 自增指定数值
*/
public Long increment(
String key,
long delta) {
return redisTemplate.opsForValue()
.increment(key, delta);
}
/**
* 自减
*/
public Long decrement(String key) {
return redisTemplate.opsForValue()
.decrement(key);
}
}
ElasticsearchConfig.java
@Configuration
public class ElasticsearchConfig {
@Value("${elasticsearch.url}")
private String url;
@Value("${elasticsearch.username}")
private String username;
@Value("${elasticsearch.password}")
private String password;
@Bean
public ElasticsearchClient elasticsearchClient() {
RestClient restClient = RestClient.builder(
HttpHost.create(url)
)
.setDefaultHeaders(new Header[]{
new BasicHeader(
HttpHeaders.AUTHORIZATION,
Credentials.basic(
username,
password
)
)
})
.build();
ElasticsearchTransport transport =
new RestClientTransport(
restClient,
new JacksonJsonpMapper()
);
return new ElasticsearchClient(transport);
}
}
MinioProperties.java
@Data
@Component
@ConfigurationProperties(prefix = "minio")
public class MinioProperties {
private String endpoint;
private String accessKey;
private String secretKey;
private String bucketName;
}
MinioConfig.java
@Configuration
@RequiredArgsConstructor
public class MinioConfig {
private final MinioProperties properties;
@Bean
public MinioClient minioClient() {
return MinioClient.builder()
.endpoint(properties.getEndpoint())
.credentials(
properties.getAccessKey(),
properties.getSecretKey()
)
.build();
}
}
MinioService.java
@Service
@RequiredArgsConstructor
public class MinioService {
private final MinioClient minioClient;
private final MinioProperties properties;
public String upload(MultipartFile file)
throws Exception {
String filename =
UUID.randomUUID()
+ "-" + file.getOriginalFilename();
minioClient.putObject(
PutObjectArgs.builder()
.bucket(properties.getBucketName())
.object(filename)
.stream(
file.getInputStream(),
file.getSize(),
-1
)
.contentType(file.getContentType())
.build()
);
return filename;
}
}
全局
ResultFailEnum.java
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum ResultFailEnum {
NOT_FOUND(404, "请求地址不存在"),
MESSAGE_DATA_DUPLICATE(601, "信息已经存在"),
SERVER_ERROR(500, "服务器返回错误"),
PARAMETER_ERROR(600, "请求参数错误");
private final Integer code;
private final String message;
}
Result.java
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
private static final String SUCCESS = "success";
private static final String SUCCESS_MESSAGE = "请求成功";
private static final String FAIL = "error";
private Integer code;
private String status;
private String message;
private T data;
public static <T> Result<T> ok() {
return Result.<T>builder()
.code(200)
.status(SUCCESS)
.message(SUCCESS_MESSAGE)
.build();
}
public static <T> Result<T> ok(T data) {
return Result.<T>builder()
.code(200)
.status(SUCCESS)
.message(SUCCESS_MESSAGE)
.data(data)
.build();
}
public static <T> Result<T> fail(
String message) {
return Result.<T>builder()
.code(500)
.status(FAIL)
.message(message)
.build();
}
public static <T> Result<T> fail(
ResultFailEnum resultFailEnum) {
return Result.<T>builder()
.code(resultFailEnum.getCode())
.status(FAIL)
.message(resultFailEnum.getMessage())
.build();
}
}
BusinessException
@Getter
public class BusinessException extends RuntimeException {
private ResultFailEnum codeEnum;
private Integer code;
private String message;
public BusinessException(String message, Throwable e) {
super(message, e);
this.message = message;
}
public BusinessException(String message) {
super(message);
this.message = message;
}
public BusinessException(Throwable e) {
super(e);
}
public BusinessException(ResultFailEnum codeEnum) {
super(codeEnum.getMessage());
this.codeEnum = codeEnum;
this.code = codeEnum.getCode();
this.message = codeEnum.getMessage();
}
public BusinessException(Integer code, String message) {
super(message);
this.code = code;
this.message = message;
}
@Override
public synchronized Throwable fillInStackTrace() {
return this;
}
}
GlobalExceptionHandler
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(value = Exception.class)
Object handleException(Exception e, HttpServletRequest request) {
log.error("请求错误,请求地址{},错误信息:", request.getRequestURL(), e);
Result ajaxResponse = new Result();
//404
String FAIL = "error";
if (e instanceof NoHandlerFoundException) {
ajaxResponse.setCode(ResultFailEnum.NOT_FOUND.getCode());
ajaxResponse.setMessage(ResultFailEnum.NOT_FOUND.getMessage());
ajaxResponse.setStatus(FAIL);
} else if (e instanceof BusinessException) {
//业务错误
BusinessException biz = (BusinessException) e;
ajaxResponse.setCode(biz.getCode() == null ? ResultFailEnum.PARAMETER_ERROR.getCode() : biz.getCode());
ajaxResponse.setMessage(biz.getMessage());
ajaxResponse.setStatus(FAIL);
} else if (e instanceof BindException || e instanceof MethodArgumentTypeMismatchException) {
//参数类型错误
ajaxResponse.setCode(ResultFailEnum.PARAMETER_ERROR.getCode());
ajaxResponse.setMessage(ResultFailEnum.PARAMETER_ERROR.getMessage());
ajaxResponse.setStatus(FAIL);
} else if (e instanceof DuplicateKeyException) {
//主键冲突
ajaxResponse.setCode(ResultFailEnum.MESSAGE_DATA_DUPLICATE.getCode());
ajaxResponse.setMessage(ResultFailEnum.MESSAGE_DATA_DUPLICATE.getMessage());
ajaxResponse.setStatus(FAIL);
} else if (e instanceof ConstraintViolationException || e instanceof BindException) {
//请求参数错误
ConstraintViolationException ce = (ConstraintViolationException) e;
ajaxResponse.setCode(ResultFailEnum.PARAMETER_ERROR.getCode());
ajaxResponse.setMessage(ResultFailEnum.PARAMETER_ERROR.getMessage());
ajaxResponse.setStatus(FAIL);
} else {
ajaxResponse.setCode(ResultFailEnum.SERVER_ERROR.getCode());
ajaxResponse.setMessage(ResultFailEnum.SERVER_ERROR.getMessage());
ajaxResponse.setStatus(FAIL);
}
return ajaxResponse;
}
}
Top comments (0)