1、題目要求

2、整體思想
首先是在前兩階段已經完成的echarts可視化、利用Jsoup爬取疫情數據基礎上來進行調用與完善。大致思想是在Android Studio上完成交互去調用ecplise中的Servlet,我新建了兩個Servlet為PaquServlet、SearchServlet分別用來接受進行移動端的請求,與Web端的YqServlet分開來,當然也可以不新建Servlet直接調用Web端的YqServlet也是可以的。PaquServlet就是接收到移動端的調用之后開始執行,爬取疫情數據。SearchServlet當接收到移動端的請求調用后開始執行查找功能,這里我分為了兩個方法,一個用來查找國內疫情數據,另一個用來查找海外的數據。而國內與海外都存放在同一個數據庫表中,所以我又在數據庫表中添加了一個Kind的欄位,里面的值為1或2,爬取數據的時候,國內與海外的數據分開爬取,國內的數據Kind等於1,海外的數據Kind等於2,這樣查詢國內或海外的數據的時候就就方便了,SearchServlet中返回的json數據在Android Studio中解析出來后,我利用的是哈希表來完成分配數據的,使用的是LinearLayout中ListView布局,由於對Android Studio的不熟悉,解析與顯示數據以及布局也是在開發過程中最讓我頭疼的一部分了。注意:本文采用的是ecplise與Android Studio交互遠程連接數據庫,Android Studio上面並沒有直接連取數據庫
3、代碼實現
3.1 Web端(包含前兩階段代碼)

