PageHelper
<!-- pageHelper分页插件 --><dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>1.4.2</version></dependency>
# pagehelper分页插件配置# 配置分页数据库pagehelper.helper-dialect=mysql#启用合理化,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页pagehelper.reasonable=true#为了支持startPage(Object params)方法,增加了该参数来配置参数映射,用于从对象中根据属性名取值# 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值# 默认值为pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZeropagehelper.params=count=countSql#支持通过 Mapper 接口参数来传递分页参数,默认值false# 分页插件会从查询方法的参数值中,自动根据上面 params 配置的字段中取值,查找到合适的值时就会自动分页pagehelper.supportMethodsArguments=true#如果 pageSize=0 就会查询出全部的结果(相当于没有执行分页查询)pagehelper.page-size-zero=true
// 查询mapper执行前赋值PageHelper.startPage(1,20);
ES
1、添加maven依赖
<!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-elasticsearch --><dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-elasticsearch</artifactId><version>4.3.4</version></dependency>
2、设置springboot中ES的版本
<!-- 需要在项目中引入springboot parent包来覆盖es版本 --><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.0</version><relativePath/></parent><!-- 覆盖springboot默认es版本 --><elasticsearch.version>7.13.0</elasticsearch.version>
3、添加es配置文件
spring:data:elasticsearch:client:reactive:endpoints: 172.16.88.128:9200username:password:connection-timeout: 5000socket-timeout: 60000
4、添加es client配置
/*** 初始化 es客户端 bean 7.13.0* @return*/@Override@Beanpublic RestHighLevelClient elasticsearchClient() {final ClientConfiguration clientConfiguration = ClientConfiguration.builder().connectedTo(endpoints).withConnectTimeout(connectTimeout).withSocketTimeout(socketTimeout).withBasicAuth(username, password).build();log.info("==============初始化ES客户端连接" + endpoints + "=================");return RestClients.create(clientConfiguration).rest();}
继承 ElasticsearchRepository 即可使用通用api
package com.csj.demo.business.repository;import com.csj.demo.business.POJO.DO.User;import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;/*** @Desc : 用户es操作类* @Author : chen* @Date : 2022/4/22 17:11*/public interface UserRepository extends ElasticsearchRepository<User, Long> {}
TK-Mybatis-Mapper
<!--通用Mapper--><dependency><groupId>tk.mybatis</groupId><artifactId>mapper-spring-boot-starter</artifactId><version>4.2.0</version></dependency>
# tkmapper 使用mapper.identity=MYSQLmapper.not-empty=true
package com.csj.demo.business.component;import tk.mybatis.mapper.common.Mapper;import tk.mybatis.mapper.common.MySqlMapper;/*** @Desc : basemapper* @Author : chen* @Date : 2022/4/24 15:37*/public interface MyMapper<T> extends Mapper<T>, MySqlMapper<T> {}
// 使用过程中继承mymapper即可public interface UserMapper extends MyMapper<User> {}
mybatis
# mybatis配置# mybatis全局配置mybatis.config-location:classpath:mybatis-config.xml# resultType 可以只写类的全限定名mybatis.type-aliases-package=com.csj.demo.business.POJO.DO# xml文件目录mybatis.mapper-locations=classpath:mapper/*Mapper.xml
<!-- mybatis --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.2</version></dependency>
Druid
spring:# 设置数据源datasource:url: jdbc:mysql://172.16.88.128:3306/DB01?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8username: rootpassword: 123456type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverdruid:# 初始化大小,最小,最大活跃数initial-size: 3min-idle: 3max-active: 20# 配置获取连接等待超时的时间,单位是毫秒max-wait: 60000# asyncInit是1.1.4中新增加的配置,如果有initialSize数量较多时,打开会加快应用启动时间async-init: true# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒time-between-eviction-runs-millis: 60000# 配置一个连接在池中最小生存的时间,单位是毫秒min-evictable-idle-time-millis: 30000max-evictable-idle-time-millis: 300000# 用于检查连接的语句validation-query: SELECT 1test-while-idle: true# 申请连接时执行validationQuery检测连接是否有效,会影响性能test-on-borrow: false# 归还连接时执行validationQuery检测连接是否有效test-on-return: false# 打开缓存PSCache,并且指定每个连接上PSCache的大小,mysql作用不大建议关闭,oracle开启效果好pool-prepared-statements: falsemax-pool-prepared-statement-per-connection-size: -1max-open-prepared-statements: -1#配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙,slf4j(用log4j需要额外导入依赖,版本不同,这里也可以去掉slf4j)filters: stat,wall,slf4j# 通过connectProperties属性来打开mergeSql功能;慢SQL记录connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=200;druid.stat.logSlowSql=true;config.decrypt=true# 配置监控服务器stat-view-servlet:enabled: trueurl-pattern: /druid/*# 是否可重置数据reset-enable: falselogin-username: druidlogin-password: druid123web-stat-filter:enabled: trueurl-pattern: /*exclusions: /druid/*,*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,*.woff2session-stat-enable: truesession-stat-max-count: 10filter:slf4j:enabled: truestatement-create-after-log-enabled: falsestatement-close-after-log-enabled: falseresult-set-open-after-log-enabled: falseresult-set-close-after-log-enabled: false
//单数据源时可忽略,springboot会自动配置@Value("${spring.datasource.url}")private String url;@Value("${spring.datasource.username}")private String username;@Value("${spring.datasource.password}")private String password;@Value("${spring.datasource.driver-class-name}")private String driverClassName;@Value("${spring.datasource.initial-size}")private int initialSize;@Value("${spring.datasource.min-idle}")private int minIdle;@Value("${spring.datasource.max-active}")private int maxActive;@Value("${spring.datasource.max-wait}")private int maxWait;@Value("${spring.datasource.time-between-eviction-runs-millis}")private int timeBetweenEvictionRunsMillis;@Value("${spring.datasource.min-evictable-idle-time-millis}")private int minEvictableIdleTimeMillis;@Value("${spring.datasource.max-evictable-idle-time-millis}")private int maxEvictableIdleTimeMillis;@Value("${spring.datasource.pool-prepared-statements}")private boolean poolPreparedStatements;@Value("${spring.datasource.max-pool-prepared-statement-per-connection-size}")private int maxPoolPreparedStatementPerConnectionSize;@Value("${spring.datasource.validation-query}")private String validationQuery;@Value("${spring.datasource.test-while-idle}")private boolean testWhileIdle;@Value("${spring.datasource.test-on-borrow}")private boolean testOnBorrow;@Value("${spring.datasource.test-on-return}")private boolean testOnReturn;@Value("{spring.datasource.connection-properties}")private String connectionProperties;@Bean@Primary@ConfigurationProperties("spring.datasource.druid")public DataSource dataSource() {DruidDataSource datasource = new DruidDataSource();datasource.setUrl(url);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(maxEvictableIdleTimeMillis);datasource.setValidationQuery(validationQuery);datasource.setTestWhileIdle(testWhileIdle);datasource.setTestOnBorrow(testOnBorrow);datasource.setTestOnReturn(testOnReturn);datasource.setPoolPreparedStatements(poolPreparedStatements);datasource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);datasource.setConnectionProperties(connectionProperties);log.info("===========数据库连接" + url + "===========");return datasource;}@Bean(name = "dataSource")@Primary@ConfigurationProperties("spring.datasource.druid")public DataSource dataSource(){log.info("===========数据库连接" + url + "===========");return DruidDataSourceBuilder.create().build();}@Value("${mybatis.config-location}")private String configLocation;@Value("${mybatis.mapper-locations}")private String mapperLocations;@Value("${mybatis.type-aliases-package}")private String typeAliasesPackage;@Bean(name = "sqlSessionFactory")@PrimarySqlSessionFactory sqlSessionFactory(@Qualifier("dataSource")DataSource dataSource) throws Exception {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();bean.setDataSource(dataSource);bean.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));bean.setTypeAliasesPackage(typeAliasesPackage);return bean.getObject();}@Bean@Primarypublic SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {return new SqlSessionTemplate(sqlSessionFactory);}/*** 监控页面相关*/@Value("spring.datasource.druid.stat-view-servlet.url-pattern")private String urlPattern;@Value("spring.datasource.druid.stat-view-servlet.reset-enable")private String resetEnable;@Value("spring.datasource.druid.stat-view-servlet.login-username")private String loginUsername;@Value("spring.datasource.druid.stat-view-servlet.login-password")private String loginPassword;/*** 配置 Druid 监控界面* 内置没有web.xml文件,使用 SpringBoot 注册 Servlet 方式配置*/@Beanpublic ServletRegistrationBean statViewServlet() {ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");// 这些参数可以在StatViewServlet 的父类 ResourceServlet 中找到Map<String, String> initParams = new HashMap<>();initParams.put("loginUsername", "admin"); //后台管理界面的登录账号initParams.put("loginPassword", "123456"); //后台管理界面的登录密码// 页面允许某用户可以访问// initParams.put("allow", "localhost"):表示只有本机可以访问// 第二参数为空或者为null时,表示允许所有访问initParams.put("allow", "");// 页面拒绝xxx访问// initParams.put("xxx", "127.0.0.1"):表示禁止此ip访问// 设置初始化参数bean.setInitParameters(initParams);return bean;}
Logback
<?xml version="1.0" encoding="UTF-8"?><configuration scan="true" scanPeriod="10 seconds"><contextName>logback</contextName><!-- 给某些包设置特定的日志级别 --><logger name="org.springframework" level="WARN"/><logger name="druid.sql" level="WARN"/><logger name="springfox.documentation" level="WARN"/><logger name="org.hibernate" level="WARN"/><logger name="org.mybatis" level="WARN"/><logger name="com.alibaba" level="WARN"/><!-- 设置es的日志级别 `--><logger name="org.springframework.data.elasticsearch.client.WIRE" level="trace"/><logger name="org.springframework.data.convert.CustomConversions" level="ERROR"/><springProperty scope="context" name="logLevel" source="logging.level"/><!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符--><property name="LOG_PATTERN" value="%date{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" /><!-- <property name="LOG_PATTERN" value="%msg%n" />--><!-- <property name="LOG_PATTERN" value="{'date':'%date{yyyy-MM-dd HH:mm:ss.SSS}', 'process':'%thread', 'level':'%-5level', 'author':'%logger{36}','msg':%msg}%n" />--><!-- 定义日志存储的路径 --><property name="FILE_PATH" value="./logs" /><!-- 控制台输出日志 --><appender name="console" class="ch.qos.logback.core.ConsoleAppender"><encoder><!-- 按照上面配置的LOG_PATTERN来打印日志 --><pattern>${LOG_PATTERN}</pattern></encoder></appender><!-- 每天生成一个日志文件,每月生成一个文件夹,每天超出100M新增一个文件。 --><appender name="rollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${FILE_PATH}/log_current.log</file><rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><!--日志文件输出的文件名--><FileNamePattern>${FILE_PATH}/%d{yyyy-MM, aux}/log-%d{yyyy-MM-dd}.%i.log</FileNamePattern><!-- 日志文件最大尺寸 --><maxFileSize>100MB</maxFileSize></rollingPolicy><encoder><pattern>${LOG_PATTERN}</pattern></encoder></appender><!-- 日志输出级别 --><root level="${logLevel}"><appender-ref ref="console" /><appender-ref ref="rollingFile" /></root></configuration>
# 设置日志级别logging:level:com.csj.demo.business: debug
JWT
标准中注册的声明 (建议但不强制使用) :iss: jwt签发者sub: jwt所面向的用户aud: 接收jwt的一方exp: jwt的过期时间,这个过期时间必须要大于签发时间nbf: 定义在什么时间之前,该jwt都是不可用的.iat: jwt的签发时间jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
自定义注解跳过验证@PassToken:跳过验证,通常是入口方法上用这个,比如登录接口
jwt工具类
package com.csj.demo.business.utils;import com.auth0.jwt.JWT;import com.auth0.jwt.JWTCreator;import com.auth0.jwt.JWTVerifier;import com.auth0.jwt.algorithms.Algorithm;import com.auth0.jwt.exceptions.JWTDecodeException;import com.auth0.jwt.interfaces.Claim;import com.csj.demo.business.exception.OperationException;import com.csj.demo.business.exception.ResultUtils;import java.util.Calendar;import java.util.Date;import java.util.HashMap;import java.util.Map;/*** @Desc : jwt工具类* @Author : chen* @Date : 2022/4/25 13:59*/public class JwtUtils {//盐值private static String SECRET = "superadmin";//jwt的过期时间private static Integer EXP = 30;//jwt签发者private static String ISS = "business";//接收人员private static String AUD = "WEB";//面向用户private static String SUB = "WEB";/*** 获取token* @param userId* @param userName* @return*/public static String getJWTToken(String userId, String userName){return createToken(userId,userName);}/*** 签发token* @param userId* @param userName* @return*/private static String createToken(String userId, String userName) {//签发时间Date iatDate = new Date();//过期时间Calendar nowTime = Calendar.getInstance();nowTime.add(Calendar.MINUTE, EXP);Date expiresDate = nowTime.getTime();// header MapMap<String, Object> header = new HashMap<>();//加密算法header.put("alg", "HS256");//类型 jwtheader.put("typ", "JWT");JWTCreator.Builder builder = JWT.create();//headerbuilder.withHeader(header);//发行人,发行时间,过期时间builder.withIssuer(ISS).withIssuedAt(iatDate).withExpiresAt(expiresDate);//载荷,负载信息 可在验证过后从token中获取到builder.withClaim("userName", userName);//aud 接收人员,sub 面向的用户builder.withAudience(userId).withSubject(SUB);//token 唯一标识builder.withJWTId(userId);//加密return builder.sign(Algorithm.HMAC256(userId + SECRET));}/*** 验证token* @param token* @param userId* @throws OperationException*/public static void verifyToken(String token, String userId) throws OperationException {try {JWTVerifier verifier = JWT.require(Algorithm.HMAC256(userId + SECRET)).build();verifier.verify(token);} catch (Exception e) {//效验失败 抛出异常throw new OperationException(ResultUtils.JWT_ERRCODE_NULL);}}/*** 获取接收人员* @param token* @return* @throws OperationException*/public static String getAudience(String token) throws OperationException {String audience = null;try {audience = JWT.decode(token).getAudience().get(0);} catch (JWTDecodeException j) {//这里是token解析失败throw new OperationException(ResultUtils.JWT_ERRCODE_NULL);}return audience;}/*** 获取token载荷* @param token* @param name* @return*/public static Claim getClaimByName(String token, String name) {return JWT.decode(token).getClaim(name);}}
package com.csj.demo.business.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/*** @Desc : 自定义注解:跳过token* @Author : chen* @Date : 2022/4/25 16:38*/@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface PassToken {boolean required() default true;}
自定义拦截器验证token
package com.csj.demo.business.interceptor;import com.csj.demo.business.annotation.PassToken;import com.csj.demo.business.exception.OperationException;import com.csj.demo.business.exception.ResultUtils;import com.csj.demo.business.service.UserService;import com.csj.demo.business.utils.JwtUtils;import com.oracle.tools.packager.Log;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.method.HandlerMethod;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.lang.reflect.Method;/*** @Desc : jwt验证拦截器* @Author : chen* @Date : 2022/4/25 16:24*/public class JwtAuthenticationInterceptor implements HandlerInterceptor {@Autowiredprivate UserService userService;@Overridepublic boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {// 从请求头中取出 token 这里需要和前端约定好把jwt放到请求头一个叫token的地方String token = httpServletRequest.getHeader("token");// 如果不是映射到方法直接通过if (!(object instanceof HandlerMethod)) {return true;}HandlerMethod handlerMethod = (HandlerMethod) object;Method method = handlerMethod.getMethod();//检查是否有passtoken注释,有则跳过认证if (method.isAnnotationPresent(PassToken.class)) {PassToken passToken = method.getAnnotation(PassToken.class);if (passToken.required()) {return true;}}else {//默认全部检查Log.info("jwt拦截验证Token");// 执行认证if (token == null) {//这里其实是登录失效,没token了throw new OperationException(ResultUtils.JWT_ERRCODE_NULL);}// 获取 token 中的 user NameString userId = JwtUtils.getAudience(token);//验证用户是否存在boolean b = userService.existsUserById(Long.parseLong(userId));if (!b) {throw new OperationException(ResultUtils.INVALID_USER);}// 验证 tokenJwtUtils.verifyToken(token, userId);//获取载荷内容String userName = JwtUtils.getClaimByName(token, "userName").asString();return true;}return true;}@Overridepublic void postHandle(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,Object o, ModelAndView modelAndView) throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,Object o, Exception e) throws Exception {}}
配置自定义拦截器
package com.csj.demo.business.config;import com.csj.demo.business.interceptor.JwtAuthenticationInterceptor;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** @Desc : 拦截器* @Author : chen* @Date : 2022/4/25 16:21*/@Configurationpublic class InterceptorConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {//默认拦截所有路径registry.addInterceptor(authenticationInterceptor()).addPathPatterns("/**");}@Beanpublic JwtAuthenticationInterceptor authenticationInterceptor() {return new JwtAuthenticationInterceptor();}}
热部署devtools
<!--devtools--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency>
spring:devtools:add-properties: falserestart:enabled: trueadditional-paths: src/main/javaadditional-exclude: static/**,public/**#exclude: static/**
