注解式并发控制器

一般接口并发控制的做法:

  1. 前端做loading限制(如果绕过前端发起请求这种方式则会失效)
  2. 后端根据特定请求参数、间隔时间来限制(后端会有一定开发工作量)

基于上述情况,开发了一款基于注解使用的并发控制器,需要使用到redis来做分布式锁

使用说明:

  • @CurrentControl  注解申明
  • field 请求中的哪些字段需要用来做唯一校验,如果不写那么就是全字段
  • locktime 锁的时间(秒数),方法运行结束会自动释放锁,如果未运行结束,会在locktime到期后释放锁
  • handling 并发控制触发后的处理方式,默认HandlingType.THROWEXP(抛出业务异常),HandlingType.QUEUE(队列模式,暂未开放)
  • expMessage 自定义异常信息,如果没有自定义异常信息会抛出系统默认异常信息

 

需要的朋友可以根据下面的源码自行修改使用,想直接使用的话只需要把redis注入改下就行了

/**
* <p>Title:并发控制注解</p>
* <p>Description:</p>
* @author QIQI
* @params
* @return
* @throws
* @date 2020/11/13 14:15
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CurrentControl {
    String[] field() default {"$"}; //筛选字段,哪些字段用来判断是否被并发
    long locktime() default 1000; //锁定时间,毫秒
    HandlingType handling() default HandlingType.THROWEXP; //并发控制触发后的处理方式
    String expMessage() default "触发并发控制,请求被拒绝,请稍候尝试!"; //触发后的处理方式选择THROWEXP,那么自定义抛出的异常信息,如果未自定义将会使用默认错误信息返回
}


/**
 * <p>Title:</p>
 * <p>Description:</p>
 *
 * @author QIQI
 * @date
 */
@Data
public class CurrentBean implements Serializable {
    private static final long serialVersionUID = 356924975742559410L;
    private String[] field; //筛选字段,哪些字段用来判断是否被并发
    private long locktime; //锁定时间,毫秒
    private HandlingType handling; //并发控制触发后的处理方式
    private String expMessage; //触发后的处理方式选择THROWEXP,那么自定义抛出的异常信息,如果未自定义将会使用默认错误信息返回
}


/**
 * <p>Title:</p>
 * <p>Description:</p>
 *
 * @author QIQI
 * @date
 */
@Slf4j
@Aspect
@Order(-1)
@Component
public class CurrentControlAspect {
    @Resource(name = "redisPoolManagerImpl")
    private RedisManager redisManager;
    @Autowired
    private RequestParsing requestParsing;
    private static final String CURRENT_PREFIX = "CURRENT_PREFIX:";
    private static final ThreadLocal<String> LOCAL_ASPECT = new TransmittableThreadLocal<>(); //记录当前请求redis锁的key,不重复解析
    private static final ThreadLocal<Boolean> LOCAL_ASPECT_CHECK = new TransmittableThreadLocal<>(); //记录本线程redis加锁状态

    @Pointcut("@annotation(com.wms.framework.concurrency.CurrentControl)")
    public void aspect() {
    }

    /**
     * <p>Title:前置切面</p>
     * <p>Description:</p>
     *
     * @return void
     * @throws
     * @author QIQI
     * @params [point]
     * @date 2019-07-19 13:55
     */
    @Before("aspect() && @annotation(currentControl)")
    public void before(JoinPoint point, CurrentControl currentControl) {
        CurrentBean currentBean = getAnnotation( currentControl );
        HashMap<String, String> lockMap = new HashMap<>();
        requestParsing.getLockMap( lockMap, new ArrayList<>( Arrays.asList( currentBean.getField() ) ) );
        String key = CURRENT_PREFIX + lockMap;
        String resp = redisManager.set( key, "", "NX", "EX", currentBean.getLocktime() );
        if (null != resp && resp.equalsIgnoreCase( "OK" )) {
            LOCAL_ASPECT_CHECK.set( true );
            LOCAL_ASPECT.set( key );
        } else {
            if (currentBean.getHandling().equals( HandlingType.THROWEXP )) {
                log.warn( "触发并发控制,lockMap is {}", lockMap );
                throwexp();
            }
        }
    }

