1. 项目概述与核心价值
最近几年,带过不少计算机专业的毕业设计,发现一个挺有意思的现象:很多同学一听到“文件加密系统”就觉得头大,要么觉得太简单没深度,要么觉得太复杂无从下手。其实,用 Python 和 Flask 来做一个文件加密系统,是一个相当不错的毕业设计选题。它技术栈主流,涉及前后端、密码学、文件处理等多个核心知识点,既有理论深度,又有很强的实践性,最关键的是,它做出来的东西是“有用”的,不是那种做完就丢的玩具。
这个项目本质上是一个 Web 应用,用户通过浏览器上传文件,系统在服务器端对文件进行加密,生成加密后的文件供用户下载;反之,用户上传加密文件并提供正确密钥,系统则进行解密。听起来简单,对吧?但这里面门道可不少。比如,加密算法选 AES 还是 RSA?密钥怎么管理和存储才安全?大文件上传怎么处理?Flask 如何组织代码才清晰?这些都是在动手之前必须想清楚的问题。这个选题的价值在于,它能让你系统地实践从需求分析、技术选型、系统设计、编码实现到测试部署的完整软件开发流程,这正是毕业设计要考察的核心能力。
2. 系统整体设计与技术选型思路
2.1 需求分析与功能模块拆解
做任何项目,第一步永远是搞清楚要做什么。对于一个文件加密系统,我们不能只停留在“能加密解密”的层面。我们需要从用户视角和系统视角来细化需求。
从用户角度看,核心需求无非是:
- 文件上传:能方便地上传待加密或待解密的文件。
- 加密操作:选择或输入密钥,对上传的文件进行加密,并下载加密后的结果。
- 解密操作:上传加密文件,输入正确的密钥,进行解密并下载原文件。
- 操作反馈:清晰的操作成功或失败提示(如密钥错误、文件类型不支持等)。
为了支撑这些用户功能,系统后端需要设计以下核心模块:
- 用户会话管理模块:虽然是个简单系统,但引入基础的登录注册(或用 Flask-Login)能更好地管理用户文件,避免不同用户的文件混淆。这对于毕业设计来说,是加分项。
- 文件处理模块:负责接收前端上传的文件,进行临时存储或直接流式处理。这里要特别注意安全性,比如限制文件类型、检查文件大小、防止路径遍历攻击。
- 加密解密核心模块:这是项目的“心脏”。需要实现选定的加密算法(如 AES),并提供稳定的加密/解密接口。密钥的输入、生成、派生(如果使用口令)都在这里处理。
- 密钥管理模块:这是一个容易被忽略但至关重要的部分。绝对不能在数据库或日志里明文存储用户密钥。我们需要设计一种方式,让密钥只在内存中存在,用于单次加解密操作,完成后立即清除。对于需要存储的场景(如“我的密钥”功能),必须对密钥进行二次加密(如使用用户主密码加密)后再存储。
- 任务与结果返回模块:处理加密/解密的异步任务(如果涉及大文件),并将处理结果(成功后的文件下载链接,或失败的错误信息)返回给前端。
2.2 核心技术栈选型与理由
为什么是 Python + Flask?这是经过深思熟虑的。
后端框架:FlaskFlask 是一个轻量级的 Web 框架,被称为“微框架”。它的“微”体现在核心简单,但通过丰富的扩展可以构建复杂应用。对于毕业设计级别的项目,Flask 比 Django 更合适。Django 功能大而全,但“约定大于配置”的风格可能会掩盖很多底层细节。而 Flask 需要你手动配置路由、连接数据库、处理表单,这个过程能让你更深刻地理解 Web 应用的工作原理。从开题报告和论文撰写的角度,你也有更多“设计”和“实现”的细节可以阐述。常用的扩展有:
Flask-WTF(表单处理与验证)、Flask-Login(用户会话管理)、Flask-SQLAlchemy(数据库 ORM)。加密库:cryptographyPython 标准库里有
hashlib和hmac,但对于对称加密(如 AES),更推荐使用cryptography这个第三方库。它是目前 Python 生态中密码学事实上的标准,由 Python 密码学权威维护,API 设计安全、现代,并且默认使用安全的操作模式(如 AES-GCM)。这比你用pycryptodome手动去组合 AES-CBC 模式要安全省心得多,也能在论文的“安全性分析”部分提供有力支撑。前端技术:HTML/CSS/JavaScript + 轻度框架前端不必复杂。使用原生 HTML 表单配合一点 JavaScript 实现文件选择和上传进度显示即可。如果想界面更美观、交互更友好,可以引入轻量级的 CSS 框架如
Bootstrap或Bulma,以及使用jQuery或原生 JS 进行 AJAX 交互,实现无刷新上传。这样既能展示前后端分离的交互思想,又不会让前端工作量喧宾夺主。数据库:SQLite开发阶段和中小型部署,SQLite 是绝佳选择。它无需单独安装数据库服务,一个文件就是整个数据库,非常适合毕业设计这种单体应用。通过 Flask-SQLAlchemy 可以很方便地操作。需要存储的数据包括:用户信息、文件记录(文件名、存储路径、哈希值、上传时间、所属用户等)。切记:只存储文件的元信息,绝不存储文件内容或用户密钥明文。
开发环境与工具
- Python 环境:强烈建议使用
venv创建虚拟环境,并用pip或更快的pip-tools管理依赖。在requirements.txt里清晰列出所有包及其版本。 - 代码编辑器:VSCode 或 PyCharm 均可。VSCode 配合 Python 插件和 Pylance 语言服务器,体验非常好。
- 版本控制:必须使用 Git。从项目第一天就初始化仓库,定期提交。这不仅是为了备份,更是你开发过程的证明,可以在论文的“实现过程”章节提供素材。
- Python 环境:强烈建议使用
3. 核心模块详细设计与实现要点
3.1 Flask 应用结构与配置
一个清晰的项目结构是良好代码的开始。不要把所有代码都堆在app.py一个文件里。推荐采用按功能模块分包的结构:
/your_project ├── app/ │ ├── __init__.py # 应用工厂函数,初始化Flask app、扩展等 │ ├── models.py # 数据库模型定义(User, FileRecord) │ ├── auth/ # 认证相关蓝图 │ │ ├── __init__.py │ │ ├── routes.py │ │ └── forms.py │ ├── main/ # 核心功能蓝图(文件上传、加解密) │ │ ├── __init__.py │ │ ├── routes.py │ │ └── forms.py │ ├── utils/ # 工具函数 │ │ ├── __init__.py │ │ ├── crypto.py # 加密解密核心函数 │ │ └── file_handler.py # 文件安全检查与处理 │ └── templates/ # 模板文件 │ ├── base.html │ ├── index.html │ ├── auth/ │ └── main/ ├── instance/ # 实例文件夹,放配置文件、SQLite数据库 │ └── config.py ├── tests/ # 单元测试 ├── requirements.txt ├── .gitignore └── run.py # 启动脚本在app/__init__.py中,使用应用工厂模式创建 Flask 实例:
from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_login import LoginManager from config import Config db = SQLAlchemy() login_manager = LoginManager() login_manager.login_view = 'auth.login' def create_app(config_class=Config): app = Flask(__name__) app.config.from_object(config_class) db.init_app(app) login_manager.init_app(app) # 注册蓝图 from app.auth import bp as auth_bp app.register_blueprint(auth_bp, url_prefix='/auth') from app.main import bp as main_bp app.register_blueprint(main_bp) return app注意事项:配置文件instance/config.py中,SECRET_KEY必须使用强随机字符串,且绝不能提交到版本库。数据库路径使用os.path.join动态构建,确保可移植性。
3.2 加密解密核心实现(cryptography库)
这是项目的技术核心。我们选择 AES-GCM 模式。GCM 不仅提供保密性(加密),还提供完整性认证(防篡改),比传统的 CBC 模式更安全、更现代。
在app/utils/crypto.py中:
from cryptography.hazmat.primitives.ciphers.aead import AESGCM from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC import os import base64 class FileCrypto: def __init__(self): # AES-GCM 的 nonce 推荐长度为 12 字节 self.nonce_size = 12 def _derive_key_from_password(self, password: str, salt: bytes) -> bytes: """使用 PBKDF2 从口令派生密钥。""" kdf = PBKDF2HMAC( algorithm=hashes.SHA256(), length=32, # AES-256 需要 32 字节密钥 salt=salt, iterations=100000, # 迭代次数,增加暴力破解成本 ) # password 需要编码为 bytes key = kdf.derive(password.encode('utf-8')) return key def encrypt_file(self, source_file_path: str, target_file_path: str, password: str): """ 加密文件。 :param source_file_path: 源文件路径 :param target_file_path: 目标加密文件路径 :param password: 用户提供的口令 """ # 1. 生成随机盐和nonce salt = os.urandom(16) nonce = os.urandom(self.nonce_size) # 2. 从口令派生密钥 key = self._derive_key_from_password(password, salt) # 3. 初始化 AES-GCM aesgcm = AESGCM(key) # 4. 读取源文件并加密 with open(source_file_path, 'rb') as f: plaintext = f.read() # 加密。associated_data 可以为空,或用于绑定额外数据(如文件头) ciphertext = aesgcm.encrypt(nonce, plaintext, None) # 5. 将 salt + nonce + ciphertext 组合写入目标文件 # 这是一种简单的序列化方式,解密时需要按相同结构解析 with open(target_file_path, 'wb') as f: f.write(salt) f.write(nonce) f.write(ciphertext) def decrypt_file(self, source_file_path: str, target_file_path: str, password: str) -> bool: """ 解密文件。 :return: 成功返回True,失败(如密码错误)返回False """ try: with open(source_file_path, 'rb') as f: # 按写入时的结构读取 salt = f.read(16) nonce = f.read(self.nonce_size) ciphertext = f.read() # 派生密钥 key = self._derive_key_from_password(password, salt) # 解密 aesgcm = AESGCM(key) plaintext = aesgcm.decrypt(nonce, ciphertext, None) with open(target_file_path, 'wb') as f: f.write(plaintext) return True except Exception as e: # cryptography.exceptions.InvalidKey 等异常会被捕获 print(f"解密失败: {e}") return False关键点解析与避坑指南:
- 密钥派生:永远不要直接用用户输入的字符串作为密钥。必须使用 PBKDF2、Scrypt 等密钥派生函数(KDF)。
iterations参数很关键,它决定了派生过程的计算成本,是抵御暴力破解的重要防线。10万次是一个合理的起点。 - Nonce(随机数):GCM 模式中,同一个密钥下,nonce 绝对不能重复使用,否则会严重破坏安全性。每次加密都必须使用新的随机 nonce。我们这里将 salt 和 nonce 一起保存到文件头是常见做法。
- 异常处理:解密时,密钥错误或文件被篡改都会导致
InvalidTag异常。必须妥善捕获这个异常,并给用户返回“密码错误或文件已损坏”的友好提示,而不是让程序崩溃。 - 大文件处理:上面的示例是一次性读入内存,对于超大文件(如数GB)不友好。
cryptography库支持流式加密,但更复杂。对于毕业设计,可以在需求中说明“支持常见文件加密”,并在代码中添加文件大小检查(如限制为100MB),这在实际应用中也是合理的。
3.3 文件上传与安全处理
文件上传是 Web 安全的重灾区。在app/utils/file_handler.py和路由中,必须做好防护。
import os from werkzeug.utils import secure_filename from flask import current_app ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'doc', 'docx'} def allowed_file(filename): """检查文件扩展名是否允许。""" return '.' in filename and \ filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS def save_uploaded_file(file, upload_folder): """ 安全地保存上传的文件。 :param file: Werkzeug FileStorage 对象 :param upload_folder: 上传目录 :return: 保存后的安全文件名,或 None(如果失败) """ if file.filename == '': return None if file and allowed_file(file.filename): # 1. 使用 secure_filename 过滤危险字符 filename = secure_filename(file.filename) # 2. 避免文件名冲突,可以添加时间戳或唯一ID from datetime import datetime unique_filename = f"{datetime.utcnow().strftime('%Y%m%d_%H%M%S_%f')}_{filename}" file_path = os.path.join(upload_folder, unique_filename) # 3. 确保上传目录存在 os.makedirs(upload_folder, exist_ok=True) # 4. 保存文件 file.save(file_path) return unique_filename return None在 Flask 路由中处理上传:
from flask import render_template, request, flash, redirect, url_for, send_from_directory from app.main import bp from app.utils.file_handler import save_uploaded_file from app.utils.crypto import FileCrypto import os @bp.route('/upload', methods=['GET', 'POST']) def upload_file(): if request.method == 'POST': # 检查是否有文件部分 if 'file' not in request.files: flash('没有选择文件') return redirect(request.url) file = request.files['file'] password = request.form.get('password') operation = request.form.get('operation') # 'encrypt' or 'decrypt' if not password: flash('请输入密码') return redirect(request.url) if file.filename == '': flash('未选择文件') return redirect(request.url) # 保存上传的原始文件 upload_folder = current_app.config['UPLOAD_FOLDER'] original_filename = save_uploaded_file(file, upload_folder) if not original_filename: flash('文件类型不允许或保存失败') return redirect(request.url) original_path = os.path.join(upload_folder, original_filename) crypto = FileCrypto() try: if operation == 'encrypt': # 加密操作 encrypted_filename = f"encrypted_{original_filename}" encrypted_path = os.path.join(upload_folder, encrypted_filename) crypto.encrypt_file(original_path, encrypted_path, password) # 删除原始文件(可选,根据需求) os.remove(original_path) # 提供加密文件下载 return redirect(url_for('main.download_file', filename=encrypted_filename)) else: # 解密操作 decrypted_filename = f"decrypted_{original_filename}" decrypted_path = os.path.join(upload_folder, decrypted_filename) success = crypto.decrypt_file(original_path, decrypted_path, password) if success: os.remove(original_path) # 删除上传的加密文件 return redirect(url_for('main.download_file', filename=decrypted_filename)) else: flash('解密失败!请检查密码是否正确,或文件是否已损坏。') # 清理失败产生的临时文件 if os.path.exists(decrypted_path): os.remove(decrypted_path) return redirect(request.url) except Exception as e: current_app.logger.error(f"加解密处理异常: {e}") flash('服务器处理文件时发生错误') return redirect(request.url) finally: # 确保无论如何都尝试清理原始文件(如果还存在) pass return render_template('main/upload.html')安全与实操要点:
- 限制扩展名:
ALLOWED_EXTENSIONS列表必须严格控制,防止上传可执行文件(.exe, .sh, .php等)。 - 使用
secure_filename:这个函数会过滤掉文件名中的路径分隔符(/,\)和其他危险字符,有效防止路径遍历攻击。 - 文件大小限制:在 Flask 应用配置中设置
MAX_CONTENT_LENGTH,例如app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 # 100MB。超过此限制的请求会被 Flask 自动拒绝并返回 413 错误。 - 文件存储隔离:上传的文件不要放在 Web 根目录下,应该放在一个专门的、无法通过 URL 直接访问的目录。通过 Flask 的
send_from_directory视图来安全地提供下载。 - 及时清理:加解密操作完成后,应立即删除服务器上的临时文件(原始上传文件和中间生成文件),释放磁盘空间并减少安全风险。可以使用 Python 的
tempfile模块来管理临时文件生命周期。
4. 前端交互与用户体验优化
4.1 基础表单与AJAX上传
基础版本可以使用传统的表单提交,但页面会刷新。为了更好的体验,可以使用 AJAX。
templates/main/upload.html基础部分:
{% extends "base.html" %} {% block content %} <h2>文件加密/解密系统</h2> <form id="uploadForm" enctype="multipart/form-data"> <div> <label for="file">选择文件:</label> <input type="file" name="file" id="file" required> </div> <div> <label for="password">密码:</label> <input type="password" name="password" id="password" required> </div> <div> <label>操作:</label> <label><input type="radio" name="operation" value="encrypt" checked> 加密</label> <label><input type="radio" name="operation" value="decrypt"> 解密</label> </div> <button type="submit" id="submitBtn">开始处理</button> </form> <div id="progress" style="display:none;"> <p>文件正在处理中,请稍候...</p> <!-- 可以在这里加入进度条 --> </div> <div id="result"></div> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <script> $(document).ready(function() { $('#uploadForm').on('submit', function(e) { e.preventDefault(); // 阻止表单默认提交 var formData = new FormData(this); $('#submitBtn').prop('disabled', true); $('#progress').show(); $('#result').html(''); $.ajax({ url: '{{ url_for("main.upload_file") }}', type: 'POST', data: formData, processData: false, // 必须 contentType: false, // 必须 success: function(response) { $('#progress').hide(); $('#submitBtn').prop('disabled', false); // 假设成功返回的是一个包含下载链接的JSON或重定向 if (response.success) { $('#result').html('<div class="alert alert-success">处理成功!<a href="' + response.download_url + '">点击下载文件</a></div>'); } else { $('#result').html('<div class="alert alert-danger">' + response.message + '</div>'); } }, error: function(xhr) { $('#progress').hide(); $('#submitBtn').prop('disabled', false); var msg = '请求失败'; if (xhr.responseJSON && xhr.responseJSON.message) { msg = xhr.responseJSON.message; } $('#result').html('<div class="alert alert-danger">' + msg + '</div>'); } }); }); }); </script> {% endblock %}对应的,后端路由需要稍作修改以返回 JSON 响应,而不是直接重定向。这涉及到将之前的视图函数拆分为更细粒度的 API。
4.2 进度反馈与用户体验
对于大文件,处理时间可能较长。除了显示“处理中”的提示,更高级的做法是使用服务器推送(Server-Sent Events, SSE)或 WebSocket 来实时反馈进度。但对于毕业设计,一个简单的解决方案是:前端显示加载动画,后端将耗时操作(加解密)放入后台任务队列(如 Celery 或 RQ),并立即返回一个任务 ID。前端随后轮询另一个 API 接口,通过任务 ID 查询处理状态。这不仅能提升体验,还能让你的系统设计显得更“高级”,在论文中可以作为“异步任务处理”的亮点来写。
5. 数据库设计与用户文件管理
5.1 数据模型定义
在app/models.py中定义核心模型:
from datetime import datetime from flask_login import UserMixin from app import db, login_manager class User(UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), unique=True, index=True, nullable=False) email = db.Column(db.String(120), unique=True, index=True, nullable=False) password_hash = db.Column(db.String(128), nullable=False) # 关系:一个用户有多个文件记录 files = db.relationship('FileRecord', backref='owner', lazy='dynamic') def __repr__(self): return f'<User {self.username}>' class FileRecord(db.Model): id = db.Column(db.Integer, primary_key=True) original_filename = db.Column(db.String(255), nullable=False) # 用户上传时的文件名 stored_filename = db.Column(db.String(255), unique=True, nullable=False) # 服务器存储的唯一文件名 file_hash = db.Column(db.String(64)) # 文件哈希值,用于去重或校验(可选) operation = db.Column(db.String(20)) # 'encrypt' 或 'decrypt' status = db.Column(db.String(20), default='processing') # processing, completed, failed upload_time = db.Column(db.DateTime, index=True, default=datetime.utcnow) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) def __repr__(self): return f'<FileRecord {self.original_filename}>'设计思考:
FileRecord表记录了每一次文件操作,这为后续扩展功能(如操作历史、审计日志)打下了基础。stored_filename是服务器上生成的安全唯一文件名,对应我们之前用时间戳生成的文件名。file_hash字段可以存储文件的 SHA-256 哈希。虽然加解密后文件内容变了,但存储原始文件的哈希可以用于去重(避免同一文件重复加密占用空间)或完整性校验(在解密后验证文件是否与原始一致)。这是一个可选但能体现思考深度的设计点。status字段在实现异步任务时非常有用,前端可以轮询文件状态。
5.2 用户认证集成
使用 Flask-Login 可以快速实现用户会话管理。在app/auth/routes.py中实现注册、登录、登出视图。在app/__init__.py中设置login_manager.user_loader回调函数。这样,在main蓝图的路由中,就可以使用@login_required装饰器来保护视图,确保只有登录用户才能使用加解密功能,并将文件记录与用户关联。
6. 系统部署与上线考量
毕业设计不仅要求实现,还常常要求部署演示。这里有几个简单的方案。
6.1 本地部署与演示
最简单的方式是使用 Flask 内置的开发服务器。在项目根目录创建run.py:
from app import create_app app = create_app() if __name__ == '__main__': app.run(debug=True, host='0.0.0.0', port=5000)运行python run.py即可启动。host='0.0.0.0'允许同一网络下的其他设备访问,方便在答辩时用手机或评委电脑演示。注意:生产环境绝对不要使用debug=True。
6.2 使用 Gunicorn + Nginx 部署(Linux)
这是 Python Web 应用标准的部署方式之一,写在论文里会显得很专业。
- 安装 Gunicorn:
pip install gunicorn - 使用 Gunicorn 启动:
gunicorn -w 4 -b 127.0.0.1:8000 "app:create_app()"。这里-w 4表示启动 4 个 worker 进程处理并发请求。 - 配置 Nginx 反向代理:让 Nginx 监听 80 端口,将请求转发给 Gunicorn (127.0.0.1:8000)。Nginx 还能处理静态文件,效率更高。
一个简单的 Nginx 站点配置 (/etc/nginx/sites-available/myflaskapp):
server { listen 80; server_name your_domain_or_ip; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # 可选:让Nginx直接处理静态文件,减轻Flask负担 location /static { alias /path/to/your/project/app/static; expires 30d; } }6.3 使用 Docker 容器化部署
Docker 是现在更流行的部署方式,能保证环境一致性。编写Dockerfile和docker-compose.yml文件是加分项。
一个简单的Dockerfile:
# 使用官方Python镜像 FROM python:3.9-slim # 设置工作目录 WORKDIR /app # 复制依赖文件并安装 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 复制应用代码 COPY . . # 设置环境变量(生产环境配置) ENV FLASK_APP=run.py ENV FLASK_ENV=production # 暴露端口 EXPOSE 5000 # 启动命令,使用Gunicorn CMD ["gunicorn", "--bind", "0.0.0.0:5000", "run:app"]然后使用docker build -t file-encrypt-app .构建镜像,docker run -p 5000:5000 file-encrypt-app运行。你可以在论文中阐述容器化部署的优势,如环境隔离、易于扩展等。
7. 毕业设计文档(程序+开题+论文)撰写要点
有了扎实的项目,文档就是展示你工作的窗口。
开题报告:重点阐述“为什么要做这个系统”(研究背景与意义)、“它要做什么”(研究内容与目标)以及“你打算怎么做”(研究方案与技术路线)。技术路线部分,可以把本章前面提到的技术选型(Flask, cryptography, SQLite等)和系统模块图放进去。
论文正文:
- 绪论:讲清楚文件加密的背景、现有研究的不足、本系统的创新点或实用价值。
- 相关技术介绍:不要简单罗列,要结合你的系统。介绍 Flask 时,说明为什么选它而不是 Django;介绍 AES-GCM 时,说明它比 ECB、CBC 模式好在哪里。
- 系统分析与设计:这是核心章节。画出系统的用例图、功能模块图、数据库 ER 图、系统架构图(展示用户、浏览器、Nginx、Gunicorn、Flask App、数据库之间的关系)。详细描述每个模块的设计思路,比如加密模块的类图、序列图(描述加密请求的处理流程)。
- 系统实现:展示关键代码片段,并配上详细说明。不要贴大段无解释的代码。重点展示:Flask 应用工厂、加密解密核心函数、文件上传的安全处理、数据库模型定义。解释你如何解决关键技术难点,比如大文件处理、密钥安全管理。
- 系统测试:设计测试用例。包括功能测试(加密、解密、错误密码处理、文件类型限制)、性能测试(不同大小文件的加解密耗时)、安全性测试(尝试路径遍历、上传恶意文件)。可以使用 Python 的
unittest或pytest框架,并把测试结果和截图放在论文里。 - 总结与展望:总结你完成了什么,有什么收获。展望部分可以提出一些可行的扩展方向,比如:增加对非对称加密(RSA)的支持、实现 WebSocket 实时进度、开发移动端 App、集成云存储等。
程序源码:整理一个干净的代码仓库,包含完整的源代码、
requirements.txt、README.md(说明如何安装和运行)。确保代码风格良好(可以用black格式化),有必要的注释。
8. 常见问题与排查技巧实录
在实际开发中,你肯定会遇到各种坑。这里记录几个典型的:
ImportError: No module named 'cryptography'或类似错误- 问题:明明用 pip 安装了,运行时却找不到模块。
- 原因:最常见的原因是虚拟环境(venv)未激活,或者在系统 Python 下安装的包,却在虚拟环境中运行。
- 解决:始终在项目目录下,先执行
source venv/bin/activate(Linux/Mac) 或venv\Scripts\activate(Windows) 激活虚拟环境,再运行python run.py。使用pip freeze检查当前环境已安装的包。
加密/解密时出现
cryptography.exceptions.InvalidKey或解密后文件损坏- 问题:解密失败,或者解密出来的文件打不开。
- 原因: a.密钥错误:加密和解密使用的密码不一致。 b.文件结构被破坏:加密文件在存储或传输过程中损坏,或者加解密时读写文件的逻辑不一致(比如 salt/nonce 长度弄错)。 c.编码问题:密码字符串在加解密过程中编码不一致。
- 排查:
- 首先,用同一个密码对一个小文本文件进行加密再解密,看是否成功。
- 在加密和解密函数中,打印或日志记录 salt 和 nonce 的值,确保它们被正确读写。
- 检查密钥派生过程:确保加密和解密时使用的
salt是同一个,iterations参数相同。 - 对于文件损坏,可以计算加密前后文件的哈希值,确保加密过程本身没问题。
文件上传后找不到,或
send_from_directory报 404 错误- 问题:文件上传成功,但下载时提示找不到。
- 原因: a.路径错误:
UPLOAD_FOLDER配置的路径不对,或者save_uploaded_file函数保存的路径与send_from_directory查找的路径不一致。 b.权限问题:Web 服务器进程(如 gunicorn worker)对上传目录没有写权限或读权限。 - 解决:
- 在保存文件和提供下载时,都使用
os.path.join(current_app.config['UPLOAD_FOLDER'], filename)来构建绝对路径。 - 检查上传目录是否存在:
os.makedirs(upload_folder, exist_ok=True)。 - 在 Linux 部署时,使用
ls -la检查上传目录的所有者和权限,确保运行 Flask 的用户有权读写。
- 在保存文件和提供下载时,都使用
SQLite 数据库操作报错,特别是
OperationalError: no such table- 问题:定义了模型,但运行时报错说表不存在。
- 原因:没有创建数据库表。Flask-SQLAlchemy 不会自动根据模型创建表。
- 解决:在首次运行前,需要在 Python Shell 中执行:
或者写一个简单的初始化脚本from app import create_app, db app = create_app() with app.app_context(): db.create_all() # 这会根据所有模型创建数据表init_db.py。
前端 AJAX 上传,后端收不到文件或表单数据
- 问题:前端显示上传成功,但后端
request.files或request.form为空。 - 原因:AJAX 请求的
contentType和processData设置不正确。 - 解决:确保 AJAX 配置中设置了
processData: false和contentType: false。这样 jQuery 才不会对 FormData 对象进行错误的处理。
- 问题:前端显示上传成功,但后端
在 Windows 开发,部署到 Linux 后路径问题
- 问题:代码在 Windows 上运行正常,在 Linux 上各种文件路径错误。
- 原因:Windows 使用反斜杠
\作为路径分隔符,Linux 使用正斜杠/。 - 解决:始终使用
os.path.join()函数来拼接路径,它是跨平台的。避免在代码中直接写死C:\uploads或/var/uploads这样的路径,而是从配置文件中读取。
这个项目麻雀虽小,五脏俱全。认真走完从设计到实现的每一步,你收获的将不仅仅是一个能运行的毕业设计,更是一套完整的、可复用的 Web 应用开发方法论。遇到问题别怕,善用搜索引擎(关键词:Flask upload file, cryptography AES GCM example, SQLAlchemy filter by user),多查阅官方文档,大部分坑都有现成的解决方案。最后,代码和文档的整洁度,往往比功能的复杂度更能打动评委。祝你顺利通过答辩!