軟件工程實踐2020_結對第二次作業 —— 某次疫情統計可視化的實現


這個作業屬於哪個課程 <2020春W班 (福州大學)>
這個作業要求在哪里 <作業要求>
結對學號 <221701412、221701420>
這個作業的目標 <某次疫情統計可視化的實現>
作業正文 <作業正文>
其他參考文獻 <echarts、springboot官方開發文檔、天行數據>

Part.01 結對合作Github倉庫地址和代碼規范

在文章開頭給出Github倉庫地址和代碼規范鏈接


  • 創建倉庫分dev支,先在dev分支上開發

github


  • 二人在倉庫合作開發

github


Part.02 成品展示

展示你的成品,要求提供10張以上的圖片,或者采用GIF或者視頻嵌入的方式來展示作業要求的功能。如果部署到雲服務器上,可以一並給出鏈接

  • 阿里雲服務器地址 http://47.95.3.253:8080 <點擊進入>

  • 統計爬取了2020-02-12至今日的所有的真實數據

  • ps:建議使用火狐貨谷歌瀏覽器訪問(服務器幾年前買的學生機,比較卡)

  • 將項目前后端部署到了阿里雲服務器(window server2008系統)上,使用 tomcat9.0 + phpstudy + nginx配置服務器

aliyun

  • 功能1:實現通過地圖的形式來直觀顯示疫情的大致分布情況,還可以查看具體省份的疫情統計情況

    • 可以選擇具體的日期
      img
    • 在全國地圖上使用不同的顏色代表大概確診人數區間
      img
    • 顏色的深淺表示疫情的嚴重程度,可以直觀了解高危區域
      img
    • 鼠標移到每個省份會高亮顯示;點擊鼠標會顯示該省具體疫情情況
      img
    • 效果總覽
      gif
  • 功能2:點擊某個省份顯示該省疫情的具體情況

    • 可以選擇具體的日期
      img
    • 顯示該省份對應的感染患者人數、疑似患者人數、治愈人數、死亡人數
      img
    • 該省份到目前為止的新增確診趨勢、新增疑似趨勢、治愈趨勢和死亡趨勢;繪制該省份的趨勢變化曲線圖
      img
    • 效果總覽
      gif
  • 拓展功能:當日最新熱點新聞模塊
    gif


Part.03 結對討論過程

結對討論過程描述,即剛開始拿到題目后,和隊友怎么討論,解決問題和查找資料的過程,並提供兩人結對討論的截圖

  • 分工前后端分離,221701412負責后端使用springboot寫接口,使用postman初步測試后直接掛在服務器提供接口,221701420負責前端界面編寫

pairwork


  • 獲得了實時數據,項目有所進展

pairwork


  • 后端基本完工,將接口的功能文檔提供給前端使用

pairwork


  • 對於接口的討論

pairwork
pairwork
pairwork


  • 增加新的接口

pairwork
pairwork


  • 關於界面的修改討論

pairwork
pairwork
pairwork
pairwork


Part.04 設計實現過程

描述設計實現過程,給出功能結構圖

221701412-后端

  • 1.確定項目基本結構

使用 springboot 作為基本框架,maven 做為包管理器,jackson 用來封裝 json 數據以及篩選 json 數據。

  • 2.爬取數據

從網絡上找到可爬取的接口進行爬取數據,解析存入數據庫

  • 3.前后端交互

前后端交互通過http接口,由后端為前端提供接口文檔

  • 4.功能結構圖

backend

221701420-前端

  • 1.日期選擇器

設計一個日期選擇器,選擇想要看的數據的日期

  • 2.地圖模塊

根據日期生成全國地圖,在全國地圖上使用不同的顏色代表大概確診人數區間,顏色的深淺表示疫情的嚴重程度,可以直觀了解高危區域;點擊鼠標會顯示該省具體疫情情況;具體展現為統計圖

  • 3.統計圖模塊