    private void throwexp() {
        //抛出自定义异常或者默认异常信息
        LOCAL_ASPECT_CHECK.set( false );
        throw new RuntimeException( "并发限制触发" );
    }

    /**
     * <p>Title:后置切面</p>
     * <p>Description:</p>
     *
     * @return void
     * @throws
     * @author QIQI
     * @params [point]
     * @date 2019-07-19 13:55
     */
    @After("aspect() && @annotation(currentControl)")
    public void after(JoinPoint point, CurrentControl currentControl) {
        if (LOCAL_ASPECT_CHECK.get()) {
            redisManager.del( LOCAL_ASPECT.get() ); //处理结束,无论是否成功,请求返回之前必须释放锁
            LOCAL_ASPECT.remove();
        }
        LOCAL_ASPECT_CHECK.remove();
    }

    /**
     * <p>Title:获取注解值</p>
     * <p>Description:</p>
     *
     * @return java.util.Map<java.lang.String, java.lang.Object>
     * @throws
     * @author QIQI
     * @params [point]
     * @date 2019-07-19 13:52
     */
    private CurrentBean getAnnotation(CurrentControl currentControl) {
        CurrentBean currentBean = new CurrentBean();
        currentBean.setExpMessage( currentControl.expMessage() );
        currentBean.setField( currentControl.field() );
        currentBean.setHandling( currentControl.handling() );
        currentBean.setLocktime( currentControl.locktime() );
        return currentBean;
    }
}

/**
 * <p>Title:并发控制触发时的处理方式</p>
 * <p>Description:</p>
 *
 * @author QIQI
 * @date
 */
public enum HandlingType {
    THROWEXP,
    QUEUE
}

@Component
@Order(10000)
@WebFilter(filterName = "HttpServletRequestFilter", urlPatterns = "/")
public class HttpServletRequestFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig){

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        ServletRequest requestWrapper = null;
        String path = "";
        if (servletRequest instanceof HttpServletRequest && servletRequest.getInputStream() != null) {
            path = ((HttpServletRequest) servletRequest).getRequestURI();
            if(path.equals( "/health" )){
                filterChain.doFilter(servletRequest, servletResponse);
                return;
            }
            requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest);
        }
        //获取请求中的流如何,将取出来的字符串,再次转换成流,然后把它放入到新request对象中
        //在chain.doFiler方法中传递新的request对象
        if (null == requestWrapper) {
            filterChain.doFilter(servletRequest, servletResponse);
        } else {
            filterChain.doFilter(requestWrapper, servletResponse);
        }
        if(servletResponse.getOutputStream() != null){
            RequestWrapper.transmittableThreadLocal.remove();
        }
    }

    @Override
    public void destroy() {

    }
}

/**
 * <p>Title:请求解析器</p>
 * <p>Description:</p>
 *
 * @author QIQI
 * @date
 */
@Slf4j
@Service
public class RequestParsing {
    @Autowired
    private HttpServletRequest request;

    /**
     * <p>Title:获取锁定Map</p>
     * <p>Description:</p>
     *
     * @return void
     * @throws
     * @author QIQI
     * @params [locakMap, fieldList]
     * @date 2020/11/13 16:37
     */
    public void getLockMap(HashMap<String, String> locakMap, List<String> fieldList) {
        //每次解析都会判断fieldList长度是否 > 0,如果== 0说明已经解析完成,无需再次深入解析
        //解析优先级 header > parameter > body
        parseHeader( locakMap, fieldList );
        parseParameter( locakMap, fieldList );
        parseObject( locakMap, fieldList );
    }

    /**
     * <p>Title:解析head值</p>
     * <p>Description:</p>
     *
     * @return void
     * @throws
     * @author QIQI
     * @params [locakMap, fieldList]
     * @date 2020/11/13 16:35
     */
    private void parseHeader(HashMap<String, String> locakMap, List<String> fieldList) {
        if (fieldList.get( 0 ).equals( "$" )) {
            Enumeration<String> stringEnumeration = request.getHeaderNames();
            while (stringEnumeration.hasMoreElements()) {
                String key = stringEnumeration.nextElement();
                locakMap.put( key, request.getHeader( key ) );
            }
        } else {
            if (fieldList.size() > 0) {
                forEachList( fieldList, "header", locakMap );
            }
        }
    }

    private void forEachList(List<String> fieldList, String type, HashMap<String, String> locakMap) {
        Iterator<String> iterator = fieldList.iterator();
        while (iterator.hasNext()) {
            String key = iterator.next();
            String headVal = "";
            if (type.equals( "header" )) {
                headVal = request.getHeader( key );
            } else if (type.equals( "params" )) {
                headVal = request.getParameter( key );
            }
            if (Optional.ofNullable( headVal ).isPresent()) {
                locakMap.put( key, headVal );
                iterator.remove();
            }
        }
    }

    /**
     * <p>Title:解析parameter值</p>
     * <p>Description:</p>
     *
     * @return void
     * @throws
     * @author QIQI
     * @params [locakMap, fieldList]
     * @date 2020/11/13 16:36
     */
    private void parseParameter(HashMap<String, String> locakMap, List<String> fieldList) {
        if (fieldList.size() > 0 && fieldList.get( 0 ).equals( "$" )) {
            Enumeration<String> stringEnumeration = request.getParameterNames();
            while (stringEnumeration.hasMoreElements()) {
                String key = stringEnumeration.nextElement();
                locakMap.put( key, request.getParameter( key ) );
            }
        } else {
            if (fieldList.size() > 0) {
                forEachList( fieldList, "params", locakMap );
            }
        }
    }

    /**
     * <p>Title:解析body对象内部值</p>
     * <p>Description:</p>
     *
     * @return void
     * @throws
     * @author QIQI
     * @params [locakMap, fieldList]
     * @date 2020/11/13 16:36
     */
    private void parseObject(HashMap<String, String> locakMap, List<String> fieldList) {
        if (fieldList.size() > 0 && fieldList.get( 0 ).equals( "$" )) {
            Enumeration<String> stringEnumeration = request.getParameterNames();
            while (stringEnumeration.hasMoreElements()) {
                String key = stringEnumeration.nextElement();
                locakMap.put( key, request.getParameter( key ) );
            }
        } else {
            if (fieldList.size() > 0) {
                JSONObject jsonObject = JSON.parseObject( RequestWrapper.transmittableThreadLocal.get() );
                Iterator<String> iterator = fieldList.iterator();
                while (iterator.hasNext()){
                    String key = iterator.next();
                    if(null != jsonObject.get( key )){
                        String val = String.valueOf( jsonObject.get( key ) );
                        locakMap.put( key,val );
                        iterator.remove();
                    }
                }
            }
        }
    }
}


/**
* <p>Title:HttpServletRequest 包装器</p>
* <p>Description:
 * 解决: request.getInputStream()只能读取一次的问题
 * 目标: 流可重复读
 * </p>
* @author QIQI
* @params
* @return
* @throws
* @date 2020/11/16 14:24
*/
public class RequestWrapper extends HttpServletRequestWrapper {
    public static final ThreadLocal<String> transmittableThreadLocal = new TransmittableThreadLocal<>();
    /**
     * 请求体
     */
    private String mBody;

    public RequestWrapper(HttpServletRequest request) {
        super(request);
        // 将body数据存储起来
        mBody = getBody(request);
        transmittableThreadLocal.set(mBody);
    }

    /**
     * 获取请求体
     *
     * @param request 请求
     * @return 请求体
     */
    private String getBody(HttpServletRequest request) {
        return getBodyString(request);
    }

    /**
     * 获取请求体
     *
     * @return 请求体
     */
    public String getBody() {
        return mBody;
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        // 创建字节数组输入流
        final ByteArrayInputStream bais = new ByteArrayInputStream(mBody.getBytes( StandardCharsets.UTF_8));

        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }

            @Override
            public int read() throws IOException {
                return bais.read();
            }
        };
    }

    /**
     * 获取请求Body
     *
     * @param request
     * @return
     */
    private String getBodyString(ServletRequest request) {
        StringBuilder sb = new StringBuilder();
        InputStream inputStream = null;
        BufferedReader reader = null;
        try {
            inputStream = request.getInputStream();
            reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
            String line = "";
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return sb.toString();
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菠萝-琪琪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值