springboot:集成Kaptcha实现图片验证码

news2024/12/23 15:49:09

文章目录

  • springboot:集成Kaptcha实现图片验证码
    • 一、导入依赖
      • 系统配置文件
    • 二、生成验证码
      • 1、Kaptcha的配置
      • 2、自定义验证码文本生成器
      • 3、具体实现
    • 三、校验验证码
      • 1、controller接口
      • 2、自定义前端过滤器
      • 3、自定义验证码处理过滤器
      • 4、自定义BodyReaderFilter解决读取body错误问题
      • 5、注意

springboot:集成Kaptcha实现图片验证码

系统环境:

windows 10

jdk 1.8

springboot版本: 2.1.10.RELEASE

一、导入依赖

        <dependency>
            <groupId>com.github.penggle</groupId>
            <artifactId>kaptcha</artifactId>
            <version>2.3.2</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

系统配置文件

server:
  port: 81
spring:
  redis:
    database: 1
    host: 127.0.0.1
    port: 6379
    password:      # 密码(默认为空)
    timeout: 6000ms  # 连接超时时长(毫秒)
    lettuce:
      pool:
        max-active: 1000  # 连接池最大连接数(使用负值表示没有限制)
        max-wait: -1ms      # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-idle: 10      # 连接池中的最大空闲连接
        min-idle: 5       # 连接池中的最小空闲连接

二、生成验证码

1、Kaptcha的配置

验证码文本生成器:这个需要自己生成并且修改下面的配置文件为你文件的路径

package com.yolo.springboot.kaptcha.config;

import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Properties;

/**
 * @ClassName CaptchaConfig
 * @Description 验证码配置
 * @Author hl
 * @Date 2022/12/6 9:37
 * @Version 1.0
 */
@Configuration
public class CaptchaConfig {

    @Bean(name = "captchaProducerMath")
    public DefaultKaptcha getKaptchaBeanMath() {
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        Properties properties = new Properties();
        // 是否有边框 默认为true 我们可以自己设置yes,no
        properties.setProperty("kaptcha.border", "yes");
        // 边框颜色 默认为Color.BLACK
        properties.setProperty("kaptcha.border.color", "105,179,90");
        // 验证码文本字符颜色 默认为Color.BLACK
        properties.setProperty("kaptcha.textproducer.font.color", "blue");
        // 验证码图片宽度 默认为200
        properties.setProperty("kaptcha.image.width", "160");
        // 验证码图片高度 默认为50
        properties.setProperty("kaptcha.image.height", "60");
        // 验证码文本字符大小 默认为40
        properties.setProperty("kaptcha.textproducer.font.size", "35");
        // KAPTCHA_SESSION_KEY
        properties.setProperty("kaptcha.session.key", "kaptchaCodeMath");
        // 验证码文本生成器
        properties.setProperty("kaptcha.textproducer.impl", "com.yolo.springboot.kaptcha.config.KaptchaTextCreator");
        // 验证码文本字符间距 默认为2
        properties.setProperty("kaptcha.textproducer.char.space", "3");
        // 验证码文本字符长度 默认为5
        properties.setProperty("kaptcha.textproducer.char.length", "6");
        // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1,
        // fontSize)
        properties.setProperty("kaptcha.textproducer.font.names", "Arial,Courier");
        // 验证码噪点颜色 默认为Color.BLACK
        properties.setProperty("kaptcha.noise.color", "white");
        // 干扰实现类
        properties.setProperty("kaptcha.noise.impl", "com.google.code.kaptcha.impl.NoNoise");
        // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple
        // 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy
        // 阴影com.google.code.kaptcha.impl.ShadowGimpy
        properties.setProperty("kaptcha.obscurificator.impl", "com.google.code.kaptcha.impl.ShadowGimpy");
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
}

2、自定义验证码文本生成器

package com.yolo.springboot.kaptcha.config;

import com.google.code.kaptcha.text.impl.DefaultTextCreator;

import java.util.Random;

/**
 * @ClassName KaptchaTextCreator
 * @Description 验证码文本生成器
 * @Author hl
 * @Date 2022/12/6 10:14
 * @Version 1.0
 */
public class KaptchaTextCreator extends DefaultTextCreator {

