JavaEE进阶(6)SpringBoot 配置文件(作用、格式、properties配置文件说明、yml配置文件说明、验证码案例)

接上次博客:JavaEE进阶(5)Spring IoC&DI:入门、IoC介绍、IoC详解(两种主要IoC容器实现、IoC和DI对对象的管理、Bean存储、方法注解 @Bean)、DI详解:注入方式、总结-CSDN博客

目录

配置文件作用

SpringBoot配置文件

配置文件的格式

[properties 配置文件说明](#properties 配置文件说明)

[properties 基本语法](#properties 基本语法)

读取配置文件

[properties 缺点分析](#properties 缺点分析)

[yml 配置文件说明](#yml 配置文件说明)

[yml 基本语法](#yml 基本语法)

[yml 使用进阶](#yml 使用进阶)

[yml 配置不同数据类型及 null](#yml 配置不同数据类型及 null)

[yml 配置读取](#yml 配置读取)

配置对象

配置集合

配置Map

yml优缺点

综合性练习

验证码案例

[Kaptcha 插件介绍](#Kaptcha 插件介绍)

原理

引入依赖

生成验证码

添加配置项

Kaptcha详细配置

需求

准备工作

index.html:

success.html:

参考逻辑

约定前后端交互接口

实现服务器端代码

引入依赖

通过配置创建验证码生成器

验证码校验

调整前端页面代码

运行测试


配置文件作用

计算机系统中存在着大量的配置文件,虽然绝大多数用户不直接与这些文件交互,但它们在各种软件和应用中发挥着关键作用。无论是浏览器、微信、IDE(集成开发环境)还是智能手机,都依赖于配置文件的存在。这些文件以不同的形式分布在计算机上,例如在C:\Users、C:\Windows等文件夹下,以及各种 .config、.xml 文件。

配置文件主要是为了解决硬编码带来的问题, 把可能会发生改变的信息, 放在一个集中的地方, 当我们启动某个程序时,应用程序从配置⽂件中读取数据,并加载运行。

配置文件的作用和重要性:

解决硬编码问题: 主要作用之一是解决硬编码的问题。硬编码将数据直接嵌入到程序源代码中,导致数据固定且不易修改。通过配置文件,可以将可能发生变化的信息集中存储,使得修改配置更加方便,而不需要修改源代码。

硬编码(Hard Coding)是指直接将数据、配置或参数等值直接嵌入到程序或可执行对象的源代码中,而不是通过外部配置文件或其他可变的机制进行设置。这种做法使得这些值变得固定,不易修改,而且需要修改源代码才能改变这些值。

以手机字体大小为例,如果采用硬编码的方式,开发者会在程序源代码中明确指定字体大小的数值。这样,所有用户使用该应用时都将看到相同的字体大小,而无法根据个人偏好进行调整。如果不同用户有不同的偏好,他们将无法根据个人偏好调整字体大小。

集中管理配置信息: 许多软件和系统需要配置信息,例如路径、数据库连接、用户设置等。配置文件提供了一种集中管理这些信息的方式,使得修改配置信息更加方便,而不需要在整个代码库中查找和修改。

提高灵活性: 配置文件使得应用程序的行为能够在不修改程序代码的情况下进行调整。这使得软件更具灵活性,能够适应不同的使用场景和需求,而无需重新编译和部署。

适应不同环境: 不同的环境(开发、测试、生产等)可能需要不同的配置。通过使用不同的配置文件,可以轻松地适应不同的环境需求,而无需更改源代码。

用户和应用程序的交互: 配置文件为用户提供了调整应用程序行为的手段,例如更改界面样式、设置偏好等。用户可以通过修改配置文件而不是程序代码来个性化应用。

应用程序间的交互: 在分布式系统中,不同的应用程序可能需要相互通信和协作。通过配置文件,可以配置不同应用程序之间的交互规则和参数,实现更好的集成和协作。

简化部署和维护: 将配置信息存储在文件中使得部署和维护过程更加简化。不同的配置文件可以轻松地用于不同的部署环境,同时也方便备份和恢复。

综合而言,配置文件在软件开发和计算机系统中扮演了重要的角色,为软件提供了灵活性、可维护性和用户友好性。通过合理使用配置文件,可以使得应用程序更易于开发、维护和升级。

SpringBoot配置文件

SpringBoot提供了强大的配置文件支持,允许开发人员灵活地配置和管理应用程序的各种参数。配置文件的格式得到了SpringBoot的支持和定义,这不仅有助于规范化项目的配置,同时也为其他框架集成到SpringBoot提供了一种便捷的方式。

在配置文件中,可以设置许多项目或框架的关键信息,包括但不限于:

  • 项目的启动端口: 开发人员可以轻松地指定应用程序监听的端口,确保应用在启动时使用正确的端口进行通信。

  • 数据库的连接信息: 数据库是许多应用程序的核心组成部分,而数据库连接信息通常包括用户名和密码等敏感信息。通过配置文件,这些信息可以被安全地存储和管理。

  • 第三方系统的调用密钥: 集成到其他系统或服务时,经常需要提供访问权限验证的密钥。将这些密钥放在配置文件中,有助于在应用程序中轻松管理这些敏感信息。

  • 用于发现和定位问题的日志信息: 配置文件中可以定义各种日志参数,包括常规日志和异常日志。这些日志对于开发人员在调试和解决问题时提供了宝贵的信息。

通过使用SpringBoot的配置文件,开发人员可以集中管理应用程序的配置,而不必硬编码这些参数,从而增加了灵活性。这种方式还使得配置信息可以根据不同环境(如开发、测试、生产)进行调整,而不必修改源代码。总体而言,SpringBoot的配置文件功能为开发人员提供了更方便、可维护性更强的配置管理方式。

在Spring框架中,Spring Boot的配置文件主要是通过application.properties(或者application.yml)来定义项目的配置信息。我们其实已经见过一些常见配置项:

项目的启动端口

Spring Boot内置了Tomcat服务器,默认端口号是8080。但由于电脑上8080端口可能被其他应用程序占用,因此Spring Boot允许用户自定义端口号。通过在application.properties文件中设置如下配置,可以指定项目的启动端口:

java 复制代码
server.port=自定义端口号

这样,应用程序将使用指定的端口号进行监听,确保不与其他应用程序发生冲突。

数据库连接信息

为了更方便简单地访问数据库,出现了一些持久层框架,它们对JDBC进行了更深层次的封装。这些框架允许用户通过简短的代码实现数据库访问。然而,不同的应用程序通常需要访问不同的数据库,因此这些持久层框架需要支持用户自定义配置数据库的连接信息。

application.properties文件中,可以设置数据库连接的相关信息,例如:

java 复制代码
spring.datasource.url=jdbc:mysql://localhost:3306/数据库名
spring.datasource.username=数据库用户名
spring.datasource.password=数据库密码
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

这样,开发人员可以轻松地配置应用程序连接到特定数据库的详细信息,而不必在代码中硬编码这些参数。

总体而言,通过application.properties文件,Spring Boot提供了一个集中管理和灵活配置项目的途径,使得我们能够轻松地自定义端口号和数据库连接信息,以满足不同项目的需求。

配置文件的格式

在Spring Boot中,配置文件是一种关键的组件,用于定义应用程序的各种属性和设置。

这些配置文件主要有三种格式:

  • application.properties
  • application.yml
  • application.yaml

其中,yml是yaml的缩写,而在实际开发中,yml格式的配置文件的使用频率最高。需要注意的是,yaml和yml在使用方式上是相同的,但在文件扩展名上有所不同,而我们主要介绍yml文件的使用。

当Spring Boot应用程序启动时,它会自动寻找并加载位于classpath路径下的application.properties、application.yaml或者application.yml文件。这样的自动加载机制使得配置文件的管理变得非常方便,同时也为我们提供了更灵活的配置选项。

除了默认的加载路径外,还可以通过使用spring.config.name属性来指定配置文件的路径和名称。这为项目中的多个配置文件提供了支持,使得我们可以根据环境或其他条件选择合适的配置文件。例如,我们可以通过spring.config.name=custom-config来指定加载名为custom-config.yml或custom-config.properties的配置文件。具体参考官方文档:Core Features (spring.io)

以下是一个示例application.yml文件,展示了如何配置一些常见的应用属性:

java 复制代码
# application.yml

server:
  port: 8080

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydatabase
    username: root
    password: password
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true

myapp:
  custom-property: custom-value

在这个例子中,我们通过server配置了服务器的端口,使用了spring配置来定义数据库连接信息,而myapp下则是自定义的应用属性。通过这种方式,就可以灵活地管理和配置Spring Boot应用程序的各种参数和设置。

我们把 application.properties里面的代码注释掉,然后:

类比商品包装,Spring Boot的配置文件可以看作是两种不同的包装:properties 类型的配置文件就像是"老款包装",而yml 则是"新版包装"。默认情况下,在创建Spring Boot项目时,会采用 properties 格式的配置文件,这主要是为了兼容性和传统。这种选择的原因可能是因为在仓库中还有大量使用老款包装的库存,因此作为默认。

然而,yml格式的配置文件被认为是"新版包装",更加现代且易读。如果用户对情况比较了解,并希望使用更新的配置文件格式,可以直接选择使用yml。这就好像用户可以选择购买商品时,如果了解情况并喜欢新版包装,那么商家就直接提供新版包装的产品。

因此,当用户在创建Spring Boot项目时,如果对配置文件格式有特定需求,可以直接指定要使用的包装,即选择 properties 或 yml,就像在购物时选择商品包装一样。这样,用户可以更加灵活地根据个人偏好或项目需求选择适当的配置文件格式。

那么如果我们让两个文件并存,哪个更优先呢?

特殊说明:

  • 理论上,.properties 和 .yml 可以同时存在于一个项目中。当这两种配置文件并存时,两个配置文件都会被加载。然而,如果配置文件内容存在冲突,那么以 .properties 为主,也就是说,.properties 的优先级更高。这意味着在存在冲突的情况下,.properties 文件中的配置项会覆盖相同配置项在 .yml 文件中的设置。
  • 同一个配置项在两个配置文件都存在的情况下,.properties 的优先级高于 .yml 文件;不同的配置项,在.properties 和 .yml 文件中配置都会生效。
  • 虽然理论上可以让 .properties 和 .yml 共存,但在实际的业务中,通常会选择一种统一的配置文件格式。这样的做法有助于更好地维护项目,降低故障率。通过采用一致的配置文件格式,开发团队能够更容易理解和管理配置,减少因格式差异而引起的问题。因此,为了项目的一致性和维护性,通常会选择在一个项目中使用一种主要的配置文件格式。

properties 配置文件说明

properties 配置文件是最早期的配置文件格式,也是创建 Spring Boot 项目时的默认配置文件。这种格式以键值对的形式存储配置信息,每一行表示一个属性的设置。由于其简单直观的语法,properties 配置文件在项目早期得到了广泛应用。

在 Spring Boot 的早期版本中,使用 properties 格式的配置文件是主流,这也符合许多传统 Java 项目的配置需求。然而,随着时间的推移和开发者对更灵活、易读的配置的需求增加,新一代的配置文件格式逐渐崭露头角,其中以 yml 格式为代表。

尽管 properties 配置文件在默认设置中仍然保留,但随着 Spring Boot 的演进,更多项目和开发者转向使用 yml 格式的配置文件。这种趋势主要因为 yml 具有更为结构化和可读性强的语法,使得配置信息更清晰、易维护,从而提高了开发效率。不过,properties 仍然在一些场景中得到应用,特别是在传统项目或与其他系统集成时。

properties 基本语法

properties 配置文件的基本语法是采用键值对的形式,其中键和值之间用等号 = 连接。以下是一个简单的示例,展示了如何使用 properties 文件配置一些常见的项:

java 复制代码
# 配置项目端口号
server.port=8080

# 配置数据库连接信息
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/testdb?characterEncoding=utf8&
spring.datasource.username=root
spring.datasource.password=root

在这个示例中:

  • server.port=8080 表示配置了应用程序的端口号为 8080。
  • spring.datasource.url=jdbc:mysql://127.0.0.1:3306/testdb?characterEncoding=utf8& 配置了数据库连接的URL。
  • spring.datasource.username=root 和 spring.datasource.password=root 配置了数据库的用户名和密码。

此外,可以使用 # 符号在配置文件中添加注释信息,这对于提供配置项的说明或者添加一些备注非常有用。注释部分对应于配置文件中的说明而不会影响实际的配置。比如上述示例中的注释用于解释每个配置项的用途。

你也可以自定义配置:

我们现在先学习语法和具体的使用,更多配置信息会随着深入学习进行补充。如果你很感兴趣,可以参考:Common Application Properties (spring.io)

这些全是一些默认配置:

你可能会很头大,这要怎么学......?

不用学,用的时候现去查就行,而且与其在官方文档查,还不如直接百度。

我整理了几个application.yml文件的常用的配置项,可以直接复制粘贴:

java 复制代码
# 数据源配置
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mycnblog?characterEncoding=utf8&useSSL=false
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

# Spring MVC 配置
spring:
  mvc:
    favicon:
      enable: false

# 多平台配置
spring:
  profiles:
    active: dev

# Mybatis 配置
mybatis:
  # 设置 Mybatis 的 XML 文件保存路径
  mapper-locations: classpath:mapper/*Mapper.xml
  
  # Mybatis 配置
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true  # 自动将数据库字段转为驼峰命名

# 日志配置
logging:
  file:
    name: logs/springboot.log  # 指定日志文件位置
  logback:
    rollingpolicy:
      max-file-size: 1KB  # 设置日志文件大小
      file-name-pattern: ${LOG_FILE}.%d{yyyy-MM-dd}.%i  # 指定日志文件名格式
  level:
    com:
      example:
        demo: debug  # 设置特定包的日志级别为debug

上述配置包含了常见的数据源配置、Spring MVC 配置、多平台配置、MyBatis 配置以及日志配置。注释部分提供了对各个配置项的简要解释。这样的配置文件已经可以满足常见的Spring Boot项目的需求,同时你也可以根据实际情况进行调整和扩展。

读取配置文件

在Spring Boot项目中,可以使用@Value注解来主动读取配置文件中的内容。这个注解的使用方式是在类的字段上添加注解,并使用${}的格式来引用配置文件中的属性值。

我们先创建一个新的Controller:

启动之后去网页看看:

现在我们通过@Value注解来拿key1的值:

再多增加一个:

如果把 ${ } 去掉就代表赋值:

它同时也是有一些校验的,比如现在把key3改成字符串:

在运行就不对了:

properties 缺点分析

properties 配置文件以 key-value 的形式存储配置信息,具有简单直观的语法。然而,随着项目的复杂性增加,properties 配置文件也会暴露一些缺点,其中一些包括:

  1. **缺乏层次结构:**properties 配置文件本身不支持层次结构,所有配置项都是扁平的。这意味着在配置复杂的对象结构时,需要使用复杂的命名规则来模拟层次结构,导致配置文件变得冗长和难以维护。

    java 复制代码
    person.name=John Doe
    person.age=30
    person.address.city=New York
    person.address.zip=10001
  2. 冗余的信息: properties 配置文件中可能包含大量重复的信息,尤其是在键的前缀相同的情况下。这可能导致配置文件变得臃肿,并且当需要修改一组相关的配置项时,可能需要在多个地方进行调整。

  3. 可读性差: 随着配置项的增多,properties 文件可能变得难以阅读和理解。复杂的结构和大量的键值对可能使得配置文件的维护变得繁琐。

相对于 properties ,yml 配置文件提供了更具层次结构和可读性的格式。通过使用缩进和冒号的方式,yml 允许在配置文件中创建更复杂的数据结构,减少了冗余的信息,并提高了可读性。因此,在需要处理复杂配置结构和提高配置文件可读性的情况下,yml 格式通常更受开发者欢迎。

yml 配置文件说明

yml 是 YAML 是缩写,YAML(YAML 的原意是 "YAML Ain't Markup Language",这是一种自指的递归缩写。尽管 "Yet Another Markup Language" 也是 YAML 的一种解释,翻译成中文就是"另一种标记语言",但官方的定义是 "YAML Ain't Markup Language"。这个缩写旨在表达 YAML 不是一种传统的标记语言(markup language),而是一种数据序列化格式,更注重数据的表达和易读性。强调 YAML 的设计目标和特性。)是一种人类可读的数据序列化格式,常被用于配置文件和数据交换的场景。

YAML 的跨平台性体现在它的语法规则和数据结构的表达方式上。YAML 的语法是清晰、简洁、易读的,且不依赖于特定编程语言,因此可以在不同的编程语言和平台之间进行交换和共享。

在实践中,许多编程语言都有对 YAML 格式的解析和生成支持,使得开发者可以在不同的平台上使用 YAML 文件进行配置。这种灵活性和跨平台性使 YAML 成为许多项目中配置文件的首选格式。

我们先来学习yml文件的基本语法和说明:

yml 基本语法

yml 是树形结构的配置文件,它的基础语法是"key: value",key 和 value 之间使用英文冒号加空格的方式组成,空格不可省略!!!

如果一个键值对中的值包含多个单词,通过换行的缩进表示,这种方式通常用于表示复杂的数据结构或长的文本块:

java 复制代码
description: |
  This is a multiline
  description that spans
  multiple lines in YAML.
  1. 基本结构: yml使用缩进来表示结构,而不是像其他语言一样使用大括号。缩进的空格数目是有意义的,通常是两个空格。

    java 复制代码
    key1: value1
    key2:
      subkey1: subvalue1
      subkey2: subvalue2
  2. 键值对: 键值对使用冒号 : 分隔,表示键和值的关系。

    java 复制代码
    name: John Doe
    age: 30
  3. 列表: 使用连字符 - 表示列表中的每个元素。

    java 复制代码
    fruits:
      - apple
      - orange
      - banana
  4. 多行文本: 使用 | 符号表示多行文本块,保留换行符。

    java 复制代码
    description: |
      This is a multiline
      text block.
  5. 注释: 使用 # 符号表示注释。

    java 复制代码
    # This is a comment
    key: value
  6. 引用: 使用 & 符号创建锚点(anchor),使用 * 符号引用锚点。

    java 复制代码
    defaults: &defaults
      username: guest
      password: guest
    
    user1: *defaults
    user2:
      <<: *defaults
      password: secure_password

这些是.yml文件的基本语法。YAML以人类可读的方式表示数据结构,其简洁性和可读性使其成为配置文件和数据交换的常用格式。在Spring Boot项目中,.yml文件通常用于配置应用程序的属性,提供了一种更易读、更清晰的配置方式。

使用.yml连接数据库:

java 复制代码
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/dbname?characterEncoding=utf8&useSSL=false
    username: root
    password: root

使用.properties连接数据库:

java 复制代码
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/dbname?characterEncoding=utf8&useSSL=false
spring.datasource.username=root
spring.datasource.password=root

对比来看,在这两个例子中,配置的含义是相同的,都是配置了Spring Boot应用程序连接到MySQL数据库的相关信息。主要的区别在于语法格式:

  • .yml文件: 使用缩进和冒号来表示层次结构和键值对。
  • .properties文件: 使用等号来表示键值对,每个配置项在文件中占据一行。

在实际使用中,选择.yml还是.properties通常取决于个人或团队的偏好,以及项目的具体需求。

.yml文件相对更加易读和清晰,特别适合配置文件较为复杂的情况。.properties文件则更加传统,适用于简单的配置需求。

yml 使用进阶

yml 配置不同数据类型及 null

java 复制代码
# 字符串
string:
  value: Hello

# 布尔值,true或false
boolean:
  value: true
  value1: false

# 整数
int:
  value: 10

# 浮点数
float:
  value: 3.14159

# Null,~代表null
null:
  value: ~

# 空字符串
# 直接后面什么都不加就可以了, 但这种方式不直观, 更多的表示是使用引号括起来
empty:
  value: ''

yml 配置读取

yml 读取配置的方式和 properties 相同,使用 @Value 注解即可:

所以你会发现,把.properties中的" . "改成换行缩进就是.yml文件格式。

注意事项:value 值加单双引号

在 YAML 文件中,对字符串的表示有一些注意事项,尤其涉及到单引号和双引号。

字符串默认不用加上单引号或者双引号,如果加英文的单双引号可以表示特殊的含义。

尝试在 application.yml 中配置如下信息:

java 复制代码
string:
 str1: Hello \n Spring Boot.
 str2: 'Hello \n Spring Boot.'
 str3: "Hello \n Spring Boot."

从上述实例可以得出以下结论:

  1. 字符串默认不需要加上单引号或者双引号: 在 str1 中,字符串 "Hello \n Spring Boot." 没有加上任何引号,它会被解释为一个普通字符串,特殊字符 \n 会被当作两个字符。

  2. 单引号会转义特殊字符: 在 str2 中,字符串被单引号包裹,导致 \n 失去特殊功能,它会被解释为普通字符串中的两个字符,而不是换行符。

  3. 双引号不会转义字符串里面的特殊字符: 在 str3 中,字符串被双引号包裹,特殊字符 \n 会被保留其本身的含义,即表示换行符。

注意这里的描述,可能我们的第一反应相反。

  • 此处的转义理解起来会有些拗口,\n 本意表示的是换行。
  • 使用单引号会转义,就是说,\n 不再表示换行了,而是表示一个普通的字符串。
  • 使用双引号不会转义,\n 表示的是它本身的含义,就是换行。

配置对象

我们还可以在 yml 中配置对象,如下配置:

或者是使用行内写法(与上面的写法作用⼀致):

这个时候就不能用 @Value 来读取配置中的对象了,此时要使用另⼀个注解 @ConfigurationProperties 来读取,具体实现如下:

注意要加上@Component: 如果希望Student类成为Spring容器的一个Bean,通常需要在这个类上添加@Component注解,以便Spring能够扫描并管理这个Bean。

调用类的实现如下:

配置集合

配置⽂件也可以配置 list 集合,如下所示:

注意缩进!!!

java 复制代码
Dbtypes:
  name:
    - mysql
    - sqlserver
    - db2

集合的读取和对象⼀样,也是使用 @ConfigurationProperties 来读取的,具体实现如下:

访问集合的实现如下:

空格绝对不可以省略!否则此处将被当作一个对象!

省略空格后没有报错,但是含义完全改变!

配置Map

配置文件也可以配置 map,如下所示:

或者是使用行内写法(与上面的写法作用⼀致,也要空格):

Map的读取和对象一样,也是使⽤ @ConfigurationProperties 来读取的,具体实现如下:

打印类的实现如下:

yml优缺点

优点:

  1. 可读性高,写法简单,易于理解: YAML 使用缩进和简洁的语法结构,使文件更加易读,不需要像 XML 或 JSON 那样使用大量的符号和标记。

  2. 支持更多的数据类型: YAML 支持丰富的数据类型,包括对象、数组、List、Map 等,使其能够简单表达各种数据形态,适用于多样化的应用场景。

  3. 跨编程语言支持: YAML 不仅在 Java 中得到广泛应用,还能够在其他编程语言中使用,如 Golang、Python、Ruby、JavaScript 等,提供了更大的灵活性和通用性。

缺点:

  1. 不适合写复杂的配置文件: 尽管 YAML 对于简单的配置文件表达十分方便,但对于复杂配置文件的书写可能变得困难。与 properties 格式相比,复杂配置的转换过程可能会花费更多精力,可读性也会下降。

    例如,对于一份复杂的配置,YAML 的可读性较差:

    java 复制代码
    keycloak:
      realm: demo
      resource: fm-cache-cloud
      credentials:
        secret: d4589683-Oce7-4982-bcd3
      security:
        - authRoles:
          - user
        collections:
          - name: ssologinurl
            patterns:
              - /login/*

    而properties格式如下:

    java 复制代码
    keycloak.realm = demo
    keycloak.resource = fm-cache-cloud
    keycloak.credentials.secret = d4589683-Oce7-4982-bcd3
    keycloak.security[0].authRoles[0]= user
    keycloak.security[0].collections[0].name = ssologinurl
    keycloak.security[0].collections[0].patterns[0] = /login/*
  2. 对格式有较强的要求: YAML 对缩进和格式有较强的要求,一个空格的差异可能导致解析错误。这可能使得在编辑或处理 YAML 文件时更加容易出错,需要维护者保持良好的格式规范。

总的来说,YAML 适用于简单和中等复杂度的配置文件,但在处理高度复杂的配置时,可能会显得繁琐并降低可读性。在选择配置文件格式时,需要根据具体的应用场景和需求权衡其优缺点。

综合性练习

验证码案例

随着对安全性要求的日益提升,目前许多项目都广泛采用验证码作为一种重要的安全验证手段。验证码的形式多种多样,其中更为复杂的图形验证码和行为验证码已经成为当前的主流趋势。随着技术的不断发展,这些高级验证码不仅提升了安全性,还为用户提供了更加可靠和有效的身份验证方式。

验证码的实现方式很多, 网上也有比较多的插件或者工具包可以使用,咱们选择使用Google的开源项目 Kaptcha来实现。

Kaptcha 插件介绍

Kaptcha是由Google推出的一款高度可配置的实用验证码生成工具。该工具以其灵活性和高度定制化而著称,为开发者提供了生成验证码的便捷解决方案。通过Kaptcha,用户可以根据具体需求调整验证码的各种参数,包括但不限于验证码的外观、复杂性和大小等。这使得Kaptcha成为许多项目中首选的验证码生成工具之一,为用户提供了一种可靠而安全的身份验证机制。

代码:Google Code Archive - Long-term storage for Google Code Project Hosting.

网上有很多人甚至公司都基于Google的kaptcha进行了二次开发。

我们选择⼀个直接适配SpringBoot的 开源项目。

GitHub - oopsguy/kaptcha-spring-boot: Kaptcha Spring Boot Starter help you use Google Kaptcha with Spring Boot easier. 一个简单封装了 Kaptcha 验证码库的 Spring Boot Starter

但是这篇文章比较粗糙......所以,我们下面先简单解释一下插件的使用,然后再进行一个验证码程序的详细编写过程。

原理

验证码的生成可以在客户端进行,也可以在服务器端进行。

对于普通的字符验证码,后端通常分两个主要步骤。首先,生成验证码的内容,根据验证码内容以及干扰项等因素,生成相应的图像,并将图像返回给客户端。其次,将验证码的内容存储起来,以便在校验时取出进行比对。

**在这个流程中,kaptcha插件采取了将验证码内容存储在Session中的策略。**这意味着生成的验证码内容会被存储在服务器端的Session对象中,以确保安全性和一致性。在校验时,系统会从Session中取出相应的验证码内容,然后与用户输入的验证码进行比对,以完成验证过程。这种方法有效地维护了验证码的状态和安全性,为用户提供了可靠的身份验证机制。

引入依赖
java 复制代码
<dependency>
 <groupId>com.oopsguy.kaptcha</groupId>
 <artifactId>kaptcha-spring-boot-starter</artifactId>
 <version>1.0.0-beta-2</version>
</dependency>
生成验证码

该插件提供了两种方式生成验证码 :

1、通过代码来生成:参考文档:kaptcha-spring-boot/README_zh-CN.md at master · oopsguy/kaptcha-spring-boot · GitHub

2、仅通过配置文件来生成验证码(推荐)

我们接下来介绍的方法就是通过配置文件来生成验证码:

添加配置项
java 复制代码
# 应用服务 WEB 访问端口
server:
  port: 8080

kaptcha:
  items:
    # home captcha
    home:
      path: /home/captcha
      session:
        key: HOME_KAPTCHA_SESSION_KEY
        date: HOME_KAPTCHA_SESSION_DATE
    # admin captcha
    admin:
      path: /admin/captcha
      session:
        key: ADMIN_KAPTCHA_SESSION_KEY
        date: ADMIN_KAPTCHA_SESSION_DATE

配置完之后就可以运行一下看看:

访问一下这个URL:

可以发现,验证码已经出来了,而且随着我们的刷新会进行更改。

看起来好像很神奇,我们好像没有做什么工作,但是验证码就已经出现了。

好吧,总有人替你负重前行。

我们导入的依赖里面的jar包中有人帮你完成了代码编写的过程:

感兴趣的可以看看源码,代码量其实不多:

1、最开始的是一个名为KaptchaConst的Java接口,定义了一个常量AUTO_CONFIG_PREFIX,其值为字符串"kaptcha"。这样的接口通常用于存放项目中使用的常量,以提高代码的可维护性和可读性。在这里,KaptchaConst接口定义了一个用于自动配置的前缀常量,该前缀用于处理Kaptcha验证码生成库的自动配置属性。在其他部分的代码中,可以通过引用KaptchaConst.AUTO_CONFIG_PREFIX来获取这个前缀,以确保一致性和避免硬编码。

2、ConfigUtils类的目的是提供一组方法,将Kaptcha的配置信息转换为Properties对象,使得配置信息更易于处理和传递。这对于在应用程序中动态配置Kaptcha生成库的行为非常有用。

3、接下来是一个名为BaseProperties的抽象类,用于定义Kaptcha验证码生成库的基本属性。该类包含了多个内部静态类,每个静态类表示不同的配置项,形成了一个层次结构。以下是主要的配置项和它们的含义:

  1. Border: 边框配置项,包括是否启用边框(enabled)、边框颜色(color)、边框厚度(thickness)。
  2. Noise: 噪点配置项,包括噪点颜色(color)和噪点实现类(impl)。
  3. Obscurificator: 扭曲器配置项,包括扭曲器实现类(impl)。
  4. Producer: 生产器配置项,包括生产器实现类(impl)。
  5. TextProducer: 文本生成器配置项,包括文本生成器实现类(impl)、字符配置项(character)和字体配置项(font)。
  6. Background: 背景配置项,包括背景实现类(impl)、背景颜色起始值(colorFrom)和背景颜色结束值(colorTo)。
  7. Word: 单词配置项,包括单词实现类(impl)。
  8. Image: 图像配置项,包括图像宽度(width)和图像高度(height)。

每个配置项都有相应的Getter和Setter方法,用于获取和设置其属性值。这种结构使得可以灵活配置Kaptcha验证码生成库的各个方面,使其适应不同的需求和场景。BaseProperties类的实例通常作为其他类的属性,例如KaptchaProperties和ConfigUtils中的属性。

4、KaptchaAutoConfigure类没有直接继承BaseProperties类。相反,它使用了KaptchaProperties类,并通过@EnableConfigurationProperties({KaptchaProperties.class})注解启用了对KaptchaProperties类的配置属性支持。这意味着KaptchaProperties类中的属性将会被映射到KaptchaAutoConfigure中,以便在自动配置过程中使用。

虽然KaptchaAutoConfigure类没有直接继承BaseProperties类,但它通过引用KaptchaProperties类间接地使用了BaseProperties类中定义的属性,因为KaptchaProperties类继承了BaseProperties类。

KaptchaAutoConfigure类是一个Spring Boot自动配置类,用于配置和初始化Kaptcha验证码生成库的相关组件。

  1. @Configuration: 表示这是一个配置类。
  2. @EnableConfigurationProperties({KaptchaProperties.class}): 启用对KaptchaProperties类的配置属性支持,使得可以在配置文件中配置Kaptcha的属性。
  3. @Bean(name = {"kaptchaProps"}): 定义了名为kaptchaProps的Bean,该Bean用于将KaptchaProperties对象转换为Properties对象,以便后续使用。
  4. @Bean: 定义了名为defaultKaptcha的Bean,该Bean用于创建并配置DefaultKaptcha对象,作为Kaptcha验证码生成的默认实现。这里使用了ConfigUtils类中的方法将Properties对象应用到DefaultKaptcha中。
  5. @ConditionalOnMissingBean({Producer.class}): 仅当不存在名为Producer的Bean时,才创建defaultKaptcha Bean。这意味着如果应用程序已经定义了自己的Producer Bean,则不会覆盖它。
  6. @DependsOn({"kaptchaProps"}): 表示defaultKaptcha Bean依赖于kaptchaProps Bean。
  7. @Bean: 定义了一个名为webConfig的Bean,该Bean是ServletContextInitializer的实例,用于在Web应用程序启动时注册Kaptcha的Servlet。这里使用了ServletRegisterInitializer类。

总体而言,这个自动配置类负责将Kaptcha集成到Spring Boot应用程序中。它提供了默认的Kaptcha实现(DefaultKaptcha),并允许用户在配置文件中灵活地配置Kaptcha的各种属性。如果用户已经定义了自己的Producer Bean,则默认的Kaptcha实现不会覆盖用户的定义。

5、然后是刚刚提到的继承自BaseProperties的KaptchaProperties类,它用于配置Kaptcha验证码生成库的属性,逐步解释一下代码的结构和含义:

  1. @ConfigurationProperties(prefix = "kaptcha"): 这是一个Spring Boot注解,表明该类用于处理以"kaptcha"为前缀的配置属性。
  2. public class KaptchaProperties extends BaseProperties: KaptchaProperties类扩展了BaseProperties类,说明它继承了一些基本的属性。
  3. private Map<String, SingleKaptchaProperties> items = new HashMap();: 这里定义了一个Map类型的属性items,用于存储不同类型的Kaptcha配置。每个配置由一个字符串键(key)和一个SingleKaptchaProperties对象值组成。
  4. public Map<String, SingleKaptchaProperties> getItems(): 提供了获取items属性的方法。
  5. public void setItems(Map<String, SingleKaptchaProperties> items): 提供了设置items属性的方法。
  6. public static class SingleKaptchaProperties extends BaseProperties: 定义了一个静态内部类SingleKaptchaProperties,该类也扩展了BaseProperties类。
  7. private Session session = new Session();: 在SingleKaptchaProperties类中定义了一个Session类型的属性session,并初始化为new Session()。
  8. private String path;: 定义了一个字符串类型的属性path。
  9. 对于Session类:
    private String key;: 定义了一个字符串类型的属性key。
    private String date;: 定义了一个字符串类型的属性date。
  10. 提供了相应的Getter和Setter方法,用于获取和设置各个属性的值。

这个配置类的目的是为Kaptcha验证码生成库提供灵活的配置选项,支持多种配置情况。其中,KaptchaProperties类包含了一个Map,每个条目对应一个特定类型的Kaptcha配置,而SingleKaptchaProperties类则包含了该类型的详细配置信息,包括Session类中的属性。

6、最后的一个类是一个ServletContextInitializer的实现类,名为ServletRegisterInitializer。它的主要目的是在Servlet容器启动时,向ServletContext注册Kaptcha验证码生成的Servlet。

让我解释一下该类的主要结构和功能:

  1. private static final String KAPTCHA_SERVLET_BEAN_NAME_SUBFFIX = "KapthcaServlet": 定义了一个常量,用于生成Kaptcha验证码Servlet的Bean名称后缀。

  2. @Resource: 注解用于注入依赖。

    • private KaptchaProperties kaptchaProperties: 注入KaptchaProperties对象,用于获取Kaptcha的配置信息。

    • @Resource(name = "kaptchaProps"): 注入名为kaptchaProps的Properties对象,该对象通过ConfigUtils类将KaptchaProperties转换而来。

  3. public void onStartup(ServletContext servletContext) throws ServletException: 实现了ServletContextInitializer接口的方法,在Servlet容器启动时执行。该方法的主要逻辑如下:

    • 获取Kaptcha的不同配置项(SingleKaptchaProperties)。

    • 遍历配置项,为每个配置项创建相应的Kaptcha验证码Servlet,并将其注册到ServletContext中。

    • 使用addServlet方法注册Servlet,其中Servlet的名称由配置项的键和常量后缀拼接而成。

    • 使用addMapping方法为Servlet指定映射路径,路径由配置项的path属性决定。

    • 通过setInitParameter方法设置Servlet的初始化参数,这些参数来自于kaptchaProps和subProps,其中subProps是通过ConfigUtils类生成的。

    • 注册完成后,每个Kaptcha配置项对应的Servlet就可以在指定的路径上响应请求了。

总体而言,ServletRegisterInitializer类负责在Servlet容器启动时注册Kaptcha验证码生成的Servlet,并根据配置项提供灵活的配置选项。

Kaptcha详细配置

这里提供了每个配置项的说明和默认值:

配置项 配置说明 默认值
kaptcha.border 图片边框,合法值:yes,no yes
kaptcha.border.color 边框颜色,合法值:r,g,b (and optional alpha) 或者 white, black, blue black
kaptcha.image.width 图片宽度 200
kaptcha.image.height 图片高度 50
kaptcha.producer.impl 图片实现类 com.google.code.kaptcha.impl.DefaultKaptcha
kaptcha.textproducer.impl 文本实现类 com.google.code.kaptcha.text.impl.DefaultTextCreator
kaptcha.textproducer.char.string 文本集合,验证码值从此集合中获取 abcde2345678gfynmnpwx
kaptcha.textproducer.char.length 验证码长度 5
kaptcha.textproducer.font.names 字体 Arial, Courier
kaptcha.textproducer.font.size 字体大小 40px
kaptcha.textproducer.font.color 字体颜色,合法值:r,g,b 或者 white, black, blue black
kaptcha.textproducer.char.space 文字间隔 2
kaptcha.noise.impl 干扰实现类 com.google.code.kaptcha.impl.DefaultNoise
kaptcha.noise.color 干扰颜色,合法值:r,g,b 或者 white, black, blue black
kaptcha.obscurificator.impl 图片样式 com.google.code.kaptcha.impl.WaterRipple, com.google.code.kaptcha.impl.FishEyeGimpy, com.google.code.kaptcha.impl.ShadowGimpy
kaptcha.background.impl 背景实现类 com.google.code.kaptcha.impl.DefaultBackground
kaptcha.background.clear.from 背景颜色渐变,开始颜色 light grey
kaptcha.background.clear.to 背景颜色渐变,结束颜色 white
kaptcha.word.impl 文字渲染器 com.google.code.kaptcha.text.impl.DefaultWordRenderer
kaptcha.session.key Session Key KAPTCHA_SESSION_KEY
kaptcha.session.date Session Date KAPTCHA_SESSION_DATE

但是这个配置具体还是以代码为准。

我们刚刚使用的就是 kaptcha.items 配置多个验证码生成器。

kaptcha.items 是一个包含验证码生成器配置信息的Map。在这个Map中,每个key代表一个特定的验证码生成器的名称,而对应的value则包括了该生成器的详细配置。这些配置可能涉及验证码的长度、字符集、字体样式、噪点类型等多个参数,以确保生成的验证码符合特定的需求和标准。通过这样的映射关系,系统能够灵活地选择和使用不同的验证码生成器,并根据具体的场景需求进行定制化配置,以提供更加安全和个性化的验证码生成服务。这种模块化的设计使得系统在验证码生成方面具有较高的可扩展性和定制性,适应不同应用场景的需求。

如上,我们配置了两个验证码,这两个验证码都是可以应用的。

当然,你也可以配置多个验证码。

为了使用 kaptcha.items 配置多个验证码生成器,你可以按照以下方式扩展和完善配置。

在这里,我仍然以两个验证码生成器("home" 和 "admin")为例,提供详细的配置说明:

java 复制代码
kaptcha:
  items:
    # Configuration for home captcha generator
    home:
      path: /home/captcha            # URL路径
      session:
        key: HOME_KAPTCHA_SESSION_KEY   # Session中验证码的键
        date: HOME_KAPTCHA_SESSION_DATE # Session中验证码生成时间的键
      producer:
        impl: com.google.code.kaptcha.impl.DefaultKaptcha  # 图片生成器实现类
        width: 200                  # 图片宽度
        height: 50                  # 图片高度
        textproducer:
          impl: com.google.code.kaptcha.text.impl.DefaultTextCreator  # 文本生成器实现类
          char.string: abcde2345678gfynmnpwx      # 文本集合
          char.length: 5             # 验证码长度
          font.names: Arial, Courier  # 字体
          font.size: 40               # 字体大小
          font.color: black           # 字体颜色
          char.space: 2               # 文字间隔
        noise:
          impl: com.google.code.kaptcha.impl.DefaultNoise  # 干扰实现类
          color: black                # 干扰颜色
        obscurificator:
          impl: com.google.code.kaptcha.impl.WaterRipple  # 图片样式
        background:
          impl: com.google.code.kaptcha.impl.DefaultBackground  # 背景实现类
          clear:
            from: light grey          # 背景颜色渐变开始颜色
            to: white                  # 背景颜色渐变结束颜色
        word:
          impl: com.google.code.kaptcha.text.impl.DefaultWordRenderer  # 文字渲染器

    # Configuration for admin captcha generator
    admin:
      path: /admin/captcha
      session:
        key: ADMIN_KAPTCHA_SESSION_KEY
        date: ADMIN_KAPTCHA_SESSION_DATE
      producer:
        impl: com.google.code.kaptcha.impl.DefaultKaptcha
        width: 200
        height: 50
        textproducer:
          impl: com.google.code.kaptcha.text.impl.DefaultTextCreator
          char.string: abcde2345678gfynmnpwx
          char.length: 5
          font.names: Arial, Courier
          font.size: 40
          font.color: black
          char.space: 2
        noise:
          impl: com.google.code.kaptcha.impl.DefaultNoise
          color: black
        obscurificator:
          impl: com.google.code.kaptcha.impl.WaterRipple
        background:
          impl: com.google.code.kaptcha.impl.DefaultBackground
          clear:
            from: light grey
            to: white
        word:
          impl: com.google.code.kaptcha.text.impl.DefaultWordRenderer

学习完成之后我们就可以来做一个关于验证码的小项目了。

需求

界面如下图所示:

  1. 页面生成验证码;
  2. 输入验证码,点击提交,验证用户输入验证码是否正确,正确则进行页面跳转。

准备工作

创建项目,引入SpringMVC的依赖包, 把前端页面放在项目中:

index.html:
html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">

  <title>验证码</title>
  <style>
    #inputCaptcha {
      height: 30px;
      vertical-align: middle; 
    }
    #verificationCodeImg{
      vertical-align: middle; 
    }
    #checkCaptcha{
      height: 40px;
      width: 100px;
    }
  </style>
</head>

<body>
  <h1>输入验证码</h1>
  <div id="confirm">
    <input type="text" name="inputCaptcha" id="inputCaptcha">
    <img id="verificationCodeImg" src="/admin/captcha" style="cursor: pointer;" title="看不清?换一张" />
    <input type="button" value="提交" id="checkCaptcha">
  </div>
  <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
  <script>
    
    $("#verificationCodeImg").click(function(){
      $(this).hide().attr('src', '/admin/captcha?dt=' + new Date().getTime()).fadeIn();
    });

    $("#checkCaptcha").click(function () {
        alert("验证码校验");
    });

  </script>
</body>

</html>

这前端代码是一个简单的验证码输入页面:

  1. HTML结构:

    • <!DOCTYPE html>: 定义文档类型为HTML。
    • <html lang="en">: 标明文档的语言为英语。
    • <head>: 包含文档的元信息,如字符集和页面标题。
    • <body>: 包含页面的实际内容。
  2. **CSS样式:**设置了一些样式,如输入框高度、垂直对齐方式等。

  3. 页面内容:

    • <h1>输入验证码</h1>: 页面标题,提示用户当前页面的目的。
    • <div id="confirm">: 包裹验证码输入相关元素的容器。
  4. 验证码相关元素:

    • <input type="text" name="inputCaptcha" id="inputCaptcha">: 用于输入验证码的文本框。

    • <img id="verificationCodeImg" src="/admin/captcha" style="cursor: pointer;" title="看不清?换一张" />: 显示验证码图片的img标签。通过点击图片,触发jQuery事件 ,在点击时隐藏图片,通过添加时间戳参数来强制浏览器刷新图片,以获取新的验证码图片。

      **<img id="verificationCodeImg">:**创建一个图片元素,使用id属性为其指定一个唯一的标识符,方便通过JavaScript或CSS进行操作。

      src="/admin/captcha": 设置图片的源路径为/admin/captcha,这是验证码图片的获取路径,它将相对于当前页面的 URL 进行解析;此处可以是绝对路径、相对路径和网络路径。

      也就是说我们下面配置的URL返回的是一个图片:

      **style="cursor: pointer;":**添加样式,将鼠标指针设置为手型,以提示用户该图片可以点击。

      title="看不清?换一张": 设置图片的标题,这将在用户将鼠标悬停在图片上时显示。提示用户如果验证码不清晰,可以点击图片来获取新的验证码。

    • **<input type="button" value="提交" id="checkCaptcha">:**提交按钮,用于触发验证码校验。

    • 相关的jQuery事件:

      html 复制代码
      $("#verificationCodeImg").click(function(){
        $(this).hide().attr('src', '/admin/captcha?dt=' + new Date().getTime()).fadeIn();
      });

      **$("#verificationCodeImg").click():**通过jQuery选择器选中id为verificationCodeImg的图片元素,然后为其绑定一个点击事件。

      **$(this).hide():**在点击事件中,首先隐藏当前的验证码图片。

      **.attr('src', '/admin/captcha?dt=' + new Date().getTime()):**修改图片的src属性,通过添加时间戳参数(?dt=' + new Date().getTime()),以确保浏览器认为这是一个新的URL,从而强制刷新验证码图片。

      .fadeIn(): 将修改后的图片以淡入效果显示,使新的验证码图片在页面中渐显出来。

  5. jQuery脚本:

    • 引入jQuery库。
    • $("#verificationCodeImg").click(): 给验证码图片添加点击事件,通过隐藏当前图片、修改src属性添加时间戳参数、然后渐显显示新图片,实现更换验证码的效果。
    • $("#checkCaptcha").click(): 给提交按钮添加点击事件,点击按钮时弹出一个提示框,显示"验证码校验"。

总体而言,这段前端代码实现了一个简单的验证码输入页面,其中用户可以通过点击验证码图片来更换验证码,点击提交按钮会触发一个提示框。

success.html:
html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>验证成功页</title>
    <script>
        console.log("Frontend: success.html loaded."); // 添加调试信息
    </script>
</head>
<body>
    <h1>验证成功</h1>
</body>
</html>

可以直接运行看看:

点击图片就可以切换新的验证码。

你也可以通过更改配置来调整界面:

参考逻辑

如果我们想自己实现一个验证码的程序,并且希望使用类似Kaptcha的方式,可以参考这个类的实现:

java 复制代码
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.google.code.kaptcha.servlet;

import com.google.code.kaptcha.Producer;
import com.google.code.kaptcha.util.Config;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Date;
import java.util.Enumeration;
import java.util.Properties;
import javax.imageio.ImageIO;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class KaptchaServlet extends HttpServlet implements Servlet {
    private Properties props = new Properties();
    private Producer kaptchaProducer = null;
    private String sessionKeyValue = null;
    private String sessionKeyDateValue = null;

    public KaptchaServlet() {
    }

    public void init(ServletConfig conf) throws ServletException {
        super.init(conf);
        ImageIO.setUseCache(false);
        Enumeration<?> initParams = conf.getInitParameterNames();

        while(initParams.hasMoreElements()) {
            String key = (String)initParams.nextElement();
            String value = conf.getInitParameter(key);
            this.props.put(key, value);
        }

        Config config = new Config(this.props);
        this.kaptchaProducer = config.getProducerImpl();
        this.sessionKeyValue = config.getSessionKey();
        this.sessionKeyDateValue = config.getSessionDate();
    }

    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setDateHeader("Expires", 0L);
        resp.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
        resp.addHeader("Cache-Control", "post-check=0, pre-check=0");
        resp.setHeader("Pragma", "no-cache");
        resp.setContentType("image/jpeg");
        String capText = this.kaptchaProducer.createText();
        req.getSession().setAttribute(this.sessionKeyValue, capText);
        req.getSession().setAttribute(this.sessionKeyDateValue, new Date());
        BufferedImage bi = this.kaptchaProducer.createImage(capText);
        ServletOutputStream out = resp.getOutputStream();
        ImageIO.write(bi, "jpg", out);
    }
}

这个类是一个实现验证码生成的Servlet类,主要用于生成Kaptcha验证码图片。以下是这个类的主要功能:

  1. 初始化方法 (init):

    • 在Servlet初始化时被调用。
    • 设置ImageIO的缓存使用为false,确保验证码图片不会被缓存。
    • 通过获取初始化参数,使用这些参数初始化 Config 对象。
    • 从 Config 对象中获取验证码生成器、会话键名和日期键名。
  2. GET请求处理方法 (doGet):

    • 处理HTTP GET请求,用于生成验证码图片。
    • 设置HTTP响应头,禁用缓存,设置响应类型为JPEG。
    • 通过验证码生成器 (kaptchaProducer) 创建验证码文本和图片。
    • 将验证码文本和日期信息存储在会话中。
    • 将验证码图片写入响应输出流。
  3. 属性:

    • props: 用于存储Servlet初始化参数的 Properties 对象。
    • kaptchaProducer: 用于生成验证码图片的 Producer 对象。
    • sessionKeyValue: 存储验证码文本的会话键名。
    • sessionKeyDateValue: 存储验证码日期信息的会话键名。
  4. **构造方法:**没有参数的构造方法。

这个类的作用是处理GET请求,生成Kaptcha验证码图片,并将验证码文本和日期信息存储在会话中。这是一个常见的用于实现验证码功能的Servlet类。

主要逻辑包括:

  • 初始化配置:在 init 方法中,通过获取Servlet初始化参数,创建 Config 对象,从中获取验证码生成器、会话键名和日期键名。
  • 生成验证码图片:在 doGet 方法中,设置HTTP响应头,生成验证码文本和图片,将验证码文本和日期信息存储在会话中,并将验证码图片写入响应输出流。
  • 使用ImageIO生成图片:使用 ImageIO 类来创建验证码图片,并通过 ServletOutputStream 将图片写入响应输出流。

接下来我们只需要完成KaptchaController 类的编写工作,然后结合导入的前面提到的 KaptchaServlet 类,就可以构成一个简单的验证码程序。

我们的 KaptchaController 类主要用于验证码的校验,而 KaptchaServlet 类则用于生成验证码图片。

关键的步骤如下:

  1. **生成验证码:**使用 KaptchaServlet 类的 doGet 方法生成验证码图片,并在其中将验证码文本和日期信息存储在会话中。

  2. 校验验证码:

    • 使用 KaptchaController 类的 check 方法,获取用户输入的验证码,并从会话中获取生成的验证码文本和日期信息。
    • 比对用户输入的验证码是否与生成的验证码一致,并检查验证码是否过期。
  3. 前端交互:

    • 在前端页面中引用验证码图片的路径为 /admin/captcha。
    • 用户在页面上输入验证码,并通过提交按钮触发 KaptchaController 的 check 方法进行验证。

总之,在整个流程中,用户在页面上看到的验证码图片和用户输入的验证码将经由我们的前端和后端协作实现。

约定前后端交互接口

需求分析: 在前后端交互的过程中,后端需要提供两个主要服务,分别是:

  1. 生成验证码,并将生成的验证码返回给前端。
  2. 校验用户输入的验证码是否正确。

接口定义:

  1. 生成验证码

    • **请求:**GET /admin/captcha

    • 响应: 图片内容

    用户通过浏览器发送一个GET请求至服务器,路径为/admin/captcha。服务器在收到请求后,生成一个验证码图片,并将该图片的内容作为响应返回给浏览器。浏览器接收到响应后,在页面上显示验证码图片。

  2. 校验验证码是否正确

    • 请求:
      • POST /admin/check
      • 参数:captcha=xn8d(用户输入的验证码)
    • **响应:**true 或 false

    用户在登录或提交表单时,通过浏览器向服务器发送一个POST请求,路径为/admin/check,同时携带用户输入的验证码参数(例如:captcha=xn8d)。服务器收到请求后,根据用户输入的验证码进行校验,如果验证码正确,则返回true,表示验证成功;否则返回false,表示验证失败。

通过以上接口定义,前端可以实现验证码的生成和校验功能,增强系统的安全性和用户验证机制。

实现服务器端代码

引入依赖

如前

通过配置创建验证码生成器

如前

启动项目,访问http://127.0.0.1:8080/admin/captcha,显示验证码。

验证码校验
java 复制代码
package com.example.captchademo;

import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpSession;
import java.util.Date;

@RequestMapping("/admin")
@RestController
public class KaptchaController {
    private static final String KAPTCHA_SESSION_KEY = "KAPTCHA_SESSION_KEY";
    private static final String KAPTCHA_SESSION_DATE = "KAPTCHA_SESSION_DATE";
    private static final long TIME_OUT = 60*1000;//一分钟, 毫秒数
    /**
     * 校验验证码是否正确
     * @param inputCaptcha  用户输入的验证码
     * @return
     */
    @RequestMapping("/check")
    public boolean check(String inputCaptcha, HttpSession session){
        //1. 判断输入的验证码是否为空
        //2. 获取生成的验证码
        //3. 比对 生成的验证码和输入的验证码是否一致
        //4. 确认验证码是否过期
        if (!StringUtils.hasLength(inputCaptcha)){
            return false;
        }
        //生成的验证码(正确的验证码)
        String saveCaptcha = (String)session.getAttribute(KAPTCHA_SESSION_KEY);
        Date savaCaptchaDate = (Date)session.getAttribute(KAPTCHA_SESSION_DATE);

        if (inputCaptcha.equalsIgnoreCase(saveCaptcha)){//不区分大小写
            if (savaCaptchaDate!=null || System.currentTimeMillis()-savaCaptchaDate.getTime()<TIME_OUT){
                return true;
            }
        }
        return false;
    }
}

比对Session中存储的验证码是否和用户输入的⼀致?

如果⼀致,并且时间在⼀分钟以为就认为成功。

调整前端页面代码

修改 index.html :

补充ajax代码,点击提交按钮,发送请求去服务端进行校验:

html 复制代码
$("#checkCaptcha").click(function () {
  // 发送Ajax请求校验验证码
  $.ajax({
    url: "/admin/check", // 服务端校验验证码的接口路径
    type: "post", // 请求类型为POST
    data: { inputCaptcha: $("#inputCaptcha").val() }, // 向服务端发送的数据,包括用户输入的验证

    success: function (result) {
      // 请求成功的回调函数
      if (result) {
        // 如果服务端返回true,表示验证码校验成功
        location.href = "success.html"; // 重定向到success.html页面
      } else {
        // 如果服务端返回false,表示验证码校验失败
        alert("验证码错误"); // 弹出提示框,提示用户验证码错误
        console.log("Frontend: Verification failed."); // 添加调试信息
        $("#inputCaptcha").val(""); // 清空用户输入的验证码
      }
    },
    error: function (xhr, status, error) {
      console.log("Ajax Error:", xhr, status, error); // 输出Ajax请求错误信息
    }

  });
});
html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">

  <title>验证码</title>
  <style>
    #inputCaptcha {
      height: 30px;
      vertical-align: middle; 
    }
    #verificationCodeImg{
      vertical-align: middle; 
    }
    #checkCaptcha{
      height: 40px;
      width: 100px;
    }
  </style>
</head>

<body>
  <h1>输入验证码</h1>
  <div id="confirm">
    <input type="text" name="inputCaptcha" id="inputCaptcha">
    <img id="verificationCodeImg" src="/admin/captcha" style="cursor: pointer;" title="看不清?换一张" />
    <input type="button" value="提交" id="checkCaptcha">
  </div>
  <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
  <script>
    
    $("#verificationCodeImg").click(function(){
      $(this).hide().attr('src', '/admin/captcha?dt=' + new Date().getTime()).fadeIn();
    });

    $("#checkCaptcha").click(function () {
  // 发送Ajax请求校验验证码
  $.ajax({
    url: "/admin/check", // 服务端校验验证码的接口路径
    type: "post", // 请求类型为POST
    data: { inputCaptcha: $("#inputCaptcha").val() }, // 向服务端发送的数据,包括用户输入的验证

    success: function (result) {
      // 请求成功的回调函数
      if (result) {
        // 如果服务端返回true,表示验证码校验成功
        location.href = "success.html"; // 重定向到success.html页面
      } else {
        // 如果服务端返回false,表示验证码校验失败
        alert("验证码错误"); // 弹出提示框,提示用户验证码错误
        console.log("Frontend: Verification failed."); // 添加调试信息
        $("#inputCaptcha").val(""); // 清空用户输入的验证码
      }
    },
    error: function (xhr, status, error) {
      console.log("Ajax Error:", xhr, status, error); // 输出Ajax请求错误信息
    }

  });
});



  </script>
</body>

</html>

运行测试

相关推荐
我是浮夸1 小时前
MyBatisPlus——学习笔记
java·spring boot·mybatis
杨荧1 小时前
【JAVA开源】基于Vue和SpringBoot的水果购物网站
java·开发语言·vue.js·spring boot·spring cloud·开源
liuxin334455662 小时前
大学生就业招聘:Spring Boot系统的高效实现
spring boot·后端·mfc
杨哥带你写代码2 小时前
构建高效新闻推荐系统:Spring Boot的力量
服务器·spring boot·php
我焦虑的编程日记2 小时前
【RabbitMQ】RabbitMQ学习
java·数据库·java-ee
2402_857583496 小时前
新闻推荐系统:Spring Boot的架构优势
数据库·spring boot·架构
2401_854391086 小时前
新闻推荐系统:Spring Boot与大数据
java·spring boot·后端
全职计算机毕业设计7 小时前
基于协同过滤的网络文学智能推荐平台的设计与实现(小说)springboot mysql Redis Thymeleaf
spring boot·后端·mysql
原机小子7 小时前
大学生就业桥梁:基于Spring Boot的招聘系统
服务器·数据库·spring boot
忙里偷闲的sin8 小时前
整理Maven坐标,Spring Boot集成工具依赖版本差异问题
java·spring boot·maven