![]()
一個(gè)完整用戶系統(tǒng)的后端代碼,網(wǎng)上能搜到上萬份。但把「為什么這樣寫」講清楚的,十不足一。
這篇教程用Flask+MySQL搭了一套帶登錄、注冊(cè)、商品展示的全棧應(yīng)用。代碼量不大,卻塞進(jìn)了三層架構(gòu)、會(huì)話管理和數(shù)據(jù)庫交互三種核心模式。對(duì)想從調(diào)包俠進(jìn)階的開發(fā)者來說,這是塊不錯(cuò)的墊腳石。
三層架構(gòu)不是炫技,是分工
教程把系統(tǒng)拆成表現(xiàn)層、業(yè)務(wù)邏輯層、數(shù)據(jù)訪問層。表現(xiàn)層只管渲染模板和收發(fā)表單,業(yè)務(wù)層處理驗(yàn)證和流程控制,數(shù)據(jù)層只和MySQL打交道。
這種拆法讓改需求變得便宜。想換前端框架?動(dòng)表現(xiàn)層就行。數(shù)據(jù)庫從MySQL遷到PostgreSQL?改數(shù)據(jù)層的連接配置。各層之間用接口約定,而不是直接掏對(duì)方的數(shù)據(jù)結(jié)構(gòu)。
Flask本身沒強(qiáng)制分層,所以新手常犯的錯(cuò)誤是把SQL語句直接糊在路由函數(shù)里。教程里的products()路由雖然簡單,但已經(jīng)做了示范:cursor的創(chuàng)建、查詢、關(guān)閉三步走,沒讓數(shù)據(jù)庫連接泄漏到全局。
會(huì)話管理:Flask的secret_key到底防什么
代碼里有一行容易被跳過:app.secret_key = 'secret-key'。這串字符是會(huì)話簽名用的私鑰,不是加密用戶數(shù)據(jù)的。
Flask的session默認(rèn)存客戶端cookie里,內(nèi)容是Base64編碼的JSON,任何人都能解碼看明文。secret_key的作用是生成HMAC簽名,讓服務(wù)端能識(shí)別cookie有沒有被篡改。攻擊者改了session里的customer_id,簽名對(duì)不上,服務(wù)端直接拒掉。
教程用的'secret-key'是占位符,生產(chǎn)環(huán)境得換成32字節(jié)以上的隨機(jī)串。2023年有團(tuán)隊(duì)因?yàn)榘衙荑€提交到GitHub,被爬蟲批量薅走數(shù)據(jù)庫憑證,這類事故在OWASP報(bào)告里年年上榜。
登錄流程的防御細(xì)節(jié)
顧客登錄路由customer_login做了幾件事:POST請(qǐng)求時(shí)取表單數(shù)據(jù),SQL參數(shù)化查詢防注入,驗(yàn)證通過后往session寫用戶ID和姓名,失敗則回傳錯(cuò)誤信息。
參數(shù)化查詢的%s不是字符串拼接,是MySQLdb的占位符機(jī)制。即使用戶輸入' OR '1'='1,也會(huì)被當(dāng)作普通字符串處理,不會(huì)篡改查詢語義。這是2008年就成熟的防御手段,但2024年的漏洞庫里依然能搜到拼接SQL的新項(xiàng)目。
session里只存了customer_id和customer_name,沒存密碼哈希。這是正確的——密碼驗(yàn)證是一次性的,沒必要在內(nèi)存里留著。有些框架的示例代碼會(huì)把整個(gè)用戶對(duì)象塞進(jìn)session,既浪費(fèi)帶寬又增加泄露面。
注冊(cè)校驗(yàn)的邊界情況
注冊(cè)路由customer_signup的校驗(yàn)邏輯值得細(xì)看。先檢查四個(gè)字段是否全填,再比對(duì)密碼和確認(rèn)密碼,最后查郵箱是否已存在。三步校驗(yàn)的順序有講究:前端該做的格式檢查(如郵箱正則)這里沒做,因?yàn)榻坛叹劢购蠖耍坏蠖吮仨毞赖闹貜?fù)注冊(cè)和空值注入,一步?jīng)]落。
密碼比對(duì)用明文,教程注釋里應(yīng)該提了生產(chǎn)環(huán)境要換bcrypt或Argon2。明文存儲(chǔ)是2010年前的做法,現(xiàn)在連CSDN都不再犯這個(gè)錯(cuò)。如果這是教學(xué)代碼,需要加顯式警告;如果是項(xiàng)目代碼,屬于需要立即修復(fù)的漏洞。
查重郵箱的SQL用了SELECT id而不是SELECT *,這是個(gè)小優(yōu)化。MySQL的覆蓋索引機(jī)制下,只查主鍵可以不走回表,雖然在這個(gè)數(shù)據(jù)量下差別微乎其微,但習(xí)慣養(yǎng)成后在大表上能省出數(shù)量級(jí)的時(shí)間。
模板渲染的權(quán)限暗示
登錄和注冊(cè)路由都傳了role和role_key給模板,說明這套系統(tǒng)可能還有管理員或其他角色。模板層根據(jù)這些變量渲染不同的文案和跳轉(zhuǎn)鏈接,但真正的權(quán)限校驗(yàn)應(yīng)該在路由裝飾器或中間件里做。
教程沒展示管理員代碼,但從結(jié)構(gòu)能推測:如果/admin路由只檢查session.get('admin_id'),那偽造session就能越權(quán)。完整的方案是每次請(qǐng)求都查數(shù)據(jù)庫驗(yàn)證角色,或者把角色信息寫進(jìn)JWT并設(shè)短過期時(shí)間。Flask-Login這類擴(kuò)展封裝了這些細(xì)節(jié),但理解原生實(shí)現(xiàn)有助于調(diào)bug。
MySQL連接的隱式坑
配置段把用戶名密碼寫死在代碼里,是教程的常見妥協(xié)。實(shí)際部署至少要用環(huán)境變量或密鑰管理服務(wù),Docker場景下可以掛secret文件。
更隱蔽的問題是連接池。Flask-MySQLdb每次請(qǐng)求新建連接,小流量沒事,并發(fā)稍高就會(huì)打爆MySQL的max_connections。生產(chǎn)環(huán)境該換SQLAlchemy配連接池,或者把Flask跑在Gunicorn多進(jìn)程模式下分擔(dān)壓力。教程沒提這些,但代碼結(jié)構(gòu)預(yù)留了替換空間——數(shù)據(jù)層集中在幾個(gè)路由函數(shù)里,不像有些項(xiàng)目把SQL撒得滿屏都是。
數(shù)據(jù)庫名noeari看起來是隨機(jī)打的,這種命名在團(tuán)隊(duì)協(xié)作時(shí)會(huì)制造困惑。測試環(huán)境用app_test、生產(chǎn)用app_prod是更清晰的約定,配合Flask的app.config.from_envvar可以按環(huán)境加載不同配置。
商品列表路由products加了is_available = 1的過濾條件,說明數(shù)據(jù)庫里可能有軟刪除或下架機(jī)制。沒展示的是這個(gè)字段誰有權(quán)修改——如果普通用戶能通過某個(gè)接口把自己買的商品標(biāo)為不可用,就屬于業(yè)務(wù)邏輯漏洞。三層架構(gòu)防不了這種錯(cuò),得靠測試用例覆蓋。
代碼里沒異常處理。MySQL連接失敗會(huì)拋OperationalError,直接暴露500頁面和堆棧信息。Flask的errorhandler裝飾器可以攔截這些異常,返回友好的錯(cuò)誤頁并記日志。對(duì)新手教程來說,省略異常處理是合理的,但上線前必須補(bǔ)上。
session的持久化也沒提。默認(rèn)cookie-based session在瀏覽器關(guān)閉后就消失,要實(shí)現(xiàn)「記住我」功能得換服務(wù)端存儲(chǔ),比如Redis或數(shù)據(jù)庫表。Flask-Session擴(kuò)展做了這層封裝,但理解cookie的局限性能幫你選對(duì)方案。
整個(gè)項(xiàng)目的代碼風(fēng)格是教學(xué)式的:變量名直白,注釋分段清晰,沒有抽象過度。比如路由函數(shù)里直接操作cursor,沒封裝成DAO類。這種寫法在真實(shí)項(xiàng)目里會(huì)膨脹,但作為入門材料,讓讀者一眼看清數(shù)據(jù)流向比追求架構(gòu)完美更重要。
如果你跟著敲完這套代碼,下一步該問自己:如果把MySQL換成MongoDB,哪些層要?jiǎng)樱咳绻鯝PI版本控制,URL路由怎么設(shè)計(jì)?如果用戶量漲到十萬,session存儲(chǔ)怎么擴(kuò)展?這些問題沒有標(biāo)準(zhǔn)答案,但三層架構(gòu)給了你做決定的坐標(biāo)系。
教程最后沒給部署指南,這是故意的——本地跑通和上線運(yùn)維之間,還隔著Nginx配置、HTTPS證書、日志輪轉(zhuǎn)、監(jiān)控告警十條街。那些是另一篇文章的篇幅,也是區(qū)分「能寫代碼」和「能扛系統(tǒng)」的分水嶺。
你現(xiàn)在手邊有類似的項(xiàng)目嗎?是直接把SQL寫在視圖函數(shù)里,還是已經(jīng)拆出了獨(dú)立的數(shù)據(jù)層?
特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺(tái)“網(wǎng)易號(hào)”用戶上傳并發(fā)布,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.