文章目錄
- 🧱 技術棧
- 🗂? 項目結構與配置
- 🔐 用戶身份校驗邏輯
- 📤 頭像上傳接口:`/file/avatar/upload`
- 📥 加載頭像接口:`/file/avatar/load/<filename>`
- 🧪 示例請求(使用 cURL 測試)
- 上傳頭像
- 加載頭像
- 🔒 安全性與優化建議
在 Web 應用中,文件上傳是一個非常常見且不可或缺的需求,尤其是在涉及用戶頭像、圖片展示等功能時。本文將基于 Flask 框架,結合簡單的用戶身份驗證邏輯,實現一個完整的“頭像上傳與加載”模塊。
🧱 技術棧
我們的模塊采用了一套簡潔而高效的技術棧:
- 后端框架: Flask
- 數據庫: MySQL(通過自定義的
SQL.DatabasePool
封裝類訪問) - 緩存: Flask-Caching(示例中配置為
simple
緩存) - 安全處理: 文件名加密、路徑過濾、防止目錄遍歷攻擊
🗂? 項目結構與配置
首先,我們定義好上傳文件的保存路徑。這確保了所有頭像都存儲在一個集中且易于訪問的位置:
import osBASE_DIR = os.path.dirname(os.path.abspath(__file__))
# 假設 'apps/fileManage' 是一個子模塊,調整 BASE_DIR 到項目根目錄
BASE_DIR = BASE_DIR.split(r"\apps\fileManage")[0]
UPLOAD_FOLDER = os.path.join(BASE_DIR, 'uploads')
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
這段代碼保證了以下幾點:
- 上傳文件將位于項目根目錄下的
uploads
文件夾中。 - 如果
uploads
文件夾不存在,它將自動創建,避免常見的系統錯誤。
🔐 用戶身份校驗邏輯
在執行任何文件操作之前,服務器會通過檢查傳入 Cookie 中的 token
字段來驗證用戶身份。這一關鍵步驟確保只有授權用戶才能上傳或訪問文件。
# 假設 'request' 已從 flask 導入
cookie = request.headers.get('Cookie')
# ... 額外解析以從 cookie 字符串中提取 token
token = dict1.get('token') # 假設 dict1 已填充 cookie 鍵值對
res = sql.executeQuery("SELECT userId FROM onlineUsers JOIN users ON onlineUsers.SessionId = users.userId WHERE onlineUsers.SessionId = '{}'".format(token)
)
如果用戶未登錄或會話失效,服務器將返回 401 Unauthorized
錯誤,從而保護您的應用程序免受未經授權的訪問。
📤 頭像上傳接口:/file/avatar/upload
此 POST
接口負責處理頭像圖片的上傳。完整的流程設計注重魯棒性和安全性:
- 身份驗證: 系統首先解析 Cookie 并驗證用戶會話的合法性。
- 文件校驗:
- 它會驗證請求中是否存在文件(
file
對象)。 - 文件名不能為空。
- 至關重要的是,只允許圖片文件擴展名(
.png
、.jpg
、.jpeg
、.gif
),防止上傳潛在的惡意文件類型。
- 它會驗證請求中是否存在文件(
- 重命名處理: 為了避免命名沖突并確保每個頭像都有一個唯一的標識符,文件名會使用 MD5 進行哈希處理,結合用戶名和原始文件名生成一個唯一字符串,并附加原始文件擴展名。
- 文件保存: 經驗證和重命名的文件隨后安全地保存到服務器上指定的
uploads
目錄中。 - 數據庫更新: 新上傳頭像的路徑會更新到
users
表中,將頭像與相應的用戶關聯起來。 - 返回結果: 成功消息以及可訪問的文件路徑會返回給客戶端。
核心實現代碼如下:
# 假設 'request' 已從 flask 導入,并且 'encrypt' 和 'sql' 已定義
file = request.files['file']
# 使用用戶名和原始文件名的 MD5 哈希生成唯一文件名
newFilename = encrypt.hash_md5(username + file.filename) + '.' + file.filename.split('.')[-1]
file.save(os.path.join(UPLOAD_FOLDER, newFilename))
# 更新數據庫中用戶的頭像路徑
sql.executeUpdate("UPDATE users SET avatar = 'server:{}' WHERE userId = {}".format(newFilename, userId))
📥 加載頭像接口:/file/avatar/load/<filename>
此 GET
接口根據唯一的頭像文件名提供已上傳的圖片資源。它包含了多項安全和性能增強:
- 路徑過濾: 這是一項關鍵的安全措施,可防止用戶在文件名中構造
../
序列,從而嘗試進行目錄遍歷攻擊并訪問uploads
目錄之外的未經授權的文件。 - 后綴驗證: 只允許提供圖片文件類型,防止暴露非圖片文件。
- 緩存處理: 該接口支持瀏覽器緩存,通過減少服務器請求顯著提高頻繁訪問頭像的性能。(更多內容請參閱“安全性與優化建議”部分。)
- MIME 類型推斷: 服務器會根據文件擴展名自動推斷并設置響應的適當
Content-Type
(MIME 類型)(例如,image/png
、image/jpeg
),確保瀏覽器正確渲染圖像。
# 假設 'Response' 已從 flask 導入,并且 'data' 包含圖像內容
mimetype = f'image/{filename.rsplit(".", 1)[-1].lower()}'
return Response(data, mimetype=mimetype)
🧪 示例請求(使用 cURL 測試)
為了幫助您測試和理解 API 交互,以下是上傳和加載頭像的 cURL 命令:
上傳頭像
curl -X POST http://yourdomain.com/file/avatar/upload \-H "Cookie: token=YOUR_TOKEN" \-F "file=@/path/to/your/avatar.png"
請將 YOUR_TOKEN
替換為有效的用戶會話令牌,將 /path/to/your/avatar.png
替換為您的圖片文件的實際路徑。
加載頭像
curl http://yourdomain.com/file/avatar/load/xxxxxx.png --output avatar.png
請將 xxxxxx.png
替換為您希望加載的頭像的實際唯一文件名。--output avatar.png
標志會將下載的圖像保存為 avatar.png
文件。
🔒 安全性與優化建議
雖然我們的模塊提供了堅實的基礎,但對于生產環境,請考慮以下額外的措施:
-
? 文件名哈希: 已實現,這一關鍵步驟通過確保唯一文件名來防止用戶互相覆蓋文件。
-
? 路徑遍歷保護: 已實現,這可以防止惡意用戶訪問指定上傳目錄之外的文件。
-
? 文件大小限制: 實現服務器端檢查以限制最大文件大小。這可以防止由于上傳過大文件而導致的拒絕服務攻擊,并節省服務器資源。
-
? 健壯的錯誤處理: 增強文件操作的錯誤處理(例如,磁盤已滿、權限問題),以便為用戶和管理員提供更具信息量的反饋。
-
? 云存儲集成: 對于生產環境,特別是流量較高的場景,請考慮將頭像存儲卸載到專門的云存儲解決方案,如阿里云 OSS、七牛云或 Amazon S3。這可以顯著提高可伸縮性、可靠性,并減輕應用程序服務器的負擔。
-
? 瀏覽器緩存以提高性能: 明確地為頭像等靜態資源向
Response
添加Cache-Control
頭。這允許瀏覽器緩存圖像,從而加快后續加載速度并減少服務器負載。return Response(data, mimetype=mimetype, headers={'Cache-Control': 'public, max-age=86400'})
此頭信息告訴瀏覽器將圖像緩存 24 小時(86400 秒)。