Spring Boot 项目搭建模板
首先来看一下项目结构
看看项目实际都集成了哪些模块
- 全局异常处理
- 响应码枚举
- 自定义统一响应体
- 全局处理响应数据
- 拦截器
- 全局跨域
- 阿里短信服务
- 阿里 OSS 服务
- MyBatis
- MySQL
- Redis
- Swagger
- JWT
- Lombok
- Https 认证
全局异常处理
首先来看一下代码的目录结构
-
自定义异常
/** * @description: 全局异常处理类 * @author: wubowen * @date: 2021/5/25 0025 10:04 */ @Slf4j @RestControllerAdvice public class GlobalExceptionHandler { /** * 处理自定义异常 */ @ExceptionHandler(value = BadRequestException.class) public ResponseEntity<ApiError> badRequestException(BadRequestException e) { return buildResponseEntity(ApiError.error(e.getStatus(), e.getMessage())); } /** * 统一返回 */ private ResponseEntity<ApiError> buildResponseEntity(ApiError apiError) { return new ResponseEntity<>(apiError, HttpStatus.valueOf(apiError.getStatus())); } }
-
全局异常处理类
/** * @description: 全局异常处理类 * @author: wubowen * @date: 2021/5/25 0025 10:04 */ @Slf4j @RestControllerAdvice public class GlobalExceptionHandler { /** * 处理自定义异常 */ @ExceptionHandler(value = BadRequestException.class) public ResponseEntity<ApiError> badRequestException(BadRequestException e) { return buildResponseEntity(ApiError.error(e.getStatus(), e.getMessage())); } /** * 统一返回 */ private ResponseEntity<ApiError> buildResponseEntity(ApiError apiError) { return new ResponseEntity<>(apiError, HttpStatus.valueOf(apiError.getStatus())); } }
-
统一返回体实现
/** * @description: 全局异常处理类 * @author: wubowen * @date: 2021/5/25 0025 10:04 */ @Slf4j @RestControllerAdvice public class GlobalExceptionHandler { /** * 处理自定义异常 */ @ExceptionHandler(value = BadRequestException.class) public ResultV0<String> badRequestException(BadRequestException e) { return new ResultV0<>(ResultCode.VALIDATE_FAILED, null); } }
响应码枚举
/**
* @description: 响应码枚举类
* @author: wubowen
* @date: 2021/5/25 0025 14:46
*/
@Getter
public enum ResultCode {
SUCCESS(1000, "操作成功"),
VALIDATE_FAILED(1001, "参数校验失败");
private int code;
private String msg;
ResultCode(int code, String msg) {
this.code = code;
this.msg = msg;
}
}
自定义统一响应体
package com.stork.base;
import com.stork.utils.enums.ResultCode;
import lombok.Getter;
/**
* @description: 自定义统一的响应体
* @author: wubowen
* @date: 2021/5/26 0026 16:48
*/
@Getter
public class ResultV0<T> {
/**
* 状态码
*/
private int code;
/**
* 响应信息
*/
private String msg;
/**
* 响应的具体数据
*/
private T data;
public ResultV0(T data) {
this(ResultCode.SUCCESS, data);
}
public ResultV0(ResultCode resultCode, T data) {
this.code = resultCode.getCode();
this.msg = resultCode.getMsg();
this.data = data;
}
public ResultV0(ResultCode resultCode) {
this.code = resultCode.getCode();
this.msg = resultCode.getMsg();
this.data = null;
}
public ResultV0(ResultCode resultCode, String msg) {
this.code = resultCode.getCode();
this.msg = msg;
this.data = null;
}
}
全局处理响应数据
package com.stork.global;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.stork.base.ResultV0;
import com.stork.exception.BadRequestException;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
/**
* @description: 全局处理响应数据
* 包装返回的实体类,统一返回体
* @author: wubowen
* @date: 2021/5/26 0026 17:03
*/
@RestControllerAdvice(basePackages = "com.stork.handler")
public class ResponseControllerAdvice implements ResponseBodyAdvice<Object> {
public static final String NEWSIMAGEUPLOADPATH = "/news/uploadImageForNews";
public static final String NEWSVIDEOUPLOADPATH = "/news/uploadVideoForNews";
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
/**
* 如果接口返回的类型本身就是ResultV0,那就没有必要进行额外操作,直接返回false
*/
return !methodParameter.getParameterType().equals(ResultV0.class);
}
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
if (serverHttpRequest.getURI().getPath().equals(NEWSIMAGEUPLOADPATH)
||serverHttpRequest.getURI().getPath().equals(NEWSVIDEOUPLOADPATH)) {
return o;
}
/**
* String类型不能直接包装,所以要进行些特别的处理
*/
if (methodParameter.getGenericParameterType().equals(String.class)) {
ObjectMapper objectMapper = new ObjectMapper();
try {
/**
* String类型不能直接包装,所以要进行些特别的处理
*/
return objectMapper.writeValueAsString(new ResultV0<>(o));
} catch (JsonProcessingException e) {
throw new BadRequestException("返回String类型错误");
}
}
/**
* 将原本的数据包装到ResultVO里面
*/
return new ResultV0<>(o);
}
}
@RestControllerAdvice注解必须加上扫描包的路径,否则会影响Swagger的使用
拦截器
-
新建一个拦截器
package com.stork.global; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @description: 测试拦截器 * @author: wubowen * @date: 2021/7/12 0012 15:31 */ public class TestIntercepter implements HandlerInterceptor { /** 在请求处理之前被调用,在Controller方法调用之前调用 **/ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 此处为某些接口放行,不走以下逻辑 if (request.getRequestURI().contains("/.")) { return true; } return false; } /** 在请求处理之后被调用,但是在视图渲染之前调用 **/ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } }
-
配置新建的拦截器
package com.stork.config; import com.stork.global.TestIntercepter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * @description: 配置拦截器 * @author: wubowen * @date: 2021/7/12 0012 15:47 */ @Configuration public class TestIntercepterConfiguration implements WebMvcConfigurer { @Bean public HandlerInterceptor getTestInterceptor() { return new TestIntercepter(); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(getTestInterceptor()).addPathPatterns("/test"); } }
全局跨域配置
看下目录结构
/**
* @description: 跨域配置
* @author: wubowen
* @date: 2021/5/26 0026 10:17
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
/**
* 跨域配置
* addMapping:对哪种格式的请求路径进行处理
* allowedHeaders:允许的请求头
* allowedMethods:允许的请求方法
* maxAge:探测请求的有效期
* allowedOrigins:支持的域
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedHeaders("*")
.allowedMethods("*")
.maxAge(1800)
.allowedOrigins("*");
}
}
阿里短信服务
-
配置yml文件
# 阿里短信服务配置 sms: sign-name : ********** template-code : ********** access-keyID : ********** access-keySecret : **********
-
导入短信依赖
<!-- 阿里短信依赖--> <dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-core</artifactId> <version>4.5.3</version> </dependency>
-
创建短信接口
package com.stork.service; /** * @description: 短信服务 * @author: wubowen * @date: 2021/7/12 0012 17:51 */ public interface SmsService { /** * 根据电话号码发送短信 * @param phone 电话号码 */ void sendSmsCode(String phone); /** * 验证短信验证码正确性 * @param phone 电话号码 * @param code 验证码 * @return */ boolean validSmsCode(String phone, String code); }
-
完成短信实际业务
@Service public class SmsServiceImpl implements SmsService { @Value("${sms.access-keyID}") private String accessID; @Value("${sms.access-keySecret}") private String accessKey; @Value("${sms.sign-name}") private String signName; @Value("${sms.template-code}") private String templageCode; @Autowired private RedisUtils redisUtils; // JSON处理工具 @Autowired private ObjectMapper objectMapper; @Override public void sendSmsCode(String phone) { // 是否频繁发送验证码 if (isSendOften(phone)) { throw new BadRequestException("验证码请求过于频繁"); } Sms sms = makeCode(phone); ObjectNode smsJson = objectMapper.createObjectNode(); smsJson.put("code", sms.getSmsCode()); smsJson.put("product", "Stork"); try { CommonResponse commonResponse = send(phone, signName, templageCode, smsJson); if (commonResponse.getData() != null) { redisCode(sms); } } catch (JsonProcessingException e) { e.printStackTrace(); } } @Override public boolean validSmsCode(String phone, String code) { try { Sms sms = objectMapper.readValue(String.valueOf(redisUtils.get(phone)), Sms.class); if (sms == null) { return false; } if (sms.getSmsCode().equals(code)) { return true; } } catch (JsonProcessingException e) { e.printStackTrace(); } return false; } /** * 生成四位随机的验证码 * @param phone 电话号码 * @return */ private Sms makeCode(String phone) { String smsCode = RandomUtils.getRandomString(4); Sms sms = new Sms(); sms.setPhone(phone); sms.setSmsCode(smsCode); sms.setTime(System.currentTimeMillis()); return sms; } /** * 验证这个电话短信发送是否频繁 * @param phone 电话号码 * @return */ private boolean isSendOften(String phone) { if (redisUtils.get(phone) == null) { return false; } // 反序列化 Sms sms = null; try { sms = objectMapper.readValue(String.valueOf(redisUtils.get(phone)), Sms.class); } catch (JsonProcessingException e) { e.printStackTrace(); } if (sms.getTime() + 60*1000 >= System.currentTimeMillis()) { return true; } return false; } /** * 将验证码缓存到redis,并且制定缓存时间为10分钟 * @param sms 短信实体 */ private void redisCode(Sms sms) throws JsonProcessingException { redisUtils.set(sms.getPhone(), objectMapper.writeValueAsString(sms), 10L, TimeUnit.MINUTES); } /** * 发送短信 * @param phone * @param signName * @param templageCode * @param params * @return */ private CommonResponse send(String phone, String signName, String templageCode, ObjectNode params) throws JsonProcessingException { DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessID, accessKey); IAcsClient client = new DefaultAcsClient(profile); CommonResponse response = null; CommonRequest request = new CommonRequest(); request.setSysMethod(MethodType.POST); request.setSysDomain("dysmsapi.aliyuncs.com"); request.setSysVersion("2017-05-25"); request.setSysAction("SendSms"); request.putQueryParameter("RegionId", "cn-chengdu"); request.putQueryParameter("PhoneNumbers", phone); request.putQueryParameter("SignName", signName); request.putQueryParameter("TemplateCode", templageCode); request.putQueryParameter("TemplateParam", objectMapper.writeValueAsString(params)); try { response = client.getCommonResponse(request); } catch (ServerException e) { e.printStackTrace(); } catch (ClientException e) { e.printStackTrace(); } return response; } }
阿里 OSS 服务
-
阿里云基础配置
# 阿里云基础配置 aliyun: accessKeyId : ********** accessKeySecret : ********** chengduEndpoint : https://oss-cn-chengdu.aliyuncs.com
-
导入 OSS 依赖
<!--阿里OSS业务--> <dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>3.10.2</version> </dependency>
-
创建 OSS 接口
package com.stork.service; import com.aliyun.oss.model.OSSObject; import org.springframework.web.multipart.MultipartFile; /** * @description: OSS服务 * @author: wubowen * @date: 2021/7/14 0014 15:18 */ public interface OSSService { /** * 文件上传接口 * @param endpoint * @param bucketName * @param objectName <yourObjectName>上传文件到OSS时需要指定包含文件后缀在内的完整路径,例如abc/efg/123.jpg。 * @param file 上传文件 */ void uploadFile(String endpoint, String bucketName, String objectName, MultipartFile file); /** * 文件下载接口 * @param endpoint * @param bucketName * @param objectName <yourObjectName>从OSS下载文件时需要指定包含文件后缀在内的完整路径,例如abc/efg/123.jpg。 * @return */ OSSObject downloadFile(String endpoint, String bucketName, String objectName); /** * 文件删除接口 * @param endpoint * @param bucketName * @param objectName 填写文件完整路径。文件完整路径中不能包含Bucket名称。 */ void deleteFile(String endpoint, String bucketName, String objectName); }
-
完成 OSS 实现类
package com.stork.service.Impl; import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.model.OSSObject; import com.stork.service.OSSService; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.io.ByteArrayInputStream; import java.io.IOException; /** * @description: some desc * @author: wubowen * @date: 2021/7/14 0014 15:45 */ @Service public class OSSServiceImpl implements OSSService { @Value("${aliyun.accessKeyId}") private String accessKeyId; @Value("${aliyun.accessKeySecret}") private String accessKeySecret; @Override public void uploadFile(String endpoint, String bucketName, String objectName, MultipartFile file) { // 创建OSSClient实例。 OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); // 上传文件到指定的存储空间(bucketName)并将其保存为指定的文件名称(objectName)。 try { ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(file.getBytes())); } catch (IOException e) { e.printStackTrace(); } // 关闭OSSClient。 ossClient.shutdown(); } @Override public OSSObject downloadFile(String endpoint, String bucketName, String objectName) { return null; } @Override public void deleteFile(String endpoint, String bucketName, String objectName) { // 创建OSSClient实例。 OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); // 删除文件或目录。如果要删除目录,目录必须为空。 ossClient.deleteObject(bucketName, objectName); // 关闭OSSClient。 ossClient.shutdown(); } }
MyBatis
-
引入依赖
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency>
-
扫描指定包下所有Mapper
注意:如果mapper配置文件没有放在resources文件夹下需要重新在pom.xml中指定资源文件夹路径
<build>
<!-- 重新指定资源文件夹的路径 -->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
</build>
MySQL
-
引入依赖
<!--Mysql依赖包--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
-
配置 yml 文件
spring: datasource: url: jdbc:mysql://主机名:3306/yunying?useUnicode=true&serverTimezone=GMT%2B8&characterEncoding=UTF-8&useSSL=false&allowPublicKeyRetrieval=true username: 账号 password: 密码 driver-class-name: com.mysql.cj.jdbc.Driver
Redis
-
引入 Redis 依赖
<!--Spring boot Redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
-
配置 Redis
redis: #主机IP地址 host : localhost #Redis服务器连接端口 port : 6379 #Redis服务器连接密码(默认为空) #password : #连接池最大连接数(使用负值表示没有限制) max-active : 8 #连接池最大阻塞等待时间(使用负值表示没有限制) max-wait : -1 #连接池中的最大空闲连接 max-idle : 8 #连接池中的最小空闲连接 min-idle : 0 #连接超时时间(毫秒) timeout : 30000
-
编写 Redis 工具类
/** * @description: Redis工具类 * @author: wubowen * @date: 2021/5/27 0027 14:39 */ @Component public class RedisUtils { private static final Logger log = LoggerFactory.getLogger(RedisUtils.class); private RedisTemplate<Object, Object> redisTemplate; // @Value("${jwt.online-key}") // private String onlineKey; public RedisUtils(RedisTemplate<Object, Object> redisTemplate) { this.redisTemplate = redisTemplate; } /** * 指定缓存失效时间 * * @param key 键 * @param time 时间(秒) */ public boolean expire(String key, long time) { try { if (time > 0) { redisTemplate.expire(key, time, TimeUnit.SECONDS); } } catch (Exception e) { log.error(e.getMessage(), e); return false; } return true; } /** * 指定缓存失效时间 * * @param key 键 * @param time 时间(秒) * @param timeUnit 单位 */ public boolean expire(String key, long time, TimeUnit timeUnit) { try { if (time > 0) { redisTemplate.expire(key, time, timeUnit); } } catch (Exception e) { log.error(e.getMessage(), e); return false; } return true; } /** * 根据 key 获取过期时间 * * @param key 键 不能为null * @return 时间(秒) 返回0代表为永久有效 */ public long getExpire(Object key) { return redisTemplate.getExpire(key, TimeUnit.SECONDS); } /** * 查找匹配key * * @param pattern key * @return / */ public List<String> scan(String pattern) { ScanOptions options = ScanOptions.scanOptions().match(pattern).build(); RedisConnectionFactory factory = redisTemplate.getConnectionFactory(); RedisConnection rc = Objects.requireNonNull(factory).getConnection(); Cursor<byte[]> cursor = rc.scan(options); List<String> result = new ArrayList<>(); while (cursor.hasNext()) { result.add(new String(cursor.next())); } try { RedisConnectionUtils.releaseConnection(rc, factory); } catch (Exception e) { log.error(e.getMessage(), e); } return result; } /** * 分页查询 key * * @param patternKey key * @param page 页码 * @param size 每页数目 * @return / */ public List<String> findKeysForPage(String patternKey, int page, int size) { ScanOptions options = ScanOptions.scanOptions().match(patternKey).build(); RedisConnectionFactory factory = redisTemplate.getConnectionFactory(); RedisConnection rc = Objects.requireNonNull(factory).getConnection(); Cursor<byte[]> cursor = rc.scan(options); List<String> result = new ArrayList<>(size); int tmpIndex = 0; int fromIndex = page * size; int toIndex = page * size + size; while (cursor.hasNext()) { if (tmpIndex >= fromIndex && tmpIndex < toIndex) { result.add(new String(cursor.next())); tmpIndex++; continue; } // 获取到满足条件的数据后,就可以退出了 if (tmpIndex >= toIndex) { break; } tmpIndex++; cursor.next(); } try { RedisConnectionUtils.releaseConnection(rc, factory); } catch (Exception e) { log.error(e.getMessage(), e); } return result; } /** * 判断key是否存在 * * @param key 键 * @return true 存在 false不存在 */ public boolean hasKey(String key) { try { return redisTemplate.hasKey(key); } catch (Exception e) { log.error(e.getMessage(), e); return false; } } /** * 删除缓存 * * @param key 可以传一个值 或多个 */ public void del(String... keys) { if (keys != null && keys.length > 0) { if (keys.length == 1) { boolean result = redisTemplate.delete(keys[0]); log.debug("--------------------------------------------"); log.debug(new StringBuilder("删除缓存:").append(keys[0]).append(",结果:").append(result).toString()); log.debug("--------------------------------------------"); } else { Set<Object> keySet = new HashSet<>(); for (String key : keys) { keySet.addAll(redisTemplate.keys(key)); } long count = redisTemplate.delete(keySet); log.debug("--------------------------------------------"); log.debug("成功删除缓存:" + keySet.toString()); log.debug("缓存删除数量:" + count + "个"); log.debug("--------------------------------------------"); } } } // ============================String============================= /** * 普通缓存获取 * * @param key 键 * @return 值 */ public Object get(String key) { return key == null ? null : redisTemplate.opsForValue().get(key); } /** * 批量获取 * * @param keys * @return */ public List<Object> multiGet(List<String> keys) { Object obj = redisTemplate.opsForValue().multiGet(Collections.singleton(keys)); return null; } /** * 普通缓存放入 * * @param key 键 * @param value 值 * @return true成功 false失败 */ public boolean set(String key, Object value) { try { redisTemplate.opsForValue().set(key, value); return true; } catch (Exception e) { log.error(e.getMessage(), e); return false; } } /** * 普通缓存放入并设置时间 * * @param key 键 * @param value 值 * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 * @return true成功 false 失败 */ public boolean set(String key, Object value, long time) { try { if (time > 0) { redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); } else { set(key, value); } return true; } catch (Exception e) { log.error(e.getMessage(), e); return false; } } /** * 普通缓存放入并设置时间 * * @param key 键 * @param value 值 * @param time 时间 * @param timeUnit 类型 * @return true成功 false 失败 */ public boolean set(String key, Object value, long time, TimeUnit timeUnit) { try { if (time > 0) { redisTemplate.opsForValue().set(key, value, time, timeUnit); } else { set(key, value); } return true; } catch (Exception e) { log.error(e.getMessage(), e); return false; } } // ================================Map================================= /** * HashGet * * @param key 键 不能为null * @param item 项 不能为null * @return 值 */ public Object hget(String key, String item) { return redisTemplate.opsForHash().get(key, item); } /** * 获取hashKey对应的所有键值 * * @param key 键 * @return 对应的多个键值 */ public Map<Object, Object> hmget(String key) { return redisTemplate.opsForHash().entries(key); } /** * HashSet * * @param key 键 * @param map 对应多个键值 * @return true 成功 false 失败 */ public boolean hmset(String key, Map<String, Object> map) { try { redisTemplate.opsForHash().putAll(key, map); return true; } catch (Exception e) { log.error(e.getMessage(), e); return false; } } /** * HashSet 并设置时间 * * @param key 键 * @param map 对应多个键值 * @param time 时间(秒) * @return true成功 false失败 */ public boolean hmset(String key, Map<String, Object> map, long time) { try { redisTemplate.opsForHash().putAll(key, map); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { log.error(e.getMessage(), e); return false; } } /** * 向一张hash表中放入数据,如果不存在将创建 * * @param key 键 * @param item 项 * @param value 值 * @return true 成功 false失败 */ public boolean hset(String key, String item, Object value) { try { redisTemplate.opsForHash().put(key, item, value); return true; } catch (Exception e) { log.error(e.getMessage(), e); return false; } } /** * 向一张hash表中放入数据,如果不存在将创建 * * @param key 键 * @param item 项 * @param value 值 * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间 * @return true 成功 false失败 */ public boolean hset(String key, String item, Object value, long time) { try { redisTemplate.opsForHash().put(key, item, value); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { log.error(e.getMessage(), e); return false; } } /** * 删除hash表中的值 * * @param key 键 不能为null * @param item 项 可以使多个 不能为null */ public void hdel(String key, Object... item) { redisTemplate.opsForHash().delete(key, item); } /** * 判断hash表中是否有该项的值 * * @param key 键 不能为null * @param item 项 不能为null * @return true 存在 false不存在 */ public boolean hHasKey(String key, String item) { return redisTemplate.opsForHash().hasKey(key, item); } /** * hash递增 如果不存在,就会创建一个 并把新增后的值返回 * * @param key 键 * @param item 项 * @param by 要增加几(大于0) * @return */ public double hincr(String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, by); } /** * hash递减 * * @param key 键 * @param item 项 * @param by 要减少记(小于0) * @return */ public double hdecr(String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, -by); } // ============================set============================= /** * 根据key获取Set中的所有值 * * @param key 键 * @return */ public Set<Object> sGet(String key) { try { return redisTemplate.opsForSet().members(key); } catch (Exception e) { log.error(e.getMessage(), e); return null; } } /** * 根据value从一个set中查询,是否存在 * * @param key 键 * @param value 值 * @return true 存在 false不存在 */ public boolean sHasKey(String key, Object value) { try { return redisTemplate.opsForSet().isMember(key, value); } catch (Exception e) { log.error(e.getMessage(), e); return false; } } /** * 将数据放入set缓存 * * @param key 键 * @param values 值 可以是多个 * @return 成功个数 */ public long sSet(String key, Object... values) { try { return redisTemplate.opsForSet().add(key, values); } catch (Exception e) { log.error(e.getMessage(), e); return 0; } } /** * 将set数据放入缓存 * * @param key 键 * @param time 时间(秒) * @param values 值 可以是多个 * @return 成功个数 */ public long sSetAndTime(String key, long time, Object... values) { try { Long count = redisTemplate.opsForSet().add(key, values); if (time > 0) { expire(key, time); } return count; } catch (Exception e) { log.error(e.getMessage(), e); return 0; } } /** * 获取set缓存的长度 * * @param key 键 * @return */ public long sGetSetSize(String key) { try { return redisTemplate.opsForSet().size(key); } catch (Exception e) { log.error(e.getMessage(), e); return 0; } } /** * 移除值为value的 * * @param key 键 * @param values 值 可以是多个 * @return 移除的个数 */ public long setRemove(String key, Object... values) { try { Long count = redisTemplate.opsForSet().remove(key, values); return count; } catch (Exception e) { log.error(e.getMessage(), e); return 0; } } // ===============================list================================= /** * 获取list缓存的内容 * * @param key 键 * @param start 开始 * @param end 结束 0 到 -1代表所有值 * @return */ public List<Object> lGet(String key, long start, long end) { try { return redisTemplate.opsForList().range(key, start, end); } catch (Exception e) { log.error(e.getMessage(), e); return null; } } /** * 获取list缓存的长度 * * @param key 键 * @return */ public long lGetListSize(String key) { try { return redisTemplate.opsForList().size(key); } catch (Exception e) { log.error(e.getMessage(), e); return 0; } } /** * 通过索引 获取list中的值 * * @param key 键 * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推 * @return */ public Object lGetIndex(String key, long index) { try { return redisTemplate.opsForList().index(key, index); } catch (Exception e) { log.error(e.getMessage(), e); return null; } } /** * 将list放入缓存 * * @param key 键 * @param value 值 * @return */ public boolean lSet(String key, Object value) { try { redisTemplate.opsForList().rightPush(key, value); return true; } catch (Exception e) { log.error(e.getMessage(), e); return false; } } /** * 将list放入缓存 * * @param key 键 * @param value 值 * @param time 时间(秒) * @return */ public boolean lSet(String key, Object value, long time) { try { redisTemplate.opsForList().rightPush(key, value); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { log.error(e.getMessage(), e); return false; } } /** * 将list放入缓存 * * @param key 键 * @param value 值 * @return */ public boolean lSet(String key, List<Object> value) { try { redisTemplate.opsForList().rightPushAll(key, value); return true; } catch (Exception e) { log.error(e.getMessage(), e); return false; } } /** * 将list放入缓存 * * @param key 键 * @param value 值 * @param time 时间(秒) * @return */ public boolean lSet(String key, List<Object> value, long time) { try { redisTemplate.opsForList().rightPushAll(key, value); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { log.error(e.getMessage(), e); return false; } } /** * 根据索引修改list中的某条数据 * * @param key 键 * @param index 索引 * @param value 值 * @return / */ public boolean lUpdateIndex(String key, long index, Object value) { try { redisTemplate.opsForList().set(key, index, value); return true; } catch (Exception e) { log.error(e.getMessage(), e); return false; } } /** * 移除N个值为value * * @param key 键 * @param count 移除多少个 * @param value 值 * @return 移除的个数 */ public long lRemove(String key, long count, Object value) { try { return redisTemplate.opsForList().remove(key, count, value); } catch (Exception e) { log.error(e.getMessage(), e); return 0; } } /** * @param prefix 前缀 * @param ids id */ public void delByKeys(String prefix, Set<Long> ids) { Set<Object> keys = new HashSet<>(); for (Long id : ids) { keys.addAll(redisTemplate.keys(new StringBuffer(prefix).append(id).toString())); } long count = redisTemplate.delete(keys); // 此处提示可自行删除 log.debug("--------------------------------------------"); log.debug("成功删除缓存:" + keys.toString()); log.debug("缓存删除数量:" + count + "个"); log.debug("--------------------------------------------"); } }
-
测试
@ApiOperation("创建临时用户(用于测试Redis)") @PostMapping("/addTempUser") public String addTempUser(@RequestBody User user) { redisUtils.set(user.getUsername(), user.getPassword()); String password = (String) redisUtils.get(user.getUsername()); return "您设置的密码为:" + password; }
Swagger
-
依赖导入
<!--Swagger接口管理工具相关依赖--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <!--SwaggerUI依赖,接口的可视化查看--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency>
-
配置
package com.stork.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ParameterBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.schema.ModelRef; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; import springfox.documentation.service.Parameter; import java.util.ArrayList; import java.util.List; /** * @description: 接口管理工具Swagger的配置类 * @author: wubowen * @date: 2021/5/25 0025 17:13 */ @Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket api(){ // 如果接口需要token验证,那么在header中新增一个参数 Parameter tokenPara = new ParameterBuilder() .name("token") .modelRef(new ModelRef("string")) .parameterType("header") .required(false) .build(); List<Parameter> paras = new ArrayList<>(); paras.add(tokenPara); return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.any()) .paths(PathSelectors.any()) .build() .globalOperationParameters(paras); } }
JWT
-
引入 JWT 依赖
<!-- JWT --> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.8.2</version> </dependency>
-
新建 JWT 工具类
package com.stork.utils; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; import com.stork.entities.StorkUser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * @description: JWT工具类,生成JWT和认证 * @author: wubowen * @date: 2021/11/13 0013 9:48 */ public class JwtUtil { private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class); /** * 秘钥 */ private static final String SECRET = "yunying_secret"; /** * 过期时间 */ private static final long EXPIRATION = 2592000L; /** * 生成用户token,设置超时时间 * @param storkUser * @return */ public static String createToken(StorkUser storkUser) { // 过期时间 Date expireDate = new Date(System.currentTimeMillis() + EXPIRATION * 1000); // 设置JWT规则 Map<String, Object> rule = new HashMap<>(); rule.put("alg", "HS256"); rule.put("typ", "JWT"); // 创建token String token = JWT.create() .withHeader(rule) .withClaim("username", storkUser.getUsername()) .withExpiresAt(expireDate) .withIssuedAt(new Date()) .sign(Algorithm.HMAC256(SECRET)); return token; } /** * 校验token并解析token * @param token 需要验证的token * @return */ public static Map<String, Claim> verifyToken(String token) { DecodedJWT jwt = null; try { JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build(); jwt = verifier.verify(token); } catch (Exception e) { logger.error(e.getMessage()); logger.error("token解码异常"); return null; } return jwt.getClaims(); } }
-
拦截器中加入 token 验证
package com.stork.global; import com.auth0.jwt.interfaces.Claim; import com.stork.entities.StorkUser; import com.stork.exception.BadRequestException; import com.stork.utils.JwtUtil; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Date; import java.util.Map; /** * @description: 接口请求拦截器 * @author: wubowen * @date: 2021/7/12 0012 15:31 */ public class RequestIntercepter implements HandlerInterceptor { /** 在请求处理之前被调用,在Controller方法调用之前调用 **/ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 此处为某些接口放行,不走以下逻辑 //if (request.getRequestURI().contains("/.")) { // return true; //} // 获取header里的token final String token = request.getHeader("token"); // 验证token的有效性 if (token == null) { throw new BadRequestException("请求失败,请重新登录"); } // 解析token Map<String, Claim> userData = JwtUtil.verifyToken(token); // 检查token是否过期 Date expireDate = userData.get("exp").asDate(); boolean isExpire = expireDate.after(new Date()); if (!isExpire) { throw new BadRequestException("请求过期,请重新登录"); } // 更新token StorkUser storkUser = new StorkUser(); storkUser.setUsername(userData.get("username").asString()); String newToken = JwtUtil.createToken(storkUser); response.setHeader("token", newToken); return true; } /** 在请求处理之后被调用,但是在视图渲染之前调用 **/ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } }
Lombok
-
安装插件
-
引入依赖
<!--lombok插件--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>
Https 认证
-
生成证书
输入命令行
keytool -genkey -alias yunyinghttps -keyalg RSA -keysize 2048 -keystore sang.p12 -validity 365
2. 拷贝证书到项目根目录
-
配置 yml 文件
server: port: 8080 # https配置 ssl: key-store: sang.p12 key-alias: yunyinghttps key-store-password: ******