    private static final String[] Number = "0,1,2,3,4,5,6,7,8,9,10".split(",");
    @Override
    public String getText()
    {
        int result;
        Random random = new Random();
        int x = random.nextInt(10);
        int y = random.nextInt(10);
        StringBuilder suChinese = new StringBuilder();
        int randomOperand = (int) Math.round(Math.random() * 2);
        if (randomOperand == 0) {
            result = x * y;
            suChinese.append(Number[x]);
            suChinese.append("*");
            suChinese.append(Number[y]);
        } else if (randomOperand == 1) {
            if (!(x == 0) && y % x == 0) {
                result = y / x;
                suChinese.append(Number[y]);
                suChinese.append("/");
                suChinese.append(Number[x]);
            } else {
                result = x + y;
                suChinese.append(Number[x]);
                suChinese.append("+");
                suChinese.append(Number[y]);
            }
        } else if (randomOperand == 2) {
            if (x >= y) {
                result = x - y;
                suChinese.append(Number[x]);
                suChinese.append("-");
                suChinese.append(Number[y]);
            } else {
                result = y - x;
                suChinese.append(Number[y]);
                suChinese.append("-");
                suChinese.append(Number[x]);
            }
        } else {
            result = x + y;
            suChinese.append(Number[x]);
            suChinese.append("+");
            suChinese.append(Number[y]);
        }
        suChinese.append("=?@").append(result);
        return suChinese.toString();
    }
}

3、具体实现

package com.yolo.springboot.kaptcha.controller;

import cn.hutool.json.JSONUtil;
import com.google.code.kaptcha.Producer;
import com.hl.springbootcommon.common.HttpResponseTemp;
import com.hl.springbootcommon.common.ResultStat;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.MediaType;
import org.springframework.util.FastByteArrayOutputStream;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName CaptchaController
 * @Description 验证码
 * @Author hl
 * @Date 2022/12/6 9:45
 * @Version 1.0
 */
@RestController
@Slf4j
public class CaptchaController {

    @Autowired
    private Producer producer;

    @Autowired
    private StringRedisTemplate redisTemplate;

    public static final String DEFAULT_CODE_KEY = "random_code_";

   /**
      * @MethodName createCaptcha
      * @Description  生成验证码
      * @param httpServletResponse 响应流
      * @Author hl
      * @Date 2022/12/6 10:30
      */
    @GetMapping("/create/captcha")
    public void createCaptcha(HttpServletResponse httpServletResponse) throws IOException {
        // 生成验证码
        String capText = producer.createText();
        String capStr = capText.substring(0, capText.lastIndexOf("@"));
        String result = capText.substring(capText.lastIndexOf("@") + 1);
        BufferedImage image = producer.createImage(capStr);
        // 保存验证码信息
        String randomStr = UUID.randomUUID().toString().replaceAll("-", "");
        System.out.println("随机数为:" + randomStr);
        redisTemplate.opsForValue().set(DEFAULT_CODE_KEY + randomStr, result, 3600, TimeUnit.SECONDS);
        // 转换流信息写出
        FastByteArrayOutputStream os = new FastByteArrayOutputStream();
        try {
            ImageIO.write(image, "jpg", os);
        } catch (IOException e) {
            log.error("ImageIO write err", e);
            httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        // 定义response输出类型为image/jpeg类型,使用response输出流输出图片的byte数组
        byte[] bytes = os.toByteArray();
        //设置响应头
        httpServletResponse.setHeader("Cache-Control", "no-store");
        //设置响应头
        httpServletResponse.setHeader("randomstr",randomStr);
        //设置响应头
        httpServletResponse.setHeader("Pragma", "no-cache");
        //在代理服务器端防止缓冲
        httpServletResponse.setDateHeader("Expires", 0);
        //设置响应内容类型
        ServletOutputStream responseOutputStream = httpServletResponse.getOutputStream();
        responseOutputStream.write(bytes);
        responseOutputStream.flush();
        responseOutputStream.close();
    }
}

在这里插入图片描述

三、校验验证码

这里校验验证码,我用了过滤器来实现的,其中遇到了很多问题,下面有我详细的解决方法

1、controller接口

    @PostMapping("/login")
    public HttpResponseTemp<?> login(@RequestBody LoginDto loginDto){

        System.out.println(JSONUtil.toJsonStr(loginDto));
        return ResultStat.OK.wrap("","成功");
    }

@Data
public class LoginDto {

    private String captcha;
    private String randomStr;
}

2、自定义前端过滤器

这里是我写了一个简单的前端页面,然后发现这里会有一些前端的文件,所以需要过滤一下

package com.yolo.springboot.kaptcha.filter;

import cn.hutool.core.collection.ListUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.filter.ShallowEtagHeaderFilter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

/**
 * @ClassName SuffixFilter
 * @Description 前端文件过滤
 * @Author hl
 * @Date 2022/12/6 12:40
 * @Version 1.0
 */
public class FrontFilter extends ShallowEtagHeaderFilter implements Filter {

    private static final List<String> suffix = ListUtil.of(".css",".eot",".gif",".ico",".js",".map",".png",".svg",".swf",".ttf",".TTF",".woff",".woff2");


    @Override
    protected boolean shouldNotFilterAsyncDispatch() {
        return false;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        response.setHeader("Server", "Apache-Coyote/1.1");
        response.setHeader("Cache-Control", "max-age=0");
        String uri = request.getRequestURI();
        if (!StringUtils.isBlank(uri)) {
            int index = uri.lastIndexOf(".");
            if (index > 0 && suffix.contains(uri.substring(index))) {
                response.setHeader("Cache-Control", "max-age=3600");
            }
            if (uri.startsWith("/lib")) {
                response.setHeader("Cache-Control", "max-age=3600, immutable");
            }
        }
        super.doFilterInternal(request, response, filterChain);
    }
}

然后需要把我们自定的过滤器加入到spring中让他生效

package com.yolo.springboot.kaptcha.config;

import com.yolo.springboot.kaptcha.filter.FrontFilter;
import com.yolo.springboot.kaptcha.filter.ImgCodeFilter;
import com.yolo.springboot.kaptcha.filter.BodyReaderFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<?> frontFilterRegistration() {
        FilterRegistrationBean<FrontFilter> registration = new FilterRegistrationBean<>();
        // 将过滤器配置到FilterRegistrationBean对象中
        registration.setFilter(new FrontFilter());
        // 给过滤器取名
        registration.setName("frontFilter");
        // 设置过滤器优先级,该值越小越优先被执行
        registration.setOrder(0);
        List<String> urlPatterns = new ArrayList<>();
        urlPatterns.add("/*");
        // 设置urlPatterns参数
        registration.setUrlPatterns(urlPatterns);
        return registration;
    }
}

这里我给他设置的拦截全部请求,并且优先级是第一位的

3、自定义验证码处理过滤器

package com.yolo.springboot.kaptcha.filter;

import com.alibaba.fastjson.JSONObject;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.stream.Collectors;

/**
 * @ClassName ImgCodeFilter
 * @Description 验证码处理
 * @Author hl
 * @Date 2022/12/6 10:35
 * @Version 1.0
 */
@AllArgsConstructor
public class ImgCodeFilter implements Filter {

    private final StringRedisTemplate redisTemplate;

    private final static String AUTH_URL = "/login";

    public static final String DEFAULT_CODE_KEY = "random_code_";


    /**
     * filter对象只会创建一次,init方法也只会执行一次。
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    /**
     * 主要的业务代码编写方法
     */
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //只有转换为HttpServletRequest 对象才可以获取路径参数
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String requestURI = request.getRequestURI();
        if (!AUTH_URL.equalsIgnoreCase(requestURI)){
            //放行
            filterChain.doFilter(servletRequest, servletResponse);
        }

        try {
            String bodyStr = resolveBodyFromRequest(request);
            JSONObject bodyJson=JSONObject.parseObject(bodyStr);
            String code = (String) bodyJson.get("captcha");
            String randomStr = (String) bodyJson.get("randomStr");
            // 校验验证码
            checkCode(code, randomStr);
        } catch (Exception e) {
            HttpServletResponse response = (HttpServletResponse) servletResponse;
            response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
            response.setHeader("Content-Type", "application/json;charset=UTF-8");
            response.sendError(HttpStatus.UNAUTHORIZED.value(),"验证码认证失败或者过期");
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }

    /**
     * 检查code
     */
    @SneakyThrows
    private void checkCode(String code, String randomStr) {
        if (StringUtils.isBlank(code)) {
            throw new RuntimeException("验证码不能为空");
        }
        if (StringUtils.isBlank(randomStr)) {
            throw new RuntimeException("验证码不合法");
        }
        String key = DEFAULT_CODE_KEY + randomStr;
        String result = redisTemplate.opsForValue().get(key);
        redisTemplate.delete(key);
        if (!code.equalsIgnoreCase(result)) {
            throw new RuntimeException("验证码不合法");
        }
    }

    /**
       * @MethodName resolveBodyFromRequest
       * @Description  不能和@Requestbody搭配使用
       * 原因: getInputStream() has already been called for this request,流不能读取第二次,@Requestbody已经读取过一次了
       * @param request 请求流
       * 解决方案: 重写HttpServletRequestWrapper类,将HttpServletRequest的数据读到wrapper的缓存中去(用 byte[] 存储),再次读取时读缓存就可以了
       * 当接口涉及到上传下载时,会有一些异常问题,最好在过滤器中排除这些路径
       * @return: java.lang.String
       * @Author hl
       * @Date 2022/12/6 15:18
       */

    private String resolveBodyFromRequest(HttpServletRequest request){
        String bodyStr = null;
        // 获取请求体
        if ("POST".equalsIgnoreCase(request.getMethod())){
            try {
                bodyStr = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return bodyStr;
    }

    /**
     * 在销毁Filter时自动调用。
     */
    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

加入到配置中

这里校验需要用到redis,用构造方法给他注入

    @Autowired
    private StringRedisTemplate redisTemplate;
    @Bean
    public FilterRegistrationBean<?> imgCodeFilterRegistration() {
        FilterRegistrationBean<ImgCodeFilter> registration = new FilterRegistrationBean<>();
        // 将过滤器配置到FilterRegistrationBean对象中
        registration.setFilter(new ImgCodeFilter(redisTemplate));
        // 给过滤器取名
        registration.setName("imgCodeFilter");
        // 设置过滤器优先级,该值越小越优先被执行
        registration.setOrder(2);
        List<String> urlPatterns = new ArrayList<>();
        urlPatterns.add("/login");
        // 设置urlPatterns参数
        registration.setUrlPatterns(urlPatterns);
        return registration;
    }

遇到的问题及解决思路

问题:流不能多次被调用

ERROR m.e.handler.GlobalExceptionHandler - getInputStream() has already been called for this request
java.lang.IllegalStateException: getInputStream() has already been called for this request
    at org.apache.catalina.connector.Request.getReader(Request.java:1212)
    at org.apache.catalina.connector.RequestFacade.getReader(RequestFacade.java:504)

根据报错信息分析简单来说,就是getInputStream()已经被调用了,不能再次调用。可是我看代码上,我也没调用。经过一番检索,原来@RequestBody注解配置后,默认会使用流来读取数据

具体原因:

  • 默认配置时,getInputStream()和getReader()一起使用会报错,使用两遍getInputStream(),第二遍会为空
  • 当存在@RequestBody等注解时,springMVC已读取过一遍流,默认单独使用getInputStream()或getReader()都为空。

实测,不加@RequestBody注解,可以如期获得请求中的json参数,但是又不得不加@RequestBody注解。这样就需要新的思路

解决思路:

写filter继承HttpServletRequestWrapper,缓存InputStream,覆盖getInputStream()和getReader()方法,使用ByteArrayInputStream is = new ByteArrayInputStream(body.getBytes());读取InputStream。下面自定义BodyReaderFilter和BodyReaderWrapper就是具体解决方法

4、自定义BodyReaderFilter解决读取body错误问题

BodyReaderWrapper

package com.yolo.springboot.kaptcha.filter;

import org.springframework.util.StreamUtils;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

/**
 * 自定义 BodyReaderWrapper
 * 问题原因:在controller中我们通过@RequestBody注解来获取前端传过来的json数据,这里已经使用了一次request来获取body中的值。再次通过request获取body中的值,就会报错
 * 使用场景:通过request能获取到一次body中的值,有时候我们需要多次获取body中的值的需求,因此需要对流再次封装再次传递
 */
public class BodyReaderWrapper extends HttpServletRequestWrapper {
    private byte[] body;

    public BodyReaderWrapper(HttpServletRequest request) throws IOException {
        super(request);
        //保存一份InputStream,将其转换为字节数组
        body = StreamUtils.copyToByteArray(request.getInputStream());
    }

    //转换成String
    public String getBodyString(){
        return new String(body,StandardCharsets.UTF_8);
    }


    @Override
    public BufferedReader getReader() {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }
	//把保存好的InputStream,传下去
    @Override
    public ServletInputStream getInputStream() {
        final ByteArrayInputStream bais = new ByteArrayInputStream(body);
        return new ServletInputStream() {
            @Override
            public int read() {
                return bais.read();
            }
            @Override
            public boolean isFinished() {
                return false;
            }
            @Override
            public boolean isReady() {
                return false;
            }
            @Override
            public void setReadListener(ReadListener readListener) {
            }
        };
    }
    public void setInputStream(byte[] body) {
        this.body = body;
    }
}

BodyReaderFilter

package com.yolo.springboot.kaptcha.filter;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @ClassName RequestFilter
 * @Description 自定义BodyReaderFilter解决读取controller中使用@Requestbody重复读取流错误问题
 * @Author hl
 * @Date 2022/12/6 15:44
 * @Version 1.0
 */
public class BodyReaderFilter implements Filter {
    private List<String> noFilterUrls;

    @Override
    public void init(FilterConfig filterConfig){
        // 从过滤器配置中获取initParams参数
        String noFilterUrl = filterConfig.getInitParameter("noFilterUrl");
        // 将排除的URL放入成员变量noFilterUrls中
        if (StringUtils.isNotBlank(noFilterUrl)) {
            noFilterUrls = new ArrayList<>(Arrays.asList(noFilterUrl.split(",")));
        }
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
        ServletRequest requestWrapper = null;
        String requestURI = null;

        if (servletRequest instanceof HttpServletRequest) {
            //获取请求中的流如何,将取出来的字符串,再次转换成流,然后把它放入到新request对象中。
            requestWrapper = new BodyReaderWrapper((HttpServletRequest) servletRequest);
            requestURI = ((HttpServletRequest) servletRequest).getRequestURI();
        }

        //如果请求是需要排除的,直接放行,例如上传文件
        if ((CollUtil.isNotEmpty(noFilterUrls) && StrUtil.isNotBlank(requestURI) && noFilterUrls.contains(requestURI)) || requestWrapper == null){
            chain.doFilter(servletRequest, servletResponse);
        }else {
            // 在chain.doFiler方法中传递新的request对象
            chain.doFilter(requestWrapper, servletResponse);
        }
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

加入到配置中

这里需要注意,拦截的是所有请求,上传文件的时候需要排除,上传文件的路径

    @Bean
    public FilterRegistrationBean<?> bodyReaderFilterRegistration() {
        FilterRegistrationBean<BodyReaderFilter> registration = new FilterRegistrationBean<>();
        // 将过滤器配置到FilterRegistrationBean对象中
        registration.setFilter(new BodyReaderFilter());
        // 给过滤器取名
        registration.setName("bodyReaderFilter");
        // 设置过滤器优先级,该值越小越优先被执行
        registration.setOrder(1);
        List<String> urlPatterns = new ArrayList<>();
        //这里需要填写排除上传文件的接口
        Map<String, String> paramMap = new HashMap<>();
        paramMap.put("noFilterUrl", "/test");
        // 设置initParams参数
        registration.setInitParameters(paramMap);
        urlPatterns.add("/*");
        // 设置urlPatterns参数
        registration.setUrlPatterns(urlPatterns);
        return registration;
    }

在这里插入图片描述

测试成功:这里我原本用的form-data传参,然后一直获取到body为空,用这种方法是需要在raw中进行填写的

获取form表单的数据

		//方式一:getParameterMap(),获得请求参数map
        Map<String,String[]> map= request.getParameterMap();  //key 参数名称 value:具体值
		//方式二:getParameterNames():获取所有参数名称
        Enumeration a = request.getParameterNames();

5、注意

自定义的过滤器不要交给spring管理,也就是说不要添加@Component注解,不然每一个请求都会进行过滤

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/66285.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redis——Jedis的使用

前言 接上文&#xff0c;上一篇文章分享了在Linux下安装redis&#xff0c;以及redis的一些命令的使用。本文要分享的内容是java使用代码连接操作redis。 一、连接redis 这里我们要用到Jedis&#xff0c;那么什么是Jedis 简单来说&#xff0c;Jedis就是Redis官方推荐的Java连接…

【元胞自动机】模拟电波在整个心脏中的传导和传播的时空动力学研究(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

(八)SpringCloud+Security+Oauth2--token增强个性化和格式化输出

一 token的个性化输出 我们知道token默认的输出格式是: {"access_token": "21bd6b0b-0c24-40d1-8928-93274aa1180f","token_type": "bearer","refresh_token": "2c38965b-d4ce-4151-b88d-e39f278ce1bb","e…

[思考进阶]02 如何进行认知升级?

除了要提升自己的技术能力&#xff0c;思维的学习和成长也非常非常重要&#xff0c;特推出此[思考进阶]系列&#xff0c;进行刻意练习&#xff0c;从而提升自己的认知。 最近在看东野的《无名之町》&#xff0c;这本书写于2021年&#xff0c;日本正值疫情&#xff0c;书中也有大…

这个项目获2022世界物联网博览会三新成果奖!

近日&#xff0c;2022世界物联网无锡峰会在无锡太湖国际博览中心召开。天翼物联科技有限公司副总经理赵建军代表中国电信出席会议。 大会颁发了“物联网新技术新产品新应用金奖成果奖”&#xff08;简称“三新成果奖”&#xff09;&#xff0c;中国电信天翼物联“基于5G物联孪…

gRPC:以 C++为例

文章目录1、gRPC 环境搭建1.1、安装 cmake1.2、安装 gcc/gdb1.3、安装 gRPC1.4、protobuf 安装1.5、测试环境2.1、grpc 同步2.1、定义服务2.2、gRPC 服务端2.3、gRPC 客户端2.4、消息流3、gRPC stream3.1、服务端&#xff1a;RPC 实现3.2、客户端&#xff1a;RPC 调用3.3、流的…

刷爆力扣之子数组最大平均数 I

刷爆力扣之子数组最大平均数 I HELLO&#xff0c;各位看官大大好&#xff0c;我是阿呆 &#x1f648;&#x1f648;&#x1f648; 今天阿呆继续记录下力扣刷题过程&#xff0c;收录在专栏算法中 &#x1f61c;&#x1f61c;&#x1f61c; 该专栏按照不同类别标签进行刷题&…

Centos 8.2 本地部署 Jenkins

文章目录1. 简介2. 准备条件3. 安装依赖工具4. 配置 jenkins 源5. 安装 java 176. 安装 Jenkins7. 登陆8. 安装插件8.1 kubernets 插件8.2 git 插件8.3 docker 插件9. 创建 pipeline job9.1 加载本地 Jenkinsfile 构建9.2 git 构建10. 问题1. 简介 Jenkins 是一个 CI/CD 工具。…

Transformer是如何进军点云学习领域的?

点击进入—>3D视觉工坊学习交流群0.笔者个人体会&#xff1a;这个工作来自于牛津大学、香港大学、香港中文大学和Intel Labs&#xff0c;发表于ICCV2021。我们知道&#xff0c;Transformer在近两年来于各个领域内大放异彩。其最开始是自然语言处理领域的一个强有力的工具。后…

Unity 动画系统(Animation,Animator,Timeline)

文章目录1. Animation1.1 创建Animation1.2 Animation 属性2. Animator2.1 Animator 组件2.2 Animation 状态2.3 状态控制参数2.4 代码中控制状态3. 代码控制动画的播放/暂停/继续播放1. Animation 1.1 创建Animation 选中需要添加动画的物体&#xff0c;打开Animation面板 …

乡村科技杂志乡村科技杂志社乡村科技编辑部2022年第20期目录

三农资讯 科技特派员助力柘城县大豆玉米带状复合种植见成效 宋先锋;贾志远; 1《乡村科技》投稿&#xff1a;cnqikantg126.com 河南省科技特派员赴遂平县指导多花黑麦草防治 蒋洪杰;欧阳曦; 2 河南省肉牛产业科技特派员服务团到光山县开展技术培训服务 翟媛媛;朱燚波…

la3_系统调用(上)

1. 实验内容 理解操作系统接口&#xff1b;系统调用的实现&#xff1a; 应用程序 调用库函数 &#xff08;API&#xff09;API 将 系统调用号 放入 EAX 中&#xff0c; 然后通过中断调用 使系统进入内核态&#xff1b;内核中的中断处理函数 根据系统调用号&#xff0c; 调用对…

通过postgres_fdw实现跨库访问

瀚高数据库 目录 文档用途 详细信息 介绍Postgresql跨库访问中postgres_fdw的使用方法 详细信息 PostgreSQL 外部数据包装器&#xff0c;即 PostgreSQL Foreign Data Wrappers&#xff0c;是现实数据库使用场景中一个非常实用的功能&#xff0c;PostgreSQL 的 FDW 类似于 Ora…

2022年12月编程语言排行榜,数据来了!

2022年迎来了最后一个月&#xff0c;我们可以看到&#xff0c;在这一年中编程语言起起伏伏&#xff0c;有的语言始终炙手可热&#xff0c;而有的语言却逐渐“没落”… 日前&#xff0c;全球知名TIOBE编程语言社区发布了12月编程语言排行榜&#xff0c;有哪些新变化&#xff1f…

木聚糖-聚乙二醇-透明质酸,Hyaluronicacid-PEG-Xylan,透明质酸-PEG-木聚糖

木聚糖-聚乙二醇-透明质酸,Hyaluronicacid-PEG-Xylan,透明质酸-PEG-木聚糖 中文名称&#xff1a;木聚糖-透明质酸 英文名称&#xff1a;Xylan-Hyaluronicacid 别称&#xff1a;透明质酸修饰木聚糖&#xff0c;HA-木聚糖 存储条件&#xff1a;-20C&#xff0c;避光&#xff…

农产品商城毕业设计,农产品销售系统毕业设计,农产品电商毕业设计论文方案需求分析作品参考

项目背景和意义 目的&#xff1a;本课题主要目标是设计并能够实现一个基于web网页的多用户商城系统&#xff0c;整个网站项目使用了B/S架构&#xff0c;基于python的Django框架下开发&#xff1b;用户通过登录网站&#xff0c;查询商品&#xff0c;购买商品&#xff0c;下单&am…

奋勇拼搏绿茵场,永不言败足球魂——2022卡塔尔世界杯纪念

“我从来都不惧怕压力,老实说,我享受这种压力。”——C罗 第一部分&#xff1a;&#x1f1f6;&#x1f1e6;卡塔尔世界杯 2022年卡塔尔世界杯&#xff08;英语&#xff1a;FIFA World Cup Qatar 2022&#xff09;是第二十二届世界杯足球赛&#xff0c;是历史上首次在卡塔尔和中…

Apple官方优化Stable Diffusion绘画教程

Apple官方优化Stable Diffusion绘画教程 苹果为M1芯片优化Stable Diffusion模型&#xff0c;其中Mac Studio (M1 Ultra, 64-core GPU)生成512*512的图像时间为9秒。想要1秒出图&#xff0c;可以在线体验3090显卡AI绘画。 AI绘图在线体验 二次元绘图 在线体验地址:Stable Di…

AI模型神预测谁是卡塔尔世界杯冠军

推荐教程&#xff1a;AI模型神预测谁是冠军 2022年卡塔尔世界杯 猜猜他们是谁&#xff1f; 谁是最后的冠军&#xff1f; 2022年FIFA世界杯已经拉开帷幕&#xff0c;全世界的球迷都热切地想要知道&#xff1a;谁将获得那梦寐以求的 大力神杯&#xff1f; 2018年俄罗斯世界杯 方…

1,2-二苯基-1,2-二(4-羧基苯)乙烯 ;CAS: 1609575-40-7

英文名称&#xff1a; 4,4-(1,2-Diphenylethene-1,2-diyl)dibenzoic acid 中文名称&#xff1a; 1,2-二苯基-1,2-二(4-羧基苯)乙烯 MF&#xff1a; C28H20O4 MW&#xff1a; 420.46 CAS&#xff1a; 1609575-40-7 AIE聚集诱导发光材料的特点&#xff1a; 1.在固态下有强…