生命中曾经拥有过的所有灿烂,原来终究,都需要用寂寞来偿还。

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

这个作业属于哪个课程 https://edu.cnblogs.com/campus/fzu/2020SpringW
这个作业要求在哪里 https://edu.cnblogs.com/campus/fzu/2020SpringW/homework/10456
这个作业的目标 采用web技术,结合寒假第二次作业的成果实现原型设计中的部分功能
结对学号 221701233、221701234
作业正文 https://www.cnblogs.com/sillyby/p/12492953.html
其他参考文献

Github仓库地址https://github.com/WallofWonder/InfectStatisticWeb

代码规范链接https://github.com/WallofWonder/InfectStatisticWeb/blob/master/codestyle.md

演示地址:阿里云 (初次加载比较慢,请耐心等待)

成果展示

全国疫情概览:

  • 展示现有确诊、现有疑似、现有重症、累计确诊、累计治愈、累计死亡等数据
  • 提供现有确诊、累计确诊的全国地图可视化展示

image-20200314115312816

切换地图数据

地图切换

省份详情:

image-20200314101130308

省份高亮

单击地图上的省份进入省详情页面:

省份详情

折线图的数据切换:

折线图

向下滚动可查看该省各市的疫情数据:

image-20200314120222248

未完成的扩展功能页面:

包括疫情新闻、同程查询、谣言鉴别

404-1584158871195

结对过程

作业发布后,我们首先讨论的是采用什么技术和框架来实现,我们决定采用springboot+vue的前后端分离技术来实现作业的要求,原因是我们其中一人在寒假学习过springboot,另一人学习过vue,而且都没有用学到的知识真正搭建过一个像样的东西,正好能拿这次作业练练手。

确定好所采用的技术之后,第一个问题是如何获取数据,为了方便拿到数据我们采用了天行数据的接口

我们根据所采用的技术指定了代码规范,接下来就是进行分工:

  • 221701233:后端、后端部分文档博客撰写、博客整合和发布
  • 221701234:前端、前端部分文档博客撰写、云服务器部署

之后,我们开始进行协作的磨合,由于负责前端的同学git还不是很熟练,两人之前也从未接触过正式的git协作,我们花了两天的时间进行git的知识储备和协作测试,大致摸清了协作流程之后,开始上手开发。

由于是前后端分离开发,我们可以同时进行项目的推进,并在开发过程中交流保持进度同步,互相测试代码的功能,并且共同解决一些难以解决的问题。

实现了基础功能后,进行紧张的代码复审和部署测试操作,在这个过程中遇到了许许多多的小毛病,但最后还是逐个解决了,并合作完成博客撰写。

结对过程截图

image-20200314112906722

image-20200314112816282

image-20200314112737775

遇到的主要困难以及克服过程

前后端跨域问题

image-20200314153817124

跨域

分析:经过查找资料,这应该是前后端分离所导致的问题,由于前后端分别运行在不同端口,之间的数据交互属于跨域,而因为浏览器收到同源策略(不同的域名, 不同端口, 不同的协议不允许共享资源的,保障浏览器安全。)的限制,当前域名的js只能读取同域名下的窗口属性。

解决方案:前端请求接口时加上http://头即可。

刻苦铭心的经历——代码丢失

起因:ddl的前一天,前端同学undo了一次commit,再次commit的时候github只提交了新增文件,而没有提交已有代码的修改,此时前端同学又从GitHub拉取了一次代码,导致对已有代码的修改丢失,一个下午的努力似乎付诸东流。

解决:大家都很紧张,乱了阵脚,熬夜寻找解决方案,最后的解决方法却十分简单——通过IDE的历史记录回滚恢复修改的代码,并再次commit,真的是虚惊一场。

针对该次事件的反思:可能表达语言水平有限,没法体现出我们当时心态有多崩,不过这个问题也反映出了我们对git的不熟练,导致这一星期许多时间都用在解决git多人协作的问题上了,严重拖慢了进度,只勉强完成了基础功能,为了后续的学习和团队实践我们两人都需要抓紧熟练git多人协作的使用了。

云服务器部署问题

前端同学在搭建过程中的探索已经总结并写成了一篇博客,在先贴上博客链接吧

https://www.cnblogs.com/QEEZ/p/12501197.html

主要的问题概括如下:

  • 后端链接数据库被拒绝访问,解决方法:linux数据库配置问题,安装时没有配好
  • 前端请求资源失败,解决方法:修改前端请求中的localhost为服务器的公网ip。

设计实现

我们决定采用springboot+vue的前后端分离技术来实现作业的要求。

确定好所采用的技术之后,第一个问题是如何获取数据,为了方便拿到数据我们采用了天行数据的接口,同时为了减轻第三方接口压力进行数据的持久化,前端发送至后端的请求是面向数据库的,而后端负责从第三方接口定时获取数据维护数据库,同时因为第三方接口返回的json数据繁杂,不符合前端需要的数据格式,后端需要对其进行过滤和整理。

