這個作業屬於哪個課程 | <2020春W班 (福州大學)> |
---|---|
這個作業要求在哪里 | <作業要求> |
結對學號 | <221701412、221701420> |
這個作業的目標 | <某次疫情統計可視化的實現> |
作業正文 | <作業正文> |
其他參考文獻 | <echarts、springboot官方開發文檔、天行數據> |
Part.01 結對合作Github倉庫地址和代碼規范
在文章開頭給出Github倉庫地址和代碼規范鏈接
-
Github倉庫地址 “https://github.com/theTuring/InfectStatisticWeb” <點擊進入>
-
221701412的代碼規范鏈接 “My coolstyle.md” <點擊進入>
-
221701420的代碼規范鏈接 “My coolstyle.md” <點擊進入>
-
創建倉庫分dev支,先在dev分支上開發
-
二人在倉庫合作開發
Part.02 成品展示
展示你的成品,要求提供10張以上的圖片,或者采用GIF或者視頻嵌入的方式來展示作業要求的功能。如果部署到雲服務器上,可以一並給出鏈接
-
阿里雲服務器地址 “http://47.95.3.253:8080” <點擊進入>
-
統計爬取了2020-02-12至今日的所有的真實數據
-
ps:建議使用火狐貨谷歌瀏覽器訪問(服務器幾年前買的學生機,比較卡)
-
將項目前后端部署到了阿里雲服務器(window server2008系統)上,使用 tomcat9.0 + phpstudy + nginx配置服務器
-
功能1:實現通過地圖的形式來直觀顯示疫情的大致分布情況,還可以查看具體省份的疫情統計情況
- 可以選擇具體的日期
- 在全國地圖上使用不同的顏色代表大概確診人數區間
- 顏色的深淺表示疫情的嚴重程度,可以直觀了解高危區域
- 鼠標移到每個省份會高亮顯示;點擊鼠標會顯示該省具體疫情情況
- 效果總覽
- 可以選擇具體的日期
-
功能2:點擊某個省份顯示該省疫情的具體情況
- 可以選擇具體的日期
- 顯示該省份對應的感染患者人數、疑似患者人數、治愈人數、死亡人數
- 該省份到目前為止的新增確診趨勢、新增疑似趨勢、治愈趨勢和死亡趨勢;繪制該省份的趨勢變化曲線圖
- 效果總覽
- 可以選擇具體的日期
-
拓展功能:當日最新熱點新聞模塊
Part.03 結對討論過程
結對討論過程描述,即剛開始拿到題目后,和隊友怎么討論,解決問題和查找資料的過程,並提供兩人結對討論的截圖
-
分工前后端分離,221701412負責后端使用springboot寫接口,使用postman初步測試后直接掛在服務器提供接口,221701420負責前端界面編寫
-
獲得了實時數據,項目有所進展
-
后端基本完工,將接口的功能文檔提供給前端使用
-
對於接口的討論
-
增加新的接口
-
關於界面的修改討論
Part.04 設計實現過程
描述設計實現過程,給出功能結構圖
221701412-后端
-
1.確定項目基本結構
使用 springboot 作為基本框架,maven 做為包管理器,jackson 用來封裝 json 數據以及篩選 json 數據。
-
2.爬取數據
從網絡上找到可爬取的接口進行爬取數據,解析存入數據庫
-
3.前后端交互
前后端交互通過http接口,由后端為前端提供接口文檔
-
4.功能結構圖
221701420-前端
-
1.日期選擇器
設計一個日期選擇器,選擇想要看的數據的日期
-
2.地圖模塊
根據日期生成全國地圖,在全國地圖上使用不同的顏色代表大概確診人數區間,顏色的深淺表示疫情的嚴重程度,可以直觀了解高危區域;點擊鼠標會顯示該省具體疫情情況;具體展現為統計圖
-
3.統計圖模塊
根據在地圖選擇的省份和日期選擇器的日期,生成一份統計圖展示該省份在選擇日期最近幾天的數據變化情況
-
4.新聞模塊
展示每日的熱點新聞
-
5.實現后台腳本
- 使用layui提供的日期選項框在前端生成選擇器,然后對其功能進行包裝以適應本次的開發。
- 根據日期選項框提供的日期生成一個可以點擊省份查看詳細信息,展示基本數據的地圖,在點擊省份后觸發統計圖模塊更新數據。
- 使用地圖提供的省份名以及日期選擇器提供的日期,訪問接口獲得詳細數據裝載統計圖。
- 在日期改變時觸發更新地圖和統計圖的事件。
-
6.功能結構圖
Part.05 關鍵代碼
代碼說明。展示出項目關鍵代碼,300行左右,並解釋思路
221701412-后端
-
本次后端目錄結構使用spring boot官方推薦的目錄結構
-
啟動類及定時器
@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
我的隊友是一個學習能力很強的人,凡事親歷親為,寫的接口又好用,說話又好聽