基于以下代码修改实现,桌面会话窗口不在显示的最顶层但锁屏倒计时和登陆界面必须要弹出在最顶层。仅给出需修改部分的代码即可。
import tkinter as tk
from tkinter import simpledialog, messagebox, scrolledtext, ttk
import time
import threading
import json
import os
import platform
from cryptography.fernet import Fernet
from pynput import mouse, keyboard
import sys
import subprocess
import csv
import pandas as pd
# 获取当前脚本所在目录作为基础路径
if getattr(sys, 'frozen', False):
# 打包后的可执行文件路径
BASE_DIR = os.path.dirname(sys.executable)
else:
# 脚本文件路径
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
# 配置参数(常量使用大写)
IDLE_THRESHOLD = 5 # 3分钟空闲时间(秒)
COUNTDOWN_DURATION = 10 # 锁屏倒计时(秒)
KEY_FILE = os.path.join(BASE_DIR, "system_secret.key") # 加密密钥文件名改为绝对路径
LOG_FILE = os.path.join(BASE_DIR, "secure_usage_log.log") # 操作日志文件路径改为绝对路径
ADMIN_PASSWORD = "123" # 管理员密码
LOCK_SCREEN_BG = "#000000" # 锁屏背景色(黑色)
LOGIN_BG = "#2c3e50" # 登录界面背景色(深蓝色)
BUTTON_BG = "#3498db" # 按钮背景色(亮蓝色)
USER_FILE = os.path.join(BASE_DIR, "培训通过人员名单.csv") # 改为绝对路径
USER_MANAGEMENT_PASSWORD = ADMIN_PASSWORD # 用户管理密码设置为管理员密码
# 确保BASE_DIR存在
os.makedirs(BASE_DIR, exist_ok=True)
# 培训通过人员名单(工号:密码)
# 修改全局用户加载逻辑
def load_trained_users(file_path):
"""
从CSV文件加载培训通过人员名单
文件格式要求:工号,密码
返回字典 {工号: 密码}
"""
users = {}
try:
# 检查文件是否存在
if not os.path.exists(file_path):
print(f"用户文件 {file_path} 不存在,创建初始管理员账号")
# 创建只包含管理员的初始用户名单
users = {"ADMIN": ADMIN_PASSWORD}
# 保存加密的用户名单
save_user_file(users, file_path)
return users
# 加载加密密钥
if not os.path.exists(KEY_FILE):
key = Fernet.generate_key()
with open(KEY_FILE, "wb") as f:
f.write(key)
with open(KEY_FILE, "rb") as f:
key = f.read()
cipher = Fernet(key)
# 解密用户数据
with open(file_path, "rb") as f:
encrypted_data = f.read()
decrypted_data = cipher.decrypt(encrypted_data)
return json.loads(decrypted_data)
except Exception as e:
print(f"加载用户名单错误: {str(e)}")
# 返回只包含管理员的用户名单
return {"ADMIN": ADMIN_PASSWORD}
# 添加保存用户文件的函数
def save_user_file(users, file_path):
"""保存用户字典到CSV文件"""
try:
# 确保密钥存在
if not os.path.exists(KEY_FILE):
key = Fernet.generate_key()
with open(KEY_FILE, "wb") as f:
f.write(key)
with open(KEY_FILE, "rb") as f:
key = f.read()
cipher = Fernet(key)
# 加密用户数据
encrypted_data = cipher.encrypt(json.dumps(users).encode())
# 新加
os.makedirs(os.path.dirname(file_path) or '.', exist_ok=True)
with open(file_path, "wb") as f:
f.write(encrypted_data)
return True
except Exception as e:
print(f"保存用户文件时出错: {str(e)}")
return False
# 从文件加载培训通过人员名单
TRAINED_USERS = load_trained_users(USER_FILE)
# 确保管理员账户存在
if "ADMIN" not in TRAINED_USERS:
TRAINED_USERS["ADMIN"] = ADMIN_PASSWORD
save_user_file(TRAINED_USERS, USER_FILE)
class SecureDesktopMonitor:
def __init__(self):
# 创建主窗口
self.root = tk.Tk() # 创建主窗口对象
self.root.title("安全桌面监控系统") # 设置窗口标题
self.root.geometry("800x600") # 初始窗口尺寸
self.root.configure(bg=LOGIN_BG) # 设置背景色
# 设置窗口在最顶层
self.root.attributes("-topmost", True)
# 系统状态变量,防止用户绕过监控
self.current_user = None # 当前登录用户(未登录时为None)
self.login_time = 0 # 登陆时间(初始为0)
self.last_activity = time.time() # 最后活动时间(初始化为当前时间)
self.idle_timer = None # 空闲检测定时器(用于推迟锁屏)
self.countdown_timer = None # 倒计时定时器(锁屏前提示)
self.listening = False # 监听器状态标志
self.is_locked = False # 界面锁屏状态(初始为True,即未登录时锁定)
# 创建鼠标键盘监听器
self.mouse_listener = mouse.Listener(on_move=self.activity_detected) # 鼠标移动触发活动检测
self.keyboard_listener = keyboard.Listener(on_press=self.activity_detected) # 键盘按键触发活动检测
# 显示初始登录界面
self.show_login_screen()
# Tkinter事件循环(阻塞式,保持程序运行),mainloop()是Tkinter的核心,用于处理用户事件(如点击、输入)
self.root.mainloop()
def show_login_screen(self):
"""显示登录界面"""
self.clear_screen() # 清空当前屏幕所有内容
self.is_locked = True # 标记系统为锁定状态
# 设置全屏模式(防止用户切换窗口)
self.root.attributes('-fullscreen', True)
# 创建居中主框架
main_frame = tk.Frame(self.root, bg=LOGIN_BG) # bg颜色深蓝色
main_frame.place(relx=0.5, rely=0.5, anchor=tk.CENTER) # 使用place布局精确居中
# 系统标题标签
title = tk.Label(
main_frame,
text="安全桌面登录",
font=("Arial", 24, "bold"), # 字体族、大小、粗体
fg="white", # 前景色(白色)
bg=LOGIN_BG # 背景色与界面一致
)
title.pack(pady=20) # 打包布局,垂直间距20像素
# 登录表单框架(使用grid布局对齐输入框)
form_frame = tk.Frame(main_frame, bg=LOGIN_BG)
form_frame.pack(pady=20)
# 工号输入标签和输入框
tk.Label(
form_frame, # 框架格式
text="工号:",
font=("黑体", 14),
fg="white",
bg=LOGIN_BG
).grid(row=0, column=0, padx=10, pady=10, sticky="e")
self.id_entry = tk.Entry(form_frame, font=("Arial", 14), width=20) # 单行文本输入框
self.id_entry.grid(row=0, column=1, padx=10, pady=10)
self.id_entry.focus_set() # 自动聚焦到工号输入框(提升用户体验)
# 密码输入标签和输入框(内容显示为*号)
tk.Label(
form_frame,
text="密码:",
font=("黑体", 14),
fg="white",
bg=LOGIN_BG
).grid(row=1, column=0, padx=10, pady=10, sticky="e")
self.pw_entry = tk.Entry(form_frame, show="*", font=("Arial", 14), width=20) # show="*" 隐藏输入
self.pw_entry.grid(row=1, column=1, padx=10, pady=10) # 放置位置
# 绑定回车键(用户输入密码后按回车可直接登录)
self.pw_entry.bind("<Return>", lambda event: self.authenticate_user())
# 登录按钮
login_btn = tk.Button(
form_frame,
text="登 录",
command=self.authenticate_user, # 点击时触发认证方法
font=("黑体", 18, "bold"),
bg=BUTTON_BG, # 按钮背景色
fg="white", # 按钮文字颜色
width=15,
height=2
)
login_btn.grid(row=2, column=0, columnspan=2, pady=20) # 放置位置跨两列居中
# 系统信息
sys_info = tk.Label(
main_frame,
text="用户使用时长监测系统 | 仅限授权人员使用",
font=("黑体", 10),
fg="#bdc3c7",
bg=LOGIN_BG
)
sys_info.pack(side=tk.BOTTOM, pady=10)
def authenticate_user(self):
"""验证用户身份(首次登录设置密码)"""
user_id = self.id_entry.get().strip() # 获取用户id和密码
password = self.pw_entry.get().strip()
# 检查是否是管理员登录且用户名单只有管理员
is_only_admin = len(TRAINED_USERS) == 1 and "ADMIN" in TRAINED_USERS
# 检查用户是否存在
if user_id in TRAINED_USERS:
# 如果是管理员且用户名单为空,直接进入用户管理界面
if is_only_admin and user_id == "ADMIN":
self.current_user = user_id
self.login_time = time.time()
self.last_activity = self.login_time
self.is_locked = False
self.manage_users() # 直接进入用户管理
return
# 处理首次登录(密码为空)
if TRAINED_USERS[user_id] == "":
self.set_initial_password(user_id)
return
# 如果id和密码匹配,则正常启动桌面会话
if TRAINED_USERS[user_id] == password:
self.current_user = user_id
self.login_time = time.time()
self.last_activity = self.login_time
self.is_locked = False
self.start_desktop_session() # 开始桌面会话函数
return
else:
messagebox.showerror("访问拒绝", "输入密码有误,请重新输入!")
self.pw_entry.delete(0, tk.END)
return
# 如果用户名单只有管理员,给出特定提示
if is_only_admin:
messagebox.showerror("访问拒绝", "当前只有管理员可以登录系统,请使用管理员账号登录")
else:
messagebox.showerror("访问拒绝", "未授权操作!禁止访问系统!")
# 清空密码框
self.pw_entry.delete(0, tk.END)
# 新用户首次登陆设置密码
def set_initial_password(self, user_id):
"""新用户首次登录设置密码"""
dialog = tk.Toplevel(self.root)
dialog.title("设置初始密码")
dialog.geometry("300x200")
dialog.transient(self.root)
dialog.grab_set()
# 窗口居中设置
screen_width = dialog.winfo_screenwidth()
screen_height = dialog.winfo_screenheight()
x = (screen_width - 300) // 2
y = (screen_height - 200) // 2
dialog.geometry(f"300x200+{x}+{y}")
tk.Label(dialog, text=f"欢迎新用户 {user_id}").pack(pady=(10, 0))
tk.Label(dialog, text="请设置您的初始密码:").pack(pady=(10, 0))
password_entry = tk.Entry(dialog, show="*", width=20)
password_entry.pack()
tk.Label(dialog, text="确认密码:").pack(pady=(10, 0))
confirm_entry = tk.Entry(dialog, show="*", width=20)
confirm_entry.pack()
def save_password():
password = password_entry.get().strip()
confirm = confirm_entry.get().strip()
if not password:
messagebox.showerror("错误", "密码不能为空", parent=dialog)
return
if password != confirm:
messagebox.showerror("错误", "两次输入的密码不一致", parent=dialog)
return
# 更新密码
TRAINED_USERS[user_id] = password
save_user_file(TRAINED_USERS, USER_FILE)
# 完成设置
dialog.destroy()
messagebox.showinfo("成功", "密码设置成功!")
# 自动登录
self.current_user = user_id
self.login_time = time.time()
self.last_activity = self.login_time
self.is_locked = False
self.start_desktop_session()
tk.Button(dialog, text="保存", command=save_password).pack(pady=10)
def start_desktop_session(self):
"""开始桌面会话"""
self.clear_screen()
self.root.attributes('-fullscreen', False)
# 设置悬浮窗口属性
self.root.overrideredirect(True) # 移除窗口边框和标题栏
self.root.attributes("-topmost", False) # 取消置顶显示
self.root.geometry("300x300")
self.root.title(f"使用中 - 用户: {self.current_user}")
# 关键修改:设置悬浮窗口背景色和透明度
self.root.configure(bg="#f0f0f0") # 浅灰色背景
# 移除窗口关闭功能
self.root.protocol("WM_DELETE_WINDOW", lambda: None)
# 停止可能存在的旧监听器
self.stop_activity_monitoring()
# 启动新的监听
self.start_activity_monitoring()
# 显示桌面内容
desktop_frame = tk.Frame(self.root, bg="#f0f0f0") # 设置内部框架同样背景色
desktop_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=50) # expand可将组件由其势力范围扩大到扩展范围
# 欢迎信息(简化显示)
welcome_msg = tk.Label(
desktop_frame,
text=f"用户: {self.current_user}",
font=("Arial", 14, "bold"),
pady=10
)
welcome_msg.pack()
# 使用时间显示
self.time_label = tk.Label(
desktop_frame,
text="使用时间: 00:00",
font=("黑体", 10)
)
self.time_label.pack(pady=5)
# 手动锁定按钮
lock_btn = tk.Button(
desktop_frame,
text="锁定系统",
command=self.lock_system,
font=("黑体", 10),
bg="#e74c3c",
fg="white"
)
lock_btn.pack(pady=10)
# ⭐管理员按钮组框架
admin_frame = tk.Frame(desktop_frame)
admin_frame.pack(pady=5)
# 管理员查看记录按钮(仅管理员可见)
if self.current_user == "ADMIN":
admin_btn = tk.Button(
admin_frame,
text="查看使用记录",
command=self.admin_view,
font=("黑体", 10),
bg="#F5FFFA",
fg="#e74c3c"
)
admin_btn.pack(side=tk.LEFT, padx=5)
# 新增:管理用户名单按钮(仅管理员可见)
manage_users_btn = tk.Button(
admin_frame,
text="管理用户名单",
command=self.manage_users,
font=("黑体", 10),
bg="#F5FFFA",
fg="#e74c3c"
)
manage_users_btn.pack(side=tk.LEFT, padx=5)
# 启动鼠标键盘事件监听
self.start_activity_monitoring() # 鼠标键盘监听函数
# 开始空闲检测
self.start_idle_monitor()
# 开始更新使用时间显示
self.update_usage_time()
# 管理员管理培训通过名单⭐
def manage_users(self):
"""管理员管理用户名单"""
# 如果是首次登录(只有管理员),跳过密码验证
if len(TRAINED_USERS) > 1 or "ADMIN" not in TRAINED_USERS:
# 验证管理员密码
password = simpledialog.askstring("❗", "请输入管理员密码:", show='*')
if password != ADMIN_PASSWORD:
messagebox.showerror("认证失败", "密码错误!")
return
# 如果是首次登录(只有管理员),显示特殊提示
is_only_admin = len(TRAINED_USERS) == 1 and "ADMIN" in TRAINED_USERS
self.clear_screen()
self.root.title("用户名单管理")
self.root.geometry("800x600")
# 标题
title = tk.Label(
self.root,
text="用户名单管理",
font=("Arial", 24, "bold"),
pady=20,
bg=LOGIN_BG,
fg="white"
)
title.pack()
# 如果是首次登录,显示提示信息
if is_only_admin:
prompt = tk.Label(
self.root,
text="首次使用请添加至少一个普通用户账号",
font=("黑体", 14),
fg="red",
pady=10
)
prompt.pack()
# 框架容器
container = tk.Frame(self.root)
container.pack(fill=tk.BOTH, expand=True, padx=20, pady=10) # both指定容器在X和Y两个方向上填充父容器分配的空间, expand容器会随着父容器的扩大而扩展, pady设置容器的外间距(边距)
# 创建Treeview显示用户(只显示工号)
columns = ("工号",) # 单元素元组需要加逗号
self.user_tree = ttk.Treeview(container, columns=columns, show="headings", selectmode="browse")
# 设置列标题
for col in columns:
self.user_tree.heading(col, text=col)
self.user_tree.column(col, width=100, anchor=tk.CENTER)
# 添加滚动条
scrollbar = ttk.Scrollbar(container, orient=tk.VERTICAL, command=self.user_tree.yview)
self.user_tree.configure(yscroll=scrollbar.set)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.user_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# 刷新用户列表
self.refresh_user_list()
# 按钮框架
btn_frame = tk.Frame(self.root)
btn_frame.pack(pady=20)
# 添加用户按钮
add_btn = tk.Button(
btn_frame,
text="添加用户",
command=self.add_user,
font=("黑体", 12),
bg="#2ecc71",
fg="white"
)
add_btn.pack(side=tk.LEFT, padx=10)
# 删除用户按钮
delete_btn = tk.Button(
btn_frame,
text="删除用户",
command=self.delete_user,
font=("黑体", 12),
bg="#e74c3c",
fg="white"
)
delete_btn.pack(side=tk.LEFT, padx=10)
# 导出用户名单按钮
export_users_btn = tk.Button(
btn_frame,
text="导出文件",
command=self.export_users_csv,
font=("黑体", 12),
bg=BUTTON_BG,
fg="white"
)
export_users_btn.pack(side=tk.LEFT, padx=10)
# 返回按钮
back_btn = tk.Button(
btn_frame,
text="返回桌面",
command=self.start_desktop_session,
font=("黑体", 12),
bg=BUTTON_BG,
fg="white"
)
back_btn.pack(side=tk.LEFT, padx=10)
def refresh_user_list(self):
"""刷新用户列表显示(只显示工号)"""
# 清除现有数据
for item in self.user_tree.get_children():
self.user_tree.delete(item)
# 添加用户数据(只显示工号)
for user_id in TRAINED_USERS.keys():
self.user_tree.insert("", tk.END, values=(user_id,))
def add_user(self):
"""添加新用户(只添加工号,密码为空)"""
dialog = tk.Toplevel(self.root)
dialog.title("添加新用户")
dialog.geometry("200x150")
dialog.transient(self.root)
dialog.grab_set()
# 窗口剧中设置
screen_width = dialog.winfo_screenwidth()
screen_height = dialog.winfo_screenheight()
x = (screen_width - 200) // 2
y = (screen_height - 150) // 2
dialog.geometry(f"200x150+{x}+{y}")
# 只要求输入工号(不需要密码)
tk.Label(dialog, text="工号:").pack(pady=(20, 0))
id_entry = tk.Entry(dialog, width=20)
id_entry.pack()
# tk.Label(dialog, text="密码:").pack(pady=(10, 0))
# password_entry = tk.Entry(dialog, show="*", width=20)
# password_entry.pack()
def save_new_user():
user_id = id_entry.get().strip()
if not user_id:
messagebox.showerror("错误", "工号不能为空", parent=dialog)
return
if user_id in TRAINED_USERS:
messagebox.showerror("错误", "该工号已存在", parent=dialog)
return
# 添加到全局用户列表(密码初始化为空字符串)
TRAINED_USERS[user_id] = "" # 设置初始密码为空
save_user_file(TRAINED_USERS, USER_FILE)
# 刷新显示
self.refresh_user_list()
dialog.destroy()
messagebox.showinfo("成功", f"用户 {user_id} 添加成功")
tk.Button(dialog, text="保存", command=save_new_user).pack(pady=20)
def delete_user(self):
"""删除选中用户"""
selected = self.user_tree.selection()
if not selected:
messagebox.showerror("错误", "请先选择一个用户")
return
item = selected[0]
values = self.user_tree.item(item, "values")
user_id = values[0]
if user_id == "ADMIN":
messagebox.showerror("错误", "不能删除管理员账户")
return
if messagebox.askyesno("确认", f"确定要删除用户 {user_id} 吗?"):
# 从全局用户列表中删除
if user_id in TRAINED_USERS:
del TRAINED_USERS[user_id]
save_user_file(TRAINED_USERS, USER_FILE)
self.refresh_user_list()
messagebox.showinfo("成功", f"用户 {user_id} 已删除")
def update_usage_time(self):
"""更新使用时间显示"""
if not self.is_locked: # 如果没有锁
usage_seconds = time.time() - self.login_time
minutes, seconds = divmod(int(usage_seconds), 60)
self.time_label.config(text=f"使用时间: {minutes:02d}:{seconds:02d}")
self.root.after(1000, self.update_usage_time) # 1s后更新时间显示
def start_activity_monitoring(self):
"""启动鼠标键盘事件监听"""
# 如果已有监听器在运行,先停止
if self.listening:
self.stop_activity_monitoring()
# 创建新的监听器实例
self.mouse_listener = mouse.Listener(on_move=self.activity_detected)
self.keyboard_listener = keyboard.Listener(on_press=self.activity_detected)
# 启动线程
mouse_thread = threading.Thread(target=self.mouse_listener.start)
keyboard_thread = threading.Thread(target=self.keyboard_listener.start)
mouse_thread.daemon = True
keyboard_thread.daemon = True
mouse_thread.start()
keyboard_thread.start()
self.listening = True
def stop_activity_monitoring(self):
"""停止鼠标键盘事件监听"""
if self.listening:
if self.mouse_listener:
self.mouse_listener.stop()
if self.keyboard_listener:
self.keyboard_listener.stop()
self.mouse_listener = None
self.keyboard_listener = None
self.listening = False
def activity_detected(self, *args):
"""检测到用户活动"""
if not self.is_locked: # 系统未被锁定
self.last_activity = time.time() # 更新最后活动时间
# ⭐ 修改点1:确保在倒计时期间检测到活动时重新启动监听
if self.countdown_timer: # 如果倒计时正在进行
self.root.after_cancel(self.countdown_timer)
self.countdown_timer = None
# 重新启动桌面会话和监听
self.stop_activity_monitoring() # 先停止当前监听
self.clear_screen()
self.start_desktop_session() # 这会重新启动监听
def start_idle_monitor(self):
"""监控空闲状态"""
if not self.is_locked:
idle_time = time.time() - self.last_activity
if idle_time > IDLE_THRESHOLD: # 空闲超时
self.start_lock_countdown()
else:
# 每秒检查一次
self.idle_timer = self.root.after(1000, self.start_idle_monitor)
def start_lock_countdown(self):
"""开始锁屏倒计时"""
# 关键修改:在倒计时开始前恢复全屏模式
self.root.overrideredirect(False) # 恢复窗口边框
self.root.attributes('-fullscreen', True) # 设置全屏
self.clear_screen()
self.root.configure(bg=LOCK_SCREEN_BG)
# self.root.attributes('-fullscreen', True)
# 倒计时显示
self.countdown = COUNTDOWN_DURATION
self.countdown_label = tk.Label(
self.root,
text=f"系统将在 {self.countdown} 秒后锁定...",
font=("Arial", 36, "bold"),
fg="red",
bg=LOCK_SCREEN_BG
)
self.countdown_label.place(relx=0.5, rely=0.4, anchor=tk.CENTER)
# 提示信息
prompt = tk.Label(
self.root,
text="检测到系统空闲,移动鼠标或按键取消锁定",
font=("Arial", 20),
fg="#3498db",
bg=LOCK_SCREEN_BG
)
prompt.place(relx=0.5, rely=0.5, anchor=tk.CENTER)
# 用户信息
user_info = tk.Label(
self.root,
text=f"当前用户: {self.current_user}",
font=("Arial", 16),
fg="white",
bg=LOCK_SCREEN_BG
)
user_info.place(relx=0.5, rely=0.6, anchor=tk.CENTER)
# 开始倒计时
self.update_countdown()
def update_countdown(self):
"""更新倒计时显示"""
self.countdown -= 1
self.countdown_label.config(text=f"系统将在 {self.countdown} 秒后锁定...")
if self.countdown <= 0:
self.lock_system()
else: # 每秒检查一次
self.countdown_timer = self.root.after(1000, self.update_countdown)
def lock_system(self, manual=False):
"""锁定系统并保存使用记录"""
logout_time = time.time()
usage_seconds = logout_time - self.login_time
# 格式化使用时间
minutes, seconds = divmod(int(usage_seconds), 60)
usage_time = f"{minutes:02d}:{seconds:02d}"
# 创建记录
record = {
"user_id": self.current_user,
"login_time": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(self.login_time)),
"logout_time": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(logout_time)),
"usage_duration": usage_time
}
# 加密保存记录
self.save_usage_record(record)
# 停止监听和计时器
if self.idle_timer:
self.root.after_cancel(self.idle_timer)
if self.countdown_timer:
self.root.after_cancel(self.countdown_timer)
self.stop_activity_monitoring()
# 重置用户状态
self.current_user = None
self.is_locked = True
# 关键修改:在锁定时恢复全屏模式
self.root.overrideredirect(False) # 恢复窗口边框
self.root.attributes('-fullscreen', True) # 设置全屏
# 显示登录界面
self.show_login_screen()
def save_usage_record(self, record):
"""加密保存使用记录"""
# 生成或加载加密密钥
if not os.path.exists(KEY_FILE): # 检查密钥文件是否存在
key = Fernet.generate_key() # 若不存在(首次运行),Fernet.generate_key()生成一个新的对称加密密钥(256位),并以二进制模式写入文件
with open(KEY_FILE, "wb") as f:
f.write(key)
with open(KEY_FILE, "rb") as f: # 如果已存在,直接读取密钥内容
key = f.read()
cipher = Fernet(key) # 创建Fernet加密器实例.Fernet是AES加密的封装库,提供简单易用的对称加密功能
# 读取已有记录,避免新记录覆盖旧数据
records = []
if os.path.exists(LOG_FILE): # 检查日志文件是否存在
with open(LOG_FILE, "rb") as f: # 如果存在,以二进制模式读取加密数据
encrypted_data = f.read()
decrypted_data = cipher.decrypt(encrypted_data) # 使用cipher.decrypt()解密
records = json.loads(decrypted_data) # 通过json.loads()将JSON字符串解析为Python列表
# 添加新记录
records.append(record)
# 加密保存
encrypted_data = cipher.encrypt(json.dumps(records).encode()) # 将整个记录列表重新加密
with open(LOG_FILE, "wb") as f:
f.write(encrypted_data)
# 管理员管理使用记录⭐
def admin_view(self):
"""管理员查看使用记录"""
password = simpledialog.askstring("❗", "请输入管理员密码:", show='*')
if password == ADMIN_PASSWORD:
records = self.get_usage_records() # 获取使用记录函数
self.display_records(records) # 显示使用记录函数
else:
messagebox.showerror("认证失败", "密码错误!") # 密码框
def get_usage_records(self):
"""获取使用记录(解密)"""
if not os.path.exists(KEY_FILE) or not os.path.exists(LOG_FILE):
return []
try:
with open(KEY_FILE, "rb") as f:
key = f.read()
cipher = Fernet(key)
with open(LOG_FILE, "rb") as f:
encrypted_data = f.read()
decrypted_data = cipher.decrypt(encrypted_data)
return json.loads(decrypted_data)
except:
return []
def display_records(self, records):
"""显示使用记录"""
self.clear_screen()
self.root.title("使用记录 - 管理员视图")
self.root.geometry("1000x700")
# 标题
title = tk.Label(
self.root,
text="电脑使用记录 - 安全报告",
font=("Arial", 24, "bold"),
pady=20,
bg=LOGIN_BG,
fg="white"
)
title.pack()
# 滚动文本框
text_area = scrolledtext.ScrolledText(
self.root,
wrap=tk.WORD,
font=("Consolas", 12),
width=120,
height=30
)
text_area.pack(padx=20, pady=3, fill=tk.BOTH, expand=True)
# 添加表头
header = "工号 登录时间 登出时间 使用时间\n"
text_area.insert(tk.INSERT, header)
text_area.insert(tk.INSERT, "-" * 65 + "\n")
# 添加记录
for record in records:
line = (
f"{record['user_id']:<8} "
f"{record['login_time']:<20} "
f"{record['logout_time']:<20} "
f"{record['usage_duration']:<8}\n"
# f"{record.get('lock_type', '自动'):<8}\n"
)
text_area.insert(tk.INSERT, line)
text_area.configure(state='disabled') # 设为只读
# 添加返回桌面按钮
btn_frame = tk.Frame(self.root)
btn_frame.pack(pady=5) # pady参数在垂直方向(上下)添加10像素的填充,确保与其他界面元素有足够的间距
# 导出记录按钮
tk.Button(
btn_frame,
text="导出文件",
command=lambda: self.export_records_csv(records),
font=("黑体", 12),
bg=BUTTON_BG,
fg="white",
width=15
).pack(side=tk.LEFT, padx=10)
# 返回按钮
tk.Button(
btn_frame,
text="返回桌面",
command=self.start_desktop_session,
font=("黑体", 12),
bg=BUTTON_BG,
fg="white",
width=15
).pack(side=tk.LEFT, padx=10)
#
# tk.Button(
# btn_frame,
# text="返回登录界面",
# command=self.show_login_screen,
# font=("黑体", 12),
# bg=BUTTON_BG,
# fg="white",
# width=20
# ).pack(side=tk.LEFT, padx=10)
# 将使用记录和人员名单导出
def export_records_csv(self, records):
"""导出使用记录为CSV文件"""
try:
# 获取当前时间作为文件名
timestamp = time.strftime("%Y%m%d_%H%M%S", time.localtime())
filename = f"使用记录_{timestamp}.csv"
with open(filename, 'w', newline='', encoding='utf-8') as file:
writer = csv.writer(file)
# 写入表头
writer.writerow(["工号", "登录时间", "登出时间", "使用时间"])
# 写入每条记录
for record in records:
writer.writerow([
record['user_id'],
record['login_time'],
record['logout_time'],
record['usage_duration']
])
# ⭐导出为excel表格
# try:
# # 获取当前时间作为文件名
# timestamp = time.strftime("%Y%m%d_%H%M%S", time.localtime())
# filename = f"使用记录_{timestamp}.xlsx"
#
# # 创建DataFrame
# data = {
# "工号": [record['user_id'] for record in records],
# "登录时间": [record['login_time'] for record in records],
# "登出时间": [record['logout_time'] for record in records],
# "使用时间": [record['usage_duration'] for record in records]
# }
# df = pd.DataFrame(data)
#
# # 导出到Excel
# df.to_excel(filename, index=False, engine='openpyxl')
#
messagebox.showinfo("导出成功", f"使用记录已导出到: {os.path.abspath(filename)}")
except Exception as e:
messagebox.showerror("导出失败", f"导出使用记录时出错: {str(e)}")
def export_users_csv(self):
"""导出用户名单为CSV文件(解密后导出只包含工号)"""
try:
# 获取当前时间作为文件名
timestamp = time.strftime("%Y%m%d_%H%M%S", time.localtime())
filename = f"用户名单_{timestamp}.csv"
with open(filename, 'w', newline='', encoding='utf-8') as file:
writer = csv.writer(file)
# 写入表头(只包含工号)
writer.writerow(["工号"])
# 写入每条记录(只导出工号)
for user_id in TRAINED_USERS.keys():
writer.writerow([user_id])
messagebox.showinfo("导出成功", f"用户名单已解密并导出到: {os.path.abspath(filename)}")
except Exception as e:
messagebox.showerror("导出失败", f"导出用户名单时出错: {str(e)}")
"""导出excel文件"""
# try:
# timestamp = time.strftime("%Y%m%d_%H%M%S", time.localtime())
# filename = f"用户名单_{timestamp}.xlsx"
#
# # 创建dataframe
# data = []
# for user_id, password in TRAINED_USERS.items():
# data.append([user_id, password])
# df = pd.DataFrame(data, columns=["工号", "密码"])
#
# # 导出到excel
# df.to_excel(filename, index=False, engine='openpyxl')
def clear_screen(self):
"""清除当前屏幕所有内容"""
for widget in self.root.winfo_children():
widget.destroy()
def lock_on_close(self):
"""关闭窗口时锁定系统"""
if self.current_user:
self.lock_system(manual=True)
self.root.destroy()
# 启动系统
if __name__ == "__main__":
app = SecureDesktopMonitor()
# 绑定窗口关闭事件
app.root.protocol("WM_DELETE_WINDOW", app.lock_on_close)
最新发布