根據在地圖選擇的省份和日期選擇器的日期,生成一份統計圖展示該省份在選擇日期最近幾天的數據變化情況

  • 4.新聞模塊

展示每日的熱點新聞

  • 5.實現后台腳本

    • 使用layui提供的日期選項框在前端生成選擇器,然后對其功能進行包裝以適應本次的開發。
    • 根據日期選項框提供的日期生成一個可以點擊省份查看詳細信息,展示基本數據的地圖,在點擊省份后觸發統計圖模塊更新數據。
    • 使用地圖提供的省份名以及日期選擇器提供的日期,訪問接口獲得詳細數據裝載統計圖。
    • 在日期改變時觸發更新地圖和統計圖的事件。
  • 6.功能結構圖

frontend


Part.05 關鍵代碼

代碼說明。展示出項目關鍵代碼,300行左右,並解釋思路

221701412-后端

  • 本次后端目錄結構使用spring boot官方推薦的目錄結構

backend
backend

  • 啟動類及定時器

@SpringBootApplication
//exclude表示自動配置時不包括Multipart配置
@EnableAutoConfiguration(exclude = {MultipartAutoConfiguration.class})
@ServletComponentScan
public class InfectStatisticApplication extends SpringBootServletInitializer{

    public static void main(String[] args) throws SQLException {

        // 創建定時器
        Timer timer = new Timer();

        timer.schedule(new TimerTask() {
            // 在run方法中的語句就是定時任務執行時運行的語句。
            public void run() {

                //json解析類實例化
                AnalysisJson analysisJson = new AnalysisJson();

                try {
                    analysisJson.TimerExecute();
                } catch (SQLException e) {
                    e.printStackTrace();
                }


            }
            // 表示在3秒之后開始執行,並且每8640秒(一天)執行一次
        }, 3000, 1000 * 60 * 60 * 24);


//        SpringApplication.run(InfectStatisticApplication.class, args);

    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(InfectStatisticApplication.class);
    }

    /**
     * ajax跨域
     */
    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**").allowedOrigins("*");
            }
        };
    }
}

  • 解析接口

 //解析全國省份城市統計的json
    public static List<JsonResultProvince> ProvinceJson() {

        Gson gson=new Gson();

        //http://api.tianapi.com/txapi/ncovcity/index?key=6e07e5626fdebe0394ff896b6bdb52a3
        String json_temp = HttpRequest.sendGet("http://api.tianapi.com/txapi/ncovcity/index?key=6e07e5626fdebe0394ff896b6bdb52a3");

        //解析對象:第一個參數:待解析的字符串 第二個參數結果數據類型的Class對象
        JsonResultProvinceList jsonResultBooksList=gson.fromJson(json_temp, JsonResultProvinceList.class);

        return jsonResultBooksList.getNewslist();

    }
  • mapper層

@Mapper
public interface NationMapper {

    //查詢全部
    @Select("select * from nation")
    List<Nation> getAllNation();

    //日期date查找
    @Select("SELECT * FROM nation WHERE date =#{date}")
    Nation queryNationByDate(String date);

    //添加國家統計信息
    @Insert("INSERT INTO nation (date, current_diagnosis, cumulative_diagnosis, suspected, cured, acute, dead) "
            + "VALUES (#{date}, #{current_diagnosis}, #{cumulative_diagnosis}, #{suspected}, #{cured}, #{acute}, #{dead})")
    int insertNation(Nation nation);

}
    
@Mapper
public interface ProvinceMapper {

    //查詢全部
    @Select("select * from province")
    List<Province> getAllProvince();

    //省份名province和日期date查找
    @Select("SELECT * FROM province WHERE province =#{province} AND date =#{date}")
    Province queryEvRecordByBoth(@Param("province") String province, @Param("date") String date);

    //日期date查找
    @Select("SELECT * FROM province WHERE date =#{date}")
    List<Province> queryEvRecordByDate(String date);

