Skip to content

DaHuoji555/Members_Manager

Repository files navigation

Member Management System — 开发文档

目录

  1. 项目简介
  2. 技术栈
  3. 项目结构
  4. 架构说明
  5. 数据库设计
  6. 快速开始
  7. 功能详解
  8. 会员到期邮件通知
  9. 模块说明
  10. 错误处理
  11. 开发扩展指南

1. 项目简介

Member Management System 是一个基于 Python + PySide6 + MySQL 的桌面端会员数据管理系统。

主要面向需要通过 Excel 批量维护会员档案的场景,支持:

  • 将 Excel 文件拖入窗口即可完成批量导入
  • 在表格中直接编辑、删除单条记录
  • 一键导出当前视图(或选中行)为 Excel
  • 通过点击列头进行 SQL 级排序

Excel 数据源结构(来自项目内 .xlsx 示例文件):

Excel 列名 数据库列名 类型 说明
Member member INT (PK) 会员编号(主键)
Type type VARCHAR 会员类型
Date in date_in DATE 入会日期
Payment payment DATE 缴费日期
$ dollar VARCHAR 金额/类别标记
Issued issued DATE 证件签发日期
Renewal renewal DATE 到期/续费日期
Last last VARCHAR 姓(Last Name)
First first VARCHAR 名(First Name)
Country country VARCHAR 国家
Address H address_h TEXT 住宅地址
Address B address_b TEXT 单位地址
email email VARCHAR 电子邮件
web web VARCHAR 网址
tel tel VARCHAR 电话
Activity activity VARCHAR 活动标记
other other TEXT 备注

2. 技术栈

层次 技术 版本要求
语言 Python ≥ 3.10
GUI PySide6 ≥ 6.5
ORM / 数据库访问 SQLAlchemy (Core) ≥ 2.0
数据库驱动 PyMySQL ≥ 1.1
数据库 MySQL ≥ 5.7 / 8.x
Excel 处理 pandas + openpyxl pandas ≥ 2.0

3. 项目结构

Members_Manager/
│
├── main.py                   # 程序入口
├── create_database.py        # 独立建库脚本(首次运行)
├── requirements.txt          # Python 依赖清单
├── .gitignore
│
├── utils/
│   ├── __init__.py
│   ├── helpers.py            # 通用工具函数(列名转换等)
│   └── db_config.py          # 数据库连接配置(由 create_database.py 生成)
│
├── database/
│   ├── __init__.py
│   ├── db.py                 # SQLAlchemy Engine 管理
│   └── dynamic_model.py      # 运行时反射表结构,提供列名/类型/主键
│
├── services/
│   ├── __init__.py
│   ├── member_service.py     # 数据库 CRUD 操作(含智能排序)
│   ├── excel_service.py      # Excel 导入 / 导出逻辑
│   ├── email_service.py      # SMTP 配置与邮件发送
│   └── notification_service.py  # 到期检测、去重、通知编排
│
└── ui/
    ├── __init__.py
    ├── main_window.py        # 主窗口(工具栏、表格、拖放)
    └── dialogs.py            # 新增 / 编辑记录对话框

4. 架构说明

本项目采用三层架构,各层职责严格分离:

┌─────────────────────────────────────────┐
│              UI 层  (ui/)               │
│   main_window.py  ·  dialogs.py         │
│   负责:界面渲染、用户交互、事件响应        │
└────────────────┬────────────────────────┘
                 │ 调用
┌────────────────▼────────────────────────┐
│           业务逻辑层  (services/)         │
│   member_service.py  ·  excel_service.py│
│   负责:数据校验、导入/导出规则、CRUD 封装  │
└────────────────┬────────────────────────┘
                 │ 调用
┌────────────────▼────────────────────────┐
│            数据层  (database/)           │
│   db.py  ·  dynamic_model.py            │
│   负责:连接管理、表结构反射、SQL 执行      │
└─────────────────────────────────────────┘

重要设计原则:

  • UI 层不直接访问数据库,所有数据操作经由 services/ 层中转。
  • database/dynamic_model.py 在启动时通过 SQLAlchemy 反射(MetaData.reflect())自动感知表结构,无需硬编码列名,使系统具备适应不同 Excel 模板的能力。
  • create_database.py 与主程序完全独立,仅在初始化阶段运行一次。

5. 数据库设计

列名映射规则

create_database.py 使用 utils/helpers.py 中的 sanitize_column_name() 函数,将 Excel 列名自动转换为合法的 SQL 标识符:

转换规则(按优先级):
  1. 特殊字符映射:$ → dollar,# → number,% → percent
  2. 转为小写
  3. 连续空格 → 下划线
  4. 非字母数字字符 → 下划线
  5. 合并连续下划线
  6. 去除首尾下划线
  7. 若结果为空或以数字开头,加前缀 col_

示例:Date indate_inAddress Haddress_h$dollar

主键约定

