这两天对防止重复提交做了一个回顾,发现之前博文中有很多都不全面,这里要说一声抱歉了,经过我重新整理一番,现重新发布。。。
【希望大家多多指正】
现附上我测试的demo地址:Github
对于重复提交问题,可能大多数人并没太注意,由于问题本身难被发现,导致人们的忽视。但这个问题一旦发生,就可能是致命的问题,特别是对于电商项目,或者金融类等会有致命性错误。
这两天在网上看了很多资料,,前端或者后端都有很好的实现方法,这里只总结了下后端处理方法。
这里我使用redis分布式锁,springAOP切面,自定义注解方式实现。
首先思路是:
-
在前端调接口时候,需要传token授权口令到后台接口;
-
接口在根据token和接口地址生成对应的key值,在通过UUID生成唯一value;
-
将key-value锁添加到redis缓存中并设置自定义的过期时间(如果能添加锁成功,则表示不是重复提交,否则为重复提交)
-
最后在接口执行完之后一定要释放锁哦。
现在启动你的redis,开始你的疯狂之旅吧。。。。。。
对于redis配置,以及pom文件,在这里直接粘贴出来:
#redis
spring.redis.open=true
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
spring.redis.timeout=6000ms
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-wait=-1ms
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.min-idle=0
<properties>
<java.version>1.8</java.version>
<mybatis-plus-boot-starter>3.0.6</mybatis-plus-boot-starter>
<pagehelper-starter.version>1.2.10</pagehelper-starter.version>
<pagehelper.version>5.1.8</pagehelper.version>
<spring-data-commons.version>2.2.6.RELEASE</spring-data-commons.version>
<spring-boot-starter-data-jpa>2.0.4.RELEASE</spring-boot-starter-data-jpa>
<mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version>
<druid.version>1.0.28</druid.version>
<mysql.version>8.0.11</mysql.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus-boot-starter}</version>
</dependency>
<!--SpringData工具包-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>${spring-data-commons.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>${spring-boot-starter-data-jpa}</version>
</dependency>
<!--MyBatis分页插件starter-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper-starter.version}</version>
</dependency>
<!--MyBatis分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>${pagehelper.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.spring.boot.version}</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- 在springboot中引入spring-boot-starter-data-redis依赖时,
默认使用的时Lettuce,有时可能我们不想使用Lettuce而是使用Jedis来操作redis,
这就需要我们在引入spring-boot-starter-data-redis依赖时做一些额外的依赖配置. -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</exclusion>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.45</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api logger -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 logger -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
<!-- <scope>test</scope> -->
</dependency>
</dependencies>
1.首先自定义不重复提交注解NoRepeatSubmit
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 防止重复提交自定义注解
* @author zhang
*
*/
@Target(ElementType.METHOD) // 说明了Annotation所修饰的对象范围
@Retention(RetentionPolicy.RUNTIME) // 用于描述注解的生命周期(即:被描述的注解在什么范围内有效)
@Documented // 是一个标记注解,没有成员 (文档化)
public @interface NoRepeatSubmit {
/**
* 设置请求锁定时间
*
* @return
*/
int lockTime() default 10;
}
2.第二就是切面处理RepeatSubmitAspect
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
import com.zhang.common.annotation.NoRepeatSubmit;
import com.zhang.common.utils.R1;
import com.zhang.common.utils.RedisUtils;
import com.zhang.common.utils.RequestUtils;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.UUID;
/**
* 防止重复提交处理
* @author zhang
*
*/
@Aspect
@Component
public class RepeatSubmitAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(RepeatSubmitAspect.class);
@Autowired
private RedisUtils redisUtils;
@Pointcut("@annotation(noRepeatSubmit)")
public void pointCut(NoRepeatSubmit noRepeatSubmit) {
}
@Around("pointCut(noRepeatSubmit)")
public Object around(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) throws Throwable {
int lockSeconds = noRepeatSubmit.lockTime();
String threadName = Thread.currentThread().getName();// 获取当前线程名称
HttpServletRequest request = RequestUtils.getRequest();
Assert.notNull(request, "request can not null");
String token = request.getHeader("Authorization");// 前端调后台接口传 登录时已获取到的授权口令Authorization
String path = request.getServletPath();// 前端调接口地址
LOGGER.info("token;当前请求线程名称threadName;path:, token = [{}], threadName = [{}] , path = [{}]", token, threadName, path);
String key = getKey(token, path);// 调接口时生成临时key
String clientId = getClientId();// 调接口时生成临时value
// 此处为自测通过ProceedingJoinPoint获取当前请求的方法名称
// 方法一
Signature signature = pjp.getSignature();
MethodSignature ms = (MethodSignature)signature;
Method method = ms.getMethod();
LOGGER.info("获取方法一, method = [{}]", method);
// 方法二
Signature sig = pjp.getSignature();
MethodSignature msig = null;
if (!(sig instanceof MethodSignature)) {
throw new IllegalArgumentException("该注解只能用于方法");
}
msig = (MethodSignature) sig;
Object target = pjp.getTarget();
Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
LOGGER.info("获取方法二, currentMethod = [{}]", currentMethod);
boolean isSuccess = redisUtils.tryLock(key, clientId, lockSeconds);// 用于添加锁,如果添加成功返回true,失败返回false
LOGGER.info("tryLock 当前请求线程名称threadName, threadName = [{}], key = [{}], clientId = [{}]", threadName, key, clientId);
if (isSuccess) {
LOGGER.info("tryLock success 线程名称threadName:, threadName = [{}], key = [{}], clientId = [{}]",threadName, key, clientId);
// 获取锁成功
Object result;
try {
// 执行进程
result = pjp.proceed();// aop代理链执行的方法
} finally {
// 据key从redis中获取value
String value = redisUtils.get(key);
LOGGER.info("releaseLock start when 'value = clientId', key = [{}], value = [{}], clientId = [{}]", key, value, clientId);
if (clientId.equals(redisUtils.get(key))) {
LOGGER.info("releaseLock start, key = [{}], value = [{}], clientId = [{}]", key, value, clientId);
// 解锁
redisUtils.releaseLock(key, clientId);
LOGGER.info("releaseLock success 线程名称threadName:, threadName = [{}], key = [{}], clientId = [{}]",threadName, key, clientId);
}
}
return result;
} else {
// 添加锁失败,认为是重复提交的请求
LOGGER.info("重复请求,请稍后再试 线程名称threadName:" + threadName);
LOGGER.info("tryLock fail 线程名称threadName:, threadName = [{}], key = [{}]",threadName, key);
return R1.error(403, "重复请求,请稍后再试");
}
}
private String getKey(String token, String path) {
return token + path;
}
private String getClientId() {
return UUID.randomUUID().toString();
}
}
3.第三是redis工具类和RequestUtils工具类以及接口统一返回格式R1工具类
import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
/**
* Redis 工具类
* @author zhang
* @date 2020-04-24 10:28:40
*/
@Component
public class RedisUtils {
@Autowired
private StringRedisTemplate stringRedisTemplate;
private static final Long RELEASE_SUCCESS = 1L;
// private static final String LOCK_SUCCESS = "OK";
private static final Long LOCK_SUCCESS1 = 1L;
private static final String SET_IF_NOT_EXIST = "NX";
/** 设置过期时间单位, EX = seconds; PX = milliseconds */
private static final String SET_WITH_EXPIRE_TIME = "EX";
// if get(key) == value return del(key)
private static final String RELEASE_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
/** 不设置过期时长 */
public final static long NOT_EXPIRE = -1;
/** 默认过期时长,单位:秒 */
public final static long DEFAULT_EXPIRE = 60 * 60 * 24;
Gson gson = new Gson();
public void set(String key, Object value, long expire){
stringRedisTemplate.opsForValue().set(key, toJson(value));
if(expire != NOT_EXPIRE){
stringRedisTemplate.expire(key, expire, TimeUnit.SECONDS);
}
}
public void set(String key, Object value){
set(key, value, DEFAULT_EXPIRE);
}
public <T> T get(String key, Class<T> clazz, long expire) {
String value = stringRedisTemplate.opsForValue().get(key);
if(expire != NOT_EXPIRE){
stringRedisTemplate.expire(key, expire, TimeUnit.SECONDS);
}
return value == null ? null : fromJson(value, clazz);
}
public <T> T get(String key, Class<T> clazz) {
return get(key, clazz, NOT_EXPIRE);
}
public String get(String key, long expire) {
String value = stringRedisTemplate.opsForValue().get(key);
if(expire != NOT_EXPIRE){
stringRedisTemplate.expire(key, expire, TimeUnit.SECONDS);
}
return value;
}
public String get(String key) {
return get(key, NOT_EXPIRE);
}
public void delete(String key) {
stringRedisTemplate.delete(key);
}
/**
* 该加锁方法仅针对单实例 Redis 可实现分布式加锁
* 对于 Redis 集群则无法使用
*
* 支持重复,线程安全
*
* @param lockKey 加锁键
* @param clientId 加锁客户端唯一标识(采用UUID)
* @param seconds 锁过期时间
* @return
*/
public boolean tryLock(String lockKey, String clientId, int seconds) {
return stringRedisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
Jedis jedis = (Jedis) redisConnection.getNativeConnection();
//此方法报错,查看源码jedis.set方法不能如此传参
// String result = jedis.set(lockKey, clientId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, seconds);
// SetParams setParams = new SetParams();// 设置key的过期时间单位
// setParams.px(seconds); // (EX = seconds; PX = milliseconds)
// String result = jedis.set(lockKey, clientId, setParams);
long result = jedis.setnx(lockKey, clientId);// jedis.setnx该方式赋值(成功返回1,否则返回0)
if (LOCK_SUCCESS1 == result) {
return true;
}
return false;
});
}
/**
* 与 tryLock 相对应,用作释放锁
*
* @param lockKey
* @param clientId
* @return
*/
public boolean releaseLock(String lockKey, String clientId) {
return stringRedisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
Jedis jedis = (Jedis) redisConnection.getNativeConnection();
Object result = jedis.eval(RELEASE_LOCK_SCRIPT, Collections.singletonList(lockKey),
Collections.singletonList(clientId));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
});
}
/**
* Object转成JSON数据(Gson方式)
*/
private String toJson(Object object){
if(object instanceof Integer || object instanceof Long || object instanceof Float ||
object instanceof Double || object instanceof Boolean || object instanceof String){
return String.valueOf(object);
}
return gson.toJson(object);
}
/**
* JSON数据转成Object(Gson方式)
*/
private <T> T fromJson(String json, Class<T> clazz){
return gson.fromJson(json, clazz);
}
}
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
public class RequestUtils {
public static HttpServletRequest getRequest() {
ServletRequestAttributes ra= (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return ra.getRequest();
}
}
import java.util.ArrayList;
import java.util.Map;
import org.apache.http.HttpStatus;
/**
* 接口统一返回格式
* @author zhang
*
*/
public class R1<T> {
private static final long serialVersionUID = 1L;
private int code;
private T data;
private String message;
/**
* 无参构造
*/
public R1() {
this.code = 0;
this.message = "success";
this.data = (T) new ArrayList<Object>();
}
/**
* 有参构造
* @param code
* @param data
* @param message
*/
public R1(int code, T data, String message) {
this.code = code;
this.data = data;
this.message = message;
}
/**
* ok————无参返回
* @return
*/
public static <T> R1<T> ok() {
return new <T> R1<T>();
}
/**
* ok————指定提示语
* @param message
* @return
*/
public static <T> R1<T> ok(String message) {
R1<T> r = new R1<T>();
r.message = message;
r.data = (T) new ArrayList<Object>();
return r;
}
/**
* ok————指定data(承载数据)
* @param map
* @return
*/
public static <T> R1<T> ok(Map<String, Object> map) {
R1<T> r = new R1<T>();
r.data = (T) map;
return r;
}
/**
* error————无参返回
* @return
*/
public static <T> R1<T> error() {
return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "未知异常,请联系管理员");
}
/**
* error————指定message
* @param message
* @return
*/
public static <T> R1<T> error(String message) {
return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, message);
}
/**
* error————指定code, message
* @param code
* @param message
* @return
*/
public static <T> R1<T> error(int code, String message) {
R1<T> r = new R1<T>();
r.code = code;
r.message = message;
r.data = (T) new ArrayList<Object>();
return r;
}
/**
* ok————指定data(承载数据)
* @param data
* @return
*/
public R1<T> ok(T data) {
R1<T> r = new R1<T>();
r.data = data;
return r;
}
/**
* 指定data(承载数据)
* @param data
* @return
*/
public static <T> R1<T> data(T data) {
return data(data, "success");
}
public static <T> R1<T> data(T data, String message) {
return data(HttpStatus.SC_OK, data, message);
}
public static <T> R1<T> data(int code, T data, String message) {
return new R1(code, data, data == null ? "暂无承载数据" : message);
}
public int getCode() {
return this.code;
}
public T getData() {
return this.data;
}
public String getmessage() {
return this.message;
}
public void setCode(final int code) {
this.code = code;
}
public void setData(final T data) {
this.data = data;
}
public void setmessage(final String message) {
this.message = message;
}
@Override
public String toString() {
return "R1 [code=" + code + ", data=" + data + ", message=" + message + "]";
}
}
4.第四写一个提交接口用于测试
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.zhang.common.annotation.NoRepeatSubmit;
import com.zhang.common.utils.R1;
/**
* 测试noReatSubmit
* @author zhang
*
*/
@RestController
@RequestMapping("/noReatSubmit")
public class NoReatSubmitController {
@PostMapping("submit")
@NoRepeatSubmit(lockTime = 30)
public R1<?> submit(@RequestBody UserBean userBean) {
try {
// 模拟业务场景
System.out.println("模拟业务场景开始...,当前请求线程名:" + userBean.getThreadName());
Thread.sleep(1500);
} catch (InterruptedException e) {
System.out.println("模拟业务场景失败");
e.printStackTrace();
}
System.out.println("模拟业务场景成功,当前请求线程名称:" + userBean.getThreadName());
return new R1<Object>(200, userBean.getUserId(), "成功");
}
public static class UserBean {
private String userId;
private String threadName;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId == null ? null : userId.trim();
}
public String getThreadName() {
return threadName;
}
public void setThreadName(String threadName) {
this.threadName = threadName;
}
@Override
public String toString() {
return "UserBean [userId=" + userId + ", threadName=" + threadName + "]";
}
}
}
我这边是多线程模拟多次请求,以HTTP方式请求接口测试。
5.第五亮出HTTP请求方法工具类HttpClientUtil
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.net.ssl.SSLContext;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.TrustStrategy;
import org.apache.http.util.EntityUtils;
/**
* http请求方式
* @author zhang
*
*/
public class HttpClientUtil {
private static RequestConfig requestConfig;
private static SSLConnectionSocketFactory sslsf;
static {
SSLContext sslContext = null;
try {
sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
// 信任所有证书 https://blog.csdn.net/sleeping_/article/details/50500351
@Override
public boolean isTrusted(java.security.cert.X509Certificate[] arg0, String arg1)
throws java.security.cert.CertificateException {
return true;
}
}).build();
} catch (Exception e) {
e.printStackTrace();
}
sslsf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
}
/**
* get方式請求(有参)
* @param url
* @param param
* @param Authorization 口令授权
* @return
*/
public static String doGet(String url, Map<String, Object> param, String Authorization, String webAuthorization) {
// 创建Httpclient对象
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
String resultString = "";
CloseableHttpResponse response = null;
try {
// 创建uri
URIBuilder builder = new URIBuilder(url);
if (param != null) {
for (String key : param.keySet()) {
if (param.get(key).getClass().isArray()) {
String[] arr = (String[]) param.get(key);
for (String string : arr) {
builder.addParameter(key, string);
}
}else{
builder.addParameter(key, param.get(key).toString());
}
}
}
URI uri = builder.build();
// 创建http GET请求
HttpGet httpGet = new HttpGet(uri);
httpGet.addHeader("Connection", "close");
httpGet.addHeader("Authorization", Authorization);
httpGet.addHeader("Web-Authorization", webAuthorization);
httpGet.setConfig(requestConfig);
// 执行请求
response = httpclient.execute(httpGet);
// 判断返回状态是否为200
if (response.getStatusLine().getStatusCode() == 200) {
resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (response != null) {
response.close();
}
httpclient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
/**
* get方式请求(无参)
* @param url
* @param Authorization 口令授权
* @return
*/
public static String doGet(String url, String Authorization, String webAuthorization) {
return doGet(url, null, Authorization, webAuthorization);
}
/**
* post方式请求(有参)
* @param url
* @param param
* @param Authorization 口令授权
* @return
*/
public static String doPost(String url, Map<String, String> param, String Authorization, String webAuthorization) {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);
httpPost.addHeader("Connection", "close");
httpPost.addHeader("Authorization", Authorization);
httpPost.addHeader("Web-Authorization", webAuthorization);
httpPost.setConfig(requestConfig);
// 创建参数列表
if (param != null) {
List<NameValuePair> paramList = new ArrayList<>();
for (String key : param.keySet()) {
paramList.add(new BasicNameValuePair(key, param.get(key)));
}
// 模拟表单
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList, "utf-8");
httpPost.setEntity(entity);
}
// 执行http请求
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), "utf-8");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
/**
* post方式请求(无参)
* @param url
* @param Authorization 口令授权
* @return
*/
public static String doPost(String url, String Authorization, String webAuthorization) {
return doPost(url, null, Authorization, webAuthorization);
}
/**
* delete方式请求(有参)
* @param url
* @param param
* @param Authorization 口令授权
* @return
*/
public static String doDelete(String url, String Authorization, String webAuthorization) {
// 创建Httpclient对象
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
String resultString = "";
CloseableHttpResponse response = null;
try {
// 创建http delete请求
HttpDelete httpDelete = new HttpDelete(url);
httpDelete.addHeader("Connection", "close");
httpDelete.addHeader("Authorization", Authorization);
httpDelete.addHeader("Web-Authorization", webAuthorization);
httpDelete.setConfig(requestConfig);
// 执行请求
response = httpclient.execute(httpDelete);
// 判断返回状态是否为200
if (response.getStatusLine().getStatusCode() == 200) {
resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (response != null) {
response.close();
}
httpclient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
/**
* put方式请求(有参)
* @param url
* @param param
* @param Authorization 口令授权
* @return
*/
public static String doPut(String url, String json, String Authorization, String webAuthorization) {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 创建Http Post请求
HttpPut httpPut = new HttpPut(url);
httpPut.addHeader("Connection", "close");
httpPut.addHeader("Authorization", Authorization);
httpPut.addHeader("Web-Authorization", webAuthorization);
httpPut.setConfig(requestConfig);
// 创建请求内容
StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
httpPut.setEntity(entity);
// 执行http请求
response = httpClient.execute(httpPut);
resultString = EntityUtils.toString(response.getEntity(), "utf-8");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
/**
* 请求的参数类型为json
* post方式请求 (有参)
* @param url
* @param json
* @param Authorization 口令授权
* @return
*/
public static String doPostJson(String url, String json, String Authorization, String webAuthorization) {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);
System.out.println("创建Http Post请求");
httpPost.addHeader("Connection", "close");
httpPost.addHeader("Authorization", Authorization);
httpPost.addHeader("Web-Authorization", webAuthorization);
httpPost.setConfig(requestConfig);
// 创建请求内容
StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
System.out.println("请求内容:" + entity.toString());
httpPost.setEntity(entity);
// 执行http请求
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), "utf-8");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
}
6.第六步创建线程进行调接口测试RunnableDemo
import com.alibaba.fastjson.JSONObject;
import com.zhang.common.utils.HttpClientUtil;
import com.zhang.modules.test.controller.NoReatSubmitController.UserBean;
/**
* 创建线程测试接口
* @author zhang
*
*/
public class RunnableDemo implements Runnable {
private Thread t;
private String threadName;
public RunnableDemo(String name) {
threadName = name;
System.out.println("Creating: " + threadName);
}
public String threadName() {
return threadName;
}
public void run() {
System.out.println("Running, threadName = [{}]: " + threadName);
UserBean userBean = new UserBean();
userBean.setUserId("123123123");
userBean.setThreadName(threadName);
String url="http://localhost:9999/zhang/noReatSubmit/submit";
HttpClientUtil.doPostJson(url, JSONObject.toJSONString(userBean), "youToken", null);
System.out.println("Thread " + threadName + " exiting.");
}
public void start() {
System.out.println("Starting " + threadName);
if (t == null) {
t = new Thread(this, threadName);
t.start();
}
}
}
7.第七步,写一个main函数,加上线程启动项目测试
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import com.zhang.modules.test.controller.RunnableDemo;
@SpringBootApplication
@ComponentScan(basePackages={"com.zhang"})
public class TestProjectApplication {
public static void main(String[] args) {
SpringApplication.run(TestProjectApplication.class, args);
System.out.println("启动成功!");
// 开启线程
RunnableDemo R1 = new RunnableDemo( "Thread-1");
RunnableDemo R2 = new RunnableDemo( "Thread-2");
// RunnableDemo R3 = new RunnableDemo( "Thread-3");
// RunnableDemo R4 = new RunnableDemo( "Thread-4");
R1.start();
R2.start();
// R3.start();
// R4.start();
}
}
测试结果(控制台日志)如下:
参考链接:
https://www.cnblogs.com/peida/archive/2013/04/24/3036689.html —— 深入理解Java:注解(Annotation)自定义注解入门
https://www.cnblogs.com/linjiqin/p/8003838.html —— Redis分布式锁的正确实现方式
http://redisdoc.com/ —— Redis 命令参考