    //添加省份統計信息
    @Insert("INSERT INTO province (province, date, current_diagnosis, cumulative_diagnosis, suspected, cured, acute, dead) "
            + "VALUES (#{province}, #{date}, #{current_diagnosis}, #{cumulative_diagnosis}, #{suspected}, #{cured}, #{acute}, #{dead})")
    int insertProvince(Province province);
}
    
  • controller層(接口)

/**
 * GetController
 * TODO
 * @description 所有的get請求的接口
 * 0./api/init/province/all/date/{date} 初始化某一時間點所有的省份狀態(后端測試用前端勿用)
 * 1./api/query/nation/all 查詢全部的國家統計信息
 * 2./api/query/province/all 查詢全部的國家省份統計信息
 * 3./api/init/province/all 初始化所有的省份狀態(后端測試用前端勿用)
 * 將所有省份置為"date":"1970-01-01","current_diagnosis":0,"cumulative_diagnosis":0,"suspected":0,"cured":0,"acute":0,"dead":0
 * 4./api/query/nation/date/{date} 根據日期查詢國家統計信息,返回國家實體
 * 5./api/query/province/date/province/{date}/{province} 根據日期和省份名查詢國家省份統計信息,返回省份實體
 * 6./api/query/province/city/all 直接查詢查看即時的國家省份城市統計信息(api獲取)
 * 7./api/query/news 直接查詢即時熱點信息(api獲取)
 * 8./api/query/nation/increase/{date} 根據日期查詢國家統計信息,返回國家當日增加實體
 * 9./api/query/province/increase/{date}/{province} 根據日期查詢國家省份統計信息,返回對應省份當日增加實體
 * 10./api/query/province/date/{date} 根據日期查詢國家省份統計信息,返回省份list
 * 11./api/query/province/increase/date1_to_date2/{date1}/{date2}/{province} 根據日期查詢國家省份統計信息,返回一個時間段對應省份當日增加實體
 * @author 221701412_theTuring
 * @version v 1.0.0
 * @since 2020.3.8
 */
@RestController
@CrossOrigin
@RequestMapping("/api")
public class GetController implements ProvinceConstant{

    @Autowired
    private NationService nationService;

    @Autowired
    private ProvinceService provinceService;

    //初始化所有的省份狀態
    @RequestMapping("init/province/all/date/{date}")
    public JsonResult initProvinceDateAll(@PathVariable String date) {

        //實例化省份的實體
        Province province = new Province();

        for(int i=0; i<PROVINCE_NUM; i++){

            province.setProvince(PROVINCES[i]);
            province.setDate(date);
            province.setCurrent_diagnosis(INIT_NUM);
            province.setCumulative_diagnosis(INIT_NUM);
            province.setSuspected(INIT_NUM);
            province.setAcute(INIT_NUM);
            province.setCured(INIT_NUM);
            province.setDead(INIT_NUM);

            int temp = provinceService.insertProvince(province);

        }

        return JsonResult.build(200,"success",null);

    }

    //mysql單類型查詢()
    @RequestMapping("query/nation/all")
    public JsonResult queryNationAll() {

        List<Nation> list = this.nationService.getAllNation();

        return JsonResult.ok(list);

    }

    //mysql單類型查詢()
    @RequestMapping("query/province/all")
    public JsonResult queryProvinceAll() {

        List<Province> list = this.provinceService.getAllProvince();

        return JsonResult.ok(list);

    }
............(省略取一部分)

221701420-前端

