java高并发处理,限制同一接口访问次数

第一种实现方式 

1、首先在打开拦截器,拦截访问的接口。

package com.zh.config;

import java.nio.charset.Charset;
import java.util.List;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

import com.zh.filter.AuthorizationInterceptor2;
import com.zh.router.Rest;

/**
 * @ClassName: MySpringMvcConfig
 * @Description:拦截器配置
 * @author: renkai721
 * @date: 2018年7月30日 上午11:53:34
 */
@Configuration
public class MySpringMvcConfig extends WebMvcConfigurationSupport {
	@Bean
	public HttpMessageConverter<String> responseBodyConverter() {
		// 设置字符集,不然返回到前台中文乱码
		StringHttpMessageConverter converter = new StringHttpMessageConverter(Charset.forName("UTF-8"));
		return converter;
	}
	@Override
	public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
		super.configureMessageConverters(converters);
		converters.add(responseBodyConverter());
	}
	
	@Override
    public void addInterceptors(InterceptorRegistry registry) {
		String n1 = Rest.Captcha.Root + Rest.Captcha.getCaptchaImage;
		String n2 = Rest.Login.Root + Rest.Login.checkLoginName;
		String n3 = Rest.Login.Root + Rest.Login.doLogin;
		String n4 = Rest.Login.Root + Rest.Login.logout;
		
        registry.addInterceptor(new AuthorizationInterceptor2()).addPathPatterns("/**").excludePathPatterns(
        		n1,n2,n3,n4);
        super.addInterceptors(registry);
    }


}

2、拦截器里面的处理逻辑

package com.zh.filter;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.util.concurrent.Semaphore;

import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import com.zh.util.MsgUtils;
import com.zh.util.USDConstants;

import lombok.extern.slf4j.Slf4j;

/**
 * @ClassName: AuthorizationInterceptor
 * @Description:
 * @author: renkai721
 * @date: 2019年6月14日 下午3:45:47
 */
@Slf4j
public class AuthorizationInterceptor2 extends HandlerInterceptorAdapter {
	// 定义资源的总数量,2表示2个资源总数
	private static int size = 2;
	public static Semaphore welcomeSemaphore = new Semaphore(size);

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		BodyReaderHttpServletRequestWrapper requestWrapper = null;
		if (request instanceof BodyReaderHttpServletRequestWrapper) {
			requestWrapper = (BodyReaderHttpServletRequestWrapper) request;
			// 默认记录后台接口请求日志记录
			String url = requestWrapper.getRequestURL().toString();
			if(url.indexOf("/test/test") != 0) {
				synchronized(welcomeSemaphore) {
					int availablePermits = welcomeSemaphore.availablePermits();
					if (availablePermits > 0) {
						try {
							// 请求占用一个资源
							welcomeSemaphore.acquire(1);
						} catch (Exception e) {
							e.printStackTrace();
						}
					} else {
						log.info("同一秒中,同一接口,只能允许"+size+"个人访问");
						PrintWriter out = response.getWriter();
						String msg = MsgUtils.outJson(USDConstants.BM_CODE_URL_MAX_COUNT);
						out.print(msg);
						out.flush();
						return false;
					}
				}
			}
		}
		return super.preHandle(request, response, handler);
	}
	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
		BodyReaderHttpServletRequestWrapper requestWrapper;
		if (request instanceof BodyReaderHttpServletRequestWrapper) {
			requestWrapper = (BodyReaderHttpServletRequestWrapper) request;
		}
	}
	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
	}
	/**
	 * 获取请求Body
	 * @param request
	 * @return
	 */
	public static String getBodyString(final ServletRequest request) {
		StringBuilder sb = new StringBuilder();
		InputStream inputStream = null;
		BufferedReader reader = null;
		try {
			inputStream = cloneInputStream(request.getInputStream());
			reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("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();
	}
	/**
	 * Description: 复制输入流</br>
	 * @param inputStream
	 * @return</br>
	 */
	public static InputStream cloneInputStream(ServletInputStream inputStream) {
		ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
		byte[] buffer = new byte[1024];
		int len;
		try {
			while ((len = inputStream.read(buffer)) > -1) {
				byteArrayOutputStream.write(buffer, 0, len);
			}
			byteArrayOutputStream.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
		InputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
		return byteArrayInputStream;
	}
}

3、BodyReaderHttpServletRequestWrapper

package com.zh.filter;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

import org.apache.commons.lang3.StringUtils;

/**
 * @ClassName: BodyReaderHttpServletRequestWrapper
 * @Description:
 * @author: renkai721
 * @date: 2019年10月15日 下午2:24:17
 */
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
	private final byte[] body;

	public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
		super(request);
		body = readBytes(request.getReader(), "utf-8");
	}
	@Override
	public BufferedReader getReader() throws IOException {
		return new BufferedReader(new InputStreamReader(getInputStream()));
	}
	@Override
	public ServletInputStream getInputStream() throws IOException {
		final ByteArrayInputStream bais = new ByteArrayInputStream(body);
		return new ServletInputStream() {
			@Override
			public boolean isFinished() {
				return false;
			}
			@Override
			public boolean isReady() {
				return false;
			}
			@Override
			public void setReadListener(ReadListener listener) {
			}
			@Override
			public int read() throws IOException {
				return bais.read();
			}
		};
	}
	/**
	 * 通过BufferedReader和字符编码集转换成byte数组
	 * @param br
	 * @param encoding
	 * @return
	 * @throws IOException
	 */
	private byte[] readBytes(BufferedReader br, String encoding) throws IOException {
		String str = null, retStr = "";
		while ((str = br.readLine()) != null) {
			retStr += str;
		}
		if (StringUtils.isNotBlank(retStr)) {
			return retStr.getBytes(Charset.forName(encoding));
		}
		return null;
	}
}

