华中师范大学校园网自动登录器 - 一个基于PyQt5的GUI程序设计谈
本程序可用于华中师范大学无线校园网(SSID:CCNU)以及有线校园网的连接认证,可以替代网页认证。本程序适用于解决:1、开机程序自动登录网络,而无须手动干预。2、连接网络后打不开认证界面(尤其是宿舍晚上以及图书馆等人较多的地方)。由于学校网络认证复杂,新旧混乱,因此程序提供了自动切换认证服务器的功能。
比如,当你在田家炳登录,使用的是旧认证服务器,而在图书馆登录,使用的是新服务器,二者登录地址不同,传递参数不同,采用一般登录脚本,虽然可以登录,但是由于登录的服务器不是当前区域的服务器,因此亦不能上网。本程序可自动识别和判断与自动切换,一切只需要按下“登录”按钮,或者启动应用即可(静默登录模式下)。虽然说,正因如此,本来50行的程序活生生的写了500行。
一、写作缘由
学校校园网的登录认证的主要方式是连接有线或者无线,之后等系统弹出登录网页,或者你自己上一个网站,会自动跳转到登录界面,在大多数情况下,这没有什么问题,但是在人比较多的时候,比如图书馆,或者信号覆盖不是很好的时候,就不会弹出这个页面,网络状态直接显示感叹号,网页打不开,更有甚者,即便是手动输入认证服务器网址,也有一定几率打不开登录页面。
这个程序本来是写给自己用来自动登录有线网的,在NAS里做自动开机登录使用,这个脚本写的很简单,就十几行代码。后来想要自己的笔记本也可以自动登录无线网,就扩充了下脚本,但是,当真正开始用的时候,发现了很多的问题,这些问题都是脚本难以解决的,就干脆把脚本扩展成了GUI程序,并且同时支持了无线网络和有线网络的登录,以及旧认证服务器和新认证服务器的自动切换登录。
二、学校网络概况
这是2018年1月份写的程序,在这个时候,学校的校园网主要包含有线和无线两种连接方式,其中有线,包括校园网有线、三家运营商的有线,这四种登录方式使用的是新的服务器,也就是2017年底部署的新的认证服务器,登录网址为login.ccnu.edu.cn,或者l.ccnu.edu.cn,或者是10.220.250.50,认证地址为:
http://10.220.250.50/0.htm
无线的话,比较复杂,在不同地方的无线服务器不同,有些地方,比如图书馆,使用的是新的认证服务器(登陆界面包含输入用户名、密码、运营商选择三个部分),认证地址和上面的一样。但是,在大部分学校的其它地方,比如田家炳楼、宿舍无线使用的是旧的服务器,其打开界面后没有选择运营商的RadioButton,只有输入用户名和密码的地方,其认证地址为:
http://securelogin.arubanetworks.com/auth/index.html/uauth/index.html/u
但是,有些时候,在本该属于新服务器认证的地方,偶尔会跳转到旧服务器认证,所以,如果想要正确登录,就需要尝试不同的服务器,但是,新的问题是,在旧服务器认证的时候,新服务器地址是可以POST通的,并且可以正常返回200并打开正常登录页面的,但是,其它正常外网网页是登不上的,因此,设计自动登录程序的时候当POST返回200和正确网页之后还需要测试是否可以打开外网,更有甚者,很多时候,就算是正确的服务器,POST返回200和正确网页后还是打不开外网,这极大增加了登录脚本的复杂度。
三、功能
程序主界面如图所示:
1、静默登陆
程序添加到开机启动项并且勾选“静默登录”后,开机时会自动登录,程序不会显示主界面,只有状态栏图标和气泡通知。
2、记住密码
密码会保存在注册表,只保存在本地,不会上传。
3、自动切换服务器登录
当新服务器无法使用时,勾选此项会自动切换服务器重试。推荐搭配静默登录一起使用。
4、调试网络
在“调试”按钮下,你可以测试用户名、密码到各个服务器的连通性,当登录时,按钮会被选中,当你注销后会自动取消选中。
5、附加
当你当前已经连接网络时,程序不会继续登录,你可以在“调试”注销相应服务器后再登录。
四、Logo设计
这个程序的Logo是一个“网”字,底色采用了华大的标志色——青,构思还可以,起码比我之前自己随手从网上找的一个地球强一些,虽然这个也是在Ai里自己画的。我始终相信这一点:
一个花费心思制作的,并且每天都用到的东西,值得设计者让它好看一点,尽管好不好看并不是它的主要功能。
五、语言和依赖
Python 3.X,需要安装requests包(脚本)以及pyqt5包(GUI)。
测试平台为Windows 10 X64,macOS HS。
六、程序
直接上程序吧,具体项目参见项目Github仓库。唯一感叹一点的就是,区区四五行的POST登陆,真正实现成一个功能,竟然写了个500行的程序,其中接近400行处理相关逻辑,100行左右处理UI。一个遗憾是,没有使用多线程,因为Qt的多线程还没有开始学习,Python的多线程和PyQt兼容性不太好,并且也不安全,所以就没有用。
postit.py这个是脚本,可以直接使用,不过要实现自动登录、自动判断登录状态并且在多次登录失败的情况下切换新旧服务器,以及GUI的话,就需要另一个main.py以及资源、UI布局文件,直接运行main.py即可。
打包可执行(EXE)文件下载
Windows打包二进制文件适用于64位PC,不需要任何依赖,不需要Python和其依赖包,可以直接执行,程序在/dist目录下,点击下载
七、程序核心代码
1、登录和注销API调用代码
import requests,json
import traceback
def loginCCNU(username="",passwd="",suffix=""):
errmessage = None
try:
url = "http://10.220.250.50/0.htm"
payload = {"DDDDD":"%s"%username,
"upass":"%s"%passwd,
"suffix":"%s"%suffix,
"0MKKey":"123"}
headers = { "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
"Content-Type": "application/x-www-form-urlencoded",}
r = requests.post(url, data=payload ,headers = headers)
# print(r.text)
if "您已登录成功,欢迎使用!请不要关闭本页。" in r.text:
return str(r.status_code),"1",errmessage
else:
return str(r.status_code),"0",errmessage
except:
errmessage = str(traceback.format_exc())
return None,"0",errmessage
def loginoutCCNU(username="",passwd="",suffix=""):
errmessage = None
try:
url = "http://10.220.250.50/F.htm"
headers = { "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
"Content-Type": "application/x-www-form-urlencoded",}
r = requests.get(url ,headers = headers)
# print(r.text)
if "华中师范大学无线校园网登录" in r.text:
return str(r.status_code),"0","已注销"
else:
return str(r.status_code),"0","返回未知网页"
except:
errmessage = str(traceback.format_exc())
return None,"0",errmessage
2、核心登录代码
def goPost(self,choose = "",check_link_userrole=True,show_check=True):
# self.postit_callback.emit("登录中...")
# 应该先判断是否当前有互联网连接,如果没有再继续下去。
# 优先使用choose,如果没有choose字段,则使用userrole设置
info = ""
self.tryinfo = "[第%s次连接]"%self.index
othererr = False
checklink = False
self.loadUserrole()
user,passwd,choose_list = self.userrole[0],self.userrole[1],self.userrole[2]
if choose == "wlan":
print("旧服务器登录中...(来自于指定的登录方式)")
response,code,errmesage = postit.loginCCNUWLAN(user,passwd)
elif choose == "lan":
print("新服务器登录中...(来自于指定的登录方式)")
response,code,errmesage = postit.superLogin(user,passwd,choose_list)
elif choose_list[1] == True:
print("旧服务器登录中...(来自于用户自定义的登录方式)")
response,code,errmesage = postit.loginCCNUWLAN(user,passwd)
elif choose_list[1] != True:
print("新服务器登录中...(来自于用户自定义的登录方式)")
response,code,errmesage = postit.superLogin(user,passwd,choose_list)
# print(response,code,errmesage)
if response == "200":
info += """<span style=" color:#00aa00;"> [200]网站已响应,</span>"""
if code == "0":
info = """<span style=" color:#ff0000;"> [200]网站已响应,但服务器返回了一个错误。</span>"""
elif code == "1":
info += """<span style=" color:#00aa00;">登录成功</span>"""
self.index = 1
self.tryinfo = "[连接成功]"
checklink = check_link_userrole
elif response == "403":
info += """<span style=" color:#ff0000;"> [403]数据提交不完整,请重试</span>"""
elif response == "502":
info += """<span style=" color:#ff0000;"> [502]物理网络设备未正确设置</span>"""
else:
info += """<span style=" color:#ff0000;"> [ERR]其它错误。</span>"""
othererr = True
if othererr:
# errmsg = QErrorMessage(self)
# QErrorMessage.showMessage(errmsg,errmesage)
print(errmesage)
self.postit_callback.emit(self.tryinfo + info)
if checklink and show_check:
time.sleep(1)
check = self.checkLink(show=1)
elif checklink and not show_check:
time.sleep(1)
check = self.checkLink(show=0)
elif not checklink:
check = 2
return response,code,errmesage,check
3、静默登陆和服务器自动切换代码
def goSlience(self):
if self.checkLink(show=0) == 1:
self.postit_callback.emit("已连接互联网,已取消登录请求(可在“测试”选项注销服务器登录后再试)")
msgIcon_info = QSystemTrayIcon.MessageIcon(QSystemTrayIcon.Information)
self.trayIcon.showMessage("已连接互联网","您已在线,已取消登录请求。",msgIcon_info)
QTimer.singleShot(10000,self.autoClose)
else:
r,code,_,check = self.loginSlience(choose = "")
# 有可能存在没有连上网但是正确登录到服务器的情况。
# code = self.checkLink(show=0)
# print("OUTCODE",code)
if str(check) != "1":
self.loadUserrole()
autoserver = self.userrole[5]
iswlan = self.userrole[2][1]
userrole_temp = self.userrole
if iswlan:
try:
postit.loginoutCCNUWLAN()
except Exception as err:
print("在旧服务器登出过程中发生错误")
else:
try:
postit.loginoutCCNU()
except Exception as err:
print("在新服务器登出过程中发生错误")
if autoserver:
if iswlan:
self.radioButton_school.setChecked(True)
self.postit_callback.emit("旧服务器无响应,正尝试其他服务器")
r,code,_,check = self.loginSlience(choose = "lan")
# 如果尝试更换服务器依然失败,那么换回更换之前的设置。
if str(check) != "1":
self.userrole = userrole_temp
self.restoreUserrole()
elif not iswlan:
# 因为使用wlan方式登陆不需要额外参数,因此只需要进行POST即可
print("here")
self.postit_callback.emit("新服务器无响应,正尝试其他服务器")
r,code,_,check = self.loginSlience(choose = "wlan")
else:
self.postit_callback.emit("""[FINAL]<span style=" color:#ff0000;"> 无法连接互联网。</span>""")
def loginSlience(self,choose = "",loop = 5):
index = 1
response = code = errmessage = check = None
msgIcon_info = QSystemTrayIcon.MessageIcon(QSystemTrayIcon.Information)
msgIcon_warn = QSystemTrayIcon.MessageIcon(QSystemTrayIcon.Warning)
msgIcon_err = QSystemTrayIcon.MessageIcon(QSystemTrayIcon.Critical)
while index < loop:
# 根据UserRole进行自动选择,所以说这里的choose为空
response,code,errmessage,check = self.goPost(choose = choose,check_link_userrole=True,show_check=False)
print("LOGINSLIENCE",response,code,check)
print("第%s次尝试登录"%index)
index = index + 1
if response == "200" and code == "1" and str(check) == "1":
break
if response == "200" and code == "1":
self.trayIcon.showMessage("登录成功","状态码[200],登录成功",msgIcon_info)
QTimer.singleShot(10000,self.autoClose)
elif response == "200" and code == "0":
self.trayIcon.showMessage("登录失败","[200]网站已响应,但服务器返回了一个错误。",msgIcon_err)
elif response == "403":
self.trayIcon.showMessage("登录失败","[403]数据提交不完整,请重新输入。",msgIcon_err)
elif response == "502":
self.trayIcon.showMessage("登录失败","[502]网络未联通,请检查硬件连接。",msgIcon_warn)
else:
self.trayIcon.showMessage("登录失败","[ERR]硬件故障",msgIcon_err)
return response,code,errmessage,check
八、版权、作者和使用说明
本程序使用 Python 和 Qt 开发,程序遵循GPL v2开源协议,你可以在 http://tools.mazhangjing.com 此网站找到程序的源代码,如果没有,请联系作者。