  • dateFormat()函數:返回一個符合“year-month-day”格式的日期,日期為日期選項框的值

//返回選項框的日期
function dateFormat(){
    var date=document.getElementById("time").value;
        //修理日期未生成時產生的接口訪問錯誤bug

    if(date==''){
        var temp=new Date();
        var years=temp.getFullYear();
        var month=temp.getMonth();
        month++;
        if(month<10) month='0'+month;
        var days=temp.getDate();
        if(days<10) days='0'+days;
        date=years+'-'+month+'-'+days;
    }
   return date;
}
  • setMap()函數:根據dateFormat()返回的日期訪問接口獲取數據並且生產全國疫情圖

function setMap(set){

    var date=dateFormat();

    var myChart = echarts.init(document.getElementById('map'));

    //根據日期獲取全國各省的情況
    axios.get('http://47.95.3.253:8080/InfectStatistic//api/query/province/date/'+date)
    .then(function (response) {
        var dataList=new Array();
        if(set=='現有確診'){
            for(var i=0;i<34;i++){
                dataList[i]={
                    name:response.data.data[i].province,
                    value:response.data.data[i].current_diagnosis
                }
            }
        }
        else{
            for(var i=0;i<34;i++){
                dataList[i]={
                    name:response.data.data[i].province,
                    value:response.data.data[i].cumulative_diagnosis
                }
            }
        }
        //console.log(dataList);

    
        option = {
            tooltip: {
                formatter:function(params,ticket, callback){
                    return params.seriesName+'<br />'+params.name+':'+params.value
                }//數據格式化
            },
            visualMap: {
                min: 0,
                max: 1500,
                left: 'left',
                top: 'bottom',
                text: ['高','低'],//取值范圍的文字
                inRange: {
                    color: ['#FFFFFF', '#FF0000']//取值范圍的顏色
                },
                show:true//圖注
            },
            geo: {
                map: 'china',
                roam: false,//不開啟縮放和平移
                zoom:1.23,//視角縮放比例
                label: {
                    normal: {
                        show: true,
                        fontSize:'10',
                        color: 'rgba(0,0,0,0.7)'
                    }
                },
                itemStyle: {
                    normal:{
                        borderColor: 'rgba(0, 0, 0, 0.2)'
                    },
                    emphasis:{
                        areaColor: '#F3B329',//鼠標選擇區域顏色
                        shadowOffsetX: 0,
                        shadowOffsetY: 0,
                        shadowBlur: 20,
                        borderWidth: 0,
                        shadowColor: 'rgba(0, 0, 0, 0.5)'
                    }
                }
            },
            series : [
                {
                    name: '信息量',
                    type: 'map',
                    geoIndex: 0,
                    data:dataList
                }
            ]
        };
        myChart.setOption(option,true);

        //點擊地圖上的省份顯示詳細信息
        myChart.on('click', function (params) {
            var pro=document.getElementById("province");
            pro.innerHTML=params.name;
            setChart();
            setBoxs();
        });

      
    })
    .catch(function (error) {
        console.log(date);
        console.log(error);
    });

}
  • setChart()函數:根據dateFormat()返回的日期和setMap()生產的地圖中選中的省份訪問接口獲得數據生成統計圖

function setChart(set){
    if(set==null||set==undefined){
        set=document.getElementById("chartName").value;
    }
    var date=new Date(Date.parse(dateFormat().replace(/-/g,  "/")));
    //相當於date2減去10天
    date-=10*24 * 60 * 60 * 1000;
    //月份格式化
    date=new Date(date);
    var month=date.getMonth();
    var day=date.getDate();
    month++;
    if(month<10) month='0'+month;
    if(day<10) day='0'+day;
    //訪問接口的日期格式化
    var date1=date.getFullYear()+'-'+month+'-'+day;
    
    var date2=dateFormat();
    //獲取省份
    province=document.getElementById("province").innerHTML;
    if(set==null||set==undefined) set=document.getElementById("chartName").value;
     //初始化圖表
     var myChart = echarts.init(document.getElementById('chart'));
     //訪問端口
    axios.get('http://47.95.3.253:8080/InfectStatistic/api/query/province/increase/date1_to_date2/'+date1+"/"+date2+"/"+province)
    .then(function (response) {
        // 指定圖表的配置項和數據
        option = ({
            title: {
                text: '統計圖'
            },
            tooltip: {
                trigger: 'axis'
            },
            legend: {
                data:['legend']
            },
            grid: {
                left: '3%',
                right: '4%',
                bottom: '3%',
                containLabel: true
            },
            toolbox: {
                feature: {
                saveAsImage: {}
            }
            },
            backgroundColor: 'white',
            xAxis: {
                type: 'category',
                axisTick:{
                    show:false,
                },
                boundaryGap: false,
                axisTick:{
                    show:false,
                },
                axisLabel:{
                    color:'black'
                },
                axisLine:{
                    lineStyle:{
                        color:'rgba(12,102,173,.5)',
                        width:2,
                    }
                },
            },
            yAxis: [
                {
                    type: 'value',
                    axisTick:{
                        show:false,//不顯示刻度線
                    },
                    axisLabel:{
                        color:'black'  //y軸上的字體顏色
                    },
                    axisLine:{
                        lineStyle:{
                            width:2,
                            color:'rgba(12,102,173,.5)',//y軸的軸線的寬度和顏色
                        }
                    },
                    splitLine: {
                        show: false       
                    }
                },
            ],
            series: [
                {
                    type:'line',
                    symbol: 'none',
                    smooth:true,
                    itemStyle: {
                        normal: {
                            color: 'red',
                        }
                    },
                    areaStyle: {
                        normal: {
                            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
                                offset: 0,
                                color: 'red'
                            }, {
                                offset: 1,
                                color: 'rgba(12,102,173,.5)'
                            }])
                        }
                    },
                }
            ]
        });    
        //根據日期獲取指定省份情況的情況
        var dataList=new Array();
        //x軸數據數組
        var xAxisData=new Array();

