返回顶部

结对第二次作业——某次疫情统计可视化的实现

这个作业属于哪个课程 2020春丨w班
这个作业要求在哪里 作业要求
结对学号 221701328 221701312
这个作业的目标 实现原型设计中的部分功能
作业正文 作业正文
其他参考文献

Github仓库地址和代码规范链接

Github仓库链接
代码规范链接

展示你的成品

成品链接

  • 头部有3个按钮和一个日期选择按钮 “实时疫情”,“全国数据”和“辟谣和防护”点击后会跳转到相应的位置

截图1

  • 选择日期后数据会截止到选择的日期

截图2

  • 疫情地图可以查看现有确诊和累计确诊两个情况下的地图

截图3

  • 鼠标移到某一省份上后会高亮显示该省份 并显示相应数据

截图4

  • 点击省份后会跳转到该省份的具体页面

截图5

  • 可以选择查看新增或是累计数据的折线图

截图6

  • 返回后 在疫情地图下方是全国数据的折线图

截图7

  • 可以查看三种情况下的全国数据折线图

截图8

  • 下方是全国数据的表格

截图9

  • 最后是辟谣与防护界面和版权信息

截图10

结对讨论过程描述

  • 最开始的时候是我们两个创建了一个组织,然后创建了一个仓库InfectStatisticWeb
    组织

  • 我们是用JavaEE开发的这个项目 所以仓库里的项目结构 如src文件夹下的servlet和web文件夹都是按JavaEE初始化好的。然后就开始讨论分工,得出的结果是我负责前端和爬虫工具,春翔负责后端和爬虫工具类的使用。爬虫工具我百度之后,就选择了Jsoup来进行爬虫,爬到网页的源代码后,再进行拆分,就得到了自己想要的数据。

  • 遇到的问题就是每个省份的数据都存储在一个json文件中,因此又百度了一下Java读取web文件的方法,然后在json文件中提取想要的数据即可。下面是我写爬虫是遇到的问题,经过讨论之后知道了数据要写成log文件的格式,就像第一次作业那样,因此就有了思路。
    结对截图1
    结对截图2

描述设计实现过程

  • 后端

后端使用JavaEE来搭建,由于我们JavaEE刚开始学习不久,只会使用Servlet没有使用Spring等后端框架。后端分为三层:servlet层用来接收请求,创建数据类,然后相应请求;data层放置数据类,把日志数据封装成类,并提供计算方法,以及将数据转化成json格式的方法,以供servlet层调用;dao层是数据接口层,使用日志文件来获取疫情数据,本层封装了访问文件和读取文件的方法,以供data层调用。

  • 前端

项目实现了前后端分离,前端使用Jquery来进行AJAX请求和实现一些小动画,由echarts来绘制地图和统计图,使用bootstrap来美化界面。

  • 功能结构图

功能结构图

关键代码

  • 这是爬虫获取丁香园疫情地图中省份数据及其对应的json文件的代码。大致思路就是爬虫获取html网页源代码,将其转换为字符串,然后在字符串中提取到想要的信息(如json文件的url地址及其对应的省份名称),将信息放入ArrayList中存储。
public static String USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:49.0) Gecko/20100101 Firefox/49.0";//模仿火狐浏览器
public static String HOST = "i.snssdk.com";
public static String REFERER = "https://i.snssdk.com/feoffline/hot_list/template/hot_list/forum_tab.html?activeWidget=1";
ArrayList<String> url = new ArrayList<>();
ArrayList<String> provinceName = new ArrayList<String>();
public void getUrlAndProvince(){
    String link = "https://ncov.dxy.cn/ncovh5/view/pneumonia";//丁香园疫情地图网址
    String resultBody = null,tempResult;
    Document doc = null;
    int position1,position2,i = 0;
    try{
        doc = Jsoup.connect(link).userAgent(USER_AGENT).header("Host",HOST).header("Referer",REFERER).get();//爬取网页html
    }catch (Exception e){
        e.printStackTrace();
    }
    resultBody = doc.getElementById("getAreaStat").toString();
    Matcher matcher = Patterns.WEB_URL.matcher(resultBody);//匹配其中的url
    StringBuffer buffer = new StringBuffer();
    matcher.find();
    while(matcher.find()){
        buffer.append(matcher.group());
        buffer.append("\n");
        i++;
    }
    String[] urls = buffer.toString().split("\n");
    for(i = 0;i < urls.length;i++){
        url.add(urls[i]);
    }

    resultBody = resultBody.replaceAll("\"","").replaceAll(",","");//获取省份信息
    for(i = 0;i < urls.length;i++) {
        position1 = resultBody.indexOf("provinceShortName");
        resultBody = resultBody.substring(position1);
        position2 = resultBody.indexOf("currentConfirmedCount");
        position1 = 0;
        tempResult = resultBody.substring(position1, position2).substring(18);
        resultBody = resultBody.substring(position2+21);
        provinceName.add(tempResult);
    }
}
  • 根据json文件的url地址,省份名,还有log文件的路径名,生成连续的log文件,log文件的名称格式为yyyy-mm-dd.log.txt
