springboot自定义注解使用AOP防止重复提交

这两天对防止重复提交做了一个回顾,发现之前博文中有很多都不全面,这里要说一声抱歉了,经过我重新整理一番,现重新发布。。。

【希望大家多多指正】

现附上我测试的demo地址:Github

 

对于重复提交问题,可能大多数人并没太注意,由于问题本身难被发现,导致人们的忽视。但这个问题一旦发生,就可能是致命的问题,特别是对于电商项目,或者金融类等会有致命性错误。

这两天在网上看了很多资料,,前端或者后端都有很好的实现方法,这里只总结了下后端处理方法。

这里我使用redis分布式锁,springAOP切面,自定义注解方式实现。

首先思路是:

  1. 在前端调接口时候,需要传token授权口令到后台接口;

  2. 接口在根据token和接口地址生成对应的key值,在通过UUID生成唯一value;

  3. 将key-value锁添加到redis缓存中并设置自定义的过期时间(如果能添加锁成功,则表示不是重复提交,否则为重复提交)

  4. 最后在接口执行完之后一定要释放锁哦。

现在启动你的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 

 


        1.8
        3.0.6
        1.2.10
        5.1.8
        2.2.6.RELEASE
        2.0.4.RELEASE
        1.3.0
        1.0.28
        8.0.11
    

    
        
            org.springframework.boot
            spring-boot-starter-web
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
            
                
                    org.junit.vintage
                    junit-vintage-engine
                
            
        

        
            com.baomidou
            mybatis-plus-boot-starter
            ${mybatis-plus-boot-starter}
        

        
        
            org.springframework.data
            spring-data-commons
            ${spring-data-commons.version}
        

        
            org.springframework.boot
            spring-boot-starter-data-jpa
            ${spring-boot-starter-data-jpa}
        

        
        
            com.github.pagehelper
            pagehelper-spring-boot-starter
            ${pagehelper-starter.version}
        
        
        
            com.github.pagehelper
            pagehelper
            ${pagehelper.version}
        


        
            ch.qos.logback
            logback-classic
            1.2.3
        

        
            org.projectlombok
            lombok
        

        
            org.mybatis.spring.boot
            mybatis-spring-boot-starter
            ${mybatis.spring.boot.version}
        

        
        
            mysql
            mysql-connector-java
            ${mysql.version}
        
        
            com.alibaba
            druid
            ${druid.version}
        

        
            org.springframework.boot
            spring-boot-starter-aop
        
        
            org.springframework
            spring-context-support
        
        
            org.springframework.boot
            spring-boot-starter-data-redis
        
        
            redis.clients
            jedis
        
        
            com.google.code.gson
            gson
            2.8.2
        

        
            org.apache.commons
            commons-pool2
        

        
        
            org.springframework.boot
            spring-boot-starter-data-redis
            
                
                    redis.clients
                    jedis
                
                
                    io.lettuce
                    lettuce-core
                
            
        
        
            com.alibaba
            fastjson
            1.2.45
        
        
            log4j
            log4j
            1.2.17
        

        
        
            org.slf4j
            slf4j-api
            1.7.25
        
        
        
            org.slf4j
            slf4j-log4j12
            1.7.25
            
        


    

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 get(String key, Class 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 get(String key, Class 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) 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) 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 fromJson(String json, Class 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 {

    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();
    }

    /**
     * 有参构造
     * @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  R1 ok() {
        return new  R1();
    }

    /**
     * ok————指定提示语
     * @param message
     * @return
     */
    public static  R1 ok(String message) {
        R1 r = new R1();
        r.message = message;
        r.data = (T) new ArrayList();
        return r;
    }

    /**
     * ok————指定data(承载数据)
     * @param map
     * @return
     */
    public static  R1 ok(Map map) {
        R1 r = new R1();
        r.data = (T) map;
        return r;
    }

    /**
     * error————无参返回
     * @return
     */
    public static  R1 error() {
        return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "未知异常,请联系管理员");
    }

    /**
     * error————指定message
     * @param message
     * @return
     */
    public static  R1 error(String message) {
        return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, message);
    }

    /**
     * error————指定code, message
     * @param code
     * @param message
     * @return
     */
    public static  R1 error(int code, String message) {
        R1 r = new R1();
        r.code = code;
        r.message = message;
        r.data = (T) new ArrayList();
        return r;
    }

    /**
     * ok————指定data(承载数据)
     * @param data
     * @return
     */
    public R1 ok(T data) {
        R1 r = new R1();
        r.data = data;
        return r;
    }

    /**
     * 指定data(承载数据)
     * @param data
     * @return
     */
    public static  R1 data(T data) {
        return data(data, "success");
    }

    public static  R1 data(T data, String message) {
        return data(HttpStatus.SC_OK, data, message);
    }

    public static  R1 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(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 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 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 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();
	    
	}
}

测试结果(控制台日志)如下:

springboot自定义注解使用AOP防止重复提交_第1张图片

参考链接:

https://www.toutiao.com/i6768274855004471821/?tt_from=weixin&utm_campaign=client_share&wxshare_count=1&from=groupmessage×tamp=1582519759&app=news_article&utm_source=weixin&utm_medium=toutiao_android&req_id=202002241249180101310981964407620D&group_id=6768274855004471821   ——   Spring Boot 使用 AOP 防止重复提交

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 命令参考

你可能感兴趣的:(所遇问题,springboot,HTTP)