        for(var i=0;i<response.data.data.length;i++){
            if(set=="新增感染者"){
                dataList[i]=response.data.data[i].current_diagnosis;
                if(dataList[i]<0) dataList[i]=0;
            }
            else if(set=="累計感染者"){
                dataList[i]=response.data.data[i].cumulative_diagnosis;
                if(dataList[i]<0) dataList[i]=0;
            }
            else if(set=="治愈"){
                dataList[i]=response.data.data[i].cured;
                if(dataList[i]<0) dataList[i]=0;
            }
            else if(set=="死亡"){
                dataList[i]=response.data.data[i].dead;
                if(dataList[i]<0) dataList[i]=0;
            }
            //x軸為日期
            xAxisData[i]=response.data.data[i].date;

        }
        option.series[0].data = dataList;
        //x軸數據設定
        option.xAxis.data = xAxisData;
        myChart.setOption(option);
    });
}
  • change.js:根據dateFormat()返回的日期訪問接口獲取相較於昨天的數據變化並且顯示在頁面

function dataChange(){
    var date=dateFormat();
    if(date==''){
        var temp=new Date();
        var years=temp.getFullYear();
        var month=temp.getMonth();
        month++;
        if(month<10) month='0'+month;
        var days=temp.getDate();
        if(days<10) days='0'+days;
        date=years+'-'+month+'-'+days;
    }
    axios.get('http://47.95.3.253:8080/InfectStatistic//api/query/nation/increase/'+date)
    .then(function (response) {
        document.getElementById("nationExistDiagnosisChange").innerHTML=response.data.data.current_diagnosis;
        document.getElementById("nationExistSuspectsChange").innerHTML=response.data.data.suspected;
        document.getElementById("nationExistSevereChange").innerHTML=0;
        document.getElementById("nationCumulativeDiagnosisChange").innerHTML=response.data.data.cumulative_diagnosis;
        document.getElementById("nationCumulativeCureChange").innerHTML=response.data.data.cured;
        document.getElementById("nationCumulativeDeadChange").innerHTML=response.data.data.dead;
    })
}
  • setBox()函數:根據dateFormat()返回的日期獲取訪問接口獲得當日的各項數據展示在網頁

