报表JfreeChart生成柱状图、线形图并通过钉钉机器人定时发送到钉钉群

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/java_zyq/article/details/84562571

     最近由于单位原有业务更改,导致原有周报获取数据方式有所更改,周报汇总数也随之需要重新定义输出、展现方式,在这里对之前所做的操作做下总结:

由于是要生成图片所以图表绘制方面在技术选型时就选用了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<Integer, Integer> weeklyMap = new HashMap<Integer, Integer>();
			List<SimpleColumnarGraphDataDTO> list = new ArrayList<SimpleColumnarGraphDataDTO>();
			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<Integer, Integer> 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<String, Object> dataMap = new HashMap<String, Object>();
				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<Integer, Integer> sortMapByKey(Map<Integer, Integer> map) {
		if (map == null || map.isEmpty()) {
			return null;
		}
		Map<Integer, Integer> sortMap = new TreeMap<Integer, Integer>(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<SimpleColumnarGraphDataDTO>集合)
	 *            rowTitle--横轴标题 columnTitle--纵轴标题 }
	 * @return
	 * @throws Exception
	 */
	public JFreeChart makeAppDataChart(Map<String, Object> dataMap) throws Exception {

		List<SimpleColumnarGraphDataDTO> dataList = (List<SimpleColumnarGraphDataDTO>) 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<SimpleColumnarGraphDataDTO> dataList) {
		// 统计图数据
		DefaultCategoryDataset dataset = new DefaultCategoryDataset();
		for (SimpleColumnarGraphDataDTO rowdata : dataList) {
			dataset.addValue(rowdata.getValue(), "日活数", rowdata.getColumnKey());
		}
		return dataset;
	}

	private CategoryDataset createData(Map<Integer, SimpleColumnarGraphDataDTO> 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();
	}
	


}

 来看看测试后的结果 (此图是钉钉机器人发到群里的图片)=_=:

 到这里图片生成并且能够正常发送到钉钉群,下篇总结下如何添加钉钉机器人,完成图片,文字。。。等内容发送到指定钉钉群。

猜你喜欢

转载自blog.csdn.net/java_zyq/article/details/84562571