我干了什么
突发奇想想要看看每天花在纯码代码(CTRL+CV)上的时间到底有多少,第一反应就是去找找Intellij IDEA的Marketplace,随便装一个,计时的插件应该都大同小异吧。
用time做关键字搜索出来的结果排在第一位的就是WakaTime,嚯,下载量不低,就你了。
安装之后restart IDE,要求输入api key,这个api key 是需要去它的官网注册后每个帐号对应一个api key,注册过程按下不表。在settings-account拿到api key填上进入IDE,它就开始计时了。过一段时间,就可以在网站的Dashboard中看到自己的编程时间统计图。
类似这样:
没事找事
最初的目的已经实现了,但,这样就完了吗?NO!
我发现官网提供了API文档,详细说明了API接口的请求格式;认证方式;以及多种不同资源的数据接口的请求路径格式,参数,响应实例,例如可查询对单个提交所花费的时间,对所有提交的编程时间列表,所有项目的编程时间列表,给定时间范围编程活动列表等。
出于闲,那就拿这个API玩一玩吧。
要请求接口需要对用户身份进行验证,API文档给出两种认证方式:
- 一种是使用OAuth 2.0
- 一种是使用API密钥
- 或是使用HTTP基本认证,在请求头的Authorization字段带上base64编码的API key
- 或是直接将API key做为查询参数传递
拿到数据
我使用HTTP基本认证的方式对我的请求进行认证。以请求我的一段时间的编码活动为例,部分代码如下:private static final String KEY = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";//API key
public static String getHeader() {
//base64编码
byte[] encodedAuth = Base64.encode(KEY.getBytes(Charset.forName("US-ASCII")));
return "Basic " + new String(encodedAuth);
}
public static Object getSummaries(String urlStr) throws Exception {
//创建HttpClient对象
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
HttpGet get = new HttpGet(urlStr);
//设置HTTP基本认证
get.addHeader("Authorization", getHeader());
HttpResponse response = httpClient.execute(get);
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
//返回json格式
String res = EntityUtils.toString(response.getEntity());
JSONObject object = JSONObject.parseObject(res);
JSONObject data = (JSONObject) object.getJSONArray("data")
Gson gson = new Gson();
Summary summaries;
//反序列化
summaries = gson.fromJson(data.toString(), Summary.class);
return summaries;
}
return response.getStatusLine().getReasonPhrase();
}
public static void main(String[] args) {
Object summaries = null;
String url = "https://wakatime.com/api/v1/users/current/summaries?start=2020-09-23&end=2020-09-27";
summaries = getSummaries(url);
}
该接口的必要参数是start范围开始日期和end范围结束日期,可选参数如project可具体到某一项目,branches具体到某些分支等。
返回的json字符串又臭又长,看起来很伤脑筋。我利用Gson将获得的json字符串反序列化得到了我根据json数据结构定义的实体Summary,它只有一个属性:
private List<JsonOuter> days;其中包含start-end之间的每一天(JsonOuter)的统计数据。有了这个实体,才可更愉快的玩下去。
发送邮件
拿到了数据实体,对数据的处理结果需要通知给我,我就想到可以试试邮件通知。
汇整的数据应以表格的形式呈现出来,邮件内容支持html语言,所以可以将html源码做为邮件正文发送。
再者,表格仍然不够直观,WakaTime的Dashboard是用图表现的,我也可以试试。
表格
以某一天的categories和languages为例,创建表格所需的html源码的方法函数如下:
private static String createMailContext(Object Outer){
Map<String,List<JsonInner>> items = new HashMap<String,List<JsonInner>>();
StringBuffer sb = new StringBuffer();
JsonOuter jsonOuter = null;
if (Outer instanceof JsonOuter) {
jsonOuter = (JsonOuter)Outer;
List<JsonInner> categories = jsonOuter.getCategories();
List<JsonInner> languages = jsonOuter.getLanguages();
items.put("categories",categories);
items.put("languages",languages);
}else{
sb.append(Outer.toString());
return sb.toString();
}
for(String key : items.keySet()){
List<JsonInner> part = items.get(key);
StringBuffer oneItemStr = new StringBuffer("<TABLE width=\"100%\" border=\"1\" cellspacing=\"0\" >\n" +
"<tr style='background-color:#fdf6e3;'> \n" +
" <th colspan=\"3\">");
oneItemStr.append(key);
oneItemStr.append("</th>\n" +
" </tr>\n" +
"<tr style='text-align:center;background-color:#77773c'> \n" +
"<td>name</td> \n" +
"<td>digital</td>\n" +
"<td>percent</td>\n" +
"</tr> \n");
for(JsonInner inner : part){
oneItemStr.append("<tr style=\"text-align:center;background-color:#5cd65c;\"> \n" );
oneItemStr.append("<td>\n") ;
oneItemStr.append(inner.getName());
oneItemStr.append("</td> \n");
oneItemStr.append("<td>") ;
oneItemStr.append(inner.getDigital());
oneItemStr.append("</td> \n");
oneItemStr.append("<td>") ;
oneItemStr.append(inner.getPercent());
oneItemStr.append("</td> \n");
oneItemStr.append(" </tr> \n");
}
oneItemStr.append("</TABLE>\n<br /><br/>");
sb.append(oneItemStr);
}
return sb.toString();
};
JsonOuter是一天的统计数据,它包括了几个维度: private List<JsonInner> categories;
private List<JsonInner> editors;
private JsonInner grand_total;
private List<JsonInner> languages;
private List<JsonInner> projects;
而JsonInner是每个维度的内部属性,上述的五个维度都具有这些属性,它们是:
public class JsonInner {
private String digital;
private int hours;
private int minutes;
private int seconds;
private String name;
private String text;
private Double percent;
private Double total_seconds;
}为直观理解Summary/JsonOuter/JsonInner三者的关系,上图:
接回上面所说的表格生成方法,它将生成categories、languages两个表格,有name、digital、percent三列。
饼图
以languages为例,生成各种编程语言所占比例饼图并将生成的图片保存。创建方法如下:
private static void createPieChart(JsonOuter outer,String filePath) {
List<JsonInner> languages = outer.getLanguages();
DefaultPieDataset pds = new DefaultPieDataset();
for(JsonInner language : languages){
pds.setValue(language.getName(),language.getTotal_seconds());
}
JFreeChart chart = ChartFactory.createPieChart("", pds, false, false, true);
try {
// 分别是:显示图表的标题、需要提供对应图表的DateSet对象、是否显示图例、是否生成贴士以及是否生成URL链接
// 如果不使用Font,中文将显示不出来
Font font = new Font("宋体", Font.BOLD, 16);
// 设置图片标题的字体
chart.getTitle().setFont(font);
// 得到图块,准备设置标签的字体
PiePlot plot = (PiePlot) chart.getPlot();
// 设置标签字体
plot.setLabelFont(font);
plot.setStartAngle(new Float(3.14f / 2f));
// 设置plot的前景色透明度
plot.setForegroundAlpha(0.7f);
// 设置plot的背景色透明度
plot.setBackgroundAlpha(0.0f);
// 设置标签生成器(默认{0})
// {0}:key {1}:value {2}:百分比 {3}:sum
plot.setLabelGenerator(new StandardPieSectionLabelGenerator("{0}({1}占{2})"));
File directory = new File("./");
System.out.println(directory.getAbsolutePath());
// 将内存中的图片写到本地硬盘
ChartUtilities.saveChartAsJPEG(new File(filePath), chart, 600, 300);
} catch (Exception e) {
e.printStackTrace();
}
}
当然了,看到注释如此完整就知道肯定是借来的轮子咯。组装
显然,这封邮件包含了文本和图片,需要把它们组装在一起作为邮件的正文。看代码:
/**
* 创建一封邮件
*
* @param session 和服务器交互的会话
* @param sendMail 发件人邮箱
* @param receiveMail 收件人邮箱
* @return
* @throws Exception
*/
public static MimeMessage createMimeMessage(Session session,Object jsonOuter, String sendMail, String receiveMail) throws Exception {
// 1. 创建一封邮件
MimeMessage message = new MimeMessage(session);
// 2. From: 发件人
message.setFrom(new InternetAddress(sendMail, "工具人", "UTF-8"));
// 3. To: 收件人(可以增加多个收件人、抄送、密送)
message.setRecipient(MimeMessage.RecipientType.TO, new InternetAddress(receiveMail, "自由人", "UTF-8"));
// 4. Subject: 邮件主题
message.setSubject(Util.getLastDate(new Date()) +"编程汇报", "UTF-8");
MimeBodyPart image = new MimeBodyPart();
if(jsonOuter instanceof JsonOuter){
// 5. 创建图片"节点"
createPieChart((JsonOuter)jsonOuter,LanguageImagePath);
// 读取本地文件
DataHandler dh = new DataHandler(new FileDataSource(LanguageImagePath));
// 将图片数据添加到"节点"
image.setDataHandler(dh);
// 为"节点"设置一个唯一编号(在文本"节点"将引用该ID)
//image.setContentID("mailTestPic");
image.setHeader("Content-ID", "<image>");
}
String context = createMailContext(jsonOuter); //生成正文
// 5. Content: 邮件正文(可以使用html标签)
MimeBodyPart text = new MimeBodyPart();
text.setContent(context+"<img src='cid:image'/>", "text/html;charset=UTF-8");
// 7. (文本+图片)设置 文本 和 图片"节点"的关系(将 文本 和 图片"节点"合成一个混合"节点")
MimeMultipart mm_text_image = new MimeMultipart();
mm_text_image.addBodyPart(text);
mm_text_image.addBodyPart(image);
mm_text_image.setSubType("related"); // 关联关系
message.setContent(mm_text_image);
// 6. 设置发件时间
message.setSentDate(new Date());
// 7. 保存设置
message.saveChanges();
return message;
}
现在,一封邮件就正式生成了。让我们把它发出去吧!发送
我将利用我的qq邮箱把这份邮件发送至我的gmail邮箱。注意,发送邮件需要开通SMTP服务并获得授权码。发送邮件如下:
public static String myEmailAccount = "xxxxxxxxxx@qq.com";
public static String myEmailPassword = "xxxxxxxxxxxxxxxx"; //授权码
public static String myEmailSMTPHost = "smtp.qq.com";
public static String receiveMailAccount = "xxxxxxxxxxxxx@gmail.com";
public static void sendEmail(Object jsonOuter){
// 1. 创建参数配置, 用于连接邮件服务器的参数配置
Properties props = new Properties(); // 参数配置
props.setProperty("mail.transport.protocol", "smtp"); // 使用的协议(JavaMail规范要求)
props.setProperty("mail.smtp.host", myEmailSMTPHost); // 发件人的邮箱的 SMTP 服务器地址
props.setProperty("mail.smtp.auth", "true"); // 需要请求认证
// 获取默认session对象
Session session = Session.getDefaultInstance(props,new Authenticator(){
public PasswordAuthentication getPasswordAuthentication()
{
//发件人邮件用户名、授权码
return new PasswordAuthentication(myEmailAccount, myEmailPassword);
}
});
// 设置为debug模式, 可以查看详细的发送 log
session.setDebug(true);
// 3. 创建一封邮件
MimeMessage message = null;
try {
message = createMimeMessage(session,jsonOuter, myEmailAccount, receiveMailAccount);
} catch (Exception e) {
e.printStackTrace();
}
// 4. 根据 Session 获取邮件传输对象
Transport transport = null;
try {
transport = session.getTransport();
// 5. 使用 邮箱账号 和 密码 连接邮件服务器, 这里认证的邮箱必须与 message 中的发件人邮箱一致, 否则报错
transport.connect(myEmailAccount, myEmailPassword);
// 6. 发送邮件, 发到所有的收件地址, message.getAllRecipients() 获取到的是在创建邮件对象时添加的所有收件人, 抄送人, 密送人
transport.sendMessage(message, message.getAllRecipients());
// 7. 关闭连接
transport.close();
File languageFile = new File(LanguageImagePath);
//删除图片
languageFile.delete();
} catch (NoSuchProviderException e) {
e.printStackTrace();
} catch (javax.mail.MessagingException e) {
e.printStackTrace();
}
}
至此,一封汇报通知邮件就发送出去了。事实上,我也已经将程序打包为jar并在cmd中运行它。这样做的目的是我希望能够把它做成一个自动任务在后它进行,比如每天的早上九点就调用一次接口查询昨天的编程数据,生成邮件并发送至我的邮箱。这样我就可以看到昨天做了什么。
可以对日期进行判断,在每周一都对上周总体情况进行汇总并报告。
免费用户只能查询过去两周,因此无法做月报。但我可以在每天对昨天数据查询后把json字符串保存在数据库里,这样我就可以做月报了。
另外还可以写一个脚本,使该自动任务随系统启动而启动,无需我再去使用java -jar命令启动它。
毕竟折腾没有尽头~~




No comments:
Post a Comment