当前位置:网站首页>Idempotent design of Internet API interface
Idempotent design of Internet API interface
2022-07-02 09:37:00 【niceyz】
The concept of idempotence : Guarantee the only meaning How to prevent interfaces from repeatedly submitting === Ensure interface idempotency
Causes of interface idempotence :1.rpc Network delay when calling ( Retry sending request ) 2. Form resubmission
Solutions :redis+token, Use Tonken token , Guarantee temporary and unique , take token Put in redis in , And set expiration time
How to use Token Solve idempotency , step :
1. Generate the corresponding token before calling the interface (Token), Store in Redis
2. When calling the interface , Put the token into the request header | Form hidden in field
3. Interface to get the corresponding token , If you can get the token ( Delete the current token ) Directly execute the business logic of the access
4. Interface to get the corresponding token , If the token cannot be obtained , Return directly, please do not submit repeatedly
Code section , Use AOP Custom annotation method is right Token To verify . Prevent the form from being submitted repeatedly , Use AOP Annotation generation Token
1.rpc Network delay when calling ( Retry sending request )
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- introduce redis The dependency package of -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.28</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.36</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
</dependencies>
application.properties
# REDIS (RedisProperties)
# Redis Database index ( The default is 0)
spring.redis.database=0
# Redis Server address
spring.redis.host=localhost
# Redis Server connection port
spring.redis.port=6379
# Redis Server connection password ( The default is empty. )
spring.redis.password=
# Maximum number of connections in connection pool ( Use a negative value to indicate that there is no limit )
spring.redis.jedis.pool.max-active=8
# Connection pool maximum blocking wait time ( Use a negative value to indicate that there is no limit )
spring.redis.jedis.pool.max-wait=-1
# The maximum free connection in the connection pool
spring.redis.jedis.pool.max-idle=8
# The smallest free connection in the connection pool
spring.redis.jedis.pool.min-idle=0
# Connection timeout ( millisecond )
spring.redis.timeout=5000
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.mapper-locations=mybatis/**/*Mapper.xml
mybatis.type-aliases-package=com.yz.entity
spring.datasource.url=jdbc:mysql://localhost:3306/test01
spring.datasource.username=root
spring.datasource.password=1234
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.UUID;
/**
* Generate token, Put in redis in
* Created by yz on 2018/7/29.
*/
@Component
public class RedisToken {
@Autowired
private BaseRedisService baseRedisService;
private static final long TOKENTIME = 60*60;
public String getToken(){
String token = "token"+UUID.randomUUID();
baseRedisService.setString(token,token,TOKENTIME);
return token;
}
public boolean checkToken(String tokenKey){
String tokenValue = baseRedisService.getString(tokenKey);
if(StringUtils.isEmpty(tokenValue)){
return false;
}
// Ensure that each interface corresponds to token You can only visit once , Ensure the idempotency of the interface
baseRedisService.delKey(tokenKey);
return true;
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* Integrated packaging redis
* Created by yz on 2018/7/29.
*/
@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 String getString(String key){
return stringRedisTemplate.opsForValue().get(key);
}
public void delKey(String key){
stringRedisTemplate.delete(key);
}
}
import com.yz.entity.User;
import com.yz.service.UserService;
import com.yz.utils.RedisToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/**
* Handle rpc Call request
* Created by yz on 2018/7/29.
*/
@RestController
public class UserController {
@Autowired
private RedisToken redisToken;
@Autowired
private UserService userService;
@RequestMapping(value = "/createRedisToken")
public String createRedisToken(){
return redisToken.getToken();
}
@RequestMapping(value = "/addUser")
public String addOrder(User user, HttpServletRequest request){
// Get the token token
String token = request.getHeader("token");
if(StringUtils.isEmpty(token)){
return " Parameter error ";
}
// check token
boolean isToken = redisToken.checkToken(token);
if(!isToken){
return " Do not submit again !";
}
// Business logic
int result = userService.addUser(user);
return result >0 ? " Add success " : " Add failure ";
}
}
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan("com.yz.mapper")
@SpringBootApplication
public class YzApplication {
public static void main(String[] args) {
SpringApplication.run(YzApplication.class, args);
}
}
The test results :
obtain token
Request interface
Ask again :
Transform the code into AOP Annotation implementation
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Solve the problem of interface idempotence , Support network delay and form submission
* Created by yz on 2018/7/29.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckToken {
// Distinguish the source of the request
String type();
}
import com.yz.annotation.CheckToken;
import com.yz.utils.ConstantUtils;
import com.yz.utils.RedisToken;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
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.util.StringUtils;
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;
/**
* Interface idempotent facet
* Created by yz on 2018/7/29.
*/
@Aspect
@Component
public class ExtApiAopIdempotent {
@Autowired
private RedisToken redisToken;
// The breakthrough point , Intercept all requests
@Pointcut("execution(public * com.yz.controller.*.*(..))")
public void rlAop(){}
// Block all access around the notification
@Around("rlAop()")
public Object doBefore(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// Whether there is any addition to the judgment method ExtApiAopIdempotent annotation
MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
CheckToken declaredAnnotation = methodSignature.getMethod().getDeclaredAnnotation(CheckToken.class);
if(declaredAnnotation != null){
String type = declaredAnnotation.type();
String token = null;
HttpServletRequest request = getRequest();
if(type.equals(ConstantUtils.EXTAPIHEAD)){
// Get the token token
token = request.getHeader("token");
}else{
// Get from form token
token = request.getParameter("token");
}
if(StringUtils.isEmpty(token)){
return " Parameter error ";
}
// check token
boolean isToken = redisToken.checkToken(token);
if(!isToken){
return " Do not submit again !";
}
}
// release
Object proceed = proceedingJoinPoint.proceed();
return proceed;
}
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.print(msg);
} finally {
writer.close();
}
}
}
controller Use @CheckToken annotation :
import com.yz.annotation.CheckToken;
import com.yz.entity.User;
import com.yz.service.UserService;
import com.yz.utils.ConstantUtils;
import com.yz.utils.RedisToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/**
* Handle rpc Call request
* Created by yz on 2018/7/29.
*/
@RestController
public class UserController {
@Autowired
private RedisToken redisToken;
@Autowired
private UserService userService;
@RequestMapping(value = "/createRedisToken")
public String createRedisToken(){
return redisToken.getToken();
}
// Use CheckToken Annotation ensures the idempotency of the request
@RequestMapping(value = "/addUser")
@CheckToken(type = ConstantUtils.EXTAPIHEAD)
public String addOrder(User user, HttpServletRequest request){
// Business logic
int result = userService.addUser(user);
return result >0 ? " Add success " : " Add failure ";
}
}
Execution effect :
2. Form resubmission
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
spring.http.encoding.force=true
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
server.tomcat.uri-encoding=UTF-8
index.jsp
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/addUserForPage" method="post">
<input type="hidden" id="token" name="token" value="${token}">
name: <input id="name" name="name" />
<p>
age: <input id="age" name="age" />
<p>
<input type="submit" value="submit" />
</form>
</body>
</html>
import com.yz.annotation.CheckToken;
import com.yz.entity.User;
import com.yz.service.UserService;
import com.yz.utils.ConstantUtils;
import com.yz.utils.RedisToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
/**
* Processing form submission requests
* Created by yz on 2018/7/29.
*/
@Controller
public class UserPageController {
@Autowired
private RedisToken redisToken;
@Autowired
private UserService userService;
/**
* Page Jump
* @param req
* @return
*/
@RequestMapping("/indexPage")
public String indexPage(HttpServletRequest req){
req.setAttribute("token",redisToken.getToken());
return "index";
}
// Use CheckToken Annotation ensures the idempotency of the request
@RequestMapping(value = "/addUserForPage")
@CheckToken(type = ConstantUtils.EXTAPIFROM)
@ResponseBody
public String addOrder(User user, HttpServletRequest request){
// Business logic
int result = userService.addUser(user);
return result >0 ? " Add success " : " Add failure ";
}
}
Custom annotation generation Token, take req.setAttribute("token",redisToken.getToken()); Put it in AOP in , Reduce code redundancy :
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Custom annotation generation Token
* Created by yz on 2018/7/29.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CreatToken {
}
// The breakthrough point , Intercept all requests
@Pointcut("execution(public * com.yz.controller.*.*(..))")
public void rlAop(){}
// Pre notice , Generate Token
@Before("rlAop()")
public void before(JoinPoint point){
MethodSignature signature = (MethodSignature) point.getSignature();
CreatToken declaredAnnotation = signature.getMethod().getDeclaredAnnotation(CreatToken.class);
if(declaredAnnotation != null){
getRequest().setAttribute("token",redisToken.getToken());
}
}
import com.yz.annotation.CheckToken;
import com.yz.annotation.CreatToken;
import com.yz.entity.User;
import com.yz.service.UserService;
import com.yz.utils.ConstantUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
/**
* Processing form submission requests
* Created by yz on 2018/7/29.
*/
@Controller
public class UserPageController {
@Autowired
private UserService userService;
/**
* Page Jump , Use custom annotations to generate token, Pass it to the jump page
* @param req
* @return
*/
@RequestMapping("/indexPage")
@CreatToken
public String indexPage(HttpServletRequest req){
//req.setAttribute("token",redisToken.getToken());
return "index";
}
// Use CheckToken Annotation ensures the idempotency of the request
@RequestMapping(value = "/addUserForPage")
@CheckToken(type = ConstantUtils.EXTAPIFROM)
@ResponseBody
public String addOrder(User user, HttpServletRequest request){
// Business logic
int result = userService.addUser(user);
return result >0 ? " Add success " : " Add failure ";
}
}
When requesting a page ,AOP Annotations will be created token Pass it into the page :
Code download address :https://github.com/yangzeng1211/api_idempotent.git
边栏推荐
- The channel cannot be viewed when the queue manager is running
- Chrome browser plug-in fatkun installation and introduction
- Say goodbye to 996. What are the necessary plug-ins in idea?
- How to install PHP in CentOS
- Idea view bytecode configuration
- TD联合Modelsim进行功能仿真
- Knife4j 2. Solution to the problem of file control without selection when uploading x version files
- Navicat 远程连接Mysql报错1045 - Access denied for user ‘root‘@‘222.173.220.236‘ (using password: YES)
- QT QLabel样式设置
- Matplotlib swordsman line - first acquaintance with Matplotlib
猜你喜欢
Probability is not yet. Look at statistical learning methods -- Chapter 4, naive Bayesian method
分享一篇博客(水一篇博客)
MySQL事务
Chrome browser plug-in fatkun installation and introduction
Required request body is missing: (cross domain problem)
每天睡前30分钟阅读Day6_Day6_Date_Calendar_LocalDate_TimeStamp_LocalTime
个人经历&&博客现状
How to use PHP spoole to implement millisecond scheduled tasks
图像识别-数据清洗
Ora-12514 problem solving method
随机推荐
定时线程池实现请求合并
Don't look for it. All the necessary plug-ins for Chrome browser are here
微服务实战|手把手教你开发负载均衡组件
MySQL error: unblock with mysqladmin flush hosts
Chrome浏览器插件-Fatkun安装和介绍
College Students' CET-4 and CET-6 composition template (self created version, successfully crossed CET-6)
Fragmenttabhost implements the interface of housing loan calculator
互联网API接口幂等设计
Mathematics in machine learning -- point estimation (I): basic knowledge
Creation and jump of activity
Microservice practice | load balancing component and source code analysis
记录一下初次使用Xray的有趣过程
记录下对游戏主机配置的个人理解与心得
JVM instruction mnemonic
How to use pyqt5 to make a sensitive word detection tool
Micro service practice | introduction and practice of zuul, a micro service gateway
Chrome video download Plug-in – video downloader for Chrome
Knife4j 2. Solution to the problem of file control without selection when uploading x version files
Chrome browser plug-in fatkun installation and introduction
Customize redis connection pool