目录
一、内容介绍
本次大作业主要使用JSoup和鸿蒙开发来实现一个教务查询软件。软件分为两个部分:教务查询和个人中心。用户只需要知道自己的学号、教务系统密码以及内网VPN密码,无论是否使用校园网,都能登录该应用。教务查询部分全部用JSoup实现并展示在鸿蒙手机应用中,该部分包括成绩查询、考试查询、GPA查询以及成绩总表查询四个功能。个人中心部分包含用户的个人信息以及退出登录等操作。
二、工具介绍
JSoup是一款Java的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据。本次大作业利用JSoup来模拟登录进而爬取教务系统内的各种信息,获取到的成绩等信息将在鸿蒙手机应用上进行展示。
三、JSoup处理细节
1.需求分析
场景:需要访问教务系统,爬取出各种个人教务信息,并在自己所写的手机应用上进行展示。由于访问教务系统需要连接校园网,为了方便用户在校外也能使用本应用,所以本次爬取采用了“内网-教务系统”的两级爬取策略,即先模拟登录校园内网,然后携带内网cookies登录教务系统,最终爬取出相关信息。
2.模拟登录内网
内网登录界面如图1所示:
主要登录步骤如下:
- 在登录界面输入用户名以及登录密码,按下F12,并在Elements中搜索action,查找表单数据最终被提交到哪里,如图2所示:
可以看到,输入的表单数据最终被提交到了"/users/sign_in"里。 - 点击登录,在Network里面找到sign_in,可以看到模拟登录需要的各种信息,如图3所示:
- 写代码模拟登录。
3.模拟登录教务系统
模拟登录进入到了内网界面后,点击教务系统,进入到教务系统的登录界面,利用同样的方式获取模拟登录所需的信息,登录到教务系统中。
四、界面
1.登录界面
登录界面如图4所示:
用户输入自己的学号、教务系统密码以及内网VPN密码后,点击Login按钮,即可成功登录。登录后,鸿蒙开发提供的工具包Preferences将自动保存用户的所有登录信息,信息存储在用户的SD卡中。当再次打开APP时,系统将自动登录。
2.查询主界面
查询主界面如图5所示:
在查询主界面,利用JSoup爬取到的网页信息设计了多个查询功能,下面将依次介绍。
2.1 成绩查询
点击查询主界面的“成绩查询”按钮,进入到成绩查询界面,点击学年、学期以及查询性质三个按钮,会弹出相应选项,如图6所示:
学年、学期以及课程性质都选择完毕后,点击查询按钮,结果如图7所示:
成绩信息中只包含课程名和成绩分数两项信息。点击其中任意一条成绩信息,随后会弹出该门课程更加详细的信息,如图8所示:
详细信息中包括课程性质、考试性质以及开课学院等信息。
2.2 考试安排
点击查询主界面的“考试安排”按钮,进入到考试安排查询界面,选择学年、学期选项,然后点击查询,结果如图9所示:
考试信息中只包括考试科目以及考试地点两项信息。任意点击其中一条信息,会弹出更加详细的考试信息,如图10所示:
详细信息中包括考试时间、教学班组成以及考试校区等信息。
2.3 GPA查询
点击查询主界面的“GPA”按钮,进入到GPA查询界面。选择学年、学期以及课程性质三个选项,然后点击查询,结果如图11所示:
可以看出该名学生2019-2020学年第2学期的GPA是4.37。
2.4 成绩总表
点击查询主界面的“成绩总表”按钮,进入到成绩总表查询界面,结果如图12所示:
成绩总表中包含该用户截止目前的所有学分以及课程的详细情况。
3.个人中心界面
个人中心界面显示了用户的个人信息,所有信息均通过JSoup爬取教务系统网页获得。当用户在登录界面点击登录后,获取到的用户个人信息将用于个人中心界面的初始化操作。用户登录成功后,个人中心界面如图13所示:
用户在个人中心界面可以完成查看个人信息、修改个性签名、查看作者博客、退出登录等操作。
3.1 个性签名
点击个人中心界面的“个性签名”按钮,进入到个性签名修改界面,如图14所示:
重新输入个性签名后,点击确定,即可成功修改。如果用户并未改变原有的内容,点击返回键可正常返回,否则系统会提示用户点击“确认”键来进行修改操作。修改后的个性签名如图15所示:
3.2 作者博客
点击个人中心界面的“作者博客”按钮,即可进入到开发者的CSDN博客主页,如图16所示:
3.3 退出登录
点击个人中心界面的“退出登录”按钮,会弹出一个提示窗口,如图17所示:
点击确认后,系统将清除掉用户的所有数据并跳转到登录界面(图4),重新进行登录。
五、附录
完整代码地址:代码,原创不易,请随手给个follow和star,感谢!!!
1.登录界面处理逻辑
package com.example.ncepu;
import com.example.ncepu.JWUtils.ConnectJWGL;
import com.example.ncepu.Utils.PreferenceUtils;
import com.example.ncepu.Utils.TimeUtils;
import com.example.ncepu.slice.MainAbilitySlice;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.content.Intent;
import ohos.aafwk.content.Operation;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.Button;
import ohos.agp.components.TextField;
import ohos.agp.utils.Color;
import ohos.agp.window.dialog.ToastDialog;
import ohos.app.Context;
import ohos.data.DatabaseHelper;
import ohos.data.preferences.Preferences;
import java.util.Map;
public class MainAbility extends Ability {
public static ConnectJWGL connectJWGL;
private Button login;
private TextField textId, textPwd1, textPwd2;
private Preferences preferences;
private DatabaseHelper databaseHelper;
public static Context mcontext;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setMainRoute(MainAbilitySlice.class.getName());
super.setUIContent(ResourceTable.Layout_ability_main);
mcontext = getContext();
initViews();
}
public void initViews() {
login = (Button) findComponentById(ResourceTable.Id_main_btn_login);
textId = (TextField) findComponentById(ResourceTable.Id_usersid);
textPwd1 = (TextField) findComponentById(ResourceTable.Id_password);
textPwd2 = (TextField) findComponentById(ResourceTable.Id_password1);
login.setClickedListener(component -> {
if(!TimeUtils.isFastClick()) {
new ToastDialog(getContext()).setText("正在处理,请不要频繁点击!").show();
return;
}
//设置颜色
login.setTextColor(new Color(Color.getIntColor("#7adfb8")));
String _id= textId.getText();
String password=textPwd1.getText().toString();
String password1 = textPwd2.getText().toString();
if(_id.equals("")) {
new ToastDialog(getContext()).setText("学号不能为空").show();
}else if(password.equals("")) {
new ToastDialog(getContext()).setText("教务系统密码不能为空!").show();
}else if(password1.equals("")) {
new ToastDialog(getContext()).setText("内网密码不能为空!").show();
}else {
new Thread(() -> {
try{
login(_id, password1, password);
}catch(Exception e){
e.printStackTrace();
}
}).start();//线程启动
}
});
}
private void login(String _id, String password1, String password) {
try {
connectJWGL = new ConnectJWGL(_id, password1, password, MainAbility.this);
//内网登录密码错误,登录失败
if(connectJWGL.init() == 0) {
new ToastDialog(getContext()).setText("内网密码错误!").show();
// runOnUiThread(() -> ToastUtil.showMessage(MainActivity.this, "内网密码错误!"));
}else {
if(MainAbility.connectJWGL.beginLogin()) {
Map<String, String> info_map = connectJWGL.getStudentInformation();
PreferenceUtils.getInstance().putString("id", _id);
PreferenceUtils.getInstance().putString("in_net", password1);
PreferenceUtils.getInstance().putString("sign", "Hungry And Humble");
PreferenceUtils.getInstance().putString("jw_password", password);
PreferenceUtils.getInstance().putString("name", info_map.get("name"));
PreferenceUtils.getInstance().putString("stu_id", info_map.get("id"));
PreferenceUtils.getInstance().putString("class", info_map.get("class"));
PreferenceUtils.getInstance().putString("major", info_map.get("major"));
PreferenceUtils.getInstance().putString("sex", info_map.get("sex"));
PreferenceUtils.getInstance().putString("dept", info_map.get("dept"));
PreferenceUtils.getInstance().putString("year", info_map.get("year"));
String cookies = connectJWGL.cookies.toString();
String cookies_in = connectJWGL.cookies_innet.toString();
PreferenceUtils.getInstance().putString("cookies", cookies);
PreferenceUtils.getInstance().putString("cookies_in", cookies_in);
// preferences.flush();
Intent intent = new Intent();
Operation operation = new Intent.OperationBuilder()
.withDeviceId("")
.withBundleName("com.example.ncepu")//这个必须填包名
.withAbilityName("com.example.ncepu.Student.StudentMainAbility")//这个必须填包名+类名
.build();
intent.setOperation(operation);
// 通过AbilitySlice的startAbility接口实现启动另一个页面
startAbility(intent);
// runOnUiThread(() -> ToastUtil.showMessage(MainActivity.this, "登录成功!"));
}else {
new ToastDialog(getContext()).setText("教务系统密码错误!").show();
// runOnUiThread(() -> ToastUtil.showMessage(MainActivity.this, "教务系统密码错误!"));
}
}
} catch (Exception e) {
// runOnUiThread(() -> ToastUtil.showMessage(MainActivity.this, "内网连接超时,请检查网络是否正常连接或内网是否能正常访问!"));
new ToastDialog(getContext()).setText("内网连接超时,请检查网络是否正常连接或内网是否能正常访问!").show();
e.printStackTrace();
}
}
}
2.JSoup模拟登录
package com.example.ncepu.JWUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.example.ncepu.Utils.Exam;
import ohos.app.Context;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import java.util.*;
import com.example.ncepu.Utils.Grade;
public class ConnectJWGL {
public Map<String,String> cookies_innet;
Context mContext;
private final String url = "***";
public Map<String,String> cookies = new HashMap<>();
private String modulus;
private String exponent;
private String csrftoken;
private Connection connection;
private Connection.Response response;
private Document document;
private String stuNum;
private String jw_Password;
private String in_Password;
public ConnectJWGL(String stuNum, String in_Password, String jw_Password, Context mContext) throws Exception {
this.stuNum = stuNum;
this.in_Password = in_Password;
this.jw_Password = jw_Password;
this.mContext = mContext;
Login_Innet login_innet = new Login_Innet(stuNum, in_Password);
cookies_innet = login_innet.in_net();
}
//登录
public boolean beginLogin() throws Exception{
connection = Jsoup.connect(url+ "/jwglxt/xtgl/login_slogin.html").cookies(cookies_innet);
//connection.header("Content-Type","application/x-www-form-urlencoded;charset=utf-8");
connection.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36");
connection.header("Connection", "keep-alive");
connection.data("csrftoken",csrftoken);
// connection.data("language", "zh_CN");
connection.data("yhm",stuNum);
connection.data("mm", jw_Password);
connection.data("mm", jw_Password);
response = connection.cookies(cookies).ignoreContentType(true).followRedirects(true)
.method(Connection.Method.POST).timeout(60000).execute();
// response = connection.execute();
System.out.println("登录后的cookies为:" + response.cookies().get("JSESSIONID"));
Map<String, String> subCookies = new HashMap<>();
subCookies.put("JSESSIONID", response.cookies().get("JSESSIONID"));
System.out.println("subCookies为:" + subCookies);
cookies = subCookies;
document = Jsoup.parse(response.body());
// System.out.println("登录后的界面为:" + document);
if(document.getElementById("tips") == null){
System.out.println("教务系统密码正确");
return true;
}else{
System.out.println(document.getElementById("tips").text());
return false;
}
}
}