function setBoxs(){
    var date=dateFormat();

    axios.get('http://47.95.3.253:8080/InfectStatistic//api/query/nation/all')
    .then(function(response){
        for(var i=0;i<response.data.data.length;i++){
            if(response.data.data[i].date==date){
                document.getElementById("nationExistDiagnosis").innerHTML=response.data.data[i].current_diagnosis;
                document.getElementById("nationExistSuspects").innerHTML=response.data.data[i].suspected;
                document.getElementById("nationExistSevere").innerHTML=0;
                document.getElementById("nationCumulativeDiagnosis").innerHTML=response.data.data[i].cumulative_diagnosis;
                document.getElementById("nationCumulativeCure").innerHTML=response.data.data[i].cured;
                document.getElementById("nationCumulativeDead").innerHTML=response.data.data[i].dead;

            }
        }
    });
    axios.get('http://47.95.3.253:8080/InfectStatistic///api/query/province/all')
    .then(function(response){
        var pro=document.getElementById("province").innerHTML;
        for(var i=0;i<response.data.data.length;i++){
            if(response.data.data[i].date==date&&response.data.data[i].province==pro){

                document.getElementById("provinceExistDiagnosis").innerHTML=response.data.data[i].current_diagnosis;
                document.getElementById("provinceCumulativeDiagnosis").innerHTML=response.data.data[i].cumulative_diagnosis;
                document.getElementById("provinceCumulativeCure").innerHTML=response.data.data[i].cured;
                document.getElementById("provinceCumulativeDead").innerHTML=response.data.data[i].dead;
            }
        }
    });
}
  • news.js:訪問接口獲取當天的新聞顯示在頁面

axios.get('http://api.tianapi.com/txapi/ncov/index?key=6e07e5626fdebe0394ff896b6bdb52a3')
.then(function (response) {
    var news=document.getElementById("news");
    for(var i=0;i<response.data.newslist[0].news.length;i++){
        var node=document.createElement('div');
        var a=document.createElement('a');
        a.innerHTML=response.data.newslist[0].news[i].title;
        a.setAttribute("href",response.data.newslist[0].news[i].sourceUrl)
        node.appendChild(a);
        news.appendChild(node);
    }
    
})
.catch(function(error){
    console.log(error);
})

Part.06 心路歷程與收獲

閱讀《構建之法》第四章至第五章的內容,結合在構建之法中學習到的相關內容,結對伙伴分別撰寫結對開發項目的心路歷程與收獲,並評價結對隊友

閱讀心得

  • 1.第四章心得
    對於構建之法第四章里面所要求的兩人合作的要求,本次疫情因素導致只能遠程進行交流,所以我們采用了前后端分離,僅僅通過接口進行交互,使用了GitHub的合作倉庫在dev分支上共同開發,對結對編程這種合作方法有了一定的新的體驗。

  • 2.第五章心得
    在第五章提到 l 團隊合作和流程這個對於我們接下來的團隊項目將很有幫助。作為一個團隊,要有一致的目標、明確的分工。首先這一點是最為關鍵的,在團隊中要時刻注意和保持。

心路歷程及收獲

  • 221701412
    這次的作業比較大的收獲應該是研究了spring boot的官網文檔,增加了自己不少對該框架的認知,項目的目錄結構也相對的更加規范,其次最大的難點就是對數據的獲取,開始找了很多網站使用webmmagic爬取,渲染使用谷歌內核driver獲得渲染網頁,百度,360,丁香均只能獲得當前數據,后來在天行數據找到了接口直接解析得到了前幾日的數據,最后和隊友成功完成作業,可喜可賀!

  • 221701420
    這次的前端工作總體來說不難,但是如果真的要擴展許多功能就比較困難了,所以這次只制作了一個熱點的擴展功能。在實現的過程中遇到許多問題,但是在隊友和百度的幫助下,總算度過難關。可喜可賀!

對隊友的評價

  • 221701420
    我的隊友代碼規范,易交流,即使我天天騷擾也不厭其煩

  • 221701420
    我的隊友是一個學習能力很強的人,凡事親歷親為,寫的接口又好用,說話又好聽



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM