levycui 发表于 2022-4-20 19:31:18

基于springboot+redis+国际化+定时任务的疫情项目

问题导读:
1、如何理解搜索引擎原理?
2、如何进行数据分析流程设计?
3、Mybatis如何进行整合?
4、图形化数据如何处理?

文章目录
    一、效果图
    二、技术栈
    三、项目背景
    四、搜索引擎原理
    五、数据分析
      5.1 分析数据源
      5.2 获取疫情数据
    六、数据处理
      6.1 初识json
      6.2 数据展示
    七、网络请求
    八、数据存储
      8.1 Mybatis整合
      8.2 数据初始化及定时更新
    九、图形化数据处理
      9.1 折线图
      9.2 地图
      9.3 拓展部分
    十、页面数据处理
    十一、国际化
    十二、Redis的使用

一、效果图





二、技术栈

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

三、项目背景

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

四、搜索引擎原理

链接人和内容

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

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

分类:
通用型爬虫 和 垂直型爬虫

疫情数据系统:
数据采集 -》 数据存储 -》 数据计算 -》 数据展示

五、数据分析


5.1 分析数据源

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

5.2 获取疫情数据

[*]    国内各省份表格数据-对应请求
[*]    对应的数据格式:json
[*]    国外表格数据-对应请求
[*]    国内趋势数据

六、数据处理


6.1 初识json


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




<div>
</div><div>Gson -> From Google

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

6.2 数据展示

controller - service - handler
数据加载到model中 返回给html渲染

@Controller
public class DataController {

    @Autowired
    private DataService dataService;

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

public interface DataService {
   
    List<DataBean> list();
}


@Service
public class DataServiceImpl implements DataService {

    @Override
    public List<DataBean> list() {
      return MyDataHandler.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


[*]    “应用层协议”
[*]    了解HTTP不同版本的演进
[*]    了解GET和POST请求的区别

    // 需要的参数url
    // 创建一个远程的连接对象设置方法类型 GET
    // 设置相关参数    发送请求
    // 通过io接收数据后返回

    public static String doGet(String urlStr) {
      HttpURLConnection conn = null;
      InputStream is = null;
      BufferedReader br = null;
      StringBuilder result = new StringBuilder();

      try {
            // 创建远程url连接对象
            URL url = new URL(urlStr);
            // 打开一个连接
            conn = (HttpURLConnection) url.openConnection();
            // 设置为GET请求
            conn.setRequestMethod("GET");
            // 设置重要的参数连接超时时间 和 读取超时时间
            // 超时时间更多被距离影响   读取时间更多被数据量影响
            conn.setConnectTimeout(15000);
            conn.setReadTimeout(60000);

            // header参数设置可以不设置
            conn.setRequestProperty("Accept", "application/json");

            // 发送请求
            conn.connect();

            // 状态码200302404500 ?
            // 如果比较时可能出现空指针把确定的值放在前面可以避免
            if (200 == conn.getResponseCode()) {
                is = conn.getInputStream();
                br = new BufferedReader(new InputStreamReader(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=308 DEFAULT CHARSET=utf8;



d) 编写类及文件夹

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

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

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

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

@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("epidemic")
public class DataBean implements Serializable {

    // Alt + Insert 调用   安装插件
    private static final long serialVersionUID
            = 4938260405189292371L;

    // Alt + 7 查看类的方法

    // 地区累计确诊人数现有确诊人数   死亡人数治愈人数
    private long id;
    private String area;
//    @TableField("all_confirm")
    // 如果属性名和表中字段名不一致可以通过 TableField注解指定
    private int confirm;
    private int nowConfirm;
    private int dead;
    private int heal;
}

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

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

@SpringBootApplication
@MapperScan("com.duing.mapper")
public class DataHandlerApplication {

    public static void main(String[] args) {
      SpringApplication.run(DataHandlerApplication.class, args);
    }

}


public interface DataMapper extends BaseMapper<DataBean> {
}

创建service接口及其实现类

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

public interface DataService extends IService<DataBean> {

}


@Service
public class DataServiceImpl
      extends ServiceImpl<DataMapper,DataBean>
      implements DataService {

}

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

@Controller
public class DataController {

    @Autowired
    private DataService dataService;

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

8.2 数据初始化及定时更新

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

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

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

//先将DataHandler托管到spring容器中使用@Component
//以便于获取到 dataService对象
@Component
public class DataHandler {

    @Autowired
    private DataService dataService;

    // 数据初始化
    //   在服务器加载Servlet时运行且 只运行一次
    @PostConstruct
    public void saveData() {
      List<DataBean> dataBeans = getData();
      // mybatis-plus提供了可用的方法
      // 删除全部数据批量新增数据
      dataService.remove(null);
      dataService.saveBatch(dataBeans);
    }
...
}
定时更新其实也是定时任务

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

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

使用方式如下:

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

@SpringBootApplication
@MapperScan("com.duing.mapper")
@EnableScheduling
public class DataHandlerApplication {

    public static void main(String[] args) {
      SpringApplication.run(DataHandlerApplication.class, args);
    }

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

    // 定时更新
    @Scheduled(cron = "0 0 0/1 * * ? ")
    public void updateData(){
      System.out.println("要更新数据啦");
      System.out.println("当前时间:" + dateFormat.format(new Date()));

      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)
public void updateData1(){}

// 固定间隔任务   上一次执行结束后   再隔10s进行下一次执行
// 如设置间隔10s而方法需执行20s   那么会从上一次执行完成后开始计算10s后开始下一次执行
// 唤起方法真正的时间间隔为30s
@Scheduled(fixedDelay = 10000)
public void updateData2(){}
九、图形化数据处理

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=As Follows
list.table.name1=Area
list.table.name2=Now Confirm
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


最新经典文章,欢迎关注公众号http://www.aboutyun.com/data/attachment/forum/201903/18/215536lzpn7n3u7m7u90vm.jpg

freeforme 发表于 2022-4-21 10:04:42

很细很好,
页: [1]
查看完整版本: 基于springboot+redis+国际化+定时任务的疫情项目