场景

在12 月份做的报表功能中,直接从 ES 查询一个月的数据。当数据量特别大时,查询速度会非常缓慢甚至查询失败。解决方案是使用定时任务,在每天凌晨指定时间自动查询前一天的数据,然后写入 CSV 文件中,每天追加。生成报表文件时,就不用再查询 ES,而是读取 CSV 文件,然后统计一个月每天数据的总和。

一、定时任务

定时任务使用的是 Quartz 框架。

Quartz 是什么

Quartz 是一个开源的作业调度框架,由 java 编写,在.NET 平台为 Quartz.Net,通过 Quart 可以快速完成任务调度的工作。

Quartz 使用场景

如定时发送邮件、定时统计数据生成报表等等

在项目中使用 Quartz

添加依赖

<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.3.0</version>
</dependency>

spring-quartz.xml 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
    <!-- 要调用的工作类 -->
    <bean id="quartzJob" class="com.devywb.quartzJob"></bean>
    <!-- 定义调用的对象和方法 -->
    <bean id="jobTask" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
        <!-- 调用的类 -->
        <property name="targetObject">
            <ref bean="quartzJob"></ref>
        </property>
        <!-- 调用的方法 -->
        <property name="targetMethod">
            <value>work</value>
        </property>
    </bean>

    <!-- 定义触发的时间 -->
    <bean id="doTime" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
      <property name="jobDetail">
                <ref bean="jobTask"/>
      </property>
      <!-- cron表达式 -->
      <property name="cronExpression">
        <!-- 每隔20秒钟执行一次 -->
        <!-- <value>*/20 * * * * ?</value> -->
        <!-- 每天凌晨五点执行 -->
        <value>0 0 5 * * ?</value>
      </property>
    </bean>

     <!-- 总管理类 -->
     <bean id="startQuertz" lazy-init="false" autowire="no" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
         <property name="triggers">
             <list>
                 <ref bean="doTime"/>
             </list>
         </property>
     </bean>
</beans>

二、Tablesaw

Tablesaw是一套内存内数据表,其中包含多种数据工具与面向列的存储格式。其设计思路认为没人会面向小型任务执行分布式分析,而大家可以在单一服务器上对200万行级别的表进行交互。

大家能够利用Tablesaw执行各种规则,从而检查显示布局、数据优先级或者针对数据显示及交互向特定用户提供扩展控制范围。在它的帮助下,我们可以利用RDBMS与CSV文件导入数据,添加及删除列,执行映射与规约操作或者将表保存在经过压缩的列式存储格式当中。

添加依赖

<!-- https://mvnrepository.com/artifact/tech.tablesaw/tablesaw-core-->
<dependency>
    <groupId>tech.tablesaw</groupId>
    <artifactId>tablesaw-core</artifactId>
    <version>0.11.2</version>
    <exclusions>
        <exclusion>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-math3</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.0</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-math3 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-math3</artifactId>
    <version>3.0</version>
</dependency>

在项目中添加 Tablesaw 的依赖后,项目无法启动,原因是 commons-lang3 和 commons-math3 两个依赖的版本太高了。后来采用的解决方法是排除依赖再另外引入低版本依赖。

常用 API

// 读取 csv 文件
Table table = Table.read().csv(String filePath);

// 获取所有列名
List<String> columnNames = table.columnNames();

// sum
table.sum("列名").get();

// 排序
table.sortDescendingOn("列名"); // 降序
table.sortAscendingOn("列名"); // 升序

// 分组
table.groupBy(columns);

// 前多少条
table.first(nRows)

// 生成 saw 文件
Table table = Table.read().csv(contents, tableName);
table.save(folder);

// 读取 saw 文件
Table table = Table.readTable(tableNameAndPath)

踩过的坑

写入数据至 CSV 文件时,当数据中某个字段包含分隔符或其他特殊符号时,程序会报错。下面列出两种解决方案。

第一种,使用第三方库,例如 openCSV,它底层对特殊符号做了处理;