image-20200314112003226

在前端的界面设计上大致遵循原型设计的风格,决定采用ElementUI组件库美化界面,同时嵌入Echarts完成对图表的绘制。

关键代码——后端

请求第三方接口的数据

初始化数据库时,为了获取过去20天的数据,需要以较高的频率调用第三方接口获取数据,用同一个apikey高频调用会出现不稳定情况,于是使用三个apikey分担压力。

/**
 * @param httpUrl 请求接口
 * @param httpArg 参数
 * @param tag     apiKey选择标识
 * @return 返回结果
 */
public static String request(String httpUrl, String httpArg, int tag) {
    BufferedReader reader = null;
    String result = null;
    StringBuffer sbf = new StringBuffer();
    String apiKey = "";

    if (tag == 0) {
        apiKey = API_KEY0;
    }
    else if (tag == 1) {
        apiKey = API_KEY1;
    }
    else if (tag == 2) {
        apiKey = API_KEY2;
    }
    httpUrl = httpUrl + "?key="
            + apiKey
            + ((httpArg == null) ? "" : httpArg);

    try {
        URL url = new URL(httpUrl);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("GET");
        InputStream is = connection.getInputStream();
        reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
        String strRead = null;
        while ((strRead = reader.readLine()) != null) {
            sbf.append(strRead);
            sbf.append("\r\n");
        }
        reader.close();
        result = sbf.toString();

    } catch (IOException e) {
        e.printStackTrace();
    }
    return result;
}

配置定时任务

为保证前端获取的数据的可靠性,设置每隔10分钟更新一次数据库。和启动时的初始化不同,定时任务只会更新当日数据,不会高频调用接口。

@Component
@Slf4j
public class ScheduledJob {

    @Resource(name = "provinceServiceImpl")
    ProvinceService provinceService;

    @Resource(name = "cityServiceImpl")
    CityService cityService;

    @Resource(name = "nationServiceImpl")
    NationService nationService;

    private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    /**
     * 每10分钟更新一次数据库
     */
    @Scheduled(cron = "0 */10 * * * ? ")
    public void cronJob() {
        log.info("=========================== >> 更新数据库...");
        updateNationData();
        updateProvinceDataToday();
        upadateCityData();
        log.info("=========================== >> 数据库更新完成。");
    }

    /**
     * 应用启动时更新数据库
     */
    @PostConstruct
    public void updateAtStart() {
        log.info("=========================== >> 更新数据库...");
        updateNationData();
        updateProvinceData();
        upadateCityData();
        log.info("=========================== >> 数据库更新完成。");
    }
    
    ...
}

image-20200314164845549

定时任务的截图👆

控制层暴露接口

主要分为全国数据、省数据和市数据的获取接口

@Controller
@RequestMapping("/cities")
@Slf4j
public class CityController {

    @Resource(name = "cityServiceImpl")
    CityService cityService;

    /**
     * 获取某省的所有城市疫情
     *
     * @param province URL编码后的省名称
     * @return
     * @throws UnsupportedEncodingException
     */
    @GetMapping("list/{province}")
    @ResponseBody
    public List<CityVO> getCities(@PathVariable String province) throws UnsupportedEncodingException {
        String decodedName = URLDecoder.decode(URLDecoder.decode(province, "UTF-8"), "UTF-8");
        log.info("收到请求:/citieslist/" + decodedName);
        return cityService.selectCities(decodedName);
    }
}
@Controller
@RequestMapping("/nations")
@Slf4j
public class NationController {

    @Resource(name = "nationServiceImpl")
    NationService nationService;

    /**
     * 获取全国疫情数据
     *
     * @return
     */
    @GetMapping("/all")
    @ResponseBody
    NationVO getNation() {
        log.info("收到请求:/nations/all");
        return nationService.select();
    }
}
@Controller
@RequestMapping("/statistics")
@Slf4j
public class ProvinceController {

    @Resource(name = "provinceServiceImpl")
    ProvinceService provinceService;

    /**
     * 获取全国各省疫情数据
     *
     * @return
     */
    @GetMapping("/provinces/{type}")
    @ResponseBody
    ProvinceMapVO getNationalProvinces(@PathVariable String type) {
        log.info("收到请求:/statistics/provinces/" + type);
        return provinceService.getNationalProvince(LocalDate.now(), type);
    }

    /**
     * 获取某个省的数据
     *
     * @param province URL编码后的省名称
     * @return
     * @throws UnsupportedEncodingException
     */
    @GetMapping("/provinces/one/{province}")
    @ResponseBody
    ProvinceVO getProvince(@PathVariable String province) throws UnsupportedEncodingException {
        String decodedName = URLDecoder.decode(URLDecoder.decode(province, "UTF-8"), "UTF-8");
        log.info("收到请求:/statistics/one/" + decodedName);
        return provinceService.selectByNameAndDate(decodedName, LocalDate.now());
    }

