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裏面的釋放資源代碼需要放開。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章