4、我们写一个test接口

package com.zh.crs.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.zh.common.MyBaseController;
import com.zh.router.Rest;
import com.zh.util.MsgUtils;

/**
 * @ClassName: TestController
 * @Description: 生成验证码
 * @author: renkai721
 * @date: 2019年3月28日 上午9:31:21
 */
@RestController
@RequestMapping(Rest.Test.Root)
public class TestController2 extends MyBaseController {
	@RequestMapping(value = Rest.Test.test, method = RequestMethod.GET)
	@ResponseBody
	public synchronized String test() {
		try {
			// 处理自己的逻辑
			Thread.sleep(1);
			// 逻辑处理完了就要释放一个资源
//			AuthorizationInterceptor.welcomeSemaphore.release(1);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return MsgUtils.outJsonSuccess();
	}
}

5、运行结果

2019-10-15 14:15:15 org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/fxcrs] [173] | Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-10-15 14:15:15 org.springframework.web.servlet.DispatcherServlet [524] | Initializing Servlet 'dispatcherServlet'
2019-10-15 14:15:15 org.springframework.web.servlet.DispatcherServlet [546] | Completed initialization in 10 ms
2019-10-15 14:15:15 com.zh.filter.AuthorizationInterceptor2 [57] | 同一秒中,同一接口,只能允许2个人访问
2019-10-15 14:15:15 com.zh.util.MsgUtils [195] | {"errorText":"同一接口访问上限,请稍后再试","iTotalRecords":0,"aaData":"","iTotalDisplayRecords":0,"errorCode":"bm_code_url_max_count","sEcho":"","iDisplayStart":0}
2019-10-15 14:15:15 com.zh.util.MsgUtils [222] | {"errorText":"操作成功","iTotalRecords":0,"aaData":"","iTotalDisplayRecords":0,"errorCode":"success","sEcho":"","iDisplayStart":0}
2019-10-15 14:15:15 com.zh.util.MsgUtils [222] | {"errorText":"操作成功","iTotalRecords":0,"aaData":"","iTotalDisplayRecords":0,"errorCode":"success","sEcho":"","iDisplayStart":0}
2019-10-15 14:15:15 com.zh.filter.AuthorizationInterceptor2 [57] | 同一秒中,同一接口,只能允许2个人访问
2019-10-15 14:15:15 com.zh.util.MsgUtils [195] | {"errorText":"同一接口访问上限,请稍后再试","iTotalRecords":0,"aaData":"","iTotalDisplayRecords":0,"errorCode":"bm_code_url_max_count","sEcho":"","iDisplayStart":0}
2019-10-15 14:15:15 com.zh.filter.AuthorizationInterceptor2 [57] | 同一秒中,同一接口,只能允许2个人访问
2019-10-15 14:15:15 com.zh.util.MsgUtils [195] | {"errorText":"同一接口访问上限,请稍后再试","iTotalRecords":0,"aaData":"","iTotalDisplayRecords":0,"errorCode":"bm_code_url_max_count","sEcho":"","iDisplayStart":0}
2019-10-15 14:15:15 com.zh.filter.AuthorizationInterceptor2 [57] | 同一秒中,同一接口,只能允许2个人访问
2019-10-15 14:15:15 com.zh.util.MsgUtils [195] | {"errorText":"同一接口访问上限,请稍后再试","iTotalRecords":0,"aaData":"","iTotalDisplayRecords":0,"errorCode":"bm_code_url_max_count","sEcho":"","iDisplayStart":0}
2019-10-15 14:15:16 com.zh.filter.AuthorizationInterceptor2 [57] | 同一秒中,同一接口,只能允许2个人访问
2019-10-15 14:15:16 com.zh.util.MsgUtils [195] | {"errorText":"同一接口访问上限,请稍后再试","iTotalRecords":0,"aaData":"","iTotalDisplayRecords":0,"errorCode":"bm_code_url_max_count","sEcho":"","iDisplayStart":0}

