本章主要讲述如何通过花式手段(代理请求)登录自己学校的正方教务系统,如果看到这篇文章的你学校也是正方教务系统,也是可以尝试的。
技术栈:
前端:小程序
后端:node
前提:
1.学校使用的是正方教务系统。
2.本例子只是提供基础结构和大致流程,具体的学校系统间的鉴权逻辑需要自己处理一下,一般是cookie,refer等条件需要着重注意一下。
准备工作
- 通过终端ping通学校教务系统,获得IP,这个步骤是为了后面在请求中保证验证码正确做准备。
ping xx.xxxx.cn注:需要去掉http://或者https:// - 适当的对node有了解
- 会自行建立项目,安装对应的模块(
cheerio,superagent,charset) - 如果2和3都不会,扫下面二维码公众号私我,我教你。
// 大致步骤如下
npm init // 创建项目
npm i cheerio
npm i superagent
npm i charset
分析
我先来总结一下流程:进入教务系统=>找到需要的参数=>找到请求接口=>注意观察信息=>获得结果
用户名、密码、验证码、身份],身份是默认值,前期可以选择先不传,所以最后要传的就是[用户名、密码、验证码]
验证码的问题
用户名和密码我们可以通过input标签输入,但是验证码我们应该如何拿到呢,F12打开控制台,从标签中找到验证码,验证码的url是.aspx结尾。
此时就不能按常规图片链接方式处理了,我们可以这样理解,首先这个验证码肯定是一个动态地址。那么我们如何获得这个动态地址,我们有教务系统的链接,xxx.xxx.xxx/(xxxxxxx)/default.aspx,做一种假设,我把验证码上的xx.aspx放在链接的后面,是不是就可以了?
尝试一下,发现ok。这样就可以把链接和xx.aspx做一个替换放在img的url上。这样就可以获取到验证码。现在我们先不输入点一下登录,看看会不会有隐藏信息。
果然不少,那么接下来通过常规方式进入教务系统,看看都有哪些被填写了。
经过测试发现[__VIEWSTATE,txtUserName,TextBox2,txtSecretCode,RadioButtonList1]这几项是必填的,后四项其实就是表单里的数据,那么这个__VIEWSTATE是哪里来的,通过标签搜索我发现它是一个标签里的value值。
登录
这一块其实主要就是界面和接口的实现了,由于之前的分析我们已经有了一个大体的思路。 实现思路: onLoad(根据教务系统ip获得url地址) => 输入用户信息和验证码 => 请求学校的登录接口
- 获取验证码API
const hostUrl = '学校教务系统IP';
const getCodeUrl = ctx => {
return new Promise((resolve, reject) => {
superagent
.get(hostUrl)
.charset('gb2312')
.set(headers)
.end(function(err, res) {
console.log('res', res);
const body = res.text;
// 获得重定向地址
const systemUrl = res.redirects[0];
// 获得codeUrl后缀
$ = cheerio.load(body);
const viewState = $('#form1 > input')[0].attribs.value;
// 替换返回验证码地址
const codeUrl = systemUrl.replace(/default2.aspx/, 'CheckCode.aspx');
resolve({ codeUrl, viewState, systemUrl });
});
});
};
这块的逻辑就是根据superagent(请求代理模块)去请求【教务系统】的页面数据,通过返回的ip进行验证码的url地址拼接(保证url的正确性),最后把一些必要的信息返回给前端【小程序】。
- 小程序请求API
getCodeUrl: function() {
let that = this;
wx.request({
url: 'http://127.0.0.1:3000/login/getCodeUrl',
header: {
'content-type': 'application/json'
},
success(res) {
const data = res.data;
that.setData({
codeUrl: data.codeUrl,
systemUrl: data.systemUrl,
})
}
})
},
小程序去请求getCodeUrl获得【验证码url】和【教务系统url】。
- 小程序页面
<!-- login.wxml -->
<view class="container" bindsubmit="formSubmit">
<view class="loginTitle">
<image src="{{loginIcon}}"></image>
<text>登录</text>
</view>
<form class="loginFrom" bindsubmit="formSubmit">
<view class="formItem">
<text class="title">学号</text>
<input name="txtUserName" placeholder="请输入学号" maxlength='10' />
</view>
<view class="formItem">
<view class="title">密码</view>
<input name="TextBox2" placeholder="请输入密码" type="password" />
</view>
<view class="formItem">
<view class="title">验证码</view>
<input name="txtSecretCode" placeholder="请输入验证码" class="codeInput" maxlength='4'/>
<image src="{{codeUrl}}" class="codeImg"></image>
</view>
<button form-type="submit">提交</button>
</form>
</view>
这块其实就是普通的页面展示(大概样子),主要是为了让你能看到在第二步【setData】时的数据是干什么用的。完成这三步就完成了【登录】需要的百分之50功能。
- 登录
// 表单提交
formSubmit(e){
const data = e.detail.value;
// 登录主页
const payload = {
RadioButtonList1:"%D1%A7%C9%FA",
...data
}
this.loginSystem(payload);
},
// 得到系统主页链接
getSystemUrl(payload) {
let that = this;
const data = {
__VIEWSTATE: "学校与学校的不一样",
Textbox1: "",
Button1: "",
lbLanguage: "",
hidPdrs: "",
hidsc: "",
systemUrl: that.data.systemUrl,
...payload
};
wx.request({
url: 'http://127.0.0.1:3000/login/loginSystem',
method: 'post',
header: {'content-type': 'application/x-www-form-urlencoded'},
data,
success(res) {
const { data:{ errCode = "", url = "", name = "" } = {} } = res;
// 缓存系统地址
wx.setStorageSync('systemUrl',url);
wx.setStorageSync('stuName', name);
wx.setStorageSync('stuCode', payload.txtUserName);
},
complete() {}
})
},
为了保证看起来比较简单,我删去了【加载】【防多点】【异常兼容】等代码。这块主要通过form表单获得输入的数据,经过添加处理后提交给node接收参数。
- 登录API
// 获取系统主页面
const getMainSystemUrl = payload => {
// 传参
const url = payload.systemUrl;
const __VIEWSTATE = payload.__VIEWSTATE;
const data = {
__VIEWSTATE,
txtUserName: payload.txtUserName,
Textbox1: '',
TextBox2: payload.TextBox2,
txtSecretCode: payload.txtSecretCode,
RadioButtonList1: '%D1%A7%C9%FA',
Button1: '',
lbLanguage: '',
hidPdrs: '',
hidsc: ''
};
return new Promise((resolve, reject) => {
superagent
.post(url)
.charset('gb2312')
.set(headers)
.send(data)
.end(function(err, res) {
const mainSystemUrl = res.redirects[0];
const body = res.text;
$ = cheerio.load(body);
const stuName = $('#xhxm')
.text()
.replace(/同学/, '');
resolve({ url: mainSystemUrl, name: stuName });
});
});
};
这一块就是node接收到小程序传递的参数,对数据进行处理后,去请求【教务系统】的登录接口,通过接口返回的内容,取出【姓名】【学号】等信息,辅助我们完成接下来的功能。
如果你看到了这里,并且每一块都详细的看明白了,那你用python,java都可以实现这样一套简单的接口请求。但是如果你看完之后,copy到了自己的项目中发现没法使用,你可以扫描下面的公众号二维码,后台提出问题我替你解答。获得完整源码可以微信搜索【前端小白的成长记录】关注或者扫码二维码回复教务系统获得。
【注】如果你们学校的教务系统是正方的,源码只需修改教务系统IP即可使用,目前已有查询成绩和查询课表功能。