这个HttpClientUtil工具类分享在GitHub上已经半年多的时间了,并且得到了不小的关注,有25颗star,被fork了38次。有了大家的鼓励,工具类一直也在完善中。最近比较忙,两个多月前的修改在今天刚修改测试完成,今天再次分享给大家。

验证码识别这项技术并不是本工具类的功能,而是通过一个开源的api来识别验证码的。这里做了一个简单的封装,主要是用来解决登陆时的验证码的问题。在线验证码识别官网:http://lab.ocrking.com/,github地址:https://github.com/AvensLab/OcrKing/,是一个功能非常强大的工具。

好了,言归正传,本次封装的工具重要代码如下:

/** * 识别验证码* * @author arron* @date 2016年3月24日 上午9:44:35 * @version 1.0 */
public class OCR {/*** 接口说明:* https://github.com/AvensLab/OcrKing/blob/master/线上识别http接口说明.txt*/private static final String apiUrl = "http://lab.ocrking.com/ok.html";private static final String apiKey = PropertiesUtil.getProperty("OCR.key");private static final String boundary = "----------------------------OcrKing_Client_Aven_s_Lab";private static final String end="\r\n--" + boundary + "--\r\n";private static final Header[] headers = HttpHeader.custom()                                          .referer("http://lab.ocrking.com/?javaclient0.1)").build();private static final Map<String, Object> map = getParaMap();private static HttpClient client  =null; //=HCB.custom().proxy("127.0.0.1", 8888).build();public static void debug(){client =HCB.custom().proxy("127.0.0.1", 8888).build();}public static void exitDebug(){client =null;}//获取固定参数private static Map<String, Object> getParaMap(){//加载所有参数Map<String , Object> map = new HashMap<String, Object>();map.put("service", "OcrKingForCaptcha");map.put("language", "eng");map.put("charset", "7");//7-数字大写小写,5-数字大写字母map.put("type", "http://www.unknown.com");map.put("apiKey", apiKey);return map;}/*** 识别本地校验码(英文:字母+大小写)* * @param imgFilePath    验证码地址* @return*/public static String ocrCode(String filePath){return ocrCode(filePath, 0);}/*** 识别本地校验码(英文:字母+大小写)* * @param imgFilePath    验证码地址* @param limitCodeLen 验证码长度(如果结果与设定长度不一致,则返回获取失败的提示)* @return*/@SuppressWarnings("resource")public static String ocrCode(String imgFilePath, int limitCodeLen){byte[] data = null;String fileName = imgFilePath.replaceAll("[^/]*/|[^\\\\]*\\\\", "");StringBuffer strBuf = new StringBuffer();for (Entry<String, Object> entry : map.entrySet()) {strBuf.append("\r\n").append("--").append(boundary).append("\r\n");strBuf.append("Content-Disposition: form-data; name=\"" + entry.getKey() + "\"\r\n\r\n");strBuf.append(entry.getValue());}strBuf.append("\r\n").append("--").append(boundary).append("\r\n");strBuf.append("Content-Disposition: form-data; name=\"ocrfile\"; filename=\"" + fileName + "\"\r\n");strBuf.append("Content-Type:application/octet-stream\r\n\r\n");//读取文件File f = new File(imgFilePath);if(!f.exists()){return "Error:文件不存在!";}//内容长度=参数长度+文件长度+结尾字符串长度ByteArrayOutputStream bos = new ByteArrayOutputStream(strBuf.length()+(int)f.length()+end.length()); try {bos.write(strBuf.toString().getBytes());//转化参数内容BufferedInputStream in = new BufferedInputStream(new FileInputStream(f));  int buf_size = 1024;int len = 0;  byte[] buf = new byte[buf_size];  while (-1 != (len = in.read(buf, 0, buf_size))) {  bos.write(buf, 0, len);  }  bos.write(end.getBytes());data= bos.toByteArray();  } catch (IOException e) {  e.printStackTrace();}Map<String , Object> m = new HashMap<String, Object>();m.put(Utils.ENTITY_BYTES, data);String html;try {html = HttpClientUtil.post(HttpConfig.custom().client(client).url(apiUrl).headers(headers).map(m));//System.out.println(html);String[] results = StringUtil.regex("<Result>([^<]*)</Result>\\s*<Status>([^<]*)</Status>", html);if(results.length>0){//System.out.println(results[0]);if(limitCodeLen<=0 || limitCodeLen==results[0].length()){//不判断长度或者长度一致时,直接返回return results[0];}}} catch (HttpProcessException e) {e.printStackTrace();}return "Error:获取失败!";}/*** 直接获取网络验证码(验证码不刷新)* * @param imgUrl            验证码地址* @return*/public static String ocrCode4Net(String imgUrl){return ocrCode4Net(imgUrl, 0);}/*** 直接获取网络验证码(验证码不刷新)* * @param imgUrl          验证码地址* @param limitCodeLen 验证码长度* @return*/public static String ocrCode4Net(String imgUrl, int limitCodeLen){Map<String, Object> map = getParaMap();map.put("url", imgUrl);Header[] headers = HttpHeader.custom().userAgent("Mozilla/5.0 (Windows NT 5.1; zh-CN; rv:1.9.1.3) Gecko/20100101 Firefox/8.0").build();try {String html = HttpClientUtil.post(HttpConfig.custom().client(client).url(apiUrl).headers(headers).map(map));//System.out.println(html);String[] results = StringUtil.regex("<Result>([^<]*)</Result>\\s*<Status>([^<]*)</Status>", html);if(results.length>0){//System.out.println(results[0]);if(limitCodeLen<=0 || limitCodeLen==results[0].length()){//不判断长度或者长度一致时,直接返回return results[0];}}} catch (HttpProcessException e) {e.printStackTrace();}return "Error:获取失败!";}/*** 直接获取网络验证码(通过获取图片流,然后识别验证码)* * @param config             HttpConfig对象(设置cookie)* @param savePath      图片保存的完整路径(值为null时,不保存),如:c:/1.png* @return*/public static String ocrCode4Net(HttpConfig config, String savePath){return ocrCode4Net(config, savePath, 0);}/*** 直接获取网络验证码(通过获取图片流,然后识别验证码)* * @param config                HttpConfig对象(设置cookie)* @param savePath      图片保存的完整路径(值为null时,不保存),如:c:/1.png* @param limitCodeLen  验证码长度* @return*/@SuppressWarnings("resource")public static String ocrCode4Net(HttpConfig config, String savePath, int limitCodeLen){byte[] data = null;StringBuffer strBuf = new StringBuffer();for (Entry<String, Object> entry : map.entrySet()) {strBuf.append("\r\n").append("--").append(boundary).append("\r\n");strBuf.append("Content-Disposition: form-data; name=\"" + entry.getKey() + "\"\r\n\r\n");strBuf.append(entry.getValue());}strBuf.append("\r\n").append("--").append(boundary).append("\r\n");strBuf.append("Content-Disposition: form-data; name=\"ocrfile\"; filename=\"" + "aaa" + "\"\r\n");strBuf.append("Content-Type:application/octet-stream\r\n\r\n");//下载图片ByteArrayOutputStream out = new ByteArrayOutputStream();try {out = (ByteArrayOutputStream) HttpClientUtil.down(config.client(client).out(out));if(savePath==null || savePath.equals("")){}else{//本地测试,可以保存一下图片,方便核验FileOutputStream fos = new FileOutputStream(savePath);fos.write(out.toByteArray());}ByteArrayOutputStream bos = new ByteArrayOutputStream(out.size()+strBuf.length()+end.length());bos.write(strBuf.toString().getBytes());bos.write(out.toByteArray());bos.write(end.getBytes());data= bos.toByteArray();} catch (HttpProcessException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}Map<String , Object> m = new HashMap<String, Object>();m.put(Utils.ENTITY_BYTES, data);String html;try {html = HttpClientUtil.post(config.client(client).url(apiUrl).headers(headers).map(m));//System.out.println(html);String[] results = StringUtil.regex("<Result>([^<]*)</Result>\\s*<Status>([^<]*)</Status>", html);if(results.length>0){//System.out.println(results[0]);if(limitCodeLen<=0 || limitCodeLen==results[0].length()){//不判断长度或者长度一致时,直接返回return results[0];}}} catch (HttpProcessException e) {e.printStackTrace();}return "Error:获取失败!";}
}

