Commit 01a3a848 authored by zhouxudong's avatar zhouxudong

提交代码

parents
Pipeline #110 failed with stages
## 概述
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)
<?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>
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;
}
}
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 {
}
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();
}
}
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;
}
}
}
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
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);
}
}
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";
}
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;
}
}
}
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;
}
}
}
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 + '\'' +
'}';
}
}
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 + '\'' +
'}';
}
}
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);
}
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();
}
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;
}
}
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));
}
}
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;
}
}
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;
}
}
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;
}
}
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");
}
}
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;
}
}
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);
}
}
}
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;
}
}
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
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;
}
}
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;
}
}
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);
}
}
}
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;
}
}
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(0F).equals(fps)) {
delay = Math.round(100f / 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));
}
}
}
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;
}
}
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;
}
}
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
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
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
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();
}
}
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.keking.anti_reptile.config.AntiReptileAutoConfig
\ No newline at end of file
<!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"> &nbsp;&nbsp;&nbsp;&nbsp;
<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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment