基础后台框架-基于SprongBoot+Maven
提示:框架基于SpringBoot+Maven构建
框架依赖:MySQL、Redis、Sa-Token、Redisson、MyBatis-Plus、HuTool
框架中,使用MyBatis-Plus已有的基础方法,可以快速实现基础业务
提示:使用此框架可以快速拓展自由业务逻辑
基础后台框架-基于SprongBoot+Maven+MySQL+Redis
前言
框架依赖:MySQL、Redis、Sa-Token、Redisson、MyBatis-Plus
框架中,使用MyBatis-Plus已有的基础方法,可以快速实现基础业务。
一、Maven下载依赖
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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.9</version>
<relativePath/>
</parent>
<name>applet</name>
<groupId>top.allopen.applet</groupId>
<artifactId>applet</artifactId>
<version>0.0.1-SNAPSHOT</version>
<description>Demo project for Spring Boot</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<druid.version>1.1.0</druid.version>
<mybatis-plus.version>3.1.0</mybatis-plus.version>
<mybatis.version>3.0.0</mybatis.version>
<hutool.version>5.8.15</hutool.version>
<hystrix.version>1.5.18</hystrix.version>
<poi.version>1.0.6</poi.version>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!--Redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.8.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1-jre</version>
</dependency>
<dependency>
<groupId>com.yungouos.pay</groupId>
<artifactId>yungouos-pay-sdk</artifactId>
<version>2.0.29</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Sa-Token 权限认证,在线文档:https://sa-token.cc -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.34.0</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>org.apache.poi.xwpf.converter.pdf</artifactId>
<version>${poi.version}</version>
</dependency>
<!-- 引入 Hystrix 依赖 -->
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-core</artifactId>
<version>${hystrix.version}</version>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-javanica</artifactId>
<version>${hystrix.version}</version>
</dependency>
</dependencies>
<build>
<finalName>applet</finalName>
<!--防止编译出错-->
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
<include>**/*.yml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>top.allopen.applet.AppletApplication</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
二、基础配置文件
application.yml
spring:
profiles:
active: dev
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
global-config:
db-config:
id-type: auto
field-strategy: NOT_EMPTY
db-type: MYSQL
configuration:
map-underscore-to-camel-case: true
call-setters-on-nulls: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
logging:
config: classpath:logback-spring.xml
application-dev.yml
server:
port: 8088
############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
sa-token:
# token名称 (同时也是cookie名称)
token-name: satoken
# token有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
activity-timeout: -1
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: true
# token风格
token-style: uuid
# 是否输出操作日志
is-log: false
token-prefix: allopen.top
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
# com.mysql.jdbc.Driver
driverClassName: com.mysql.cj.jdbc.Driver
# 换为自己的数据库信息
url: jdbc:mysql://ip:端口/数据库名称?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
username: 用户名
password: 密码
initial-size: 5
# 最大连接池个数
max-active: 20
# 最小连接池个数——》已经不再使用,配置了也没效果
min-idle: 5
# 配置获取连接等待超时的时间,单位毫秒,缺省启用公平锁,并发效率会有所下降
max-wait: 60000
# 打开PSCache,并且指定每个连接上PSCache的大小
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
max-evictable-idle-time-millis: 60000
validation-query: SELECT 1 FROM DUAL
# validation-query-timeout: 5000
# 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
test-on-borrow: false
# 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
test-on-return: false
# 建议配置为true,不影响性能,并且保证安全性。
# 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
test-while-idle: true
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 通过别名的方式配置扩展插件,多个英文逗号分隔,常用的插件有:
# 监控统计用的filter:stat
#filters: #配置多个英文逗号分隔(统计,sql注入,log4j过滤) 防御sql注入的filter:wall
filters: stat,wall
stat-view-servlet:
enabled: true
url-pattern: /druid/*
# login-username: root
# login-password: root
redis:
# 换为自己的redis链接信息
host: ip
port: 端口
password: 密码
database: 5
lettuce:
pool:
max-idle: 16
max-active: 20
min-idle: 8
mybatis:
# config-location: classpath:mybatis.cfg2.xml # mybatis主配置文件所在路径
type-aliases-package: top.allopen.applet.entity # 定义所有操作类的别名所在包
mapper-locations: # 所有的mapper映射文件
- classpath:mapper/*.xml
#showSql
logging:
level:
com.example.demo.mapper: debug
1.源文件
异常处理
package top.allopen.applet.advice;
import cn.hutool.json.JSONUtil;
import com.google.common.collect.Lists;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import top.allopen.applet.res.Result;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Description: 全局异常处理
* @Package top.allopen.applet.advice
* @Date 2023-03-11
* @Author Dale
* @Since 3.1
*/
@RestControllerAdvice
public class ExceptionConfig {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public String handleValidException(MethodArgumentNotValidException e) {
List<ObjectError> list = e.getBindingResult().getAllErrors();
List<String> data = Lists.newArrayList();
list.forEach(temp -> {
data.add(temp.getDefaultMessage());
});
String result = JSONUtil.toJsonStr(Result.fail("参数校验发生异常", data));
return result;
}
@ExceptionHandler(value = ConstraintViolationException.class)
public String handleConstraintViolationException(ConstraintViolationException e) {
return e.getConstraintViolations()
.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.toList()).get(0);
}
}
限流注解
package top.allopen.applet.annotate;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* @Description: Description
* @Package top.allopen.applet.annotate
* @Date 2023-03-11
* @Author Dale
* @Since 3.1
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface Limit {
/**
* 最多的访问限制次数
*/
double permitsPerSecond () default 10D;
/**
* 获取令牌最大等待时间
*/
long timeout() default 3000;
/**
* 获取令牌最大等待时间,单位(例:分钟/秒/毫秒) 默认:毫秒
*/
TimeUnit timeunit() default TimeUnit.MILLISECONDS;
/**
* 得不到令牌的提示语
*/
String msg() default "因流量限制,系统繁忙,请稍后再试.";
}
自定义aop
package top.allopen.applet.aop;
import cn.hutool.json.JSONUtil;
import com.google.common.util.concurrent.RateLimiter;
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.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import top.allopen.applet.annotate.Limit;
import top.allopen.applet.res.Result;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* @Description: Description
* @Package top.allopen.applet.aop
* @Date 2023-03-11
* @Author Dale
* @Since 3.1
*/
@Slf4j
@Aspect
@Component
public class LimitAop {
/**
* 不同的接口,不同的流量控制
* map的key为 Limiter.key
*/
private final Map<String, RateLimiter> limitMap = new HashMap<>();
@Around("@annotation(top.allopen.applet.annotate.Limit)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Limit limit = method.getAnnotation(Limit.class);
if (limit != null) {
String key = joinPoint.getTarget().getClass().getSimpleName() + method.getName();
RateLimiter rateLimiter = null;
if (!limitMap.containsKey(key)) {
rateLimiter = RateLimiter.create(limit.permitsPerSecond());
limitMap.put(key, rateLimiter);
log.info("创建令牌桶: {}, 容量: {}", key, limit.permitsPerSecond());
}
rateLimiter = limitMap.get(key);
// 获取令牌
boolean acquire = rateLimiter.tryAcquire(limit.timeout(), limit.timeunit());
if (!acquire) {
log.debug("令牌桶: {}, 获取令牌失败", key);
this.responseFail(limit.msg());
return null;
}
}
return joinPoint.proceed();
}
/**
* 直接向前端抛出异常
* @param msg 提示信息
*/
private void responseFail(String msg) {
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
Result<Object> resultData = Result.fail(msg);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
PrintWriter out = null;
try {
out = response.getWriter();
out.append(JSONUtil.toJsonStr(resultData));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (out != null) {
out.close();
}
}
}
}
线程池配置
package top.allopen.applet.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import com.alibaba.druid.util.Utils;
/**
* @Description: Druid数据库连接池配置文件
* @Package top.allopen.applet.config
* @Date 2023-03-11
* @Author Dale
* @Since 3.1
*/
@Configuration
public class DruidConfig {
private static final Logger logger = LoggerFactory.getLogger(DruidConfig.class);
@Value("${spring.datasource.druid.url}")
private String dbUrl;
@Value("${spring.datasource.druid.username}")
private String username;
@Value("${spring.datasource.druid.password}")
private String password;
@Value("${spring.datasource.druid.driverClassName}")
private String driverClassName;
@Value("${spring.datasource.druid.initial-size}")
private int initialSize;
@Value("${spring.datasource.druid.max-active}")
private int maxActive;
@Value("${spring.datasource.druid.min-idle}")
private int minIdle;
@Value("${spring.datasource.druid.max-wait}")
private int maxWait;
@Value("${spring.datasource.druid.pool-prepared-statements}")
private boolean poolPreparedStatements;
@Value("${spring.datasource.druid.max-pool-prepared-statement-per-connection-size}")
private int maxPoolPreparedStatementPerConnectionSize;
@Value("${spring.datasource.druid.time-between-eviction-runs-millis}")
private int timeBetweenEvictionRunsMillis;
@Value("${spring.datasource.druid.min-evictable-idle-time-millis}")
private int minEvictableIdleTimeMillis;
@Value("${spring.datasource.druid.max-evictable-idle-time-millis}")
private int maxEvictableIdleTimeMillis;
@Value("${spring.datasource.druid.validation-query}")
private String validationQuery;
@Value("${spring.datasource.druid.test-while-idle}")
private boolean testWhileIdle;
@Value("${spring.datasource.druid.test-on-borrow}")
private boolean testOnBorrow;
@Value("${spring.datasource.druid.test-on-return}")
private boolean testOnReturn;
@Value("${spring.datasource.druid.filters}")
private String filters;
@Value("{spring.datasource.druid.connection-properties}")
private String connectionProperties;
/**
* Druid 连接池配置
*/
@Bean //声明其为Bean实例
public DruidDataSource dataSource() {
DruidDataSource datasource = new DruidDataSource();
datasource.setUrl(dbUrl);
datasource.setUsername(username);
datasource.setPassword(password);
datasource.setDriverClassName(driverClassName);
datasource.setInitialSize(initialSize);
datasource.setMinIdle(minIdle);
datasource.setMaxActive(maxActive);
datasource.setMaxWait(maxWait);
datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
datasource.setMaxEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
datasource.setValidationQuery(validationQuery);
datasource.setTestWhileIdle(testWhileIdle);
datasource.setTestOnBorrow(testOnBorrow);
datasource.setTestOnReturn(testOnReturn);
datasource.setPoolPreparedStatements(poolPreparedStatements);
datasource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
try {
datasource.setFilters(filters);
} catch (Exception e) {
logger.error("druid configuration initialization filter", e);
}
datasource.setConnectionProperties(connectionProperties);
return datasource;
}
/**
* 去除监控页面底部的广告
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
@ConditionalOnProperty(name = "spring.datasource.druid.statViewServlet.enabled", havingValue = "true")
public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties)
{
// 获取web监控页面的参数
DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
// 提取common.js的配置路径
String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
final String filePath = "support/http/resources/js/common.js";
// 创建filter进行过滤
Filter filter = new Filter()
{
@Override
public void init(javax.servlet.FilterConfig filterConfig) throws ServletException
{
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
chain.doFilter(request, response);
// 重置缓冲区,响应头不会被重置
response.resetBuffer();
// 获取common.js
String text = Utils.readFromResource(filePath);
// 正则替换banner, 除去底部的广告信息
text = text.replaceAll("<a.*?banner\"></a><br/>", "");
text = text.replaceAll("powered.*?shrek.wang</a>", "");
response.getWriter().write(text);
}
@Override
public void destroy()
{
}
};
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(filter);
registrationBean.addUrlPatterns(commonJsPattern);
return registrationBean;
}
/**
* JDBC操作配置
*/
@Bean(name = "dataOneTemplate")
public JdbcTemplate jdbcTemplate (@Autowired DruidDataSource dataSource){
return new JdbcTemplate(dataSource) ;
}
/**
* 配置 Druid 监控界面
*/
@Bean
public ServletRegistrationBean statViewServlet(){
ServletRegistrationBean srb =
new ServletRegistrationBean(new StatViewServlet(),"/druid/*");
//设置控制台管理用户
// srb.addInitParameter("loginUsername","root");
// srb.addInitParameter("loginPassword","root");
//是否可以重置数据
srb.addInitParameter("resetEnable","false");
return srb;
}
@Bean
public FilterRegistrationBean statFilter(){
//创建过滤器
FilterRegistrationBean frb =
new FilterRegistrationBean(new WebStatFilter());
//设置过滤器过滤路径
frb.addUrlPatterns("/*");
//忽略过滤的形式
frb.addInitParameter("exclusions",
"*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return frb;
}
}
熔断配置
package top.allopen.applet.config;
import com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* @Description: 开启 AOP 代理的支持
* @Package top.allopen.applet.config
* @Date 2023-03-13
* @Author Dale
* @Since 3.1
*/
@Configuration
@EnableAspectJAutoProxy
public class HystrixConfig {
@Bean
public HystrixCommandAspect hystrixCommandAspect() {
return new HystrixCommandAspect();
}
}
MyBatis-Plus配置
package top.allopen.applet.config;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor;
import org.springframework.context.annotation.Bean;
/**
* @Description: Description
* @Package top.allopen.applet.config
* @Date 2023-03-10
* @Author Dale
* @Since 3.1
*/
public class MybatisPlusConfig {
/**
* mybatis-plus SQL执行效率插件【生产环境可以关闭】
*/
@Bean
public PerformanceInterceptor performanceInterceptor() {
return new PerformanceInterceptor();
}
/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}
Redis配置
package top.allopen.applet.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
/**
* @Description: Description
* @Package top.allopen.applet.config
* @Date 2023-03-11
* @Author Dale
* @Since 3.1
*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化解决乱码
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(30))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
Redisson配置
package top.allopen.applet.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Description: Description
* @Package top.allopen.applet.config
* @Date 2023-03-13
* @Author Dale
* @Since 3.1
*/
@Configuration
public class RedissonConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.password}")
private String paw;
@Value("${spring.redis.port}")
private String port;
@Bean
public RedissonClient getRedisson() throws Exception{
RedissonClient redisson = null;
System.out.println(host);
Config config = new Config();
config.useSingleServer()
.setPassword(paw)
.setAddress("http://"+host+":"+port);
redisson = Redisson.create(config);
System.out.println(redisson.getConfig().toJSON().toString());
return redisson;
}
}
常量配置
package top.allopen.applet.constant;
/**
* @Description: 常量配置
* @Package top.allopen.applet.constant
* @Date 2023-03-11
* @Author Dale
* @Since 3.1
*/
public class Constant {
/**
* 全局请求追踪ID
*/
public static final String TRACE_ID = "traceId";
/**
* 请求熔断时间 毫秒
*/
public static final String FUSING_TIME = "5000";
/**
* 常量 整数 0
*/
public static final int FIX_INT_0 = 0;
}
接口请求控制器
package top.allopen.applet.controller;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import top.allopen.applet.annotate.Limit;
import top.allopen.applet.constant.Constant;
import top.allopen.applet.entity.User;
import top.allopen.applet.res.Result;
import top.allopen.applet.service.UserService;
import java.util.List;
/**
* @Description: Description
* @Package top.allopen.applet.controller
* @Date 2023-03-10
* @Author Dale
* @Since 3.1
*/
@SuppressWarnings("all")
@RestController
@RequestMapping("/test")
public class TestController extends BaseController {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private UserService userService;
@Limit
@HystrixCommand(commandProperties= {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = Constant.FUSING_TIME),
@HystrixProperty(name = "execution.timeout.enabled", value = "true")
}, fallbackMethod="fallbackMethod")
@RequestMapping(value = "list", method = RequestMethod.GET)
public Result list() {
redisTemplate.opsForValue().set("data", "data");
Object result = redisTemplate.opsForValue().get("data");
List<User> data = userService.list();
return Result.success(data);
}
}
基类控制器
package top.allopen.applet.controller;
import top.allopen.applet.res.Result;
/**
* @Description: Description
* @Package top.allopen.applet.controller
* @Date 2023-03-10
* @Author Dale
* @Since 3.1
*/
public class BaseController {
/**
* @apiNote 触发熔断机制返回数据
*
* @author dale
* @since 1.0
* @date 2023-03-13 9:36
* @return Result
**/
protected Result fallbackMethod() {
return Result.fail("触发熔断机制");
}
}
dao层
package top.allopen.applet.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import top.allopen.applet.entity.User;
/**
* @Description: Description
* @Package top.allopen.applet.dao
* @Date 2023-03-10
* @Author Dale
* @Since 3.1
*/
@Mapper
public interface UserInfoDao extends BaseMapper<User> {
}
服务层
package top.allopen.applet.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springframework.stereotype.Service;
import top.allopen.applet.entity.User;
import java.util.List;
/**
* @Description: Description
* @Package top.allopen.applet.service
* @Date 2023-03-10
* @Author Dale
* @Since 3.1
*/
@Service
public interface UserService extends IService<User> {
@Override
List list();
}
服务实现层
package top.allopen.applet.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import top.allopen.applet.dao.UserInfoDao;
import top.allopen.applet.entity.User;
import top.allopen.applet.service.UserService;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* @Description: Description
* @Package top.allopen.applet.service.impl
* @Date 2023-03-10
* @Author Dale
* @Since 3.1
*/
@Service
public class UserInfoServiceImpl extends ServiceImpl<UserInfoDao, User> implements UserService {
@Autowired
private RedissonClient redissonClient;
@Override
@Cacheable(value = "top.allopen.applet.service.impl.UserInfoServiceImpl.list")
public List list() {
List<User> result = null;
RLock lock = redissonClient.getLock("user:list-lock");
try {
boolean isLock = lock.tryLock(5,30, TimeUnit.SECONDS);
if(isLock) {
result = super.list();
}
} catch (Exception e) {
} finally {
if(lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
return result;
}
}
实体
package top.allopen.applet.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
/**
* @Description: Description
* @Package top.allopen.applet.entity
* @Date 2023-03-10
* @Author Dale
* @Since 3.1
*/
@TableName("user_info")
public class User implements Serializable {
@TableId(type = IdType.AUTO)
private Integer id;
@TableField(value = "userName")
private String userName;
@TableField(value = "passWord")
private String passWord;
@TableField(value = "realName")
private String realName;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
public String getRealName() {
return realName;
}
public void setRealName(String realName) {
this.realName = realName;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", userName='" + userName + '\'' +
", passWord='" + passWord + '\'' +
", realName='" + realName + '\'' +
'}';
}
}
自定义异常类
package top.allopen.applet.exception;
import lombok.Data;
/**
* @Description: 自定义异常
* @Package top.allopen.applet.exception
* @Date 2023-03-13
* @Author Dale
*@Since 3.1
*/
@Data
public class CustomException extends RuntimeException {
private Integer code;
private String msg;
public CustomException() {
super();
}
public CustomException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public CustomException(String message, Throwable cause) {
super(message, cause);
}
public CustomException(String message) {
super(message);
}
public CustomException(String message,Integer code) {
super(message);
this.code = code;
}
public CustomException(Throwable cause) {
super(cause);
}
public CustomException(Throwable cause,Integer code) {
super(cause);
this.code = code;
}
public static CustomException requestFast() {
CustomException e = new CustomException("请求过快", 429);
return e;
}
}
自定义filter生成全局追踪ID
package top.allopen.applet.filter;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import top.allopen.applet.constant.Constant;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.UUID;
/**
* @Description: Description
* @Package top.allopen.applet.filter
* @Date 2023-03-11
* @Author Dale
* @Since 3.1
*/
@Component
@Slf4j
public class TraceFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
MDC.put(Constant.TRACE_ID, getTraceId());
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
private String getTraceId() {
long timestamp = System.currentTimeMillis();
UUID uuid = UUID.randomUUID();
String uniqueId = timestamp + uuid.toString().replace("-", "");
return uniqueId;
}
}
统一返回封装类
package top.allopen.applet.res;
import cn.hutool.json.JSONUtil;
import lombok.Data;
import org.slf4j.MDC;
import top.allopen.applet.constant.Constant;
//import top.allopen.applet.util.HitCache;
import java.io.Serializable;
import java.util.Optional;
/**
* @Description: Description
* @Package top.allopen.applet.res
* @Date 2023-03-10
* @Author Dale
* @Since 3.1
*/
@Data
public class Result<T> implements Serializable {
/**
* serialVersionUID
*/
private static final long serialVersionUID = 1L;
private static final String SUCCESS_MSG = "success";
private static final int SUCCESS_CODE = 100;
private static final int FAIL_CODE = 116;
/**
* 是否成功
*
* @Description 是否成功
*/
private boolean success = false;
/**
* 是否命中缓存
*
* @Description 是否命中缓存
*/
// private boolean hitcache = HitCache.getInstance().get();
/**
* 错误信息
*
* @Description 错误信息
*/
private String message;
/**
* @Description
*/
private String sysMessage;
/**
* 错误信息
*
* @Description 错误信息
*/
private String errorMsg;
/**
* @Description
*/
private String version = "1.0";
/**
* 主数据
*
* @Description 主数据
*/
private T responseData;
/**
* 返回码
*
* @Description 返回码
*/
private int msgCode;
/**
* 接口耗时
*
* @Description 接口耗时
*/
private long timing;
/**
* 追踪码
* @Description 追踪码
*/
private String requestId = MDC.get(Constant.TRACE_ID);
/**
* 返回的额外数据
*
* @Description 返回的额外数据
*/
private Object ext;
private Result(boolean success, String message, T responseData, int code) {
this.success = success;
this.message = message;
this.responseData = responseData;
this.msgCode = code;
}
public Result() {
}
public Optional<T> optional() {
return success ? Optional.of(responseData) : Optional.empty();
}
public void setData(T data) {
this.responseData = data;
}
public void setMessage(String message) {
this.message = message;
}
public void setSuccess(boolean success) {
this.success = success;
}
public static <T> Result<T> success() {
return success(null, null);
}
public static <T> Result<T> success(T data) {
return success(data, null);
}
public static <T> Result<T> success(T data, int code) {
return success(data, null, code);
}
public static <T> Result<T> success(Optional<T> data) {
if (data != null && data.isPresent())
return success(data.get(), null);
return success();
}
public static <T> Result<T> success(Optional<T> data, int code) {
if (data != null && data.isPresent())
return success(data.get(), null, code);
return success();
}
public static <T> Result<T> success(T data, String desc) {
return new Result<T>(true, (desc == null || desc.trim().length() <= 0) ? SUCCESS_MSG : desc, data, SUCCESS_CODE);
}
public static <T> Result<T> success(T data, String desc, int code) {
return new Result<T>(true, (desc == null || desc.trim().length() <= 0) ? SUCCESS_MSG : desc, data, code);
}
public static <T> Result<T> fail(Throwable e) {
return fail(e.getMessage());
}
public static <T> Result<T> fail(String errMsg) {
Result<T> result = new Result<T>();
result.success = false;
result.errorMsg = errMsg;
result.msgCode = FAIL_CODE;
return result;
}
public static <T> Result<T> fail(String errorMsg, int code) {
Result<T> result = new Result<T>();
result.success = false;
result.errorMsg = errorMsg;
result.msgCode = code;
return result;
}
public static <T> Result<T> fail(String errorMsg, T responseData) {
Result<T> result = new Result<T>();
result.success = false;
result.errorMsg = errorMsg;
result.responseData = responseData;
result.msgCode = FAIL_CODE;
return result;
}
public static <T> Result<T> fail(String errorMsg, T responseData, int code) {
Result<T> result = new Result<T>();
result.success = false;
result.errorMsg = errorMsg;
result.responseData = responseData;
result.msgCode = code;
return result;
}
public Result<T> responseData(T responseData) {
this.responseData = responseData;
return this;
}
public Result<T> message(String message) {
this.message = message;
return this;
}
public Result<T> code(int code) {
this.msgCode = code;
return this;
}
public Result<T> errorMsg(String errorMsg) {
this.errorMsg = errorMsg;
return this;
}
public Result<T> requestId(String requestId) {
// this.requestId = requestId;
return this;
}
public Result<T> ext(Object ext) {
this.ext = ext;
return this;
}
public String toJson() {
return JSONUtil.toJsonStr(this);
}
public void setTiming(Long timing) {
this.timing = timing;
}
public long getTiming() {
return timing;
}
// public void setRequestId(String requestId) {
// this.requestId = requestId;
// }
}
启动类
package top.allopen.applet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SuppressWarnings("all")
@SpringBootApplication
public class AppletApplication {
public static void main(String[] args) {
SpringApplication.run(AppletApplication.class, args);
}
}
2.SQL文件
CREATE TABLE `user` (
`id` int(32) NOT NULL AUTO_INCREMENT,
`userName` varchar(32) NOT NULL,
`passWord` varchar(50) NOT NULL,
`realName` varchar(32) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;