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=pageSizeZero
pagehelper.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:9200
username:
password:
connection-timeout: 5000
socket-timeout: 60000
4、添加es client配置
/**
* 初始化 es客户端 bean 7.13.0
* @return
*/
@Override
@Bean
public 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=MYSQL
mapper.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%2B8
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
druid:
# 初始化大小,最小,最大活跃数
initial-size: 3
min-idle: 3
max-active: 20
# 配置获取连接等待超时的时间,单位是毫秒
max-wait: 60000
# asyncInit是1.1.4中新增加的配置,如果有initialSize数量较多时,打开会加快应用启动时间
async-init: true
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
time-between-eviction-runs-millis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
min-evictable-idle-time-millis: 30000
max-evictable-idle-time-millis: 300000
# 用于检查连接的语句
validation-query: SELECT 1
test-while-idle: true
# 申请连接时执行validationQuery检测连接是否有效,会影响性能
test-on-borrow: false
# 归还连接时执行validationQuery检测连接是否有效
test-on-return: false
# 打开缓存PSCache,并且指定每个连接上PSCache的大小,mysql作用不大建议关闭,oracle开启效果好
pool-prepared-statements: false
max-pool-prepared-statement-per-connection-size: -1
max-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: true
url-pattern: /druid/*
# 是否可重置数据
reset-enable: false
login-username: druid
login-password: druid123
web-stat-filter:
enabled: true
url-pattern: /*
exclusions: /druid/*,*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,*.woff2
session-stat-enable: true
session-stat-max-count: 10
filter:
slf4j:
enabled: true
statement-create-after-log-enabled: false
statement-close-after-log-enabled: false
result-set-open-after-log-enabled: false
result-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")
@Primary
SqlSessionFactory 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
@Primary
public 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 方式配置
*/
@Bean
public 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 Map
Map<String, Object> header = new HashMap<>();
//加密算法
header.put("alg", "HS256");
//类型 jwt
header.put("typ", "JWT");
JWTCreator.Builder builder = JWT.create();
//header
builder.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 {
@Autowired
private UserService userService;
@Override
public 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 Name
String userId = JwtUtils.getAudience(token);
//验证用户是否存在
boolean b = userService.existsUserById(Long.parseLong(userId));
if (!b) {
throw new OperationException(ResultUtils.INVALID_USER);
}
// 验证 token
JwtUtils.verifyToken(token, userId);
//获取载荷内容
String userName = JwtUtils.getClaimByName(token, "userName").asString();
return true;
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public 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
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//默认拦截所有路径
registry.addInterceptor(authenticationInterceptor())
.addPathPatterns("/**");
}
@Bean
public 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: false
restart:
enabled: true
additional-paths: src/main/java
additional-exclude: static/**,public/**
#exclude: static/**