0


基于springboot+redis+国际化+定时任务的疫情项目【已上线】

这是我自己做的一套疫情实时数据项目,有地图、折线图、表格

该项目涵盖了大部分springboot项目实际开发所必需的技术

目前已经上线,🔍公号【步尔斯特】获取源码,可直接拿走学习或复用,不谢。

文章目录

一、效果图

请添加图片描述

请添加图片描述

在这里插入图片描述

在这里插入图片描述

二、技术栈

  1. 基础框架(springboot + mybatis + mybatis-plus)
  2. 缓存数据(redis)
  3. 国际化(一键切换不同语种)
  4. 定时任务(定时更新数据)
  5. 爬虫
  6. 加密
  7. 感知数据变更,进行通知推送
  8. 日志监控
  9. 数据计算和二次存储(为图表服务 - 比如趋势图)
  10. 数据展示和渲染 Thymeleaf Echarts
  11. 模拟http请求

三、项目背景

模拟疫情数据展示网站,做出一个完整的数据采集、数据存储、数据计算、数据展示的疫情数据系统。

四、搜索引擎原理

链接人和内容

网页爬取 -》 网页去重 -》 网页分析 -》 内容保存(倒排索引)

关键字查询 -》 关键字分析 -》 去匹配内容 -》 筛选出比如100条数据 -》 数据排序 (可口可乐的秘方)

分类:

通用型爬虫 和 垂直型爬虫

疫情数据系统:

数据采集 -》 数据存储 -》 数据计算 -》 数据展示

五、数据分析

5.1 分析数据源

确认能够通过代码 获取到数据 (定位到具体的http请求)

5.2 获取疫情数据

  1. 国内各省份表格数据-对应请求
  2. 对应的数据格式:json
  3. 国外表格数据-对应请求
  4. 国内趋势数据

六、数据处理

6.1 初识json

json = javascript object notation (js对象表示法)
独立于语言,具有自我描述性

请添加图片描述

Gson -> From Google

new Gson().toJson(Object obj) 将对象转化为json字符串
new Gson().fromJson(String jsonStr, T.class) 将json字符串转化为对象

6.2 数据展示

controller - service - handler

数据加载到model中 返回给html渲染

@ControllerpublicclassDataController{@AutowiredprivateDataService dataService;@GetMapping("/")publicStringlist(Model model){List<DataBean> beanList = dataService.list();
        model.addAttribute("beanList", beanList);return"list";}}
publicinterfaceDataService{List<DataBean>list();}@ServicepublicclassDataServiceImplimplementsDataService{@OverridepublicList<DataBean>list(){returnMyDataHandler.getData();}}
<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head><meta charset="UTF-8"><title>Title</title></head><body><h2> 国内疫情情况如下 </h2><br><table><thead><tr><th>地区</th><th>现有</th><th>累计</th><th>治愈</th><th>死亡</th></tr></thead><tbody><tr th:each="bean:${beanList}"><td th:text="${bean.area}">name</td><td th:text="${bean.nowConfirm}">nowConfirm</td><td th:text="${bean.confirm}">confirm</td><td th:text="${bean.heal}">heal</td><td th:text="${bean.dead}">dead</td></tr></tbody></table></body></html>

七、网络请求

HTTP
  1. “应用层协议”
  2. 了解HTTP不同版本的演进
  3. 了解GET和POST请求的区别
// 需要的参数url// 创建一个远程的连接对象  设置方法类型 GET// 设置相关参数    发送请求// 通过io接收数据后返回publicstaticStringdoGet(String urlStr){HttpURLConnection conn =null;InputStream is =null;BufferedReader br =null;StringBuilder result =newStringBuilder();try{// 创建远程url连接对象URL url =newURL(urlStr);// 打开一个连接
            conn =(HttpURLConnection) url.openConnection();// 设置为GET请求
            conn.setRequestMethod("GET");// 设置重要的参数  连接超时时间 和 读取超时时间// 超时时间  更多被距离影响   读取时间  更多被数据量影响
            conn.setConnectTimeout(15000);
            conn.setReadTimeout(60000);// header参数设置  可以不设置
            conn.setRequestProperty("Accept","application/json");// 发送请求
            conn.connect();// 状态码  200  302  404  500 ?// 如果比较时  可能出现空指针  把确定的值放在前面  可以避免if(200== conn.getResponseCode()){
                is = conn.getInputStream();
                br =newBufferedReader(newInputStreamReader(is,"UTF-8"));String line;while((line = br.readLine())!=null){
                    result.append(line);}}else{System.out.println("error responseCode :"+ conn.getResponseCode());}}catch(Exception e){
            e.printStackTrace();}finally{try{if(br !=null){
                    br.close();}if(is !=null){
                    is.close();}}catch(Exception e){
                e.printStackTrace();}

            conn.disconnect();}return result.toString();}

八、数据存储

8.1 Mybatis整合

SSM的整合 -> 入门课的整合 -> mybatis-plus的整合

明确数据结构 -> 创建mapper文件夹 -> 调用getData方法 将数据据存储到数据库中 -> 查询时从数据库中读取

a) 引入依赖
<!-- mybatis整合
             mybatis-spring-boot-starter
             mybatis-plus -boot-starter
             mysql驱动--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.1</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.2.0</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency>
b) 配置数据库连接参数
spring.datasource.url=jdbc:mysql://localhost:3306/illness?characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=123456
c) 创建数据库
CREATE DATABASE /*!32312 IF NOT EXISTS*/`illness` /*!40100 DEFAULT CHARACTER SET utf8 */;

USE `illness`;/*Table structure for table `epidemic` */

DROP TABLE IF EXISTS `epidemic`;

CREATE TABLE `epidemic` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `area` varchar(100)DEFAULT NULL,
  `confirm` int(11)DEFAULT NULL,
  `now_confirm` int(11)DEFAULT NULL,
  `dead` int(11)DEFAULT NULL,
  `heal` int(11)DEFAULT NULL,PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=308DEFAULT CHARSET=utf8;
d) 编写类及文件夹

将实体类对应到数据库的表中 属性和字段也需对应

需注意 属性的命名为驼峰命名法

如 nowConfirm 字段的命名是下划线分隔 如 now_confirm

可以使用@TableName和@TableField等注解指定对应关系

@Data@NoArgsConstructor@AllArgsConstructor@TableName("epidemic")publicclassDataBeanimplementsSerializable{// Alt + Insert 调用   安装插件privatestaticfinallong serialVersionUID
            =4938260405189292371L;// Alt + 7 查看类的方法// 地区  累计确诊人数  现有确诊人数   死亡人数  治愈人数privatelong id;privateString area;//    @TableField("all_confirm")// 如果属性名和表中字段名  不一致  可以通过 TableField注解指定privateint confirm;privateint nowConfirm;privateint dead;privateint heal;}

在主程序类上 增加@MapperScan注解 指定mapper类所在文件夹

在此文件夹下 创建mapper类 实战中 如表名叫user mapper会命名为UserMapper

@SpringBootApplication@MapperScan("com.duing.mapper")publicclassDataHandlerApplication{publicstaticvoidmain(String[] args){SpringApplication.run(DataHandlerApplication.class, args);}}publicinterfaceDataMapperextendsBaseMapper<DataBean>{}

创建service接口及其实现类

整合mybatis-plus,用其提供的IService父接口 和 ServiceImpl实现父类

publicinterfaceDataServiceextendsIService<DataBean>{}@ServicepublicclassDataServiceImplextendsServiceImpl<DataMapper,DataBean>implementsDataService{}

在controller中调用mybatis-plus实现的CRUD方法

@ControllerpublicclassDataController{@AutowiredprivateDataService dataService;@GetMapping("/")publicStringlist(Model model){List<DataBean> beanList = dataService.list();
        model.addAttribute("beanList", beanList);return"list";}}

8.2 数据初始化及定时更新

在数据查询前,先将数据存储到数据库中,我们称之为数据初始化

可以在项目启动时,先采集一次数据存储到数据库中,然后再进行定期更新

而项目启动时执行且只执行一次的逻辑,可以使用注解

@PostConstruct
//  先将DataHandler托管到spring容器中  使用@Component//  以便于获取到 dataService对象@ComponentpublicclassDataHandler{@AutowiredprivateDataService dataService;// 数据初始化//   在服务器加载Servlet时运行  且 只运行一次@PostConstructpublicvoidsaveData(){List<DataBean> dataBeans =getData();// mybatis-plus提供了可用的方法// 删除全部数据  批量新增数据
        dataService.remove(null);
        dataService.saveBatch(dataBeans);}...}

定时更新其实也是定时任务

可以通过注解@Scheduled + cron表达式来实现

Scheduled 英文原意是调度的意思 意思是我们将某段逻辑 按照指定的时间间隔 进行调度 即为定时处理

使用方式如下:

首先在入口类上 打开定时任务开关 使用注解

@EnableScheduling
@SpringBootApplication@MapperScan("com.duing.mapper")@EnableSchedulingpublicclassDataHandlerApplication{publicstaticvoidmain(String[] args){SpringApplication.run(DataHandlerApplication.class, args);}}

然后在方法上 使用 @Scheduled 搭配cron表达式 决定方法多久执行一次

// 定时更新@Scheduled(cron ="0 0 0/1 * * ? ")publicvoidupdateData(){System.out.println("要更新数据啦");System.out.println("当前时间:"+ dateFormat.format(newDate()));List<DataBean> dataBeans =getData();
        dataService.remove(null);
        dataService.saveBatch(dataBeans);}

其中cron表达式,是由6个表达不同时间单位的字段拼接而成

可以通过在线cron表达式生成器来生成
请添加图片描述
cron表达式几乎可以满足所有定时执行的需求

可以生成各类需求对应的表达式熟悉一下

除此之外,@Scheduled还支持固定时间间隔的参数设置

分别为 fixedRate 和 fixedDelay

// 固定频率任务   以ms为单位  10000代表每10s执行一次//   使用时要注意间隔时间  和  任务消耗时间的  大小关系      //   如设置间隔10s  而方法需执行20s   那么方法会等待上一次执行完成才会执行  //   唤起方法真正的时间间隔为20s@Scheduled(fixedRate =10000)publicvoidupdateData1(){}// 固定间隔任务   上一次执行结束后   再隔10s进行下一次执行// 如设置间隔10s  而方法需执行20s   那么会从上一次执行完成后开始计算  10s后开始下一次执行// 唤起方法真正的时间间隔为30s@Scheduled(fixedDelay =10000)publicvoidupdateData2(){}

九、图形化数据处理

9.1 折线图

<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head><meta charset="UTF-8"><title>Title</title><script type="text/javascript" src="js/echarts.min.js"></script></head><body><!-- 为ECharts准备一个具备大小(宽高)的Dom--><div id="main" style="width: 600px;height:400px;"></div><script th:inline="javascript">var dateStr =[[${dateList}]];var confirmStr =[[${confirmList}]];var suspectStr =[[${suspectList}]];// 基于准备好的dom  初始化实例var mychart = echarts.init(document.getElementById("main"));var option ={// 标题
        title:{
            text: '全国疫情新增趋势'
        },
        legend:{
            data:['新增确诊','新增疑似']},// x轴的数据
        xAxis:{
            data: dateStr
        },// y轴的数据类型
        yAxis:{
            type:'value'},
        series:[{
                name:'新增确诊',
                type:'line',
                data: confirmStr
            },{
                name:'新增疑似',
                type:'line',
                data: suspectStr
            }]};// 将参数设置进去
    mychart.setOption(option);</script></body></html>

9.2 地图

<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head><meta charset="UTF-8"><title>Title</title><script type="text/javascript" src="js/echarts.min.js"></script><script type="text/javascript" src="js/china.js"></script></head><body><!-- 为ECharts准备一个具备大小(宽高)的Dom--><div id="main" style="width: 1000px;height:700px;"></div><script th:inline="javascript">var dataStr1 =[[${mapData1}]];var dataStr2 =[[${mapData2}]];// 基于准备好的dom  初始化实例var mychart = echarts.init(document.getElementById("main"));var option ={
        title:{
            text:'疫情地图',
            subtext:'仅供参考',
            x:'center'},
        tooltip:{
            trigger:'item'},
        legend:{
            orient: 'vertical',
            left:'left',
            data:['现有确诊','累计确诊']},
        visualMap:{
            type: 'piecewise',
            pieces:[{min:10000, max:1000000, label: '10000人及以上', color: '#de1f05'},{min:1000, max:9999, label: '1000-9999人', color: '#ff2736'},{min:500, max:999, label: '500-999人', color: '#ff6341'},{min:100, max:499, label: '100-499人', color: '#ffa577'},{min:10, max:99, label:'10-99人', color: '#ffcea0'},{min:1, max:9, label:'1-9人', color: '#ffe7b2'},{min:0, max:0, label:'0人', color: '#e2ebf4'},],
            calculate:true},
        series:[{
                name:'现有确诊',
                type:'map',
                mapType:'china',
                roam:false,
                label:{
                    normal:{
                        position:'center',
                        show:true}},
                data: JSON.parse(dataStr1)},{
                name:'累计确诊',
                type:'map',
                mapType:'china',
                roam:false,
                label:{
                    normal:{
                        position:'center',
                        show:true}},
                data: JSON.parse(dataStr2)}]};// 将参数设置进去
    mychart.setOption(option);</script></body></html>

9.3 拓展部分

我们可以根据国内表格数据的处理方式、以及图形化数据的处理方式,举一反三的去处理海外数据,只要掌握了处理流程,其实是可以模拟腾讯新闻,将全部数据展示出来的,要注意的是,我们的项目是为了练习springboot+mybatis+thymeleaf+echarts的使用,了解http请求+json数据处理+定时任务等等功能是如何实现的,那么对数据采集方面要慎重,尽量只采集需要的少部分数据,并且控制请求次数和请求频率,一定要做到“友好访问”。

十、页面数据处理

Jsoup(Ji soup 鸡汤)----页面解析器
解析一个HTML文档的方式如下:
String html ="<html><head><title>First parse</title></head>"+"<body><p>Parsed HTML into a doc.</p></body></html>";Document doc =Jsoup.parse(html);

解析器能够尽最大可能从你提供的HTML文档中创建出一个干净的解析结果,无论HTML的格式是否完整。
比如它可以处理:

1)没有关闭的标签 
   <p>Lorem<p>Ipsum  
   可以解析成
   <p>Lorem</p><p>Ipsum</p>2)隐式标签 (补充没有显示的标签)
   它可以自动将 <td>Table data</td> 包装成 <table><tr><td>...3)创建可靠的文档结构  
   html标签包含head 和 body,且在head只出现恰当的元素
   
文档的对象模型                
1)文档由多个Elements和TextNodes组成
2)继承结构如下:Document继承Element继承Node. TextNode继承 Node.3)一个Element包含一系列的子节点,并拥有一个父Element。他们还唯一提供了一个子元素过滤列表。

十一、国际化

根据不同的用户群,显示不同的语言,此功能叫做国际化,如丁香医生页面切换语种的功能。

要想实现动态显示不同语言,首先要将页面中的数据转为动态显示,放在配置文件中读取

创建list.properties

list.title=步尔斯特
list.h2=国内疫情情况如下
list.table.name1=地区
list.table.name2=现有确诊
list.table.name3=累计确诊
list.table.name4=治愈
list.table.name5=死亡

我们将list.properties放在i18n的文件夹下,然后在application.properties中设置好指定配置

spring.messages.basename=i18n.list

这样就可以再创建一个list_en_US.properties文件,内容如下

list.title=burst
list.h2=AsFollows
list.table.name1=Area
list.table.name2=NowConfirm
list.table.name3=Confirm
list.table.name4=Heal
list.table.name5=Dead

对应的list.html页面修改如下

<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head><meta charset="UTF-8"><title th:text="#{list.title}">Title</title><link rel="stylesheet" th:href="@{css/bootstrap.css}"></head><body><h2 th:text="#{list.h2}"> 国内疫情情况如下 </h2><br><table class="table table-hover"><thead><tr><th><p th:text="#{list.table.name1}">地区</p></th><th><p th:text="#{list.table.name2}">现有</p></th><th><p th:text="#{list.table.name3}">累计</p></th><th><p th:text="#{list.table.name4}">治愈</p></th><th><p th:text="#{list.table.name5}">死亡</p></th></tr></thead><tbody><tr th:each="bean:${beanList}"><td th:text="${bean.area}">name</td><td th:text="${bean.nowConfirm}">nowConfirm</td><td th:text="${bean.confirm}">confirm</td><td th:text="${bean.heal}">heal</td><td th:text="${bean.dead}">dead</td></tr></tbody></table></body></html>

当我们运行项目后,通过浏览器更改语言,就可以切换成不同语种的显示了。

以chrome为例,将中文置顶,则显示中文,将英语(美国)置顶,则显示英文,这里要注意,如果没有英语(美国)这个语言,可以在底下的添加语言中,先搜索后添加。

那是如何实现切换的呢,当我们调出控制台,比对两笔请求的http,可以发现请求头中Accept-Language参数是不同的,请求中文时以zh-CN开头,请求英文时以en-US开头,如果你对其他语种感兴趣,也可以看一下其他语种的简称。

但是通过浏览器切换仍有些麻烦,所以我们尝试在页面中添加切换按钮

当我们设置了参数lan用来区分不同语种时,需要使用一个处理器来接收语种参数

然后将处理器注入到spring容器中

同时增加一个list_zh_CN.properties文件,声明各字段的中文值
再次启动项目,就可在页面中进行中英文切换了。

十二、Redis的使用

三大客户端框架: Jedis Redission Lettuce

其中spring-data-redis 是将redis整合在spring中使用的jar包

在springboot2.0以后的版本 将原本的jedis升级成Lettuce 而本质上是一种连接池的封装

redis数据的存储,可以在项目启动时存储,另外可以在请求第一次被调用时存储(懒加载)
请求 -》 redis(存在 -》 直接返回,不存在 -》 mysql中查询 -》 存储到redis中 -》 返回)
-》 mysql(存在 ,不存在 -》 发送http请求 -》 数据清洗和计算后存储 -》 返回)

以折线图数据为例
在处理数据的过程中,有三种数据形态

1)url直接返回的原始数据
2)第一次解析后处理的 List<GraphBean>
3) echarts的直观需要的三个list, 一个对应x轴,两个对应y轴

第一种数据重要,但一般不放在redis中存储,甚至不放在mysql中。


本文转载自: https://blog.csdn.net/CSDN_SAVIOR/article/details/123948991
版权归原作者 步尔斯特 所有, 如有侵权,请联系我们删除。

“基于springboot+redis+国际化+定时任务的疫情项目【已上线】”的评论:

还没有评论