声明:笔者并非专业前端开发者,只有十分基础的jsDOM操作,所以有些写的不好的地方,敬请见谅!
本人所在学校:浙江经济职业技术学院
开发面向的教务管理系统:校内信息服务网
官方开发者文档:开发者文档
这里声明我是从 2020/11/21 开始进行小爱课程表的开发,主要功能就是实现导入本校的教务系统课程表。今天(2020/11/22)完成并自测通过了,就等审核通过上线啦。现在就来记录一下这次开发。
下面我们就来详细盘一盘开发过程中的需要注意的地方,以及一些我的思路。
开发之前我们要明确使用什么样的语言开发?
答案当然使用Javascript,我们需要从网页上获取到课程表的HTML代码,理所当然使用JS对其进行解析
开发者工具:AlSchedule Devtools (浏览器插件)
使用方式:
将下载的ZIP压缩包解压
浏览器地址栏中输入 Edge浏览器 --> edge://extensions/ Chrome浏览器 --> chrome://extensions/
在浏览器中打开开发者选项,导入AISchedule DevTools文件夹
OK,到这里我们的开发环境就准备好了。
在Chrome打开一个新的Tab,打开、登陆自己的教务系统,进入课程页面(比如下图展示课程表)。
然后在网页内右键「检查」或者按下F12,打开Chrome开发者工具,会有新增的AISchedule标签,进入后请跟随新手引导,创建「适配项目」。
注意:在创建项目时,教务系统URL一栏要填写的是:你的教务系统的登录界面首页,而不是访问你的课程表的url。这点很重要,因为再后期的E2E自测时,手机会根据你给的URL自动访问教务系统,需要你的手动登录,否则是获取不了你的课程表的。
当你在创建好适配项目后,工具会为你创建一个默认的代码模板,分别是两个函数 provider/parser
provider函数:是是用来获取html的函数,将获取到的html传给 Parser 进行数据处理
输入参数:iframeContent = "",frameContent = "",dom = document(是当前网页的html代码)
返回结果:格式必须是字符串,该结果会被传递给parser函数进行解析
parser函数:获取provider提取的html,从中截器对应的课程表信息,再封装为规定的json格式数据返回
输入参数:html,是provider函数的返回值,是包含了所有的课程信息的字符串
返回结果:是json格式的字符串,scheduleHtmlParser函数的输出须符合以下数据结构
{
"courseInfos": [
{
"name": "数学",
"position": "教学楼1",
"teacher": "张三",
"weeks": [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20
],
"day": 3,
"sections": [
{
"section": 2,
"startTime": "08:00",//可不填
"endTime": "08:50"//可不填
}
],
},
{
"name": "语文",
"position": "基础楼",
"teacher": "荆州",
"weeks": [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20
],
"day": 2,
"sections": [
{
"section": 2,
"startTime": "08:00",//可不填
"endTime": "08:50"//可不填
},
{
"section": 3,
"startTime": "09:00",//可不填
"endTime": "09:50"//可不填
}
],
}
],
"sectionTimes": [
{
"section": 1,
"startTime": "07:00",
"endTime": "07:50"
},
{
"section": 2,
"startTime": "08:00",
"endTime": "08:50"
},
{
"section": 3,
"startTime": "09:00",
"endTime": "09:50"
},
{
"section": 4,
"startTime": "10:10",
"endTime": "11:00"
},
{
"section": 5,
"startTime": "11:10",
"endTime": "12:00"
},
{
"section": 6,
"startTime": "13:00",
"endTime": "13:50"
},
{
"section": 7,
"startTime": "14:00",
"endTime": "14:50"
},
{
"section": 8,
"startTime": "15:10",
"endTime": "16:00"
},
{
"section": 9,
"startTime": "16:10",
"endTime": "17:00"
},
{
"section": 10,
"startTime": "17:10",
"endTime": "18:00"
},
{
"section": 11,
"startTime": "18:40",
"endTime": "19:30"
},
{
"section": 12,
"startTime": "19:40",
"endTime": "20:30"
},
{
"section": 13,
"startTime": "20:40",
"endTime": "21:30"
}
]
}
注意:这里的两个函数除了函数名以外都是可以编辑的,但是由于不了解其中的运行方式,所以我建议不改动函数名及其参数,只编辑其函数体实现功能。
还需要注意的一点就是:scheduleHtmlParser函数:输入课程页面的HTML字符串,提取课程信息,按约定的格式输出JSON。因为这部分是在服务端解析的,用到了cheerio的环境,所以其中不包含document和window对象!
OK,到这里我们就可以开始编程实践了!
这里provider函数需要我们先对网页进行解析,获取到对应所需的课程表内信息,传递给下一个函数
首先我先去看了我的课程表网页的源代码。
发现课程表的网页代码是被嵌入在了当前网页的一个 iframe标签中,并且该标签有一个 ID属性,id=“iframeautoheight”。
并且课程表是在当前网页的子网页的一个 table标签 中,并且该标签也有一个 ID属性,id=“Table1”。
考虑到在 parser函数 中不能使用document和window对象,就需要使用 cheerio 进行解析,但是笔者只会一点简单的JS,所以我决定不再 parser函数 中进行解析,将解析操作全部放在 provider函数 中完成,返回处理好的结果(自定义结构)。
所以,我先在 provider函数 完成所有解析,取出网页课程表中所有的课程信息,组成一个字符长串,使用 }{ 作为每个课程信息的分隔符,最后返回这个字符串交给下一个函数
function scheduleHtmlProvider(iframeContent = "", frameContent = "", dom = document) {
//解析后的课程表
table1 = dom.querySelector("#iframeautoheight").contentWindow.document.querySelector("#Table1")
//考虑到在 parser函数中解析比较困难,所以我们现在 本函数中解析好后传递给parser
//约定格式为 }{ 区分不同的td中的文本
courseInfoString = ""
all_td_tags = table1.getElementsByTagName("td");
for( var j=0 ; j<all_td_tags.length ; j++){
tdContext = all_td_tags[j].innerHTML
//判断 td标签中的信息是否含有 br标签,不含有就不是课程信息
if(tdContext.indexOf("<br>") == -1){
continue;
}
courseInfoString = courseInfoString + tdContext + "}{"
}
return courseInfoString
}
执行函数后弹出了的窗口包括了课程表信息,就代表截取成功啦!如下图
parser函数,获取到 provider 解析后得到的课程信息字符串(课程信息使用 }{ 进行分隔),所以获取到首先就使用进行分离得到一个包含粗略课程信息的数组(contextArry)。
但是这并不是最终的所有课程信息,因为有些课程会有停课周,以及换课周,所以后续还要进行更细致的分割,最终获得稍详细的课程信息数组(InfoArry)。
根据函数返回数据结构可知,除了要返回课程信息数组(courseInfos),还要返回的是包含每一节课上课信息的数组(sectionTimes),
因此我们先创建一个存储一天中每节课信息的数组(sectionTimes),并将其组合完整。
接下来就要解析获得的为全部解析的课程信息数组了,循环数组,根据每个数组中每个元素进行解析,提取出每一个课程所需的信息,组成一个课程对象,并追加到要返回的课程信息数组中。
注意:这里需要注意几个十分忽略的点
function scheduleHtmlParser(html) {
// contextArry 存放 td标签 中获得内容,即上课信息
contextArry = html.split("}{");
//将表格中解析得到的所有信息都存储在一个数组中
InfoArry = new Array();
//定义一个数组,设置每节课对应的开始结束时间
courseStartTime = ["","08:10","08:55","09:40","10:25","11:10","13:40","14:25","15:10","15:55","16:40","18:00","18:45","19:30"];
courseEndTime = ["","08:50","09:35","10:20","11:05","11:50","14:20","15:05","15:50","16:35","17:20","18:40","19:25","20:10"];
//定义星期数组
weekArry = {"周一" : 1,"周二" : 2,"周三" : 3,"周四" : 4,"周五" : 5,"周六" : 6,"周日" : 7};
//定义课时信息,并赋值
sectionTimes = [];
for(var i = 1 ; i<=13 ; i++){
sectionTimes.push({"section" : i , "startTime" : courseStartTime[i] , "endTime" : courseEndTime[i]});
};
//定义要返回的结果信息
result = new Array();
//只需要遍历 上一个函数传过来的 字符串进行分割后的数组
for(var t=0 ; t<contextArry.length-1 ; t++){
if(contextArry[t].indexOf("<br><br>") == -1){
InfoArry.push(contextArry[t]);
}else{
//注意这里会有 换课的课程 也就是出现 <br><br> 但是后面就没有了的情况,所以需要判断
context_split = contextArry[t].split("<br><br>")
InfoArry.push(context_split[0]);
for(var f=2 ; f<context_split.length ; f++){
if(context_split[f].length > 0 & context_split[f].indexOf("</font>") == -1){
InfoArry.push(context_split[f]);
}
}
}
}
//解析获取到的所有课程信息
for(var i = 0 ; i < InfoArry.length ; i++){
courseInfo = InfoArry[i].split("<br>");
// ["分布式存储计算系统", "周二第1,2节{第2-17周}", "王义勇", "信息科学楼8-4"]
name = courseInfo[0];
position = courseInfo[3];
teacher = courseInfo[2];
day = weekArry[courseInfo[1].substring(0,2)];
//对上课时间进行截取 得到的只有 上课时间和持续的周数
courseInfo[1] = courseInfo[1].substring(2,courseInfo[1].length-1); //第1,2节{第2-17周
str1 = courseInfo[1].split("{")[0]; //第1,2节
str2 = courseInfo[1].split("{")[1]; //第2-17周
str1 = str1.substring(1,str1.length-1); //1,2
str2 = str2.substring(1,str2.length-1); //2-17
arry1 = str1.split(","); // {"1", "2"}
arry2 = str2.split("-"); // {"2", "17"} 当该数组最后存在 -1/-2 时,表示该课程分单/双周
weeks = [];
sections = [];
// arry1(上课的节数,如:1,2) 转化数组内的值类型 (字符串 > 数字),并根据数组中的内容 找对应的 sections
for(var a=0 ; a<arry1.length ; a++){
arry1[a] = parseInt(arry1[a])
sections.push(sectionTimes[arry1[a]-1])
}
arry2[0] = parseInt(arry2[0])
// arry2(上课的周数 如:2-5) 转化数组内的值类型 (区分单双周)
for(var b=0 ; b<arry2.length ; b++){
if(arry2[b].length > 2 & b == 1){
var temp_str = arry2[b]
arry2[b] = parseInt(temp_str.substring(0,2));
if(temp_str.substring(2).split("|")[1] == "双"){
arry2[b+1] = -2;
}else{
arry2[b+1] = -1;
}
}else{
arry2[b] = parseInt(arry2[b])
}
}
//循环 arry2 ,获取要上课的周
for(var c=arry2[0] ; c <= arry2[1] ; c++){
//区分单双周
if(arry2.length > 2 ){
//单周
if(arry2[2] == -1 & c%2 == 1 ){
weeks.push(c);
}
//双周
if(arry2[2] == -2 & c%2 == 0){
weeks.push(c);
}
}else{
weeks.push(c);
}
}
course = {"name" : name , "position" : position , "teacher" : teacher , "weeks" : weeks , "day" : day, "sections" : sections};
result.push(course);
};
return { courseInfos: result , sectionTimes : sectionTimes}
}
执行函数后弹出了的窗口包括了正确的格式的返回值信息,并且控制台输出 All run Successfully,就代表截取成功啦!如下图
OK,到这里说明以上两个函数都运行没有问题了,这样可以提交了(需要先登录哦)!
在提交完代码后,在插件左侧就会出现一个刚提交的代码版本号,并显示为 E2E自测
接下就可以到手机端进行自测了,打开vControl选项
在手机端打开课程表的教务导入功能,搜索学校,选择自己提交的适配,你可亲自体验,验证可用性。如果你觉得没问题,请点击反馈按钮「完美」,至此,你的适配已完成,状态为审核中。
如果未导入成功,请查看右下角vControl中的log->info,核对返回值:
到这里如果自测通过那么开发就算成功了,反馈是 完美 ,程序会自动提交给进行内部审核,审核通过后就可以发布使用啦!
接下来就只要坐等审核通过上线就好了!(生怕有bug又打回来)
提交审核时间:2020-11-22 16:39:12 星期日
。。。。。。
测试通过了,啊哈哈哈!项目已经上线,可以正式面向全校师生使用了。
现在使用小爱课程表,选择学校:【 浙江经济职业技术学院-教务管理 】,找到自己的课表,点击【 一键导入 】,就可以使用了 !
好了,今天的分享到这里就结束了,希望此刻的你会对 小爱课程表 有更深入的了解。
如果有什么问题的话可以在下方留言哦(留言我可能不容易看到,也可以私信给我)。
欢迎关注我的个人博客!2020-11-23 15:04:28 星期一
Name | Gender | Admin | |
---|---|---|---|
姜家伟 | 男 | jiawei15214742755@163.com | 是 |
Hello Beautiful World!
未来会怎么样,没有必要过多去想象,一路走下去才会知道。
改变命运的,并不是所谓的机遇,而是你我对人生的态度。
笑面人生,会发现哪里都是阳光灿烂;笑面困难,会发现困难并非难已克服。
每天清晨,保持乐观积极的态度,迎着阳光,一路向前!
评论