备注:大家要注意test接口中注释的那一段话,其中Thread.sleep(1);是我做测试用的,实际中是没有这句话的,实际中应该是自己的业务逻辑,也当然不止1毫秒。下面的释放资源的代码,实际中需要放开,因为一个用户的请求处理完了,下一个用户就可以访问了,所以需要释放资源。

第二种实现方式

1、拦截器中使用map来保存请求的url

// 资源总数
	private static int size = 2;
	public static ConcurrentHashMap<String, Integer> urlCount = new ConcurrentHashMap<String, Integer>();
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		BodyReaderHttpServletRequestWrapper requestWrapper = null;
		if (request instanceof BodyReaderHttpServletRequestWrapper) {
			// 签名处理过程 start....
			requestWrapper = (BodyReaderHttpServletRequestWrapper) request;
			// 签名处理过程 end....
			// 默认记录后台接口请求日志记录
			String url = requestWrapper.getRequestURL().toString();
			if(url.indexOf("/test/welcome") != 0) {
				synchronized(urlCount) {
					String f = "yyyyMMddHHmmss";
					String key = LocalDateTime.now().format(DateTimeFormatter.ofPattern(f)) + "_" + "/test/welcome";
					log.info("key=" + key);
					Integer v = urlCount.get(key);
					log.info("v=" + v);
					if (null != v) {
						v++;
						if (v > size) {
							log.info("同一秒中,同一接口,只能允许"+size+"个人访问");
							PrintWriter out = response.getWriter();
							String msg = MsgUtils.outJson(USDConstants.BM_CODE_URL_MAX_COUNT);
							out.print(msg);
							out.flush();
							return false;
						}
						urlCount.put(key, v);
					} else {
						urlCount.put(key, 1);
					}
				}
			}
		}
		return super.preHandle(request, response, handler);
	}

2、具体的welcome接口逻辑

@RequestMapping(value = Rest.Test.welcome, method = RequestMethod.GET)
	@ResponseBody
	public synchronized  String welcome() {
		try {
			// 处理自己的逻辑
			Thread.sleep(1);
//			String f = "yyyyMMddHHmmss";
//			String key = LocalDateTime.now().format(DateTimeFormatter.ofPattern(f)) + "_" + "/test/welcome";
//			Integer v = AuthorizationInterceptor.urlCount.get(key);
//			// 逻辑处理完,释放一个资源
//			v--;
//			System.out.println("逻辑处理完成释放一个资源,key="+key+",value="+v);
//			AuthorizationInterceptor.urlCount.put(key, v);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return MsgUtils.outJsonSuccess();
	}

备注:在实际的场景中welcome里面的释放资源代码需要放开。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

renkai721

谢谢您的打赏!

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

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

打赏作者

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

抵扣说明:

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

余额充值