第二种,手动处理字段中的特殊符号;

/**
 * list 转 CSV 字符串
 * @param header
 * @param data
 * @return
 */
public static String list2CsvStr(Map<String, String> header, List<Map<String, Object>> data) {
    String content = "";
    if (header != null && header.size() > 0) {
        if (data != null && data.size() > 0) {
            StringBuilder sb = new StringBuilder();
            for (Map<String, Object> map : data) {
                Set<Entry<String, String>> entrySet = header.entrySet();
                int i = 0;
                for (Entry<String, String> entry : entrySet) {
                    String key = entry.getKey();
                    if(i > 0) sb.append(SEPARATOR); // 不是第一列时,添加分隔符
                    if(map.containsKey(key)) {
                        Object value = map.get(key);
                        String valueStr = value != null ? value.toString() : "";
                        if(valueStr.contains(SEPARATOR)) { // 如果数据包含分割符
                            if(valueStr.contains("\"")) { // 如果字段中包含双引号,替换成两个
                                valueStr.replace("\"", "\"\"");
                            }
                            // 如果字段包含分割符,则用双引号括起来
                            valueStr = "\"" + valueStr +"\"";
                        }
                        sb.append(valueStr);
                    } else {
                        sb.append("");
                    }
                    i ++ ;
                }
                sb.append("\n");
            }
            content = sb.toString();
        }
    }
    return content;
}

/**
 * map 转 CSV 字符串
 * @param header
 * @param data
 * @return
 */
public static String map2CsvStr(Map<String, String> header, Map<String, Object> data) {
    String content = "";
    if (header != null && header.size() > 0) {
        if (data != null && data.size() > 0) {
            StringBuilder sb = new StringBuilder();
            int i = 0;
            Set<Entry<String, String>> entrySet = header.entrySet();
            for (Entry<String, String> entry : entrySet) {
                String key = entry.getKey();
                if(i > 0) sb.append(SEPARATOR);
                if(data.containsKey(key)) {
                    Object value = data.get(key);
                    String valueStr = value != null ? value.toString() : "";
                    if(valueStr.contains(SEPARATOR)) { // 如果数据包含分割符
                        if(valueStr.contains("\"")) { // 如果字段中包含双引号,替换成两个
                            valueStr.replace("\"", "\"\"");
                        }
                        // 如果字段包含分割符,则用双引号括起来
                        valueStr = "\"" + valueStr +"\"";
                    }
                    sb.append(valueStr);
                } else {
                    sb.append("");
                }
                i ++;
            }
            sb.append("\n");
            content = sb.toString();
        }
    }
    return content;
}

