Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
K
kk-anti-reptile
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
cfld-backend
kk-anti-reptile
Commits
01a3a848
Commit
01a3a848
authored
Dec 26, 2023
by
zhouxudong
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
提交代码
parents
Pipeline
#110
failed with stages
Changes
38
Pipelines
1
Show whitespace changes
Inline
Side-by-side
Showing
38 changed files
with
4537 additions
and
0 deletions
+4537
-0
README.md
README.md
+169
-0
pom.xml
pom.xml
+118
-0
ValidateFormService.java
...main/java/cn/keking/anti_reptile/ValidateFormService.java
+72
-0
AntiReptile.java
...n/java/cn/keking/anti_reptile/annotation/AntiReptile.java
+15
-0
AntiReptileAutoConfig.java
.../cn/keking/anti_reptile/config/AntiReptileAutoConfig.java
+79
-0
AntiReptileProperties.java
.../cn/keking/anti_reptile/config/AntiReptileProperties.java
+226
-0
RedissonAutoConfig.java
...ava/cn/keking/anti_reptile/config/RedissonAutoConfig.java
+206
-0
WebMvcConfig.java
...main/java/cn/keking/anti_reptile/config/WebMvcConfig.java
+26
-0
AntiReptileConsts.java
...va/cn/keking/anti_reptile/constant/AntiReptileConsts.java
+10
-0
AntiReptileInterceptor.java
...king/anti_reptile/interceptor/AntiReptileInterceptor.java
+128
-0
AntiReptileInterceptor1.java
...ing/anti_reptile/interceptor/AntiReptileInterceptor1.java
+118
-0
VerifyImageDTO.java
...in/java/cn/keking/anti_reptile/module/VerifyImageDTO.java
+66
-0
VerifyImageVO.java
...ain/java/cn/keking/anti_reptile/module/VerifyImageVO.java
+49
-0
AbstractRule.java
src/main/java/cn/keking/anti_reptile/rule/AbstractRule.java
+19
-0
AntiReptileRule.java
...ain/java/cn/keking/anti_reptile/rule/AntiReptileRule.java
+31
-0
IpRule.java
src/main/java/cn/keking/anti_reptile/rule/IpRule.java
+187
-0
RuleActuator.java
src/main/java/cn/keking/anti_reptile/rule/RuleActuator.java
+37
-0
UaRule.java
src/main/java/cn/keking/anti_reptile/rule/UaRule.java
+69
-0
RefreshFormServlet.java
...va/cn/keking/anti_reptile/servlet/RefreshFormServlet.java
+46
-0
ValidateFormServlet.java
...a/cn/keking/anti_reptile/servlet/ValidateFormServlet.java
+46
-0
CrosUtil.java
src/main/java/cn/keking/anti_reptile/util/CrosUtil.java
+30
-0
VerifyImageUtil.java
...ain/java/cn/keking/anti_reptile/util/VerifyImageUtil.java
+53
-0
AbstractChineseCaptcha.java
src/main/java/com/wf/captcha/AbstractChineseCaptcha.java
+50
-0
AbstractMathCaptcha.java
src/main/java/com/wf/captcha/AbstractMathCaptcha.java
+39
-0
BaseCaptcha.java
src/main/java/com/wf/captcha/BaseCaptcha.java
+216
-0
ChineseCaptcha.java
src/main/java/com/wf/captcha/ChineseCaptcha.java
+108
-0
ChineseGifCaptcha.java
src/main/java/com/wf/captcha/ChineseGifCaptcha.java
+125
-0
Encoder.java
src/main/java/com/wf/captcha/Encoder.java
+340
-0
GifCaptcha.java
src/main/java/com/wf/captcha/GifCaptcha.java
+128
-0
GifEncoder.java
src/main/java/com/wf/captcha/GifEncoder.java
+518
-0
MathCaptcha.java
src/main/java/com/wf/captcha/MathCaptcha.java
+107
-0
MathGifCaptcha.java
src/main/java/com/wf/captcha/MathGifCaptcha.java
+127
-0
Quant.java
src/main/java/com/wf/captcha/Quant.java
+464
-0
Randoms.java
src/main/java/com/wf/captcha/Randoms.java
+74
-0
SpecCaptcha.java
src/main/java/com/wf/captcha/SpecCaptcha.java
+106
-0
CaptchaUtil.java
src/main/java/com/wf/captcha/utils/CaptchaUtil.java
+185
-0
spring.factories
src/main/resources/META-INF/spring.factories
+4
-0
index.html
src/main/resources/verify/index.html
+146
-0
No files found.
README.md
0 → 100644
View file @
01a3a848
## 概述
kk-anti-reptile 是适用于
`基于 spring-boot 开发的分布式系统`
的开源反爬虫接口防刷组件。
## 开源地址
[
https://gitee.com/kekingcn/kk-anti-reptile
](
https://gitee.com/kekingcn/kk-anti-reptile
)
[
https://github.com/kekingcn/kk-anti-reptile
](
https://github.com/kekingcn/kk-anti-reptile
)
## 系统要求
-
基于 spring-boot 开发(spring-boot1.x, spring-boot2.x 均可)
-
需要使用 redis
## 工作流程
kk-anti-reptile 使用
[
SpringMVC拦截器
](
https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-handlermapping-interceptor
)
对请求进行过滤,通过 spring-boot 的扩展点机制,实例化一个Spring HandlerInterceptor Bean,通过 Spring 注入到 Servlet 容器中,从而实现对请求的过滤
在 kk-anti-reptile 的过滤 Interceptor 内部,又通过责任链模式,将各种不同的过滤规则织入,并提供抽象接口,可由调用方进行规则扩展
Interceptor 调用则链进行请求过滤,如过滤不通过,则拦截请求,返回状态码
`509`
,并输出验证码输入页面,输出验证码正确后,调用过滤规则链对规则进行重置
目前规则链中有如下两个规则
### ip-rule
ip-rule 通过时间窗口统计当前时间窗口内请求数,小于规定的最大请求数则可通过,否则不通过。时间窗口、最大请求数、ip 白名单等均可配置
### ua-rule
ua-rule 通过判断请求携带的 User-Agent,得到操作系统、设备信息、浏览器信息等,可配置各种维度对请求进行过滤
## 验证码页面
命中爬虫和防盗刷规则后,会阻断请求,并生成接除阻断的验证码,验证码有多种组合方式,如果客户端可以正确输入验证码,则可以继续访问
![](
https://kkfileview.keking.cn/anti-reptile/06114318_NlQW.png
)
验证码有中文、英文字母+数字、简单算术三种形式,每种形式又有静态图片和 GIF 动图两种图片格式,即目前共有如下六种,所有类型的验证码会随机出现,目前技术手段识别难度极高,可有效阻止防止爬虫大规模爬取数据
![](
https://kkfileview.keking.cn/anti-reptile/up-0e140d960cdf1771d71663dace1b3b0b151.png
)![](
https://kkfileview.keking.cn/anti-reptile/up-1e95900b91df071f7fe9c6e487e1d8ec3bb.gif
)
![](
https://kkfileview.keking.cn/anti-reptile/up-ede7ddd514dbd1be7744453cabd56a67e81.png
)![](
https://oscimg.oschina.net/oscnet/up-42a72529601c93ab4d7bbd43dc4b10ae795.gif
)
![](
https://kkfileview.keking.cn/anti-reptile/up-f63acf78d822e46b1d4a490fac235e5f098.png
)![](
https://kkfileview.keking.cn/anti-reptile/up-1884209f099a909b2839fddfa09ff7025f0.gif
)
## 接入使用
接入非常简单,只需要引用 kk-anti-reptile 的 maven 依赖,并配置启用 kk-anti-reptile 即可
### 1. 加入 maven 依赖
```
xml
<dependency>
<groupId>
cn.keking.project
</groupId>
<artifactId>
kk-anti-reptile
</artifactId>
<version>
1.0.0-RELEASE
</version>
</dependency>
```
### 2. 配置启用 kk-anti-reptile
在spring-boot配置文件中加入如下配置
`anti.reptile.manager.enabled`
```
properties
anti.reptile.manager.enabled
=
true
```
### 3. 配置需要反爬的接口
配置反爬接口有如下两种方式,两种方式可以同时使用
1.
使用配置文件
在spring-boot配置文件中加入如下配置项
`anti.reptile.manager.include-urls`
,值为反爬的接口URI(如:/client/list),支持正则表达式匹配(如:^/admin/.
*
$),多项用
`,`
分隔
```
properties
anti.reptile.manager.include-urls
=
/client/list,/user/list,^/admin/.*$
```
2.
使用注解
在需要反爬的接口Controller对象对应的接口上加上
`@AntiReptile`
注解即可,示例如下
```
java
@RestController
@RequestMapping
(
"/demo"
)
public
class
DemoController
{
@AntiReptile
@GetMapping
(
""
)
public
String
demo
()
{
return
"Hello,World!"
;
}
}
```
### 4. 前端统一处理验证码页面
前端需要在统一发送请求的 ajax 处加入拦截,拦截到请求返回状态码
`509`
后弹出一个新页面,并把响应内容转出到页面中,然后向页面中传入后端接口
`baseUrl`
参数即可,以使用 axios 请求为例:
```
javascript
import
axios
from
'axios'
;
import
{
baseUrl
}
from
'./config'
;
axios
.
interceptors
.
response
.
use
(
data
=>
{
return
data
;
},
error
=>
{
if
(
error
.
response
.
status
===
509
)
{
let
html
=
error
.
response
.
data
;
let
verifyWindow
=
window
.
open
(
""
,
"_blank"
,
"height=400,width=560"
);
verifyWindow
.
document
.
write
(
html
);
verifyWindow
.
document
.
getElementById
(
"baseUrl"
).
value
=
baseUrl
;
}
}
);
export
default
axios
;
```
## 注意
1.
apollo-client 需启用 bootstrap
使用 apollo 配置中心的用户,由于组件内部用到
`@ConditionalOnProperty`
,要在 application.properties/bootstrap.properties 中加入如下样例配置,(apollo-client 需要 0.10.0 及以上版本)详见
[
apollo bootstrap 说明
](
https://github.com/ctripcorp/apollo/wiki/Java%E5%AE%A2%E6%88%B7%E7%AB%AF%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97#3213-spring-boot%E9%9B%86%E6%88%90%E6%96%B9%E5%BC%8F%E6%8E%A8%E8%8D%90
)
```
properties
apollo.bootstrap.enabled
=
true
```
1.
需要有 Redisson 连接
如果项目中有用到 Redisson,kk-anti-reptile 会自动获取 RedissonClient 实例对象; 如果没用到,需要在配置文件加入如下 Redisson 连接相关配置
```
properties
spring.redisson.address
=
redis://192.168.1.204:6379
spring.redisson.password
=
xxx
```
## 配置一览表
在 spring-boot 中,所有配置在配置文件都会有自动提示和说明,如下图
![
配置自动提示及说明
](
https://kkfileview.keking.cn/anti-reptile/06114319_IJlq.png
)
所有配置都以
`anti.reptile.manager`
为前缀,如下为所有配置项及说明
| NAME | 描述 | 默认值 | 示例 |
| --- | --- | --- | --- |
| enabled | 是否启用反爬虫插件 | true | true |
| globalFilterMode | 是否启用全局拦截模式 | false | true |
| include-urls | 局部拦截时,需要反爬的接口列表,以','分隔,支持正则匹配。全局拦截模式下无需配置 | 空 | /client,/user,^/admin/.
*
$ |
| ip-rule.enabled | 是否启用 IP Rule | true | true |
| ip-rule.expiration-time | 时间窗口长度(ms) | 5000 | 5000 |
| ip-rule.request-max-size | 单个时间窗口内,最大请求数 | 20 | 20 |
| ip-rule.lock-expire | 命中规则后自动解除时间(单位:s) | 10天 | 20 |
| ip-rule.ignore-ip | IP 白名单,支持后缀'
*'通配,以','分隔 | 空 | 192.168.*
,127.0.0.1 |
| ua-rule.enabled | 是否启用 User-Agent Rule | true | true |
| ua-rule.allowed-linux | 是否允许 Linux 系统访问 | false | false |
| ua-rule.allowed-mobile | 是否允许移动端设备访问 | true | true |
| ua-rule.allowed-pc | 是否允许移 PC 设备访问 | true | true |
| ua-rule.allowed-iot | 是否允许物联网设备访问 | false | false |
| ua-rule.allowed-proxy | 是否允许代理访问 | false | false |
## 联系我们
使用过程中有任何问题,都可以加入官方 QQ 群:613025121 咨询讨论
![
官方 QQ 群
](
https://kkfileview.keking.cn/anti-reptile/qq.png
)
pom.xml
0 → 100644
View file @
01a3a848
<?xml version="1.0" encoding="UTF-8"?>
<project
xmlns=
"http://maven.apache.org/POM/4.0.0"
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=
"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
>
<modelVersion>
4.0.0
</modelVersion>
<parent>
<groupId>
org.sonatype.oss
</groupId>
<artifactId>
oss-parent
</artifactId>
<version>
7
</version>
</parent>
<groupId>
cn.keking.project
</groupId>
<artifactId>
kk-anti-reptile
</artifactId>
<version>
1.0.0-RELEASE
</version>
<properties>
<java.version>
1.8
</java.version>
<project.build.sourceEncoding>
UTF-8
</project.build.sourceEncoding>
<maven-compiler-plugin.version>
3.8.1
</maven-compiler-plugin.version>
<maven-source-plugin.version>
3.2.1
</maven-source-plugin.version>
<spring-boot.version>
1.5.9.RELEASE
</spring-boot.version>
<redisson.version>
3.11.0
</redisson.version>
<servlet-api.version>
3.0.1
</servlet-api.version>
<ua-util.version>
1.21
</ua-util.version>
<commons-fileupload.version>
1.4
</commons-fileupload.version>
</properties>
<name>
kk-anti-reptile
</name>
<url>
https://gitee.com/kekingcn/kk-anti-reptile
</url>
<dependencies>
<dependency>
<groupId>
org.projectlombok
</groupId>
<artifactId>
lombok
</artifactId>
<version>
1.18.28
</version>
</dependency>
<dependency>
<groupId>
javax.servlet
</groupId>
<artifactId>
javax.servlet-api
</artifactId>
<version>
${servlet-api.version}
</version>
<scope>
provided
</scope>
</dependency>
<dependency>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot
</artifactId>
<version>
${spring-boot.version}
</version>
<scope>
provided
</scope>
</dependency>
<dependency>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-autoconfigure
</artifactId>
<version>
${spring-boot.version}
</version>
<scope>
provided
</scope>
</dependency>
<dependency>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-starter-web
</artifactId>
<version>
${spring-boot.version}
</version>
<scope>
provided
</scope>
</dependency>
<dependency>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-configuration-processor
</artifactId>
<version>
${spring-boot.version}
</version>
<scope>
provided
</scope>
</dependency>
<dependency>
<groupId>
org.redisson
</groupId>
<artifactId>
redisson
</artifactId>
<version>
${redisson.version}
</version>
</dependency>
<!-- User-Agent库 -->
<dependency>
<groupId>
eu.bitwalker
</groupId>
<artifactId>
UserAgentUtils
</artifactId>
<version>
${ua-util.version}
</version>
</dependency>
<!-- common-fileupload用于servlet处理form表单验证请求 -->
<dependency>
<groupId>
commons-fileupload
</groupId>
<artifactId>
commons-fileupload
</artifactId>
<version>
${commons-fileupload.version}
</version>
</dependency>
<dependency>
<groupId>
cn.hutool
</groupId>
<artifactId>
hutool-all
</artifactId>
<version>
5.8.20
</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>
org.apache.maven.plugins
</groupId>
<artifactId>
maven-compiler-plugin
</artifactId>
<version>
${maven-compiler-plugin.version}
</version>
<configuration>
<source>
${java.version}
</source>
<target>
${java.version}
</target>
</configuration>
</plugin>
<plugin>
<groupId>
org.apache.maven.plugins
</groupId>
<artifactId>
maven-source-plugin
</artifactId>
<version>
${maven-source-plugin.version}
</version>
<executions>
<execution>
<id>
attach-sources
</id>
<goals>
<goal>
jar-no-fork
</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
src/main/java/cn/keking/anti_reptile/ValidateFormService.java
0 → 100644
View file @
01a3a848
package
cn
.
keking
.
anti_reptile
;
import
cn.keking.anti_reptile.module.VerifyImageDTO
;
import
cn.keking.anti_reptile.module.VerifyImageVO
;
import
cn.keking.anti_reptile.rule.RuleActuator
;
import
cn.keking.anti_reptile.util.VerifyImageUtil
;
import
org.apache.commons.fileupload.FileItem
;
import
org.apache.commons.fileupload.FileUploadException
;
import
org.apache.commons.fileupload.disk.DiskFileItemFactory
;
import
org.apache.commons.fileupload.servlet.ServletFileUpload
;
import
org.springframework.beans.BeanUtils
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
javax.servlet.http.HttpServletRequest
;
import
java.io.UnsupportedEncodingException
;
import
java.util.HashMap
;
import
java.util.List
;
import
java.util.Map
;
/**
* @author kl @kailing.pub
* @since 2019/7/9
*/
public
class
ValidateFormService
{
@Autowired
private
RuleActuator
actuator
;
@Autowired
private
VerifyImageUtil
verifyImageUtil
;
public
String
validate
(
HttpServletRequest
request
)
throws
UnsupportedEncodingException
{
DiskFileItemFactory
factory
=
new
DiskFileItemFactory
();
ServletFileUpload
upload
=
new
ServletFileUpload
(
factory
);
upload
.
setHeaderEncoding
(
"UTF-8"
);
List
items
=
null
;
try
{
items
=
upload
.
parseRequest
(
request
);
}
catch
(
FileUploadException
e
)
{
e
.
printStackTrace
();
}
Map
<
String
,
String
>
params
=
new
HashMap
<
String
,
String
>();
for
(
Object
object
:
items
){
FileItem
fileItem
=
(
FileItem
)
object
;
if
(
fileItem
.
isFormField
())
{
params
.
put
(
fileItem
.
getFieldName
(),
fileItem
.
getString
(
"UTF-8"
));
}
}
String
verifyId
=
params
.
get
(
"verifyId"
);
String
result
=
params
.
get
(
"result"
);
String
realRequestUri
=
params
.
get
(
"realRequestUri"
);
String
actualResult
=
verifyImageUtil
.
getVerifyCodeFromRedis
(
verifyId
);
if
(
actualResult
!=
null
&&
request
!=
null
&&
actualResult
.
equals
(
result
.
toLowerCase
()))
{
actuator
.
reset
(
request
,
realRequestUri
);
return
"{\"result\":true}"
;
}
return
"{\"result\":false}"
;
}
public
String
refresh
(
HttpServletRequest
request
)
{
String
verifyId
=
request
.
getParameter
(
"verifyId"
);
verifyImageUtil
.
deleteVerifyCodeFromRedis
(
verifyId
);
VerifyImageDTO
verifyImage
=
verifyImageUtil
.
generateVerifyImg
();
verifyImageUtil
.
saveVerifyCodeToRedis
(
verifyImage
);
VerifyImageVO
verifyImageVO
=
new
VerifyImageVO
();
BeanUtils
.
copyProperties
(
verifyImage
,
verifyImageVO
);
String
result
=
"{\"verifyId\": \""
+
verifyImageVO
.
getVerifyId
()
+
"\",\"verifyImgStr\": \""
+
verifyImageVO
.
getVerifyImgStr
()
+
"\"}"
;
return
result
;
}
}
src/main/java/cn/keking/anti_reptile/annotation/AntiReptile.java
0 → 100644
View file @
01a3a848
package
cn
.
keking
.
anti_reptile
.
annotation
;
import
java.lang.annotation.ElementType
;
import
java.lang.annotation.Retention
;
import
java.lang.annotation.RetentionPolicy
;
import
java.lang.annotation.Target
;
/**
* @author chenjh 接口反爬虫注解
* @since 2020/2/4 15:44
*/
@Target
(
value
=
{
ElementType
.
METHOD
})
@Retention
(
value
=
RetentionPolicy
.
RUNTIME
)
public
@interface
AntiReptile
{
}
src/main/java/cn/keking/anti_reptile/config/AntiReptileAutoConfig.java
0 → 100644
View file @
01a3a848
package
cn
.
keking
.
anti_reptile
.
config
;
import
cn.keking.anti_reptile.ValidateFormService
;
import
cn.keking.anti_reptile.constant.AntiReptileConsts
;
import
cn.keking.anti_reptile.interceptor.AntiReptileInterceptor
;
import
cn.keking.anti_reptile.rule.AntiReptileRule
;
import
cn.keking.anti_reptile.rule.IpRule
;
import
cn.keking.anti_reptile.rule.RuleActuator
;
import
cn.keking.anti_reptile.rule.UaRule
;
import
cn.keking.anti_reptile.servlet.RefreshFormServlet
;
import
cn.keking.anti_reptile.servlet.ValidateFormServlet
;
import
cn.keking.anti_reptile.util.VerifyImageUtil
;
import
org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
;
import
org.springframework.boot.context.properties.EnableConfigurationProperties
;
import
org.springframework.boot.web.servlet.ServletRegistrationBean
;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.context.annotation.Configuration
;
import
org.springframework.context.annotation.Import
;
import
java.util.Comparator
;
import
java.util.List
;
import
java.util.stream.Collectors
;
/**
* RedissonAutoConfiguration 的 AutoConfigureOrder 为默认值(0),此处在它后面加载
* @author kl @kailing.pub
* @since 2019/7/8
*/
@Configuration
@EnableConfigurationProperties
(
AntiReptileProperties
.
class
)
@ConditionalOnProperty
(
prefix
=
"anti.reptile.manager"
,
value
=
"enabled"
,
havingValue
=
"true"
)
@Import
({
RedissonAutoConfig
.
class
,
WebMvcConfig
.
class
})
public
class
AntiReptileAutoConfig
{
@Bean
public
ServletRegistrationBean
validateFormServlet
()
{
return
new
ServletRegistrationBean
(
new
ValidateFormServlet
(),
AntiReptileConsts
.
VALIDATE_REQUEST_URI
);
}
@Bean
public
ServletRegistrationBean
refreshFormServlet
()
{
return
new
ServletRegistrationBean
(
new
RefreshFormServlet
(),
AntiReptileConsts
.
REFRESH_REQUEST_URI
);
}
@Bean
@ConditionalOnProperty
(
prefix
=
"anti.reptile.manager.ip-rule"
,
value
=
"enabled"
,
havingValue
=
"true"
,
matchIfMissing
=
true
)
public
IpRule
ipRule
(){
return
new
IpRule
();
}
@Bean
@ConditionalOnProperty
(
prefix
=
"anti.reptile.manager.ua-rule"
,
value
=
"enabled"
,
havingValue
=
"true"
,
matchIfMissing
=
true
)
public
UaRule
uaRule
()
{
return
new
UaRule
();
}
@Bean
public
VerifyImageUtil
verifyImageUtil
()
{
return
new
VerifyImageUtil
();
}
@Bean
public
RuleActuator
ruleActuator
(
final
List
<
AntiReptileRule
>
rules
){
final
List
<
AntiReptileRule
>
antiReptileRules
=
rules
.
stream
()
.
sorted
(
Comparator
.
comparingInt
(
AntiReptileRule:
:
getOrder
)).
collect
(
Collectors
.
toList
());
return
new
RuleActuator
(
antiReptileRules
);
}
@Bean
public
ValidateFormService
validateFormService
(){
return
new
ValidateFormService
();
}
@Bean
public
AntiReptileInterceptor
antiReptileInterceptor
()
{
return
new
AntiReptileInterceptor
();
}
}
src/main/java/cn/keking/anti_reptile/config/AntiReptileProperties.java
0 → 100644
View file @
01a3a848
package
cn
.
keking
.
anti_reptile
.
config
;
import
org.springframework.boot.context.properties.ConfigurationProperties
;
import
java.util.List
;
import
java.util.concurrent.TimeUnit
;
/**
* @author kl @kailing.pub
* @since 2019/7/9
*/
@ConfigurationProperties
(
prefix
=
"anti.reptile.manager"
)
public
class
AntiReptileProperties
{
/**
* 是否启用反爬虫插件
*/
private
boolean
enabled
;
/**
* 是否启用全局拦截,默认为false,可设置为true全局拦截
*/
private
boolean
globalFilterMode
=
false
;
/**
* 非全局拦截下,需要反爬的接口列表,以'/'开头,以','分隔
*/
private
List
<
String
>
includeUrls
;
/**
* 基于请求IP的反爬规则
*/
private
IpRule
ipRule
=
new
IpRule
();
/**
* 基于请求User-Agent的反爬规则
*/
private
UaRule
uaRule
=
new
UaRule
();
public
boolean
isEnabled
()
{
return
enabled
;
}
public
void
setEnabled
(
boolean
enabled
)
{
this
.
enabled
=
enabled
;
}
public
List
<
String
>
getIncludeUrls
()
{
return
includeUrls
;
}
public
void
setIncludeUrls
(
List
<
String
>
includeUrls
)
{
this
.
includeUrls
=
includeUrls
;
}
public
IpRule
getIpRule
()
{
return
ipRule
;
}
public
void
setIpRule
(
IpRule
ipRule
)
{
this
.
ipRule
=
ipRule
;
}
public
UaRule
getUaRule
()
{
return
uaRule
;
}
public
void
setUaRule
(
UaRule
uaRule
)
{
this
.
uaRule
=
uaRule
;
}
public
boolean
isGlobalFilterMode
()
{
return
globalFilterMode
;
}
public
void
setGlobalFilterMode
(
boolean
globalFilterMode
)
{
this
.
globalFilterMode
=
globalFilterMode
;
}
public
static
class
IpRule
{
/**
* 是否启用IP Rule:默认启用
*/
private
boolean
enabled
=
true
;
/**
* 时间窗口:默认5000ms
*/
private
Integer
expirationTime
=
5000
;
/**
* 最大请求数,默认20
*/
private
Integer
requestMaxSize
=
20
;
/**
* 命中规则后,锁定期限,默认10天,单位:秒(s)
*/
private
long
lockExpire
=
TimeUnit
.
DAYS
.
toSeconds
(
1
);
/**
* IP白名单,支持后缀'*'通配,以','分隔
*/
private
List
<
String
>
ignoreIp
;
public
long
getLockExpire
()
{
return
lockExpire
;
}
public
void
setLockExpire
(
long
lockExpire
)
{
this
.
lockExpire
=
lockExpire
;
}
public
boolean
isEnabled
()
{
return
enabled
;
}
public
void
setEnabled
(
boolean
enabled
)
{
this
.
enabled
=
enabled
;
}
public
Integer
getExpirationTime
()
{
return
expirationTime
;
}
public
void
setExpirationTime
(
Integer
expirationTime
)
{
this
.
expirationTime
=
expirationTime
;
}
public
Integer
getRequestMaxSize
()
{
return
requestMaxSize
;
}
public
void
setRequestMaxSize
(
Integer
requestMaxSize
)
{
this
.
requestMaxSize
=
requestMaxSize
;
}
public
List
<
String
>
getIgnoreIp
()
{
return
ignoreIp
;
}
public
void
setIgnoreIp
(
List
<
String
>
ignoreIp
)
{
this
.
ignoreIp
=
ignoreIp
;
}
}
public
static
class
UaRule
{
/**
* 是否启用User-Agent Rule:默认启用
*/
private
boolean
enabled
=
true
;
/**
* 是否允许Linux系统访问:默认否
*/
private
boolean
allowedLinux
=
false
;
/**
* 是否允许移动端设备访问:默认是
*/
private
boolean
allowedMobile
=
true
;
/**
* 是否允许移PC设备访问: 默认是
*/
private
boolean
allowedPc
=
true
;
/**
* 是否允许Iot设备访问:默认否
*/
private
boolean
allowedIot
=
false
;
/**
* 是否允许代理访问:默认否
*/
private
boolean
allowedProxy
=
false
;
public
boolean
isEnabled
()
{
return
enabled
;
}
public
void
setEnabled
(
boolean
enabled
)
{
this
.
enabled
=
enabled
;
}
public
boolean
isAllowedLinux
()
{
return
allowedLinux
;
}
public
void
setAllowedLinux
(
boolean
allowedLinux
)
{
this
.
allowedLinux
=
allowedLinux
;
}
public
boolean
isAllowedMobile
()
{
return
allowedMobile
;
}
public
void
setAllowedMobile
(
boolean
allowedMobile
)
{
this
.
allowedMobile
=
allowedMobile
;
}
public
boolean
isAllowedPc
()
{
return
allowedPc
;
}
public
void
setAllowedPc
(
boolean
allowedPc
)
{
this
.
allowedPc
=
allowedPc
;
}
public
boolean
isAllowedIot
()
{
return
allowedIot
;
}
public
void
setAllowedIot
(
boolean
allowedIot
)
{
this
.
allowedIot
=
allowedIot
;
}
public
boolean
isAllowedProxy
()
{
return
allowedProxy
;
}
public
void
setAllowedProxy
(
boolean
allowedProxy
)
{
this
.
allowedProxy
=
allowedProxy
;
}
}
}
src/main/java/cn/keking/anti_reptile/config/RedissonAutoConfig.java
0 → 100644
View file @
01a3a848
package
cn
.
keking
.
anti_reptile
.
config
;
import
io.netty.channel.nio.NioEventLoopGroup
;
import
org.redisson.Redisson
;
import
org.redisson.api.RedissonClient
;
import
org.redisson.client.codec.Codec
;
import
org.redisson.config.Config
;
import
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
;
import
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass
;
import
org.springframework.boot.context.properties.ConfigurationProperties
;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.context.annotation.Configuration
;
import
org.springframework.util.ClassUtils
;
/**
* 没有redisson starter时才加载
* @author chenjh
* @since 2019/7/17 8:21
*/
@ConfigurationProperties
(
prefix
=
"spring.redisson"
)
@Configuration
@ConditionalOnMissingClass
(
"org.redisson.spring.starter.RedissonAutoConfiguration"
)
public
class
RedissonAutoConfig
{
private
String
address
;
private
int
connectionMinimumIdleSize
=
10
;
private
int
idleConnectionTimeout
=
10000
;
private
int
connectTimeout
=
10000
;
private
int
timeout
=
3000
;
private
int
retryAttempts
=
3
;
private
int
retryInterval
=
1500
;
private
String
password
=
null
;
private
int
subscriptionsPerConnection
=
5
;
private
String
clientName
=
null
;
private
int
subscriptionConnectionMinimumIdleSize
=
1
;
private
int
subscriptionConnectionPoolSize
=
50
;
private
int
connectionPoolSize
=
64
;
private
int
database
=
0
;
private
boolean
dnsMonitoring
=
false
;
private
int
dnsMonitoringInterval
=
5000
;
private
String
codec
=
"org.redisson.codec.JsonJacksonCodec"
;
@Bean
(
destroyMethod
=
"shutdown"
)
@ConditionalOnMissingBean
RedissonClient
redisson
()
throws
Exception
{
Config
config
=
new
Config
();
config
.
useSingleServer
().
setAddress
(
address
)
.
setConnectionMinimumIdleSize
(
connectionMinimumIdleSize
)
.
setConnectionPoolSize
(
connectionPoolSize
)
.
setDatabase
(
database
)
.
setDnsMonitoringInterval
(
dnsMonitoringInterval
)
.
setSubscriptionConnectionMinimumIdleSize
(
subscriptionConnectionMinimumIdleSize
)
.
setSubscriptionConnectionPoolSize
(
subscriptionConnectionPoolSize
)
.
setSubscriptionsPerConnection
(
subscriptionsPerConnection
)
.
setClientName
(
clientName
)
.
setRetryAttempts
(
retryAttempts
)
.
setRetryInterval
(
retryInterval
)
.
setTimeout
(
timeout
)
.
setConnectTimeout
(
connectTimeout
)
.
setIdleConnectionTimeout
(
idleConnectionTimeout
)
.
setPassword
(
password
);
Codec
codec
=(
Codec
)
ClassUtils
.
forName
(
getCodec
(),
ClassUtils
.
getDefaultClassLoader
()).
newInstance
();
config
.
setCodec
(
codec
);
config
.
setEventLoopGroup
(
new
NioEventLoopGroup
());
return
Redisson
.
create
(
config
);
}
public
String
getAddress
()
{
return
address
;
}
public
void
setAddress
(
String
address
)
{
this
.
address
=
address
;
}
public
int
getIdleConnectionTimeout
()
{
return
idleConnectionTimeout
;
}
public
void
setIdleConnectionTimeout
(
int
idleConnectionTimeout
)
{
this
.
idleConnectionTimeout
=
idleConnectionTimeout
;
}
public
int
getConnectTimeout
()
{
return
connectTimeout
;
}
public
void
setConnectTimeout
(
int
connectTimeout
)
{
this
.
connectTimeout
=
connectTimeout
;
}
public
int
getTimeout
()
{
return
timeout
;
}
public
void
setTimeout
(
int
timeout
)
{
this
.
timeout
=
timeout
;
}
public
int
getRetryAttempts
()
{
return
retryAttempts
;
}
public
void
setRetryAttempts
(
int
retryAttempts
)
{
this
.
retryAttempts
=
retryAttempts
;
}
public
int
getRetryInterval
()
{
return
retryInterval
;
}
public
void
setRetryInterval
(
int
retryInterval
)
{
this
.
retryInterval
=
retryInterval
;
}
public
String
getPassword
()
{
return
password
;
}
public
void
setPassword
(
String
password
)
{
this
.
password
=
password
;
}
public
int
getSubscriptionsPerConnection
()
{
return
subscriptionsPerConnection
;
}
public
void
setSubscriptionsPerConnection
(
int
subscriptionsPerConnection
)
{
this
.
subscriptionsPerConnection
=
subscriptionsPerConnection
;
}
public
String
getClientName
()
{
return
clientName
;
}
public
void
setClientName
(
String
clientName
)
{
this
.
clientName
=
clientName
;
}
public
int
getSubscriptionConnectionMinimumIdleSize
()
{
return
subscriptionConnectionMinimumIdleSize
;
}
public
void
setSubscriptionConnectionMinimumIdleSize
(
int
subscriptionConnectionMinimumIdleSize
)
{
this
.
subscriptionConnectionMinimumIdleSize
=
subscriptionConnectionMinimumIdleSize
;
}
public
int
getSubscriptionConnectionPoolSize
()
{
return
subscriptionConnectionPoolSize
;
}
public
void
setSubscriptionConnectionPoolSize
(
int
subscriptionConnectionPoolSize
)
{
this
.
subscriptionConnectionPoolSize
=
subscriptionConnectionPoolSize
;
}
public
int
getConnectionMinimumIdleSize
()
{
return
connectionMinimumIdleSize
;
}
public
void
setConnectionMinimumIdleSize
(
int
connectionMinimumIdleSize
)
{
this
.
connectionMinimumIdleSize
=
connectionMinimumIdleSize
;
}
public
int
getConnectionPoolSize
()
{
return
connectionPoolSize
;
}
public
void
setConnectionPoolSize
(
int
connectionPoolSize
)
{
this
.
connectionPoolSize
=
connectionPoolSize
;
}
public
int
getDatabase
()
{
return
database
;
}
public
void
setDatabase
(
int
database
)
{
this
.
database
=
database
;
}
public
boolean
isDnsMonitoring
()
{
return
dnsMonitoring
;
}
public
void
setDnsMonitoring
(
boolean
dnsMonitoring
)
{
this
.
dnsMonitoring
=
dnsMonitoring
;
}
public
int
getDnsMonitoringInterval
()
{
return
dnsMonitoringInterval
;
}
public
void
setDnsMonitoringInterval
(
int
dnsMonitoringInterval
)
{
this
.
dnsMonitoringInterval
=
dnsMonitoringInterval
;
}
public
String
getCodec
()
{
return
codec
;
}
public
void
setCodec
(
String
codec
)
{
this
.
codec
=
codec
;
}
}
\ No newline at end of file
src/main/java/cn/keking/anti_reptile/config/WebMvcConfig.java
0 → 100644
View file @
01a3a848
package
cn
.
keking
.
anti_reptile
.
config
;
import
cn.keking.anti_reptile.interceptor.AntiReptileInterceptor
;
import
org.springframework.context.annotation.Configuration
;
import
org.springframework.web.servlet.config.annotation.InterceptorRegistry
;
import
org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
;
/**
* @author chenjh
* @since 2020/2/4 17:40
*/
@Configuration
public
class
WebMvcConfig
extends
WebMvcConfigurerAdapter
{
private
AntiReptileInterceptor
antiReptileInterceptor
;
public
WebMvcConfig
(
AntiReptileInterceptor
antiReptileInterceptor
)
{
this
.
antiReptileInterceptor
=
antiReptileInterceptor
;
}
@Override
public
void
addInterceptors
(
InterceptorRegistry
registry
)
{
registry
.
addInterceptor
(
this
.
antiReptileInterceptor
).
addPathPatterns
(
"/**"
);
super
.
addInterceptors
(
registry
);
}
}
src/main/java/cn/keking/anti_reptile/constant/AntiReptileConsts.java
0 → 100644
View file @
01a3a848
package
cn
.
keking
.
anti_reptile
.
constant
;
/**
* @author chenjh
* @since 2019/7/16 10:14
*/
public
class
AntiReptileConsts
{
public
static
final
String
VALIDATE_REQUEST_URI
=
"/kk-anti-reptile/validate/"
;
public
static
final
String
REFRESH_REQUEST_URI
=
"/kk-anti-reptile/refresh"
;
}
src/main/java/cn/keking/anti_reptile/interceptor/AntiReptileInterceptor.java
0 → 100644
View file @
01a3a848
package
cn
.
keking
.
anti_reptile
.
interceptor
;
import
cn.hutool.json.JSONUtil
;
import
cn.keking.anti_reptile.annotation.AntiReptile
;
import
cn.keking.anti_reptile.config.AntiReptileProperties
;
import
cn.keking.anti_reptile.module.VerifyImageDTO
;
import
cn.keking.anti_reptile.rule.RuleActuator
;
import
cn.keking.anti_reptile.util.CrosUtil
;
import
cn.keking.anti_reptile.util.VerifyImageUtil
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.context.ApplicationContext
;
import
org.springframework.core.annotation.AnnotationUtils
;
import
org.springframework.core.io.ClassPathResource
;
import
org.springframework.util.FileCopyUtils
;
import
org.springframework.web.context.support.WebApplicationContextUtils
;
import
org.springframework.web.method.HandlerMethod
;
import
org.springframework.web.servlet.handler.HandlerInterceptorAdapter
;
import
javax.servlet.ServletContext
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletResponse
;
import
java.io.IOException
;
import
java.lang.reflect.Method
;
import
java.nio.charset.StandardCharsets
;
import
java.util.ArrayList
;
import
java.util.HashMap
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.concurrent.atomic.AtomicBoolean
;
import
java.util.regex.Pattern
;
/**
* @author chenjh
* @since 2020/2/4 17:45
*/
@Slf4j
public
class
AntiReptileInterceptor
extends
HandlerInterceptorAdapter
{
private
String
antiReptileForm
;
private
RuleActuator
actuator
;
private
List
<
String
>
includeUrls
;
private
boolean
globalFilterMode
;
private
VerifyImageUtil
verifyImageUtil
;
private
AtomicBoolean
initialized
=
new
AtomicBoolean
(
false
);
public
void
init
(
ServletContext
context
)
{
ClassPathResource
classPathResource
=
new
ClassPathResource
(
"verify/index.html"
);
try
{
classPathResource
.
getInputStream
();
byte
[]
bytes
=
FileCopyUtils
.
copyToByteArray
(
classPathResource
.
getInputStream
());
this
.
antiReptileForm
=
new
String
(
bytes
,
StandardCharsets
.
UTF_8
);
}
catch
(
IOException
e
)
{
System
.
out
.
println
(
"反爬虫验证模板加载失败!"
);
e
.
printStackTrace
();
}
ApplicationContext
ctx
=
WebApplicationContextUtils
.
getWebApplicationContext
(
context
);
assert
ctx
!=
null
;
this
.
actuator
=
ctx
.
getBean
(
RuleActuator
.
class
);
this
.
verifyImageUtil
=
ctx
.
getBean
(
VerifyImageUtil
.
class
);
this
.
includeUrls
=
ctx
.
getBean
(
AntiReptileProperties
.
class
).
getIncludeUrls
();
this
.
globalFilterMode
=
ctx
.
getBean
(
AntiReptileProperties
.
class
).
isGlobalFilterMode
();
if
(
this
.
includeUrls
==
null
)
{
this
.
includeUrls
=
new
ArrayList
<>();
}
}
@Override
public
boolean
preHandle
(
HttpServletRequest
request
,
HttpServletResponse
response
,
Object
handler
)
throws
Exception
{
if
(!
initialized
.
get
())
{
init
(
request
.
getServletContext
());
initialized
.
set
(
true
);
}
HandlerMethod
handlerMethod
;
try
{
handlerMethod
=
(
HandlerMethod
)
handler
;
}
catch
(
ClassCastException
e
)
{
return
true
;
}
Method
method
=
handlerMethod
.
getMethod
();
AntiReptile
antiReptile
=
AnnotationUtils
.
findAnnotation
(
method
,
AntiReptile
.
class
);
boolean
isAntiReptileAnnotation
=
antiReptile
!=
null
;
String
requestUrl
=
request
.
getRequestURI
();
if
(
isIntercept
(
requestUrl
,
isAntiReptileAnnotation
)
&&
!
actuator
.
isAllowed
(
request
,
response
))
{
CrosUtil
.
setCrosHeader
(
response
);
response
.
setContentType
(
"text/html;charset=utf-8"
);
response
.
setStatus
(
509
);
VerifyImageDTO
verifyImage
=
verifyImageUtil
.
generateVerifyImg
();
verifyImageUtil
.
saveVerifyCodeToRedis
(
verifyImage
);
String
str1
=
this
.
antiReptileForm
.
replace
(
"verifyId_value"
,
verifyImage
.
getVerifyId
());
String
str2
=
str1
.
replaceAll
(
"verifyImg_value"
,
verifyImage
.
getVerifyImgStr
());
String
str3
=
str2
.
replaceAll
(
"realRequestUri_value"
,
requestUrl
);
//response.getWriter().write(str3);
Map
<
String
,
Object
>
result
=
new
HashMap
<>();
result
.
put
(
"verifyId_value"
,
verifyImage
.
getVerifyId
());
result
.
put
(
"verifyImg_value"
,
verifyImage
.
getVerifyImgStr
());
result
.
put
(
"realRequestUri_value"
,
requestUrl
);
response
.
getWriter
().
write
(
JSONUtil
.
toJsonStr
(
result
));
response
.
getWriter
().
close
();
return
false
;
}
return
true
;
}
/**
* 是否拦截
* @param requestUrl 请求uri
* @param isAntiReptileAnnotation 是否有AntiReptile注解
* @return 是否拦截
*/
public
boolean
isIntercept
(
String
requestUrl
,
Boolean
isAntiReptileAnnotation
)
{
if
(
this
.
globalFilterMode
||
isAntiReptileAnnotation
||
this
.
includeUrls
.
contains
(
requestUrl
))
{
return
true
;
}
else
{
for
(
String
includeUrl
:
includeUrls
)
{
if
(
Pattern
.
matches
(
includeUrl
,
requestUrl
))
{
return
true
;
}
}
return
false
;
}
}
}
src/main/java/cn/keking/anti_reptile/interceptor/AntiReptileInterceptor1.java
0 → 100644
View file @
01a3a848
package
cn
.
keking
.
anti_reptile
.
interceptor
;
import
cn.keking.anti_reptile.annotation.AntiReptile
;
import
cn.keking.anti_reptile.config.AntiReptileProperties
;
import
cn.keking.anti_reptile.module.VerifyImageDTO
;
import
cn.keking.anti_reptile.rule.RuleActuator
;
import
cn.keking.anti_reptile.util.CrosUtil
;
import
cn.keking.anti_reptile.util.VerifyImageUtil
;
import
org.springframework.context.ApplicationContext
;
import
org.springframework.core.annotation.AnnotationUtils
;
import
org.springframework.core.io.ClassPathResource
;
import
org.springframework.util.FileCopyUtils
;
import
org.springframework.web.context.support.WebApplicationContextUtils
;
import
org.springframework.web.method.HandlerMethod
;
import
org.springframework.web.servlet.handler.HandlerInterceptorAdapter
;
import
javax.servlet.ServletContext
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletResponse
;
import
java.io.IOException
;
import
java.lang.reflect.Method
;
import
java.nio.charset.StandardCharsets
;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.concurrent.atomic.AtomicBoolean
;
import
java.util.regex.Pattern
;
/**
* @author chenjh
* @since 2020/2/4 17:45
*/
public
class
AntiReptileInterceptor1
extends
HandlerInterceptorAdapter
{
private
String
antiReptileForm
;
private
RuleActuator
actuator
;
private
List
<
String
>
includeUrls
;
private
boolean
globalFilterMode
;
private
VerifyImageUtil
verifyImageUtil
;
private
AtomicBoolean
initialized
=
new
AtomicBoolean
(
false
);
public
void
init
(
ServletContext
context
)
{
ClassPathResource
classPathResource
=
new
ClassPathResource
(
"verify/index.html"
);
try
{
classPathResource
.
getInputStream
();
byte
[]
bytes
=
FileCopyUtils
.
copyToByteArray
(
classPathResource
.
getInputStream
());
this
.
antiReptileForm
=
new
String
(
bytes
,
StandardCharsets
.
UTF_8
);
}
catch
(
IOException
e
)
{
System
.
out
.
println
(
"反爬虫验证模板加载失败!"
);
e
.
printStackTrace
();
}
ApplicationContext
ctx
=
WebApplicationContextUtils
.
getWebApplicationContext
(
context
);
assert
ctx
!=
null
;
this
.
actuator
=
ctx
.
getBean
(
RuleActuator
.
class
);
this
.
verifyImageUtil
=
ctx
.
getBean
(
VerifyImageUtil
.
class
);
this
.
includeUrls
=
ctx
.
getBean
(
AntiReptileProperties
.
class
).
getIncludeUrls
();
this
.
globalFilterMode
=
ctx
.
getBean
(
AntiReptileProperties
.
class
).
isGlobalFilterMode
();
if
(
this
.
includeUrls
==
null
)
{
this
.
includeUrls
=
new
ArrayList
<>();
}
}
@Override
public
boolean
preHandle
(
HttpServletRequest
request
,
HttpServletResponse
response
,
Object
handler
)
throws
Exception
{
if
(!
initialized
.
get
())
{
init
(
request
.
getServletContext
());
initialized
.
set
(
true
);
}
HandlerMethod
handlerMethod
;
try
{
handlerMethod
=
(
HandlerMethod
)
handler
;
}
catch
(
ClassCastException
e
)
{
return
true
;
}
Method
method
=
handlerMethod
.
getMethod
();
AntiReptile
antiReptile
=
AnnotationUtils
.
findAnnotation
(
method
,
AntiReptile
.
class
);
boolean
isAntiReptileAnnotation
=
antiReptile
!=
null
;
String
requestUrl
=
request
.
getRequestURI
();
if
(
isIntercept
(
requestUrl
,
isAntiReptileAnnotation
)
&&
!
actuator
.
isAllowed
(
request
,
response
))
{
CrosUtil
.
setCrosHeader
(
response
);
response
.
setContentType
(
"text/html;charset=utf-8"
);
response
.
setStatus
(
509
);
VerifyImageDTO
verifyImage
=
verifyImageUtil
.
generateVerifyImg
();
verifyImageUtil
.
saveVerifyCodeToRedis
(
verifyImage
);
String
str1
=
this
.
antiReptileForm
.
replace
(
"verifyId_value"
,
verifyImage
.
getVerifyId
());
String
str2
=
str1
.
replaceAll
(
"verifyImg_value"
,
verifyImage
.
getVerifyImgStr
());
String
str3
=
str2
.
replaceAll
(
"realRequestUri_value"
,
requestUrl
);
response
.
getWriter
().
write
(
str3
);
response
.
getWriter
().
close
();
return
false
;
}
return
true
;
}
/**
* 是否拦截
* @param requestUrl 请求uri
* @param isAntiReptileAnnotation 是否有AntiReptile注解
* @return 是否拦截
*/
public
boolean
isIntercept
(
String
requestUrl
,
Boolean
isAntiReptileAnnotation
)
{
if
(
this
.
globalFilterMode
||
isAntiReptileAnnotation
||
this
.
includeUrls
.
contains
(
requestUrl
))
{
return
true
;
}
else
{
for
(
String
includeUrl
:
includeUrls
)
{
if
(
Pattern
.
matches
(
includeUrl
,
requestUrl
))
{
return
true
;
}
}
return
false
;
}
}
}
src/main/java/cn/keking/anti_reptile/module/VerifyImageDTO.java
0 → 100644
View file @
01a3a848
package
cn
.
keking
.
anti_reptile
.
module
;
import
java.io.Serializable
;
/**
* @author chenjh
* @since 2019/7/16 11:55
*/
public
class
VerifyImageDTO
implements
Serializable
{
private
static
final
long
serialVersionUID
=
6741944800448697513L
;
private
String
verifyId
;
private
String
verifyType
;
private
String
verifyImgStr
;
private
String
result
;
public
VerifyImageDTO
(
String
verifyId
,
String
verifyType
,
String
verifyImgStr
,
String
result
)
{
this
.
verifyId
=
verifyId
;
this
.
verifyType
=
verifyType
;
this
.
verifyImgStr
=
verifyImgStr
;
this
.
result
=
result
;
}
public
String
getVerifyId
()
{
return
verifyId
;
}
public
void
setVerifyId
(
String
verifyId
)
{
this
.
verifyId
=
verifyId
;
}
public
String
getVerifyType
()
{
return
verifyType
;
}
public
void
setVerifyType
(
String
verifyType
)
{
this
.
verifyType
=
verifyType
;
}
public
String
getVerifyImgStr
()
{
return
verifyImgStr
;
}
public
void
setVerifyImgStr
(
String
verifyImgStr
)
{
this
.
verifyImgStr
=
verifyImgStr
;
}
public
String
getResult
()
{
return
result
;
}
public
void
setResult
(
String
result
)
{
this
.
result
=
result
;
}
@Override
public
String
toString
()
{
return
"VerifyImageDTO{"
+
"verifyId='"
+
verifyId
+
'\''
+
", verifyType='"
+
verifyType
+
'\''
+
", verifyImgStr='"
+
verifyImgStr
+
'\''
+
", result='"
+
result
+
'\''
+
'}'
;
}
}
src/main/java/cn/keking/anti_reptile/module/VerifyImageVO.java
0 → 100644
View file @
01a3a848
package
cn
.
keking
.
anti_reptile
.
module
;
import
java.io.Serializable
;
/**
* @author chenjh
* @since 2019/7/16 14:58
*/
public
class
VerifyImageVO
implements
Serializable
{
private
static
final
long
serialVersionUID
=
345634706484343777L
;
private
String
verifyId
;
private
String
verifyType
;
private
String
verifyImgStr
;
public
String
getVerifyId
()
{
return
verifyId
;
}
public
void
setVerifyId
(
String
verifyId
)
{
this
.
verifyId
=
verifyId
;
}
public
String
getVerifyType
()
{
return
verifyType
;
}
public
void
setVerifyType
(
String
verifyType
)
{
this
.
verifyType
=
verifyType
;
}
public
String
getVerifyImgStr
()
{
return
verifyImgStr
;
}
public
void
setVerifyImgStr
(
String
verifyImgStr
)
{
this
.
verifyImgStr
=
verifyImgStr
;
}
@Override
public
String
toString
()
{
return
"VerifyImageVO{"
+
"verifyId='"
+
verifyId
+
'\''
+
", verifyType='"
+
verifyType
+
'\''
+
", verifyImgStr='"
+
verifyImgStr
+
'\''
+
'}'
;
}
}
src/main/java/cn/keking/anti_reptile/rule/AbstractRule.java
0 → 100644
View file @
01a3a848
package
cn
.
keking
.
anti_reptile
.
rule
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletResponse
;
/**
* @author kl @kailing.pub
* @since 2019/7/8
*/
public
abstract
class
AbstractRule
implements
AntiReptileRule
{
@Override
public
boolean
execute
(
HttpServletRequest
request
,
HttpServletResponse
response
)
{
return
doExecute
(
request
,
response
);
}
protected
abstract
boolean
doExecute
(
HttpServletRequest
request
,
HttpServletResponse
response
);
}
src/main/java/cn/keking/anti_reptile/rule/AntiReptileRule.java
0 → 100644
View file @
01a3a848
package
cn
.
keking
.
anti_reptile
.
rule
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletResponse
;
/**
* @author kl @kailing.pub
* @since 2019/7/8
*/
public
interface
AntiReptileRule
{
/**
* 反爬规则具体实现
* @param request 请求
* @param response 响应
* @return true为击中反爬规则
*/
boolean
execute
(
HttpServletRequest
request
,
HttpServletResponse
response
);
/**
* 重置已记录规则
* @param request 请求
* @param realRequestUri 原始请求uri
*/
void
reset
(
HttpServletRequest
request
,
String
realRequestUri
);
/**
* 规则优先级
* @return 优先级
*/
int
getOrder
();
}
src/main/java/cn/keking/anti_reptile/rule/IpRule.java
0 → 100644
View file @
01a3a848
package
cn
.
keking
.
anti_reptile
.
rule
;
import
cn.hutool.core.util.NumberUtil
;
import
cn.hutool.core.util.StrUtil
;
import
cn.hutool.extra.servlet.ServletUtil
;
import
cn.keking.anti_reptile.config.AntiReptileProperties
;
import
lombok.SneakyThrows
;
import
lombok.extern.slf4j.Slf4j
;
import
org.redisson.api.RAtomicLong
;
import
org.redisson.api.RMap
;
import
org.redisson.api.RedissonClient
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletResponse
;
import
java.net.*
;
import
java.util.*
;
import
java.util.concurrent.TimeUnit
;
/**
* @author kl @kailing.pub
* @since 2019/7/8
*/
@Slf4j
public
class
IpRule
extends
AbstractRule
{
private
static
final
Logger
LOGGER
=
LoggerFactory
.
getLogger
(
IpRule
.
class
);
@Autowired
private
RedissonClient
redissonClient
;
@Autowired
private
AntiReptileProperties
properties
;
private
static
final
String
RATELIMITER_COUNT_PREFIX
=
"ratelimiter_request_count"
;
private
static
final
String
RATELIMITER_EXPIRATIONTIME_PREFIX
=
"ratelimiter_expirationtime"
;
private
static
final
String
RATELIMITER_HIT_CRAWLERSTRATEGY
=
"ratelimiter_hit_crawlerstrategy"
;
@Override
@SuppressWarnings
(
"unchecked"
)
protected
boolean
doExecute
(
HttpServletRequest
request
,
HttpServletResponse
response
)
{
String
ipAddress
=
getIpAddr
(
request
);
List
<
String
>
ignoreIpList
=
properties
.
getIpRule
().
getIgnoreIp
();
if
(
ignoreIpList
!=
null
&&
ignoreIpList
.
size
()
>
0
)
{
for
(
String
ignoreIp
:
ignoreIpList
)
{
if
(
ignoreIp
.
endsWith
(
"*"
))
{
ignoreIp
=
ignoreIp
.
substring
(
0
,
ignoreIp
.
length
()
-
1
);
}
if
(
ipAddress
.
startsWith
(
ignoreIp
))
{
return
false
;
}
}
}
String
requestUrl
=
request
.
getRequestURI
();
requestUrl
=
dealUrl
(
requestUrl
);
// 毫秒,默认5000
int
expirationTime
=
properties
.
getIpRule
().
getExpirationTime
();
// 最高expirationTime时间内请求数
int
requestMaxSize
=
properties
.
getIpRule
().
getRequestMaxSize
();
RAtomicLong
rRequestCount
=
redissonClient
.
getAtomicLong
(
RATELIMITER_COUNT_PREFIX
.
concat
(
requestUrl
).
concat
(
ipAddress
));
RAtomicLong
rExpirationTime
=
redissonClient
.
getAtomicLong
(
RATELIMITER_EXPIRATIONTIME_PREFIX
.
concat
(
requestUrl
).
concat
(
ipAddress
));
if
(!
rExpirationTime
.
isExists
())
{
rRequestCount
.
set
(
0L
);
rExpirationTime
.
set
(
0L
);
rExpirationTime
.
expire
(
expirationTime
,
TimeUnit
.
MILLISECONDS
);
}
else
{
RMap
rHitMap
=
redissonClient
.
getMap
(
RATELIMITER_HIT_CRAWLERSTRATEGY
);
if
((
rRequestCount
.
incrementAndGet
()
>
requestMaxSize
)
||
rHitMap
.
containsKey
(
ipAddress
))
{
// 触发爬虫策略 ,默认10天后可重新访问
long
lockExpire
=
properties
.
getIpRule
().
getLockExpire
();
rExpirationTime
.
expire
(
lockExpire
,
TimeUnit
.
SECONDS
);
// 保存触发来源
rHitMap
.
put
(
ipAddress
,
requestUrl
);
LOGGER
.
info
(
"Intercepted request, uri: {}, ip:{}, request :{}, times in {} ms。Automatically unlock after {} seconds"
,
requestUrl
,
ipAddress
,
requestMaxSize
,
expirationTime
,
lockExpire
);
return
true
;
}
}
return
false
;
}
private
String
dealUrl
(
String
url
)
{
String
[]
split
=
url
.
split
(
"/"
);
String
s
=
split
[
split
.
length
-
1
];
if
(
NumberUtil
.
isNumber
(
s
))
{
return
StrUtil
.
removeSuffix
(
url
,
"/"
+
s
);
}
return
url
;
}
/**
* 重置已记录规则
*
* @param request 请求
* @param realRequestUri 原始请求uri
*/
@Override
public
void
reset
(
HttpServletRequest
request
,
String
realRequestUri
)
{
String
ipAddress
=
getIpAddr
(
request
);
String
requestUrl
=
realRequestUri
;
requestUrl
=
dealUrl
(
requestUrl
);
/** 重置计数器 */
int
expirationTime
=
properties
.
getIpRule
().
getExpirationTime
();
RAtomicLong
rRequestCount
=
redissonClient
.
getAtomicLong
(
RATELIMITER_COUNT_PREFIX
.
concat
(
requestUrl
).
concat
(
ipAddress
));
RAtomicLong
rExpirationTime
=
redissonClient
.
getAtomicLong
(
RATELIMITER_EXPIRATIONTIME_PREFIX
.
concat
(
requestUrl
).
concat
(
ipAddress
));
rRequestCount
.
set
(
0L
);
rExpirationTime
.
set
(
0L
);
rExpirationTime
.
expire
(
expirationTime
,
TimeUnit
.
MILLISECONDS
);
/** 清除记录 */
RMap
rHitMap
=
redissonClient
.
getMap
(
RATELIMITER_HIT_CRAWLERSTRATEGY
);
rHitMap
.
remove
(
ipAddress
);
}
/* private static String getIpAddr(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}*/
@SneakyThrows
public
static
String
getIpAddr
(
HttpServletRequest
request
)
{
if
(
request
==
null
)
{
return
null
;
}
String
ip
=
null
;
// X-Forwarded-For:Squid 服务代理
String
ipAddresses
=
request
.
getHeader
(
"X-Forwarded-For"
);
if
(
ipAddresses
==
null
||
ipAddresses
.
length
()
==
0
||
"unknown"
.
equalsIgnoreCase
(
ipAddresses
))
{
// Proxy-Client-IP:apache 服务代理
ipAddresses
=
request
.
getHeader
(
"Proxy-Client-IP"
);
}
if
(
ipAddresses
==
null
||
ipAddresses
.
length
()
==
0
||
"unknown"
.
equalsIgnoreCase
(
ipAddresses
))
{
// WL-Proxy-Client-IP:weblogic 服务代理
ipAddresses
=
request
.
getHeader
(
"WL-Proxy-Client-IP"
);
}
if
(
ipAddresses
==
null
||
ipAddresses
.
length
()
==
0
||
"unknown"
.
equalsIgnoreCase
(
ipAddresses
))
{
// HTTP_CLIENT_IP:有些代理服务器
ipAddresses
=
request
.
getHeader
(
"HTTP_CLIENT_IP"
);
}
if
(
ipAddresses
==
null
||
ipAddresses
.
length
()
==
0
||
"unknown"
.
equalsIgnoreCase
(
ipAddresses
))
{
// X-Real-IP:nginx服务代理
ipAddresses
=
request
.
getHeader
(
"X-Real-IP"
);
}
// 有些网络通过多层代理,那么获取到的ip就会有多个,一般都是通过逗号(,)分割开来,并且第一个ip为客户端的真实IP
if
(
ipAddresses
!=
null
&&
ipAddresses
.
length
()
!=
0
)
{
ip
=
ipAddresses
.
split
(
","
)[
0
];
}
// 还是不能获取到,最后再通过request.getRemoteAddr();获取
if
(
ip
==
null
||
ip
.
length
()
==
0
||
"unknown"
.
equalsIgnoreCase
(
ipAddresses
))
{
ip
=
request
.
getRemoteAddr
();
}
return
ip
.
equals
(
"0:0:0:0:0:0:0:1"
)
?
"127.0.0.1"
:
ip
;
}
@Override
public
int
getOrder
()
{
return
0
;
}
}
src/main/java/cn/keking/anti_reptile/rule/RuleActuator.java
0 → 100644
View file @
01a3a848
package
cn
.
keking
.
anti_reptile
.
rule
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletResponse
;
import
java.util.List
;
/**
* @author kl @kailing.pub
* @since 2019/7/8
*/
public
class
RuleActuator
{
private
List
<
AntiReptileRule
>
ruleList
;
public
RuleActuator
(
List
<
AntiReptileRule
>
rules
)
{
ruleList
=
rules
;
}
/**
* 是否允许通过请求
* @param request 请求
* @param response 响应
* @return 请求是否允许通过
*/
public
boolean
isAllowed
(
HttpServletRequest
request
,
HttpServletResponse
response
){
for
(
AntiReptileRule
rule:
ruleList
){
if
(
rule
.
execute
(
request
,
response
)){
return
false
;
}
}
return
true
;
}
public
void
reset
(
HttpServletRequest
request
,
String
realRequestUri
){
ruleList
.
forEach
(
rule
->
rule
.
reset
(
request
,
realRequestUri
));
}
}
src/main/java/cn/keking/anti_reptile/rule/UaRule.java
0 → 100644
View file @
01a3a848
package
cn
.
keking
.
anti_reptile
.
rule
;
import
cn.keking.anti_reptile.config.AntiReptileProperties
;
import
eu.bitwalker.useragentutils.DeviceType
;
import
eu.bitwalker.useragentutils.OperatingSystem
;
import
eu.bitwalker.useragentutils.UserAgent
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletResponse
;
/**
* @author chenjh
* @since 2019/7/17 10:13
*/
public
class
UaRule
extends
AbstractRule
{
@Autowired
private
AntiReptileProperties
properties
;
@Override
protected
boolean
doExecute
(
HttpServletRequest
request
,
HttpServletResponse
response
)
{
AntiReptileProperties
.
UaRule
uaRule
=
properties
.
getUaRule
();
UserAgent
userAgent
=
UserAgent
.
parseUserAgentString
(
request
.
getHeader
(
"User-Agent"
));
OperatingSystem
os
=
userAgent
.
getOperatingSystem
();
OperatingSystem
osGroup
=
userAgent
.
getOperatingSystem
().
getGroup
();
DeviceType
deviceType
=
userAgent
.
getOperatingSystem
().
getDeviceType
();
if
(
DeviceType
.
UNKNOWN
.
equals
(
deviceType
))
{
System
.
out
.
println
(
"Intercepted request, uri: "
+
request
.
getRequestURI
()
+
" Unknown device, User-Agent: "
+
userAgent
.
toString
());
return
true
;
}
else
if
(
OperatingSystem
.
UNKNOWN
.
equals
(
os
)
||
OperatingSystem
.
UNKNOWN_MOBILE
.
equals
(
os
)
||
OperatingSystem
.
UNKNOWN_TABLET
.
equals
(
os
))
{
System
.
out
.
println
(
"Intercepted request, uri: "
+
request
.
getRequestURI
()
+
" Unknown OperatingSystem, User-Agent: "
+
userAgent
.
toString
());
return
true
;
}
if
(!
uaRule
.
isAllowedLinux
()
&&
(
OperatingSystem
.
LINUX
.
equals
(
osGroup
)
||
OperatingSystem
.
LINUX
.
equals
(
os
)))
{
System
.
out
.
println
(
"Intercepted request, uri: "
+
request
.
getRequestURI
()
+
" Not Allowed Linux request, User-Agent: "
+
userAgent
.
toString
());
return
true
;
}
if
(!
uaRule
.
isAllowedMobile
()
&&
(
DeviceType
.
MOBILE
.
equals
(
deviceType
)
||
DeviceType
.
TABLET
.
equals
(
deviceType
)))
{
System
.
out
.
println
(
"Intercepted request, uri: "
+
request
.
getRequestURI
()
+
" Not Allowed Mobile Device request, User-Agent: "
+
userAgent
.
toString
());
return
true
;
}
if
(!
uaRule
.
isAllowedPc
()
&&
DeviceType
.
COMPUTER
.
equals
(
deviceType
))
{
System
.
out
.
println
(
"Intercepted request, uri: "
+
request
.
getRequestURI
()
+
" Not Allowed PC request, User-Agent: "
+
userAgent
.
toString
());
return
true
;
}
if
(!
uaRule
.
isAllowedIot
()
&&
(
DeviceType
.
DMR
.
equals
(
deviceType
)
||
DeviceType
.
GAME_CONSOLE
.
equals
(
deviceType
)
||
DeviceType
.
WEARABLE
.
equals
(
deviceType
)))
{
System
.
out
.
println
(
"Intercepted request, uri: "
+
request
.
getRequestURI
()
+
" Not Allowed Iot Device request, User-Agent: "
+
userAgent
.
toString
());
return
true
;
}
if
(!
uaRule
.
isAllowedProxy
()
&&
OperatingSystem
.
PROXY
.
equals
(
os
))
{
System
.
out
.
println
(
"Intercepted request, uri: "
+
request
.
getRequestURI
()
+
" Not Allowed Proxy request, User-Agent: "
+
userAgent
.
toString
());
return
true
;
}
return
false
;
}
@Override
public
void
reset
(
HttpServletRequest
request
,
String
realRequestUri
)
{
return
;
}
@Override
public
int
getOrder
()
{
return
1
;
}
}
src/main/java/cn/keking/anti_reptile/servlet/RefreshFormServlet.java
0 → 100644
View file @
01a3a848
package
cn
.
keking
.
anti_reptile
.
servlet
;
import
cn.keking.anti_reptile.ValidateFormService
;
import
cn.keking.anti_reptile.util.CrosUtil
;
import
org.springframework.context.ApplicationContext
;
import
org.springframework.web.context.support.WebApplicationContextUtils
;
import
javax.servlet.ServletContext
;
import
javax.servlet.ServletException
;
import
javax.servlet.http.HttpServlet
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletResponse
;
import
java.io.IOException
;
import
java.util.concurrent.atomic.AtomicBoolean
;
/**
* @author chenjh
* @since 2020/2/10 9:42
*/
public
class
RefreshFormServlet
extends
HttpServlet
{
private
ValidateFormService
validateFormService
;
private
AtomicBoolean
initialized
=
new
AtomicBoolean
(
false
);
private
synchronized
void
init
(
ServletContext
servletContext
)
{
ApplicationContext
ctx
=
WebApplicationContextUtils
.
getWebApplicationContext
(
servletContext
);
assert
ctx
!=
null
;
this
.
validateFormService
=
ctx
.
getBean
(
ValidateFormService
.
class
);
}
@Override
protected
void
doPost
(
HttpServletRequest
request
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
if
(!
initialized
.
get
())
{
init
(
request
.
getServletContext
());
initialized
.
set
(
true
);
}
String
result
=
validateFormService
.
refresh
(
request
);
CrosUtil
.
setCrosHeader
(
response
);
response
.
setContentType
(
"application/json;charset=utf-8"
);
response
.
setStatus
(
200
);
response
.
getWriter
().
write
(
result
);
response
.
getWriter
().
close
();
return
;
}
}
src/main/java/cn/keking/anti_reptile/servlet/ValidateFormServlet.java
0 → 100644
View file @
01a3a848
package
cn
.
keking
.
anti_reptile
.
servlet
;
import
cn.keking.anti_reptile.ValidateFormService
;
import
cn.keking.anti_reptile.util.CrosUtil
;
import
org.springframework.context.ApplicationContext
;
import
org.springframework.web.context.support.WebApplicationContextUtils
;
import
javax.servlet.ServletContext
;
import
javax.servlet.ServletException
;
import
javax.servlet.http.HttpServlet
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletResponse
;
import
java.io.IOException
;
import
java.util.concurrent.atomic.AtomicBoolean
;
/**
* @author chenjh
* @since 2020/2/10 09:20
*/
public
class
ValidateFormServlet
extends
HttpServlet
{
private
ValidateFormService
validateFormService
;
private
AtomicBoolean
initialized
=
new
AtomicBoolean
(
false
);
private
synchronized
void
init
(
ServletContext
servletContext
)
{
ApplicationContext
ctx
=
WebApplicationContextUtils
.
getWebApplicationContext
(
servletContext
);
assert
ctx
!=
null
;
this
.
validateFormService
=
ctx
.
getBean
(
ValidateFormService
.
class
);
}
@Override
protected
void
doPost
(
HttpServletRequest
request
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
if
(!
initialized
.
get
())
{
init
(
request
.
getServletContext
());
initialized
.
set
(
true
);
}
String
result
=
validateFormService
.
validate
(
request
);
CrosUtil
.
setCrosHeader
(
response
);
response
.
setContentType
(
"application/json;charset=utf-8"
);
response
.
setStatus
(
200
);
response
.
getWriter
().
write
(
result
);
response
.
getWriter
().
close
();
return
;
}
}
src/main/java/cn/keking/anti_reptile/util/CrosUtil.java
0 → 100644
View file @
01a3a848
package
cn
.
keking
.
anti_reptile
.
util
;
import
javax.servlet.http.HttpServletResponse
;
/**
* @author chenjh
* @since 2019/7/17 17:22
*/
public
class
CrosUtil
{
private
static
final
String
ALLOWED_HEADERS
=
"x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN,token,username,client"
;
private
static
final
String
ALLOWED_METHODS
=
"*"
;
private
static
final
String
ALLOWED_ORIGIN
=
"*"
;
private
static
final
String
ALLOWED_EXPOSE
=
"*"
;
private
static
final
String
MAX_AGE
=
"18000L"
;
public
static
void
setCrosHeader
(
HttpServletResponse
response
)
{
response
.
setHeader
(
"Access-Control-Allow-Origin"
,
ALLOWED_ORIGIN
);
response
.
setHeader
(
"Access-Control-Allow-Methods"
,
ALLOWED_METHODS
);
response
.
setHeader
(
"Access-Control-Max-Age"
,
MAX_AGE
);
response
.
setHeader
(
"Access-Control-Allow-Headers"
,
ALLOWED_HEADERS
);
response
.
setHeader
(
"Access-Control-Expose-Headers"
,
ALLOWED_EXPOSE
);
response
.
setHeader
(
"Access-Control-Allow-Credentials"
,
"true"
);
}
}
src/main/java/cn/keking/anti_reptile/util/VerifyImageUtil.java
0 → 100644
View file @
01a3a848
package
cn
.
keking
.
anti_reptile
.
util
;
import
cn.keking.anti_reptile.module.VerifyImageDTO
;
import
com.wf.captcha.utils.CaptchaUtil
;
import
org.redisson.api.RBucket
;
import
org.redisson.api.RedissonClient
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
java.awt.Color
;
import
java.io.ByteArrayOutputStream
;
import
java.util.Base64
;
import
java.util.Random
;
import
java.util.UUID
;
import
java.util.concurrent.TimeUnit
;
/**
* @author chenjh
* @since 2019/7/16 11:05
*/
public
class
VerifyImageUtil
{
private
static
final
String
VERIFY_CODE_KEY
=
"kk-antireptile_verifycdoe_"
;
@Autowired
private
RedissonClient
redissonClient
;
public
VerifyImageDTO
generateVerifyImg
()
{
ByteArrayOutputStream
outputStream
=
new
ByteArrayOutputStream
();
String
result
=
CaptchaUtil
.
out
(
outputStream
);
String
base64Image
=
"data:image/jpeg;base64,"
+
Base64
.
getEncoder
().
encodeToString
(
outputStream
.
toByteArray
());
String
verifyId
=
UUID
.
randomUUID
().
toString
();
return
new
VerifyImageDTO
(
verifyId
,
null
,
base64Image
,
result
);
}
public
void
saveVerifyCodeToRedis
(
VerifyImageDTO
verifyImage
)
{
RBucket
<
String
>
rBucket
=
redissonClient
.
getBucket
(
VERIFY_CODE_KEY
+
verifyImage
.
getVerifyId
());
rBucket
.
set
(
verifyImage
.
getResult
(),
60
,
TimeUnit
.
SECONDS
);
}
public
void
deleteVerifyCodeFromRedis
(
String
verifyId
)
{
RBucket
<
String
>
rBucket
=
redissonClient
.
getBucket
(
VERIFY_CODE_KEY
+
verifyId
);
rBucket
.
delete
();
}
public
String
getVerifyCodeFromRedis
(
String
verifyId
)
{
String
result
=
null
;
RBucket
<
String
>
rBucket
=
redissonClient
.
getBucket
(
VERIFY_CODE_KEY
+
verifyId
);
result
=
rBucket
.
get
();
return
result
;
}
}
src/main/java/com/wf/captcha/AbstractChineseCaptcha.java
0 → 100644
View file @
01a3a848
package
com
.
wf
.
captcha
;
import
java.awt.*
;
public
abstract
class
AbstractChineseCaptcha
extends
BaseCaptcha
{
// 常用汉字
public
static
final
String
DELTA
=
"\u7684\u4e00\u4e86\u662f\u6211\u4e0d\u5728\u4eba\u4eec\u6709\u6765\u4ed6\u8fd9\u4e0a\u7740\u4e2a\u5730\u5230\u5927\u91cc\u8bf4\u5c31\u53bb\u5b50\u5f97\u4e5f\u548c\u90a3\u8981\u4e0b\u770b\u5929\u65f6\u8fc7\u51fa\u5c0f\u4e48\u8d77\u4f60\u90fd\u628a\u597d\u8fd8\u591a\u6ca1\u4e3a\u53c8\u53ef\u5bb6\u5b66\u53ea\u4ee5\u4e3b\u4f1a\u6837\u5e74\u60f3\u751f\u540c\u8001\u4e2d\u5341\u4ece\u81ea\u9762\u524d\u5934\u9053\u5b83\u540e\u7136\u8d70\u5f88\u50cf\u89c1\u4e24\u7528\u5979\u56fd\u52a8\u8fdb\u6210\u56de\u4ec0\u8fb9\u4f5c\u5bf9\u5f00\u800c\u5df1\u4e9b\u73b0\u5c71\u6c11\u5019\u7ecf\u53d1\u5de5\u5411\u4e8b\u547d\u7ed9\u957f\u6c34\u51e0\u4e49\u4e09\u58f0\u4e8e\u9ad8\u624b\u77e5\u7406\u773c\u5fd7\u70b9\u5fc3\u6218\u4e8c\u95ee\u4f46\u8eab\u65b9\u5b9e\u5403\u505a\u53eb\u5f53\u4f4f\u542c\u9769\u6253\u5462\u771f\u5168\u624d\u56db\u5df2\u6240\u654c\u4e4b\u6700\u5149\u4ea7\u60c5\u8def\u5206\u603b\u6761\u767d\u8bdd\u4e1c\u5e2d\u6b21\u4eb2\u5982\u88ab\u82b1\u53e3\u653e\u513f\u5e38\u6c14\u4e94\u7b2c\u4f7f\u5199\u519b\u5427\u6587\u8fd0\u518d\u679c\u600e\u5b9a\u8bb8\u5feb\u660e\u884c\u56e0\u522b\u98de\u5916\u6811\u7269\u6d3b\u90e8\u95e8\u65e0\u5f80\u8239\u671b\u65b0\u5e26\u961f\u5148\u529b\u5b8c\u5374\u7ad9\u4ee3\u5458\u673a\u66f4\u4e5d\u60a8\u6bcf\u98ce\u7ea7\u8ddf\u7b11\u554a\u5b69\u4e07\u5c11\u76f4\u610f\u591c\u6bd4\u9636\u8fde\u8f66\u91cd\u4fbf\u6597\u9a6c\u54ea\u5316\u592a\u6307\u53d8\u793e\u4f3c\u58eb\u8005\u5e72\u77f3\u6ee1\u65e5\u51b3\u767e\u539f\u62ff\u7fa4\u7a76\u5404\u516d\u672c\u601d\u89e3\u7acb\u6cb3\u6751\u516b\u96be\u65e9\u8bba\u5417\u6839\u5171\u8ba9\u76f8\u7814\u4eca\u5176\u4e66\u5750\u63a5\u5e94\u5173\u4fe1\u89c9\u6b65\u53cd\u5904\u8bb0\u5c06\u5343\u627e\u4e89\u9886\u6216\u5e08\u7ed3\u5757\u8dd1\u8c01\u8349\u8d8a\u5b57\u52a0\u811a\u7d27\u7231\u7b49\u4e60\u9635\u6015\u6708\u9752\u534a\u706b\u6cd5\u9898\u5efa\u8d76\u4f4d\u5531\u6d77\u4e03\u5973\u4efb\u4ef6\u611f\u51c6\u5f20\u56e2\u5c4b\u79bb\u8272\u8138\u7247\u79d1\u5012\u775b\u5229\u4e16\u521a\u4e14\u7531\u9001\u5207\u661f\u5bfc\u665a\u8868\u591f\u6574\u8ba4\u54cd\u96ea\u6d41\u672a\u573a\u8be5\u5e76\u5e95\u6df1\u523b\u5e73\u4f1f\u5fd9\u63d0\u786e\u8fd1\u4eae\u8f7b\u8bb2\u519c\u53e4\u9ed1\u544a\u754c\u62c9\u540d\u5440\u571f\u6e05\u9633\u7167\u529e\u53f2\u6539\u5386\u8f6c\u753b\u9020\u5634\u6b64\u6cbb\u5317\u5fc5\u670d\u96e8\u7a7f\u5185\u8bc6\u9a8c\u4f20\u4e1a\u83dc\u722c\u7761\u5174\u5f62\u91cf\u54b1\u89c2\u82e6\u4f53\u4f17\u901a\u51b2\u5408\u7834\u53cb\u5ea6\u672f\u996d\u516c\u65c1\u623f\u6781\u5357\u67aa\u8bfb\u6c99\u5c81\u7ebf\u91ce\u575a\u7a7a\u6536\u7b97\u81f3\u653f\u57ce\u52b3\u843d\u94b1\u7279\u56f4\u5f1f\u80dc\u6559\u70ed\u5c55\u5305\u6b4c\u7c7b\u6e10\u5f3a\u6570\u4e61\u547c\u6027\u97f3\u7b54\u54e5\u9645\u65e7\u795e\u5ea7\u7ae0\u5e2e\u5566\u53d7\u7cfb\u4ee4\u8df3\u975e\u4f55\u725b\u53d6\u5165\u5cb8\u6562\u6389\u5ffd\u79cd\u88c5\u9876\u6025\u6797\u505c\u606f\u53e5\u533a\u8863\u822c\u62a5\u53f6\u538b\u6162\u53d4\u80cc\u7ec6"
;
public
AbstractChineseCaptcha
()
{
setFont
(
new
Font
(
"楷体"
,
Font
.
PLAIN
,
28
));
setLen
(
4
);
}
/**
* 生成随机验证码
*
* @return 验证码字符数组
*/
@Override
protected
char
[]
alphas
()
{
char
[]
cs
=
new
char
[
len
];
for
(
int
i
=
0
;
i
<
len
;
i
++)
{
cs
[
i
]
=
alphaHan
();
}
chars
=
new
String
(
cs
);
return
cs
;
}
/**
* 返回随机汉字
*
* @return 随机汉字
*/
public
static
char
alphaHan
()
{
return
DELTA
.
charAt
(
num
(
DELTA
.
length
()));
}
@Override
public
void
drawLine
(
int
num
,
Color
color
,
Graphics2D
g
)
{
for
(
int
i
=
0
;
i
<
num
;
i
++)
{
g
.
setColor
(
color
==
null
?
color
(
150
,
250
)
:
color
);
int
x1
=
num
(-
10
,
width
/
3
);
int
y1
=
num
(
5
,
height
-
5
);
int
x2
=
num
(
width
/
3
*
2
,
width
+
10
);
int
y2
=
num
(
2
,
height
-
2
);
g
.
drawLine
(
x1
,
y1
,
x2
,
y2
);
}
}
}
src/main/java/com/wf/captcha/AbstractMathCaptcha.java
0 → 100644
View file @
01a3a848
package
com
.
wf
.
captcha
;
import
java.util.Random
;
/**
* @author chenjh
* @since 2019/7/16 16:04
*/
public
abstract
class
AbstractMathCaptcha
extends
BaseCaptcha
{
/**
* 生成随机加减验证码
*
* @return 验证码字符数组
*/
@Override
protected
char
[]
alphas
()
{
// 生成随机类
Random
random
=
new
Random
();
char
[]
cs
=
new
char
[
4
];
int
rand0
=
random
.
nextInt
(
10
);
if
(
rand0
==
0
)
{
rand0
=
1
;
}
int
rand1
=
random
.
nextInt
(
10
);
boolean
rand2
=
random
.
nextBoolean
();
int
rand3
=
random
.
nextInt
(
10
);
cs
[
0
]
=
(
char
)
(
'0'
+
rand0
);
cs
[
1
]
=
(
char
)
(
'0'
+
rand1
);
cs
[
2
]
=
rand2
?
'+'
:
'-'
;
cs
[
3
]
=
(
char
)
(
'0'
+
rand3
);
int
num1
=
rand0
*
10
+
rand1
;
int
num2
=
rand3
;
int
result
=
rand2
?
num1
+
num2
:
num1
-
num2
;
chars
=
String
.
valueOf
(
result
);
return
cs
;
}
}
src/main/java/com/wf/captcha/BaseCaptcha.java
0 → 100644
View file @
01a3a848
package
com
.
wf
.
captcha
;
import
java.awt.*
;
import
java.io.OutputStream
;
public
abstract
class
BaseCaptcha
extends
Randoms
{
protected
Font
font
=
new
Font
(
"Arial"
,
Font
.
BOLD
,
32
);
// 字体Verdana
protected
int
len
=
4
;
// 验证码随机字符长度
protected
int
width
=
130
;
// 验证码显示宽度
protected
int
height
=
48
;
// 验证码显示高度
protected
String
chars
=
null
;
// 当前验证码
protected
int
charType
=
TYPE_DEFAULT
;
// 验证码类型,1字母数字混合,2纯数字,3纯字母
public
static
final
int
TYPE_DEFAULT
=
1
;
// 字母数字混合
public
static
final
int
TYPE_ONLY_NUMBER
=
2
;
// 纯数字
public
static
final
int
TYPE_ONLY_CHAR
=
3
;
// 纯字母
public
static
final
int
TYPE_ONLY_UPPER
=
4
;
// 纯大写字母
public
static
final
int
TYPE_ONLY_LOWER
=
5
;
// 纯小写字母
public
static
final
int
TYPE_NUM_AND_UPPER
=
6
;
// 数字大写字母
// 常用颜色
public
static
final
int
[][]
COLOR
=
{{
0
,
135
,
255
},
{
51
,
153
,
51
},
{
255
,
102
,
102
},
{
255
,
153
,
0
},
{
153
,
102
,
0
},
{
153
,
102
,
153
},
{
51
,
153
,
153
},
{
102
,
102
,
255
},
{
0
,
102
,
204
},
{
204
,
51
,
51
},
{
0
,
153
,
204
},
{
0
,
51
,
102
}};
/**
* 生成随机验证码
*
* @return 验证码字符数组
*/
protected
char
[]
alphas
()
{
char
[]
cs
=
new
char
[
len
];
for
(
int
i
=
0
;
i
<
len
;
i
++)
{
switch
(
charType
)
{
case
2
:
cs
[
i
]
=
alpha
(
numMaxIndex
);
break
;
case
3
:
cs
[
i
]
=
alpha
(
charMinIndex
,
charMaxIndex
);
break
;
case
4
:
cs
[
i
]
=
alpha
(
upperMinIndex
,
upperMaxIndex
);
break
;
case
5
:
cs
[
i
]
=
alpha
(
lowerMinIndex
,
lowerMaxIndex
);
break
;
case
6
:
cs
[
i
]
=
alpha
(
upperMaxIndex
);
break
;
default
:
cs
[
i
]
=
alpha
();
}
}
chars
=
new
String
(
cs
);
return
cs
;
}
/**
* 给定范围获得随机颜色
*
* @param fc 0-255
* @param bc 0-255
* @return 随机颜色
*/
protected
Color
color
(
int
fc
,
int
bc
)
{
if
(
fc
>
255
)
{
fc
=
255
;
}
if
(
bc
>
255
)
{
bc
=
255
;
}
int
r
=
fc
+
num
(
bc
-
fc
);
int
g
=
fc
+
num
(
bc
-
fc
);
int
b
=
fc
+
num
(
bc
-
fc
);
return
new
Color
(
r
,
g
,
b
);
}
/**
* 获取随机常用颜色
*
* @return 随机颜色
*/
protected
Color
color
()
{
int
[]
color
=
COLOR
[
num
(
COLOR
.
length
)];
return
new
Color
(
color
[
0
],
color
[
1
],
color
[
2
]);
}
/**
* 验证码输出,抽象方法,由子类实现
*
* @param os 输出流
* @return 是否成功
*/
public
abstract
boolean
out
(
OutputStream
os
);
/**
* 获取当前的验证码
*
* @return 字符串
*/
public
String
text
()
{
checkAlpha
();
return
chars
;
}
/**
* 获取当前验证码的字符数组
*
* @return 字符数组
*/
public
char
[]
textChar
()
{
checkAlpha
();
return
chars
.
toCharArray
();
}
/**
* 检查验证码是否生成,没有这立即生成
*/
public
void
checkAlpha
()
{
if
(
chars
==
null
)
{
alphas
();
// 生成验证码
}
}
/**
* 随机画干扰线
*
* @param num 数量
* @param g Graphics2D
*/
public
void
drawLine
(
int
num
,
Graphics2D
g
)
{
drawLine
(
num
,
null
,
g
);
}
/**
* 随机画干扰线
*
* @param num 数量
* @param color 颜色
* @param g Graphics2D
*/
public
void
drawLine
(
int
num
,
Color
color
,
Graphics2D
g
)
{
for
(
int
i
=
0
;
i
<
num
;
i
++)
{
g
.
setColor
(
color
==
null
?
color
(
150
,
250
)
:
color
);
int
x1
=
num
(-
10
,
width
-
10
);
int
y1
=
num
(
5
,
height
-
5
);
int
x2
=
num
(
10
,
width
+
10
);
int
y2
=
num
(
2
,
height
-
2
);
g
.
drawLine
(
x1
,
y1
,
x2
,
y2
);
}
}
/**
* 随机画干扰圆
*
* @param num 数量
* @param g Graphics2D
*/
public
void
drawOval
(
int
num
,
Graphics2D
g
)
{
for
(
int
i
=
0
;
i
<
num
;
i
++)
{
g
.
setColor
(
color
(
100
,
250
));
g
.
drawOval
(
num
(
width
),
num
(
height
),
10
+
num
(
20
),
10
+
num
(
20
));
}
}
/**
* 随机画干扰圆
*
* @param num 数量
* @param color 颜色
* @param g Graphics2D
*/
public
void
drawOval
(
int
num
,
Color
color
,
Graphics2D
g
)
{
for
(
int
i
=
0
;
i
<
num
;
i
++)
{
g
.
setColor
(
color
==
null
?
color
(
100
,
250
)
:
color
);
g
.
drawOval
(
num
(
width
),
num
(
height
),
10
+
num
(
20
),
10
+
num
(
20
));
}
}
public
Font
getFont
()
{
return
font
;
}
public
void
setFont
(
Font
font
)
{
this
.
font
=
font
;
}
public
int
getLen
()
{
return
len
;
}
public
void
setLen
(
int
len
)
{
this
.
len
=
len
;
}
public
int
getWidth
()
{
return
width
;
}
public
void
setWidth
(
int
width
)
{
this
.
width
=
width
;
}
public
int
getHeight
()
{
return
height
;
}
public
void
setHeight
(
int
height
)
{
this
.
height
=
height
;
}
public
int
getCharType
()
{
return
charType
;
}
public
void
setCharType
(
int
charType
)
{
this
.
charType
=
charType
;
}
}
\ No newline at end of file
src/main/java/com/wf/captcha/ChineseCaptcha.java
0 → 100644
View file @
01a3a848
package
com
.
wf
.
captcha
;
import
javax.imageio.ImageIO
;
import
java.awt.*
;
import
java.awt.image.BufferedImage
;
import
java.io.IOException
;
import
java.io.OutputStream
;
public
class
ChineseCaptcha
extends
AbstractChineseCaptcha
{
public
ChineseCaptcha
()
{
super
();
}
public
ChineseCaptcha
(
int
width
,
int
height
)
{
this
();
setWidth
(
width
);
setHeight
(
height
);
}
public
ChineseCaptcha
(
int
width
,
int
height
,
int
len
)
{
this
(
width
,
height
);
setLen
(
len
);
}
public
ChineseCaptcha
(
int
width
,
int
height
,
int
len
,
Font
font
)
{
this
(
width
,
height
,
len
);
setFont
(
font
);
}
/**
* 生成验证码
*
* @param out 输出流
* @return 是否成功
*/
@Override
public
boolean
out
(
OutputStream
out
)
{
checkAlpha
();
return
graphicsImage
(
textChar
(),
out
);
}
/**
* 生成验证码图形
*
* @param strs 验证码
* @param out 输出流
* @return boolean
*/
private
boolean
graphicsImage
(
char
[]
strs
,
OutputStream
out
)
{
boolean
ok
;
try
{
BufferedImage
bi
=
new
BufferedImage
(
width
,
height
,
BufferedImage
.
TYPE_INT_RGB
);
Graphics2D
g
=
(
Graphics2D
)
bi
.
getGraphics
();
AlphaComposite
ac3
;
int
len
=
strs
.
length
;
g
.
setColor
(
Color
.
WHITE
);
g
.
fillRect
(
0
,
0
,
width
,
height
);
// 抗锯齿
g
.
setColor
(
color
());
g
.
setRenderingHint
(
RenderingHints
.
KEY_ANTIALIASING
,
RenderingHints
.
VALUE_ANTIALIAS_ON
);
int
hp
=
(
height
-
font
.
getSize
())
>>
1
;
int
h
=
height
-
hp
;
int
w
=
width
/
strs
.
length
;
int
sp
=
(
w
-
font
.
getSize
())
/
2
;
// 画字符串
for
(
int
i
=
0
;
i
<
len
;
i
++)
{
// 计算坐标
int
x
=
i
*
w
+
sp
+
num
(-
5
,
5
);
int
y
=
h
+
num
(-
5
,
5
);
if
(
x
<
5
)
{
x
=
5
;
}
if
(
x
+
font
.
getSize
()
>
width
)
{
x
=
width
-
font
.
getSize
();
}
if
(
y
>
height
)
{
y
=
height
;
}
if
(
y
-
font
.
getSize
()
<
0
)
{
y
=
font
.
getSize
();
}
g
.
setFont
(
font
.
deriveFont
(
num
(
2
)
==
0
?
Font
.
PLAIN
:
Font
.
ITALIC
));
g
.
drawString
(
String
.
valueOf
(
strs
[
i
]),
x
,
y
);
}
// 随机画干扰线
g
.
setStroke
(
new
BasicStroke
(
1.25f
,
BasicStroke
.
CAP_BUTT
,
BasicStroke
.
JOIN_BEVEL
));
ac3
=
AlphaComposite
.
getInstance
(
AlphaComposite
.
SRC_OVER
,
0.7f
);
// 指定透明度
g
.
setComposite
(
ac3
);
drawLine
(
2
,
g
.
getColor
(),
g
);
// 画干扰圆圈
drawOval
(
5
,
g
.
getColor
(),
g
);
ImageIO
.
write
(
bi
,
"png"
,
out
);
out
.
flush
();
ok
=
true
;
}
catch
(
IOException
e
)
{
ok
=
false
;
e
.
printStackTrace
();
}
finally
{
try
{
out
.
close
();
}
catch
(
IOException
e
)
{
e
.
printStackTrace
();
}
}
return
ok
;
}
}
src/main/java/com/wf/captcha/ChineseGifCaptcha.java
0 → 100644
View file @
01a3a848
package
com
.
wf
.
captcha
;
import
java.awt.*
;
import
java.awt.image.BufferedImage
;
import
java.io.IOException
;
import
java.io.OutputStream
;
public
class
ChineseGifCaptcha
extends
AbstractChineseCaptcha
{
public
ChineseGifCaptcha
()
{
}
public
ChineseGifCaptcha
(
int
width
,
int
height
)
{
setWidth
(
width
);
setHeight
(
height
);
}
public
ChineseGifCaptcha
(
int
width
,
int
height
,
int
len
)
{
this
(
width
,
height
);
setLen
(
len
);
}
public
ChineseGifCaptcha
(
int
width
,
int
height
,
int
len
,
Font
font
)
{
this
(
width
,
height
,
len
);
setFont
(
font
);
}
@Override
public
boolean
out
(
OutputStream
os
)
{
checkAlpha
();
boolean
ok
;
try
{
char
[]
rands
=
textChar
();
// 获取验证码数组
GifEncoder
gifEncoder
=
new
GifEncoder
();
gifEncoder
.
start
(
os
);
gifEncoder
.
setQuality
(
180
);
gifEncoder
.
setDelay
(
100
);
gifEncoder
.
setRepeat
(
0
);
BufferedImage
frame
;
Color
fontcolor
=
color
();
for
(
int
i
=
0
;
i
<
len
;
i
++)
{
frame
=
graphicsImage
(
fontcolor
,
rands
,
i
);
gifEncoder
.
addFrame
(
frame
);
frame
.
flush
();
}
gifEncoder
.
finish
();
ok
=
true
;
}
finally
{
try
{
os
.
close
();
}
catch
(
IOException
e
)
{
e
.
printStackTrace
();
}
}
return
ok
;
}
/**
* 画随机码图
*
* @param fontcolor 随机字体颜色
* @param strs 字符数组
* @param flag 透明度使用
* @return BufferedImage
*/
private
BufferedImage
graphicsImage
(
Color
fontcolor
,
char
[]
strs
,
int
flag
)
{
BufferedImage
image
=
new
BufferedImage
(
width
,
height
,
BufferedImage
.
TYPE_INT_RGB
);
Graphics2D
g2d
=
(
Graphics2D
)
image
.
getGraphics
();
g2d
.
setColor
(
Color
.
WHITE
);
// 填充背景颜色
g2d
.
fillRect
(
0
,
0
,
width
,
height
);
// 抗锯齿
AlphaComposite
ac3
;
g2d
.
setColor
(
fontcolor
);
g2d
.
setRenderingHint
(
RenderingHints
.
KEY_ANTIALIASING
,
RenderingHints
.
VALUE_ANTIALIAS_ON
);
// 画验证码
int
hp
=
(
height
-
font
.
getSize
())
>>
1
;
int
h
=
height
-
hp
;
int
w
=
width
/
strs
.
length
;
int
sp
=
(
w
-
font
.
getSize
())
/
2
;
for
(
int
i
=
0
;
i
<
len
;
i
++)
{
ac3
=
AlphaComposite
.
getInstance
(
AlphaComposite
.
SRC_OVER
,
getAlpha
(
flag
,
i
));
g2d
.
setComposite
(
ac3
);
// 计算坐标
int
x
=
i
*
w
+
sp
+
num
(-
3
,
3
);
int
y
=
h
+
num
(-
3
,
3
);
if
(
x
<
0
)
{
x
=
0
;
}
if
(
x
+
font
.
getSize
()
>
width
)
{
x
=
width
-
font
.
getSize
();
}
if
(
y
>
height
)
{
y
=
height
;
}
if
(
y
-
font
.
getSize
()
<
0
)
{
y
=
font
.
getSize
();
}
g2d
.
setFont
(
font
.
deriveFont
(
num
(
2
)
==
0
?
Font
.
PLAIN
:
Font
.
ITALIC
));
g2d
.
drawString
(
String
.
valueOf
(
strs
[
i
]),
x
,
y
);
}
// 随机画干扰线
g2d
.
setStroke
(
new
BasicStroke
(
1.25f
,
BasicStroke
.
CAP_BUTT
,
BasicStroke
.
JOIN_BEVEL
));
ac3
=
AlphaComposite
.
getInstance
(
AlphaComposite
.
SRC_OVER
,
0.45f
);
g2d
.
setComposite
(
ac3
);
drawLine
(
1
,
g2d
.
getColor
(),
g2d
);
// 画干扰圆圈
drawOval
(
3
,
g2d
.
getColor
(),
g2d
);
g2d
.
dispose
();
return
image
;
}
/**
* 获取透明度,从0到1,自动计算步长
*
* @param i
* @param j
* @return 透明度
*/
private
float
getAlpha
(
int
i
,
int
j
)
{
int
num
=
i
+
j
;
float
r
=
(
float
)
1
/
(
len
-
1
);
float
s
=
len
*
r
;
return
num
>=
len
?
(
num
*
r
-
s
)
:
num
*
r
;
}
}
src/main/java/com/wf/captcha/Encoder.java
0 → 100644
View file @
01a3a848
package
com
.
wf
.
captcha
;
import
java.io.IOException
;
import
java.io.OutputStream
;
public
class
Encoder
{
private
static
final
int
EOF
=
-
1
;
// 图片的宽高
private
int
imgW
,
imgH
;
private
byte
[]
pixAry
;
private
int
initCodeSize
;
// 验证码位数
private
int
remaining
;
// 剩余数量
private
int
curPixel
;
// 像素
static
final
int
BITS
=
12
;
static
final
int
HSIZE
=
5003
;
// 80% 占用率
int
n_bits
;
// number of bits/code
int
maxbits
=
BITS
;
// user settable max # bits/code
int
maxcode
;
// maximum code, given n_bits
int
maxmaxcode
=
1
<<
BITS
;
// should NEVER generate this code
int
[]
htab
=
new
int
[
HSIZE
];
int
[]
codetab
=
new
int
[
HSIZE
];
int
hsize
=
HSIZE
;
// for dynamic table sizing
int
free_ent
=
0
;
// first unused entry
// block compression parameters -- after all codes are used up,
// and compression rate changes, start over.
boolean
clear_flg
=
false
;
// Algorithm: use open addressing double hashing (no chaining) on the
// prefix code / next character combination. We do a variant of Knuth's
// algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
// secondary probe. Here, the modular division first probe is gives way
// to a faster exclusive-or manipulation. Also do block compression with
// an adaptive reset, whereby the code table is cleared when the compression
// ratio decreases, but after the table fills. The variable-length output
// codes are re-sized at this point, and a special CLEAR code is generated
// for the decompressor. Late addition: construct the table according to
// file size for noticeable speed improvement on small files. Please direct
// questions about this implementation to ames!jaw.
int
g_init_bits
;
int
ClearCode
;
int
EOFCode
;
// output
//
// Output the given code.
// Inputs:
// code: A n_bits-bit integer. If == -1, then EOF. This assumes
// that n_bits =< wordsize - 1.
// Outputs:
// Outputs code to the file.
// Assumptions:
// Chars are 8 bits long.
// Algorithm:
// Maintain a BITS character long buffer (so that 8 codes will
// fit in it exactly). Use the VAX insv instruction to insert each
// code in turn. When the buffer fills up empty it and start over.
int
cur_accum
=
0
;
int
cur_bits
=
0
;
int
masks
[]
=
{
0x0000
,
0x0001
,
0x0003
,
0x0007
,
0x000F
,
0x001F
,
0x003F
,
0x007F
,
0x00FF
,
0x01FF
,
0x03FF
,
0x07FF
,
0x0FFF
,
0x1FFF
,
0x3FFF
,
0x7FFF
,
0xFFFF
};
// Number of characters so far in this 'packet'
int
a_count
;
// Define the storage for the packet accumulator
byte
[]
accum
=
new
byte
[
256
];
//----------------------------------------------------------------------------
/**
* @param width 宽度
* @param height 高度
* @param pixels 像素
* @param color_depth 颜色
*/
Encoder
(
int
width
,
int
height
,
byte
[]
pixels
,
int
color_depth
)
{
imgW
=
width
;
imgH
=
height
;
pixAry
=
pixels
;
initCodeSize
=
Math
.
max
(
2
,
color_depth
);
}
// Add a character to the end of the current packet, and if it is 254
// characters, flush the packet to disk.
/**
* @param c 字节
* @param outs 输出流
* @throws IOException IO异常
*/
void
char_out
(
byte
c
,
OutputStream
outs
)
throws
IOException
{
accum
[
a_count
++]
=
c
;
if
(
a_count
>=
254
)
{
flush_char
(
outs
);
}
}
// Clear out the hash table
// table clear for block compress
/**
* @param outs 输出流
* @throws IOException IO异常
*/
void
cl_block
(
OutputStream
outs
)
throws
IOException
{
cl_hash
(
hsize
);
free_ent
=
ClearCode
+
2
;
clear_flg
=
true
;
output
(
ClearCode
,
outs
);
}
// reset code table
/**
* @param hsize int
*/
void
cl_hash
(
int
hsize
)
{
for
(
int
i
=
0
;
i
<
hsize
;
++
i
)
{
htab
[
i
]
=
-
1
;
}
}
/**
* @param init_bits int
* @param outs 输出流
* @throws IOException IO异常
*/
void
compress
(
int
init_bits
,
OutputStream
outs
)
throws
IOException
{
int
fcode
;
int
i
/* = 0 */
;
int
c
;
int
ent
;
int
disp
;
int
hsize_reg
;
int
hshift
;
// Set up the globals: g_init_bits - initial number of bits
g_init_bits
=
init_bits
;
// Set up the necessary values
clear_flg
=
false
;
n_bits
=
g_init_bits
;
maxcode
=
MAXCODE
(
n_bits
);
ClearCode
=
1
<<
(
init_bits
-
1
);
EOFCode
=
ClearCode
+
1
;
free_ent
=
ClearCode
+
2
;
a_count
=
0
;
// clear packet
ent
=
nextPixel
();
hshift
=
0
;
for
(
fcode
=
hsize
;
fcode
<
65536
;
fcode
*=
2
)
{
++
hshift
;
}
hshift
=
8
-
hshift
;
// set hash code range bound
hsize_reg
=
hsize
;
cl_hash
(
hsize_reg
);
// clear hash table
output
(
ClearCode
,
outs
);
outer_loop:
while
((
c
=
nextPixel
())
!=
EOF
)
{
fcode
=
(
c
<<
maxbits
)
+
ent
;
i
=
(
c
<<
hshift
)
^
ent
;
// xor hashing
if
(
htab
[
i
]
==
fcode
)
{
ent
=
codetab
[
i
];
continue
;
}
else
if
(
htab
[
i
]
>=
0
)
// non-empty slot
{
disp
=
hsize_reg
-
i
;
// secondary hash (after G. Knott)
if
(
i
==
0
)
{
disp
=
1
;
}
do
{
if
((
i
-=
disp
)
<
0
)
{
i
+=
hsize_reg
;
}
if
(
htab
[
i
]
==
fcode
)
{
ent
=
codetab
[
i
];
continue
outer_loop
;
}
}
while
(
htab
[
i
]
>=
0
);
}
output
(
ent
,
outs
);
ent
=
c
;
if
(
free_ent
<
maxmaxcode
)
{
codetab
[
i
]
=
free_ent
++;
// code -> hashtable
htab
[
i
]
=
fcode
;
}
else
{
cl_block
(
outs
);
}
}
// Put out the final code.
output
(
ent
,
outs
);
output
(
EOFCode
,
outs
);
}
//----------------------------------------------------------------------------
/**
* @param os 输出流
* @throws IOException IO异常
*/
void
encode
(
OutputStream
os
)
throws
IOException
{
os
.
write
(
initCodeSize
);
// write "initial code size" byte
remaining
=
imgW
*
imgH
;
// reset navigation variables
curPixel
=
0
;
compress
(
initCodeSize
+
1
,
os
);
// compress and write the pixel data
os
.
write
(
0
);
// write block terminator
}
// Flush the packet to disk, and reset the accumulator
/**
* @param outs 输出流
* @throws IOException IO异常
*/
void
flush_char
(
OutputStream
outs
)
throws
IOException
{
if
(
a_count
>
0
)
{
outs
.
write
(
a_count
);
outs
.
write
(
accum
,
0
,
a_count
);
a_count
=
0
;
}
}
/**
* @param n_bits int
* @return int
*/
final
int
MAXCODE
(
int
n_bits
)
{
return
(
1
<<
n_bits
)
-
1
;
}
//----------------------------------------------------------------------------
// Return the next pixel from the image
//----------------------------------------------------------------------------
/**
* @return int
*/
private
int
nextPixel
()
{
if
(
remaining
==
0
)
{
return
EOF
;
}
--
remaining
;
byte
pix
=
pixAry
[
curPixel
++];
return
pix
&
0xff
;
}
/**
* @param code int
* @param outs 输出流
* @throws IOException IO异常
*/
void
output
(
int
code
,
OutputStream
outs
)
throws
IOException
{
cur_accum
&=
masks
[
cur_bits
];
if
(
cur_bits
>
0
)
{
cur_accum
|=
(
code
<<
cur_bits
);
}
else
{
cur_accum
=
code
;
}
cur_bits
+=
n_bits
;
while
(
cur_bits
>=
8
)
{
char_out
((
byte
)
(
cur_accum
&
0xff
),
outs
);
cur_accum
>>=
8
;
cur_bits
-=
8
;
}
// If the next entry is going to be too big for the code size,
// then increase it, if possible.
if
(
free_ent
>
maxcode
||
clear_flg
)
{
if
(
clear_flg
)
{
maxcode
=
MAXCODE
(
n_bits
=
g_init_bits
);
clear_flg
=
false
;
}
else
{
++
n_bits
;
if
(
n_bits
==
maxbits
)
{
maxcode
=
maxmaxcode
;
}
else
{
maxcode
=
MAXCODE
(
n_bits
);
}
}
}
if
(
code
==
EOFCode
)
{
// At EOF, write the rest of the buffer.
while
(
cur_bits
>
0
)
{
char_out
((
byte
)
(
cur_accum
&
0xff
),
outs
);
cur_accum
>>=
8
;
cur_bits
-=
8
;
}
flush_char
(
outs
);
}
}
}
src/main/java/com/wf/captcha/GifCaptcha.java
0 → 100644
View file @
01a3a848
package
com
.
wf
.
captcha
;
import
java.awt.*
;
import
java.awt.image.BufferedImage
;
import
java.io.IOException
;
import
java.io.OutputStream
;
public
class
GifCaptcha
extends
BaseCaptcha
{
public
GifCaptcha
()
{
}
public
GifCaptcha
(
int
width
,
int
height
)
{
setWidth
(
width
);
setHeight
(
height
);
}
public
GifCaptcha
(
int
width
,
int
height
,
int
len
)
{
this
(
width
,
height
);
setLen
(
len
);
}
public
GifCaptcha
(
int
width
,
int
height
,
int
len
,
Font
font
)
{
this
(
width
,
height
,
len
);
setFont
(
font
);
}
@Override
public
boolean
out
(
OutputStream
os
)
{
checkAlpha
();
boolean
ok
;
try
{
char
[]
rands
=
textChar
();
// 获取验证码数组
GifEncoder
gifEncoder
=
new
GifEncoder
();
gifEncoder
.
start
(
os
);
gifEncoder
.
setQuality
(
180
);
gifEncoder
.
setDelay
(
100
);
gifEncoder
.
setRepeat
(
0
);
BufferedImage
frame
;
Color
fontcolor
[]
=
new
Color
[
len
];
for
(
int
i
=
0
;
i
<
len
;
i
++)
{
fontcolor
[
i
]
=
new
Color
(
20
+
num
(
110
),
20
+
num
(
110
),
20
+
num
(
110
));
}
for
(
int
i
=
0
;
i
<
len
;
i
++)
{
frame
=
graphicsImage
(
fontcolor
,
rands
,
i
);
gifEncoder
.
addFrame
(
frame
);
frame
.
flush
();
}
gifEncoder
.
finish
();
ok
=
true
;
}
finally
{
try
{
os
.
close
();
}
catch
(
IOException
e
)
{
e
.
printStackTrace
();
}
}
return
ok
;
}
/**
* 画随机码图
*
* @param fontcolor 随机字体颜色
* @param strs 字符数组
* @param flag 透明度使用
* @return BufferedImage
*/
private
BufferedImage
graphicsImage
(
Color
[]
fontcolor
,
char
[]
strs
,
int
flag
)
{
BufferedImage
image
=
new
BufferedImage
(
width
,
height
,
BufferedImage
.
TYPE_INT_RGB
);
Graphics2D
g2d
=
(
Graphics2D
)
image
.
getGraphics
();
// 填充背景颜色
g2d
.
setColor
(
Color
.
WHITE
);
g2d
.
fillRect
(
0
,
0
,
width
,
height
);
// 抗锯齿
g2d
.
setRenderingHint
(
RenderingHints
.
KEY_ANTIALIASING
,
RenderingHints
.
VALUE_ANTIALIAS_ON
);
g2d
.
setStroke
(
new
BasicStroke
(
1.3f
,
BasicStroke
.
CAP_BUTT
,
BasicStroke
.
JOIN_BEVEL
));
// 画干扰圆圈
drawOval
(
4
,
g2d
);
// 随机画干扰线
drawLine
(
2
,
g2d
);
// 画验证码
int
hp
=
(
height
-
font
.
getSize
())
>>
1
;
int
h
=
height
-
hp
;
int
w
=
width
/
strs
.
length
;
int
sp
=
(
w
-
font
.
getSize
())
/
2
;
for
(
int
i
=
0
;
i
<
strs
.
length
;
i
++)
{
AlphaComposite
ac3
=
AlphaComposite
.
getInstance
(
AlphaComposite
.
SRC_OVER
,
getAlpha
(
flag
,
i
));
g2d
.
setComposite
(
ac3
);
g2d
.
setColor
(
fontcolor
[
i
]);
// 计算坐标
int
x
=
i
*
w
+
sp
+
num
(
3
);
int
y
=
h
-
num
(
3
,
6
);
// 调整溢出的字
if
(
x
<
8
)
{
x
=
8
;
}
if
(
x
+
font
.
getSize
()
>
width
)
{
x
=
width
-
font
.
getSize
();
}
if
(
y
>
height
)
{
y
=
height
;
}
if
(
y
-
font
.
getSize
()
<
0
)
{
y
=
font
.
getSize
();
}
g2d
.
setFont
(
font
.
deriveFont
(
num
(
2
)
==
0
?
Font
.
PLAIN
:
Font
.
ITALIC
));
g2d
.
drawString
(
String
.
valueOf
(
strs
[
i
]),
x
,
y
);
}
g2d
.
dispose
();
return
image
;
}
/**
* 获取透明度,从0到1,自动计算步长
*
* @param i
* @param j
* @return 透明度
*/
private
float
getAlpha
(
int
i
,
int
j
)
{
int
num
=
i
+
j
;
float
r
=
(
float
)
1
/
(
len
-
1
);
float
s
=
len
*
r
;
return
num
>=
len
?
(
num
*
r
-
s
)
:
num
*
r
;
}
}
src/main/java/com/wf/captcha/GifEncoder.java
0 → 100644
View file @
01a3a848
package
com
.
wf
.
captcha
;
import
java.awt.*
;
import
java.awt.image.BufferedImage
;
import
java.awt.image.DataBufferByte
;
import
java.io.*
;
/**
* Gif生成工具
* Class AnimatedGifEncoder - Encodes a GIF file consisting of one or
* more frames.
* <pre>
* Example:
* AnimatedGifEncoder e = new AnimatedGifEncoder();
* e.start(outputFileName);
* e.setDelay(1000); // 1 frame per sec
* e.addFrame(image1);
* e.addFrame(image2);
* e.finish();
* </pre>
* No copyright asserted on the source code of this class. May be used
* for any purpose, however, refer to the Unisys LZW patent for restrictions
* on use of the associated Encoder class. Please forward any corrections
* to questions at fmsware.com.
*/
public
class
GifEncoder
{
protected
int
width
;
// image size
protected
int
height
;
protected
Color
transparent
=
null
;
// transparent color if given
protected
int
transIndex
;
// transparent index in color table
protected
int
repeat
=
-
1
;
// no repeat
protected
int
delay
=
0
;
// frame delay (hundredths)
protected
boolean
started
=
false
;
// ready to output frames
protected
OutputStream
out
;
protected
BufferedImage
image
;
// current frame
protected
byte
[]
pixels
;
// BGR byte array from frame
protected
byte
[]
indexedPixels
;
// converted frame indexed to palette
protected
int
colorDepth
;
// number of bit planes
protected
byte
[]
colorTab
;
// RGB palette
protected
boolean
[]
usedEntry
=
new
boolean
[
256
];
// active palette entries
protected
int
palSize
=
7
;
// color table size (bits-1)
protected
int
dispose
=
-
1
;
// disposal code (-1 = use default)
protected
boolean
closeStream
=
false
;
// close stream when finished
protected
boolean
firstFrame
=
true
;
protected
boolean
sizeSet
=
false
;
// if false, get size from first frame
protected
int
sample
=
10
;
// default sample interval for quantizer
/**
* Sets the delay time between each frame, or changes it
* for subsequent frames (applies to last frame added).
*
* @param ms int delay time in milliseconds
*/
public
void
setDelay
(
int
ms
)
{
delay
=
Math
.
round
(
ms
/
10.0f
);
}
/**
* Sets the GIF frame disposal code for the last added frame
* and any subsequent frames. Default is 0 if no transparent
* color has been set, otherwise 2.
*
* @param code int disposal code.
*/
public
void
setDispose
(
int
code
)
{
if
(
code
>=
0
)
{
dispose
=
code
;
}
}
/**
* Sets the number of times the set of GIF frames
* should be played. Default is 1; 0 means play
* indefinitely. Must be invoked before the first
* image is added.
*
* @param iter int number of iterations.
*/
public
void
setRepeat
(
int
iter
)
{
if
(
iter
>=
0
)
{
repeat
=
iter
;
}
}
/**
* Sets the transparent color for the last added frame
* and any subsequent frames.
* Since all colors are subject to modification
* in the quantization process, the color in the final
* palette for each frame closest to the given color
* becomes the transparent color for that frame.
* May be set to null to indicate no transparent color.
*
* @param c Color to be treated as transparent on display.
*/
public
void
setTransparent
(
Color
c
)
{
transparent
=
c
;
}
/**
* Adds next GIF frame. The frame is not written immediately, but is
* actually deferred until the next frame is received so that timing
* data can be inserted. Invoking <code>finish()</code> flushes all
* frames. If <code>setSize</code> was not invoked, the size of the
* first image is used for all subsequent frames.
*
* @param im BufferedImage containing frame to write.
* @return true if successful.
*/
public
boolean
addFrame
(
BufferedImage
im
)
{
if
((
im
==
null
)
||
!
started
)
{
return
false
;
}
boolean
ok
=
true
;
try
{
if
(!
sizeSet
)
{
// use first frame's size
setSize
(
im
.
getWidth
(),
im
.
getHeight
());
}
image
=
im
;
getImagePixels
();
// convert to correct format if necessary
analyzePixels
();
// build color table & map pixels
if
(
firstFrame
)
{
writeLSD
();
// logical screen descriptior
writePalette
();
// global color table
if
(
repeat
>=
0
)
{
// use NS app extension to indicate reps
writeNetscapeExt
();
}
}
writeGraphicCtrlExt
();
// write graphic control extension
writeImageDesc
();
// image descriptor
if
(!
firstFrame
)
{
writePalette
();
// local color table
}
writePixels
();
// encode and write pixel data
firstFrame
=
false
;
}
catch
(
IOException
e
)
{
ok
=
false
;
}
return
ok
;
}
//added by alvaro
public
boolean
outFlush
()
{
boolean
ok
=
true
;
try
{
out
.
flush
();
return
ok
;
}
catch
(
IOException
e
)
{
ok
=
false
;
}
return
ok
;
}
public
byte
[]
getFrameByteArray
()
{
return
((
ByteArrayOutputStream
)
out
).
toByteArray
();
}
/**
* Flushes any pending data and closes output file.
* If writing to an OutputStream, the stream is not
* closed.
*
* @return boolean
*/
public
boolean
finish
()
{
if
(!
started
)
{
return
false
;
}
boolean
ok
=
true
;
started
=
false
;
try
{
out
.
write
(
0x3b
);
// gif trailer
out
.
flush
();
if
(
closeStream
)
{
out
.
close
();
}
}
catch
(
IOException
e
)
{
ok
=
false
;
}
return
ok
;
}
public
void
reset
()
{
// reset for subsequent use
transIndex
=
0
;
out
=
null
;
image
=
null
;
pixels
=
null
;
indexedPixels
=
null
;
colorTab
=
null
;
closeStream
=
false
;
firstFrame
=
true
;
}
/**
* Sets frame rate in frames per second. Equivalent to
* <code>setDelay(1000/fps)</code>.
*
* @param fps float frame rate (frames per second)
*/
public
void
setFrameRate
(
float
fps
)
{
if
(
new
Float
(
0
F
).
equals
(
fps
))
{
delay
=
Math
.
round
(
100
f
/
fps
);
}
}
/**
* Sets quality of color quantization (conversion of images
* to the maximum 256 colors allowed by the GIF specification).
* Lower values (minimum = 1) produce better colors, but slow
* processing significantly. 10 is the default, and produces
* good color mapping at reasonable speeds. Values greater
* than 20 do not yield significant improvements in speed.
*
* @param quality int greater than 0.
*/
public
void
setQuality
(
int
quality
)
{
if
(
quality
<
1
)
{
quality
=
1
;
}
sample
=
quality
;
}
/**
* Sets the GIF frame size. The default size is the
* size of the first frame added if this method is
* not invoked.
*
* @param w int frame width.
* @param h int frame width.
*/
public
void
setSize
(
int
w
,
int
h
)
{
if
(
started
&&
!
firstFrame
)
{
return
;
}
width
=
w
;
height
=
h
;
if
(
width
<
1
)
{
width
=
320
;
}
if
(
height
<
1
)
{
height
=
240
;
}
sizeSet
=
true
;
}
/**
* Initiates GIF file creation on the given stream. The stream
* is not closed automatically.
*
* @param os OutputStream on which GIF images are written.
* @return false if initial write failed.
*/
public
boolean
start
(
OutputStream
os
)
{
if
(
os
==
null
)
{
return
false
;
}
boolean
ok
=
true
;
closeStream
=
false
;
out
=
os
;
try
{
writeString
(
"GIF89a"
);
// header
}
catch
(
IOException
e
)
{
ok
=
false
;
}
return
started
=
ok
;
}
/**
* Initiates writing of a GIF file with the specified name.
*
* @param file String containing output file name.
* @return false if open or initial write failed.
*/
public
boolean
start
(
String
file
)
{
boolean
ok
=
true
;
try
{
out
=
new
BufferedOutputStream
(
new
FileOutputStream
(
file
));
ok
=
start
(
out
);
closeStream
=
true
;
}
catch
(
IOException
e
)
{
ok
=
false
;
}
return
started
=
ok
;
}
/**
* Analyzes image colors and creates color map.
*/
protected
void
analyzePixels
()
{
int
len
=
pixels
.
length
;
int
nPix
=
len
/
3
;
indexedPixels
=
new
byte
[
nPix
];
Quant
nq
=
new
Quant
(
pixels
,
len
,
sample
);
// initialize quantizer
colorTab
=
nq
.
process
();
// create reduced palette
// convert map from BGR to RGB
for
(
int
i
=
0
;
i
<
colorTab
.
length
;
i
+=
3
)
{
byte
temp
=
colorTab
[
i
];
colorTab
[
i
]
=
colorTab
[
i
+
2
];
colorTab
[
i
+
2
]
=
temp
;
usedEntry
[
i
/
3
]
=
false
;
}
// map image pixels to new palette
int
k
=
0
;
for
(
int
i
=
0
;
i
<
nPix
;
i
++)
{
int
index
=
nq
.
map
(
pixels
[
k
++]
&
0xff
,
pixels
[
k
++]
&
0xff
,
pixels
[
k
++]
&
0xff
);
usedEntry
[
index
]
=
true
;
indexedPixels
[
i
]
=
(
byte
)
index
;
}
pixels
=
null
;
colorDepth
=
8
;
palSize
=
7
;
// get closest match to transparent color if specified
if
(
transparent
!=
null
)
{
transIndex
=
findClosest
(
transparent
);
}
}
/**
* Returns index of palette color closest to c
*
* @param c color
* @return int
*/
protected
int
findClosest
(
Color
c
)
{
if
(
colorTab
==
null
)
{
return
-
1
;
}
int
r
=
c
.
getRed
();
int
g
=
c
.
getGreen
();
int
b
=
c
.
getBlue
();
int
minpos
=
0
;
int
dmin
=
256
*
256
*
256
;
int
len
=
colorTab
.
length
;
for
(
int
i
=
0
;
i
<
len
;
)
{
int
dr
=
r
-
(
colorTab
[
i
++]
&
0xff
);
int
dg
=
g
-
(
colorTab
[
i
++]
&
0xff
);
int
db
=
b
-
(
colorTab
[
i
]
&
0xff
);
int
d
=
dr
*
dr
+
dg
*
dg
+
db
*
db
;
int
index
=
i
/
3
;
if
(
usedEntry
[
index
]
&&
(
d
<
dmin
))
{
dmin
=
d
;
minpos
=
index
;
}
i
++;
}
return
minpos
;
}
/**
* Extracts image pixels into byte array "pixels"
*/
protected
void
getImagePixels
()
{
int
w
=
image
.
getWidth
();
int
h
=
image
.
getHeight
();
int
type
=
image
.
getType
();
if
((
w
!=
width
)
||
(
h
!=
height
)
||
(
type
!=
BufferedImage
.
TYPE_3BYTE_BGR
))
{
// create new image with right size/format
BufferedImage
temp
=
new
BufferedImage
(
width
,
height
,
BufferedImage
.
TYPE_3BYTE_BGR
);
Graphics2D
g
=
temp
.
createGraphics
();
g
.
drawImage
(
image
,
0
,
0
,
null
);
image
=
temp
;
}
pixels
=
((
DataBufferByte
)
image
.
getRaster
().
getDataBuffer
()).
getData
();
}
/**
* Writes Graphic Control Extension
*
* @throws IOException IO异常
*/
protected
void
writeGraphicCtrlExt
()
throws
IOException
{
out
.
write
(
0x21
);
// extension introducer
out
.
write
(
0xf9
);
// GCE label
out
.
write
(
4
);
// data block size
int
transp
,
disp
;
if
(
transparent
==
null
)
{
transp
=
0
;
disp
=
0
;
// dispose = no action
}
else
{
transp
=
1
;
disp
=
2
;
// force clear if using transparent color
}
if
(
dispose
>=
0
)
{
disp
=
dispose
&
7
;
// user override
}
disp
<<=
2
;
// packed fields
out
.
write
(
0
|
// 1:3 reserved
disp
|
// 4:6 disposal
0
|
// 7 user input - 0 = none
transp
);
// 8 transparency flag
writeShort
(
delay
);
// delay x 1/100 sec
out
.
write
(
transIndex
);
// transparent color index
out
.
write
(
0
);
// block terminator
}
/**
* Writes Image Descriptor
*
* @throws IOException IO异常
*/
protected
void
writeImageDesc
()
throws
IOException
{
out
.
write
(
0x2c
);
// image separator
writeShort
(
0
);
// image position x,y = 0,0
writeShort
(
0
);
writeShort
(
width
);
// image size
writeShort
(
height
);
// packed fields
if
(
firstFrame
)
{
// no LCT - GCT is used for first (or only) frame
out
.
write
(
0
);
}
else
{
// specify normal LCT
out
.
write
(
0x80
|
// 1 local color table 1=yes
0
|
// 2 interlace - 0=no
0
|
// 3 sorted - 0=no
0
|
// 4-5 reserved
palSize
);
// 6-8 size of color table
}
}
/**
* Writes Logical Screen Descriptor
*
* @throws IOException IO异常
*/
protected
void
writeLSD
()
throws
IOException
{
// logical screen size
writeShort
(
width
);
writeShort
(
height
);
// packed fields
out
.
write
((
0x80
|
// 1 : global color table flag = 1 (gct used)
0x70
|
// 2-4 : color resolution = 7
0x00
|
// 5 : gct sort flag = 0
palSize
));
// 6-8 : gct size
out
.
write
(
0
);
// background color index
out
.
write
(
0
);
// pixel aspect ratio - assume 1:1
}
/**
* Writes Netscape application extension to define
* repeat count.
*
* @throws IOException IO异常
*/
protected
void
writeNetscapeExt
()
throws
IOException
{
out
.
write
(
0x21
);
// extension introducer
out
.
write
(
0xff
);
// app extension label
out
.
write
(
11
);
// block size
writeString
(
"NETSCAPE"
+
"2.0"
);
// app id + auth code
out
.
write
(
3
);
// sub-block size
out
.
write
(
1
);
// loop sub-block id
writeShort
(
repeat
);
// loop count (extra iterations, 0=repeat forever)
out
.
write
(
0
);
// block terminator
}
/**
* Writes color table
*
* @throws IOException IO异常
*/
protected
void
writePalette
()
throws
IOException
{
out
.
write
(
colorTab
,
0
,
colorTab
.
length
);
int
n
=
(
3
*
256
)
-
colorTab
.
length
;
for
(
int
i
=
0
;
i
<
n
;
i
++)
{
out
.
write
(
0
);
}
}
/**
* Encodes and writes pixel data
*
* @throws IOException IO异常
*/
protected
void
writePixels
()
throws
IOException
{
Encoder
encoder
=
new
Encoder
(
width
,
height
,
indexedPixels
,
colorDepth
);
encoder
.
encode
(
out
);
}
/**
* Write 16-bit value to output stream, LSB first
*
* @param value int
* @throws IOException IO异常
*/
protected
void
writeShort
(
int
value
)
throws
IOException
{
out
.
write
(
value
&
0xff
);
out
.
write
((
value
>>
8
)
&
0xff
);
}
/**
* Writes string to output stream
*
* @param s string
* @throws IOException IO异常
*/
protected
void
writeString
(
String
s
)
throws
IOException
{
for
(
int
i
=
0
;
i
<
s
.
length
();
i
++)
{
out
.
write
((
byte
)
s
.
charAt
(
i
));
}
}
}
src/main/java/com/wf/captcha/MathCaptcha.java
0 → 100644
View file @
01a3a848
package
com
.
wf
.
captcha
;
import
javax.imageio.ImageIO
;
import
java.awt.*
;
import
java.awt.image.BufferedImage
;
import
java.io.IOException
;
import
java.io.OutputStream
;
/**
* @author chenjh
* @since 2019/7/16 16:22
*/
public
class
MathCaptcha
extends
AbstractMathCaptcha
{
public
MathCaptcha
(
int
width
,
int
height
)
{
setWidth
(
width
);
setHeight
(
height
);
}
public
MathCaptcha
(
int
width
,
int
height
,
int
len
)
{
this
(
width
,
height
);
setLen
(
len
);
}
public
MathCaptcha
(
int
width
,
int
height
,
int
len
,
Font
font
)
{
this
(
width
,
height
,
len
);
setFont
(
font
);
}
/**
* 生成验证码
*
* @param out 输出流
* @return 是否成功
*/
@Override
public
boolean
out
(
OutputStream
out
)
{
char
[]
chars
=
alphas
();
return
graphicsImage
(
chars
,
out
);
}
/**
* 生成验证码图形
*
* @param strs 验证码
* @param out 输出流
* @return boolean
*/
private
boolean
graphicsImage
(
char
[]
strs
,
OutputStream
out
)
{
boolean
ok
;
try
{
BufferedImage
bi
=
new
BufferedImage
(
width
,
height
,
BufferedImage
.
TYPE_INT_RGB
);
Graphics2D
g
=
(
Graphics2D
)
bi
.
getGraphics
();
AlphaComposite
ac3
;
int
len
=
strs
.
length
;
g
.
setColor
(
Color
.
WHITE
);
g
.
fillRect
(
0
,
0
,
width
,
height
);
// 抗锯齿
g
.
setColor
(
color
());
g
.
setRenderingHint
(
RenderingHints
.
KEY_ANTIALIASING
,
RenderingHints
.
VALUE_ANTIALIAS_ON
);
int
hp
=
(
height
-
font
.
getSize
())
>>
1
;
int
h
=
height
-
hp
;
int
w
=
width
/
strs
.
length
;
int
sp
=
(
w
-
font
.
getSize
())
/
2
;
// 画字符串
for
(
int
i
=
0
;
i
<
len
;
i
++)
{
// 计算坐标
int
x
=
i
*
w
+
sp
+
num
(-
5
,
5
);
int
y
=
h
+
num
(-
5
,
5
);
if
(
x
<
5
)
{
x
=
5
;
}
if
(
x
+
font
.
getSize
()
>
width
)
{
x
=
width
-
font
.
getSize
();
}
if
(
y
>
height
)
{
y
=
height
;
}
if
(
y
-
font
.
getSize
()
<
0
)
{
y
=
font
.
getSize
();
}
g
.
setFont
(
font
.
deriveFont
(
num
(
2
)
==
0
?
Font
.
PLAIN
:
Font
.
ITALIC
));
g
.
drawString
(
String
.
valueOf
(
strs
[
i
]),
x
,
y
);
}
// 随机画干扰线
g
.
setStroke
(
new
BasicStroke
(
1.25f
,
BasicStroke
.
CAP_BUTT
,
BasicStroke
.
JOIN_BEVEL
));
ac3
=
AlphaComposite
.
getInstance
(
AlphaComposite
.
SRC_OVER
,
0.7f
);
// 指定透明度
g
.
setComposite
(
ac3
);
drawLine
(
2
,
g
.
getColor
(),
g
);
// 画干扰圆圈
drawOval
(
5
,
g
.
getColor
(),
g
);
ImageIO
.
write
(
bi
,
"png"
,
out
);
out
.
flush
();
ok
=
true
;
}
catch
(
IOException
e
)
{
ok
=
false
;
e
.
printStackTrace
();
}
finally
{
try
{
out
.
close
();
}
catch
(
IOException
e
)
{
e
.
printStackTrace
();
}
}
return
ok
;
}
}
src/main/java/com/wf/captcha/MathGifCaptcha.java
0 → 100644
View file @
01a3a848
package
com
.
wf
.
captcha
;
import
java.awt.*
;
import
java.awt.image.BufferedImage
;
import
java.io.IOException
;
import
java.io.OutputStream
;
/**
* @author chenjh
* @since 2019/7/16 16:35
*/
public
class
MathGifCaptcha
extends
AbstractMathCaptcha
{
public
MathGifCaptcha
(
int
width
,
int
height
)
{
setWidth
(
width
);
setHeight
(
height
);
}
public
MathGifCaptcha
(
int
width
,
int
height
,
int
len
)
{
this
(
width
,
height
);
setLen
(
len
);
}
public
MathGifCaptcha
(
int
width
,
int
height
,
int
len
,
Font
font
)
{
this
(
width
,
height
,
len
);
setFont
(
font
);
}
@Override
public
boolean
out
(
OutputStream
os
)
{
char
[]
rands
=
alphas
();
boolean
ok
;
try
{
GifEncoder
gifEncoder
=
new
GifEncoder
();
gifEncoder
.
start
(
os
);
gifEncoder
.
setQuality
(
180
);
gifEncoder
.
setDelay
(
100
);
gifEncoder
.
setRepeat
(
0
);
BufferedImage
frame
;
Color
fontcolor
[]
=
new
Color
[
len
];
for
(
int
i
=
0
;
i
<
len
;
i
++)
{
fontcolor
[
i
]
=
new
Color
(
20
+
num
(
110
),
20
+
num
(
110
),
20
+
num
(
110
));
}
for
(
int
i
=
0
;
i
<
len
;
i
++)
{
frame
=
graphicsImage
(
fontcolor
,
rands
,
i
);
gifEncoder
.
addFrame
(
frame
);
frame
.
flush
();
}
gifEncoder
.
finish
();
ok
=
true
;
}
finally
{
try
{
os
.
close
();
}
catch
(
IOException
e
)
{
e
.
printStackTrace
();
}
}
return
ok
;
}
/**
* 画随机码图
*
* @param fontcolor 随机字体颜色
* @param strs 字符数组
* @param flag 透明度使用
* @return BufferedImage
*/
private
BufferedImage
graphicsImage
(
Color
[]
fontcolor
,
char
[]
strs
,
int
flag
)
{
BufferedImage
image
=
new
BufferedImage
(
width
,
height
,
BufferedImage
.
TYPE_INT_RGB
);
Graphics2D
g2d
=
(
Graphics2D
)
image
.
getGraphics
();
// 填充背景颜色
g2d
.
setColor
(
Color
.
WHITE
);
g2d
.
fillRect
(
0
,
0
,
width
,
height
);
// 抗锯齿
g2d
.
setRenderingHint
(
RenderingHints
.
KEY_ANTIALIASING
,
RenderingHints
.
VALUE_ANTIALIAS_ON
);
g2d
.
setStroke
(
new
BasicStroke
(
1.3f
,
BasicStroke
.
CAP_BUTT
,
BasicStroke
.
JOIN_BEVEL
));
// 画干扰圆圈
drawOval
(
4
,
g2d
);
// 随机画干扰线
drawLine
(
2
,
g2d
);
// 画验证码
int
hp
=
(
height
-
font
.
getSize
())
>>
1
;
int
h
=
height
-
hp
;
int
w
=
width
/
strs
.
length
;
int
sp
=
(
w
-
font
.
getSize
())
/
2
;
for
(
int
i
=
0
;
i
<
strs
.
length
;
i
++)
{
AlphaComposite
ac3
=
AlphaComposite
.
getInstance
(
AlphaComposite
.
SRC_OVER
,
getAlpha
(
flag
,
i
));
g2d
.
setComposite
(
ac3
);
g2d
.
setColor
(
fontcolor
[
i
]);
// 计算坐标
int
x
=
i
*
w
+
sp
+
num
(
3
);
int
y
=
h
-
num
(
3
,
6
);
// 调整溢出的字
if
(
x
<
8
)
{
x
=
8
;
}
if
(
x
+
font
.
getSize
()
>
width
)
{
x
=
width
-
font
.
getSize
();
}
if
(
y
>
height
)
{
y
=
height
;
}
if
(
y
-
font
.
getSize
()
<
0
)
{
y
=
font
.
getSize
();
}
g2d
.
setFont
(
font
.
deriveFont
(
num
(
2
)
==
0
?
Font
.
PLAIN
:
Font
.
ITALIC
));
g2d
.
drawString
(
String
.
valueOf
(
strs
[
i
]),
x
,
y
);
}
g2d
.
dispose
();
return
image
;
}
/**
* 获取透明度,从0到1,自动计算步长
*
* @param i
* @param j
* @return 透明度
*/
private
float
getAlpha
(
int
i
,
int
j
)
{
int
num
=
i
+
j
;
float
r
=
(
float
)
1
/
(
len
-
1
);
float
s
=
len
*
r
;
return
num
>=
len
?
(
num
*
r
-
s
)
:
num
*
r
;
}
}
src/main/java/com/wf/captcha/Quant.java
0 → 100644
View file @
01a3a848
package
com
.
wf
.
captcha
;
/**
*
*/
public
class
Quant
{
protected
static
final
int
netsize
=
256
;
/* number of colours used */
/* four primes near 500 - assume no image has a length so large */
/* that it is divisible by all four primes */
protected
static
final
int
prime1
=
499
;
protected
static
final
int
prime2
=
491
;
protected
static
final
int
prime3
=
487
;
protected
static
final
int
prime4
=
503
;
protected
static
final
int
minpicturebytes
=
(
3
*
prime4
);
/* minimum size for input image */
/* Program Skeleton
----------------
[select samplefac in range 1..30]
[read image from input file]
pic = (unsigned char*) malloc(3*width*height);
initnet(pic,3*width*height,samplefac);
learn();
unbiasnet();
[write output image header, using writecolourmap(f)]
inxbuild();
write output image using inxsearch(b,g,r) */
/* Network Definitions
------------------- */
protected
static
final
int
maxnetpos
=
(
netsize
-
1
);
protected
static
final
int
netbiasshift
=
4
;
/* bias for colour values */
protected
static
final
int
ncycles
=
100
;
/* no. of learning cycles */
/* defs for freq and bias */
protected
static
final
int
intbiasshift
=
16
;
/* bias for fractions */
protected
static
final
int
intbias
=
(((
int
)
1
)
<<
intbiasshift
);
protected
static
final
int
gammashift
=
10
;
/* gamma = 1024 */
protected
static
final
int
gamma
=
(((
int
)
1
)
<<
gammashift
);
protected
static
final
int
betashift
=
10
;
protected
static
final
int
beta
=
(
intbias
>>
betashift
);
/* beta = 1/1024 */
protected
static
final
int
betagamma
=
(
intbias
<<
(
gammashift
-
betashift
));
/* defs for decreasing radius factor */
protected
static
final
int
initrad
=
(
netsize
>>
3
);
/* for 256 cols, radius starts */
protected
static
final
int
radiusbiasshift
=
6
;
/* at 32.0 biased by 6 bits */
protected
static
final
int
radiusbias
=
(((
int
)
1
)
<<
radiusbiasshift
);
protected
static
final
int
initradius
=
(
initrad
*
radiusbias
);
/* and decreases by a */
protected
static
final
int
radiusdec
=
30
;
/* factor of 1/30 each cycle */
/* defs for decreasing alpha factor */
protected
static
final
int
alphabiasshift
=
10
;
/* alpha starts at 1.0 */
protected
static
final
int
initalpha
=
(((
int
)
1
)
<<
alphabiasshift
);
protected
int
alphadec
;
/* biased by 10 bits */
/* radbias and alpharadbias used for radpower calculation */
protected
static
final
int
radbiasshift
=
8
;
protected
static
final
int
radbias
=
(((
int
)
1
)
<<
radbiasshift
);
protected
static
final
int
alpharadbshift
=
(
alphabiasshift
+
radbiasshift
);
protected
static
final
int
alpharadbias
=
(((
int
)
1
)
<<
alpharadbshift
);
/* Types and Global Variables
-------------------------- */
protected
byte
[]
thepicture
;
/* the input image itself */
protected
int
lengthcount
;
/* lengthcount = H*W*3 */
protected
int
samplefac
;
/* sampling factor 1..30 */
// typedef int pixel[4]; /* BGRc */
protected
int
[][]
network
;
/* the network itself - [netsize][4] */
protected
int
[]
netindex
=
new
int
[
256
];
/* for network lookup - really 256 */
protected
int
[]
bias
=
new
int
[
netsize
];
/* bias and freq arrays for learning */
protected
int
[]
freq
=
new
int
[
netsize
];
protected
int
[]
radpower
=
new
int
[
initrad
];
/* radpower for precomputation */
/* Initialise network in range (0,0,0) to (255,255,255) and set parameters
----------------------------------------------------------------------- */
public
Quant
(
byte
[]
thepic
,
int
len
,
int
sample
)
{
int
i
;
int
[]
p
;
thepicture
=
thepic
;
lengthcount
=
len
;
samplefac
=
sample
;
network
=
new
int
[
netsize
][];
for
(
i
=
0
;
i
<
netsize
;
i
++)
{
network
[
i
]
=
new
int
[
4
];
p
=
network
[
i
];
p
[
0
]
=
p
[
1
]
=
p
[
2
]
=
(
i
<<
(
netbiasshift
+
8
))
/
netsize
;
freq
[
i
]
=
intbias
/
netsize
;
/* 1/netsize */
bias
[
i
]
=
0
;
}
}
public
byte
[]
colorMap
()
{
byte
[]
map
=
new
byte
[
3
*
netsize
];
int
[]
index
=
new
int
[
netsize
];
for
(
int
i
=
0
;
i
<
netsize
;
i
++)
{
index
[
network
[
i
][
3
]]
=
i
;
}
int
k
=
0
;
for
(
int
i
=
0
;
i
<
netsize
;
i
++)
{
int
j
=
index
[
i
];
map
[
k
++]
=
(
byte
)
(
network
[
j
][
0
]);
map
[
k
++]
=
(
byte
)
(
network
[
j
][
1
]);
map
[
k
++]
=
(
byte
)
(
network
[
j
][
2
]);
}
return
map
;
}
/* Insertion sort of network and building of netindex[0..255] (to do after unbias)
------------------------------------------------------------------------------- */
public
void
inxbuild
()
{
int
i
,
j
,
smallpos
,
smallval
;
int
[]
p
;
int
[]
q
;
int
previouscol
,
startpos
;
previouscol
=
0
;
startpos
=
0
;
for
(
i
=
0
;
i
<
netsize
;
i
++)
{
p
=
network
[
i
];
smallpos
=
i
;
smallval
=
p
[
1
];
/* index on g */
/* find smallest in i..netsize-1 */
for
(
j
=
i
+
1
;
j
<
netsize
;
j
++)
{
q
=
network
[
j
];
if
(
q
[
1
]
<
smallval
)
{
/* index on g */
smallpos
=
j
;
smallval
=
q
[
1
];
/* index on g */
}
}
q
=
network
[
smallpos
];
/* swap p (i) and q (smallpos) entries */
if
(
i
!=
smallpos
)
{
j
=
q
[
0
];
q
[
0
]
=
p
[
0
];
p
[
0
]
=
j
;
j
=
q
[
1
];
q
[
1
]
=
p
[
1
];
p
[
1
]
=
j
;
j
=
q
[
2
];
q
[
2
]
=
p
[
2
];
p
[
2
]
=
j
;
j
=
q
[
3
];
q
[
3
]
=
p
[
3
];
p
[
3
]
=
j
;
}
/* smallval entry is now in position i */
if
(
smallval
!=
previouscol
)
{
netindex
[
previouscol
]
=
(
startpos
+
i
)
>>
1
;
for
(
j
=
previouscol
+
1
;
j
<
smallval
;
j
++)
{
netindex
[
j
]
=
i
;
}
previouscol
=
smallval
;
startpos
=
i
;
}
}
netindex
[
previouscol
]
=
(
startpos
+
maxnetpos
)
>>
1
;
for
(
j
=
previouscol
+
1
;
j
<
256
;
j
++)
{
netindex
[
j
]
=
maxnetpos
;
/* really 256 */
}
}
/* Main Learning Loop
------------------ */
public
void
learn
()
{
int
i
,
j
,
b
,
g
,
r
;
int
radius
,
rad
,
alpha
,
step
,
delta
,
samplepixels
;
byte
[]
p
;
int
pix
,
lim
;
if
(
lengthcount
<
minpicturebytes
)
{
samplefac
=
1
;
}
alphadec
=
30
+
((
samplefac
-
1
)
/
3
);
p
=
thepicture
;
pix
=
0
;
lim
=
lengthcount
;
samplepixels
=
lengthcount
/
(
3
*
samplefac
);
delta
=
samplepixels
/
ncycles
;
alpha
=
initalpha
;
radius
=
initradius
;
rad
=
radius
>>
radiusbiasshift
;
if
(
rad
<=
1
)
{
rad
=
0
;
}
for
(
i
=
0
;
i
<
rad
;
i
++)
{
radpower
[
i
]
=
alpha
*
(((
rad
*
rad
-
i
*
i
)
*
radbias
)
/
(
rad
*
rad
));
}
//fprintf(stderr,"beginning 1D learning: initial radius=%d\n", rad);
if
(
lengthcount
<
minpicturebytes
)
{
step
=
3
;
}
else
if
((
lengthcount
%
prime1
)
!=
0
)
{
step
=
3
*
prime1
;
}
else
{
if
((
lengthcount
%
prime2
)
!=
0
)
{
step
=
3
*
prime2
;
}
else
{
if
((
lengthcount
%
prime3
)
!=
0
)
{
step
=
3
*
prime3
;
}
else
{
step
=
3
*
prime4
;
}
}
}
i
=
0
;
while
(
i
<
samplepixels
)
{
b
=
(
p
[
pix
+
0
]
&
0xff
)
<<
netbiasshift
;
g
=
(
p
[
pix
+
1
]
&
0xff
)
<<
netbiasshift
;
r
=
(
p
[
pix
+
2
]
&
0xff
)
<<
netbiasshift
;
j
=
contest
(
b
,
g
,
r
);
altersingle
(
alpha
,
j
,
b
,
g
,
r
);
if
(
rad
!=
0
)
{
alterneigh
(
rad
,
j
,
b
,
g
,
r
);
/* alter neighbours */
}
pix
+=
step
;
if
(
pix
>=
lim
)
{
pix
-=
lengthcount
;
}
i
++;
if
(
delta
==
0
)
{
delta
=
1
;
}
if
(
i
%
delta
==
0
)
{
alpha
-=
alpha
/
alphadec
;
radius
-=
radius
/
radiusdec
;
rad
=
radius
>>
radiusbiasshift
;
if
(
rad
<=
1
)
{
rad
=
0
;
}
for
(
j
=
0
;
j
<
rad
;
j
++)
{
radpower
[
j
]
=
alpha
*
(((
rad
*
rad
-
j
*
j
)
*
radbias
)
/
(
rad
*
rad
));
}
}
}
//fprintf(stderr,"finished 1D learning: final alpha=%f !\n",((float)alpha)/initalpha);
}
/* Search for BGR values 0..255 (after net is unbiased) and return colour index
---------------------------------------------------------------------------- */
public
int
map
(
int
b
,
int
g
,
int
r
)
{
int
i
,
j
,
dist
,
a
,
bestd
;
int
[]
p
;
int
best
;
bestd
=
1000
;
/* biggest possible dist is 256*3 */
best
=
-
1
;
i
=
netindex
[
g
];
/* index on g */
j
=
i
-
1
;
/* start at netindex[g] and work outwards */
while
((
i
<
netsize
)
||
(
j
>=
0
))
{
if
(
i
<
netsize
)
{
p
=
network
[
i
];
dist
=
p
[
1
]
-
g
;
/* inx key */
if
(
dist
>=
bestd
)
{
i
=
netsize
;
/* stop iter */
}
else
{
i
++;
if
(
dist
<
0
)
{
dist
=
-
dist
;
}
a
=
p
[
0
]
-
b
;
if
(
a
<
0
)
{
a
=
-
a
;
}
dist
+=
a
;
if
(
dist
<
bestd
)
{
a
=
p
[
2
]
-
r
;
if
(
a
<
0
)
{
a
=
-
a
;
}
dist
+=
a
;
if
(
dist
<
bestd
)
{
bestd
=
dist
;
best
=
p
[
3
];
}
}
}
}
if
(
j
>=
0
)
{
p
=
network
[
j
];
dist
=
g
-
p
[
1
];
/* inx key - reverse dif */
if
(
dist
>=
bestd
)
{
j
=
-
1
;
/* stop iter */
}
else
{
j
--;
if
(
dist
<
0
)
{
dist
=
-
dist
;
}
a
=
p
[
0
]
-
b
;
if
(
a
<
0
)
{
a
=
-
a
;
}
dist
+=
a
;
if
(
dist
<
bestd
)
{
a
=
p
[
2
]
-
r
;
if
(
a
<
0
)
{
a
=
-
a
;
}
dist
+=
a
;
if
(
dist
<
bestd
)
{
bestd
=
dist
;
best
=
p
[
3
];
}
}
}
}
}
return
(
best
);
}
public
byte
[]
process
()
{
learn
();
unbiasnet
();
inxbuild
();
return
colorMap
();
}
/* Unbias network to give byte values 0..255 and record position i to prepare for sort
----------------------------------------------------------------------------------- */
public
void
unbiasnet
()
{
int
i
,
j
;
for
(
i
=
0
;
i
<
netsize
;
i
++)
{
network
[
i
][
0
]
>>=
netbiasshift
;
network
[
i
][
1
]
>>=
netbiasshift
;
network
[
i
][
2
]
>>=
netbiasshift
;
network
[
i
][
3
]
=
i
;
/* record colour no */
}
}
/* Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in radpower[|i-j|]
--------------------------------------------------------------------------------- */
protected
void
alterneigh
(
int
rad
,
int
i
,
int
b
,
int
g
,
int
r
)
{
int
j
,
k
,
lo
,
hi
,
a
,
m
;
int
[]
p
;
lo
=
i
-
rad
;
if
(
lo
<
-
1
)
{
lo
=
-
1
;
}
hi
=
i
+
rad
;
if
(
hi
>
netsize
)
{
hi
=
netsize
;
}
j
=
i
+
1
;
k
=
i
-
1
;
m
=
1
;
while
((
j
<
hi
)
||
(
k
>
lo
))
{
a
=
radpower
[
m
++];
if
(
j
<
hi
)
{
p
=
network
[
j
++];
try
{
p
[
0
]
-=
(
a
*
(
p
[
0
]
-
b
))
/
alpharadbias
;
p
[
1
]
-=
(
a
*
(
p
[
1
]
-
g
))
/
alpharadbias
;
p
[
2
]
-=
(
a
*
(
p
[
2
]
-
r
))
/
alpharadbias
;
}
catch
(
Exception
e
)
{
}
// prevents 1.3 miscompilation
}
if
(
k
>
lo
)
{
p
=
network
[
k
--];
try
{
p
[
0
]
-=
(
a
*
(
p
[
0
]
-
b
))
/
alpharadbias
;
p
[
1
]
-=
(
a
*
(
p
[
1
]
-
g
))
/
alpharadbias
;
p
[
2
]
-=
(
a
*
(
p
[
2
]
-
r
))
/
alpharadbias
;
}
catch
(
Exception
e
)
{
}
}
}
}
/* Move neuron i towards biased (b,g,r) by factor alpha
---------------------------------------------------- */
protected
void
altersingle
(
int
alpha
,
int
i
,
int
b
,
int
g
,
int
r
)
{
/* alter hit neuron */
int
[]
n
=
network
[
i
];
n
[
0
]
-=
(
alpha
*
(
n
[
0
]
-
b
))
/
initalpha
;
n
[
1
]
-=
(
alpha
*
(
n
[
1
]
-
g
))
/
initalpha
;
n
[
2
]
-=
(
alpha
*
(
n
[
2
]
-
r
))
/
initalpha
;
}
/* Search for biased BGR values
---------------------------- */
protected
int
contest
(
int
b
,
int
g
,
int
r
)
{
/* finds closest neuron (min dist) and updates freq */
/* finds best neuron (min dist-bias) and returns position */
/* for frequently chosen neurons, freq[i] is high and bias[i] is negative */
/* bias[i] = gamma*((1/netsize)-freq[i]) */
int
i
,
dist
,
a
,
biasdist
,
betafreq
;
int
bestpos
,
bestbiaspos
,
bestd
,
bestbiasd
;
int
[]
n
;
bestd
=
~(((
int
)
1
)
<<
31
);
bestbiasd
=
bestd
;
bestpos
=
-
1
;
bestbiaspos
=
bestpos
;
for
(
i
=
0
;
i
<
netsize
;
i
++)
{
n
=
network
[
i
];
dist
=
n
[
0
]
-
b
;
if
(
dist
<
0
)
{
dist
=
-
dist
;
}
a
=
n
[
1
]
-
g
;
if
(
a
<
0
)
{
a
=
-
a
;
}
dist
+=
a
;
a
=
n
[
2
]
-
r
;
if
(
a
<
0
)
{
a
=
-
a
;
}
dist
+=
a
;
if
(
dist
<
bestd
)
{
bestd
=
dist
;
bestpos
=
i
;
}
biasdist
=
dist
-
((
bias
[
i
])
>>
(
intbiasshift
-
netbiasshift
));
if
(
biasdist
<
bestbiasd
)
{
bestbiasd
=
biasdist
;
bestbiaspos
=
i
;
}
betafreq
=
(
freq
[
i
]
>>
betashift
);
freq
[
i
]
-=
betafreq
;
bias
[
i
]
+=
(
betafreq
<<
gammashift
);
}
freq
[
bestpos
]
+=
beta
;
bias
[
bestpos
]
-=
betagamma
;
return
(
bestbiaspos
);
}
}
\ No newline at end of file
src/main/java/com/wf/captcha/Randoms.java
0 → 100644
View file @
01a3a848
package
com
.
wf
.
captcha
;
import
java.util.Random
;
/**
* 随机数工具类
* Created by 王帆 on 2018-07-27 上午 10:08.
*/
public
class
Randoms
{
protected
static
final
Random
RANDOM
=
new
Random
();
// 定义验证码字符.去除了O和I等容易混淆的字母
public
static
final
char
ALPHA
[]
=
{
'2'
,
'3'
,
'4'
,
'5'
,
'6'
,
'7'
,
'8'
,
'9'
,
'A'
,
'B'
,
'C'
,
'D'
,
'E'
,
'F'
,
'G'
,
'H'
,
'G'
,
'K'
,
'M'
,
'N'
,
'P'
,
'Q'
,
'R'
,
'S'
,
'T'
,
'U'
,
'V'
,
'W'
,
'X'
,
'Y'
,
'Z'
,
'a'
,
'b'
,
'c'
,
'd'
,
'e'
,
'f'
,
'g'
,
'h'
,
'i'
,
'j'
,
'k'
,
'm'
,
'n'
,
'p'
,
'q'
,
'r'
,
's'
,
't'
,
'u'
,
'v'
,
'w'
,
'x'
,
'y'
,
'z'
};
protected
static
final
int
numMaxIndex
=
8
;
// 数字的最大索引,不包括最大值
protected
static
final
int
charMinIndex
=
numMaxIndex
;
// 字符的最小索引,包括最小值
protected
static
final
int
charMaxIndex
=
ALPHA
.
length
;
// 字符的最大索引,不包括最大值
protected
static
final
int
upperMinIndex
=
charMinIndex
;
// 大写字符最小索引
protected
static
final
int
upperMaxIndex
=
upperMinIndex
+
23
;
// 大写字符最大索引
protected
static
final
int
lowerMinIndex
=
upperMaxIndex
;
// 小写字母最小索引
protected
static
final
int
lowerMaxIndex
=
charMaxIndex
;
// 小写字母最大索引
/**
* 产生两个数之间的随机数
*
* @param min 最小值
* @param max 最大值
* @return 随机数
*/
public
static
int
num
(
int
min
,
int
max
)
{
return
min
+
RANDOM
.
nextInt
(
max
-
min
);
}
/**
* 产生0-num的随机数,不包括num
*
* @param num 最大值
* @return 随机数
*/
public
static
int
num
(
int
num
)
{
return
RANDOM
.
nextInt
(
num
);
}
/**
* 返回ALPHA中的随机字符
*
* @return 随机字符
*/
public
static
char
alpha
()
{
return
ALPHA
[
num
(
ALPHA
.
length
)];
}
/**
* 返回ALPHA中第0位到第num位的随机字符
*
* @param num 到第几位结束
* @return 随机字符
*/
public
static
char
alpha
(
int
num
)
{
return
ALPHA
[
num
(
num
)];
}
/**
* 返回ALPHA中第min位到第max位的随机字符
*
* @param min 从第几位开始
* @param max 到第几位结束
* @return 随机字符
*/
public
static
char
alpha
(
int
min
,
int
max
)
{
return
ALPHA
[
num
(
min
,
max
)];
}
}
\ No newline at end of file
src/main/java/com/wf/captcha/SpecCaptcha.java
0 → 100644
View file @
01a3a848
package
com
.
wf
.
captcha
;
import
javax.imageio.ImageIO
;
import
java.awt.*
;
import
java.awt.image.BufferedImage
;
import
java.io.IOException
;
import
java.io.OutputStream
;
public
class
SpecCaptcha
extends
BaseCaptcha
{
public
SpecCaptcha
()
{
}
public
SpecCaptcha
(
int
width
,
int
height
)
{
this
();
setWidth
(
width
);
setHeight
(
height
);
}
public
SpecCaptcha
(
int
width
,
int
height
,
int
len
)
{
this
(
width
,
height
);
setLen
(
len
);
}
public
SpecCaptcha
(
int
width
,
int
height
,
int
len
,
Font
font
)
{
this
(
width
,
height
,
len
);
setFont
(
font
);
}
/**
* 生成验证码
*
* @param out 输出流
* @return 是否成功
*/
@Override
public
boolean
out
(
OutputStream
out
)
{
checkAlpha
();
return
graphicsImage
(
textChar
(),
out
);
}
/**
* 生成验证码图形
*
* @param strs 验证码
* @param out 输出流
* @return boolean
*/
private
boolean
graphicsImage
(
char
[]
strs
,
OutputStream
out
)
{
boolean
ok
;
try
{
BufferedImage
bi
=
new
BufferedImage
(
width
,
height
,
BufferedImage
.
TYPE_INT_RGB
);
Graphics2D
g
=
(
Graphics2D
)
bi
.
getGraphics
();
g
.
setColor
(
Color
.
WHITE
);
g
.
fillRect
(
0
,
0
,
width
,
height
);
// 抗锯齿
g
.
setRenderingHint
(
RenderingHints
.
KEY_ANTIALIASING
,
RenderingHints
.
VALUE_ANTIALIAS_ON
);
g
.
setStroke
(
new
BasicStroke
(
1.3f
,
BasicStroke
.
CAP_BUTT
,
BasicStroke
.
JOIN_BEVEL
));
// 随机画干扰线
drawLine
(
3
,
g
);
// 随机画干扰圆
drawOval
(
8
,
g
);
// 画字符串
AlphaComposite
ac3
=
AlphaComposite
.
getInstance
(
AlphaComposite
.
SRC_OVER
,
0.8f
);
// 指定透明度
g
.
setComposite
(
ac3
);
int
hp
=
(
height
-
font
.
getSize
())
>>
1
;
int
h
=
height
-
hp
;
int
w
=
width
/
strs
.
length
;
int
sp
=
(
w
-
font
.
getSize
())
/
2
;
for
(
int
i
=
0
;
i
<
strs
.
length
;
i
++)
{
g
.
setColor
(
new
Color
(
20
+
num
(
110
),
20
+
num
(
110
),
20
+
num
(
110
)));
// 计算坐标
int
x
=
i
*
w
+
sp
+
num
(
3
);
int
y
=
h
-
num
(
3
,
6
);
if
(
x
<
8
)
{
x
=
8
;
}
if
(
x
+
font
.
getSize
()
>
width
)
{
x
=
width
-
font
.
getSize
();
}
if
(
y
>
height
)
{
y
=
height
;
}
if
(
y
-
font
.
getSize
()
<
0
)
{
y
=
font
.
getSize
();
}
g
.
setFont
(
font
.
deriveFont
(
num
(
2
)
==
0
?
Font
.
PLAIN
:
Font
.
ITALIC
));
g
.
drawString
(
String
.
valueOf
(
strs
[
i
]),
x
,
y
);
}
ImageIO
.
write
(
bi
,
"png"
,
out
);
out
.
flush
();
ok
=
true
;
}
catch
(
IOException
e
)
{
ok
=
false
;
e
.
printStackTrace
();
}
finally
{
try
{
out
.
close
();
}
catch
(
IOException
e
)
{
e
.
printStackTrace
();
}
}
return
ok
;
}
}
\ No newline at end of file
src/main/java/com/wf/captcha/utils/CaptchaUtil.java
0 → 100644
View file @
01a3a848
package
com
.
wf
.
captcha
.
utils
;
import
com.wf.captcha.*
;
import
lombok.extern.slf4j.Slf4j
;
import
java.awt.*
;
import
java.io.IOException
;
import
java.io.OutputStream
;
import
java.util.Random
;
/**
* @author chenjh
* @since 2019/7/16 17:44
*/
@Slf4j
public
class
CaptchaUtil
{
/**
* 输出验证码
*
* @param outputStream OutputStream
* @return 验证码结果
*/
public
static
String
out
(
OutputStream
outputStream
)
{
return
out
(
5
,
outputStream
);
}
/**
* 输出验证码
*
* @param len 长度
* @param outputStream OutputStream
* @return 验证码结果
*/
public
static
String
out
(
int
len
,
OutputStream
outputStream
)
{
return
out
(
130
,
48
,
len
,
outputStream
);
}
/**
* 输出验证码
*
* @param len 长度
* @param font 字体
* @param outputStream OutputStream
* @return 验证码结果
*/
public
static
String
out
(
int
len
,
Font
font
,
OutputStream
outputStream
)
{
return
out
(
130
,
48
,
len
,
font
,
outputStream
);
}
/**
* 输出验证码
*
* @param width 宽度
* @param height 高度
* @param len 长度
* @param outputStream OutputStream
* @return 验证码结果
*/
public
static
String
out
(
int
width
,
int
height
,
int
len
,
OutputStream
outputStream
)
{
return
out
(
width
,
height
,
len
,
null
,
outputStream
);
}
/**
* 输出验证码
*
* @param width 宽度
* @param height 高度
* @param len 长度
* @param font 字体
* @param outputStream OutputStream
* @return 验证码结果
*/
public
static
String
out
(
int
width
,
int
height
,
int
len
,
Font
font
,
OutputStream
outputStream
)
{
int
cType
=
new
Random
().
nextInt
(
6
);
return
outCaptcha
(
width
,
height
,
len
,
font
,
cType
,
outputStream
);
}
/**
* 输出验证码
*
* @param outputStream OutputStream
* @return 验证码结果
*/
public
static
String
outPng
(
OutputStream
outputStream
)
{
return
outPng
(
5
,
outputStream
);
}
/**
* 输出验证码
*
* @param len 长度
* @param outputStream OutputStream
* @return 验证码结果
*/
public
static
String
outPng
(
int
len
,
OutputStream
outputStream
)
{
return
outPng
(
130
,
48
,
len
,
outputStream
);
}
/**
* 输出验证码
*
* @param len 长度
* @param font 字体
* @param outputStream OutputStream
* @return 验证码结果
*/
public
static
String
outPng
(
int
len
,
Font
font
,
OutputStream
outputStream
)
{
return
outPng
(
130
,
48
,
len
,
font
,
outputStream
);
}
/**
* 输出验证码
*
* @param width 宽度
* @param height 高度
* @param len 长度
* @param outputStream OutputStream
* @return 验证码结果
*/
public
static
String
outPng
(
int
width
,
int
height
,
int
len
,
OutputStream
outputStream
)
{
return
outPng
(
width
,
height
,
len
,
null
,
outputStream
);
}
/**
* 输出验证码
*
* @param width 宽度
* @param height 高度
* @param len 长度
* @param font 字体
* @param outputStream OutputStream
* @return 验证码结果
*/
public
static
String
outPng
(
int
width
,
int
height
,
int
len
,
Font
font
,
OutputStream
outputStream
)
{
int
cType
=
new
Random
().
nextInt
(
6
);
return
outCaptcha
(
width
,
height
,
len
,
font
,
cType
,
outputStream
);
}
/**
* 输出验证码
*
* @param width 宽度
* @param height 高度
* @param len 长度
* @param font 字体
* @param cType 类型
* @param outputStream OutputStream
* @return 验证码结果
*/
private
static
String
outCaptcha
(
int
width
,
int
height
,
int
len
,
Font
font
,
int
cType
,
OutputStream
outputStream
)
{
BaseCaptcha
captcha
=
null
;
/*if (cType == 0) {
captcha = new SpecCaptcha(width, height, len);
} else if (cType == 1) {
captcha = new GifCaptcha(width, height, len);
} else if (cType == 2) {
captcha = new ChineseCaptcha(width, height, len);
} else if (cType == 3) {
captcha = new ChineseGifCaptcha(width, height, len);
} else if (cType == 4) {
captcha = new MathCaptcha(width, height, 4);
} else if (cType == 5) {
captcha = new MathGifCaptcha(width, height, 4);
}*/
if
(
cType
==
0
)
{
captcha
=
new
SpecCaptcha
(
width
,
height
,
len
);
}
else
if
(
cType
==
1
)
{
captcha
=
new
SpecCaptcha
(
width
,
height
,
len
);
}
else
if
(
cType
==
2
)
{
captcha
=
new
SpecCaptcha
(
width
,
height
,
len
);
}
else
if
(
cType
==
3
)
{
captcha
=
new
MathCaptcha
(
width
,
height
,
len
);
}
else
if
(
cType
==
4
)
{
captcha
=
new
MathCaptcha
(
width
,
height
,
4
);
}
else
if
(
cType
==
5
)
{
captcha
=
new
MathCaptcha
(
width
,
height
,
4
);
}
if
(
font
!=
null
)
{
captcha
.
setFont
(
font
);
}
captcha
.
out
(
outputStream
);
return
captcha
.
text
().
toLowerCase
();
}
}
src/main/resources/META-INF/spring.factories
0 → 100644
View file @
01a3a848
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.keking.anti_reptile.config.AntiReptileAutoConfig
\ No newline at end of file
src/main/resources/verify/index.html
0 → 100644
View file @
01a3a848
<!DOCTYPE html>
<head>
<meta
charset=
"UTF-8"
>
<meta
name=
"viewport"
content=
"width=device-width, initial-scale=1.0, user-scalable=yes"
>
<title>
kk-anti-reptile验证
</title>
<script>
function
getXhr
()
{
var
xhr
=
null
;
try
{
xhr
=
new
XMLHttpRequest
();
}
catch
(
e
)
{
try
{
xhr
=
new
ActiveXObject
(
"Msxml2.XMLHTTP"
);
}
catch
(
e
)
{
xhr
=
new
ActiveXObject
(
"Microsoft.XMLHTTP"
);
}
}
return
xhr
;
}
function
refresh
()
{
var
xhr
=
getXhr
();
var
verifyId
=
document
.
getElementById
(
"verifyId"
).
value
;
var
baseUrl
=
document
.
getElementById
(
"baseUrl"
).
value
;
xhr
.
onreadystatechange
=
function
()
{
if
(
xhr
.
readyState
==
4
&&
xhr
.
status
==
200
)
{
var
verifyObj
=
JSON
.
parse
(
xhr
.
responseText
);
document
.
getElementById
(
"verifyId"
).
value
=
verifyObj
.
verifyId
;
document
.
getElementById
(
"verifyImg"
).
src
=
verifyObj
.
verifyImgStr
;
}
}
xhr
.
open
(
"POST"
,
baseUrl
+
"/kk-anti-reptile/refresh?verifyId="
+
verifyId
,
"true"
);
xhr
.
send
();
}
function
validate
()
{
var
elements
=
document
.
getElementById
(
"verifyFrom"
);
var
formData
=
new
FormData
();
for
(
var
i
=
0
;
i
<
elements
.
length
;
i
++
)
{
formData
.
append
(
elements
[
i
].
name
,
elements
[
i
].
value
);
}
var
baseUrl
=
document
.
getElementById
(
"baseUrl"
).
value
;
var
xhr
=
getXhr
();
xhr
.
onreadystatechange
=
function
()
{
if
(
xhr
.
readyState
==
4
&&
xhr
.
status
==
200
)
{
var
obj
=
JSON
.
parse
(
xhr
.
responseText
);
if
(
obj
.
result
==
true
)
{
closeThisWindows
();
}
else
{
alert
(
"验证码填写错误"
)
}
}
}
xhr
.
open
(
"POST"
,
baseUrl
+
"/kk-anti-reptile/validate"
,
"true"
);
xhr
.
send
(
formData
);
}
function
closeThisWindows
()
{
if
(
navigator
.
userAgent
.
indexOf
(
"MSIE"
)
>
0
)
{
if
(
navigator
.
userAgent
.
indexOf
(
"MSIE 6.0"
)
>
0
)
{
window
.
opener
=
null
;
window
.
close
();
}
else
{
window
.
open
(
''
,
'_top'
);
window
.
top
.
close
();
}
}
else
if
(
navigator
.
userAgent
.
indexOf
(
"Firefox"
)
>
0
)
{
window
.
location
.
href
=
'about:blank'
;
}
else
if
(
navigator
.
userAgent
.
indexOf
(
"AppleWebKit"
)
>
0
)
{
window
.
location
.
href
=
'about:blank'
;
window
.
close
();
}
else
{
window
.
opener
=
null
;
window
.
open
(
''
,
'_self'
,
''
);
window
.
close
();
}
}
</script>
<meta
http-equiv=
"Content-Type"
content=
"text/html; charset=utf-8"
>
<meta
name=
"viewport"
content=
"width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"
/>
<title>
普通验证码
</title>
<style>
*
{
box-sizing
:
border-box
;
margin
:
0
;
padding
:
0
;
}
form
{
width
:
240px
;
margin
:
100px
auto
;
padding
:
20px
;
}
input
[
type
=
"text"
]
{
margin
:
10px
0
;
padding
:
0
4px
;
width
:
100%
;
height
:
32px
;
border
:
1px
solid
#c3c3c3
;
border-radius
:
4px
;
}
input
[
type
=
"button"
]
{
width
:
100%
;
height
:
32px
;
color
:
#fff
;
background-color
:
#40a9ff
;
border-color
:
#40a9ff
;
border-radius
:
4px
;
outline
:
0
;
cursor
:
pointer
;
text-shadow
:
0
-1px
0
rgba
(
0
,
0
,
0
,
0.12
);
box-shadow
:
0
2px
0
rgba
(
0
,
0
,
0
,
0.045
);
border-style
:
none
;
}
.img-wrapper
{
display
:
flex
;
align-items
:
center
;
}
.img-wrapper
img
{
width
:
130px
;
height
:
48px
;
}
.img-wrapper
a
{
text-decoration
:
none
;
color
:
#1890ff
;
}
</style>
</head>
<body>
<form
id=
"verifyFrom"
method=
"post"
action=
""
>
<input
type=
"hidden"
id=
"baseUrl"
name=
"baseUrl"
>
<input
type=
"hidden"
id=
"verifyId"
name=
"verifyId"
value=
"verifyId_value"
>
<input
type=
"hidden"
id=
"realRequestUri"
name=
"realRequestUri"
value=
"realRequestUri_value"
>
<span>
操作频繁,请输入验证码
</span>
<div
class=
"img-wrapper"
>
<img
id=
"verifyImg"
src=
"verifyImg_value"
>
<a
href=
"javascript:void(0);"
onclick=
"refresh()"
>
刷新
</a>
</div>
<input
type=
"text"
id=
"result"
name=
"result"
>
<br/>
<input
type=
"button"
value=
"确认"
onclick=
"validate()"
>
</form>
</body>
</html>
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment