整个项目最核心的部分就是获取数据了,因为如果获取不到数据,那发送消息都是空谈。我一开始有考虑过使用别人写好的Python项目来获取数据,但找了很多,我发现都是通过分析那些数据接口来模拟发送https请求获取数据。由于不会写Python,无法去拓展别人的代码,于是就用Java构造了一个HTTPS请求类,因为Java的rt.jar这个基础类库中包含有java.net这个网络编程相关的类,通过自己封装一个方便调用的类,来对那些数据接口发送hppts请求,从而获取数据。
构造一个这个类的目的,就是为了提供简单的方法,将url、请求参数、请求头等信息放入一个https请求中,简便地发送请求,不用重复编写相关代码,即代码复用。
package com.gnz48.zzt.util;
import java.awt.Image;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import org.apache.catalina.webresources.war.Handler;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gnz48.zzt.confing.MyX509TrustManager;
import com.gnz48.zzt.exception.RuleException;
/**
* @ClassName: Https
* @Description: Https操作的工具类
* @author JuFF_白羽
* @date 2018年6月12日 上午10:39:42
*/
public class Https {
private static final Logger LOGGER = LoggerFactory.getLogger(Https.class);
private String url;
private Map params;
private String dataType;
private String result;
private Map requestPropertys;
private String payloadJson;
public Https() {
}
public Https(String url, String dataType, Map params, Map requestPropertys,
String payloadJson) {
this.url = url;
this.dataType = dataType;
this.params = params;
this.requestPropertys = requestPropertys;
this.payloadJson = payloadJson;
}
/**
* @Title: getUrl
* @Description: 获取当前使用的URL
* @author JuFF_白羽
* @return String
*/
public String getUrl() {
return url;
}
/**
* @Title: setUrl
* @Description: 设置URL
* @author JuFF_白羽
* @param url
* 请求地址
* @return Https
*/
public Https setUrl(String url) {
this.url = url;
return this;
}
/**
* @Title: getParams
* @Description: 获取当前使用的参数
* @author JuFF_白羽
* @return Map
*/
public Map getParams() {
return params;
}
/**
* @Title: setParams
* @Description: 设置参数
* @author JuFF_白羽
* @param params
* 参数键值对
* @return Https
*/
public Https setParams(Map params) {
this.params = params;
return this;
}
/**
* @Title: setDataType
* @Description: 设置请求方式
*
* 可用"POST","GET"
* @author JuFF_白羽
* @param dataType
* @return Https 返回类型
*/
public Https setDataType(String dataType) {
this.dataType = dataType;
return this;
}
/**
* @Title: setRequestProperty
* @Description: 设置请求头
* @author JuFF_白羽
* @param requestPropertys
* 请求头的Map
* @return Https 返回类型
*/
public Https setRequestProperty(Map requestPropertys) {
this.requestPropertys = requestPropertys;
return this;
}
/**
* @Title: setPayloadJson
* @Description: 设置需要用流写入的json参数
*
* 该方法是为了无法用url?携带参数的请求而编写的,使用输出流将参数写入后台请求中。
* @author JuFF_白羽
* @param payloadJson
* json字符串
* @return Https 返回类型
*/
public Https setPayloadJson(String payloadJson) {
this.payloadJson = payloadJson;
return this;
}
/**
* @Title: send
* @Description: 发送请求
* @author JuFF_白羽
* @return String 返回类型
*/
public String send() {
validateRule(this.url);
StringBuffer buffer = null;
try {
// 以SSL的规则创建SSLContext
SSLContext sslContext = SSLContext.getInstance("SSL");
TrustManager[] tm = { new MyX509TrustManager() };
// 初始化
sslContext.init(null, tm, new SecureRandom());
// 获取SSLSocketFactory对象
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
URL url = new URL(installParams(this.url, this.params));
HttpsURLConnection urlConn = (HttpsURLConnection) url.openConnection();
urlConn.setDoOutput(true);
urlConn.setDoInput(true);
// 请求不使用缓存
urlConn.setUseCaches(false);
// 设置请求头
if (requestPropertys != null) {
for (Map.Entry entry : requestPropertys.entrySet()) {
urlConn.setRequestProperty(entry.getKey(), entry.getValue());
}
}
// 设置请求方式
urlConn.setRequestMethod(this.dataType);
// 设置当前实例使用的SSLSoctetFactory
urlConn.setSSLSocketFactory(sslSocketFactory);
// 设置需要用流写入的请求参数
if (payloadJson != null && !payloadJson.equals("")) {
OutputStreamWriter writer = new OutputStreamWriter(urlConn.getOutputStream(), "UTF-8");
writer.write(payloadJson);
writer.close();
}
urlConn.connect();
// 读取服务器端返回的内容
InputStream is = urlConn.getInputStream();
InputStreamReader isr = new InputStreamReader(is, "utf-8");
BufferedReader br = new BufferedReader(isr);
buffer = new StringBuffer();
String line = null;
while ((line = br.readLine()) != null) {
buffer.append(line);
}
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
new Https(null, null, null, null, null);// 清空参数
}
return buffer.toString();
}
private static String readAll(Reader rd) throws IOException {
StringBuilder sb = new StringBuilder();
int cp;
while ((cp = rd.read()) != -1) {
sb.append((char) cp);
}
return sb.toString();
}
public static JSONObject readJsonFromUrl(String url) throws IOException, JSONException {
InputStream is = new URL(url).openStream();
try {
BufferedReader rd = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8")));
String jsonText = readAll(rd);
JSONObject json = new JSONObject(jsonText);
return json;
} finally {
is.close();
// System.out.println("同时 从这里也能看出 即便return了,仍然会执行finally的!");
}
}
/**
* @Title: validateRule
* @Description: 对传入的URL进行必要的校验
* @author JuFF_白羽
* @param url
* 请求地址
*/
public static void validateRule(String url) {
if (url.equals("")) {
throw new RuleException("url不能为空!");
}
if (!url.startsWith("http://") && !url.startsWith("https://")) {
throw new RuleException("url的格式不正确!");
}
}
/**
* @Title: installParams
* @Description: 为请求地址增加参数
* @author JuFF_白羽
* @param url
* 请求地址
* @param params
* 装有参数的Map
* @return String 用?带参数的url
*/
private static String installParams(String url, Map params) {
if (params == null) {
return url;
}
int flag = 1;
url += "?";
for (Map.Entry param : params.entrySet()) {
if (flag > 1) {
url += "&";
}
url = url + param.getKey() + "=" + param.getValue();
flag += 1;
}
return url;
}
// /**
// * @Title: getImage
// * @Description: 发送请求获取图片
// * @author JuFF_白羽
// * @param path
// * 存储地址
// * @throws IOException
// */
// public File getImage(String path) {
// validateRule(this.url);
// StringBuffer buffer = null;
// try {
// // 以SSL的规则创建SSLContext
// SSLContext sslContext = SSLContext.getInstance("SSL");
// TrustManager[] tm = { new MyX509TrustManager() };
// // 初始化
// sslContext.init(null, tm, new SecureRandom());
// // 获取SSLSocketFactory对象
// SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
// URL url = new URL(this.installParams(this.url, this.params));
// HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
// urlConn.setDoOutput(true);
// urlConn.setDoInput(true);
// // 请求不使用缓存
// urlConn.setUseCaches(false);
// // 设置请求头
// if (requestPropertys != null) {
// for (Map.Entry entry : requestPropertys.entrySet()) {
// urlConn.setRequestProperty(entry.getKey(), entry.getValue());
// }
// }
// // 设置请求方式
// urlConn.setRequestMethod(this.dataType);
// // 设置当前实例使用的SSLSoctetFactory
//// urlConn.setSSLSocketFactory(sslSocketFactory);
// // 设置需要用流写入的请求参数
// if (payloadJson != null && !payloadJson.equals("")) {
// OutputStreamWriter writer = new OutputStreamWriter(urlConn.getOutputStream(), "UTF-8");
// writer.write(payloadJson);
// writer.close();
// }
// urlConn.connect();
// // 读取服务器端返回的内容
// InputStream is = urlConn.getInputStream();
// int index;
// byte[] bytes = new byte[1024];
// FileOutputStream downloadFile = new FileOutputStream(path);
// while ((index = is.read(bytes)) != -1) {
// downloadFile.write(bytes, 0, index);
// downloadFile.flush();
// }
// downloadFile.close();
// is.close();
// } catch (IOException e) {
// e.printStackTrace();
// } catch (NoSuchAlgorithmException e) {
// e.printStackTrace();
// } catch (KeyManagementException e) {
// e.printStackTrace();
// } finally {
// new Https(null, null, null, null, null);// 清空参数
// }
// return null;
// }
}
主要参数有url、params、dataType、requestPropertys、payloadJson。
private String url:接口的地址,相关方法有getUrl()和setUrl(String url)。
private Map
private String dataType:请求方式,即GET、POST等,相关方法有setDataType(String dataType)。
private Map
private String payloadJson:json参数,是用来放不跟在url后面的参数,主要是给POST请求携带的参数使用,相关方法有setPayloadJson(String payloadJson),其中字符串payloadJson要为有效的json字符串。
在设置完主要参数后,调用send()方法后就可得到json结果集,之后就可以开始遍历json获取里面的数据。在send()方法中有几点需要注意的:第一,安全套接字协议SSLContext使用SSL的实例;第二,new URL(url)调用前,GET携带的参数一定要先放入url中;第三,json参数需要用OutputStreamWriter写入,写入后记得关闭流;第四,读取服务器端返回的内容用输入流读取。
我们所需要的数据有些是json对象,有些是存放在json对象中的html,因此并没有可封装的方法,只能一个请求接口一个解析方法。解析json需要导入新包,而解析html虽然可以用解析xml的Java自带的Document相关类,但由于过于繁琐,这里我也导入一个新包来帮助我进行解析。
导入的jar包:
org.jsoup
jsoup
1.11.3
org.json
json
20180130
解析json常用的一些方法:
/*
* 获得一个JSON对象,result为调用send()后返回的JSON结果集的字符串。
* 但result应为有效的JSON字符串,若有多余部分,须裁剪至需要的部分。
*/
JSONObject jsonObject = new JSONObject(result);
/*
* 获取key对应的Long值。
*/
jsonObject.getLong("memberId");
/*
* 获取key对应的String值。
*/
jsonObject.getString("memberAvatar");
/*
* 获取key对应的JSON对象。
*/
jsonObject.getJSONObject("data");
/*
* 获取key对应的JSON数组。
*/
jsonObject.getJSONArray("cards");
/*
* JSON对象和Map类似,都是以key和value一一对应,因此keys()可获得一个key的迭代器。
* 通过while遍历迭代器中的key,以此获取JSON对象中的每个key对应的值。
*/
Iterator iterator = jsonObject.keys();
while (iterator.hasNext()) {// 遍历全体成员信息对象
String key = (String) iterator.next();
JSONObject memberObject = jsonObject.getJSONObject(key);// 此处不一定是JSON对象,应实际情况而变
}
HTML进行Document操作的相关文档:jsoup Cookbook(中文版)
由于Quartz相关博客我已有写,我这里就贴一个工作任务类出来说明一下。
package com.gnz48.zzt.job;
import java.text.ParseException;
import org.json.JSONException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.stereotype.Component;
import com.gnz48.zzt.service.HttpsService;
/**
* @ClassName: SyncTask
* @Description: 同步信息任务类
* @author JuFF_白羽
* @date 2018年7月27日 下午5:59:14
*/
@Configuration
@Component // 此注解必加
@EnableScheduling // 此注解必加
public class SyncTask {
public SyncTask() {
}
private static final Logger LOGGER = LoggerFactory.getLogger(SyncTask.class);
/**
* Https请求服务
*/
@Autowired
private HttpsService httpsService;
/**
* @Title: syncMember
* @Description: 定时任务:每小时同步成员的信息
* @author JuFF_白羽
*/
public void syncMember() {
httpsService.syncMember();
}
/**
* @Title: syncDynamic
* @Description: 定时任务:每3分钟同步微博动态
* @author JuFF_白羽
*/
public void syncDynamic() {
httpsService.syncDynamic();
}
/**
* @Title: syncRoomMessage
* @Description: 定时任务:每分3钟同步口袋房间消息
* @author JuFF_白羽
* @throws ParseException
* @throws JSONException
*/
public void syncRoomMessage() throws JSONException, ParseException {
httpsService.syncRoomMessage();
}
/**
* @Title: syncModianPool
* @Description: 定时任务:每10分钟同步摩点集资项目
* @author JuFF_白羽
* @throws ParseException
* @throws JSONException
*/
public void syncModianPool() throws JSONException, ParseException {
httpsService.syncModianPool();
}
}
HttpsService类中具体编写了发送请求并解析返回的结果集的代码,SyncTask类中我并没有把每个同步服务写在一个方法中,而是分多个方法,意味着分了多个不同的轮询任务,这样有利于之后的动态设置任务的拓展。
Quartz相关见:Spring Boot与Quartz整合
【GNZ48-章泽婷应援会】基于Java的SNH48Group应援会机器人(一)项目简介
【GNZ48-章泽婷应援会】基于Java的SNH48Group应援会机器人(三)发送消息