最近由于单位原有业务更改钉钉机器人发送本地图片,导致原有周报获取数据方式有所更改,周报汇总数也随之需要重新定义输出、展现方式,在这里对之前所做的操作做下总结:
由于是要生成图片所以图表绘制方面在技术选型时就选用了JfreeChart,尽管JfreeChart有很多的缺点譬如说:生成的图片、文字不清晰,JfreeChart的文档要收费等等,但是这些都不是事,图片文字(尤其是对于中文乱码的问题)这个问题都可以后期自己解决的。
什么? 一步步来? 好吧
一、准备下基础的东西
1.1
pom文件加入依赖(版本号可以自行选择)
1.2
创建接收图表横纵坐标数据的DTO(get、set ...已省略)
1.3 获取横纵坐标数据(在获取数据时,若有数据不完全(网络超时等原因),则重启线程进行重试,最大重启次数设置为重启3次,重启3次后依然失败此次获取数据宣告失败),程序结束。以下是项目中获取友盟统计信息的部分代码:
@Component
public class GetUmengWeeklyActiveUsersImage {
private static final Logger logger = LoggerFactory.getLogger(GetUmengWeeklyActiveUsersImage.class);
@Autowired
AmazonS3 amazonS3;
@Autowired
ExecutorThreadPools pool;
@Autowired
UmengOpenApiClient umengOpenApiClient;
@Value("${umeng.static.appKey.****sh.ios}")
String appKeyIos = "";
@Value("${umeng.static.appKey.****sh.android}")
String appKeyAndroid = "";
@Autowired
UcenterServiceUserStaticsApiV1Client ucenterServiceUserStaticsApiV1Client;
@Autowired
MessageCenterDingCustomBotV1Client messageCenterDingCustomBotV1Client;
@Value("${cloud.aws.cephInternetPrefix}")
String cephPrefix;
String token = "*******************************************";
public void execute() {
pool.getExecuteReportPool().execute(new Worker().enableExeLimit().setExeLimit(3).setInterval(10000L));
}
class Worker extends ConfigurableThread {
@Override
protected void doRun() throws Exception {
String endDate = new SimpleDateFormat("yyyy-MM-dd").format(DateUtil.addDays(DateUtil.today(), 0));
String startDate = new SimpleDateFormat("yyyy-MM-dd").format(DateUtil.addDays(DateUtil.today(), -7));
MarkdownMessageExt reportMarkDown = new MarkdownMessageExt();
// umeng 周活统计
int thisWeekUsers = 0;
int thisWeekNewUsers = 0;
int thisWeekActiveUsers = 0;
int thisWeekLauches = 0;
String fileName = "";
String path = "";
HashMap weeklyMap = new HashMap();
List list = new ArrayList();
try {
UmengUappCountData[] WeeklyIosActiveUsers = umengOpenApiClient.getActiveUsers(appKeyIos, startDate,
new SimpleDateFormat("yyyy-MM-dd").format(DateUtil.addDays(DateUtil.today(), -1)), "daily");
for (UmengUappCountData umengUappCountData : WeeklyIosActiveUsers) {
String date = umengUappCountData.getDate();
int parseInt = Integer.parseInt(date.replace("-", ""));
Integer value = umengUappCountData.getValue();
weeklyMap.put(parseInt, value);
}
UmengUappCountData[] WeeklyAndroidActiveUsers = umengOpenApiClient.getActiveUsers(appKeyAndroid,
startDate, new SimpleDateFormat("yyyy-MM-dd").format(DateUtil.addDays(DateUtil.today(), -1)),
"daily");
int weeklyActiveUsers = 0;
for (UmengUappCountData umengUappCountData : WeeklyAndroidActiveUsers) {
String date = umengUappCountData.getDate();
int parseInt = Integer.parseInt(date.replace("-", ""));
Integer value = umengUappCountData.getValue();
for (Entry map : weeklyMap.entrySet()) {
if (map.getKey().equals(parseInt)) {
map.setValue(map.getValue() + value);
SimpleColumnarGraphDataDTO dto = new SimpleColumnarGraphDataDTO();
dto.setColumnKey(parseInt + "");
dto.setValue(weeklyMap.get(parseInt));
list.add(dto);
}
}
}
for (Integer key : weeklyMap.keySet()) {
weeklyActiveUsers += weeklyMap.get(key);
}
Map dataMap = new HashMap();
dataMap.put("本周日活量总数", weeklyActiveUsers);
dataMap.put("日活量日均", weeklyActiveUsers / 7);
dataMap.put("ActiveUsers", list);
if (!weeklyMap.isEmpty()) {
MakeAppDataImage makeAppDataImage = new MakeAppDataImage();
JFreeChart chart = makeAppDataImage.makeAppDataChart(dataMap);
File createTempFile = File.createTempFile("按天统计日活量", ".png",
new File(System.getProperty("java.io.tmpdir")));
ChartUtilities.saveChartAsPNG(createTempFile, chart, 1100, 500);
fileName = createTempFile.getName();
logger.info("文件名称为:{}", fileName);
path = uploadToCeph(createTempFile);
logger.info("按天统计用户注册数成功生成图片---> {}" + createTempFile);
} else {
logger.error("按天统计用户注册数出图失败");
}
} catch (Exception e) {
logger.info("钉钉消息发送失败." + e.getMessage(), e);
}
try {
logger.info("开始向钉钉发送消息----");
reportMarkDown.setTitle("按天统计日活量");
reportMarkDown.add(MarkdownMessage.getHeaderText(3, "按天统计日活量 "));
reportMarkDown.add(MarkdownMessage
.getImageText("https://eximages.12306.cn/wificloud/wifi-monitor/weeklyreport/" + fileName));
if (fileName.length() > 0) {
ResultBean msgBean = messageCenterDingCustomBotV1Client.sendMarkdown(token, reportMarkDown);
logger.info("消息返回 {}", msgBean);
if (msgBean.getStatus() == COMMON_API_WRAPPER_STATIC_VALUE.RESULTCODE.SUCCESS) {
markAsSuccess();
} else {
for (int tryCount = 1; tryCount < 4; tryCount++) {
execute();
logger.info("重试第{}次重新获取数据/生成图片", tryCount);
}
}
}
} catch (Exception e) {
logger.info("钉钉消息发送失败." + e.getMessage(), e);
}
// }
}
private String uploadToCeph(File sourceFile) {
String path = "wifi-monitor/weeklyreport/" + sourceFile.getName();
logger.info("开始上传到s3 {}/{}", COMMON_API_WRAPPER_STATIC_VALUE.RESOURCE.WIFI_BUCKET_NAME, path);
PutObjectRequest putObjectRequest = new PutObjectRequest(
COMMON_API_WRAPPER_STATIC_VALUE.RESOURCE.WIFI_BUCKET_NAME, path, sourceFile);
putObjectRequest.withCannedAcl(CannedAccessControlList.PublicRead);
PutObjectResult result = amazonS3.putObject(putObjectRequest);
logger.info("上传ceph的result返回结果为:{}", JSONDataUtil.toJSONString(result));
sourceFile.delete();
return COMMON_API_WRAPPER_STATIC_VALUE.RESOURCE.WIFI_BUCKET_NAME + "/" + path;
}
@Override
protected void onTimeout() {
logger.info("统计生成超时,线程结束");
}
@Override
protected void onTryout() {
logger.info("统计尝试次数超出,线程结束");
}
@Override
protected void onSuccess() {
logger.info("统计执行成功,线程结束");
}
@Override
protected void threadExceptionHandle(Exception e) {
logger.info("统计执行错误,程序退出.错误信息:" + e);
}
}
/**
* 使用 Map按key进行排序
*
* @param map
* @return
*/
private static Map sortMapByKey(Map map) {
if (map == null || map.isEmpty()) {
return null;
}
Map sortMap = new TreeMap(new MapKeyComparator());
sortMap.putAll(map);
return sortMap;
}
}
1.4获取数据后,进行数据图表图片的生成,生成图表图片后将其上传至CEPH,然后钉钉将其根据ceph图片地址对其进行获取发送。(生成图片时要特别注意处理的是 JfreeChart 关于中文乱码的问题)
/**
* @author zyq 报表图形实现类
*/
public class MakeAppDataImage {
/**
*
* @param dataMap
* map(key--value):{ title--图表标题 path--图片导出地址
* chartdata--图表数据(list集合)
* rowTitle--横轴标题 columnTitle--纵轴标题 }
* @return
* @throws Exception
*/
public JFreeChart makeAppDataChart(Map dataMap) throws Exception {
List dataList = (List) dataMap.get("ActiveUsers");
CategoryDataset createDataset = createDataset(dataList);
// StandardChartTheme standardChartTheme = new StandardChartTheme("CN");
// // 设置标题字体
// standardChartTheme.setExtraLargeFont(new Font("隶书", Font.BOLD, 20));
// // 设置图例的字体
// standardChartTheme.setRegularFont(new Font("宋书", Font.PLAIN, 15));
// // 设置轴向的字体
// standardChartTheme.setLargeFont(new Font("宋书", Font.PLAIN, 15));
// // 应用主题样式
// ChartFactory.setChartTheme(standardChartTheme);
JFreeChart chart = ChartFactory.createLineChart(
"本周日活量 总量 " + dataMap.get("本周日活量总数") + " 日均 " + dataMap.get("日活量日均"), "", "人次", createDataset,
PlotOrientation.VERTICAL, // 绘制方向
false, // 显示图例
false, // 采用标准生成器
false // 是否生成超链接
);
chart.getTitle().setFont(new Font("黑体", Font.BOLD, 18)); // 设置标题字体
// chart.getLegend().setItemFont(new Font("宋体", Font.PLAIN, 15));//
// 设置图例类别字体
chart.setBackgroundPaint(Color.WHITE);// 设置背景色
// 获取绘图区对象
CategoryPlot plot = chart.getCategoryPlot();
plot.setBackgroundPaint(Color.WHITE); // 设置绘图区背景色
plot.setRangeGridlinePaint(Color.GRAY); // 设置水平方向背景线颜色
plot.setRangeGridlinesVisible(true);// 设置是否显示水平方向背景线,默认值为true
plot.setDomainGridlinePaint(Color.WHITE); // 设置垂直方向背景线颜色
plot.setDomainGridlinesVisible(true); // 设置是否显示垂直方向背景线,默认值为false
CategoryAxis domainAxis = plot.getDomainAxis();
domainAxis.setLabelFont(new Font("黑体", Font.BOLD, 15)); // 设置横轴字体
domainAxis.setTickLabelFont(new Font("黑体", Font.BOLD, 15));// 设置坐标轴标尺值字体
domainAxis.setLowerMargin(0.01);// 左边距 边框距离
domainAxis.setUpperMargin(0.06);// 右边距 边框距离,防止最后边的一个数据靠近了坐标轴。
domainAxis.setMaximumCategoryLabelLines(2);
ValueAxis rangeAxis = plot.getRangeAxis();
rangeAxis.setLabelFont(new Font("黑体", Font.BOLD, 15));
rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());// Y轴显示整数
rangeAxis.setAutoRangeMinimumSize(1); // 最小跨度
rangeAxis.setUpperMargin(0.18);// 上边距,防止最大的一个数据靠近了坐标轴。
rangeAxis.setLowerBound(0); // 最小值显示0
rangeAxis.setAutoRange(false); // 不自动分配Y轴数据
rangeAxis.setTickMarkStroke(new BasicStroke(1.6f)); // 设置坐标标记大小
rangeAxis.setTickMarkPaint(Color.BLACK); // 设置坐标标记颜色
// 获取折线对象
LineAndShapeRenderer renderer = (LineAndShapeRenderer) plot.getRenderer();
renderer.setBaseItemLabelsVisible(true);
renderer.setBasePositiveItemLabelPosition(
new ItemLabelPosition(ItemLabelAnchor.OUTSIDE12, TextAnchor.BASELINE_CENTER));
renderer.setBaseItemLabelGenerator(new StandardCategoryItemLabelGenerator());
renderer.setSeriesPaint(0, Color.blue);// 设置线条颜色
BasicStroke realLine = new BasicStroke(1.8f); // 设置实线
// 设置虚线
float dashes[] = { 5.0f };
BasicStroke brokenLine = new BasicStroke(2.2f, // 线条粗细
BasicStroke.CAP_ROUND, // 端点风格
BasicStroke.JOIN_ROUND, // 折点风格
8f, dashes, 0.6f);
for (int i = 0; i < createDataset.getRowCount(); i++) {
if (i % 2 == 0)
renderer.setSeriesStroke(i, realLine); // 利用实线绘制
else
renderer.setSeriesStroke(i, brokenLine); // 利用虚线绘制
}
plot.setNoDataMessage("无对应的数据,请重新查询。");
plot.setNoDataMessageFont(new Font("宋体", Font.PLAIN, 15));// 字体的大小
plot.setNoDataMessagePaint(Color.BLUE);// 字体颜色
return chart;
}
private CategoryDataset createDataset(List dataList) {
// 统计图数据
DefaultCategoryDataset dataset = new DefaultCategoryDataset();
for (SimpleColumnarGraphDataDTO rowdata : dataList) {
dataset.addValue(rowdata.getValue(), "日活数", rowdata.getColumnKey());
}
return dataset;
}
private CategoryDataset createData(Map dataMap) {
// 统计图数据
DefaultCategoryDataset dataset = new DefaultCategoryDataset();
return dataset;
}
}
1.5 然后自己开一个debug测试请求,启动程序进行测试(这样就避免有些同学钉钉机器人发送本地图片,启用定时任务在等定时任务的尴尬(别笑,很早之前我就等过定时任务触发时间 哈哈))
package com.rails.wifi.monitquerylayer.api;
import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.rails.wifi.commonapiwrapper.bean.ResultBean;
import com.rails.wifi.commonapiwrapper.factory.ResultFactory;
import com.rails.wifi.monitquerylayer.core.worker.GetRegisterByDayImage;
import com.rails.wifi.monitquerylayer.core.worker.GetUmengWeeklyActiveUsersImage;
import com.rails.wifi.monitquerylayer.core.worker.WeeklyUserLogStaticsReporterPre;
@RestController
@RequestMapping(value = "/debugApi")
public class TaskDebugWeeklyApi {
private static final Logger logger = LoggerFactory.getLogger(TaskDebugWeeklyApi.class);
@Autowired
WeeklyUserLogStaticsReporterPre weeklyUserLogStaticsReporterPre;
@Autowired
GetRegisterByDayImage getRegisterByDayImage;
@Autowired
GetUmengWeeklyActiveUsersImage getUmengWeeklyActiveUsersImage;
// 日活周报发送
@GetMapping(value = "triggerUserWeeklyReport")
public ResultBean triggerUserWeeklyReport() {
logger.info("触发周报用户日活图表生成: {}", new Date());
getUmengWeeklyActiveUsersImage.execute();
return ResultFactory.success();
}
}
来看看测试后的结果 (此图是钉钉机器人发到群里的图片)=_=:
到这里图片生成并且能够正常发送到钉钉群,下篇总结下如何添加钉钉机器人,完成图片,文字。。。等内容发送到指定钉钉群。