关于前端:SpringBoot实战手把手教你实现数据报表统计并定时推送

55次阅读

共计 10791 个字符,预计需要花费 27 分钟才能阅读完成。

通过一个小的业务点登程,搭建一个能够实例应用的我的项目工程,将各种知识点串联起来; 实战演练专题中,每一个我的项目都是能够独立运行的,蕴含若干知识点,甚至能够不做批改间接利用于生产我的项目;

明天的实战我的项目次要解决的业务需要为: 每日新增用户统计,生成报表,并邮件发送给相干人

本我的项目将蕴含以下知识点:

  • 基于 MySql 的每日新增用户报表统计(如何统计每日新增用户,若日期不间断如何主动补 0?)
  • 定时执行报表统计工作
  • MyBatis + MySql 数据操作
  • 邮件发送
  • Thymeleaf 引擎实现报表模板渲染

I. 需要拆解

须要相对来说属于比拟明确的了,目标就是实现一个主动报表统计的工作,查问出每日的用户新增状况,而后推送给指定的用户

因而咱们将很清晰的晓得,咱们须要干的事件

定时工作

这里重点放在如何来反对这个工作的定时执行,通常来说定时工作会辨别为固定时刻执行 + 距离时长执行两种(留神这种辨别次要是为了不便了解,如每天五点执行的工作,也能够了解为每隔 24h 执行一次)

前者常见于 一次性工作 ,如本文中的每天统计一次,这种就是绝对典型的固定时刻执行的工作;

后者常见于 轮询式工作 ,如常见的利用探活 (每隔 30s 发一个 ping 音讯,判断服务是否健在)

定时工作的计划十分多,有趣味的小伙伴能够关注一波“一灰灰 blog”公众号,蹲守一个后续

本文将间接采纳 Spring 的定时工作实现需求场景,对这块不相熟的小伙伴能够看一下我之前的分享的博文

  • 180801-Spring 之定时工作根本应用篇
  • 180803-Spring 定时工作高级应用篇

    每日新增用户统计

每日新增用户统计,实现形式挺多的,比方举几个简略的实现思路

  • 基于 redis 的计数器:一天一个 key,当天有新用户时,同步的实现计数器 +1
  • 基于数据库,新增一个统计表,蕴含如日期 + 新增用户数 + 沉闷用户数 等字段
  • 有新用户注册时,对应日期的新增用户数,沉闷用户数 + 1
  • 老用户今日首次应用时,沉闷用户数 + 1

下面两个计划都须要借助额定的库表来辅助反对,本文则采纳间接统计用户表,依据注册工夫来聚合统计每日的新增用户数

  • 长处:简略,无额定要求,实用于数据量小的场景(比方用户量小于百万的)
  • 毛病:用户量大时,数据库压力大

对于如何应用 mysql 进行统计每日新增用户,不相熟的小伙伴,举荐参考博主之前的分享文章

  • 220707-MySql 按时、天、周、月进行数据统计 – 一灰灰 Blog [4]

报表生成 & 推送用户

接下来就是将下面统计的数据,生成报表而后推送给用户;首先是如何将数据生成报表?其次则是如何推送给指定用户?

将数据组装成报表的形式通常取决于你抉择的推送形式,如飞书、钉钉之类的,有对应的开发 api,能够间接推送富文本;

本文的实现姿态则抉择的是通过邮件的形式进行发送,why?

  • 飞书、钉钉、微信之类的,须要受权,对于不应用这些作为办公软件的小伙伴没什么意义
  • 短信须要钱 ….

对于邮件,大家应该都有,无论是 qq 邮箱,还是工作邮箱;基本上对于想要间接跑本文的小伙伴来说,没有什么额定的门槛

对于 java/spring 如何应用邮箱,对此不太熟悉的小伙伴,能够参考博主之前的分享文章

  • 【中间件】SpringBoot 系列之邮件发送姿态介绍 | 一灰灰 Blog [5]

下面文章中介绍的是 FreeMaker 来实现模板渲染,本文则介绍另外一个知识点,借助 Thymleaf 来实现数据报表的生成(一篇文章获取这么多知识点,就问你开不开心 O(∩_∩)O)

II. 散布实现

1. 我的项目搭建

首选搭建一个根本的 SpringBoot 利用,置信这一步大家都很相熟了;若有不懂的小伙伴,请点赞、评论加博主好友,手把手教你,不免费

最终的我的项目依赖如下

<dependencies>
  <!-- 邮件发送的外围依赖 -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
  </dependency>

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
  </dependency>

  <dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.1-jre</version>
  </dependency>

  <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.2</version>
  </dependency>
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
  </dependency>
</dependencies>

别看下面如同依赖了不少包,实际上各有用途

  • spring-boot-starter-web : 提供 web 服务
  • spring-boot-starter-mail : 发邮件就靠它
  • mybatis-spring-boot-starter : 数据库操作

咱们的用户存在 mysql 中,这里应用 mybatis 来实现 db 操作(又一个知识点来了,收好不谢)

2. 数据筹备

文末的源码蕴含库表构造,初始化数据,能够间接应用

既然模仿的是从数据库中读取每日新增用户,所以咱们筹备了一张表

CREATE TABLE `u1` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键 id',
  `name` varchar(64) NOT NULL DEFAULT ''COMMENT'name',
  `email` varchar(512) NOT NULL DEFAULT ''COMMENT'email',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '生成工夫',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新工夫',
  PRIMARY KEY (`id`),
  KEY `idx_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='u1 测试';

接下来筹备写入一些数据;为了模仿某些天没有新增用户,贴心的一灰灰博主给大家提供基于 python 的数据生成脚本,源码如下 (python3+,对 python 不熟的小伙伴,能够到博主的站点进补一下,超链 [6] )

import datetime

def create_day_time(n):
    now = datetime.datetime.now()
    now = now - datetime.timedelta(days = n)
    return now.strftime("%Y-%m-%d %H:%S:%M")

vals = []
for i in range(0, 100):
    if (i % 32 % 6) == 0:
        # 模仿某一天没有用户的场景
        continue
    vals.append(f"('{i}_灰灰 ','{i}hui@email.com','{create_day_time(i % 32)}','{create_day_time(i % 32)}')")

values = ',\n\t'.join(vals)
sqls = f"INSERT INTO story.u1 (name, email, create_time, update_time) VALUES \n{values};"
print(sqls)

3. 全局配置

数据筹备结束之后,接下来配置一下 db、email 相干的参数

resources/application.yml 文件内容如下

spring:
  #邮箱配置
  mail:
    host: smtp.163.com
    from: xhhuiblog@163.com
    # 应用本人的发送方用户名 + 受权码填充
    username:
    password:
    default-encoding: UTF-8
    properties:
      mail:
        smtp:
          auth: true
          starttls:
            enable: true
            required: true

  datasource:
    url: jdbc:mysql://127.0.0.1:3306/story?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password:

  thymeleaf:
    mode: HTML
    encoding: UTF-8
    servlet:
      content-type: text/html
    cache: false

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.git.hui.demo.report.dao.po 

下面的配置分为三类

  • 数据库相干:连贯信息,用户名明码,mybatis 配置
  • thymleaf:模板渲染相干
  • email: 邮箱配置相干,请留神若应用博主的源码,在本地运行时,请依照后面介绍的邮箱博文中手把手的教程,获取您本人的邮箱受权信息,填在下面的 username, password 中

4. 数据报表统计实现

接下来就正式进入大家脍炙人口的编码实现环节,咱们间接应用 mybaits 来实现数据库操作,定义一个统计的接口

/**
 * @author YiHui
 */
public interface UserStatisticMapper {
    /**
     * 统计最近多少天内的新增用户数
     *
     * @param days 统计的天数,从以后这一天开始
     * @return
     */
    List<UserStatisticPo> statisticUserCnt(int days);
}

接口中定义了一个 PO 对象,就是咱们心愿返回的数据,其定义就十分清晰简略了,工夫 + 数量

@Data
public class UserStatisticPo {
    private String day;
    private Integer count;
}

下面定义的常识接口,具体首先,当然是放在 mybatis 的传统 xml 文件中,依据后面 application.yml 配置,咱们的 xml 文件须要放在 resources/mapper 目录下,具体实现如下

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.git.hui.demo.report.dao.UserStatisticMapper">

    <resultMap id="countMap" type="com.git.hui.demo.report.dao.po.UserStatisticPo">
        <result column="day" property="day"/>
        <result column="count" property="count"/>
    </resultMap>

    <!-- 统计用户新增  -->
    <select id="statisticUserCnt" resultMap="countMap">
        SELECT date_table.day as `day`, IFNULL(data.cnt, 0) as `count`
        from
        (select DATE_FORMAT(create_time, '%Y-%m-%d') day, count(id) cnt from u1 GROUP BY day) data
            right join
        (SELECT @date := DATE_ADD(@date, interval - 1 day) day from (SELECT @date := DATE_ADD(CURDATE(), interval 1 day) from u1) days limit #{days}) date_table
        on date_table.day = data.day
    </select>
</mapper> 

重点看一下下面的 sql 实现,为什么会一个 join 逻辑?

那咱们稍稍思考,若咱们间接通过日期进行 format 之后,再 group 一下统计计数,会有什么问题?给大家 3s 的思考工夫

  • 1s
  • 2s
  • 3s

好的 3s 工夫到,当初颁布答案,当某一天一个新增用户都没有的时候,会产生什么事件?会呈现这一天的数据空缺,即返回的列表中,少了一天,不间断了,如果前段的小伙伴基于这个列表数据进行绘图,很有可能出现异常

所以出于零碎的健壮性思考(即传说中的鲁棒性),咱们心愿若某一天没有数据,则对应的计数设置为 0

具体的 sql 阐明就不开展了,请查看博文获取更多:MySql 按时、天、周、月进行数据统计 [7]

5. 报表生成实现

数据统计进去之后,接下来就是基于这些数据来生成咱们报表,咱们借助 Thymleaf 来实现,因而先写一个 html 模板,resources/templates/report.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title th:text="${vo.htmlTitle}"> 每日用户统计 </title>
</head>
<style> .title22 {
        font: 16px/24px bold;
        position: relative;
        display: block;
        padding: 0 6px;
        margin-left: -6px;
        margin-bottom: 12px;
        font-size: 22px;
        font-weight: 550;
    }

    .container {
        background: #fff;
        overflow: auto;
        padding: 6px;
        margin: 6px;
        font-family: 'Microsoft YaHei UI', 'Microsoft YaHei', '微软雅黑', SimSun, '宋体';
    }

    .content {
        overflow: auto;
        padding: 6px 12px;
        margin: 6px;
    }

    table {
        border: none;
        border-collapse: collapse;
        table-layout: fixed;
    }

    .thead {
        font: 14px/20px bold;
        font-weight: 550;
        background: #eaeaea;
        line-height: 1.5em;
    }

    .tbody {
        font: 15px/20px normal;
        font-weight: 540;
        background: #fff;
    }

    tr > td {
        padding: 6px 12px;
        border: 1px solid #d8d8d8;
        max-width: 600px;
    } </style>
<body>
<div class="container">
    <div class="content">
        <div class="title22" style="color: red;" th:text="${vo.tableTitle}"> 统计题目 </div>
        <table>
            <thead class="thead">
            <tr>
                <td class="thead" style="background:#eaeaea;"> 日期 </td>
                <td style="min-width: 50px; color: #4040e1"> 新增用户 </td>
            </tr>
            </thead>
            <tbody class="tbody">
            <tr th:each="item: ${vo.list}">
                <td class="thead" style="background:#eaeaea;" th:text="${item.day}">2022-08-01</td>
                <td style="min-width: 50px; color: #4040e1" th:text="${item.count}">1</td>
            </tr>
            </tbody>
        </table>
    </div>
</div>
</body>
</html>

一个非常简单的 table 模板,须要接管三个数据,与之对应的 vo 对象,咱们定义如下

@Data
public class StatisticVo {
    // 表格数据项,即日期 + 数量的列表
    private List<UserStatisticPo> list;
    // 网页的题目
    private String htmlTitle;
    // 表格题目
    private String tableTitle;
}

接下来就是拿到数据之后,将它与模板渲染失去咱们心愿的数据,这里次要借助的是 org.thymeleaf.spring5.SpringTemplateEngine

外围实现如下

@Service
public class StatisticAndReportService {
    @Autowired
    private UserStatisticMapper userStatisticMapper;

    @Autowired
    private JavaMailSender javaMailSender;

    @Autowired
    private Environment environment;

    @Autowired
    private SpringTemplateEngine templateEngine;

    public StatisticVo statisticAddUserReport() {List<UserStatisticPo> list = userStatisticMapper.statisticUserCnt(30);
        StatisticVo vo = new StatisticVo();
        vo.setHtmlTitle("每日新增用户统计");
        vo.setTableTitle(String.format("【%s】新增用户报表", LocalDate.now()));
        vo.setList(list);
        return vo;
    }

    public String renderReport(StatisticVo vo) {Context context = new Context();
        context.setVariable("vo", vo);
        String content = templateEngine.process("report", context);
        return content;
    }
}

模板渲染就一行 templateEngine.process("report", context),第一个参数为模板名,就是下面的 html 文件名(对于模板文件、动态资源怎么放,放在那儿,这个知识点当然也能够在一灰灰的站点获取,超链 [8]

第二个参数用于封装上下文,传递模板须要应用的参数

5. 邮件发送

报表生成之后,就是将它推送给用户,咱们这里选定的是邮箱形式,具体实现也比较简单,然而在最终部署到生产环境(如阿里云服务器时,可能会遇到坑,同样显著的知识点,博主会没有分享么?当然不会没有了,Email 生产环境发送排雷指南,你值得领有 [9]

/**
 * 发送邮件的逻辑
 *
 * @param title
 * @param content
 * @throws MessagingException
 */
public void sendMail(String title, String content) throws MessagingException {MimeMessage mimeMailMessage = javaMailSender.createMimeMessage();
    MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMailMessage, true);
    // 邮件发送人,从后面的配置参数中拿,若没有配置,则应用默认的 xhhuiblog@163.com
    mimeMessageHelper.setFrom(environment.getProperty("spring.mail.from", "xhhuiblog@163.com"));
    // 邮件接管人,能够是多个
    mimeMessageHelper.setTo("bangzewu@126.com");
    // 邮件主题
    mimeMessageHelper.setSubject(title);
    // 邮件内容
    mimeMessageHelper.setText(content, true);

    // 解决 linux 上发送邮件时,抛出异样 JavaMailSender no object DCH for MIME type multipart/mixed
    Thread.currentThread().setContextClassLoader(javax.mail.Message.class.getClassLoader());
    javaMailSender.send(mimeMailMessage);
}

下面的实现,间接写死了收件人邮箱,即我自己的邮箱,各位大佬在应用的时候,请记得替换一下啊

下面的实现除了发送邮件这个知识点之外,还有一个暗藏的获取配置参数的知识点,即 environment#getProperty(),有趣味的小伙伴翻博主的站点吧

6. 定时工作

下面几部基本上就把咱们的整个工作性能都实现了,从数据库中统计出每日新增用户,而后借助 Thymleaf 来渲染模板生成报告,而后借助 email 进行发送

最初的一步,就是工作的定时执行,间接借助 Spring 的 Schedule 来实现咱们的指标,这里咱们心愿每天 4:15 分执行这个工作,如下配置即可

// 定时发送,每天 4:15 分统计一次,发送邮件
@Scheduled(cron = "0 15 4 * * ?")
//    下下面这个是每分钟执行一次,用于本地测试
//    @Scheduled(cron = "0/1 * * * * ?")
public void autoCalculateUserStatisticAndSendEmail() throws MessagingException {StatisticVo vo = statisticAddUserReport();
        String content = renderReport(vo);
        sendMail("新增用户报告", content);
}

7. 测试

最初测试演练一下,启动办法如下,除了根本的启动注解之外,还指定了 mapper 接口地位,开启定时工作;感兴趣的小伙伴能够试一下干掉这两个注解会怎么,评论给出你的实测后果吧

@EnableScheduling
@MapperScan(basePackages = "com.git.hui.demo.report.dao")
@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class);
    }
}

当然我再理论测试的时候,不可能真等到早上四点多来看是否执行,大早晨还是要睡觉的;因而本地测试的时候,能够将下面定时工作改一下,换成每隔一分钟执行一次

接一个 debug 的两头图

关上的内容展现

此外,源码除了实现了定时推送之外,也提供了一个 web 接口,拜访之后间接能够查看报表内容,不便大家调款式,实现如下

@Controller
public class StatisticReportRest {

    @Autowired
    private StatisticAndReportService statisticAndReportSchedule;

    @GetMapping(path = "report")
    public String view(Model model) {StatisticVo vo = statisticAndReportSchedule.statisticAddUserReport();
        model.addAttribute("vo", vo);
        return "report";
    }
}

8. 干货总结

最初进入一灰灰的保留环节,这么“大”一个我的项目坐下来的,当然是得好好盘一盘它的知识点了,后面的各大节内容中有交叉的指出相应的知识点,接下来如雨的知识点将迎面袭来,不要眨眼

  • Spring 定时工作 @Schedule
  • 怎么用?-> 180801-Spring 之定时工作根本应用篇 – 一灰灰 Blog [10]
  • 多个工作串行并行,是否会相互影响?自定义线程池怎么整?一个异样会影响其余么?-> Spring 定时工作高级应用篇 – 一灰灰 Blog [11]
  • 数据库统计每日新增
  • mysql 间接统计日新增,sql 怎么写?工夫不间断,如何躲避?-> MySql 按时、天、周、月进行数据统计 – 一灰灰 Blog [12]
  • mybatis 操作 db 怎么玩?-> Mybatis 系列教程 [13]
  • 模板渲染
  • 数据报表生成,间接字符串拼接?还是模板引擎的渲染?
  • 更多的 spring web 知识点 -> SpringWeb 专栏 |
  • 邮件发送
  • 怎么发邮件?-> SpringBoot 无障碍应用邮箱服务 [15]
  • 如何防止上线不采坑 -> Email 生产环境发送排雷指南,你值得领有 [16]

除了下面比较突出的知识点之外,当然还有其余的,如 Spring 如何读取配置参数,SpringMVC 如何向模板中传递上下文,模板语法,动态资源怎么放等等

写到这我本人都惊呆了好么,一篇文章这么多知识点,还有啥好犹豫的,这可能是我这个假期内最初一篇实战干货了,马上要开学了,老婆孩子回归之后,后续的更新就靠各位读友的崔更放弃了

欢送关注我的公众号:敲代码的老贾,回复“支付”赠送《Java 面试》材料,阿里,腾讯,字节,美团,饿了么等大厂

正文完
 0