Info.java:
package Bean; public class Info { private int id; private String city; private String yisi_num; private String date; private String province; private String confirmed_num; private String cured_num; private String dead_num; private String newconfirmed_num; public String getNewconfirmed_num() { return newconfirmed_num; } public void setNewconfirmed_num(String newconfirmed_num) { this.newconfirmed_num = newconfirmed_num; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getYisi_num() { return yisi_num; } public void setYisi_num(String yisi_num) { this.yisi_num = yisi_num; } public String getDate() { return date; } public void setDate(String date) { this.date = date; } public String getProvince() { return province; } public void setProvince(String province) { this.province = province; } public String getConfirmed_num() { return confirmed_num; } public void setConfirmed_num(String confirmed_num) { this.confirmed_num = confirmed_num; } public String getCured_num() { return cured_num; } public void setCured_num(String cured_num) { this.cured_num = cured_num; } public String getDead_num() { return dead_num; } public void setDead_num(String dead_num) { this.dead_num = dead_num; } }
Paqu.java(如同它的名字,用來爬取數據的)
package control; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import com.gargoylesoftware.htmlunit.BrowserVersion; import com.gargoylesoftware.htmlunit.WebClient; import com.gargoylesoftware.htmlunit.html.HtmlPage; import Dao.AddService; public class Paqu { public static void main(String args[]) { refesh(); } public static void refesh() { // TODO Auto-generated method stub String sheng=""; String xinzeng=""; String leiji=""; String zhiyu=""; String siwang=""; String country=""; char kind; String url = "https://wp.m.163.com/163/page/news/virus_report/index.html?_nw_=1&_anw_=1"; int i=0; try { //構造一個webClient 模擬Chrome 瀏覽器 WebClient webClient = new WebClient(BrowserVersion.CHROME); //支持JavaScript webClient.getOptions().setJavaScriptEnabled(true); webClient.getOptions().setCssEnabled(false); webClient.getOptions().setActiveXNative(false); webClient.getOptions().setCssEnabled(false); webClient.getOptions().setThrowExceptionOnScriptError(false); webClient.getOptions().setThrowExceptionOnFailingStatusCode(false); webClient.getOptions().setTimeout(8000); HtmlPage rootPage = webClient.getPage(url); //設置一個運行JavaScript的時間 webClient.waitForBackgroundJavaScript(6000); String html = rootPage.asXml(); Document doc = Jsoup.parse(html); //System.out.println(doc);
//爬取國內各省數據 Element listdiv1 = doc.select(".wrap").first(); Elements listdiv2 = listdiv1.select(".province"); for(Element s:listdiv2) { Elements span = s.getElementsByTag("span"); Elements real_name=span.select(".item_name"); Elements real_newconfirm=span.select(".item_newconfirm"); Elements real_confirm=span.select(".item_confirm"); Elements real_dead=span.select(".item_dead"); Elements real_heal=span.select(".item_heal"); sheng=real_name.text(); xinzeng=real_newconfirm.text(); leiji=real_confirm.text(); zhiyu=real_heal.text(); siwang=real_dead.text(); System.out.println(sheng+" 新增確診:"+xinzeng+" 累計確診:"+leiji+" 累計治愈:"+zhiyu+" 累計死亡:"+siwang); Date currentTime=new Date(); SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm"); String time = formatter.format(currentTime);//獲取當前時間 kind='1';//1代表國內省份,2代表海外,為國內外分開查詢做基礎 AddService dao=new AddService(); dao.add("myinfo", sheng, xinzeng, leiji, zhiyu, siwang,time,kind);//將爬取到的數據添加至數據庫,注意需將“myinfo”修改為你的表名 }
//爬取海外數據 Element listdiv11 = doc.getElementById("world_block"); Elements listdiv22 =listdiv11.select(".chart_table_nation"); for(Element s:listdiv22) { Elements real_name=s.select(".chart_table_name"); Elements real_newconfirm=s.select(".chart_table_today_confirm"); Elements real_confirm=s.select(".chart_table_confirm"); Elements real_dead=s.select(".chart_table_dead"); Elements real_heal=s.select(".chart_table_heal"); country=real_name.text(); xinzeng=real_newconfirm.text(); leiji=real_confirm.text(); zhiyu=real_heal.text(); siwang=real_dead.text(); System.out.println(country+" 新增確診:"+xinzeng+" 累計確診:"+leiji+" 累計治愈:"+zhiyu+" 累計死亡:"+siwang); Date currentTime=new Date(); SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm"); String time = formatter.format(currentTime);//獲取當前時間 kind='2';//1代表國內省份,2代表海外,為國內外分開查詢做基礎 AddService dao=new AddService(); dao.add("myinfo", country, xinzeng, leiji, zhiyu, siwang,time,kind);//將爬取到的數據添加至數據庫,注意需將“myinfo”修改為你的表名 } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); System.out.println("爬取失敗"); } } }
AddService.java(上面的Paqu.java在爬取中調用了該類,將數據添加到數據庫中)
package Dao; import java.sql.Connection; import java.sql.Statement; import utils.DBUtil; public class AddService { public void add(String table,String sheng,String xinzeng,String leiji,String zhiyu,String dead,String time,char kind) { String sql = "insert into "+table+" (Province,Newconfirmed_num ,Confirmed_num,Cured_num,Dead_num,Time,Kind) values('" + sheng + "','" + xinzeng +"','" + leiji +"','" + zhiyu + "','" + dead+ "','" + time+ "','" + kind+ "')"; System.out.println(sql); Connection conn = DBUtil.getConn(); Statement state = null; int a = 0; try { state = conn.createStatement(); a=state.executeUpdate(sql); } catch (Exception e) { e.printStackTrace(); } finally { DBUtil.close(state, conn); } } }
DeleteService.java(按需刪除數據庫中的數據,當重新爬取更新今日數據時調用)
package Dao; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import utils.DBUtil; public class DeleteService { public boolean delete(String table,String value) { boolean c=false; Connection conn=DBUtil.getConn(); PreparedStatement state=null; String sql="delete from "+table+" where date(Time) =?";//date(Time)將數據庫表中Time轉換為只有日期的形式 try { state=conn.prepareStatement(sql); state.setString(1,value); int num = state.executeUpdate(); if(num!=0) { c= true; } state.close(); conn.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } return c; } }
Get.java(SearchServlet查詢表中數據時調用並以List形式返回)
package Dao; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; import java.util.List; import Bean.Info; import utils.DBUtil; public class Get {
//查詢國內各省數據 public List<Info> listAll(String date1,String date2) { ArrayList<Info> list = new ArrayList<>(); Connection conn=DBUtil.getConn(); PreparedStatement pstmt = null; ResultSet rs = null; String sql="select * from myinfo where Kind ='1' and Time between ? and ?"; try { pstmt = conn.prepareStatement(sql); pstmt.setString(1, date1); pstmt.setString(2, date2); rs = pstmt.executeQuery(); while (rs.next()) { Info yq = new Info(); yq.setId(rs.getInt(1)); yq.setDate(rs.getString(8)); yq.setProvince(rs.getString(2)); yq.setNewconfirmed_num(rs.getString(3)); yq.setConfirmed_num(rs.getString(4)); yq.setCured_num(rs.getString(6)); yq.setDead_num(rs.getString(7)); list.add(yq); } } catch (Exception e) { e.printStackTrace(); } finally { try { conn.close(); } catch (Exception e) { e.printStackTrace(); } } return list; } //查詢海外數據 public List<Info> listAll2(String date1,String date2) { ArrayList<Info> list = new ArrayList<>(); Connection conn=DBUtil.getConn(); PreparedStatement pstmt = null; ResultSet rs = null; String sql="select * from myinfo where Kind ='2' and Time between ? and ?"; try { pstmt = conn.prepareStatement(sql); pstmt.setString(1, date1); pstmt.setString(2, date2); System.out.println(sql); rs = pstmt.executeQuery(); while (rs.next()) { Info yq = new Info(); yq.setId(rs.getInt(1)); yq.setDate(rs.getString(8)); yq.setProvince(rs.getString(2)); yq.setNewconfirmed_num(rs.getString(3)); yq.setConfirmed_num(rs.getString(4)); yq.setCured_num(rs.getString(6)); yq.setDead_num(rs.getString(7)); list.add(yq); } } catch (Exception e) { e.printStackTrace(); } finally { try { conn.close(); } catch (Exception e) { e.printStackTrace(); } } return list; } }
Select.java(查詢表中是否有今日數據從而判斷是否刪除.....現在發現根本不需要該方法,直接刪除即可,不需要判斷表中有沒有數據)
package Dao; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.Statement; import java.util.ArrayList; import java.util.List; import utils.DBUtil; public class Select { public boolean select(String time) { // TODO Auto-generated method stub Connection conn=DBUtil.getConn(); PreparedStatement pstmt = null; ResultSet rs = null; boolean b=false; String sql="select * from myinfo where date(Time) = ?"; System.out.println(sql); try { pstmt = conn.prepareStatement(sql); pstmt.setString(1, time); rs = pstmt.executeQuery(); while (rs.next()) { b=true; } } catch (Exception e) { e.printStackTrace(); } finally { try { conn.close(); } catch (Exception e) { e.printStackTrace(); } } return b; } }
PaquServlet.java(這階段新建的,專門用來接收移動端爬取請求的)
package Servlet; import java.io.IOException; import java.sql.ResultSet; import java.sql.SQLException; import java.text.SimpleDateFormat; import java.util.Date; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.google.gson.Gson; import Dao.DeleteService; import control.Paqu; import utils.DBUtil; @WebServlet("/PaquServlet")//移動端爬取用到了該Servlet public class PaquServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("request--->"+request.getRequestURL()+"===="+request.getParameterMap().toString()); response.setContentType("text/html;charset=utf-8"); Date currentTime=new Date(); SimpleDateFormat formatter_date = new SimpleDateFormat("yyyy-MM-dd"); String date=formatter_date.format(currentTime); DeleteService ds=new DeleteService(); ds.delete("myinfo", date); Paqu pq=new Paqu(); pq.refesh(); } }
SearchServlet.java(也是這階段新建的,用來接收移動端的查找請求)
package Servlet; import java.io.IOException; import java.sql.ResultSet; import java.sql.SQLException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.google.gson.Gson; import Bean.Info; import Dao.DeleteService; import Dao.Get; import Dao.Select; import control.Paqu; import utils.DBUtil; @WebServlet("/SearchServlet")//移動端用到了該Servlet public class SearchServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("request--->"+request.getRequestURL()+"===="+request.getParameterMap().toString()); response.setContentType("text/html;charset=utf-8"); String method = request.getParameter("method"); String date1 = request.getParameter("username"); // 獲取客戶端傳過來的參數,移動端的參數叫username與password,我沒有修改,可以修改為易於理解的date1,date2,但移動端也要對應修改 String date2 = request.getParameter("password"); Get get=new Get(); List<Info> list=null; if(method.equals("province")) {//查詢中國省份疫情數據 list = get.listAll(date1,date2); }else if(method.equals("country")) {//查詢海外疫情數據 list = get.listAll2(date1, date2); } request.setAttribute("list",list); Gson gson = new Gson(); String json = gson.toJson(list); try { response.getWriter().println(json); // 將json數據傳給客戶端 } catch (Exception e) { e.printStackTrace(); } finally { response.getWriter().close(); // 關閉這個流,不然會發生錯誤的 } } }
YqSearch.java(前兩個階段中Web端使用的,移動端沒有調用該Servlet)
package Servlet; import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import com.google.gson.Gson; import Bean.Info; import Dao.DeleteService; import Dao.Get; import Dao.Select; import control.Paqu; /** * Servlet implementation class SearchConfirmedServlet */ @WebServlet("/YqServlet") public class YqServlet extends HttpServlet { private static final long serialVersionUID = 1L; Get get=new Get(); /** * @see HttpServlet#HttpServlet() */ public YqServlet() { super(); // TODO Auto-generated constructor stub } /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String method = request.getParameter("method"); if(method.equals("getAllProvince")) { try { getAllProvince(request, response); } catch (ParseException e) { // TODO Auto-generated catch block e.printStackTrace(); } }else if(method.equals("getAllConfirmed")) { getAllConfirmed(request, response); } } /** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub doGet(request, response); } protected void getAllProvince(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException, ParseException { response.setCharacterEncoding("UTF-8"); Select s=new Select(); Date currentTime=new Date(); SimpleDateFormat formatter_date = new SimpleDateFormat("yyyy-MM-dd"); String date=formatter_date.format(currentTime); String date1 = request.getParameter("date1"); String date2 = request.getParameter("date2"); Date today=formatter_date.parse(date);//將現在的date轉為日期,方便比較 Date date22=formatter_date.parse(date2);//將date2轉為日期,方便比較 if(today.before(date22)) {//如果今天日期today比查詢后邊的date2日期早,需要用到今天的數據 //不管數據庫中有沒有今天的數據,運行到這都需要重新爬取一遍,防止官方更新今日數據 boolean b=s.select(date);//查找數據庫中是否存在今天的數據.............黃色部分可刪除,前面說到了,用不到查詢判斷表中是否有今日數據,直接刪除就好,反正下面會重新爬取存到數據庫 if(b) {//如果有今日數據已存在,先刪除 DeleteService ds=new DeleteService(); ds.delete("myinfo", date); } Paqu pq=new Paqu();//不管數據庫是否存在今日數據都會爬取;如果存在,前面已經刪除過了,這里的爬取就相當於更新了 pq.refesh(); } List<Info> list = get.listAll(date1,date2); request.setAttribute("list",list); request.setAttribute("date1",date1); request.setAttribute("date2",date2); request.getRequestDispatcher("bar.jsp").forward(request, response); } protected void getAllConfirmed(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setCharacterEncoding("UTF-8"); String date1 = request.getParameter("date1"); String date2 = request.getParameter("date2"); System.out.println(date1); System.out.println(date2); List<Info> list = get.listAll(date1,date2); HttpSession session = request.getSession(); session.setAttribute("list",list); Gson gson = new Gson(); String json = gson.toJson(list); response.getWriter().write(json); } }
DBUtil.java
package utils; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class DBUtil { public static String db_url = "jdbc:mysql://localhost:3306/test?useSSL=false&characterEncoding=UTF-8&serverTimezone=GMT";//如果發布到雲服務器就將localhost改為雲服務器的ip public static String db_user = "root"; public static String db_pass = "root"; public static Connection getConn () { Connection conn = null; try { Class.forName("com.mysql.cj.jdbc.Driver"); conn = DriverManager.getConnection(db_url, db_user, db_pass); } catch (Exception e) { e.printStackTrace(); } return conn; } public static void close (Statement state, Connection conn) { if (state != null) { try { state.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } public static void close (ResultSet rs, Statement state, Connection conn) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (state != null) { try { state.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Insert title here</title> <link href="${pageContext.request.contextPath }/bootstrap-3.3.7-dist/css/bootstrap.min.css" rel="stylesheet"> <script src="${pageContext.request.contextPath }/js/jquery-3.3.1.min.js"></script> <script src="${pageContext.request.contextPath }/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script> <style type="text/css"> .skyblue{ background:skyblue; } .pink{ background:pink; } *{ margin:0px; padding:0px; } a{ font-size:15px; } </style> </head> <body> <div class="container"> <form action="YqServlet?method=getAllProvince" method="post"> <div class="row" style="padding-top: 20px"> <div class="col-xs-4"> <h4>起始時間:</h4> <input type="text" class="form-control" name="date1"> </div> <div class="col-xs-4"> <h4>終止時間:</h4> <input type="text" class="form-control" name="date2"> </div> <div class="col-xs-2"> <input type="submit" class="btn btn-default" value="查詢"> </div> </div> </form> </div> </body> </html>
bar.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> <link href="${pageContext.request.contextPath }/bootstrap-3.3.7-dist/css/bootstrap.min.css" rel="stylesheet"> <script src="${pageContext.request.contextPath }/js/jquery.min.js"></script> <script src="${pageContext.request.contextPath }/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script> <script type="text/javascript" src="${pageContext.request.contextPath }/js/echarts.min.js"></script> </head> <script type="text/javascript"> var dt; function getAllConfirmed(){ var date1 = "${date1}"; var date2 = "${date2}"; $.ajax({ url:"YqServlet?method=getAllConfirmed", async:false, type:"POST", data:{"date1":date1, "date2":date2 }, success:function(data){ dt = data; //alert(dt); }, error:function(){ alert("請求失敗"); }, dataType:"json" }); var myChart = echarts.init(document.getElementById('yiqingchart')); var xd = new Array(0)//長度為33 var yd = new Array(0)//長度為33 for(var i=0;i<32;i++){ xd.push(dt[i].province); yd.push(dt[i].confirmed_num); } // 指定圖表的配置項和數據 var option = { title: { text: '全國各省的確診人數' }, tooltip: { show: true, trigger: 'axis' }, legend: { data: ['確診人數'] }, grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }, toolbox: { feature: { saveAsImage: {} } }, xAxis: { type: 'category', boundaryGap: false, axisLabel:{ //橫坐標上的文字斜着顯示 文字顏色 begin interval:0, rotate:45, margin:60, textStyle:{color:"#ec6869" } //橫坐標上的文字換行顯示 文字顏色end }, data: xd }, yAxis: { type: 'value' }, series: [ { name: '確診人數', type: 'bar', stack: '總量', data: yd, barWidth:20, barGap:'10%' } ] }; // 使用剛指定的配置項和數據顯示圖表。 myChart.setOption(option); } </script> <body> <button class="btn btn-default" onclick="getAllConfirmed()" style="padding-top:20px;font-size:20px">柱狀圖</button> <div id="yiqingchart" style="width:900px; height: 600px;"> </div> <table class="table table-striped" style="font-size:20px"> <tr> <td>編號</td> <td>時間</td> <td>省份</td> <td>新增人數</td> <td>確診人數</td> <td>治愈人數</td> <td>死亡人數</td> </tr> <c:forEach items="${list}" var="info"> <tr> <td>${info.id}</td> <td>${info.date}</td> <td>${info.province}</td> <td>${info.newconfirmed_num}</td> <td>${info.confirmed_num}</td> <td>${info.cured_num}</td> <td>${info.dead_num}</td> </tr> </c:forEach> </table> </body> </html>
3.2 移動端
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.example.testnet.MainActivity"> <!-- <EditText--> <!-- android:id="@+id/et_data_uname"--> <!-- android:layout_width="match_parent"--> <!-- android:layout_height="wrap_content"--> <!-- android:hint="請輸入開始時間:"--> <!-- android:text="2020-03-18"/>--> <!-- <EditText--> <!-- android:id="@+id/et_data_upass"--> <!-- android:layout_width="match_parent"--> <!-- android:layout_height="wrap_content"--> <!-- android:hint="請輸入截止時間:"--> <!-- android:text="2020-03-19" />--> <TextView android:layout_width="match_parent" android:layout_height="60dp" android:text="請選擇開始時間" android:textSize="50sp" /> <DatePicker android:id="@+id/et_data_uname" android:layout_width="match_parent" android:layout_height="100dp" android:layout_margin="4dp" android:datePickerMode="spinner" android:calendarViewShown="false"/> <TextView android:layout_width="match_parent" android:layout_height="60dp" android:text="請選擇截止時間" android:textSize="50sp" /> <DatePicker android:id="@+id/et_data_upass" android:layout_width="match_parent" android:layout_height="100dp" android:layout_margin="4dp" android:datePickerMode="spinner" android:calendarViewShown="false"/> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="loginGet" android:text="爬取(只可獲取當天數據)" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="loginPOST" android:text="查詢國內疫情信息" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="loginPOST2" android:text="查詢海外疫情信息" /> </LinearLayout>
content_main.xml(大的表單)
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="wrap_content" android:layout_height="50dp"> <TextView android:layout_width="60dp" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_marginLeft="10dp" android:ellipsize="marquee" android:gravity="center" android:singleLine="true" android:text="省份" android:textSize="20sp" /> <TextView android:id="@+id/tv_date" android:layout_width="95dp" android:layout_height="wrap_content" android:text="時間" android:textSize="20sp" /> <TextView android:id="@+id/tv_confirmed" android:layout_width="55dp" android:layout_height="80dp" android:text="確診" android:textSize="15sp" /> <TextView android:id="@+id/tv_cured" android:layout_width="55dp" android:layout_height="80dp" android:text="治愈" android:textSize="15sp" /> <TextView android:id="@+id/tv_dead" android:layout_width="55dp" android:layout_height="80dp" android:text="死亡" android:textSize="15sp" /> <TextView android:id="@+id/tv_new" android:layout_width="55dp" android:layout_height="80dp" android:text="新增" android:textSize="15sp" /> </LinearLayout> <ListView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/lv_main"/> </LinearLayout>
list_item.xml(顯示具體的一條一條數據)
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/tv_province" android:layout_width="60dp" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_marginLeft="10dp" android:ellipsize="marquee" android:gravity="center" android:singleLine="true" android:text="省份" android:textSize="20sp" /> <TextView android:id="@+id/tv_date" android:layout_width="95dp" android:layout_height="wrap_content" android:text="時間" android:textSize="15sp" /> <TextView android:id="@+id/tv_confirmed" android:layout_width="55dp" android:layout_height="80dp" android:text="67799" android:textSize="20sp" /> <TextView android:id="@+id/tv_cured" android:layout_width="55dp" android:layout_height="80dp" android:text="56002" android:textSize="20sp" /> <TextView android:id="@+id/tv_dead" android:layout_width="55dp" android:layout_height="80dp" android:text="3" android:textSize="20sp" /> <TextView android:id="@+id/tv_new" android:layout_width="55dp" android:layout_height="80dp" android:text="5" android:textSize="20sp" /> </LinearLayout>

只需用到info和兩個activity即可,另外兩個用不到
Info.java
package com.example.testnet; public class Info { private String id; private String city; private String yisi_num; private String date; private String province; private String confirmed_num; private String cured_num; private String dead_num; private String newconfirmed_num; public String getNewconfirmed_num() { return newconfirmed_num; } public void setNewconfirmed_num(String newconfirmed_num) { this.newconfirmed_num = newconfirmed_num; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getYisi_num() { return yisi_num; } public void setYisi_num(String yisi_num) { this.yisi_num = yisi_num; } public String getDate() { return date; } public void setDate(String date) { this.date = date; } public String getProvince() { return province; } public void setProvince(String province) { this.province = province; } public String getConfirmed_num() { return confirmed_num; } public void setConfirmed_num(String confirmed_num) { this.confirmed_num = confirmed_num; } public String getCured_num() { return cured_num; } public void setCured_num(String cured_num) { this.cured_num = cured_num; } public String getDead_num() { return dead_num; } public void setDead_num(String dead_num) { this.dead_num = dead_num; } }
MainActivity.java
package com.example.testnet; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.DatePicker; import androidx.appcompat.app.AppCompatActivity; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.util.HashMap; public class MainActivity extends AppCompatActivity { String TAG = MainActivity.class.getCanonicalName(); // private EditText et_data_uname; // private EditText et_data_upass; private DatePicker et_data_uname; private DatePicker et_data_upass; private HashMap<String, String> stringHashMap; private String t; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); et_data_uname = (DatePicker) findViewById(R.id.et_data_uname); et_data_upass = (DatePicker) findViewById(R.id.et_data_upass); stringHashMap = new HashMap<>(); } public void loginPOST(View view) { stringHashMap.put("username",et_data_uname.getYear()+"-"+(et_data_uname.getMonth()+1)+"-"+et_data_uname.getDayOfMonth()); stringHashMap.put("password", et_data_upass.getYear()+"-"+(et_data_upass.getMonth()+1)+"-"+et_data_upass.getDayOfMonth()); new Thread(postRun).start(); } public void loginPOST2(View view) { stringHashMap.put("username",et_data_uname.getYear()+"-"+(et_data_uname.getMonth()+1)+"-"+et_data_uname.getDayOfMonth()); stringHashMap.put("password", et_data_upass.getYear()+"-"+(et_data_upass.getMonth()+1)+"-"+et_data_upass.getDayOfMonth()); new Thread(postRun2).start(); } public void loginGet(View view) { stringHashMap.put("username", et_data_uname.getYear()+"-"+(et_data_uname.getMonth()+1)+"-"+et_data_uname.getDayOfMonth()); stringHashMap.put("password", et_data_upass.getYear()+"-"+(et_data_upass.getMonth()+1)+"-"+et_data_upass.getDayOfMonth()); new Thread(getRun).start(); } /** * get請求線程 */ Runnable getRun = new Runnable() { @Override public void run() { // TODO Auto-generated method stub requestGet(stringHashMap); } }; /** * post請求線程 */ Runnable postRun = new Runnable() { @Override public void run() { // TODO Auto-generated method stub requestPost(stringHashMap); } }; Runnable postRun2 = new Runnable() { @Override public void run() { // TODO Auto-generated method stub requestPost2(stringHashMap); } }; /** * get提交數據 * * @param paramsMap */ private void requestGet(HashMap<String, String> paramsMap) { try { String baseUrl = "http://10.0.2.2:8080/YiQing/PaquServlet?";//如果發布到雲端,將黃色部分修改為雲服務器ip StringBuilder tempParams = new StringBuilder(); int pos = 0; for (String key : paramsMap.keySet()) { if (pos > 0) { tempParams.append("&"); } tempParams.append(String.format("%s=%s", key, URLEncoder.encode(paramsMap.get(key), "utf-8"))); pos++; } Log.e(TAG,"params--get-->>"+tempParams.toString()); String requestUrl = baseUrl + tempParams.toString(); // 新建一個URL對象 URL url = new URL(requestUrl); // 打開一個HttpURLConnection連接 HttpURLConnection urlConn = (HttpURLConnection) url.openConnection(); // 設置連接主機超時時間 urlConn.setConnectTimeout(5 * 1000); //設置從主機讀取數據超時 urlConn.setReadTimeout(5 * 1000); // 設置是否使用緩存 默認是true urlConn.setUseCaches(true); // 設置為Post請求 urlConn.setRequestMethod("GET"); //urlConn設置請求頭信息 //設置請求中的媒體類型信息。 urlConn.setRequestProperty("Content-Type", "application/json"); //設置客戶端與服務連接類型 urlConn.addRequestProperty("Connection", "Keep-Alive"); // 開始連接 urlConn.connect(); // 判斷請求是否成功 if (urlConn.getResponseCode() == 200) { // 獲取返回的數據 String result = streamToString(urlConn.getInputStream()); Log.e(TAG, "Get方式請求成功,result--->" + result); } else { Log.e(TAG, "Get方式請求失敗"); } // 關閉連接 urlConn.disconnect(); } catch (Exception e) { Log.e(TAG, e.toString()); } } /** * post提交數據 * * @param paramsMap */ private void requestPost(HashMap<String, String> paramsMap) { try { String baseUrl = "http://10.0.2.2:8080/YiQing/SearchServlet?method=province";//如若發布到雲端,將黃色部分修改為雲端ip //合成參數 StringBuilder tempParams = new StringBuilder(); int pos = 0; for (String key : paramsMap.keySet()) { if (pos >0) { tempParams.append("&"); } tempParams.append(String.format("%s=%s", key, URLEncoder.encode(paramsMap.get(key), "utf-8"))); pos++; } String params = tempParams.toString(); Log.e(TAG,"params--post-->>"+params); // 請求的參數轉換為byte數組 // byte[] postData = params.getBytes(); // 新建一個URL對象 URL url = new URL(baseUrl); // 打開一個HttpURLConnection連接 HttpURLConnection urlConn = (HttpURLConnection) url.openConnection(); // 設置連接超時時間 urlConn.setConnectTimeout(5 * 1000); //設置從主機讀取數據超時 urlConn.setReadTimeout(5 * 1000); // Post請求必須設置允許輸出 默認false urlConn.setDoOutput(true); //設置請求允許輸入 默認是true urlConn.setDoInput(true); // Post請求不能使用緩存 urlConn.setUseCaches(false); // 設置為Post請求 urlConn.setRequestMethod("POST"); //設置本次連接是否自動處理重定向 urlConn.setInstanceFollowRedirects(true); //配置請求Content-Type // urlConn.setRequestProperty("Content-Type", "application/json");//post請求不能設置這個 // 開始連接 urlConn.connect(); // 發送請求參數 PrintWriter dos = new PrintWriter(urlConn.getOutputStream()); dos.write(params); dos.flush(); dos.close(); // 判斷請求是否成功 if (urlConn.getResponseCode() == 200) { // 獲取返回的數據 String result = streamToString(urlConn.getInputStream()); Log.e(TAG, "Post方式請求成功,result--->" + result); t=result; Intent intent = new Intent(MainActivity.this,MainActivity2.class); System.out.println(t); intent.putExtra("data",t); startActivity(intent); } else { Log.e(TAG, "Post方式請求失敗"); } // 關閉連接 urlConn.disconnect(); } catch (Exception e) { Log.e(TAG, e.toString()); } } private void requestPost2(HashMap<String, String> paramsMap) { try { String baseUrl = "http://10.0.2.2:8080/YiQing/SearchServlet?method=country";//如若發布到雲端,將黃色部分修改為雲端ip //合成參數 StringBuilder tempParams = new StringBuilder(); int pos = 0; for (String key : paramsMap.keySet()) { if (pos >0) { tempParams.append("&"); } tempParams.append(String.format("%s=%s", key, URLEncoder.encode(paramsMap.get(key), "utf-8"))); pos++; } String params = tempParams.toString(); Log.e(TAG,"params--post-->>"+params); // 請求的參數轉換為byte數組 // byte[] postData = params.getBytes(); // 新建一個URL對象 URL url = new URL(baseUrl); // 打開一個HttpURLConnection連接 HttpURLConnection urlConn = (HttpURLConnection) url.openConnection(); // 設置連接超時時間 urlConn.setConnectTimeout(5 * 1000); //設置從主機讀取數據超時 urlConn.setReadTimeout(5 * 1000); // Post請求必須設置允許輸出 默認false urlConn.setDoOutput(true); //設置請求允許輸入 默認是true urlConn.setDoInput(true); // Post請求不能使用緩存 urlConn.setUseCaches(false); // 設置為Post請求 urlConn.setRequestMethod("POST"); //設置本次連接是否自動處理重定向 urlConn.setInstanceFollowRedirects(true); //配置請求Content-Type // urlConn.setRequestProperty("Content-Type", "application/json");//post請求不能設置這個 // 開始連接 urlConn.connect(); // 發送請求參數 PrintWriter dos = new PrintWriter(urlConn.getOutputStream()); dos.write(params); dos.flush(); dos.close(); // 判斷請求是否成功 if (urlConn.getResponseCode() == 200) { // 獲取返回的數據 String result = streamToString(urlConn.getInputStream()); Log.e(TAG, "Post方式請求成功,result--->" + result); t=result; Intent intent = new Intent(MainActivity.this,MainActivity2.class); System.out.println(t); intent.putExtra("data",t); startActivity(intent); } else { Log.e(TAG, "Post方式請求失敗"); } // 關閉連接 urlConn.disconnect(); } catch (Exception e) { Log.e(TAG, e.toString()); } } /** * 將輸入流轉換成字符串 * * @param is 從網絡獲取的輸入流 * @return */ public String streamToString(InputStream is) { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len = 0; while ((len = is.read(buffer)) != -1) { baos.write(buffer, 0, len); } baos.close(); is.close(); byte[] byteArray = baos.toByteArray(); return new String(byteArray); } catch (Exception e) { Log.e(TAG, e.toString()); return null; } } /** * 文件下載 * * @param fileUrl */ private void downloadFile(String fileUrl) { try { // 新建一個URL對象 URL url = new URL(fileUrl); // 打開一個HttpURLConnection連接 HttpURLConnection urlConn = (HttpURLConnection) url.openConnection(); // 設置連接主機超時時間 urlConn.setConnectTimeout(5 * 1000); //設置從主機讀取數據超時 urlConn.setReadTimeout(5 * 1000); // 設置是否使用緩存 默認是true urlConn.setUseCaches(true); // 設置為Post請求 urlConn.setRequestMethod("GET"); //urlConn設置請求頭信息 //設置請求中的媒體類型信息。 urlConn.setRequestProperty("Content-Type", "application/json"); //設置客戶端與服務連接類型 urlConn.addRequestProperty("Connection", "Keep-Alive"); // 開始連接 urlConn.connect(); // 判斷請求是否成功 if (urlConn.getResponseCode() == 200) { String filePath = "";//下載文件保存在本地的地址 File descFile = new File(filePath); FileOutputStream fos = new FileOutputStream(descFile); ; byte[] buffer = new byte[1024]; int len; InputStream inputStream = urlConn.getInputStream(); while ((len = inputStream.read(buffer)) != -1) { // 寫到本地 fos.write(buffer, 0, len); } } else { Log.e(TAG, "文件下載失敗"); } // 關閉連接 urlConn.disconnect(); } catch (Exception e) { Log.e(TAG, e.toString()); } } /** * 文件上傳 * * @param filePath * @param paramsMap */ private void upLoadFile(String filePath, HashMap<String, String> paramsMap) { try { String baseUrl = "http://xxx.com/uploadFile"; File file = new File(filePath); //新建url對象 URL url = new URL(baseUrl); //通過HttpURLConnection對象,向網絡地址發送請求 HttpURLConnection urlConn = (HttpURLConnection) url.openConnection(); //設置該連接允許讀取 urlConn.setDoOutput(true); //設置該連接允許寫入 urlConn.setDoInput(true); //設置不能適用緩存 urlConn.setUseCaches(false); //設置連接超時時間 urlConn.setConnectTimeout(5 * 1000); //設置連接超時時間 //設置讀取超時時間 urlConn.setReadTimeout(5 * 1000); //讀取超時 //設置連接方法post urlConn.setRequestMethod("POST"); //設置維持長連接 urlConn.setRequestProperty("connection", "Keep-Alive"); //設置文件字符集 urlConn.setRequestProperty("Accept-Charset", "UTF-8"); //設置文件類型 urlConn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + "*****"); String name = file.getName(); DataOutputStream requestStream = new DataOutputStream(urlConn.getOutputStream()); requestStream.writeBytes("--" + "*****" + "\r\n"); //發送文件參數信息 StringBuilder tempParams = new StringBuilder(); tempParams.append("Content-Disposition: form-data; name=\"" + name + "\"; filename=\"" + name + "\"; "); int pos = 0; int size = paramsMap.size(); for (String key : paramsMap.keySet()) { tempParams.append(String.format("%s=\"%s\"", key, paramsMap.get(key), "utf-8")); if (pos < size - 1) { tempParams.append("; "); } pos++; } tempParams.append("\r\n"); tempParams.append("Content-Type: application/octet-stream\r\n"); tempParams.append("\r\n"); String params = tempParams.toString(); requestStream.writeBytes(params); //發送文件數據 FileInputStream fileInput = new FileInputStream(file); int bytesRead; byte[] buffer = new byte[1024]; DataInputStream in = new DataInputStream(new FileInputStream(file)); while ((bytesRead = in.read(buffer)) != -1) { requestStream.write(buffer, 0, bytesRead); } requestStream.writeBytes("\r\n"); requestStream.flush(); requestStream.writeBytes("--" + "*****" + "--" + "\r\n"); requestStream.flush(); fileInput.close(); int statusCode = urlConn.getResponseCode(); if (statusCode == 200) { // 獲取返回的數據 String result = streamToString(urlConn.getInputStream()); Log.e(TAG, "上傳成功,result--->" + result); } else { Log.e(TAG, "上傳失敗"); } } catch (IOException e) { Log.e(TAG, e.toString()); } } }
MainActivity2.java(當點擊查詢后,會從MainActivity跳轉到該MainActivity2)
package com.example.testnet; import android.content.Intent; import android.os.Bundle; import android.widget.ListView; import android.widget.SimpleAdapter; import androidx.appcompat.app.AppCompatActivity; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class MainActivity2 extends AppCompatActivity { private List<Info> list ; private YqAdapter mAdapter; Info yq=new Info(); int n=0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.content_main); Intent m =getIntent(); String result=m.getStringExtra("data"); List<Map<String, Object>> listitem = new ArrayList<Map<String, Object>>(); String[] province=new String[1000]; String[] date=new String[1000]; String[] now=new String[1000]; String[] cured=new String[1000]; String[] dead=new String[1000]; String[] today=new String[1000]; try { JSONArray json = new JSONArray(result); for (int i = 0;i<json.length();i++){ JSONObject jb=json.getJSONObject(i); date[i]=jb.getString("date"); province[i]=jb.getString("province"); now[i]=jb.getString("confirmed_num"); cured[i]=jb.getString("cured_num"); dead[i]=jb.getString("dead_num"); today[i]=jb.getString("newconfirmed_num"); n=i+1; } } catch (JSONException e) { e.printStackTrace(); } for (int i = 0; i <n; i++) { Map<String,Object> map = new HashMap<String, Object>(); map.put("province",province[i]); map.put("date",date[i]); map.put("now",now[i]); map.put("cured",cured[i]); map.put("dead",dead[i]); map.put("today",today[i]); listitem.add(map); } // for (int i=0;i<n;i++){ // System.out.println(province[i]); // } SimpleAdapter adapter = new SimpleAdapter(this , listitem , R.layout.list_item , new String[]{"province","date","now","cured","dead","today"} ,new int[]{R.id.tv_province,R.id.tv_date,R.id.tv_confirmed,R.id.tv_cured,R.id.tv_dead,R.id.tv_new}); ListView listView =(ListView) findViewById(R.id.lv_main); listView.setAdapter(adapter); } }
4、移動端與Web端的交互(上面的代碼已經實現了交互)
移動端只是向Web端發送請求,調用相應的Servlet,一些復雜的運算還是ecplise中進行完成的。
剛開始可以參考該篇博客了解交互過程https://blog.csdn.net/qq_34317125/article/details/80533685
5、雲服務器配置與部署
可以參考我的另一篇博客https://www.cnblogs.com/xhj1074376195/p/12318178.html
新獲得的雲服務要像一台新電腦一樣,同樣需要下載配置jdk,mysql,Tomcat....
6、運行測試結果
Web端


移動端



7、開發過程中出現的問題
1.在Android Studio中將localhost:8080修改為雲端服務器ip時,沒有把:8080去掉,導致連接不上雲端服務器。
2.Listview的布局中,沒有弄清楚綁定的是哪一個頁面,導致一直報空指針,應該修改為ListView控件所在的xml頁面。
3.在AndroidMainifresh.xml中添加聯網設置
<uses-permission android:name="android.permission.INTERNET" />
4.新建一個MainActivity2.java之后,在AndroidMainifest.xml中添加Activity活動
<activity android:name=".MainActivity2"/>
5.在虛擬機上可以正常運行,但打包apk至真機無法運行,請教同學以及百度后得知,Android 9.0之后 的應用程序,將要求默認使用加密連接,這意味着 Android P(9.0) 將禁止 App 使用所有未加密的連接,因此運行 Android P(9.0) 系統的安卓設備無論是接收或者發送流量,未來都不能明碼傳輸,需要使用下一代(Transport Layer Security)傳輸層安全協議,而 Android Nougat 和 Oreo 則不受影響。簡單來說就是,系統為了安全起見,Android 9.0之后禁止使用不加密的連接。解決辦法,在AndroidMainifresh.xml中<application></application>下添加語句允許不加密連接
android:usesCleartextTraffic="true"
8、PSP0級時間記錄日志

9、資源分享
項目所需要的jar包 鏈接:https://pan.baidu.com/s/1z4eqP3Gpa0EqudogM-a-lw 提取碼:tr06
疫情數據顯示Web端源代碼已上傳至GitHub https://github.com/xhj1074376195/YiQing_Web
疫情數據顯示移動端源代碼已上傳至GitHub https://github.com/xhj1074376195/YiQing_App