Quartz + Tablesaw 报表统计的更多相关文章

  1. bzoj1058: [ZJOI2007]报表统计

    set.操作:insert(u,v)在u后面插入v,若u后面已插入过,在插入过的后面插入.mingap求出序列两两之间差值的最小值.minsortgap求出排序后的序列两两之间的最小值.用multis ...

  2. BZOJ 1058: [ZJOI2007]报表统计( 链表 + set )

    这种题用数据结构怎么写都能AC吧...按1~N弄个链表然后每次插入时就更新答案, 用set维护就可以了... --------------------------------------------- ...

  3. [补档][ZJOI2007] 报表统计

    [ZJOI2007] 报表统计 题目 传送门 小Q的妈妈是一个出纳,经常需要做一些统计报表的工作.今天是妈妈的生日,小Q希望可以帮妈妈分担一些工作,作为她的生日礼物之一. 经过仔细观察,小Q发现统计一 ...

  4. 【BZOJ1058】【ZJOI2007】报表统计(链表,堆,Splay)

    [BZOJ1058][ZJOI2007]报表统计 题面 题目描述 Q的妈妈是一个出纳,经常需要做一些统计报表的工作.今天是妈妈的生日,小Q希望可以帮妈妈分担一些工作,作为她的生日礼物之一. 经过仔细观 ...

  5. BZOJ_1058_[ZJOI2007]报表统计_STL

    BZOJ_1058_[ZJOI2007]报表统计_STL Description 小Q的妈妈是一个出纳,经常需要做一些统计报表的工作.今天是妈妈的生日,小Q希望可以帮妈妈分担一些工 作,作为她的生日礼 ...

  6. bzoj 1058: [ZJOI2007]报表统计 (Treap)

    链接:https://www.lydsy.com/JudgeOnline/problem.php?id=1058 题面; 1058: [ZJOI2007]报表统计 Time Limit: 15 Sec ...

  7. 基于Fusioncharts的报表统计

    先了解fusioncharts插件,fusioncharts是一款基于XML和flash的报表组件,支持Java.PHP.AngularJS等等开发语言,所以,开发出来,加入swf文件,就可以出现动态 ...

  8. 洛谷 P1110 [ZJOI2007]报表统计 解题报告

    P1110 [ZJOI2007]报表统计 题目描述 \(Q\)的妈妈是一个出纳,经常需要做一些统计报表的工作.今天是妈妈的生日,小\(Q\)希望可以帮妈妈分担一些工作,作为她的生日礼物之一. 经过仔细 ...

  9. bzoj P1058 [ZJOI2007]报表统计——solution

    1058: [ZJOI2007]报表统计 Time Limit: 15 Sec  Memory Limit: 162 MB Submit: 4099  Solved: 1390 [Submit][St ...

随机推荐

  1. React 深入系列2:组件分类

    文:徐超,<React进阶之路>作者 授权发布,转载请注明作者及出处 React 深入系列2:组件分类 React 深入系列,深入讲解了React中的重点概念.特性和模式等,旨在帮助大家加 ...

  2. maven多环境部署

    1.首先在maven的pom.xml文件中添加profiles标签,然后分别添加3个不同环境的profile, 本例中添加了dev.test.product三个环境,这个可以根据自己的需要添加和减少. ...

  3. Python内置函数(27)——range

    英文文档: range(stop) range(start, stop[, step]) Rather than being a function, range is actually an immu ...

  4. Spring Security 入门(1-3-5)Spring Security - remember me!

    Remember-Me 功能 概述 Remember-Me 是指网站能够在 Session 之间记住登录用户的身份,具体来说就是我成功认证一次之后在一定的时间内我可以不用再输入用户名和密码进行登录了, ...

  5. 日推20单词 Day01

    1.conflict n. 冲突 2.electronic adj. 电子的 3.mine n. 矿藏,地雷 4.mineral n. 矿物质 adj. 矿物的 5.undermine vt 破坏,渐 ...

  6. 三、如何使用QtDesigner

    三.如何使用QtDesigner 启动 QtDesigner,创建一个PyQt项目 拖动Label到主窗体,双击并输入自己想输入的文字 并保持为 HelloWorld.ui 此时在你Python项目下 ...

  7. hdu1005 Number Sequence---找循环节

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=1005题目大意: f(1) = 1, f(2) = 1, f(n) = (A * f(n - 1) + ...

  8. .Net中Web增加加密狗管理

    由于业务中最近需要使用到加密狗,增加对Web代码的管控,所以需要进行加密狗使用的研究 首先,对于没有接触使用过加密狗的人需要有个大致的认识,加密狗分为 MasterDog, 1.下载加密狗的开发套件, ...

  9. angularjs购物车练习

    本文是一个简单的购物车练习,功能包括增加.减少某商品的数量,从而影响该商品的购买总价以及所有商品的购买总价:从购物车内移除一项商品:清空购物车. 页面效果如图: 若使用js或jQuery来实现这个页面 ...

  10. 微信小程序:模板消息推送提示{“errcode”:41030,”errmsg”:”invalid page hint: [gP1eXXXXXX]”}

    在开发小程序 模板消息定时推送功能时,在开发版测试程序功能运行正常,但提交到线上后提示报错{“errcode”:41030,”errmsg”:”invalid page hint: [gP1eXXXX ...