毕设代码
client.py
import requests
import json
import sys
import os
import hashlib
import threading
from PyQt5.QtCore import *
from PyQt5.QtWidgets import QWidget, QApplication, QMessageBox, QFileDialog, QProgressDialog, QInputDialog, QLineEdit, \
QPushButton
from PyQt5.QtGui import *
from Ui_LoginClient import Ui_Form
from Ui_MainClient import Ui_Form as mainForm
from QCandyUi.CandyWindow import colorful
import time
from datetime import datetime
import smtplib
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from email.mime.text import MIMEText
import pickle
from email.utils import formataddr
import random
import secrets
import stat
SERVER_HOST = 'http://82.156.24.158:80'#'http://192.168.1.120:8000'
EMAIL_PASSCODE = 'cgoizqmcgbhndcdh'
EMAIL_HOST = '2761407387@qq.com'
global userdata
userdata = {}
# @colorful('blueDeep')
class MainGUI(QWidget, mainForm):
def __init__(
self) -> None: # parent: typing.Optional['QWidget'] = ..., flags: typing.Union[QtCore.Qt.WindowFlags, QtCore.Qt.WindowType] = ...
super().__init__()
self.setupUi(self)
# 禁止拉伸窗口
self.setFixedSize(self.width(), self.height())
self.setWindowTitle('MainClient')
# 禁用最大化
#self.setWindowFlags(Qt.WindowMaximizeButtonHint)
# 上传文件按钮绑定函数select files
self.upload.clicked.connect(self.selectFiles)
# 设置背景图片,背景图片显示,承载控件为一个QLabel
self.background.setPixmap(QPixmap('MainData/background.png'))
# 设置群组控件的标题为用户ID
self.groupBox.setTitle(userdata['uid'])
# 设置文件展示列表的控件背景为透明
self.filelist.setStyleSheet("background-color: transparent;")
# 将删除按钮绑定函数delete file
self.delete.clicked.connect(self.deleteFile)
# 将重命名按钮绑定函数renamefile
self.rename.clicked.connect(self.renameFile)
self.filelist.itemClicked.connect(self.itemclicker)
self.downloadmod.clicked.connect(self.downloadFile)
self.cancelbutton = QPushButton('注销', self)
self.cancelbutton.setGeometry(QRect(0, 735, 410, 20))
self.cancelbutton.setObjectName("cancel")
self.cancelbutton.clicked.connect(self.cancelfunc)
# 设置默认文件控制模式为下载
self.Filecontroler_mode = 'download' # 另有rename delete模式
# 初始化刷新线程暂停为否
self.refresh_pause = False
self.listviews()
# 创建线程使得文件列表无阻塞的展示视图得以自动化刷新,县城指定的应用函数为list view
'''self.filelist_refresher = threading.Thread(target=self.listview)
self.filelist_refresher.start()'''
# 同上,现场绑定的函数为refresh net disk
self.netdisk_refresher = threading.Thread(target=self.refreshNetdisk)
self.netdisk_refresher.start()
def refreshNetdisk(self):
# 此函数用于循环请求来刷新剩余的磁盘空间
while True:
if not self.refresh_pause:
# 当线程暂停未使用时,则开始请求服务器获得一份新的磁盘,剩余空间数据
#diskstatus_start_time =time.time()
diskstatus = json.loads(requests.get(SERVER_HOST + '/diskstatus').content)
# 将剩余的磁盘空间展示在groupbox里面的QLabel控件中
#diskstatus_end_time =time.time()
#diskstatus_time =diskstatus_end_time - diskstatus_start_time
#print(f'diskstatus_time:{diskstatus_time} seconds')
self.diskchecker.setText(
f"<html><head/><body><p><span style=\" font-size:20pt; font-weight:600; color:#55ff7f;\">剩余可用空间(MB):{diskstatus['data']}</span></p></body></html>")
# 休眠三秒减少服务器负担
time.sleep(2)
else:
# 当线程暂停被使用时,暂停三秒,减少循环pass带来的本地客户端资源占用
time.sleep(2)
pass
def deleteFile(self):
# 切换点击项的模式为删除模式
QMessageBox.about(self, u'提示', "已切换为删除模式\n非重命名模式将自动恢复/启动文件与磁盘刷新")
self.Filecontroler_mode = 'delete'
self.refresh_pause = False
def downloadFile(self):
# 切换文件控制模式为下载
QMessageBox.about(self, u'提示', "已切换为下载模式\n非重命名模式将自动恢复/启动文件与磁盘刷新")
self.refresh_pause = False
self.Filecontroler_mode = 'download'
def renameFile(self):
# 切换点击项的模式为重命名模式
QMessageBox.about(self, u'提示', "已切换为重命名模式\n自动化文件刷新与磁盘刷新暂停")
self.refresh_pause = True
# 此时,需要将线程暂停的开关设置为True
# 当请求获取文件列表的时候,服务端会占用指定文件的权限,则此时需要暂停文件的刷新来释放占用,才能请求重命名的接口,使之自由操作文件
self.Filecontroler_mode = 'rename'
def itemclicker(self, item):
# 此处为filelist的每一项点击时会触发的函数,传入item
if self.Filecontroler_mode == 'download':
# 当文件控制模式为下载时,询问是否创建点击文件的下载线程
pre_to_downloadFile = QMessageBox.question(self, 'pre_to_downloadFile',
f'是否创建文件:{item.text()} 的下载线程',
QMessageBox.Yes | QMessageBox.No)
# 当用户点击确认时,则获取点击项的文本,请求服务器进行下载
if pre_to_downloadFile == QMessageBox.Yes:
self.log.addItem(f'{datetime.today()}:开始下载{item.text()} (线程已创建)')
postdata_log = {
'uid': userdata['uid'],
'log': f'{datetime.today()}:开始下载{item.text()} (线程已创建)'
}
postdata_log = json.dumps(postdata_log)
# 上传文件与表单到upload files接口
logging_start_time =time.time()
#print(f'logging_time:{logging_time} seconds')
requests.post(SERVER_HOST + '/logging', data=postdata_log)
# 新建线程传参,防止在下载大文件时,主程序未响应
logging_end_time =time.time()
logging_time =logging_end_time - logging_start_time
print(f'logging_time:{logging_time} seconds')
download_thread = threading.Thread(target=self.downloader, args=(item.text(),))
download_thread.start()
else:
# 反之用户未点击确认时则pass
pass
elif self.Filecontroler_mode == 'delete':
# 同上
pre_to_deleteFile = QMessageBox.question(self, 'pre_to_deleteFile',
f'是否创建文件:{item.text()} 的删除线程',
QMessageBox.Yes | QMessageBox.No)
if pre_to_deleteFile == QMessageBox.Yes:
self.log.addItem(f'{datetime.today()}:开始删除{item.text()} (线程已创建)')
postdata_log = {
'uid': userdata['uid'],
'log': f'{datetime.today()}:开始删除{item.text()} (线程已创建)'
}
postdata_log = json.dumps(postdata_log)
requests.post(SERVER_HOST + '/logging', data=postdata_log)
delete_thread = threading.Thread(target=self.deleter, args=(item.text(),))
delete_thread.start()
else:
pass
elif self.Filecontroler_mode == 'rename':
# 当文件控制模式为重命名时,则弹出一个输入弹窗
value, ok = QInputDialog.getText(self, "renameInput",
f"请在下方输入要将 {item.text()} 重命名的内容(包括后缀)", QLineEdit.Normal,
"")
# 当用户输入并点击ok时则获取输入文本
if not ok:
self.log.addItem(f'{datetime.today()}:取消了重命名{item.text()}')
postdata_log = {
'uid': userdata['uid'],
'log': f'{datetime.today()}:取消了重命名{item.text()}'
}
postdata_log = json.dumps(postdata_log)
requests.post(SERVER_HOST + '/logging', data=postdata_log)
else:
self.log.addItem(f'{datetime.today()}:开始重命名{item.text()} 为 {value}(线程已创建)')
postdata_log = {
'uid': userdata['uid'],
'log': f'{datetime.today()}:开始重命名{item.text()} 为 {value}(线程已创建)'
}
postdata_log = json.dumps(postdata_log)
requests.post(SERVER_HOST + '/logging', data=postdata_log)
print(value)
# 创建线程进行重命名
self.renamer(item.text(), value)
def deleter(self, filename):
# 当删除函数被触发时
postdata = {
'uid': userdata['uid'],
'token': userdata['token'],
'filename': filename
}
# 将身份验证作为请求体,传入删除接口
#deleteFiles_end_time =time.time()
#deleteFiles_time =deleteFiles_end_time - deleteFiles_start_time
#print(f'deleteFiles_time:{deleteFiles_time} seconds')
deleteFiles_start_time =time.time()
respond = json.loads(requests.post(f'{SERVER_HOST}/deleteFiles', data=json.dumps(postdata)).content)
deleteFiles_end_time =time.time()
deleteFiles_time =deleteFiles_end_time - deleteFiles_start_time
print(f'deleteFiles_time:{deleteFiles_time} seconds')
try:
# 当返回码为-5时表示请求错误,并打印请求日志
if respond['code'] == -5:
self.log.addItem(respond['message'])
postdata = {
'uid': userdata['uid'],
'log': respond['message']
}
requests.post(SERVER_HOST + '/logging', data=postdata)
self.listviews()
return 0
except:
self.listviews()
return 0
os.remove(f"ClientData/{filename}_aesKey.pickle")
self.log.addItem(f'{datetime.today()}:删除{filename}成功!')
postdata_log = {
'uid': userdata['uid'],
'log': f'{datetime.today()}:删除{filename}成功!'
}
postdata_log = json.dumps(postdata_log)
requests.post(SERVER_HOST + '/logging', data=postdata_log)
self.listviews()
return 0
def renamer(self, filename, updatename):
# 当此重命名函数被触发时,获取传入的旧文件名与填入的更新后文件名
postdata = {
'uid': userdata['uid'],
'token': userdata['token'],
'filename': filename,
'update': updatename
}
#renameFiles_end_time =time.time()
#renameFiles_time =renameFiles_end_time - renameFiles_start_time
#print(f'renameFiles_time:{renameFiles_time} seconds')
renameFiles_start_time =time.time()
respond = json.loads(requests.post(f'{SERVER_HOST}/renameFiles', data=json.dumps(postdata)).content)
renameFiles_end_time =time.time()
renameFiles_time =renameFiles_end_time - renameFiles_start_time
print(f'renameFiles_time:{renameFiles_time} seconds')
try:
if respond['code'] == -5:
self.log.addItem(respond['message'] + '\n\n请重试')
postdata_log = {
'uid': userdata['uid'],
'log': respond['message'] + '\n\n请重试'
}
postdata_log = json.dumps(postdata_log)
requests.post(SERVER_HOST + '/logging', data=postdata_log)
return 0
except:
if os.path.exists(f'ClientData\\{filename}_aesKey.pickle'):
with open(f'ClientData\\{filename}_aesKey.pickle', 'rb') as o:
orifile = pickle.load(o)
o.close()
os.rename(f'ClientData\\{filename}_aesKey.pickle', f'ClientData\\{updatename}_aesKey.pickle')
with open(f'ClientData\\{updatename}_aesKey.pickle', 'wb') as r:
pickle.dump({
updatename: orifile[filename]
}, r)
r.close()
else:
pass
self.log.addItem(f'{datetime.today()}:重命名{filename}为{updatename}成功!')
postdata_log = {
'uid': userdata['uid'],
'log': f'{datetime.today()}:重命名{filename}为{updatename}成功!'
}
postdata_log = json.dumps(postdata_log)
self.listviews()
requests.post(SERVER_HOST + '/logging', data=postdata_log)
self.tmp_refresh()
return 0
def cancelfunc(self):
select = QMessageBox.question(self, 'Cancel your account?',
f'当前点击了注销账号,您确定需要注销吗,这将会清除您的云端保存数据',
QMessageBox.Yes | QMessageBox.No)
if select == QMessageBox.Yes:
value, ok = QInputDialog.getText(self, 'CANCEL?', f"请输入用户:{userdata['uid']}的密码完成二次校验")
if not ok:
self.log.addItem(f'{datetime.today()}:取消了注销操作')
postdata_log = {
'uid': userdata['uid'],
'log': f'{datetime.today()}:取消了注销操作'
}
postdata_log = json.dumps(postdata_log)
requests.post(SERVER_HOST + '/logging', data=postdata_log)
else:
inputhash = hashlib.sha256((str(userdata['uid']) + str(value)).encode('utf-8')).hexdigest()
if inputhash == userdata['hash']:
postdata = {
'uid': userdata['uid']
}
CancelAccount_start_time =time.time()
CancelAccount_end_time =time.time()
res = requests.post(f'{SERVER_HOST}/CancelAccount', data=json.dumps(postdata)).content
CancelAccount_end_time =time.time()
CancelAccount_time = CancelAccount_end_time - CancelAccount_start_time
print(f'CancelAccount_time:{CancelAccount_time} seconds')
self.log.addItem(f'{datetime.today()}:注销成功:{res}')
postdata_log = {
'uid': userdata['uid'],
'log': f'{datetime.today()}:注销操作'
}
postdata_log = json.dumps(postdata_log)
requests.post(SERVER_HOST + '/logging', data=postdata_log)
self.refresh_pause = True
self.close()
else:
self.log.addItem(f'{datetime.today()}:取消了注销操作')
postdata_log = {
'uid': userdata['uid'],
'log': f'{datetime.today()}:取消了注销操作'
}
postdata_log = json.dumps(postdata_log)
requests.post(SERVER_HOST + '/logging', data=postdata_log)
def selectFiles(self):
# 当点击上传文件时触发select file函数
self.directory, status = QFileDialog.getOpenFileNames(self, '请选择需要上传的文件 允许多选', "C:\\")
print(self.directory)
# 返回为一个列表,当列表长度为零时,表示未选择任何文件,则直接结束选择函数
if len(self.directory) == 0:
return None
# 当选择长度不为0时,则提出二次验证是否上传
pre_to_uploadFile = QMessageBox.question(self, 'FileSelectSuccess',
f'当前选中了\n{self.directory}\n是否确认上传',
QMessageBox.Yes | QMessageBox.No)
is_clientEncrypt = QMessageBox.question(self, 'ClientEncrypt',
f'是否在本地完成加密?服务器端也会进行一次加密,如果您不希望服务器有哪怕一瞬间获取您的文件,您可以选择上传服务器前确认此对话框来在本地离线完成加密再上传',
QMessageBox.Yes | QMessageBox.No)
if is_clientEncrypt == QMessageBox.Yes:
self.clientEncrypt = True
else:
self.clientEncrypt = False
self.count = 0
if pre_to_uploadFile == QMessageBox.Yes:
# 分别创建线程,一个文件对应一个线程,提交每一个文件的路径给线程作为传参
for task in range(len(self.directory)):
tasker = threading.Thread(target=self.acceptToUploadFile, args=(self.count,))
tasker.start()
self.count += 1
else:
pass
def get_user_aeskey_oridata(filename: str) -> dict:
# 获取aes密钥文件的原始数据字典
try:
with open(f"ClientData/{filename}_aesKey.pickle", 'rb') as r:
userdata = pickle.load(r)
r.close()
return userdata
except:
return {}
def acceptToUploadFile(self, count):
# 此为上传函数用于接收select file创建的线程
task_filepath = self.directory[count]
if self.clientEncrypt == True:
key = bytes(secrets.token_hex(16), 'utf-8')
# ecb模式生成密钥加密
aes_key_ecb = AES.new(key, AES.MODE_ECB)
# 调用方法便捷保存用户aes密钥文件
filename = os.path.basename(task_filepath)
with open(f'ClientData/{filename}_aesKey.pickle', 'wb') as saveKey:
oridata = MainGUI.get_user_aeskey_oridata(filename=filename)
oridata[filename] = key
pickle.dump(oridata, saveKey)
print(filename, '保存aeskey')
saveKey.close()
# 获取用户加密后的bytes,encrypt函数中需要pad来补全aes需要的位宽,如不填满位宽则无法加密
with open(task_filepath, 'rb') as tmp:
tmpfile = tmp.read()
encode_return = aes_key_ecb.encrypt(pad(tmpfile, AES.block_size))
with open(f'ClientData/{filename}', 'wb') as tmpwrite:
tmpwrite.write(encode_return)
tmpwrite.close()
tmp.close()
task_filepath = f'ClientData/{filename}'
# 获取列表中指定的线程
# Upload qp回一个q progress dialog用于实现进度条
uploadQP = QProgressDialog(self)
uploadQP.setWindowTitle(f'{task_filepath}')
uploadQP.setWindowFlag(Qt.WindowCloseButtonHint, False)
file = open(task_filepath, 'rb')
# filedata用于上传文件的bytes
filedata = {
'file': file
}
# Request header用于构建用户个人信息作为表单上传
request_header = {
'token': userdata['token'],
'uid': userdata['uid']
}
postdata = {
'data': json.dumps(request_header)
}
# 上传文件与表单到upload files接口
#uploadFiles_end_time =time.time()
#uploadFiles_time =uploadFiles_end_time - uploadFiles_start_time
#print(f'uploadFiles_time:{uploadFiles_time} seconds')
uploadFiles_start_time =time.time()
uploader = requests.post(SERVER_HOST + '/uploadFiles', files=filedata, data=postdata).content
uploadFiles_end_time =time.time()
uploadFiles_time =uploadFiles_end_time - uploadFiles_start_time
print(f'uploadFiles_time:{uploadFiles_time} seconds')
print(uploader)
self.log.addItem(json.loads(uploader)['status'])
postdata_log = {
'uid': userdata['uid'],
'log': json.loads(uploader)['status']
}
postdata_log = json.dumps(postdata_log)
requests.post(SERVER_HOST + '/logging', data=postdata_log)
file.close()
self.listviews()
# os.remove(f'ClientData/{filename}')
def downloader(self, item):
# 此函数用于接收下载创建的线程
if not os.path.exists('ALE_Netdisk_download/'):
os.makedirs('ALE_Netdisk_download/')
if not os.path.exists(f"ClientData/{item}_aesKey.pickle"):
ClientEncrypt = False
else:
ClientEncrypt = True
# 构建个人信息请求体
postdata = {
'uid': userdata['uid'],
'filename': item,
'token': userdata['token'],
}
#downloadFiles_end_time =time.time()
#downloadFiles_time =downloadFiles_end_time - downloadFiles_start_time
#print(f'downloadFiles_time:{downloadFiles_time} seconds')
downloadFiles_start_time =time.time()
respond = requests.post(f'{SERVER_HOST}/downloadFiles', data=json.dumps(postdata))
downloadFiles_end_time =time.time()
downloadFiles_time =downloadFiles_end_time - downloadFiles_start_time
print(f'downloadFiles_time:{downloadFiles_time} seconds')
# 服务端返回已解密的文件,直接按照item.text()返回的文件名写入即可
downloadFiles_end_time =time.time()
downloadFiles_time =downloadFiles_end_time - downloadFiles_start_time
print(f'downloadFiles_time:{downloadFiles_time} seconds')
savepath = f'ALE_Netdisk_download/{item}'
filedata = respond.content
if ClientEncrypt:
self.log.addItem(f'{datetime.today()}:检查到{item}为本地二次加密文件,运行离线解密中')
with open(f"ClientData/{item}_aesKey.pickle", 'rb') as key:
savekey = pickle.load(key)[item]
print(savekey)
key.close()
aes_key = AES.new(savekey, AES.MODE_ECB)
# 解密文件
defile = aes_key.decrypt(filedata)
# 对加密时使用pad填充的位宽进行消除,使用unpad方法
filedata = unpad(defile, AES.block_size)
print(savepath)
try:
with open(savepath, 'wb') as sv:
sv.write(filedata)
sv.close()
except:
pass
self.log.addItem(f'{datetime.today()}:下载{item}成功!')
# 当写入成功后,则请求移除临时文件接口
requests.post(f'{SERVER_HOST}/rmFiles', data=json.dumps(postdata))
postdata_log = {'uid': userdata['uid'], 'log': f"{datetime.today()}:下载{item}成功!"}
postdata_log = json.dumps(postdata_log)
requests.post(SERVER_HOST + '/logging', data=postdata_log)
def tmp_refresh(self):
# 此接口用于rename函数在完成重命名之后,尚未切换到其他模式来解除refresh pause导致的未能及时刷新文件列表,因而,构建此函数用于临时刷新单次文件列表
font = QFont()
font.setPointSize(20)
self.filelist.setFont(font)
reqdata = {
'uid': userdata['uid'],
'token': userdata['token']
}
self.filelist_ori = json.loads(requests.post(SERVER_HOST + '/filelist', data=json.dumps(reqdata)).content)[
'data']
self.filelist.clear()
for fl in self.filelist_ori:
self.filelist.addItem(fl)
def listviews(self):
# 此函数用于接收线程来构建自动化,定时刷新文件列表
font = QFont()
# 实例化字体并且应用于List widget
font.setPointSize(20) # 字体大小可修改为任意数
self.filelist.setFont(font)
if not self.refresh_pause:
reqdata = {
'uid': userdata['uid'],
'token': userdata['token']
}
self.filelist_ori = \
json.loads(requests.post(SERVER_HOST + '/filelist', data=json.dumps(reqdata)).content)['data']
#filelist_start_time =time.time()
#filelist_end_time =time.time()
#filelist_time =filelist_end_time - filelist_start_time
#print(f'filelist_time:{filelist_time} seconds')
self.filelist.clear()
for fl in self.filelist_ori:
self.filelist.addItem(fl)
def listview(self):
# 此函数用于接收线程来构建自动化,定时刷新文件列表
font = QFont()
# 实例化字体并且应用于List widget
font.setPointSize(20) # 字体大小可修改为任意数
self.filelist.setFont(font)
while True:
# 死循环在请求接口之前判断是否已经启用线程刷新暂停
if not self.refresh_pause:
reqdata = {
'uid': userdata['uid'],
'token': userdata['token']
}
self.filelist_ori = \
json.loads(requests.post(SERVER_HOST + '/filelist', data=json.dumps(reqdata)).content)['data']
self.filelist.clear()
for fl in self.filelist_ori:
self.filelist.addItem(fl)
time.sleep(2)
else:
time.sleep(2)
pass
# @colorful('blueDeep') 禁用装饰器原因:close()方法无法完全关闭装饰后的ui
class LoginGUI(QWidget, Ui_Form):
def __init__(
self) -> None: # parent: typing.Optional['QWidget'] = ..., flags: typing.Union[QtCore.Qt.WindowFlags, QtCore.Qt.WindowType] = ...
super().__init__()
self.setupUi(self)
# 禁止拉伸窗口
self.setFixedSize(self.width(), self.height())
self.setWindowTitle('LoginClient')
#self.setWindowFlags(Qt.WindowMaximizeButtonHint)
self.backgroud.setPixmap(QPixmap('MainData/background.png'))
self.loginbutton.clicked.connect(self.login)
self.register.clicked.connect(self.registerfunc)
def login(self):
# 获取两个登录窗口的输入框内容
uid = self.uidinput.text()
password = self.passwordinput.text()
hashcode = hashlib.sha256(bytes(f"{uid}{password}", 'utf-8')).hexdigest()
postdata = {
'uid': uid,
'hash': hashcode
}
#login_end_time =time.time()
#login_time =login_end_time - login_start_time
#print(f'login_time:{login_time} seconds')
login_start_time =time.time()
respond = requests.post(f'{SERVER_HOST}/login', data=json.dumps(postdata)).content
login_end_time =time.time()
login_time =login_end_time - login_start_time
print(f'login_time:{login_time} seconds')
respond = json.loads(respond)
if respond['code'] != 1:
# 当返回的状态码不为1时则表示请求错误,并且以警告框提示出错误的描述文本
QMessageBox.critical(self, 'LoginError', f"{respond['message']}", QMessageBox.Ok)
return False
else:
# 登录成功后,在客户端运行的主体中,创建一个公有的字典,并且将用户数据写入到这个字典中,字典的生命周期结束于用户关闭客户端程序窗口
QMessageBox.about(self, 'LoginSuccess', f"登录成功!欢迎您:{uid}")
self.token = respond['token']
userdata['token'] = self.token
userdata['uid'] = uid
userdata['hash'] = hashcode
# self.setuserbox
# Close方法关闭当前窗口,并且清除当前的控件
self.close()
# 启用主窗口
GUIcontroller.runMain()
def registerfunc(self):
# 此函数用于注册
uid = self.uidinput.text()
password = self.passwordinput.text()
if uid == '':
QMessageBox.critical(self, 'registerError', f"账号未输入", QMessageBox.Ok)
return False
if password == '':
QMessageBox.critical(self, 'registerError', f"密码未输入", QMessageBox.Ok)
return False
hashcode = hashlib.sha256(bytes(f"{uid}{password}", 'utf-8')).hexdigest()
postdata = {
'uid': uid,
'hash': hashcode
}
# 此处当用户点击注册,并且账号密码都已输入的同时,则弹出输入框,要求输入邮箱地址用于验证
value, ok = QInputDialog.getText(self, "reg_input_emailAddress", f"请输入您的邮箱地址", QLineEdit.Normal, "")
if not ok:
# 如果选择了取消返回空
return None
else:
# 如果选择了OK,浙江输入的邮箱地址传入到邮箱验证接口中来,验证此邮箱是否已被注册
#regchecker_end_time =time.time()
#regchecker_time =regchecker_end_time - regchecker_start_time
#print(f'regchecker_time:{regchecker_time} seconds')
regchecker_start_time =time.time()
email_isreg_cheaker = requests.post(f'{SERVER_HOST}/regchecker', data=json.dumps({'email': value})).text
regchecker_end_time =time.time()
regchecker_time =regchecker_end_time - regchecker_start_time
print(f'regchecker_time:{regchecker_time} seconds')
print(email_isreg_cheaker)
# 如果返回为false时则表示邮箱已被注册
if email_isreg_cheaker == 'false':
QMessageBox.critical(self, 'registerError', f"此邮箱已被注册", QMessageBox.Ok)
return False
# 随机生成五位验证码
random_code = random.randint(10000, 99999)
sender = user = EMAIL_HOST # 发送方的邮箱账号
passwd = EMAIL_PASSCODE # 授权码
receiver = value # 接收方的邮箱账号,不一定是QQ邮箱
# 纯文本内容
msg = MIMEText(f'您的验证码为{random_code}', 'plain', 'utf-8')
# From 的内容是有要求的,前面的abc为自己定义的 nickname,如果是ASCII格式,则可以直接写
msg['From'] = user
msg['To'] = receiver
msg['Subject'] = f'ALE netdisk 验证' # 点开详情后的标题
try:
# 建立 SMTP 、SSL 的连接,连接发送方的邮箱服务器
smtp = smtplib.SMTP_SSL('smtp.qq.com', 465)
# 登录发送方的邮箱账号
smtp.login(user, passwd)
# 发送邮件 发送方,接收方,发送的内容
smtp.sendmail(sender, receiver, msg.as_string())
print('邮件发送成功')
smtp.quit()
sms_checker, smsok = QInputDialog.getText(self, "reg_input_code",
f"验证码已发送至{value}\n请在下方输入", QLineEdit.Normal, "")
if not smsok:
return None
# 校验验证码
elif sms_checker != str(random_code):
QMessageBox.critical(self, 'registerError', f"验证码错误", QMessageBox.Ok)
return False
else:
# 当验证码正确时,在请求体中写入邮箱
# 邮箱可用性验证在本地进行,同时,为了保证安全性,可以部署在服务器端,由于只需要校验邮箱是否为可用邮箱,所以部署在本地即可
postdata['email'] = value
except Exception as e:
# 出现不可预料的错误,诸如网络因素或者邮箱以及提供的stmp码不正确时打印错误信息
QMessageBox.critical(self, 'registerError', f"{e}", QMessageBox.Ok)
return False
# 将最新构建的post data上传至服务器进行注册登记
#register_end_time =time.time()
#register_time =register_end_time - register_start_time
#print(f'register_time:{register_time} seconds')
register_start_time =time.time()
respond = requests.post(f'{SERVER_HOST}/register', data=json.dumps(postdata)).content
register_end_time =time.time()
register_time =register_end_time - register_start_time
print(f'register_time:{register_time} seconds')
respond = json.loads(respond)
if respond['code'] != 1:
# 当注册返回不为一时表示注册发生了失败,但是请求正常,并非网络因素,则打印错误信息,此处一般情况下错误仅提示用户已注册
QMessageBox.critical(self, 'registerError', f"{respond['message']}", QMessageBox.Ok)
return False
else:
# 注册成功后,把获取的个人信息以及token写入到公共的字典中,此字典的生命周期结束于客户端关闭
QMessageBox.about(self, 'registerSuccess', f"注册成功!欢迎您:{uid}")
self.token = respond['token']
userdata['token'] = self.token
userdata['uid'] = uid
userdata['hash'] = hashcode
self.close()
GUIcontroller.runMain()
class GUIcontroller(object):
# 此类用于控制窗口的切换,防止窗口与窗口之间创建线程或切换时,会引发异常,因此构建一个控制体
def __init__(self) -> None:
pass
def login(self):
self.login_main = LoginGUI()
self.login_main.show()
def runMain():
main_gui = MainGUI()
main_gui.show()
if __name__ == "__main__":
if not os.path.exists('ClientData/'):
os.makedirs('ClientData/')
print('creater')
app = QApplication(sys.argv)
controller = GUIcontroller()
# 初始化控件并且启用登录页
controller.login()
sys.exit(app.exec())
server.py
import os
import hashlib
from typing import List
from fastapi import FastAPI,Body,UploadFile,File,Request,Form
from starlette.responses import FileResponse
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad,unpad
from Crypto import Random
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5 as PKCS1_cipher
import base64
import threading
import rsa
import time
import logging
import uvicorn
import pickle
import json
from pathlib import Path
import socket
from datetime import datetime
import secrets
import os
import platform
import ctypes
import shutil
server = FastAPI()
class webserver(object):
#构架一个web接口类,分布开发服务器api接口
def __init__(self) -> None:
uvicorn.run(host=f'{api.getlocalip()}',port=8000,app=server)
@server.get('/status')
async def status():
return {
'code':200,
'message':'服务存活'
}
@server.post('/CancelAccount')
async def status(reqbody=Body(...)):
try:
uid = reqbody['uid']
except Exception as err:
return {
'code':-1,
'message':f'请求body错误\n{err}'
}
try:
shutil.rmtree(f'user/{uid}/')
return '注销成功,用户数据已全部清除'
except Exception as err:
return {
'code':-3,
'message':f'此用户不存在,请注册-{err}'
}
@server.post('/logging')
async def status(reqbody=Body(...)):
try:
uid = reqbody['uid']
except Exception as err:
return {
'code':-1,
'message':f'请求body错误\n{err}'
}
if not os.path.exists(f'user/{uid}/logging.txt'):
with open(f'user/{uid}/logging.txt','w') as log:
log.write('')
log.close()
with open(f'user/{uid}/logging.txt','r') as orilog:
orilogfile = orilog.read()
orilog.close()
with open(f'user/{uid}/logging.txt','w') as log:
newlog = str(orilogfile) + '\n' + reqbody['log']
log.write(newlog)
log.close()
@server.get('/diskstatus')
async def status():
return {
'code':200,
#调用封装的接口,获取磁盘剩余可用空间
'data':api.get_free_space_mb()#此处可传参传入盘符,默认c盘
}
@server.post('/regchecker')
async def regie(reqbody=Body(...)):
#此接口用于查看邮箱是否被注册
#从传入体中获取传入的邮箱地址
email = reqbody['email']
try:
#尝试去打开验证文件,如果报错则为文件不存在,返回Ture的同时写入一份默认的文件
with open('email_isreg.pickle','rb') as r:
#pickle载入文件,文件内为一个序列化的列表
data = pickle.load(r)
#关闭释放文件占用
r.close()
for t in data:
#从已存在的邮箱地址中逐一取值,遍历每一个进行比较,如果有任何一个与传入的邮箱一致,那么判断已被注册,返回False
if email == t:
return False
else:
pass
#如果没有一个与之匹配者返回ture表示邮箱未被注册
return True
except:
#这里是当文件不存在时,写入一份默认的文件
with open('email_isreg.pickle','wb') as w:
default_data = ['test@qq.com']
pickle.dump(default_data,w)
w.close()
#同时返回ture表达此邮箱未被注册
return True
@server.post('/register')
async def register(reqbody=Body(...)):
#如下的代码有许多都用到了空文件判断,由于各个接口的需求不同,并未封装成函数,实际需要实践的思想都一样,就是当文件或目录不存在时,新建一个默认的文件以及创建目录,并且写入相应的文件
if not os.path.exists(f"user/{reqbody['uid']}/"):
os.makedirs(f"user/{reqbody['uid']}/")
os.makedirs(f"user/{reqbody['uid']}/file/")
with open(f"user/{reqbody['uid']}/{reqbody['uid']}.pickle",'wb') as sv:
#user/{username}/{username}.pickle是一份存放了用户UID,hash code以及他的临时token还有他的邮箱地址的文件
#调用封装好的token构建方法,传入UID以及hash code来新建一个可用的token,并且发送到客户端,允许其进行上传下载,重命名等操作,Token的生命周期结束于下一次登录时刷新
token = api.token_creator(data={
'uid':reqbody['uid'],
'hash':reqbody['hash']
})
#这份字典用于保存用户数据
savedata = {
'uid':reqbody['uid'],
'email':reqbody['email'],
'hash':reqbody['hash'],#hashlib.sha256((str(reqbody['uid'])+str(reqbody['password'])).encode('utf-8')).hexdigest(),
'token':token
}
#pickle dump数据到文件中
pickle.dump(savedata,sv)
#close方法为了解除文件占用,方便下一次开启不出现文件读出为空或报错文件占用等问题,后面不再赘述
sv.close()
with open('email_isreg.pickle','rb') as r:
#此处不使用try语句,是由于当emailchecker接口被调用时,就已经创建了默认文件,此处无需判断文件是否存在
#载入一份旧的数据,并且在此基础上追加,以保证数据完整性
data = pickle.load(r)
r.close()
with open('email_isreg.pickle','wb') as w:
#在旧数据基础上追加并且保存
data.append(reqbody['email'])
pickle.dump(data,w)
w.close()
return {
'code':1,
'message':'注册成功',
'token':token
}
else:
#当文件已存在时表示,此用户文件已创建意味着账号已被创建,则返回数据表明拒绝注册
return {
'code':-4,
'message':'此用户已被注册'
}
@server.post('/filelist')
async def filelist(reqbody = Body(...)):
try:
with open(f"user/{reqbody['uid']}/{reqbody['uid']}.pickle",'rb') as ck:
fl = pickle.load(ck)
ck.close()
uid = reqbody['uid']
token = reqbody['token']
if not token == fl['token']:
return {
'code':-5,
'message':'token失效或账户不存在'
}
else:
return {
'code':1,
'message':'请求成功',
'data':os.listdir(f"user/{uid}/file")
}
except Exception as err:
return {
'code':-5,
'message':'token失效或账户不存在\n'+str(err)
}
@server.post("/uploadFiles")
#UploadFile好处:支持大文件,对内存压力小
async def update_item(file: UploadFile = File(...),data=Form(...)):
'''#列表推导式排出上传的文件列表
#无论本地端是否实现了多线程上传都支持多文件同时上传
lists = [i.filename for i in files]
'''
#这部分由于传入文件时Requests会把文件的字节以及传入的字典都封装到一起,造成没办法分离解析,只能通过另开from表单来获取,因而需要使用json载入字典
reqbody = json.loads(data)
uid = reqbody['uid']
token = reqbody['token']
#调用封装的接口判断令牌是否允许使用,如果可用,则返回ture
if not api.token_acceptuse(uid,token):
return {
'code':-1,
'message':'token已过期或uid不存在'
}
#用户的存储文件目录
with open(f"user/{uid}/file/{file.filename}",'wb') as f:
#await 异步等待文件读取 type:bytes
filedata = await file.read()
#调用封装接口获取用户的hashcode
hashcode = api.get_user_hashcode(uid=uid)
#args: usetype(encode/decode):str,data[userid,hash,fileid,file:bytes])
#调用aes工具接口选择加密,并且依次传入文件的bytes,以及用户的标识等数据
encode_data = api.aestool_func(usetype='encode',data=[uid,hashcode,file.filename,filedata])
#接口返回加密后的数据,写入到已打开的文件中,存储于服务端,此时实现了文件的加密存储
f.write(encode_data)
f.close()
return {
'status':f'已成功上传:{file.filename}'
}
@server.post('/rmFiles')
async def rmFile(reqbody=Body(...)):
#此接口具备身份校验,有且仅允许用户移出临时文件
#当下载结束后允许删除临时完整文件
with open(f"user/{reqbody['uid']}/{reqbody['uid']}.pickle",'rb') as ck:
fl = pickle.load(ck)
ck.close()
uid = reqbody['uid']
token = reqbody['token']
if not token == fl['token']:
return {
'code':-5,
'message':'token失效或账户不存在'
}
else:
try:
os.remove(f"user/{reqbody['uid']}/origin_{reqbody['filename']}")
except:
pass
return "Success"
@server.post('/downloadFiles')
async def download(reqbody=Body(...)):
#此接口用于提供用户下载文件
with open(f"user/{reqbody['uid']}/{reqbody['uid']}.pickle",'rb') as ck:
fl = pickle.load(ck)
ck.close()
uid = reqbody['uid']
token = reqbody['token']
if not token == fl['token']:
return {
'code':-5,
'message':'token失效或账户不存在'
}
else:
#token可用后的操作
with open(f"user/{uid}/file/{reqbody['filename']}",'rb') as r:
#await 异步等待文件读取 type:bytes
#此处用于逆操作解密文件
filedata = r.read()
hashcode = api.get_user_hashcode(uid=uid)
#args: usetype(encode/decode):str,data[userid,hash,fileid,file:bytes])
decode_data = api.aestool_func(usetype='decode',data=[uid,hashcode,reqbody['filename'],filedata])
r.close()
with open(f"user/{uid}/origin_{reqbody['filename']}",'wb') as f:
#将解密后的bytes写入到一份临时文件,并且推送回客户端,当客户端完成下载后,请求rmFile接口移除临时文件
f.write(decode_data)
f.close()
#构建file response返回体
fr = FileResponse(
path=f"user/{uid}/origin_{reqbody['filename']}",
filename=reqbody['filename']
)
#返回文件返回体
return fr
'''except Exception as err:
return {
'code':-5,
'message':'token失效或文件不存在或账户不存在\n'+str(err)
}'''
@server.post('/deleteFiles')
async def delete(reqbody=Body(...)):
#此接口允许用户在完成身份校验之后删除自己已有的指定文件
#作用范围仅限于user/{username}/file目录下
with open(f"user/{reqbody['uid']}/{reqbody['uid']}.pickle",'rb') as ck:
fl = pickle.load(ck)
ck.close()
uid = reqbody['uid']
token = reqbody['token']
if not token == fl['token']:
return {
'code':-5,
'message':'token失效或账户不存在'
}
else:
try:
#完成令牌可用心校验后调用os.remove方法移出文件
os.remove(f"user/{reqbody['uid']}/file/{reqbody['filename']}")
os.remove(f"RSA\{reqbody['uid']}_RSAprivate_{reqbody['filename']}.pem")
os.remove(f"user\{uid}\{uid}_aeskey_{reqbody['filename']}.pickle")
except Exception as err:
#如果remove方法报错,则返回错误信息
return {
'code':-5,
'message':f'移除失败:{err}'
}
#若未触发except则返回success表达移除成功
return "Success"
@server.post('/renameFiles')
async def rename(reqbody=Body(...)):
#此接口允许用户在完成身份校验后,对指定文件进行重命名
with open(f"user/{reqbody['uid']}/{reqbody['uid']}.pickle",'rb') as ck:
fl = pickle.load(ck)
ck.close()
uid = reqbody['uid']
token = reqbody['token']
if not token == fl['token']:
return {
'code':-5,
'message':'token失效或账户不存在'
}
else:
try:
#身份校验通过后则重命名指定文件与其对应的aes密钥文件
os.rename(f"user/{reqbody['uid']}/file/{reqbody['filename']}",f"user/{reqbody['uid']}/file/{reqbody['update']}")
os.rename(f"RSA\{reqbody['uid']}_RSAprivate_{reqbody['filename']}.pem",f"RSA\{reqbody['uid']}_RSAprivate_{reqbody['update']}.pem")
os.rename(f"user/{reqbody['uid']}/{reqbody['uid']}_aeskey_{reqbody['filename']}.pickle",f"user/{reqbody['uid']}/{reqbody['uid']}_aeskey_{reqbody['update']}.pickle")
old_keydata = api.get_user_aeskey_oridata(reqbody['uid'],reqbody['update'])[reqbody['filename']]
#此处打开旧的aes密钥文件,并且将更新后的文件名写入,密钥照旧,否则无法解密文件
with open(f"user\{uid}\{uid}_aeskey_{reqbody['update']}.pickle",'wb') as r:
pickle.dump({
reqbody['update']:old_keydata
},r)
r.close()
except Exception as err:
return {
'code':-5,
'message':f'修改失败:{err}'
}
return "Success"
@server.post('/login')
async def login(reqbody=Body(...)):
#此处为用户登录接口,当账号密码可用,并且hash code校验成功时,则给用户推送token,并刷新在服务端的令牌
try:
uid = reqbody['uid']
req_hash = reqbody['hash']
except Exception as err:
return {
'code':-1,
'message':f'请求body错误\n{err}'
}
try:
with open(f'user/{uid}/{uid}.pickle','rb') as usrfile:
userdata = pickle.load(usrfile)
usrfile.close()
#sha256校验
#由于其他接口的需求变动,请求头则默认都附带hashcode,则无需服务端再次生成
hash_userpsw = userdata['hash']
#req_userpsw = hashlib.sha256((str(uid)+str(password)).encode('utf-8')).hexdigest()
if not hash_userpsw == req_hash:
#哈希校验失败时返回账号或密码错误
return {
'code':-2,
'message':'帐号或密码错误'
}
#哈希校验通过时则允许创建新的token并刷新在服务端的令牌,且推送给用户
token = api.token_creator({
'uid':uid,
'hash':req_hash
})
#在用户个人信息文件中刷新token
userdata['token'] = token
newdata = userdata
with open(f'user/{uid}/{uid}.pickle','wb') as dp:
pickle.dump(newdata,dp)
#保存新的更新后文件,更新内容仅为刷新token,保存关闭
dp.close()
return {
'code':1,
'message':'登录成功',
'token':token
}
except Exception as err:
#如果报错则返回用户不存在,具体包括为file not found error
return {
'code':-3,
'message':f'此用户不存在,请注册-{err}'
}
#封装了一些复用的工具接口
class api(object):
def __init__(self) -> None:
#启用一个线程用于运行webapi服务,并且初始化目录
if not os.path.exists('user/'):
os.makedirs('user/')
print('creater')
#初始化结束后则使用theading创建运行FastAPI实例
apithread = threading.Thread(target=self.runwebapi)
apithread.start()
def runwebapi(self) -> None:
#此函数为线程承接函数,仅用于启用api服务
task = webserver()
task.run()
def get_user_hashcode(uid:str) -> str:
#尝试打开指定用户文件获取hash code,如果报错为file not found error,则返回空
try:
with open(f'user/{uid}/{uid}.pickle','rb') as userfile:
userdata_oncheck = pickle.load(userfile)
userfile.close()
return userdata_oncheck['hash']
except:
return None
def token_creator(data:dict) -> str:
#token生成函数 使用用户uid pswd与时间戳的sha256值
token_str_origin = f"{data['uid']}{data['hash']}{time.time()}"
#此处的token生成规则为用户ID拼接上hash code再拼接上此时的时间戳
token = hashlib.sha256(token_str_origin.encode('utf-8')).hexdigest()
#生成后返回
return token
def token_acceptuse(uid:str,token:str) -> bool:
#此接口用于判断指定用户以及他的令牌可用性,若可用返回ture
try:
with open(f'user/{uid}/{uid}.pickle','rb') as userfile:
userdata_oncheck = pickle.load(userfile)
if not token == userdata_oncheck['token']:
userfile.close()
return False
userfile.close()
return True
except:
return False
def get_free_space_mb(folder='C:\\'):
"""
获取磁盘剩余空间
:param folder: 磁盘路径 例如 D:\\
:return: 剩余空间 单位 MB
"""
#区分操作系统运行
if platform.system() == 'Windows':
free_bytes = ctypes.c_ulonglong(0)
#ctypes的函数
ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(folder), None, None, ctypes.pointer(free_bytes))
#此处除以两个1024表达返回剩余可用mb
return free_bytes.value / 1024 / 1024
else:
st = os.statvfs(folder)
return st.f_bavail * st.f_frsize / 1024 // 1024
def get_user_aeskey_oridata(uid:str,filename:str) -> dict:
#获取aes密钥文件的原始数据字典
try:
with open(f"user\{uid}\{uid}_aeskey_{filename}.pickle",'rb') as r:
userdata = pickle.load(r)
r.close()
return userdata
except:
return {}
def save_user_aeskey(uid:str,filename:str,aeskey) -> bool:
if not os.path.exists(f"RSA/"):
os.makedirs(f"RSA/")
#os.makedirs(f"RSA/SaveKeys/")
#此函数用于快速存储用户上传文件加密后的aes密钥文件
(public_key, private_key) = rsa.newkeys(2048)
'''with open(f'RSA/{uid}_RSApublic_{filename}.pem', 'wb') as file:
file.write(public_key.save_pkcs1())
file.close()'''
# 将私钥保存到PEM文件中
with open(f'RSA/{uid}_RSAprivate_{filename}.pem', 'wb') as file:
file.write(private_key.save_pkcs1())
file.close()
rsa_output = rsa.encrypt(aeskey, public_key)
with open(f"user\{uid}\{uid}_aeskey_{filename}.pickle",'wb') as w:
oridata = api.get_user_aeskey_oridata(uid=uid,filename=filename)
oridata[filename] = rsa_output
pickle.dump(oridata,w)
print(filename,'保存加密后aeskey')
w.close()
return True
def aestool_func(usetype:str,data:list):
#防止数组越界或超出限定
if len(data) != 4:
raise 'Data ERROR:len(data) != 4'
#这个函数用于便捷处理aes加解密
if usetype == 'encode':
#data[userid,hash,fileid,file:bytes])
key = bytes(secrets.token_hex(16),'utf-8')
#ecb模式生成密钥加密
aes_key_ecb = AES.new(key,AES.MODE_ECB)
#调用方法便捷保存用户aes密钥文件
api.save_user_aeskey(uid=data[0],filename=data[2],aeskey=key)
#获取用户加密后的bytes,encrypt函数中需要pad来补全aes需要的位宽,如不填满位宽则无法加密
encode_return = aes_key_ecb.encrypt(pad(data[3],AES.block_size))
return encode_return
elif usetype == 'decode':
#如果使用模式是decode
with open(f"user\{data[0]}\\file\\{data[2]}",'rb') as r:
#读出加密的文件bytes
orifile = r.read()
r.close()
#获取密钥文件
key = api.get_user_aeskey_oridata(data[0],filename=data[2])[data[2]]
print(key)
with open(f'RSA/{data[0]}_RSAprivate_{data[2]}.pem', 'rb') as file:
private_key_data = file.read()
private_key = rsa.PrivateKey.load_pkcs1(private_key_data)
file.close()
#新建一个解密对象
decrypted_key = rsa.decrypt(key, private_key)
print(decrypted_key)
aes_key = AES.new(decrypted_key,AES.MODE_ECB)
#解密文件
defile = aes_key.decrypt(orifile)
#对加密时使用pad填充的位宽进行消除,使用unpad方法
encode_return = unpad(defile,AES.block_size)
return encode_return
else:
return None
def getlocalip() -> str:
#使用udp包来获取本地ipv4,构建内网测试
try:
#新建一个socket对象
stmp = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
stmp.connect(('8.8.8.8',80))
#对获取的包头进行提取,获取到原始的IPV4,在部分情况下,比gethostname要稳定
hostip = stmp.getsockname()[0]
except Exception as err:
print(f'error by {err}')
finally:
stmp.close()
return hostip
if __name__ == '__main__':
#启动项目
api()
UI_LoginClient.py
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(593, 317)
self.backgroud = QtWidgets.QLabel(Form)
self.backgroud.setGeometry(QtCore.QRect(0, 0, 595, 318))
self.backgroud.setText("")
self.backgroud.setObjectName("backgroud")
self.title = QtWidgets.QLabel(Form)
self.title.setGeometry(QtCore.QRect(0, 0, 611, 71))
self.title.setObjectName("title")
self.uidinput = QtWidgets.QLineEdit(Form)
self.uidinput.setGeometry(QtCore.QRect(0, 130, 321, 41))
self.uidinput.setObjectName("uidinput")
self.passwordinput = QtWidgets.QLineEdit(Form)
self.passwordinput.setGeometry(QtCore.QRect(0, 210, 321, 41))
self.passwordinput.setObjectName("passwordinput")
self.loginbutton = QtWidgets.QPushButton(Form)
self.loginbutton.setGeometry(QtCore.QRect(390, 130, 131, 51))
self.loginbutton.setObjectName("loginbutton")
self.register = QtWidgets.QPushButton(Form)
self.register.setGeometry(QtCore.QRect(390, 210, 131, 41))
self.register.setObjectName("register")
self.label = QtWidgets.QLabel(Form)
self.label.setGeometry(QtCore.QRect(0, 110, 121, 16))
self.label.setObjectName("label")
self.label_2 = QtWidgets.QLabel(Form)
self.label_2.setGeometry(QtCore.QRect(0, 190, 71, 16))
self.label_2.setObjectName("label_2")
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "LoginClient"))
self.title.setText(_translate("Form", "<html><head/><body><p align=\"center\"><span style=\" font-size:28pt; font-weight:1000; text-decoration: underline;\">欢迎使用云服务器加密系统</span></p><p align=\"center\"><span style=\" font-size:10pt; font-weight:500; text-decoration: underline;\">请登录</span></p></body></html>"))
self.loginbutton.setText(_translate("Form", "登录"))
self.register.setText(_translate("Form", "注册"))
self.label.setText(_translate("Form", "在此输入账号"))
self.label_2.setText(_translate("Form", "在此输入密码"))
UI_MainClient.py
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(593, 317)
self.backgroud = QtWidgets.QLabel(Form)
self.backgroud.setGeometry(QtCore.QRect(0, 0, 595, 318))
self.backgroud.setText("")
self.backgroud.setObjectName("backgroud")
self.title = QtWidgets.QLabel(Form)
self.title.setGeometry(QtCore.QRect(0, 0, 611, 71))
self.title.setObjectName("title")
self.uidinput = QtWidgets.QLineEdit(Form)
self.uidinput.setGeometry(QtCore.QRect(0, 130, 321, 41))
self.uidinput.setObjectName("uidinput")
self.passwordinput = QtWidgets.QLineEdit(Form)
self.passwordinput.setGeometry(QtCore.QRect(0, 210, 321, 41))
self.passwordinput.setObjectName("passwordinput")
self.loginbutton = QtWidgets.QPushButton(Form)
self.loginbutton.setGeometry(QtCore.QRect(390, 130, 131, 51))
self.loginbutton.setObjectName("loginbutton")
self.register = QtWidgets.QPushButton(Form)
self.register.setGeometry(QtCore.QRect(390, 210, 131, 41))
self.register.setObjectName("register")
self.label = QtWidgets.QLabel(Form)
self.label.setGeometry(QtCore.QRect(0, 110, 121, 16))
self.label.setObjectName("label")
self.label_2 = QtWidgets.QLabel(Form)
self.label_2.setGeometry(QtCore.QRect(0, 190, 71, 16))
self.label_2.setObjectName("label_2")
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "LoginClient"))
self.title.setText(_translate("Form", "<html><head/><body><p align=\"center\"><span style=\" font-size:28pt; font-weight:1000; text-decoration: underline;\">欢迎使用云服务器加密系统</span></p><p align=\"center\"><span style=\" font-size:10pt; font-weight:500; text-decoration: underline;\">请登录</span></p></body></html>"))
self.loginbutton.setText(_translate("Form", "登录"))
self.register.setText(_translate("Form", "注册"))
self.label.setText(_translate("Form", "在此输入账号"))
self.label_2.setText(_translate("Form", "在此输入密码"))
statistics.sh
#!/bin/sh
# rocedu (http://www.cnblogs.com/rocedu)
# Wechat Public Number: rocedu
clear
echo "//=====Today:====================================="
echo "code summary information:"
find . -name "*.py" -mtime 0 | xargs cat | grep -v ^$ | wc -l
echo "documents summary information:"
find . -name "*.md" -mtime 0 | xargs cat | grep -v ^$ | wc -l
echo ""
echo "//=====This Week:================================="
echo "code summary information:"
find . -name "*.py" -mtime -7| xargs cat | grep -v ^$ | wc -l
echo "documents summary information:"
find . -name "*.md" -mtime -7| xargs cat | grep -v ^$ | wc -l
git log --pretty=format:"%h - %an,%ci: %s " | grep `date +%F --date="-0 days"`
git log --pretty=format:"%h - %an,%ci: %s " | grep `date +%F --date="-1 days"`
git log --pretty=format:"%h - %an,%ci: %s " | grep `date +%F --date="-2 days"`
git log --pretty=format:"%h - %an,%ci: %s " | grep `date +%F --date="-3 days"`
git log --pretty=format:"%h - %an,%ci: %s " | grep `date +%F --date="-4 days"`
git log --pretty=format:"%h - %an,%ci: %s " | grep `date +%F --date="-5 days"`
git log --pretty=format:"%h - %an,%ci: %s " | grep `date +%F --date="-6 days"`
echo ""
echo ""
echo "//=====This Semester:=============================="
echo "code summary information:"
find . -name "*.py"| xargs cat | grep -v ^$ | wc -l
echo "documents summary information:"
find . -name "*.md"| xargs cat | grep -v ^$ | wc -l
echo "commit history:"
git log --pretty=format:"%h - %an,%ci: %s "