其实这个类中,主要用3个方法,第一个是识别本地图片,第二个是识别网络上的固定图片,第三个是识别网络上可刷新的验证码图片。当然不管哪个方法,核心代码都是读取图片字节流,上传到api接口,通过接口进行识别。

上面代码中用到了StringUtil.regex方法,具体如下:

 /*** 通过正则表达式获取内容* * @param regex       正则表达式* @param from     原字符串* @return*/public static String[] regex(String regex, String from){Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(from);List<String> results = new ArrayList<String>();while(matcher.find()){for (int i = 0; i < matcher.groupCount(); i++) {results.add(matcher.group(i+1));}}return results.toArray(new String[]{});}

由于识别验证码的api需要用到ke'y,所以就写了一个配置文件config.properties,用于设置ke'y的值。同时提供了一个简单的配置文件工具类:

/*** 最简单的属性文件读取工具类* * @author arron* @date 2016年1月14日 下午5:37:18 * @version 1.0*/
public class PropertiesUtil {/*** 默认属性集合(文件在Constants中配置)*/protected static Properties defaultProp = null;/*** 所有读取过的属性集合* 文件名 <-> 属性集合*/protected static Map<String, Properties> allProps = new HashMap<String, Properties>();// 初始化默认的属性集合static {if (defaultProp == null) {defaultProp = loadProperties("config.properties");allProps.put("config.properties", defaultProp);}}/*** 读取属性文件,并将读出来的属性集合添加到【allProps】当中* 如果该属性文件之前已读取过,则直接从【allProps】获得*/public static Properties getProperties(String fileName) {if (fileName==null || "".equals(fileName)) {return defaultProp;} else {Properties prop = allProps.get(fileName);if(prop == null) {prop = loadProperties(fileName);allProps.put(fileName, prop);}return prop;}}      /*** 解析属性文件,将文件中的所有属性都读取到【Properties】当中*/protected static Properties loadProperties (String fileName) {Properties prop = new Properties();InputStream ins = null;ins = PropertiesUtil.class.getClassLoader().getResourceAsStream(fileName);if (ins == null) {System.err.println("Can not find the resource!");} else {try {prop.load(ins);} catch (IOException e) {System.err.println("An error occurred when reading from the input stream, "+e.getMessage());} catch (IllegalArgumentException e) {System.err.println("The input stream contains a malformed Unicode escape sequence, "+e.getMessage());}}return prop;}/*** 从指定的属性文件中获取某一属性值* 如果属性文件不存在该属性则返回 null*/public static String getProperty(String fileName, String name){return getProperties(fileName).getProperty(name);}/*** 从默认的属性文件中获取某一属性值* 如果属性文件不存在该属性则返回 null*/public static String getProperty(String name){return getProperties(null).getProperty(name);}
}

代码也就这么多。在最后,提供一段测试代码来测试该功能:核心逻辑就是通过HttpClientUtil的download方法获取图片,然后通过api进行识别,然后通过请求特定网址进行验证识别的结果是否正确。

 public static void main(String[] args) throws InterruptedException, HttpProcessException {String qq = "123456789";//qq号String imgUrl = "http://qqxoo.com/include/vdimgvt.php?t="+Math.random(); //获取验证码图片地址String verifyUrl = "http://qqxoo.com/include/vdcheck.php";String saveCodePath = "C:/1.png";//保存验证码图片路径Header[] headers = HttpHeader.custom().referer("http://qqxoo.com/main.html?qqid="+qq).build();//设置referer,是为了获取对应qq号的验证码,否则报错HttpConfig config = HttpConfig.custom().headers(headers).context(HttpCookies.custom().getContext());//必须设置context,是为了携带cookie进行操作String result =null;//识别结果do {if(result!=null){System.err.println("本次识别失败!");}//获取验证码//OCR.debug(); //开始Fiddler4抓包(127.0.0.1:8888)String code = OCR.ocrCode4Net(config.url(imgUrl), saveCodePath);while(code.length()!=5){//如果识别的验证码位数不等于5,则重新识别if(code.equals("亲,apiKey已经过期或错误,请重新获取")){System.err.println(code);return;}code = OCR.ocrCode4Net(config.url(imgUrl), saveCodePath);}System.out.println("本地识别的验证码为:"+code);System.out.println("验证码已保存到:"+saveCodePath);//开始验证识别的验证码是否正确result = HttpClientUtil.get(config.url(verifyUrl+"?vc="+code+"&qqid="+qq));} while (result.contains("succeed"));System.out.println("识别验证码成功!反馈信息如下:\n" + result);}

运行结果如下:

最新的完整代码请到GitHub上进行下载:https://github.com/Arronlong/httpclientUtil 。

httpclientUtil (QQ交流群:548452686 

轻松把玩HttpClient之封装HttpClient工具类(七),新增验证码识别功能相关推荐

  1. Java封装OkHttp3工具类

    点击关注公众号,Java干货及时送达  作者:如漩涡 https://blog.csdn.net/m0_37701381 Java封装OkHttp3工具类,适用于Java后端开发者 说实在话,用过挺多 ...

  2. writeValueAsString封装成工具类

    封装成工具类 [java] view plaincopyprint? <span style="font-family:Microsoft YaHei;">public ...

  3. MySQL数据库学习笔记(十一)----DAO设计模式实现数据库的增删改查(进一步封装JDBC工具类)...

    [声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/4 ...

  4. 基于AFNetworking的封装的工具类

    基于AFNetworking的封装的工具类MXERequestService // // MXERequestService.h // testAFNetWorking // // Created b ...

  5. 分页封装实用工具类及其使用方法

    分页封装实用工具类及其使用方法 作者: javaboy2012 Email:yanek@163.com qq:    1046011462 package com.yanek.util; import ...

  6. Redis(七) - 封装Redis工具类

    文章目录 一.封装Redis工具类 1. 使用构造方法注入StringRedisTemplate 2. 方法1:将任意Java对象序列化为json并存储在string类型的key中,并且可以设置TTL ...

  7. Java封装redis工具类RedisUtils,以及在@Postconstruct注解中调用redis可能出现redisTemplate空指针异常

    1.封装redis工具类RedisUtils import org.springframework.data.redis.core.RedisTemplate; import org.springfr ...

  8. SpringBoot整合Redis+mybatis,封装RedisUtils工具类等实战(附源码)

    点击上方蓝色字体,选择"标星公众号" 优质文章,第一时间送达 关注公众号后台回复pay或mall获取实战项目资料+视频 作者:陈彦斌 cnblogs.com/chenyanbin/ ...

  9. 装饰器/使用类和对象封装一个工具类

    # coding:utf-8 # 装饰器是以@开头,@结构称为语法糖,装饰器的作用主要是给现有的函数增加一些额外的功能. # @classmethod # @staticmethod # @prope ...

最新文章

  1. 在eclipse里jsp编译后的java和class文件的位置
  2. linux zip指定目录解压,Linux zip解压/压缩并指定目录
  3. 强行分类提取特征自编码网络例1
  4. 只需1秒,无人机就能平地翻跟头 | IEEE
  5. Cocos2d-x项目开发时在Eclipse中配置环境编译C++
  6. MySQL优化(四):count()
  7. 离开小厂进大厂的第一周,BTAJ大厂最新面试题汇集,面试总结
  8. 前端学习(3209):react中类中方法的this指向
  9. Android 系统(229)---OTA
  10. CENTOS6.5 TFS部署
  11. 一位程序员 8 年的物联网奋斗史
  12. python装饰器class_Python装饰器dataclass详解
  13. tms320c54x汇编语言,TMS320C54x DSP原理及应用
  14. 翻译: 中国北斗卫星导航系统 全球导航卫星系统 (GNSS)
  15. 阿里云虚拟主机备案期间网站调试
  16. 恶意软件\垃圾流氓通用反删除批处理文件
  17. SURF(Speeded Up Robust Features)算法原理
  18. 【金融财经】金融市场一周简报(2017-10-13)
  19. Word打开docx文件报错
  20. 印度市场救不了苹果手机,也救不了库克

热门文章

  1. AS5048A SPI 14位磁旋转编码器
  2. React 基础-创建元素
  3. 代码实现PHP web服务器下绝对路径到Web网址的转换
  4. 在Word中插入图片
  5. stm32F407讲解目录大纲(库函数)
  6. navicat循环插入gp数据库,设备数据
  7. Stack(栈)------遵循先进后出(FILO)原则,线程安全
  8. 数据结构--栈(FILO)和队列(FIFO)的应用
  9. Dobot机械臂开发学习(一)
  10. 从实习生到自动化测试工程师,面试过程给我整吐了,你看你有没有经历过。