public void getFiles(String path,String provinceName,String logPath) {
    ArrayList<String> date = new ArrayList<>();
    ArrayList<Integer> confirmedIncr = new ArrayList<>();
    ArrayList<Integer> curedIncr = new ArrayList<>();
    ArrayList<Integer> deadIncr = new ArrayList<>();
    int HttpResult, position1, position2, position3, position4;
    String result = null, temp1, temp2, temp3, temp4, line;
    try {
        URL url = new URL(path);
        URLConnection urlconn = url.openConnection();
        urlconn.connect();
        HttpURLConnection httpconn = (HttpURLConnection) urlconn;
        HttpResult = httpconn.getResponseCode();
        if (HttpResult != HttpURLConnection.HTTP_OK) {
            System.out.println("无法连接");
        } else {
            InputStreamReader isReader = new InputStreamReader(urlconn.getInputStream(), "UTF-8");
            BufferedReader reader = new BufferedReader(isReader);
            StringBuffer buffer = new StringBuffer();
            line = reader.readLine(); // 读取第一行
            while (line != null) { // 如果 line 为空说明读完了
                buffer.append(line); // 将读到的内容添加到 buffer 中
                buffer.append(" "); // 添加换行符
                line = reader.readLine(); // 读取下一行
            }
            result = buffer.toString();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    position1 = result.indexOf("[");
    position2 = result.indexOf("]");
    result = result.substring(position1 + 1, position2);
    String[] strs = result.split("}");
    strs[0] = "  " + strs[0];
    for (String str : strs) {
        str = str.substring(2);
        str = str.replaceAll(",", "  ").replaceAll("\"", "")
                .replaceAll("\\{", "");
        position3 = str.indexOf("dateId");
        position4 = str.indexOf("deadCount");
        temp1 = str.substring(position3, position4).substring(7);
        temp1 = temp1.substring(0, 4) + "-" + temp1.substring(4, 6) + "-" + temp1.substring(6, 8);
        position3 = str.indexOf("confirmedIncr");
        position4 = str.indexOf("curedCount");
        temp2 = str.substring(position3, position4).substring(14).trim();
        position3 = str.indexOf("deadIncr");
        temp3 = str.substring(position3).substring(9).trim();
        position3 = str.indexOf("curedIncr");
        position4 = str.indexOf("currentConfirmedCount");
        temp4 = str.substring(position3, position4).substring(10).trim();
        date.add(temp1);//temp1为日期 格式为yyyy-mm-dd
        confirmedIncr.add(Integer.valueOf(temp2));//temp2为确诊人数
        deadIncr.add(Integer.valueOf(temp3));//temp3为死亡人数
        curedIncr.add(Integer.valueOf(temp4));//temp4为治愈人数
    }
    for (int j = 0; j < date.size(); j++) {
        String filePath = logPath + date.get(j) + ".log.txt", content;
        File newFile = new File(filePath);
        try {
            if (!newFile.exists()) {
                newFile.createNewFile();
            }
            FileWriter fw = new FileWriter(filePath, true);
            BufferedWriter bw = new BufferedWriter(fw);
            if (confirmedIncr.get(j) != 0) {
                content = String.format("%s 新增 感染患者 %d人\n", provinceName, confirmedIncr.get(j));
                bw.write(content);//按照xx新增 感染患者 xx人的格式写入log文件中
            }
            if (deadIncr.get(j) != 0) {
                content = String.format("%s 死亡 %d人\n", provinceName, deadIncr.get(j));
                bw.write(content);//按照xx 死亡 xx人的格式写入log文件中
            }
            if (curedIncr.get(j) != 0) {
                content = String.format("%s 治愈 %d人\n", provinceName, curedIncr.get(j));
                bw.write(content);//按照xx 治愈 xx人的格式写入log文件中
            }
            bw.close();
            fw.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • eharts载入dom,并使用ajax以post方式请求服务器数据,返回数据类型为json
let existMap = echarts.init(document.getElementById("existMap"));
let cumulativeMap = echarts.init(document.getElementById("cumulativeMap"));
let incInfChart = echarts.init(document.getElementById("incInfChart"));
let exiCumInfChart = echarts.init(document.getElementById("exiCumInfChart"));
let deadCureChart = echarts.init(document.getElementById("deadCureChart"));

loadNationalData();
function loadNationalData(date) {
    existMap.showLoading();
    cumulativeMap.showLoading();
    incInfChart.showLoading();
    exiCumInfChart.showLoading();
    deadCureChart.showLoading();
    $.ajax({
        type:"POST",
        url:"NationalData",
        data:{
            date:date
        },
        dataType:"json",
        success:function (data) {
        ......
        existMap.setOption({
  • 根据数据生成日期选择下拉框,选择日期后重新调用ajax方法
$("#dropdown").empty();
for(var i =0;i < data[0].length;i++) {
    $("#dropdown").append("<li><button class='dateList btn'>"+data[0][i].date+"<tton><>");
}
$(".dateList").click(function (e) {
    loadNationalData($(this).html());
});
  • 点击地图区域跳转网页,并传递点击地区名给目标页面
existMap.on("click",function (param) {
    location.href = "province.html?province=" + encodeURI(param.name);
});
cumulativeMap.on("click",function (param) {
    location.href = "province.html?province=" + encodeURI(param.name);
})
  • 处理日志数据
public void initData(LogDao dao) {
    List<File> logList = dao.getLogList();
    for(File log : logList) {
        String date = log.getName().substring(0,log.getName().indexOf("."));
        DatePartData datePartData = new DatePartData();
        List<String> records = dao.getRecords(log);
        List<Integer> cumList;
        int num;
        for(String record : records) {
            String[] data = record.split(" ");
            if(data.length < 2) {
                continue;
            }
            switch (data[1]) {
                case "新增":
                    if ("感染患者".equals(data[2])) {
                        num = MyUtil.parseData(data[3]);
                        datePartData.add(data[0],0, num);
                        cumList = cumData.get(data[0]);
                        cumList.set(0,cumList.get(0) + num);
                    } else if ("疑似患者".equals(data[2])) {
                        num = MyUtil.parseData(data[3]);
                        datePartData.add(data[0],1, MyUtil.parseData(data[3]));
                        cumList = cumData.get(data[0]);
                        cumList.set(1,cumList.get(1) + num);
                    }
                    break;
                case "死亡":
                    num = MyUtil.parseData(data[2]);
                    datePartData.add(data[0],2, num);
                    cumList = cumData.get(data[0]);
                    cumList.set(2,cumList.get(2) + num);
                    break;
                case "治愈":
                    num = MyUtil.parseData(data[2]);
                    datePartData.add(data[0],3, num);
                    cumList = cumData.get(data[0]);
                    cumList.set(3,cumList.get(3) + num);
                    break;
            }
        }
        datePartData.setInitData(cumData);
        datePartData.compute();
        statistics.put(date,datePartData);
    }
}
  • 计算累计数据和现存数据
public void compute() {
    for(String province : provincesDataExi.keySet()) {
        List<Integer> list = provincesDataExi.get(province);
        provincesCumInfect.put(province,list.get(0));
        nationalCumInfect += list.get(0);
        list.set(0,list.get(0) - list.get(2) - list.get(3));  //现存感染等于累计感染减去死亡和治愈
        for(int i = 0;i < 4;i++) {
            nationalDataInc.set(i,nationalDataInc.get(i) + provincesDataInc.get(province).get(i));
            nationalDataExi.set(i,nationalDataExi.get(i) + provincesDataExi.get(province).get(i));
        }
    }
}
  • Servlet的dopost方法,接收请求并初始化数据对象。使用session保证在一次会话中不多次计算占用系统资源。返回json数据给前端。
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    request.setCharacterEncoding("utf-8");
    response.setContentType("application/json;charset=utf-8");
    response.setCharacterEncoding("utf-8");
    PrintWriter writer = response.getWriter();
    TotalData totalData = (TotalData) request.getSession().getAttribute("totalData");
    String date = request.getParameter("date");
    if (totalData == null) {
        String path = getServletConfig().getServletContext().getRealPath("/") + "log";
        LogDao dao = new LogDao(path);
        totalData = new TotalData();
        totalData.initData(dao);
        request.getSession().setAttribute("totalData",totalData);
    }
    if (date == null) {
        writer.write(totalData.getAllNationalJson(MyUtil.getNewestDate()));
    } else {
        writer.write(totalData.getAllNationalJson(date));
    }
    writer.flush();
    writer.close();
}
  • 把数据转化成json
private String getTotalDataJson(String date) {
    DatePartData data = statistics.get(date);
    List<Integer> increment = data.getNationalDataInc();
    List<Integer> exist = data.getNationalDataExi();
    StringBuilder jsonStr = new StringBuilder();
    jsonStr.append("[");
    jsonStr.append("{");
    jsonStr.append("\"type\":").append("\"现存确诊\"").append(",");
    jsonStr.append("\"num\":").append(exist.get(0)).append(",");
    jsonStr.append("\"compare\":").append(increment.get(0) - increment.get(2) - increment.get(3));
    jsonStr.append("}").append(",");
    jsonStr.append("{");
    jsonStr.append("\"type\":").append("\"累计确诊\"").append(",");
    jsonStr.append("\"num\":").append(data.getNationalCumInfect()).append(",");
    jsonStr.append("\"compare\":").append(increment.get(0));
    jsonStr.append("}").append(",");
    jsonStr.append("{");
    jsonStr.append("\"type\":").append("\"现存疑似\"").append(",");
    jsonStr.append("\"num\":").append(exist.get(1)).append(",");
    jsonStr.append("\"compare\":").append(increment.get(1));
    jsonStr.append("}").append(",");
    jsonStr.append("{");
    jsonStr.append("\"type\":").append("\"现存死亡\"").append(",");
    jsonStr.append("\"num\":").append(exist.get(2)).append(",");
    jsonStr.append("\"compare\":").append(increment.get(2));
    jsonStr.append("}").append(",");
    jsonStr.append("{");
    jsonStr.append("\"type\":").append("\"现存治愈\"").append(",");
    jsonStr.append("\"num\":").append(exist.get(3)).append(",");
    jsonStr.append("\"compare\":").append(increment.get(3));
    jsonStr.append("}");
    jsonStr.append("]");
    return jsonStr.toString();
}

心路历程与收获

221701328张春翔:

我有一定的web开发经验,对服务端和AJAX都有一定的了解,不需要去学习太多的内容,项目开发起来也没有遇到什么困难,比起上次结对作业要轻松不少。但相对来说这次的工作量偏大,又是两人结对,相互之间配合总是不能达到十分默契,在交流的过程中也花费了不少时间。总的来说这次结对作业也收获了很多,学习了新的GitHub使用技巧,获得了合作编程的经验等等。最后感谢队友的配合,期待下次合作。

221701312张庭博:

我的web开发经验不是很多,只能勉强写写HTML这样子,对Bootstrap之类的也不是很懂,因此这次作业对我来说难度是挺大的。刚好春翔是web经验挺多的人,我从他那儿学到了很多东西,像是AJAX,echars等等对我来说不太了解的技术,经过学习之后,也能进行一些前端的开发了。对于爬虫的话,我以前就接触过一些,因此这次的爬虫就是我负责来写,和他讨论好爬虫怎么用才方便后,我进行了爬虫工具的开发,使用工具后能生成log文件,正好与第一次作业相呼应,不用去再去写代码了,压力减小了许多。经过不断地沟通交流,我们的开发过程也是异常顺利,项目如期地完成,项目完成的时刻,我的内心充满了满足感,这一次结对作业可以说是收获颇丰。

评价结对队友

221701328张春翔:

逻辑思维很好,编程能力很强,懂得使用爬虫获取数据,从他身上我学到了很多。

221701312张庭博

前端大佬,可以说是相当可靠的队,实力强,善于沟通,跟他合作我学到了很多知识。

posted @ 2020-03-13 22:48  Rachal  阅读(411)  评论(1编辑  收藏  举报