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 responseBodyConverter() {
// 设置字符集,不然返回到前台中文乱码
StringHttpMessageConverter converter = new StringHttpMessageConverter(Charset.forName("UTF-8"));
return converter;
}
@Override
public void configureMessageConverters(List> 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: 复制输入流
* @param inputStream
* @return
*/
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 urlCount = new ConcurrentHashMap();
@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里面的释放资源代码需要放开。