博客
-
让wifi密码变成二维码
前言
技术原理
WiFi二维码的原理非常简单,它采用的是 WiFi 联盟定义的开放标准格式。只要把你的 WiFi 信息按照下面这个格式排列,任何二维码生成器都能做出来:
WIFI:T:<加密类型>;S:<WiFi名称>;P:<WiFi密码>;H:<是否隐藏>;;- T (Type):加密类型(WPA/WPA2/WPA3 用
WPA,WEP 用WEP,无密码用nopass)。 - S (SSID):你的 WiFi 名字。
- P (Password):你的 WiFi 密码。
- H (Hidden):是否是隐藏网络(一般填
false或不填)。
举个栗子:
假设你家 WiFi 名字是
MyHome,密码是88888888。你需要生成的文本内容就是:
WIFI:T:WPA;S:MyHome;P:88888888;;将这串字符生成为二维码,保存图片,打印出来即可。
- T (Type):加密类型(WPA/WPA2/WPA3 用
-
图书馆座位预约
2.0
""" ============================================================================== @Project : library @File : library.py @Contact : https://github.com/nicepoem/ @License : (C)Copyright 2025, nicepoem @Author : 新一 @Date : 2026.3.16 @Version : 2.0 @Desc : 基于 Python 和 CustomTkinter 的图书馆座位预约系统 @日志:修复晚上预约学号超出时间格式导致线程奔溃的错误 ============================================================================== """ import hashlib import json import logging import os import queue import random import re import sys import threading import time as t from datetime import datetime, timedelta from tkinter import LabelFrame, Listbox, messagebox from tkinter import font as tkfont from typing import Any, Callable, Dict, List, Optional, Tuple, Union import customtkinter as ctk import requests import schedule # ============================================================================= # 全局配置 # ============================================================================= LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' LOG_LEVEL = logging.INFO if getattr(sys, 'frozen', False): BASE_DIR = os.path.dirname(sys.executable) else: BASE_DIR = os.getcwd() logging.basicConfig( filename=os.path.join(BASE_DIR, 'application.log'), level=LOG_LEVEL, format=LOG_FORMAT, encoding='utf-8', filemode='a' ) logger = logging.getLogger(__name__) BASE_URL = 'https://seat.gsupl.edu.cn' API_ENDPOINTS = { 'config': '/interface/readingroom/singleAppConfig', 'reservation_list': '/interface/readingroom/getdespeakdata', 'cancel_reservation': '/interface/readingroom/deletedeskdata', 'check_time': '/interface/readingroom/checktimecanbesk', 'make_booking': '/interface/readingroom/beskdata', 'signin': '/interface/readingroom/beskreadersigin', 'signout': '/interface/readingroom/returntable' } ROOM_MAPPING = { 1: '23', 2: '26', 3: '28', 4: '24', 5: '27' } BOOKING_TIMES = { 'morning': ('07:00:00', '13:50:00'), 'afternoon': ('13:50:01', '22:30:00') } DEFAULT_HEADERS = { "Content-Length": "0", 'Host': 'seat.gsupl.edu.cn', 'Connection': 'Keep-Alive', 'Cookie2': '$Version=1', 'Accept-Encoding': 'gzip' } REQUEST_TIMEOUT = 10 sigin_file_path = "" table_file_path = "" baskid_file_path = "" # ============================================================================= # 工具类 # ============================================================================= class Util: """与图书馆系统交互的工具类。""" def __init__(self) -> None: self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}") self._file_cache: Dict[str, Any] = {} self._cache_timestamps: Dict[str, float] = {} self._cache_timeout: int = 300 self.session = requests.Session() def build_headers(self, jsessionid: Optional[str] = None) -> Dict[str, str]: """构建请求头,必要时附带会话 Cookie。""" headers = DEFAULT_HEADERS.copy() if jsessionid: headers['Cookie'] = jsessionid return headers def http_get( self, url: str, params: Optional[Dict[str, Any]] = None, jsessionid: Optional[str] = None, timeout: int = REQUEST_TIMEOUT, ) -> requests.Response: """发送 GET 请求并返回 Response 对象。""" headers = self.build_headers(jsessionid) return self.session.get(url, headers=headers, params=params, timeout=timeout) def http_post( self, url: str, data: Optional[Dict[str, Any]] = None, jsessionid: Optional[str] = None, timeout: int = REQUEST_TIMEOUT, ) -> requests.Response: """发送 POST 请求并返回 Response 对象。""" headers = self.build_headers(jsessionid) return self.session.post(url, headers=headers, data=data, timeout=timeout) def get_room_no(self, room_no: int) -> str: """根据房间序号返回实际 roomno(接口参数)。""" return ROOM_MAPPING.get(room_no, '27') def get_table_info(self, table_no: str, filepath: str) -> Optional[int]: """从本地 JSON 配置中获取指定座位号对应的 tableid。""" data = self.read_json_file(filepath) return data.get(table_no) if data else None def read_json_file(self, filepath: str) -> Dict[str, Any]: """读取 JSON 文件并使用简单缓存。""" current_time = t.time() if ( filepath in self._file_cache and filepath in self._cache_timestamps and current_time - self._cache_timestamps[filepath] < self._cache_timeout ): return self._file_cache[filepath] try: with open(filepath, 'r', encoding='utf-8') as file: data = json.load(file) self._file_cache[filepath] = data self._cache_timestamps[filepath] = current_time return data except (FileNotFoundError, json.JSONDecodeError) as e: self.logger.error(f"读取文件错误: {filepath}, 错误: {e}") return {} def get_jsessionid(self) -> str: """从配置接口获取 JSESSIONID,失败时返回默认值。""" url = f"{BASE_URL}{API_ENDPOINTS['config']}" try: response = self.http_post(url, timeout=REQUEST_TIMEOUT) if response.status_code == 200: set_cookie = response.headers.get('Set-Cookie') if set_cookie: return set_cookie.split(';')[0] except Exception as e: self.logger.error(f"获取JSESSIONID异常: {e}") return "JSESSIONID=71533C0C9E6F10B43BE710E33837648C" def get_checkstr(self, reader_no: str, table_no: str) -> str: """生成接口所需的 MD5 校验字符串 checkstr。""" try: current_time = datetime.now().strftime('%Y%m%d%H%M') param_string = f"{reader_no}GOLDLIBGDLIS{table_no}CHECKTABLE{current_time}" return hashlib.md5(param_string.encode('utf-8')).hexdigest() except Exception as e: self.logger.error(f"生成校验字符串错误: {e}") raise RuntimeError(f"生成校验字符串失败: {e}") def update_json_file(self, filepath: str, data: Dict[str, Any]) -> bool: """写回 JSON 文件,并清理该文件的缓存。""" try: with open(filepath, 'w', encoding='utf-8') as file: json.dump(data, file, ensure_ascii=False, indent=4) if filepath in self._file_cache: del self._file_cache[filepath] if filepath in self._cache_timestamps: del self._cache_timestamps[filepath] return True except Exception as e: self.logger.error(f"更新JSON文件错误: {e}") return False # ============================================================================= # API管理类 # ============================================================================= class LibraryAPI: """图书馆座位相关 HTTP 接口封装类。""" def __init__(self, util: Util) -> None: self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}") self.util = util def get_reservation_list(self, jsessionid: str, reader_no: str) -> Optional[List[Dict[str, Any]]]: """获取当前读者的预约列表。""" url = f"{BASE_URL}{API_ENDPOINTS['reservation_list']}?readerno={reader_no}" try: response = self.util.http_get(url, jsessionid=jsessionid, timeout=REQUEST_TIMEOUT) if response.status_code == 200: data = response.json() return data.get('Data') if data.get("ReturnValue") == 0 else None except Exception as e: self.logger.error(f"获取预约列表异常: {e}") return None def cancel_reservation(self, jsessionid: str, reader_no: str, password: str) -> Union[bool, str, None]: """取消当前读者的一条预约记录。""" reservation_data = self.get_reservation_list(jsessionid, reader_no) if not reservation_data: return "无法获取预约列表" try: strid = reservation_data[0]['ID'] params = {"password": password, "readerno": reader_no, "strid": strid} url = f"{BASE_URL}{API_ENDPOINTS['cancel_reservation']}" response = self.util.http_get(url, params=params, jsessionid=jsessionid, timeout=REQUEST_TIMEOUT) if response.status_code == 200: data = response.json() return True if data.get("ReturnValue") == 0 else data.get('Msg', '取消预约失败') return False except Exception as e: self.logger.error(f"取消预约异常: {e}") return None def get_can_bask_id(self, jsessionid: str, room_no: str) -> Union[str, None]: """获取当前可预约批次 ID(CanBaskID)。""" url = f"{BASE_URL}{API_ENDPOINTS['check_time']}?roomno={room_no}" try: response = self.util.http_get(url, jsessionid=jsessionid, timeout=REQUEST_TIMEOUT) if response.status_code == 200: data = response.json() return data.get('CanBaskID') if data.get("ReturnValue") == 0 else data.get('Msg', '未知错误') except Exception as e: self.logger.error(f"获取可预约ID异常: {e}") return None def get_bask_id(self, filepath: str, room_no: str) -> Optional[str]: """从本地 JSON 数据中读取缓存的 baskid。""" try: data = self.util.read_json_file(filepath) baskid = data.get("morning", {}).get(room_no) return str(baskid) if baskid is not None else None except Exception as e: self.logger.error(f"获取预约ID异常: {e}") return None def make_booking(self, jsessionid: str, begin_time: str, besk_id: str, check_str: str, end_time: str, reader_no: str, room_no: str, table_id: str, table_no: str) -> Union[Tuple[bool, Union[str, bool]], Tuple[bool, int], None]: params = { "begintime": begin_time, "beskid": besk_id, "checkstr": check_str, "endtime": end_time, "readerno": reader_no, "roomno": room_no, "tableid": table_id, "tableno": table_no } url = f"{BASE_URL}{API_ENDPOINTS['make_booking']}" try: response = self.util.http_get(url, params=params, jsessionid=jsessionid, timeout=REQUEST_TIMEOUT) if response.status_code == 200: data = response.json() return self._parse_booking_result(data) elif response.status_code in (502, 504): return False, response.status_code elif response.status_code == 500: t.sleep(60) return None else: return None except Exception as e: self.logger.error(f"预约异常: {e}") return None def _parse_booking_result(self, data: Dict[str, Any]) -> Tuple[bool, Union[str, bool]]: return_value = data.get("ReturnValue") msg = data.get('Msg', '未知消息') if return_value == 0: return True, msg else: if msg == "您已经预约过该时间段的座位了!无需重复预约": return True, msg elif msg == "读者没有预约权限": return False, False else: return False, msg def send_signin_req(self, readerno: str, password: str, tableno: str, jsessionid: str, checkstr: str) -> Optional[str]: params = { 'checkstr': checkstr, 'password': password, 'readerno': readerno, 'tableno': tableno } url = f"{BASE_URL}{API_ENDPOINTS['signin']}" try: response = self.util.http_get(url, params=params, jsessionid=jsessionid, timeout=REQUEST_TIMEOUT) if response.status_code == 200: data = response.json() return data.get('Msg', '未知消息') return None except Exception as e: self.logger.error(f"签到异常: {e}") return None def send_signout_req(self, readerno: str, password: str, jsessionid: str) -> Optional[str]: params = {'password': password, 'readerno': readerno} url = f"{BASE_URL}{API_ENDPOINTS['signout']}" try: response = self.util.http_get(url, params=params, jsessionid=jsessionid, timeout=REQUEST_TIMEOUT) if response.status_code == 200: data = response.json() return data.get('Msg', '未知消息') return None except Exception as e: self.logger.error(f"签退异常: {e}") return None # ============================================================================= # 线程管理类 # ============================================================================= class ThreadManager: def __init__(self, max_threads: int = 10): self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}") self.result_queue = queue.Queue() self.threads = [] self.lock = threading.RLock() self.max_threads = max_threads self._shutdown = False def start_thread(self, target: Callable, args: Tuple = (), callback: Optional[Callable[[Any], None]] = None) -> bool: if self._shutdown: return False with self.lock: if len(self.threads) >= self.max_threads: return False def thread_target(): try: result = target(*args) self.result_queue.put((result, callback)) except Exception as e: self.logger.error(f"线程执行异常: {e}") self.result_queue.put((None, callback)) try: thread = threading.Thread(target=thread_target, daemon=True, name=f"Worker-{target.__name__}") thread.start() with self.lock: self.threads.append(thread) return True except Exception as e: self.logger.error(f"启动线程失败: {e}") return False def check_results(self) -> int: processed_count = 0 while not self.result_queue.empty(): try: result, callback = self.result_queue.get_nowait() if callback: try: callback(result) except Exception as e: self.logger.error(f"回调函数执行异常: {e}") processed_count += 1 except queue.Empty: break except Exception as e: self.logger.error(f"处理结果异常: {e}") break with self.lock: self.threads = [t for t in self.threads if t.is_alive()] return processed_count def get_thread_count(self) -> int: with self.lock: return len([t for t in self.threads if t.is_alive()]) def shutdown(self, timeout: float = 5.0) -> None: self._shutdown = True with self.lock: for thread in self.threads: if thread.is_alive(): thread.join(timeout=timeout) # ============================================================================= # 主界面类 # ============================================================================= class MainInterface(ctk.CTk): def __init__(self): super().__init__() self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}") self.util = Util() self.api = LibraryAPI(self.util) self.thread_manager = ThreadManager(max_threads=5) self.tabel_pool = [] self.tabel_point = 0 self.is_running = False self.jsessionid = "" self.lock = threading.Lock() self.font_title = ctk.CTkFont(size=16, weight="bold") self.font_label = ctk.CTkFont(size=16) self.font_entry = ctk.CTkFont(size=16) self.font_button = ctk.CTkFont(size=16) self.font_text = ctk.CTkFont(size=16) self.tk_list_font = tkfont.Font(size=16) self._initialize_ui() self._load_configuration() self.after(100, self.process_thread_results) def _initialize_ui(self) -> None: self.title("图书馆座位预约") self.resizable(False, False) self.geometry(self.set_screen(600, 500)) for c in range(2): self.grid_columnconfigure(c, weight=1) for r in range(8): self.grid_rowconfigure(r, weight=0) self.grid_rowconfigure(1, weight=1) self.grid_rowconfigure(2, weight=1) self.grid_rowconfigure(7, weight=1) self._create_info_frame() self._create_time_frame() self._create_checkstr_frame() self._create_seat_frame() self._create_button_frame() self._create_output_box() def _create_info_frame(self): info_frame = LabelFrame(self, width=400, height=200, text="基本信息", font=self.font_label) info_frame.grid(row=0, column=0, padx=5, pady=5, sticky="nsew") info_frame.grid_columnconfigure(1, weight=1) ctk.CTkLabel(info_frame, text="学号:", font=self.font_label).grid(row=0, column=0, padx=10, pady=5, sticky="e") self.readerno_entry = ctk.CTkEntry(info_frame, font=self.font_entry) self.readerno_entry.grid(row=0, column=1, padx=10, pady=5, sticky="ew") ctk.CTkLabel(info_frame, text="密码:", font=self.font_label).grid(row=1, column=0, padx=10, pady=5, sticky="e") self.password_entry = ctk.CTkEntry(info_frame, font=self.font_entry) self.password_entry.grid(row=1, column=1, padx=10, pady=5, sticky="ew") def _create_time_frame(self): time_frame = LabelFrame(self, width=400, height=200, text="签到信息", font=self.font_label) time_frame.grid(row=0, column=1, padx=5, pady=5, sticky="nsew") for c in range(4): time_frame.grid_columnconfigure(c, weight=1) ctk.CTkLabel(time_frame, text="签到1", font=self.font_label).grid(row=0, column=0, pady=5, sticky="e") self.time1_entry = ctk.CTkEntry(time_frame, font=self.font_entry) self.time1_entry.grid(row=0, column=1, padx=5, pady=5, sticky="ew") ctk.CTkLabel(time_frame, text="签到2", font=self.font_label).grid(row=1, column=0, pady=5, sticky="e") self.time2_entry = ctk.CTkEntry(time_frame, font=self.font_entry) self.time2_entry.grid(row=1, column=1, padx=5, pady=5, sticky="ew") ctk.CTkLabel(time_frame, text="签退1", font=self.font_label).grid(row=0, column=2, pady=5, sticky="e") self.time3_entry = ctk.CTkEntry(time_frame, font=self.font_entry) self.time3_entry.grid(row=0, column=3, padx=5, pady=5, sticky="ew") ctk.CTkLabel(time_frame, text="签退2", font=self.font_label).grid(row=1, column=2, pady=5, sticky="e") self.time4_entry = ctk.CTkEntry(time_frame, font=self.font_entry) self.time4_entry.grid(row=1, column=3, padx=5, pady=5, sticky="ew") def _create_checkstr_frame(self): checkstr_frame = LabelFrame(self, width=400, height=200, text="校验码", font=self.font_label) checkstr_frame.grid(row=1, column=0, rowspan=2, padx=5, pady=5, sticky="nsew") checkstr_frame.grid_rowconfigure(0, weight=1) checkstr_frame.grid_columnconfigure(0, weight=1) self.checkstr_entry = ctk.CTkTextbox(checkstr_frame, state='disabled', font=self.font_text) self.checkstr_entry.grid(row=0, column=0, padx=10, pady=6, sticky="nsew") def _create_seat_frame(self): res_frame = LabelFrame(self, width=400, height=300, text="座位池信息", font=self.font_label) res_frame.grid(row=1, column=1, rowspan=2, columnspan=2, padx=5, pady=5, sticky="nsew") for c in range(3): res_frame.grid_columnconfigure(c, weight=1) for r in range(3): res_frame.grid_rowconfigure(r, weight=1) self.tabel_pool_list = Listbox(res_frame, bd=0, highlightthickness=0, relief='flat') try: self.tabel_pool_list.configure(font=self.tk_list_font) except Exception: pass self.tabel_pool_list.grid(row=0, column=0, rowspan=3, padx=10, pady=5, sticky="nsew") ctk.CTkButton(res_frame, text="添加", height=36, command=self._add_seat, font=self.font_button).grid(row=0, column=1, padx=6, pady=5, sticky="ew") ctk.CTkButton(res_frame, text="插入", height=36, command=self._insert_seat, font=self.font_button).grid(row=1, column=1, padx=6, pady=5, sticky="ew") ctk.CTkButton(res_frame, text="删除", height=36, command=self._delete_seat, font=self.font_button).grid(row=0, column=2, padx=6, pady=5, sticky="ew") ctk.CTkButton(res_frame, text="清空", height=36, command=self._clear_seats, font=self.font_button).grid(row=1, column=2, padx=6, pady=5, sticky="ew") def _create_button_frame(self): btn_frame = LabelFrame(self, width=400, height=200, text="执行专区", font=self.font_button) btn_frame.grid(row=6, column=0, columnspan=2, padx=5, pady=5, sticky="ew") for c in range(7): btn_frame.grid_columnconfigure(c, weight=1) self.start_button = ctk.CTkButton(btn_frame, text="定时运行", height=36, command=self.toggle_task, font=self.font_button) self.start_button.grid(row=0, column=0, padx=4, pady=11, sticky="ew") self.start_button.configure(text_color="#33CC33") ctk.CTkButton(btn_frame, text="立即签到", height=36, command=self.signin_now, font=self.font_button).grid(row=0, column=1, padx=4, pady=11, sticky="ew") ctk.CTkButton(btn_frame, text="立即签退", height=36, command=self.signout_now, font=self.font_button).grid( row=0, column=2, padx=4, pady=11, sticky="ew") ctk.CTkButton(btn_frame, text="预约早上", height=36, command=self._booking_morning, font=self.font_button).grid( row=0, column=3, padx=4, pady=11, sticky="ew") ctk.CTkButton(btn_frame, text="预约下午", height=36, command=self._booking_afternoon, font=self.font_button).grid(row=0, column=4, padx=4, pady=11, sticky="ew") ctk.CTkButton(btn_frame, text="取消预约", height=36, command=self.booking_cancel, font=self.font_button).grid( row=0, column=5, padx=4, pady=11, sticky="ew") ctk.CTkButton(btn_frame, text="清空输出", height=36, command=self.clear_output_box, font=self.font_button).grid( row=0, column=6, padx=4, pady=11, sticky="ew") def _create_output_box(self): self.output_box = ctk.CTkTextbox(self, state='disabled', font=self.font_text) self.output_box.grid(row=7, column=0, columnspan=2, padx=10, pady=10, sticky="nsew") def _add_seat(self): tabel_no = self.ask_string("获取座位号", "请输入新的座位号:") if not tabel_no or self.util.get_table_info(tabel_no, table_file_path) is None: self.show_warning("警告", "该座位不存在!") return if tabel_no in self.tabel_pool_list.get(0, 'end'): self.show_warning("警告", "该座位号已存在!") return self.tabel_pool_list.insert(self.tabel_pool_list.size(), tabel_no) def _insert_seat(self): tabel_no = self.ask_string("获取座位号", "请输入新的座位号:") if not tabel_no or self.util.get_table_info(tabel_no, table_file_path) is None: self.show_warning("警告", "该座位不存在!") return if tabel_no in self.tabel_pool_list.get(0, 'end'): self.show_warning("警告", "该座位号已存在!") return if self.tabel_pool_list.curselection(): self.tabel_pool_list.insert(self.tabel_pool_list.curselection(), tabel_no) else: self.tabel_pool_list.insert(0, tabel_no) def _delete_seat(self): if self.tabel_pool_list.curselection(): self.tabel_pool_list.delete(self.tabel_pool_list.curselection()) def _clear_seats(self): self.tabel_pool_list.delete(0, 'end') def _booking_morning(self): if self.is_running: self.write_log("请先点击停止···") return self.update_all_info(sigin_file_path) self.thread_manager.start_thread(self.execute_booking_plus, args=(False, True)) def _booking_afternoon(self): if self.is_running: self.write_log("请先点击停止···") return self.update_all_info(sigin_file_path) self.thread_manager.start_thread(self.execute_booking_plus, args=(True, True)) def ask_string(self, title: str, prompt: str) -> str: dialog = ctk.CTkInputDialog(title=title, text=prompt) return dialog.get_input() def show_warning(self, title: str, message: str) -> None: win = ctk.CTkToplevel(self) win.title(title) win.transient(self) win.grab_set() ctk.CTkLabel(win, text=message, wraplength=260, justify="left").pack(padx=20, pady=20) ctk.CTkButton(win, text="确定", width=80, command=win.destroy).pack(pady=10) self.wait_window(win) def set_screen(self, width, height): screen_width = self.winfo_screenwidth() screen_height = self.winfo_screenheight() window_size = '%dx%d+%d+%d' % (width, height, (screen_width - width) / 2, (screen_height - height) / 2) return window_size def _load_configuration(self) -> None: config_data = self.util.read_json_file(sigin_file_path) self.fill_controls(config_data) if self.tabel_pool: checkstr = self.util.get_checkstr( self.readerno_entry.get(), self.tabel_pool[self.tabel_point] ) self.write_checkstr(checkstr) self.jsessionid = self.util.get_jsessionid() self.write_log(f"初始化获取jsessionid:{self.jsessionid}") def fill_controls(self, data): self.readerno_entry.delete(0, 'end') self.password_entry.delete(0, 'end') self.time1_entry.delete(0, 'end') self.time2_entry.delete(0, 'end') self.time3_entry.delete(0, 'end') self.time4_entry.delete(0, 'end') self.readerno_entry.insert(0, data.get("readerno", "")) self.password_entry.insert(0, data.get("password", "")) self.time1_entry.insert(0, data.get("sigin_time1", "")) self.time2_entry.insert(0, data.get("sigin_time2", "")) self.time3_entry.insert(0, data.get("signout_time1", "")) self.time4_entry.insert(0, data.get("signout_time2", "")) self.tabel_pool = data.get("tables", []) for item in self.tabel_pool: self.tabel_pool_list.insert('end', item) def update_all_info(self, filepath): data = self.util.read_json_file(filepath) if data: data["readerno"] = self.readerno_entry.get() data["password"] = self.password_entry.get() data["sigin_time1"] = self.time1_entry.get() data["sigin_time2"] = self.time2_entry.get() data["signout_time1"] = self.time3_entry.get() data["signout_time2"] = self.time4_entry.get() self.tabel_pool.clear() self.tabel_pool = list(self.tabel_pool_list.get(0, 'end')) data["tables"] = self.tabel_pool self.util.update_json_file(filepath, data) self.write_log("成功更新所有信息至文件!") def write_checkstr(self, checkstr) -> str: self.checkstr_entry.configure(state='normal') self.checkstr_entry.delete('1.0', 'end') self.checkstr_entry.insert('end', checkstr) self.checkstr_entry.configure(state='disabled') return checkstr def write_log(self, message): timestamp = datetime.now().strftime("%H:%M:%S") log_line = f"[{timestamp}] {message}" self.output_box.configure(state='normal') self.output_box.insert('end', log_line + "\n") self.output_box.see('end') self.output_box.configure(state='disabled') def clear_output_box(self): self.output_box.configure(state='normal') self.output_box.delete('1.0', 'end') self.output_box.configure(state='disabled') def process_thread_results(self): self.thread_manager.check_results() self.after(100, self.process_thread_results) def toggle_task(self): if not self.is_running: self.clear_output_box() self.start_scheduled_tasks() self.start_button.configure(text="停止运行", text_color="#ff0000") self.is_running = True else: self.is_running = False self.start_button.configure(text="定时运行", text_color="#33CC33") def start_scheduled_tasks(self): self.update_all_info(sigin_file_path) self.write_log("开始启动定时任务调度线程") readerno = self.readerno_entry.get() password = self.password_entry.get() time1 = self.time1_entry.get() time2 = self.time2_entry.get() time3 = self.time3_entry.get() time4 = self.time4_entry.get() self.thread_manager.start_thread( target=self.schedule_tasks, args=(readerno, password, time1, time2, time3, time4), ) def schedule_tasks(self, readerno, password, time1, time2, time3, time4): with self.lock: schedule.clear() self.write_log(f"重置所有定时任务,当前学号: {readerno}") schedule.every().day.at(time1).do(self.schedule_task, readerno, password, time1) self.write_log(f"已添加定时签到任务1: {time1}") schedule.every().day.at(time2).do(self.schedule_task, readerno, password, time2) self.write_log(f"已添加定时签到任务2: {time2}") schedule.every().day.at(time3).do(self.schedule_task, readerno, password, time3, signout=True) self.write_log(f"已添加定时签退任务1: {time3}") schedule.every().day.at(time4).do(self.schedule_task, readerno, password, time4, signout=True) self.write_log(f"已添加定时签退任务2: {time4}") self.write_log("已添加上午预约触发时间: 16:59:00") schedule.every().day.at("16:59:00").do(self.execute_booking_plus, False) minutes = int(readerno[-2:]) % 60 today_booking = f"20:{minutes:02d}:00" self.write_log(f"已添加晚上预约触发时间: {today_booking}") schedule.every().day.at(today_booking).do(self.execute_booking_plus, True) schedule.every().day.at("23:00:00").do(self.clear_output_box) schedule.every().day.at("23:30:00").do(self.shuffle_seat) self.write_log("任务调度线程进入运行循环") while self.is_running: try: schedule.run_pending() except Exception as e: logging.error(f"调度器运行时出现错误: {e}") t.sleep(1) self.write_log("任务调度线程结束运行") def schedule_task(self, readerno, password, time, signout=False): if signout: self.write_log(f"定时签退任务触发: {time}") signout_info = self.api.send_signout_req(readerno, password, self.jsessionid) self.write_log(f"签退任务执行结果: {signout_info}") else: self.write_log(f"定时签到任务触发: {time}") try: now = datetime.now() t_h, t_m, t_s = map(int, time.split(":")) window_start = now.replace(hour=t_h, minute=t_m, second=t_s, microsecond=0) except Exception as e: self.write_log(f"解析签到时间失败: {time}, 错误: {e}") return window_end = window_start + timedelta(minutes=30) self.write_log(f"签到窗口开始: {window_start.strftime('%H:%M:%S')} 结束: {window_end.strftime('%H:%M:%S')}") while datetime.now() <= window_end: reservationData = self.api.get_reservation_list(self.jsessionid, readerno) if not reservationData: self.write_log("当前无可签到座位信息,自动签到任务结束") return self.write_log(f"共获取到{len(reservationData)}条可签到记录,开始逐条签到") for item in reservationData: self.write_log(f"尝试签到座位: {item['TableNo']}") checkstr = self.util.get_checkstr(readerno, item['TableNo']) sigin_info = self.api.send_signin_req(readerno, password, item['TableNo'], self.jsessionid, checkstr) if sigin_info is None: self.write_log("签到请求异常或无响应,60秒后重试") break if "未到可签到时间" in str(sigin_info): self.write_log("服务器提示未到可签到时间,60秒后重试") break if sigin_info == "签到成功": self.write_log(f"签到成功,座位: {item['TableNo']}") return self.write_log(f"签到失败,返回信息: {sigin_info},继续尝试下一条记录") self.write_log("本轮签到尝试结束,60秒后重新获取记录并重试") t.sleep(60) self.write_log("已超过签到窗口结束时间,自动签到任务结束") def shuffle_seat(self): random.shuffle(self.tabel_pool) self.tabel_pool_list.delete(0, 'end') for item in self.tabel_pool: self.tabel_pool_list.insert('end', item) self.update_all_info(sigin_file_path) self.write_log(f"座位打乱:{self.tabel_pool}") def booking_cancel(self): if self.is_running: self.write_log("请先点击停止···") return self.update_all_info(sigin_file_path) self.thread_manager.start_thread( self.api.cancel_reservation, args=(self.jsessionid, self.readerno_entry.get(), self.password_entry.get()), callback=self.booking_cancel_callback ) def booking_cancel_callback(self, result): if isinstance(result, str): self.write_log(result) elif result: self.write_log("已将预约信息取消···") else: self.write_log("具体问题请查看日志···") def signin_now(self): if self.is_running: self.write_log("请先点击停止···") return self.update_all_info(sigin_file_path) readerno = self.readerno_entry.get() password = self.password_entry.get() self.jsessionid = self.util.get_jsessionid() if self.tabel_pool_list.curselection(): tableno = self.tabel_pool[self.tabel_pool_list.curselection()[0]] else: tableno = self.tabel_pool[self.tabel_point] checkstr = self.util.get_checkstr(readerno, tableno) self.write_log(f"开始签到,座位: {tableno}") self.thread_manager.start_thread( target=self.api.send_signin_req, args=(readerno, password, tableno, self.jsessionid, checkstr), callback=self.signin_callback ) def signin_callback(self, result): self.write_log(f"签到结果: {result}") def signout_now(self): if self.is_running: self.write_log("请先点击停止···") return self.update_all_info(sigin_file_path) readerno = self.readerno_entry.get() password = self.password_entry.get() self.write_log(f"开始签退,学号: {readerno}") self.thread_manager.start_thread( target=self.api.send_signout_req, args=(readerno, password, self.jsessionid), callback=self.signout_callback ) def signout_callback(self, result): self.write_log(f"签退结果: {result}") def execute_booking_plus(self, e_time, manual_operation=False): reader_no = self.readerno_entry.get() begin_time, end_time = self.get_booking_times(e_time) time_count = 1 max_retries = 10 c_time = datetime.now().strftime("%H:%M:%S") self.write_log(f"执行预约操作,时间:{c_time}") while True: table_no = self.tabel_pool[self.tabel_point] table_id = self.util.get_table_info(table_no, table_file_path) room_no = int(table_no[0]) room_id = self.util.get_room_no(room_no) check_str = self.util.get_checkstr(reader_no, table_no) bask_id = self.api.get_bask_id(baskid_file_path, "27" if e_time else room_id) result = self.api.make_booking( self.jsessionid, begin_time, str(bask_id), check_str, end_time, reader_no, str(room_id), str(table_id), table_no ) if not result: self.write_log("预约请求出现异常,重新执行...") if time_count > max_retries: self.write_log("预约请求达到上限···") return None time_count += 1 continue if result[0]: if "预约成功" not in result[1] and self.tabel_point != 0: self.tabel_point -= 1 if self.tabel_point != 0: self.refresh_seat_info(self.tabel_point) self.write_log(f"{result[1]}:{table_no}") return True else: if not result[0] and not result[1]: self.write_log("读者没有预约权限, 等待30s重新发送请求···") t.sleep(30) continue if result[1] in (502, 504): data = self.api.get_reservation_list(self.jsessionid, reader_no) if not data: self.tabel_point += 1 if self.tabel_point >= len(self.tabel_pool): self.tabel_point = 0 self.write_log("新一轮预约···") self.write_log(f"移动到下一个座位: {self.tabel_pool[self.tabel_point]}") continue self.update_seat_info(table_no, data[0]['TableNo']) return True sleep_time_m = self.get_remaining_seconds(result[1]) if sleep_time_m is None: if "此功能停用" in str(result[1]): self.write_log(f"{result[1]}:{table_no},继续尝试···") self.tabel_point += 1 if self.tabel_point >= len(self.tabel_pool): self.tabel_point = 0 self.write_log("新一轮预约···") continue if not e_time: data = self.api.get_reservation_list(self.jsessionid, reader_no) if data: self.update_seat_info(table_no, data[0]['TableNo']) return True self.write_log(f"{result[1]}:{table_no}") self.tabel_point += 1 if self.tabel_point >= len(self.tabel_pool): self.tabel_point = 0 self.write_log("无可预约座位···") return None self.write_log(f"移动到下一个座位: {self.tabel_pool[self.tabel_point]}") else: if manual_operation: self.write_log(result[1]) return None if time_count > max_retries: self.write_log("运行达到上限···") return None time_count += 1 if sleep_time_m <= 3: adjusted_sleep = max(sleep_time_m - 0.5, 0) self.write_log(f"即将开放,缩短等待至{adjusted_sleep}秒后重新执行") if adjusted_sleep > 0: t.sleep(adjusted_sleep) else: self.write_log(f"等待{sleep_time_m}秒后重新执行") t.sleep(sleep_time_m) def refresh_seat_info(self, point): self.tabel_pool[0], self.tabel_pool[point] = self.tabel_pool[point], self.tabel_pool[0] self.tabel_point = 0 self.tabel_pool_list.delete(0, 'end') for item in self.tabel_pool: self.tabel_pool_list.insert('end', item) def update_seat_info(self, table_no, now_table_no): if table_no == now_table_no: self.write_log(f"已预约成功:{table_no}") if self.tabel_point != 0: self.refresh_seat_info(self.tabel_point) else: self.write_log(f"已有座位信息: {now_table_no}") table_pool_now = list(self.tabel_pool_list.get(0, 'end')) if now_table_no in table_pool_now: if self.tabel_point != 0: self.refresh_seat_info(table_pool_now.index(now_table_no)) else: self.tabel_pool_list.insert(0, now_table_no) self.update_all_info(sigin_file_path) def get_remaining_seconds(self, text): match = re.search(r"\d{2}:\d{2}:\d{2}", text) if not match: return None time_str = match.group() current_time = datetime.strptime(time_str, "%H:%M:%S") target_time = current_time.replace(hour=17, minute=0, second=0) time_difference = target_time - current_time remaining_seconds = int(time_difference.total_seconds()) return max(remaining_seconds, 0) def get_booking_times(self, e_time): if e_time: return "13:50:01", "22:30:00" else: return "07:00:00", "13:50:00" # ============================================================================= # 配置验证和主程序入口 # ============================================================================= def validate_config_file(file_path: str, required_fields: Optional[List[str]] = None) -> bool: if not os.path.isfile(file_path): logger.error(f"配置文件不存在: {file_path}") return False try: with open(file_path, 'r', encoding='utf-8') as f: data = json.load(f) if required_fields: missing_fields = [field for field in required_fields if field not in data] if missing_fields: logger.error(f"配置文件 {file_path} 缺少必需字段: {missing_fields}") return False return True except (json.JSONDecodeError, Exception) as e: logger.error(f"验证配置文件错误: {file_path}, 错误: {e}") return False def validate_all_configs() -> bool: current_directory = BASE_DIR config_files = { os.path.join(current_directory, "sigin_info.json"): [ "readerno", "password", "sigin_time1", "sigin_time2", "signout_time1", "signout_time2", "tables" ], os.path.join(current_directory, 'table_info.json'): [], os.path.join(current_directory, 'bask_id.json'): ["morning", "afternoon"] } all_valid = True for file_path, required_fields in config_files.items(): if not validate_config_file(file_path, required_fields): all_valid = False messagebox.showerror( "配置错误", f"配置文件验证失败:\n{file_path}\n\n请检查配置文件格式和必需字段。" ) return all_valid def create_default_configs() -> None: current_directory = BASE_DIR default_configs = { "sigin_info.json": { "readerno": "", "password": "", "sigin_time1": "07:01:00", "sigin_time2": "14:00:00", "signout_time1": "13:40:00", "signout_time2": "22:20:00", "tables": ["2-126", "2-128"] } } for filename, config in default_configs.items(): file_path = os.path.join(current_directory, filename) if not os.path.exists(file_path): try: with open(file_path, 'w', encoding='utf-8') as f: json.dump(config, f, ensure_ascii=False, indent=4) logger.info(f"创建默认配置文件: {file_path}") except Exception as e: logger.error(f"创建默认配置文件失败: {file_path}, 错误: {e}") def main(): global sigin_file_path, table_file_path, baskid_file_path logger.info("程序启动") try: current_directory = BASE_DIR sigin_file_path = os.path.join(current_directory, "sigin_info.json") table_file_path = os.path.join(current_directory, 'table_info.json') baskid_file_path = os.path.join(current_directory, 'bask_id.json') create_default_configs() if not validate_all_configs(): logger.error("配置文件验证失败,程序退出") sys.exit(1) logger.info("启动主界面") app = MainInterface() app.mainloop() except KeyboardInterrupt: logger.info("用户中断程序") except Exception as e: logger.error(f"程序运行时发生错误: {e}") messagebox.showerror("程序错误", f"程序运行时发生错误:\n{e}") finally: logger.info("程序结束") if __name__ == '__main__': main() -
智能媒体管理插件
智能媒体管理是一款专业的 WordPress 媒体库清理工具,帮助您智能识别和清理未使用的媒体文件,释放宝贵的服务器空间。插件采用先进的引用检测算法,确保不会误删正在使用的文件。
1. 智能扫描未使用文件
- 全量扫描:扫描整个媒体库,识别所有未使用的媒体文件
- 多维度检测:通过多种方式检测文件引用关系,确保准确性
- 实时统计:显示总文件数、未使用文件数、可释放空间等统计信息
- 分类显示:区分未使用文件、有引用文件、文件丢失(幽灵文件)等状态
2. 全面的引用检测系统
插件采用 6 种检测方法,全面识别文件引用:方法 1:WordPress 媒体标签检测- 检测 `wp-image-{ID}`、`wp-video-{ID}`、`wp-audio-{ID}`、`wp-att-{ID}` 等标签
- 支持图片、视频、音频等多种媒体类型
方法 2:URL 路径检测- 检测完整 URL、相对路径、文件名等多种 URL 格式
- 支持原图和缩略图的关联检测
方法 3:附件 ID 参数检测- 检测 `attachment_id=` 参数形式的引用
- 适用于下载按钮等场景
方法 4:下载链接检测- 检测 `href` 和 `download` 属性中的文件引用
- 支持单引号和双引号格式
方法 5:文章自定义字段检测- 检测存储在 `postmeta` 中的文件引用
- 支持 ACF 字段、付费下载插件等场景
方法 6:用户自定义字段检测- 检测用户头像、封面图片等存储在 `usermeta` 中的引用
- 支持多种头像插件(Simple Local Avatars 等)
- 检测序列化数据中的引用
- 智能排除系统字段,避免误报
3. 特色图片检测
- 自动检测文章特色图片(缩略图)
- 支持删除文章时一并删除特色图片(可配置)
- 智能判断特色图片是否被其他文章使用
4. 自动清理功能
- 删除文章时自动清理:删除文章时,自动检测并清理文章内容中的未使用图片
- 孤立附件清理:自动清理 `post_parent` 指向已删除文章的附件
- 安全保护:如果图片被其他文章引用,仅解除关联,不删除文件
5. 媒体库删除保护
- 在后台媒体库删除图片时,自动检测是否被引用
- 如果图片仍被文章引用,阻止删除并提示用户
- 防止误删正在使用的文件
6. 批量管理功能
- 批量扫描:一键扫描整个媒体库
- 批量删除:支持选择多个文件批量删除
- 全选功能:快速选择所有未使用文件
- 实时预览:显示文件缩略图、大小、上传日期等信息
7. 高级筛选功能
- 扫描模式:可选择扫描未使用文件或显示有引用的文件
- 文件类型筛选:按文件扩展名筛选(未来功能)
- 文件大小筛选:按文件大小范围筛选(未来功能)
- 日期范围筛选:按上传日期筛选(未来功能)
8. 排除关键字保护
- 设置排除关键字列表(逗号分隔)
- 包含关键字的文件永远不会被扫描或删除
- 适用于保护 Logo、Banner 等重要文件
9. 文章类型配置
- 可选择需要检测引用的文章类型
- 支持所有公开的文章类型(post、page、自定义类型等)
- 未选中的类型中的图片引用将被忽略
10. 日志系统
- 操作日志:记录所有扫描、删除操作
- 调试日志:详细的引用检测过程日志
- 日志查看:在后台实时查看日志内容
- 日志管理:支持复制日志、清空日志
- 可配置:可选择启用或关闭日志功能
11. 用户友好的界面
- 现代化设计:采用 Bootstrap 5 风格的现代化界面
- 响应式布局:完美适配桌面和移动设备
- Tab 导航:设置、扫描、日志分标签页管理
- 实时反馈:操作状态实时显示
- – **确认对话框**:删除操作前显示确认对话框,防止误操作
-
世界,您好!
“### 本站由 NihaoTK 论坛基于开源项目搭建:nihaotk.com
探索 Markdown 的奇妙世界
欢迎来到 Markdown 的奇妙世界!无论你是写作爱好者、开发者、博主,还是想要简单记录点什么的人,Markdown 都能成为你新的好伙伴。它不仅让写作变得简单明了,还能轻松地将内容转化为漂亮的网页格式。今天,我们将全面探讨 Markdown 的基础和进阶语法,让你在这个过程中充分享受写作的乐趣!
Markdown 是一种轻量级标记语言,用于格式化纯文本。它以简单、直观的语法而著称,可以快速地生成 HTML。Markdown 是写作与代码的完美结合,既简单又强大。
Markdown 基础语法
1. 标题:让你的内容层次分明
用
#号来创建标题。标题从#开始,#的数量表示标题的级别。# 一级标题 ## 二级标题 ### 三级标题 #### 四级标题以上代码将渲染出一组层次分明的标题,使你的内容井井有条。
2. 段落与换行:自然流畅
Markdown 中的段落就是一行接一行的文本。要创建新段落,只需在两行文本之间空一行。
3. 字体样式:强调你的文字
- • 粗体:用两个星号或下划线包裹文字,如
**粗体**或__粗体__。 - • 斜体:用一个星号或下划线包裹文字,如
*斜体*或_斜体_。 - •
删除线:用两个波浪线包裹文字,如~~删除线~~。 - • 高亮:用两个等号包裹文字,如
==高亮==。 - • 下划线:用两个加号包裹文字,如
++下划线++。 - • 波浪线:用一个波浪线包裹文字,如
~波浪线~。
这些简单的标记可以让你的内容更有层次感和重点突出。
4. 列表:整洁有序
- • 无序列表:用
-、*或+加空格开始一行。 - • 有序列表:使用数字加点号(
1.、2.)开始一行。
在列表中嵌套其他内容?只需缩进即可实现嵌套效果。
- • 无序列表项 1
- 1. 嵌套有序列表项 1
- 2. 嵌套有序列表项 2
- • 无序列表项 2
- 1. 有序列表项 1
- 2. 有序列表项 2
5. 链接与图片:丰富内容
- • 链接:用方括号和圆括号创建链接
[显示文本](链接地址)。 - • 图片:和链接类似,只需在前面加上
!,如。

doocs 轻松实现富媒体内容展示!
因微信公众号平台不支持除公众号内容以外的链接,故其他平台的链接,会呈现链接样式但无法点击跳转。
对于这些链接请注意明文书写,或点击左上角「格式->微信外链接转底部引用」开启引用,这样就可以在底部观察到链接指向。
另外,使用
<,>语法可以创建横屏滑动幻灯片,支持微信公众号平台。建议使用相似尺寸的图片以获得最佳显示效果。6. 引用:引用名言或引人深思的句子
使用
>来创建引用,只需在文本前面加上它。多层引用?在前一层>后再加一个就行。这是一个引用
这是一个嵌套引用
这让你的引用更加富有层次感。
7. 代码块:展示你的代码
- • 行内代码:用反引号包裹,如
code。 - • 代码块:用三个反引号包裹,并指定语言,如:
console.log(`Hello, Doocs!`)语法高亮让你的代码更易读。
8. 分割线:分割内容
用三个或更多的
-、*或_来创建分割线。
为你的内容添加视觉分隔。
9. 表格:清晰展示数据
Markdown 支持简单的表格,用
|和-分隔单元格和表头。链接名称 链接地址 软件工具 https://nihaotk.com/t/software-tools AI 工具 https://nihaotk.com/t/ai 公众号排版 https://md.nihaotk.com 这样的表格让数据展示更为清爽!
手动编写标记太麻烦?我们提供了便捷方式。左上方点击「编辑->插入表格」,即可快速实现表格渲染。
Markdown 进阶技巧
1. LaTeX 公式:完美展示数学表达式
Markdown 允许嵌入 LaTeX 语法展示数学公式:
- • 行内公式:用
$包裹公式,如 。 - • 块级公式:用
$$包裹公式,如:
现在还支持 LaTeX 标准格式:
- • 行内公式:用
\(...\)包裹公式,如 。 - • 块级公式:用
\[...\]包裹公式,如:
混合使用示例:传统格式 和 LaTeX 格式 可以在同一段落中共存。
- 1. 列表内块公式 1
- 2. 列表内块公式 2
这是展示复杂数学表达的利器!
2. Mermaid 流程图:可视化流程
Mermaid 是强大的可视化工具,可以在 Markdown 中创建流程图、时序图等。
GraphCommand update goto send 更新状态 流程控制 消息传递 A B C D 40%46%9%5%Key elements in Product XCalciumPotassiumMagnesiumIron6%10%70%13%为什么总是宅在家里?喜欢宅天气太热穷没人约这种方式不仅能直观展示流程,还能提升文档的专业性。
更多用法,参见:Mermaid User Guide。
3. PlantUML 流程图:可视化流程
PlantUML 是强大的可视化工具,可以在 Markdown 中创建流程图、时序图等。
ParticipantParticipantActorActorBoundaryBoundaryControlControlEntityEntityDatabaseDatabaseCollectionsCollectionsQueueQueueTo actorTo boundaryTo controlTo entityTo databaseTo collectionsTo queue更多用法,参见:PlantUML 主页。
4. Infographic 信息图:可视化数据
新一代信息图可视化引擎,让文字信息栩栩如生!
客户增长引擎多渠道触达与复购提升渠道投放与内容获客线索获取0102转化提效线索评分与自动跟进会员体系与权益运营复购提升0304口碑传播社群激励与推荐裂变更多用法,参见:AntV Infographic Gallery。
5. Ruby 注音:注音标注
支持两种格式:
1. [文字]{注音} 2. [文字]^(注音)渲染效果如下:
你好 世界
支持四种分隔符:
・(中点)、.(全角句点)、。(中文句号)、-(英文减号)示例:
[你好世界]{nǐ・hǎo・shì・jiè} [小夜時雨]^(さ・よ・しぐれ)你好世界
小夜時雨当字符串数量与分隔符数量不匹配时,会自动匹配到最合适的分隔符。
[小夜時雨]{さ・よ・しぐれ} [小夜時雨]{さ・よ} [小夜]{さ・よ・しぐれ} [小夜時雨]{さ・よ・しぐれ・extra}小夜時雨
小夜時雨
小夜
小夜時雨结语
Markdown 是一种简单、强大且易于掌握的标记语言,通过学习基础和进阶语法,你可以快速创作内容并有效传达信息。无论是技术文档、个人博客还是项目说明,Markdown 都是你的得力助手。希望这篇内容能够带你全面了解 Markdown 的潜力,让你的写作更加丰富多彩!
现在,拿起 Markdown 编辑器,开始创作吧!探索 Markdown 的世界,你会发现它远比想象中更精彩!
推荐阅读
- • 粗体:用两个星号或下划线包裹文字,如