目录
计算机系统中存在着大量的配置文件,虽然绝大多数用户不直接与这些文件交互,但它们在各种软件和应用中发挥着关键作用。无论是浏览器、微信、IDE(集成开发环境)还是智能手机,都依赖于配置文件的存在。这些文件以不同的形式分布在计算机上,例如在C:\Users、C:\Windows等文件夹下,以及各种 .config、.xml 文件。
配置文件主要是为了解决硬编码带来的问题, 把可能会发生改变的信息, 放在一个集中的地方,?当我们启动某个程序时,应用程序从配置?件中读取数据,并加载运行。
配置文件的作用和重要性:
解决硬编码问题: 主要作用之一是解决硬编码的问题。硬编码将数据直接嵌入到程序源代码中,导致数据固定且不易修改。通过配置文件,可以将可能发生变化的信息集中存储,使得修改配置更加方便,而不需要修改源代码。
硬编码(Hard Coding)是指直接将数据、配置或参数等值直接嵌入到程序或可执行对象的源代码中,而不是通过外部配置文件或其他可变的机制进行设置。这种做法使得这些值变得固定,不易修改,而且需要修改源代码才能改变这些值。
以手机字体大小为例,如果采用硬编码的方式,开发者会在程序源代码中明确指定字体大小的数值。这样,所有用户使用该应用时都将看到相同的字体大小,而无法根据个人偏好进行调整。如果不同用户有不同的偏好,他们将无法根据个人偏好调整字体大小。
集中管理配置信息: 许多软件和系统需要配置信息,例如路径、数据库连接、用户设置等。配置文件提供了一种集中管理这些信息的方式,使得修改配置信息更加方便,而不需要在整个代码库中查找和修改。
提高灵活性: 配置文件使得应用程序的行为能够在不修改程序代码的情况下进行调整。这使得软件更具灵活性,能够适应不同的使用场景和需求,而无需重新编译和部署。
适应不同环境: 不同的环境(开发、测试、生产等)可能需要不同的配置。通过使用不同的配置文件,可以轻松地适应不同的环境需求,而无需更改源代码。
用户和应用程序的交互: 配置文件为用户提供了调整应用程序行为的手段,例如更改界面样式、设置偏好等。用户可以通过修改配置文件而不是程序代码来个性化应用。
应用程序间的交互: 在分布式系统中,不同的应用程序可能需要相互通信和协作。通过配置文件,可以配置不同应用程序之间的交互规则和参数,实现更好的集成和协作。
简化部署和维护: 将配置信息存储在文件中使得部署和维护过程更加简化。不同的配置文件可以轻松地用于不同的部署环境,同时也方便备份和恢复。
综合而言,配置文件在软件开发和计算机系统中扮演了重要的角色,为软件提供了灵活性、可维护性和用户友好性。通过合理使用配置文件,可以使得应用程序更易于开发、维护和升级。
SpringBoot提供了强大的配置文件支持,允许开发人员灵活地配置和管理应用程序的各种参数。配置文件的格式得到了SpringBoot的支持和定义,这不仅有助于规范化项目的配置,同时也为其他框架集成到SpringBoot提供了一种便捷的方式。
在配置文件中,可以设置许多项目或框架的关键信息,包括但不限于:
项目的启动端口: 开发人员可以轻松地指定应用程序监听的端口,确保应用在启动时使用正确的端口进行通信。
数据库的连接信息: 数据库是许多应用程序的核心组成部分,而数据库连接信息通常包括用户名和密码等敏感信息。通过配置文件,这些信息可以被安全地存储和管理。
第三方系统的调用密钥: 集成到其他系统或服务时,经常需要提供访问权限验证的密钥。将这些密钥放在配置文件中,有助于在应用程序中轻松管理这些敏感信息。
用于发现和定位问题的日志信息: 配置文件中可以定义各种日志参数,包括常规日志和异常日志。这些日志对于开发人员在调试和解决问题时提供了宝贵的信息。
通过使用SpringBoot的配置文件,开发人员可以集中管理应用程序的配置,而不必硬编码这些参数,从而增加了灵活性。这种方式还使得配置信息可以根据不同环境(如开发、测试、生产)进行调整,而不必修改源代码。总体而言,SpringBoot的配置文件功能为开发人员提供了更方便、可维护性更强的配置管理方式。
在Spring框架中,Spring Boot的配置文件主要是通过application.properties(或者application.yml)来定义项目的配置信息。我们其实已经见过一些常见配置项:
项目的启动端口
Spring Boot内置了Tomcat服务器,默认端口号是8080。但由于电脑上8080端口可能被其他应用程序占用,因此Spring Boot允许用户自定义端口号。通过在application.properties文件中设置如下配置,可以指定项目的启动端口:
server.port=自定义端口号
这样,应用程序将使用指定的端口号进行监听,确保不与其他应用程序发生冲突。
数据库连接信息
为了更方便简单地访问数据库,出现了一些持久层框架,它们对JDBC进行了更深层次的封装。这些框架允许用户通过简短的代码实现数据库访问。然而,不同的应用程序通常需要访问不同的数据库,因此这些持久层框架需要支持用户自定义配置数据库的连接信息。
在application.properties文件中,可以设置数据库连接的相关信息,例如:
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中,配置文件是一种关键的组件,用于定义应用程序的各种属性和设置。
这些配置文件主要有三种格式:
其中,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文件,展示了如何配置一些常见的应用属性:
# 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 配置文件是最早期的配置文件格式,也是创建 Spring Boot 项目时的默认配置文件。这种格式以键值对的形式存储配置信息,每一行表示一个属性的设置。由于其简单直观的语法,properties 配置文件在项目早期得到了广泛应用。
在 Spring Boot 的早期版本中,使用 properties 格式的配置文件是主流,这也符合许多传统 Java 项目的配置需求。然而,随着时间的推移和开发者对更灵活、易读的配置的需求增加,新一代的配置文件格式逐渐崭露头角,其中以 yml 格式为代表。
尽管 properties 配置文件在默认设置中仍然保留,但随着 Spring Boot 的演进,更多项目和开发者转向使用 yml 格式的配置文件。这种趋势主要因为 yml 具有更为结构化和可读性强的语法,使得配置信息更清晰、易维护,从而提高了开发效率。不过,properties 仍然在一些场景中得到应用,特别是在传统项目或与其他系统集成时。
properties 配置文件的基本语法是采用键值对的形式,其中键和值之间用等号 = 连接。以下是一个简单的示例,展示了如何使用 properties 文件配置一些常见的项:
# 配置项目端口号
server.port=8080
# 配置数据库连接信息
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/testdb?characterEncoding=utf8&
spring.datasource.username=root
spring.datasource.password=root
在这个示例中:
此外,可以使用 # 符号在配置文件中添加注释信息,这对于提供配置项的说明或者添加一些备注非常有用。注释部分对应于配置文件中的说明而不会影响实际的配置。比如上述示例中的注释用于解释每个配置项的用途。
你也可以自定义配置:
我们现在先学习语法和具体的使用,更多配置信息会随着深入学习进行补充。如果你很感兴趣,可以参考:Common Application Properties (spring.io)
这些全是一些默认配置:
你可能会很头大,这要怎么学……?
不用学,用的时候现去查就行,而且与其在官方文档查,还不如直接百度。
我整理了几个application.yml文件的常用的配置项,可以直接复制粘贴:
# 数据源配置
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 配置文件以 key-value 的形式存储配置信息,具有简单直观的语法。然而,随着项目的复杂性增加,properties 配置文件也会暴露一些缺点,其中一些包括:
缺乏层次结构:properties 配置文件本身不支持层次结构,所有配置项都是扁平的。这意味着在配置复杂的对象结构时,需要使用复杂的命名规则来模拟层次结构,导致配置文件变得冗长和难以维护。
person.name=John Doe
person.age=30
person.address.city=New York
person.address.zip=10001
冗余的信息:?properties 配置文件中可能包含大量重复的信息,尤其是在键的前缀相同的情况下。这可能导致配置文件变得臃肿,并且当需要修改一组相关的配置项时,可能需要在多个地方进行调整。
可读性差: 随着配置项的增多,properties 文件可能变得难以阅读和理解。复杂的结构和大量的键值对可能使得配置文件的维护变得繁琐。
相对于 properties ,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 是树形结构的配置文件,它的基础语法是"key: value",key 和 value 之间使用英文冒号加空格的方式组成,空格不可省略!!!
如果一个键值对中的值包含多个单词,通过换行的缩进表示,这种方式通常用于表示复杂的数据结构或长的文本块:
description: |
This is a multiline
description that spans
multiple lines in YAML.
基本结构:?yml使用缩进来表示结构,而不是像其他语言一样使用大括号。缩进的空格数目是有意义的,通常是两个空格。
key1: value1
key2:
subkey1: subvalue1
subkey2: subvalue2
键值对: 键值对使用冒号 : 分隔,表示键和值的关系。
name: John Doe
age: 30
列表: 使用连字符?- 表示列表中的每个元素。
fruits:
- apple
- orange
- banana
多行文本: 使用 |?符号表示多行文本块,保留换行符。
description: |
This is a multiline
text block.
注释: 使用?# 符号表示注释。
# This is a comment
key: value
引用: 使用?& 符号创建锚点(anchor),使用 *?符号引用锚点。
defaults: &defaults
username: guest
password: guest
user1: *defaults
user2:
<<: *defaults
password: secure_password
这些是.yml文件的基本语法。YAML以人类可读的方式表示数据结构,其简洁性和可读性使其成为配置文件和数据交换的常用格式。在Spring Boot项目中,.yml文件通常用于配置应用程序的属性,提供了一种更易读、更清晰的配置方式。
使用.yml连接数据库:
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/dbname?characterEncoding=utf8&useSSL=false
username: root
password: root
使用.properties连接数据库:
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文件则更加传统,适用于简单的配置需求。
# 字符串
string:
value: Hello
# 布尔值,true或false
boolean:
value: true
value1: false
# 整数
int:
value: 10
# 浮点数
float:
value: 3.14159
# Null,~代表null
null:
value: ~
# 空字符串
# 直接后面什么都不加就可以了, 但这种方式不直观, 更多的表示是使用引号括起来
empty:
value: ''
yml 读取配置的方式和 properties 相同,使用?@Value 注解即可:
?
?
?
所以你会发现,把.properties中的“?. ”改成换行缩进就是.yml文件格式。
注意事项:value 值加单双引号
在 YAML 文件中,对字符串的表示有一些注意事项,尤其涉及到单引号和双引号。
字符串默认不用加上单引号或者双引号,如果加英文的单双引号可以表示特殊的含义。
尝试在 application.yml 中配置如下信息:
string:
str1: Hello \n Spring Boot.
str2: 'Hello \n Spring Boot.'
str3: "Hello \n Spring Boot."
?
从上述实例可以得出以下结论:
字符串默认不需要加上单引号或者双引号: 在?str1 中,字符串 "Hello \n Spring Boot." 没有加上任何引号,它会被解释为一个普通字符串,特殊字符 \n 会被当作两个字符。
单引号会转义特殊字符: 在?str2 中,字符串被单引号包裹,导致 \n 失去特殊功能,它会被解释为普通字符串中的两个字符,而不是换行符。
双引号不会转义字符串里面的特殊字符: 在?str3 中,字符串被双引号包裹,特殊字符 \n 会被保留其本身的含义,即表示换行符。
注意这里的描述,可能我们的第一反应相反。
我们还可以在 yml 中配置对象,如下配置:?
或者是使用行内写法(与上面的写法作用?致):
这个时候就不能用?@Value 来读取配置中的对象了,此时要使用另?个注解 @ConfigurationProperties 来读取,具体实现如下:?
注意要加上@Component: 如果希望Student类成为Spring容器的一个Bean,通常需要在这个类上添加@Component注解,以便Spring能够扫描并管理这个Bean。?
调用类的实现如下:?
配置?件也可以配置 list 集合,如下所示:?
注意缩进!!!
Dbtypes:
name:
- mysql
- sqlserver
- db2
集合的读取和对象?样,也是使用?@ConfigurationProperties 来读取的,具体实现如下:
访问集合的实现如下:
空格绝对不可以省略!否则此处将被当作一个对象!
省略空格后没有报错,但是含义完全改变!?
?
配置文件也可以配置 map,如下所示:
或者是使用行内写法(与上面的写法作用?致,也要空格):
Map的读取和对象一样,也是使? @ConfigurationProperties 来读取的,具体实现如下:
打印类的实现如下:
?
优点:
可读性高,写法简单,易于理解: YAML 使用缩进和简洁的语法结构,使文件更加易读,不需要像 XML 或 JSON 那样使用大量的符号和标记。
支持更多的数据类型: YAML 支持丰富的数据类型,包括对象、数组、List、Map 等,使其能够简单表达各种数据形态,适用于多样化的应用场景。
跨编程语言支持: YAML 不仅在 Java 中得到广泛应用,还能够在其他编程语言中使用,如 Golang、Python、Ruby、JavaScript 等,提供了更大的灵活性和通用性。
缺点:
不适合写复杂的配置文件: 尽管 YAML 对于简单的配置文件表达十分方便,但对于复杂配置文件的书写可能变得困难。与 properties 格式相比,复杂配置的转换过程可能会花费更多精力,可读性也会下降。
例如,对于一份复杂的配置,YAML 的可读性较差:
keycloak:
realm: demo
resource: fm-cache-cloud
credentials:
secret: d4589683-Oce7-4982-bcd3
security:
- authRoles:
- user
collections:
- name: ssologinurl
patterns:
- /login/*
而properties格式如下:
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/*
对格式有较强的要求: YAML 对缩进和格式有较强的要求,一个空格的差异可能导致解析错误。这可能使得在编辑或处理 YAML 文件时更加容易出错,需要维护者保持良好的格式规范。
总的来说,YAML 适用于简单和中等复杂度的配置文件,但在处理高度复杂的配置时,可能会显得繁琐并降低可读性。在选择配置文件格式时,需要根据具体的应用场景和需求权衡其优缺点。
随着对安全性要求的日益提升,目前许多项目都广泛采用验证码作为一种重要的安全验证手段。验证码的形式多种多样,其中更为复杂的图形验证码和行为验证码已经成为当前的主流趋势。随着技术的不断发展,这些高级验证码不仅提升了安全性,还为用户提供了更加可靠和有效的身份验证方式。
验证码的实现方式很多,?网上也有比较多的插件或者工具包可以使用,咱们选择使用Google的开源项目?Kaptcha来实现。
Kaptcha是由Google推出的一款高度可配置的实用验证码生成工具。该工具以其灵活性和高度定制化而著称,为开发者提供了生成验证码的便捷解决方案。通过Kaptcha,用户可以根据具体需求调整验证码的各种参数,包括但不限于验证码的外观、复杂性和大小等。这使得Kaptcha成为许多项目中首选的验证码生成工具之一,为用户提供了一种可靠而安全的身份验证机制。
代码:Google Code Archive - Long-term storage for Google Code Project Hosting.
网上有很多人甚至公司都基于Google的kaptcha进行了二次开发。
我们选择?个直接适配SpringBoot的 开源项目。
但是这篇文章比较粗糙……所以,我们下面先简单解释一下插件的使用,然后再进行一个验证码程序的详细编写过程。?
验证码的生成可以在客户端进行,也可以在服务器端进行。
对于普通的字符验证码,后端通常分两个主要步骤。首先,生成验证码的内容,根据验证码内容以及干扰项等因素,生成相应的图像,并将图像返回给客户端。其次,将验证码的内容存储起来,以便在校验时取出进行比对。
在这个流程中,kaptcha插件采取了将验证码内容存储在Session中的策略。这意味着生成的验证码内容会被存储在服务器端的Session对象中,以确保安全性和一致性。在校验时,系统会从Session中取出相应的验证码内容,然后与用户输入的验证码进行比对,以完成验证过程。这种方法有效地维护了验证码的状态和安全性,为用户提供了可靠的身份验证机制。
<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、仅通过配置文件来生成验证码(推荐)
我们接下来介绍的方法就是通过配置文件来生成验证码:
# 应用服务 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验证码生成库的基本属性。该类包含了多个内部静态类,每个静态类表示不同的配置项,形成了一个层次结构。以下是主要的配置项和它们的含义:
每个配置项都有相应的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验证码生成库的相关组件。
总体而言,这个自动配置类负责将Kaptcha集成到Spring Boot应用程序中。它提供了默认的Kaptcha实现(DefaultKaptcha),并允许用户在配置文件中灵活地配置Kaptcha的各种属性。如果用户已经定义了自己的Producer Bean,则默认的Kaptcha实现不会覆盖用户的定义。
5、然后是刚刚提到的继承自BaseProperties的KaptchaProperties类,它用于配置Kaptcha验证码生成库的属性,逐步解释一下代码的结构和含义:
这个配置类的目的是为Kaptcha验证码生成库提供灵活的配置选项,支持多种配置情况。其中,KaptchaProperties类包含了一个Map,每个条目对应一个特定类型的Kaptcha配置,而SingleKaptchaProperties类则包含了该类型的详细配置信息,包括Session类中的属性。
6、最后的一个类是一个ServletContextInitializer的实现类,名为ServletRegisterInitializer。它的主要目的是在Servlet容器启动时,向ServletContext注册Kaptcha验证码生成的Servlet。
让我解释一下该类的主要结构和功能:
private static final String KAPTCHA_SERVLET_BEAN_NAME_SUBFFIX = "KapthcaServlet": 定义了一个常量,用于生成Kaptcha验证码Servlet的Bean名称后缀。
@Resource: 注解用于注入依赖。
private KaptchaProperties kaptchaProperties: 注入KaptchaProperties对象,用于获取Kaptcha的配置信息。
@Resource(name = "kaptchaProps"): 注入名为kaptchaProps的Properties对象,该对象通过ConfigUtils类将KaptchaProperties转换而来。
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.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")为例,提供详细的配置说明:
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
学习完成之后我们就可以来做一个关于验证码的小项目了。
界面如下图所示:
创建项目,引入SpringMVC的依赖包,?把前端页面放在项目中:
?
<!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>
这前端代码是一个简单的验证码输入页面:
HTML结构:
CSS样式:设置了一些样式,如输入框高度、垂直对齐方式等。
页面内容:
验证码相关元素:
<img id="verificationCodeImg">: 创建一个图片元素,使用id属性为其指定一个唯一的标识符,方便通过JavaScript或CSS进行操作。
src="/admin/captcha": 设置图片的源路径为/admin/captcha,这是验证码图片的获取路径,它将相对于当前页面的 URL 进行解析;此处可以是绝对路径、相对路径和网络路径。
也就是说我们下面配置的URL返回的是一个图片:
style="cursor: pointer;": 添加样式,将鼠标指针设置为手型,以提示用户该图片可以点击。
title="看不清?换一张": 设置图片的标题,这将在用户将鼠标悬停在图片上时显示。提示用户如果验证码不清晰,可以点击图片来获取新的验证码。
$("#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(): 将修改后的图片以淡入效果显示,使新的验证码图片在页面中渐显出来。
jQuery脚本:
总体而言,这段前端代码实现了一个简单的验证码输入页面,其中用户可以通过点击验证码图片来更换验证码,点击提交按钮会触发一个提示框。
<!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的方式,可以参考这个类的实现:
//
// 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验证码图片。以下是这个类的主要功能:
初始化方法?(init):
GET请求处理方法?(doGet):
属性:
构造方法:没有参数的构造方法。
这个类的作用是处理GET请求,生成Kaptcha验证码图片,并将验证码文本和日期信息存储在会话中。这是一个常见的用于实现验证码功能的Servlet类。
主要逻辑包括:
接下来我们只需要完成KaptchaController 类的编写工作,然后结合导入的前面提到的 KaptchaServlet 类,就可以构成一个简单的验证码程序。
我们的 KaptchaController 类主要用于验证码的校验,而 KaptchaServlet 类则用于生成验证码图片。
关键的步骤如下:
生成验证码:使用 KaptchaServlet 类的 doGet 方法生成验证码图片,并在其中将验证码文本和日期信息存储在会话中。
校验验证码:
前端交互:
总之,在整个流程中,用户在页面上看到的验证码图片和用户输入的验证码将经由我们的前端和后端协作实现。?
需求分析: 在前后端交互的过程中,后端需要提供两个主要服务,分别是:
接口定义:
生成验证码
用户通过浏览器发送一个GET请求至服务器,路径为/admin/captcha。服务器在收到请求后,生成一个验证码图片,并将该图片的内容作为响应返回给浏览器。浏览器接收到响应后,在页面上显示验证码图片。
校验验证码是否正确
用户在登录或提交表单时,通过浏览器向服务器发送一个POST请求,路径为/admin/check,同时携带用户输入的验证码参数(例如:captcha=xn8d)。服务器收到请求后,根据用户输入的验证码进行校验,如果验证码正确,则返回true,表示验证成功;否则返回false,表示验证失败。
通过以上接口定义,前端可以实现验证码的生成和校验功能,增强系统的安全性和用户验证机制。
如前
如前
启动项目,访问http://127.0.0.1:8080/admin/captcha,显示验证码。
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代码,点击提交按钮,发送请求去服务端进行校验:
$("#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请求错误信息
}
});
});
<!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>