移动端与Web端疫情数据展示
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