zoukankan      html  css  js  c++  java
  • Java秒杀实战 (七)安全优化

    转自:https://blog.csdn.net/qq_41305266/article/details/81174782

    一、隐藏秒杀地址

    思路:秒杀开始前,先去请求接口获取秒杀地址

    1.接口改造,带上PathVariable参数

    2.添加生成地址的接口

    3.秒杀收到请求,先验证PathVariable

    二、数学公式验证码

    1.添加生产验证码接口

    2.在获取秒杀路径的时候,验证验证码

    3.ScriptEngine使用

    package com.wings.seckill.controller;

    import java.awt.image.BufferedImage;
    import java.io.OutputStream;
    import java.util.HashMap;
    import java.util.List;

    import javax.imageio.ImageIO;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;

    import org.springframework.beans.factory.InitializingBean;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.ResponseBody;

    import com.wings.seckill.access.AccessLimit;
    import com.wings.seckill.domain.SeckillOrder;
    import com.wings.seckill.domain.SeckillUser;
    import com.wings.seckill.rabbitmq.MQSender;
    import com.wings.seckill.rabbitmq.SeckillMessage;
    import com.wings.seckill.redis.GoodsKey;
    import com.wings.seckill.redis.OrderKey;
    import com.wings.seckill.redis.RedisService;
    import com.wings.seckill.redis.SeckillKey;
    import com.wings.seckill.result.CodeMsg;
    import com.wings.seckill.result.Result;
    import com.wings.seckill.service.GoodsService;
    import com.wings.seckill.service.OrderService;
    import com.wings.seckill.service.SeckillService;
    import com.wings.seckill.service.SeckillUserService;
    import com.wings.seckill.vo.GoodsVo;

    @Controller
    @RequestMapping("/seckill")
    public class SeckillController implements InitializingBean {

    @Autowired
    SeckillUserService userService;

    @Autowired
    RedisService redisService;

    @Autowired
    GoodsService goodsService;

    @Autowired
    OrderService orderService;

    @Autowired
    SeckillService seckillService;

    @Autowired
    MQSender sender;

    private HashMap<Long, Boolean> localOverMap = new HashMap<Long, Boolean>();

    @Override
    public void afterPropertiesSet() throws Exception {
    List<GoodsVo> goodsList = goodsService.listGoodsVo();
    if (goodsList == null) {
    return;
    }
    for (GoodsVo goods : goodsList) {
    redisService.set(GoodsKey.getSeckillGoodsStock, "" + goods.getId(), goods.getStockCount());
    localOverMap.put(goods.getId(), false);
    }

    }

    @RequestMapping(value = "/{path}/do_seckill", method = RequestMethod.POST)
    @ResponseBody
    public Result<Integer> list(Model model, SeckillUser user, @RequestParam("goodsId") long goodsId,
    @PathVariable("path") String path) {
    model.addAttribute("user", user);
    if (user == null) {
    return Result.error(CodeMsg.SESSION_ERROR);
    }
    // 验证path
    boolean check = seckillService.checkPath(user, goodsId, path);
    if (!check) {
    return Result.error(CodeMsg.REQUEST_ILLEGAL);
    }
    // 内存标记,减少redis访问
    boolean over = localOverMap.get(goodsId);
    if (over) {
    return Result.error(CodeMsg.SECKill_OVER);
    }
    // 预减库存
    long stock = redisService.decr(GoodsKey.getSeckillGoodsStock, "" + goodsId);
    if (stock < 0) {
    localOverMap.put(goodsId, true);
    return Result.error(CodeMsg.SECKill_OVER);
    }
    // 判断是否已经秒杀到了
    SeckillOrder order = orderService.getSeckillOrderByUserIdGoodsId(user.getId(), goodsId);
    if (order != null) {
    return Result.error(CodeMsg.REPEATE_SECKILL);
    }
    // 入队
    SeckillMessage mm = new SeckillMessage();
    mm.setUser(user);
    mm.setGoodsId(goodsId);
    sender.sendSeckillMessage(mm);
    return Result.success(0);// 排队中

    }

    @RequestMapping(value = "/reset", method = RequestMethod.GET)
    @ResponseBody
    public Result<Boolean> reset(Model model) {
    List<GoodsVo> goodsList = goodsService.listGoodsVo();
    for (GoodsVo goods : goodsList) {
    goods.setStockCount(10);
    redisService.set(GoodsKey.getSeckillGoodsStock, "" + goods.getId(), 10);
    localOverMap.put(goods.getId(), false);
    }
    redisService.delete(OrderKey.getSeckillOrderByUidGid);
    redisService.delete(SeckillKey.isGoodsOver);
    seckillService.reset(goodsList);
    return Result.success(true);
    }

    /**
    * orderId:成功 -1:秒杀失败 0: 排队中
    */
    @RequestMapping(value = "/result", method = RequestMethod.GET)
    @ResponseBody
    public Result<Long> seckillResult(Model model, SeckillUser user, @RequestParam("goodsId") long goodsId) {
    model.addAttribute("user", user);
    if (user == null) {
    return Result.error(CodeMsg.SESSION_ERROR);
    }
    long result = seckillService.getSeckillResult(user.getId(), goodsId);
    return Result.success(result);
    }

    @AccessLimit(seconds = 5, maxCount = 5, needLogin = true)
    @RequestMapping(value = "/path", method = RequestMethod.GET)
    @ResponseBody
    public Result<String> getSeckillPath(HttpServletRequest request, SeckillUser user,
    @RequestParam("goodsId") long goodsId,
    @RequestParam(value = "verifyCode", defaultValue = "0") int verifyCode) {
    if (user == null) {
    return Result.error(CodeMsg.SESSION_ERROR);
    }
    boolean check = seckillService.checkVerifyCode(user, goodsId, verifyCode);
    if (!check) {
    return Result.error(CodeMsg.REQUEST_ILLEGAL);
    }
    String path = seckillService.createSeckillPath(user, goodsId);
    return Result.success(path);
    }

    @RequestMapping(value = "/verifyCode", method = RequestMethod.GET)
    @ResponseBody
    public Result<String> getSeckillVerifyCod(HttpServletResponse response, SeckillUser user,
    @RequestParam("goodsId") long goodsId) {
    if (user == null) {
    return Result.error(CodeMsg.SESSION_ERROR);
    }
    try {
    BufferedImage image = seckillService.createVerifyCode(user, goodsId);
    OutputStream out = response.getOutputStream();
    ImageIO.write(image, "JPEG", out);
    out.flush();
    out.close();
    return null;
    } catch (Exception e) {
    e.printStackTrace();
    return Result.error(CodeMsg.SECKILL_FAIL);
    }
    }
    }
    package com.wings.seckill.service;

    import java.awt.Color;
    import java.awt.Font;
    import java.awt.Graphics;
    import java.awt.image.BufferedImage;
    import java.util.List;
    import java.util.Random;

    import javax.script.ScriptEngine;
    import javax.script.ScriptEngineManager;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import com.wings.seckill.domain.OrderInfo;
    import com.wings.seckill.domain.SeckillOrder;
    import com.wings.seckill.domain.SeckillUser;
    import com.wings.seckill.redis.RedisService;
    import com.wings.seckill.redis.SeckillKey;
    import com.wings.seckill.util.Md5Util;
    import com.wings.seckill.util.UUIDUtil;
    import com.wings.seckill.vo.GoodsVo;

    @Service
    public class SeckillService {

    @Autowired
    GoodsService goodsService;

    @Autowired
    OrderService orderService;

    @Autowired
    RedisService redisService;

    @Transactional
    public OrderInfo seckill(SeckillUser user, GoodsVo goods) {
    // 减库存 下订单 写入秒杀订单
    boolean success = goodsService.reduceStock(goods);
    if (success) {
    // order_info maiosha_order
    return orderService.createOrder(user, goods);
    } else {
    setGoodsOver(goods.getId());
    return null;
    }
    }

    public long getSeckillResult(Long userId, long goodsId) {
    SeckillOrder order = orderService.getSeckillOrderByUserIdGoodsId(userId, goodsId);
    if (order != null) {// 秒杀成功
    return order.getOrderId();
    } else {
    boolean isOver = getGoodsOver(goodsId);
    if (isOver) {
    return -1;
    } else {
    return 0;
    }
    }
    }

    private void setGoodsOver(Long goodsId) {
    redisService.set(SeckillKey.isGoodsOver, "" + goodsId, true);
    }

    private boolean getGoodsOver(long goodsId) {
    return redisService.exists(SeckillKey.isGoodsOver, "" + goodsId);
    }

    public void reset(List<GoodsVo> goodsList) {
    goodsService.resetStock(goodsList);
    orderService.deleteOrders();
    }

    public boolean checkPath(SeckillUser user, long goodsId, String path) {
    if (user == null || path == null) {
    return false;
    }
    String pathOld = redisService.get(SeckillKey.getSeckillPath, "" + user.getId() + "_" + goodsId, String.class);
    return path.equals(pathOld);
    }

    public String createSeckillPath(SeckillUser user, long goodsId) {
    if (user == null || goodsId <= 0) {
    return null;
    }
    String str = Md5Util.md5(UUIDUtil.uuid() + "123456");
    redisService.set(SeckillKey.getSeckillPath, "" + user.getId() + "_" + goodsId, str);
    return str;
    }

    public BufferedImage createVerifyCode(SeckillUser user, long goodsId) {
    if (user == null || goodsId <= 0) {
    return null;
    }
    int width = 80;
    int height = 32;
    // create the image
    BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    Graphics g = image.getGraphics();
    // set the background color
    g.setColor(new Color(0xDCDCDC));
    g.fillRect(0, 0, width, height);
    // draw the border
    g.setColor(Color.black);
    g.drawRect(0, 0, width - 1, height - 1);
    // create a random instance to generate the codes
    Random rdm = new Random();
    // make some confusion
    for (int i = 0; i < 50; i++) {
    int x = rdm.nextInt(width);
    int y = rdm.nextInt(height);
    g.drawOval(x, y, 0, 0);
    }
    // generate a random code
    String verifyCode = generateVerifyCode(rdm);
    g.setColor(new Color(0, 100, 0));
    g.setFont(new Font("Candara", Font.BOLD, 24));
    g.drawString(verifyCode, 8, 24);
    g.dispose();
    // 把验证码存到redis中
    int rnd = calc(verifyCode);
    redisService.set(SeckillKey.getSeckillVerifyCode, user.getId() + "," + goodsId, rnd);
    // 输出图片
    return image;
    }

    public boolean checkVerifyCode(SeckillUser user, long goodsId, int verifyCode) {
    if (user == null || goodsId <= 0) {
    return false;
    }
    Integer codeOld = redisService.get(SeckillKey.getSeckillVerifyCode, user.getId() + "," + goodsId,
    Integer.class);
    if (codeOld == null || codeOld - verifyCode != 0) {
    return false;
    }
    redisService.delete(SeckillKey.getSeckillVerifyCode, user.getId() + "," + goodsId);
    return true;
    }

    private static int calc(String exp) {
    try {
    ScriptEngineManager manager = new ScriptEngineManager();
    ScriptEngine engine = manager.getEngineByName("JavaScript");
    return (Integer) engine.eval(exp);
    } catch (Exception e) {
    e.printStackTrace();
    return 0;
    }
    }

    private static char[] ops = new char[] { '+', '-', '*' };

    /**
    * + - *
    */
    private String generateVerifyCode(Random rdm) {
    int num1 = rdm.nextInt(10);
    int num2 = rdm.nextInt(10);
    int num3 = rdm.nextInt(10);
    char op1 = ops[rdm.nextInt(3)];
    char op2 = ops[rdm.nextInt(3)];
    String exp = "" + num1 + op1 + num2 + op2 + num3;
    return exp;
    }
    }
    <!DOCTYPE HTML>
    <html >
    <head>
    <title>商品详情</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <!-- jquery -->
    <script type="text/javascript" src="/js/jquery.min.js"></script>
    <!-- bootstrap -->
    <link rel="stylesheet" type="text/css" href="/bootstrap/css/bootstrap.min.css" />
    <script type="text/javascript" src="/bootstrap/js/bootstrap.min.js"></script>
    <!-- jquery-validator -->
    <script type="text/javascript" src="/jquery-validation/jquery.validate.min.js"></script>
    <script type="text/javascript" src="/jquery-validation/localization/messages_zh.min.js"></script>
    <!-- layer -->
    <script type="text/javascript" src="/layer/layer.js"></script>
    <!-- md5.js -->
    <script type="text/javascript" src="/js/md5.min.js"></script>
    <!-- common.js -->
    <script type="text/javascript" src="/js/common.js"></script>
    <style type="text/css">
    html,body{
    height:100%;
    100%;
    }
    body{
    background:url('/img/bg2.jpg') no-repeat;
    background-size:100% 100%;
    }
    #goodslist td{
    border-top:1px solid #39503f61;
    }
    </style>
    </head>
    <body>

    <div class="panel panel-default" style="height:100%;background-color:rgba(222,222,222,0.8)" >
    <div class="panel-heading">秒杀商品详情</div>
    <div class="panel-body">
    <span id="userTip"> 您还没有登录,请登陆后再操作<br/></span>
    <span>没有收货地址的提示。。。</span>
    </div>
    <table class="table" id="goodslist">
    <tr>
    <td>商品名称</td>
    <td colspan="3" id="goodsName"></td>
    </tr>
    <tr>
    <td>商品图片</td>
    <td colspan="3"><img id="goodsImg" width="200" height="200" /></td>
    </tr>
    <tr>
    <td>秒杀开始时间</td>
    <td id="startTime"></td>
    <td >
    <input type="hidden" id="remainSeconds" />
    <span id="seckillTip"></span>
    </td>
    <td>
    <!--
    <form id="seckillForm" method="post" action="/seckill/do_seckill">
    <button class="btn btn-primary btn-block" type="submit" id="buyButton">立即秒杀</button>
    <input type="hidden" name="goodsId" id="goodsId" />
    </form>-->
    <div class="row">
    <div class="form-inline">
    <img id="verifyCodeImg" width="80" height="32" style="display:none" onclick="refreshVerifyCode()"/>
    <input id="verifyCode" class="form-control" style="display:none"/>
    <button class="btn btn-primary" type="button" id="buyButton"onclick="getSeckillPath()">立即秒杀</button>
    </div>
    </div>
    <input type="hidden" name="goodsId" id="goodsId" />
    </td>
    </tr>
    <tr>
    <td>商品原价</td>
    <td colspan="3" id="goodsPrice"></td>
    </tr>
    <tr>
    <td>秒杀价</td>
    <td colspan="3" id="seckillPrice"></td>
    </tr>
    <tr>
    <td>库存数量</td>
    <td colspan="3" id="stockCount"></td>
    </tr>
    </table>
    </div>
    </body>
    <script>

    function getSeckillPath(){
    var goodsId = $("#goodsId").val();
    g_showLoading();
    $.ajax({
    url:"/seckill/path",
    type:"GET",
    data:{
    goodsId:goodsId,
    verifyCode:$("#verifyCode").val()
    },
    success:function(data){
    if(data.code == 0){
    var path = data.data;
    doSeckill(path);
    }else{
    layer.msg(data.msg);
    }
    },
    error:function(){
    layer.msg("客户端请求有误");
    }
    });
    }

    function getSeckillResult(goodsId){
    g_showLoading();
    $.ajax({
    url:"/seckill/result",
    type:"GET",
    data:{
    goodsId:$("#goodsId").val(),
    },
    success:function(data){
    if(data.code == 0){
    var result = data.data;
    if(result < 0){
    layer.msg("对不起,秒杀失败");
    }else if(result == 0){//继续轮询
    setTimeout(function(){
    getSeckillResult(goodsId);
    }, 200);
    }else{
    layer.confirm("恭喜你,秒杀成功!查看订单?", {btn:["确定","取消"]},
    function(){
    window.location.href="/order_detail.htm?orderId="+result;
    },
    function(){
    layer.closeAll();
    });
    }
    }else{
    layer.msg(data.msg);
    }
    },
    error:function(){
    layer.msg("客户端请求有误");
    }
    });
    }

    function doSeckill(path){
    $.ajax({
    url:"/seckill/"+path+"/do_seckill",
    type:"POST",
    data:{
    goodsId:$("#goodsId").val()
    },
    success:function(data){
    if(data.code == 0){
    //window.location.href="/order_detail.htm?orderId="+data.data.id;
    getSeckillResult($("#goodsId").val());
    }else{
    layer.msg(data.msg);
    }
    },
    error:function(){
    layer.msg("客户端请求有误");
    }
    });

    }

    function render(detail){
    var seckillStatus = detail.seckillStatus;
    var remainSeconds = detail.remainSeconds;
    var goods = detail.goods;
    var user = detail.user;
    if(user){
    $("#userTip").hide();
    }
    $("#goodsName").text(goods.goodsName);
    $("#goodsImg").attr("src", goods.goodsImg);
    $("#startTime").text(new Date(goods.startDate).format("yyyy-MM-dd hh:mm:ss"));
    $("#remainSeconds").val(remainSeconds);
    $("#goodsId").val(goods.id);
    $("#goodsPrice").text(goods.goodsPrice);
    $("#seckillPrice").text(goods.seckillPrice);
    $("#stockCount").text(goods.stockCount);
    countDown();
    }

    $(function(){
    //countDown();
    getDetail();
    });

    function getDetail(){
    var goodsId = g_getQueryString("goodsId");
    $.ajax({
    url:"/goods/detail/"+goodsId,
    type:"GET",
    success:function(data){
    if(data.code == 0){
    render(data.data);
    }else{
    layer.msg(data.msg);
    }
    },
    error:function(){
    layer.msg("客户端请求有误");
    }
    });
    }

    function countDown(){
    var remainSeconds = $("#remainSeconds").val();
    var timeout;
    if(remainSeconds > 0){//秒杀还没开始,倒计时
    $("#buyButton").attr("disabled", true);
    $("#seckillTip").html("秒杀倒计时:"+remainSeconds+"秒");
    timeout = setTimeout(function(){
    $("#countDown").text(remainSeconds - 1);
    $("#remainSeconds").val(remainSeconds - 1);
    countDown();
    },1000);
    }else if(remainSeconds == 0){//秒杀进行中
    $("#buyButton").attr("disabled", false);
    if(timeout){
    clearTimeout(timeout);
    }
    $("#seckillTip").html("秒杀进行中");
    $("#verifyCodeImg").attr("src", "/seckill/verifyCode?goodsId="+$("#goodsId").val());
    $("#verifyCodeImg").show();
    $("#verifyCode").show();
    }else{//秒杀已经结束
    $("#buyButton").attr("disabled", true);
    $("#seckillTip").html("秒杀已经结束");
    $("#verifyCodeImg").hide();
    $("#verifyCode").hide();
    }
    }
    function refreshVerifyCode(){
    $("#verifyCodeImg").attr("src", "/seckill/verifyCode?goodsId="+$("#goodsId").val()+"&timestamp="+new Date().getTime());
    }
    </script>
    </html>
     

    三、接口防刷

    思路:对接口做限流

    1.可以用拦截器减少对业务侵入

    package com.wings.seckill.access;

    import com.wings.seckill.domain.SeckillUser;

    public class UserContext {

    private static ThreadLocal<SeckillUser> userHolder = new ThreadLocal<SeckillUser>();

    public static void setUser(SeckillUser user) {
    userHolder.set(user);
    }

    public static SeckillUser getUser() {
    return userHolder.get();
    }

    }
    package com.wings.seckill.access;

    import static java.lang.annotation.ElementType.METHOD;
    import static java.lang.annotation.RetentionPolicy.RUNTIME;

    import java.lang.annotation.Retention;
    import java.lang.annotation.Target;

    @Retention(RUNTIME)
    @Target(METHOD)
    public @interface AccessLimit {
    int seconds();
    int maxCount();
    boolean needLogin() default true;
    }
    package com.wings.seckill.access;

    import java.io.OutputStream;

    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;

    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.web.method.HandlerMethod;
    import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

    import com.alibaba.fastjson.JSON;
    import com.wings.seckill.domain.SeckillUser;
    import com.wings.seckill.redis.AccessKey;
    import com.wings.seckill.redis.RedisService;
    import com.wings.seckill.result.CodeMsg;
    import com.wings.seckill.result.Result;
    import com.wings.seckill.service.SeckillUserService;

    @Service
    public class AccessInterceptor extends HandlerInterceptorAdapter{

    @Autowired
    SeckillUserService userService;

    @Autowired
    RedisService redisService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
    throws Exception {
    if(handler instanceof HandlerMethod) {
    SeckillUser user = getUser(request, response);
    UserContext.setUser(user);
    HandlerMethod hm = (HandlerMethod)handler;
    AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);
    if(accessLimit == null) {
    return true;
    }
    int seconds = accessLimit.seconds();
    int maxCount = accessLimit.maxCount();
    boolean needLogin = accessLimit.needLogin();
    String key = request.getRequestURI();
    if(needLogin) {
    if(user == null) {
    render(response, CodeMsg.SESSION_ERROR);
    return false;
    }
    key += "_" + user.getId();
    }else {
    //do nothing
    }
    AccessKey ak = AccessKey.withExpire(seconds);
    Integer count = redisService.get(ak, key, Integer.class);
    if(count == null) {
    redisService.set(ak, key, 1);
    }else if(count < maxCount) {
    redisService.incr(ak, key);
    }else {
    render(response, CodeMsg.ACCESS_LIMIT_REACHED);
    return false;
    }
    }
    return true;
    }

    private void render(HttpServletResponse response, CodeMsg cm)throws Exception {
    response.setContentType("application/json;charset=UTF-8");
    OutputStream out = response.getOutputStream();
    String str = JSON.toJSONString(Result.error(cm));
    out.write(str.getBytes("UTF-8"));
    out.flush();
    out.close();
    }

    private SeckillUser getUser(HttpServletRequest request, HttpServletResponse response) {
    String paramToken = request.getParameter(SeckillUserService.COOKIE_TOKEN_NAME);
    String cookieToken = getCookieValue(request, SeckillUserService.COOKIE_TOKEN_NAME);
    if(StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)) {
    return null;
    }
    String token = StringUtils.isEmpty(paramToken)?cookieToken:paramToken;
    return userService.getByToken( token, response);
    }

    private String getCookieValue(HttpServletRequest request, String cookiName) {
    Cookie[] cookies = request.getCookies();
    if(cookies == null || cookies.length <= 0){
    return null;
    }
    for(Cookie cookie : cookies) {
    if(cookie.getName().equals(cookiName)) {
    return cookie.getValue();
    }
    }
    return null;
    }

    }
    package com.wings.seckill.config;

    import java.util.List;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.method.support.HandlerMethodArgumentResolver;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

    import com.wings.seckill.access.AccessInterceptor;

    @Configuration
    public class WebConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private UserArgumentResolver userArgumentResolver;

    @Autowired
    AccessInterceptor accessInterceptor;

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
    argumentResolvers.add(userArgumentResolver);
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(accessInterceptor);
    }
    }
     

     
    ---------------------
    作者:插上小翅膀的程序猿Wings
    来源:CSDN
    原文:https://blog.csdn.net/qq_41305266/article/details/81174782
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    python list间的并集、差集与交集
    kafka常用命令,启动/停止/查看主题列表/消费/生产
    python json
    lrzsz
    HashMap实现原理,源码分析
    Java中try catch finally语句中含有return语句的执行情况
    Maven
    我的面试题
    JSON数据格式
    springMVC2
  • 原文地址:https://www.cnblogs.com/sharpest/p/10960412.html
Copyright © 2011-2022 走看看