回復(fù)“電子書”獲取程序員必備電子書 大家好,我是老田,今天給大家分享的是:接口防刷。 之前,我已經(jīng)分享給四個美團(tuán)面試的技術(shù)點(diǎn): 美團(tuán)面試:講清楚MySQL結(jié)構(gòu)體系,立馬發(fā)offer
美團(tuán)面試:慢SQL有遇到過嗎?是怎么解決的?
美團(tuán)面試:String s = new String("111")會創(chuàng)建幾個對象?
美團(tuán)面試:為什么就能直接調(diào)用userMapper接口的方法? 下面是原本面試現(xiàn)場: 面試官:接口被惡意狂刷,怎么辦? 我:這個沒搞過(每天CRUD,真的沒搞過) 面試官:如果現(xiàn)在讓你來設(shè)計,你會怎么設(shè)計? 我:巴拉巴拉...胡扯一通 面試官:(帶著不耐煩的表情)我們還是換個話題吧 .....
為了不讓大家也和我有同樣的遭遇,今天,咱們就用一個非常簡單的方式實(shí)現(xiàn)防刷: 一個注解搞定防刷
技術(shù)點(diǎn) 涉及到的技術(shù)點(diǎn)有如下幾個: 其實(shí),非常簡單,主要的還是看業(yè)務(wù)。 本文主要內(nèi)容: 
自定義注解 自定義一注解AccessLimit。 import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; @Retention(RUNTIME) @Target(METHOD) public @interface AccessLimit { //次數(shù)上限 int maxCount(); //是否需要登錄 boolean needLogin()default false; }
添加Redis配置項(xiàng) 在配置文件中,加入Redis 配置; spring.redis.database=0 spring.redis.host=127.0.0.1 spring.redis.port=6379 spring.redis.jedis.pool.max-active=100 spring.redis.jedis.pool.max-idle=100 spring.redis.jedis.pool.min-idle=10 spring.redis.jedis.pool.max-wait=1000ms
注意,把Redis 的starter在pom 中引入。 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
創(chuàng)建攔截器 創(chuàng)建攔截器,所有請求都進(jìn)行攔截,防刷的主要內(nèi)容全部在這里。 // 一堆import 這里就不貼出來了,需要的自己導(dǎo)入 /** * 處理方法上 有 AccessLimitEnum 注解的方法 * @author java后端技術(shù)全棧 * @date 2021/8/6 15:42 */ @Component public class FangshuaInterceptor extends HandlerInterceptorAdapter {
@Resource private RedisTemplate<String,Object> redisTemplate;
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("----FangshuaInterceptor-----"); //判斷請求是否屬于方法的請求 if (handler instanceof HandlerMethod) {
HandlerMethod hm = (HandlerMethod) handler;
//檢查方法上室友有AccessLimit注解 AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class); if (accessLimit == null) { return true; } //獲取注解中的參數(shù), int maxCount = accessLimit.maxCount(); boolean login = accessLimit.needLogin(); String key = request.getRequestURI(); //防刷=同一個請求路徑+同一個用戶+當(dāng)天 //如果需要登錄 if (login) { //可以充session中獲取user相關(guān)信息 //這里的userId暫時寫死, Long userId = 101L; String currentDay = format(new Date(), "yyyyMMdd"); key += currentDay + userId; }else{ //可以根據(jù)用戶使用的ip+日期進(jìn)行判斷 }
//從redis中獲取用戶訪問的次數(shù) Object countCache = redisTemplate.opsForValue().get(key); if (countCache == null) { //第一次訪問,有效期為一天 //時間單位自行定義 redisTemplate.opsForValue().set(key,1,86400, TimeUnit.SECONDS); } else{ Integer count = (Integer)countCache; if (count < maxCount) { //加1 count++; //也可以使用increment(key)方法 redisTemplate.opsForValue().set(key,count); } else { //超出訪問次數(shù) render(response, "訪問次數(shù)已達(dá)上限!"); return false; } } } return true; } //僅僅是為了演示哈 private void render(HttpServletResponse response, String msg) throws Exception { response.setContentType("application/json;charset=UTF-8"); OutputStream out = response.getOutputStream(); out.write(msg.getBytes("UTF-8")); out.flush(); out.close(); } //日期格式 public static String format(Date date, String formatString) { if (formatString == null) { formatString = DATE_TIME_FORMAT; } DateFormat dd = new SimpleDateFormat(formatString); return dd.format(date); } }
注意 判斷是否為相同請求,使用:URI+userId+日期 。即Redis 的key =URI+userId+yyyyMMdd ,緩存有效期為一天。 很多都在代碼里有注釋了,另外強(qiáng)調(diào)一下,不要吐槽代碼,僅僅是演示。
注冊攔截器 盡管上面我們已經(jīng)自定義并實(shí)現(xiàn)好了攔截器,但還需要我們手動注冊。 import com.example.demo.ExceptionHander.FangshuaInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration public class WebConfig extends WebMvcConfigurerAdapter { @Autowired private FangshuaInterceptor interceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(interceptor); } }
這樣我們的注解就正式注冊到攔截器鏈中了,后面項(xiàng)目中才會有效。 使用注解 前面的準(zhǔn)備都搞定了,現(xiàn)在來具體使用。 首先,我們創(chuàng)建一個簡單的controller ,然后,在方法上加上我們自定義的注解AccessLimit ,就可以實(shí)現(xiàn)接口防刷了。 import com.example.demo.result.Result; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class FangshuaController { //具體請求次數(shù)由具體業(yè)務(wù)決定,以及是否需要登錄 @AccessLimit(maxCount=5, needLogin=true) @RequestMapping("/fangshua") @ResponseBody public Object fangshua(){ return "請求成功"; } }
測試,瀏覽器頁面上訪問:http://localhost:8080/fangshua 前面4次返回的是:請求成功 超過4次后變成:訪問次數(shù)已達(dá)上限! 一個注解就搞定了,是不是 so easy !??!
總結(jié) 關(guān)于接口防刷,如果在面試中被問到,至少還是能說個123了。也建議大家手動試試,自己搞出來了更帶勁兒。 好了,今天就分享到此,如果對你有幫助,記得點(diǎn)贊 、分享 、在看 。 參考:blog.csdn.net/weixin_42533856
|