表的第一列(Member)被自动设为整型主键(INT PRIMARY KEY, autoincrement=False)。主程序通过 get_primary_key_column() 动态获取,无硬编码。


6. 快速开始

前置条件

  • Python 3.10+
  • 已安装并运行的 MySQL 服务(本地或远程)
  • 准备好要导入的 .xlsx 文件

第一步:安装依赖

pip install -r requirements.txt

第二步:初始化数据库(仅需运行一次)

python create_database.py

脚本会交互式询问以下信息:

MySQL host    [localhost]:        ← 直接回车使用默认值
MySQL port    [3306]:
MySQL user    [root]:
MySQL password:                   ← 输入密码(不回显)
Database name [members_db]:
Table name    [members]:

脚本执行后:

  1. 自动创建数据库(如不存在)
  2. 读取 Excel 文件推断各列类型
  3. 在终端打印列映射表
  4. 创建 members 数据表
  5. 将连接配置写入 utils/db_config.py

若需使用不同的 Excel 文件作为模板:

python create_database.py --excel /path/to/your_file.xlsx

若需强制删除并重建已存在的表(会清空数据):

python create_database.py --force

第三步:启动主程序

python main.py

如果数据库连接失败,程序会弹出错误对话框并提示解决方法,不会崩溃。


7. 功能详解

7.1 数据展示

  • 启动后自动加载全部数据,展示在中央表格中
  • 表格为只读,每行末尾提供 EditDelete 操作按钮
  • 双击任意行也可进入编辑模式

7.2 排序

点击任意列头即可按该列排序(升序);再次点击同一列头切换为降序。

排序在数据库侧执行(ORDER BY),状态栏右侧会显示当前排序列和方向(▲/▼)。

7.3 Excel 导入

方式 A:拖放 将一个或多个 .xlsx / .xls 文件直接拖入程序窗口,窗口顶部的提示横条会高亮显示可放置区域。

方式 B:按钮 点击工具栏的 ⬇ Import Excel,通过文件选择对话框选取文件(支持多选)。

导入逻辑:

读取 Excel
    ↓
列名标准化(sanitize_column_name)
    ↓
检查必需列是否存在 → 缺失则报错,整文件拒绝
    ↓
逐行检查主键是否已存在于数据库
    ↓ 有冲突
列出所有冲突主键值 → 报错,整文件拒绝,不插入任何行
    ↓ 无冲突
批量 INSERT 所有行
    ↓
刷新表格,显示成功数量

注意: 主键冲突采用"全拒绝"策略——只要文件中有任意一行的主键已存在,整个文件都不会被导入,保证数据一致性。

7.4 Excel 导出

点击工具栏的 ⬆ Export Excel,弹出保存文件对话框。

  • 选中了行 → 仅导出选中行
  • 未选中任何行 → 导出当前视图的全部数据(按当前排序)

导出文件使用人类可读的列标题(如 Date InAddress H),适合直接分发。

7.5 新增记录

点击工具栏的 + Add Record,弹出动态表单。

表单字段根据数据库列结构自动生成。日期字段需填写 YYYY-MM-DD 格式(留空表示 NULL)。主键字段不可为空。

7.6 编辑记录

点击行末的 Edit 按钮,或双击该行,弹出预填数据的编辑表单。主键字段在编辑模式下为只读。

7.7 删除记录

点击行末的 Delete 按钮,弹出二次确认对话框,确认后执行数据库删除并刷新表格。


8. 会员到期邮件通知

8.1 功能说明

程序启动 2 秒后,会在后台线程自动扫描全部会员,对 30 天内即将到期 的会员发送提醒邮件。同一会员的同一到期日只会发送一次。

8.2 到期日计算规则

renewal issued 生效到期日
不为空 直接使用 renewal 的值
为空 有值 issued + 1 年 − 1 天(闰年 2 月 29 日 → 次年 2 月 28 日)
为空 也为空 跳过,不处理

8.3 邮件地址解析

email 字段可能包含多个地址,以任意非邮件字符(逗号、空格、分号、竖线等)分隔。系统使用正则表达式 [a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,} 提取全部有效地址,并同时发送给所有地址。

8.4 去重机制

数据库中有一张辅助表 notification_log,结构如下:

CREATE TABLE notification_log (
    id          INT AUTO_INCREMENT PRIMARY KEY,
    member_id   INT  NOT NULL,
    expiry_date DATE NOT NULL,
    sent_at     DATETIME NOT NULL,
    UNIQUE KEY uq_member_expiry (member_id, expiry_date)
);

每次发送成功后写入一条记录。UNIQUE KEY 保证相同的 (member_id, expiry_date) 组合永远不会重复触发,即便程序每天多次启动也不会重复发邮件。

注意:如果某会员的 renewal 日期被修改(到期日变了),新的 (member_id, new_expiry_date) 组合不在日志表中,会重新发送一封通知。

8.5 配置发送方邮箱

编辑 services/email_service.py,填入 SMTP 配置和邮件正文:

