API接口幂等性框架设计
2021-06-18 04:06
标签:mapper stl bat login getpass second pen 成功 release 表单重复提价问题 rpc远程调用时候 发生网络延迟 可能有重试机制 MQ消费者幂等(保证唯一)一样 解决方案: token 令牌 保证唯一的并且是临时的 过一段时间失效 分布式: redis+token 注意在getToken() 这种方法代码一定要上锁 保证只有一个线程执行 否则会造成token不唯一 步骤 调用接口之前生成对应的 token,存放在redis中 调用接口的时候,将该令牌放到请求头中 (获取请求头中的令牌) 接口获取对应的令牌,如果能够获取该令牌 (将当前令牌删除掉),执行该方法业务逻辑 如果获取不到对应的令牌。返回提示“老铁 不要重复提交” 哈哈 如果别人获得了你的token 然后拿去做坏事,采用机器模拟去攻击。这时候我们要用验证码来搞定。 从代码开发者的角度看,如果每次请求都要 获取token 然后进行一统校验。代码冗余啊。如果一百个接口 要写一百次 所以采用AOP的方式进行开发,通过注解方式。 如果过滤器的话,所有接口都进行了校验。 框架开发: 自定义一个注解@ 作为标记 如果哪个Controller需要进行token的验证加上注解标记 在执行代码时候AOP通过切面类中 写的 作用接口进行 判断,如果这个接口方法有 自定义的@注解 那么进行校验逻辑 校验结果 要么提示给用户 “请勿提交” 要么通过验证 继续往下执行代码 关于表单重复提交: 在表单有个隐藏域 存放token 使用 getParameter 去获取token 然后通过返回的结果进行校验 注意 获取token的这个代码 也是用AOP去解决,实现。 否则每个Controller类都写这段代码就冗余了。前置通知搞定 注解: 首先pom: 1、关于Header的token的注解封装 2、关于表单提交的注解的封装 AOP: 订单请求接口: 表单提交的请求接口: utils: redis: 常量: mvc: redis操作token工具类: tokenutils: 实体类: Mapper: yml: 启动类: 总结: 核心就是 自定义注解 controller中的方法注解 aop切面类判断对象是否有相应的注解 如果有 从parameter或者header获取参数 进行校验 API接口幂等性框架设计 标签:mapper stl bat login getpass second pen 成功 release 原文地址:https://www.cnblogs.com/toov5/p/10312458.htmlparent>
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>
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtApiIdempotent {
String value();
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtApiIdempotent {
String value();
}
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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 com.itmayeidu.ext.ExtApiIdempotent;
import com.itmayeidu.ext.ExtApiToken;
import com.itmayeidu.utils.ConstantUtils;
import com.itmayeidu.utils.RedisTokenUtils;
import com.itmayeidu.utils.TokenUtils;
@Aspect
@Component
public class ExtApiAopIdempotent {
@Autowired
private RedisTokenUtils redisTokenUtils;
//需要作用的类
@Pointcut("execution(public * com.itmayiedu.controller.*.*(..))")
public void rlAop() {
}
// 前置通知转发Token参数 进行拦截的逻辑
@Before("rlAop()")
public void before(JoinPoint point) {
//获取并判断类上是否有注解
MethodSignature signature = (MethodSignature) point.getSignature();//统一的返回值
ExtApiToken extApiToken = signature.getMethod().getDeclaredAnnotation(ExtApiToken.class);//参数是注解的那个
if (extApiToken != null) { //如果有注解的情况
extApiToken();
}
}
// 环绕通知验证参数
@Around("rlAop()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
ExtApiIdempotent extApiIdempotent = signature.getMethod().getDeclaredAnnotation(ExtApiIdempotent.class);
if (extApiIdempotent != null) { //有注解的情况 有注解的说明需要进行token校验
return extApiIdempotent(proceedingJoinPoint, signature);
}
// 放行
Object proceed = proceedingJoinPoint.proceed(); //放行 正常执行后面(Controller)的业务逻辑
return proceed;
}
// 验证Token 方法的封装
public Object extApiIdempotent(ProceedingJoinPoint proceedingJoinPoint, MethodSignature signature)
throws Throwable {
ExtApiIdempotent extApiIdempotent = signature.getMethod().getDeclaredAnnotation(ExtApiIdempotent.class);
if (extApiIdempotent == null) {
// 直接执行程序
Object proceed = proceedingJoinPoint.proceed();
return proceed;
}
// 代码步骤:
// 1.获取令牌 存放在请求头中
HttpServletRequest request = getRequest();
// value就是获取类型 请求头之类的
String valueType = extApiIdempotent.value();
if (StringUtils.isEmpty(valueType)) {
response("参数错误!");
return null;
}
String token = null;
if (valueType.equals(ConstantUtils.EXTAPIHEAD)) { //如果存在header中 从头中获取
token = request.getHeader("token"); //从头中获取
} else {
token = request.getParameter("token"); //否则从 请求参数获取
}
if (StringUtils.isEmpty(token)) {
response("参数错误!");
return null;
}
if (!redisTokenUtils.findToken(token)) {
response("请勿重复提交!");
return null;
}
Object proceed = proceedingJoinPoint.proceed();
return proceed;
}
public void extApiToken() {
String token = redisTokenUtils.getToken();
getRequest().setAttribute("token", token);
}
public HttpServletRequest getRequest() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
return request;
}
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();
}
}
}
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.itmayeidu.ext.ExtApiIdempotent;
import com.itmayeidu.utils.ConstantUtils;
import com.itmayeidu.utils.RedisTokenUtils;
import com.itmayeidu.utils.TokenUtils;
import com.itmayiedu.entity.OrderEntity;
import com.itmayiedu.mapper.OrderMapper;
@RestController
public class OrderController {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RedisTokenUtils redisTokenUtils;
// 从redis中获取Token
@RequestMapping("/redisToken")
public String RedisToken() {
return redisTokenUtils.getToken();
}
// 验证Token
@RequestMapping(value = "/addOrderExtApiIdempotent", produces = "application/json; charset=utf-8")
@ExtApiIdempotent(value = ConstantUtils.EXTAPIHEAD)
public String addOrderExtApiIdempotent(@RequestBody OrderEntity orderEntity, HttpServletRequest request) {
int result = orderMapper.addOrder(orderEntity);
return result > 0 ? "添加成功" : "添加失败" + "";
}
}
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import com.itmayeidu.ext.ExtApiIdempotent;
import com.itmayeidu.ext.ExtApiToken;
import com.itmayeidu.utils.ConstantUtils;
import com.itmayiedu.entity.OrderEntity;
import com.itmayiedu.mapper.OrderMapper;
@Controller
public class OrderPageController {
@Autowired
private OrderMapper orderMapper;
@RequestMapping("/indexPage")
@ExtApiToken
public String indexPage(HttpServletRequest req) {
return "indexPage";
}
@RequestMapping("/addOrderPage")
@ExtApiIdempotent(value = ConstantUtils.EXTAPIFROM)
public String addOrder(OrderEntity orderEntity) {
int addOrder = orderMapper.addOrder(orderEntity);
return addOrder > 0 ? "success" : "fail";
}
}
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
@Component
public class BaseRedisService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
public void setString(String key, Object data, Long timeout) {
if (data instanceof String) {
String value = (String) data;
stringRedisTemplate.opsForValue().set(key, value);
}
if (timeout != null) {
stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
}
}
public Object getString(String key) {
return stringRedisTemplate.opsForValue().get(key);
}
public void delKey(String key) {
stringRedisTemplate.delete(key);
}
}
public interface ConstantUtils {
static final String EXTAPIHEAD = "head";
static final String EXTAPIFROM = "from";
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;
@Configuration
@EnableWebMvc
@ComponentScan("com.too5.controller")
public class MyMvcConfig {
@Bean // 出现问题原因 @bean 忘记添加
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/jsp/");
viewResolver.setSuffix(".jsp");
viewResolver.setViewClass(JstlView.class);
return viewResolver;
}
}
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class RedisTokenUtils {
private long timeout = 60 * 60;
@Autowired
private BaseRedisService baseRedisService;
// 将token存入在redis
public String getToken() {
String token = "token" + System.currentTimeMillis();
baseRedisService.setString(token, token, timeout);
return token;
}
public boolean findToken(String tokenKey) {
String token = (String) baseRedisService.getString(tokenKey);
if (StringUtils.isEmpty(token)) {
return false;
}
// token 获取成功后 删除对应tokenMapstoken
baseRedisService.delKey(token);
return true;
}
}
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang.StringUtils;
public class TokenUtils {
private static Map
public class OrderEntity {
private int id;
private String orderName;
private String orderDes;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getOrderName() {
return orderName;
}
public void setOrderName(String orderName) {
this.orderName = orderName;
}
public String getOrderDes() {
return orderDes;
}
public void setOrderDes(String orderDes) {
this.orderDes = orderDes;
}
}
public class UserEntity {
private Long id;
private String userName;
private String password;
public Long getId() {
return id;
}
public void setId(Long 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;
}
@Override
public String toString() {
return "UserEntity [id=" + id + ", userName=" + userName + ", password=" + password + "]";
}
}
import org.apache.ibatis.annotations.Insert;
import com.itmayiedu.entity.OrderEntity;
public interface OrderMapper {
@Insert("insert order_info values (null,#{orderName},#{orderDes})")
public int addOrder(OrderEntity OrderEntity);
}
public interface UserMapper {
@Select(" SELECT * FROM user_info where userName=#{userName} and password=#{password}")
public UserEntity login(UserEntity userEntity);
@Insert("insert user_info values (null,#{userName},#{password})")
public int insertUser(UserEntity userEntity);
}
spring:
mvc:
view:
# 页面默认前缀目录
prefix: /WEB-INF/jsp/
# 响应页面默认后缀
suffix: .jsp
spring:
datasource:
url: jdbc:mysql://localhost:3306/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: 106.15.185.133
port: 6379
password: meitedu.+@
jedis:
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 0
timeout: 10000
domain:
name: www.toov5.com
@MapperScan(basePackages = { "com.tov5.mapper" })
@SpringBootApplication
@ServletComponentScan
public class AppB {
public static void main(String[] args) {
SpringApplication.run(AppB.class, args);
}
}