(1)使用Token(令牌),该Token是惟一的,且并不是持久化的,一般是临时的,是15分钟-120分钟(token的生成只用保证临时且唯一就可以了)
注意:分布式情况下,如果使用时间戳作为token,是有一定问题的,分布式情况下的时间戳可能会相同。可以加上锁去解决,不是很推荐使用时间戳作为token(即使加锁了也有可能重复)
(2)token+redis,将生成的token放到redis中,设置其在redis中的过期时间,就可以做到临时性(一般设置为3分钟左右即可)
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.xiyougroupId>
<artifactId>extannotationartifactId>
<version>1.0-SNAPSHOTversion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.0.0.RELEASEversion>
parent>
<dependencies>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>1.1.1version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-tomcatartifactId>
dependency>
<dependency>
<groupId>org.apache.tomcat.embedgroupId>
<artifactId>tomcat-embed-jasperartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-log4jartifactId>
<version>1.3.8.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
<dependency>
<groupId>commons-langgroupId>
<artifactId>commons-langartifactId>
<version>2.6version>
dependency>
<dependency>
<groupId>org.apache.httpcomponentsgroupId>
<artifactId>httpclientartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.47version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>jstlartifactId>
dependency>
<dependency>
<groupId>taglibsgroupId>
<artifactId>standardartifactId>
<version>1.1.2version>
dependency>
dependencies>
project>
spring:
datasource:
url: jdbc:mysql://localhost:3307/test?useUnicode=true&characterEncoding=UTF-8
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
test-while-idle: true
test-on-borrow: true
validation-query: SELECT 1 FROM DUAL
time-between-eviction-runs-millis: 300000
min-evictable-idle-time-millis: 1800000
redis:
database: 1
host: 127.0.0.1
port: 6379
password:
jedis:
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 0
timeout: 10000
domain:
name: www.xiyou.com
server:
port: 8889
package com.xiyou;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ExtApplication {
public static void main(String[] args) {
SpringApplication.run(ExtApplication.class, args);
}
}
package com.xiyou.ext;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义注解,目的是放置接口的重复提交,接口的幂等性和表单的重复提交
* @author
*/
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtApiIdempotent {
/**
* 定义类型,表示该token是来自请求头还是来自表单
* @return
*/
String type();
}
package com.xiyou.ext;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 该注解的作用是生成token,转发到页面进行展示
* @author
*/
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtApiToken {
}
(1)注解常量
package com.xiyou.utils;
/**
* 规定常量
*/
public interface ConstantUtils {
/**
* 表示该token是来自请求头
*/
public static String EXTAPIHEAD = "head";
/**
* 表示该token是来自form表单
*/
public static String EXTAPIFROM = "form";
}
(2)redis的相关工具类(设置过期时间,查找redis是否存在token)
package com.xiyou.utils;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;
import java.util.UUID;
/**
* redis的相关工具类
*/
@Component
public class RedisTokenUtils {
/**
* 设置过期时间
*/
private long timeOut = 3600L;
/**
* 自己设计的redis库
*/
private BaseRedisService baseRedisService;
public String getToken(){
String token = "token_" + UUID.randomUUID().toString().replaceAll("-", "");
// 将token放到redis中
baseRedisService.setString(token, token, timeOut);
// 返回生成的token
return token;
}
/**
* 查找需要的token是否在redis中,如果在可以继续执行(并且删除存在redis中的key),如果不在表示重复提交
* @param tokenKey
* @return
*/
public boolean findToken (String tokenKey) {
// 尝试从redis中获取值
String token = (String) baseRedisService.getString(tokenKey);
if (StringUtils.isBlank(token)) {
// 如果当前token为空
return false;
} else {
// 存在redis中,删除key
baseRedisService.delKey(tokenKey);
return true;
}
}
}
(3)redis的常用方法
package com.xiyou.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* 配置对应的redis
*/
@Component
public class BaseRedisService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
public void setString(String key, Object data, Long timeout){
if (data instanceof String) {
// value的类型是String类型
String value = (String) data;
stringRedisTemplate.opsForValue().set(key, value);
}
if (timeout != null) {
// 设置其过期时间
stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
}
}
/**
* 得到key对应的值
* @param key
* @return
*/
public Object getString(String key) {
return this.stringRedisTemplate.opsForValue().get(key);
}
/**
* 删除key对应的value
* @param key
*/
public void delKey (String key) {
this.stringRedisTemplate.delete(key);
}
}
package com.xiyou.aop;
import com.xiyou.ext.ExtApiIdempotent;
import com.xiyou.ext.ExtApiToken;
import com.xiyou.utils.ConstantUtils;
import com.xiyou.utils.RedisTokenUtils;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 切面类
* @author
*/
@Aspect
// 必须加注解,不加注解扫描不上这个包
@Component
public class ExtApiIdempotentAop {
@Autowired
private RedisTokenUtils redisTokenUtils;
/**
* 自定义切点
*/
@Pointcut("execution(public * com.xiyou.controller.*.*(..))")
public void rlAop() {
}
/**
* 前置通知: 这里用来处理自动生成token的注解,有ExtApiToken注解的类,需要自动生成token放到request中
*/
@Before("rlAop()")
public void before(JoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
// 判断当前调用的方法上面是否有注解
ExtApiToken extApiToken = signature.getMethod().getDeclaredAnnotation(ExtApiToken.class);
if (extApiToken != null) {
// 表示当前方法上面存在该注解
// 生成request对象,并且生成token,将token放到请求头中
getRequest().setAttribute("token", redisTokenUtils.getToken());
}
}
/**
* 环绕通知: 这里用来处理ExtApiIdempotent注解。该注解的作用是保证接口的幂等性
* @param proceedingJoinPoint
* @return
* @throws Throwable
*/
@Around("rlAop()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// 判断当前方法上是否有ExtApiIdempotent注解,有注解就表示了必须保证幂等性
MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
ExtApiIdempotent declaredAnnotation = methodSignature.getMethod().getDeclaredAnnotation(ExtApiIdempotent.class);
if (declaredAnnotation != null) {
// 如果当前类上有这个注解,表示需要判断其token是否存在
// 得到其配置的type属性,type属性表示了token是请求头中的还是表单中的
String type = declaredAnnotation.type();
String token = null;
// 得到request对象
HttpServletRequest request = getRequest();
if (type.equals(ConstantUtils.EXTAPIHEAD)) {
// 如果类型请求头,表示token是来自请求头中的
token = request.getHeader("token");
} else {
// 如果不是请求头,则认为其是表单提交,即使什么没有写
token = request.getParameter("token");
}
if (StringUtils.isBlank(token)) {
// 如果当前的token是空,表示错误
return "参数错误,没有token";
}
// 判断能否从redis中取到key为当前token的记录
// 能取到记录返回的是true,且删除redis中存在的key(当前token)
boolean isToken = redisTokenUtils.findToken(token);
if (!isToken) {
// 表示不可以取到,则表示重复提交
response("请勿重复提交");
return null;
}
}
// 放行程序,开始执行
Object proceed = proceedingJoinPoint.proceed();
return proceed;
}
/**
* 得到当前的请求对象
* @return
*/
public HttpServletRequest getRequest() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
return request;
}
/**
* 得到响应对象,往浏览器上写数据
* @param msg
* @throws IOException
*/
public void response(String msg) throws IOException {
// 得到响应对象
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletResponse response = attributes.getResponse();
// 设置响应头,规定字符格式,避免乱码
response.setHeader("Content-type", "text/html;charset=UTF-8");
PrintWriter writer = response.getWriter();
try {
writer.println(msg);
} catch (Exception e) {
} finally {
writer.close();
}
}
}