SMTP_HOST     = "smtp.gmail.com"          # SMTP 服务器
SMTP_PORT     = 587                        # STARTTLS 端口
SMTP_USER     = "yourname@gmail.com"       # 发件人地址
SMTP_PASSWORD = "xxxx xxxx xxxx xxxx"      # Gmail App Password

同文件中的 subjectbody_textbody_html 三个变量用于填写邮件主题和正文(支持纯文本和 HTML 双格式)。

若 SMTP 未配置,通知检查会静默跳过(不崩溃、不弹窗),仅在控制台打印提示。

8.6 相关文件

文件 职责
services/email_service.py SMTP 配置、邮件构建与发送
services/notification_service.py 扫描到期会员、去重判断、编排发送流程
ui/main_window.py 启动 2 s 后用 QTimer + 守护线程触发检查,结果通过 Signal 回传主线程

9. 模块说明

utils/helpers.py

函数 说明
sanitize_column_name(name) 将任意字符串转换为合法 SQL 列名
db_column_to_display(name) 将 DB 列名(date_in)转为显示标签(Date In

database/db.py

函数 说明
get_engine() 懒加载并缓存 SQLAlchemy Engine;失败时抛出 RuntimeError(含友好提示)
dispose_engine() 释放连接池(程序退出时调用)

database/dynamic_model.py

函数 说明
get_table_obj(table_name) 返回反射后的 SQLAlchemy Table 对象(带缓存)
get_table_columns() 返回列名列表(按定义顺序)
get_primary_key_column() 返回主键列名
get_column_types() 返回 {列名: 类型字符串} 字典,类型值为 'integer' | 'float' | 'date' | 'text' | 'string'
invalidate_cache() 清除反射缓存(表结构变更后调用)

services/member_service.py

函数 说明
get_all(order_by, ascending) 查询全部记录,支持单列排序(字符串列自动 CAST 数值排序)
get_count() 返回记录总数
member_exists(pk_value) 判断指定主键的记录是否存在
insert_member(data) 插入一条记录
update_member(pk_value, data) 更新指定主键的记录
delete_member(pk_value) 删除指定主键的记录

services/email_service.py

元素 说明
SMTP_HOST / PORT / USER / PASSWORD 发件方配置(需手动填写)
send_expiry_notice(emails, name, expiry) 构建并发送到期提醒邮件

services/notification_service.py

函数 说明
check_and_send_notifications() 入口函数;返回 (sent_count, errors)
_extract_emails(raw) 正则提取邮件地址列表
_calc_expiry(issued) 计算标准到期日(+1年−1天)
_ensure_notification_table(engine) 自动建表 notification_log(幂等)

services/excel_service.py

函数 说明
import_excel(filepath) 导入单个 Excel 文件;含列校验和主键冲突预检;返回已插入行数
export_excel(filepath, data, columns) 将数据列表导出为 Excel 文件

ui/main_window.py

MainWindow(QMainWindow) 主窗口,负责:

  • 工具栏(Import / Export / Add / Refresh)
  • 拖放事件处理(dragEnterEvent / dropEvent
  • 数据加载与表格渲染(_load_data / _repaint_table
  • 列头点击排序(_on_header_click
  • 调用 dialogs 完成新增 / 编辑流程

ui/dialogs.py

AddEditDialog(QDialog) 动态表单对话框,负责:

  • 根据列类型自动生成输入控件(QLineEditQTextEdit
  • 在保存前进行类型校验(日期格式、整数、浮点数)
  • 主键在编辑模式下置为只读

10. 错误处理

场景 处理方式
数据库连接失败 启动时弹出错误对话框,提示检查 db_config.py 或重新运行建库脚本
表不存在 dynamic_model.get_table_obj() 抛出 RuntimeError,主窗口捕获后弹框提示
Excel 文件无法读取 import_excel() 抛出 ValueError,主窗口显示错误详情
Excel 缺少必需列 同上,列出所有缺失列名
主键冲突(导入时) 列出全部冲突值,整文件拒绝
主键冲突(新增时) 捕获 IntegrityError,弹出"重复主键"提示
表单校验失败 在对话框内显示警告,不关闭对话框

11. 开发扩展指南

切换到不同的 Excel 模板

  1. 运行 python create_database.py --excel new_file.xlsx --force
  2. 重启主程序,表结构自动反射,无需修改任何代码

新增查询筛选功能

services/member_service.py 中扩展 get_all() 函数,加入 WHERE 子句参数,再在主窗口工具栏添加搜索框触发即可。

支持分页

当数据量较大时,可在 get_all() 中加入 LIMIT / OFFSET 参数,并在主窗口底部添加翻页控件。

部署到多台机器

只需将整个项目目录复制到目标机器,在目标机器上运行:

pip install -r requirements.txt
python create_database.py   # 指向共享 MySQL 服务器
python main.py

多个客户端可同时连接同一个 MySQL 数据库,实现多人协作。


文档版本:1.0 · 最后更新:2026-03-30

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages