基础后台框架-基于SprongBoot+Maven+MySQL+Redis

基础后台框架-基于SprongBoot+Maven

提示:框架基于SpringBoot+Maven构建
框架依赖:MySQL、Redis、Sa-Token、Redisson、MyBatis-Plus、HuTool
框架中,使用MyBatis-Plus已有的基础方法,可以快速实现基础业务


提示:使用此框架可以快速拓展自由业务逻辑


前言

框架依赖: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;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wcuuchina

谢谢你的鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值