    /**
     * 获取某省疫情趋势
     *
     * @param province URL编码后的省名称
     * @param type     数据类型
     * @return
     * @throws UnsupportedEncodingException
     */
    @GetMapping("/provinces/one/tends/{province}/{type}")
    @ResponseBody
    ProvinceTendencyVO getTendency(@PathVariable String province, @PathVariable String type)
            throws UnsupportedEncodingException {
        String decodedName = URLDecoder.decode(URLDecoder.decode(province, "UTF-8"), "UTF-8");
        log.info("收到请求:/statistics/one/tends/" + decodedName + "/" + type);
        return ProvinceMapper.mapToTendency(provinceService.selectByName(decodedName), type);
    }
}

关键代码——前端

获取数据渲染地图

//提前设置关于地图的相关配置
      drawLine () {
        let that = this
        // 基于准备好的dom,初始化echarts实例
        that.myChart = this.$echarts.init(document.getElementById('myChart'))
        that.myChart.showLoading()
        that.myChart.on('click', that.getProvinces)
        // that.myChart.off('click')
        // 设置相关参数
        that.option = {
          // backgroundColor:'aliceblue',
          dataRange: {
            x: '5%',
            y: '60%',
            splitList: [
              {start: 10000},
              {start: 1000, end: 9999},
              {start: 100, end: 999},
              {start: 10, end: 99},
              {start: 1, end: 9},
              {start: 0, end: 0},
            ],
            color: ['#660208', '#8c0d0d', '#cc2929', '#ff7c80', '#ffaa85', '#d9d9d9']
          },
          tooltip:{
            trigger:'item',
            formatter: '地区:{b}<br/>确诊:{c} 个'
          },

          series: [{
            x:'center',
            y:'top',
            type: 'map',
            map: 'china',
            // roam: true,
            label: {
              normal: {
                position: 'inside',
                show: true,
                fontsize: '10',
                color: 'rgba(0,0,0,0.7)'
              }
            },
            data: that.mydata
          }]
        }
      }
 //获取地图需要的数据,并且渲染地图
      getData (url) {
        let that = this

        this.axios.get(url)
          .then(function (response) {
            let datas = response.data.provinces
            for(var i=0;i<datas.length;++i){
              that.mydata.push({
                name: datas[i].name,
                value: datas[i].value
              })
            }
            //重要步骤,再次初始化option以刷新数据
            that.drawLine()
            that.myChart.setOption(that.option)
            that.myChart.hideLoading()
          }, function (err) {
            console.log(err)
          }).finally(function () {
            //清空原有数据
          that.mydata=[]
        })
      }

心路历程与收获

221701233(后端、后端部分文档博客撰写、博客整合和发布)

这一次结对编程是对上次结对原型的实现,上次结对作业中我描述了遇到的协作问题,之后经过老师的引导,我进行了反思,并在这次作业中进行了改进,在与队友交流的方面明显比上次高效很多,许多问题能得到清楚的描述和很快的响应。

不过在这次结对编程中还是暴露出了新的问题,比如磨合阶段的磕磕绊绊,花太多时间在细致末节的小问题上,面对严重的事故会乱了阵脚,导致只勉强实现了基本需求,归根结底还是对采用的技术不够熟悉,对规范的项目开发缺乏经验,需要长时间的实践加以学习进步。

对下面这个人的评价:

其实作业刚开始起步的时候,我对队友能否及时进入状态表示担心,要知道生活中他是一个有拖延症的宅233。但随着进度的推进,我发现队友能够很好地进入状态,能积极地为了实现需求而学习知识,在实践中成长,并且也能积极主动地和我沟通,因为他基础比较薄弱,产生的问题比较多,也经常要向我寻求帮助,某种程度上也是让我学到了很多。

221701234(前端、前端部分文档博客撰写)

这一次的结对作业,不仅仅是上一次的结对作业的延续,对我而言也是为接下来的团队作业打基础。虽然学了一小段时间的vue,但是仅仅停留于纸面上,只做过一点点小东西 ,可以说对于真正的项目开发还是毫无经验。也正是因为基础不好的缘故,在作业中遇到了不少问题,经过一番搜索学习,最后解决问题,在这过程中真切体会到了编程的乐趣,不断地解决遇到的一个又一个问题。

对上面那个人的评价:

我的结对队友,同时也是我的舍友兼好友,以我对他的了解,他是一个勤勉上进的人,我是一个有拖延症的人,事情喜欢拖着做,鉴于这次作业对于我这种基础不太好的人而言开头是很慢的,需要花费很多时间“摔倒再爬起”,正是他调动我的积极性,才使得我们得以如期完成我个人而言较为满意的作品。在外观上我大部都会询问他的意见来决定最终采取什么样的效果,以及开发过程中遇到的一些其他问题,我们也做了很多交流,在此再次感谢我的队友。

posted @ 2020-03-14 18:49  张溴  阅读(266)  评论(1编辑  收藏  举报