約稿平臺:需求拆解到部署上線全記錄)
最近在做一個面向動漫畫家的約稿平臺技術(shù)棧是 Python Vue開發(fā)環(huán)境主力用的是 PyCharm。簡單說這個平臺讓插畫師、漫畫家把自己的作品掛出來做個人畫廊讓有約稿需求的客戶直觀地看到畫風(fēng)、了解接單規(guī)則再在線發(fā)起約稿訂單。后端接口用 Django 搭 REST API前端用 Vue 做頁面和交互兩邊用 JSON 交流整個項(xiàng)目跑起來非常順。這篇文章把項(xiàng)目從需求拆解到數(shù)據(jù)庫設(shè)計(jì)、后端實(shí)現(xiàn)、Vue 頁面、部署上線整套流程都記錄下來包括踩過的坑和排查思路。如果你正想做一個同類的約稿平臺或者只是打算用 Python Vue 練一個完整的全棧項(xiàng)目這份記錄應(yīng)該能幫你省不少繞路的時間。1. 約稿平臺的核心需求與技術(shù)選型解析1.1 先拆需求約稿平臺到底要做什么我說句實(shí)在的約稿平臺和普通電商完全不一樣。畫畫這種東西不是標(biāo)準(zhǔn)品同一張插畫不可能量產(chǎn)每一個訂單都是定制的。所以在動工之前我先把業(yè)務(wù)邏輯拆了個遍最后落到四條核心主線上展示畫家把自己的作品集展示出來客戶瀏覽畫風(fēng)、看標(biāo)簽、看近期作品判斷這位畫師的風(fēng)格是否是自己想要的。約稿客戶看到滿意的畫師后發(fā)起咨詢描述需求比如“要一張 Q 版頭像雙人互動需要簡單背景”并約定價格和工期。交易談妥后生成訂單客戶先支付定金畫師開始創(chuàng)作過程中上傳草稿或過程圖供確認(rèn)最終交付源文件和成品。評價訂單完成后雙方互相評價形成畫師信譽(yù)沉淀也給后來的客戶提供參考。這個拆解直接決定了我后面所有數(shù)據(jù)表的設(shè)計(jì)思路。最核心的一點(diǎn)是約稿訂單不是“商品訂單”而是“服務(wù)訂單”它要記錄的不只是金額和狀態(tài)還有需求描述、參考圖、節(jié)點(diǎn)反饋、交付物列表這些跟創(chuàng)作過程強(qiáng)相關(guān)的東西。如果把約稿當(dāng)成普通商品用一套 SPU/SKU 邏輯去套后期改起來會非常痛苦。1.2 框架選型Django 還是 Flask后端用 Django 還是 Flask相當(dāng)于在選擇“自帶輪子”還是“自己造輪子”。我把兩者的差異簡單列個表對比維度DjangoFlask上手難度概念多一些簡單直接自帶功能ORM、Admin 后臺、認(rèn)證、CSRF幾乎沒有全部自己接適合場景業(yè)務(wù)復(fù)雜、需要后臺管理的系統(tǒng)輕量 API、原型快速演示生態(tài)組件豐富自由組合度高這個項(xiàng)目我最后選了 Django核心原因有三個。第一約稿平臺有三類角色畫師、約稿客戶、管理員權(quán)限模型比較復(fù)雜Django 自帶的用戶體系和認(rèn)證框架能省掉大量底層工作。第二訂單、作品、消息這些數(shù)據(jù)都需要一個后臺給運(yùn)營人員做審核和查看Django 自帶的 Admin 后臺幾乎是白送的。第三數(shù)據(jù)庫遷移工具很成熟模型字段改來改去也不會亂。Flask 絕對不是一個差選擇它的靈活性和輕量程度在純 API 場景下讓人愛不釋手。但如果我把認(rèn)證、ORM、后臺管理、審核流程全部自己拼一遍時間和踩坑成本都會成倍增加。既然這個項(xiàng)目的目標(biāo)是上線運(yùn)營不是做個課程作業(yè)那么選 Django 是更穩(wěn)的一條路。1.3 前端選型與開發(fā)環(huán)境準(zhǔn)備前端我用的是 Vue 的方案。選擇 Vue 的理由很現(xiàn)實(shí)Vue 的雙向綁定在處理表單類交互時效率極高而約稿平臺恰恰就是一個“作品展示 復(fù)雜表單 狀態(tài)切換”的項(xiàng)目。生態(tài)方面周邊工具鏈和組件庫都非常成熟頁面開發(fā)速度明顯比另一套知名框架快。開發(fā)環(huán)境這塊我直接用 PyCharm 作為主力 IDE這個選擇估計(jì)讓不少人意外。其實(shí) PyCharm 的專業(yè)版本本身支持 JavaScript 和 Vue 文件的編輯調(diào)試我把 Django 后端和 Vue 前端放在了同一個工作區(qū)。這樣改完一行 Python 代碼切到 Vue 文件完全不需要在兩個 IDE 之間來回切換斷點(diǎn)調(diào)試也能同時跟 Python 接口和前端請求。當(dāng)然這個配置有一點(diǎn)要注意PyCharm 對大型 Vue 項(xiàng)目的索引會變慢所以我特意在前端依賴目錄上右鍵標(biāo)記了“排除”讓 IDE 不索引 node_modules 里的海量文件。不設(shè)置的話你會發(fā)現(xiàn)打開項(xiàng)目后風(fēng)扇狂轉(zhuǎn)寫代碼都有延遲感。2. 數(shù)據(jù)庫設(shè)計(jì)與后端核心模塊實(shí)現(xiàn)2.1 表結(jié)構(gòu)設(shè)計(jì)一次把模型想清楚在設(shè)計(jì)數(shù)據(jù)庫時我反復(fù)提醒自己一句話約稿平臺的核心是一張訂單流水的舞臺而不是一張作品表的相冊。所以“作品集”和“訂單”是兩條獨(dú)立但互相關(guān)聯(lián)的線。最終核心表定了七張用戶表擴(kuò)展了 Django 內(nèi)置用戶增加手機(jī)號、昵稱、頭像、用戶類型整數(shù)區(qū)分畫師與客戶角色、個人簡介。畫師資料表只給畫師角色使用存儲接單狀態(tài)是否開放約稿、起價、平均完成天數(shù)、畫風(fēng)標(biāo)簽。作品集表關(guān)聯(lián)畫師文字字段有標(biāo)題、描述、標(biāo)簽媒體字段有原圖地址、縮略圖地址、圖片寬高比。訂單表核心服務(wù)訂單關(guān)聯(lián)發(fā)起方、畫師、需求描述、金額、狀態(tài)、定金比例、截止日期、交付物列表。消息表訂單內(nèi)的溝通記錄關(guān)聯(lián)發(fā)送人、接收人、所屬訂單。評價表訂單完成后雙方互相評價包含評分和內(nèi)容。附件表保存訂單過程中產(chǎn)生的參考圖、過程圖、最終交付文件統(tǒng)一做版本管理。這里特別想提醒一點(diǎn)訂單狀態(tài)不要用字符串硬編碼在數(shù)據(jù)庫里我用的是整數(shù)狀態(tài)碼具體含義在代碼層的狀態(tài)機(jī)里管理。這樣訂單從“待付款”到“已完成”的每一次流轉(zhuǎn)都能被服務(wù)層校驗(yàn)不會出現(xiàn)臟數(shù)據(jù)。2.2 Django REST Framework 接口設(shè)計(jì)與 JWT 認(rèn)證后端我用 Django REST FrameworkDRF搭建 REST API接口風(fēng)格完全走 REST。核心接口清單大概是這樣的POST /api/auth/register 和 POST /api/auth/token 處理注冊和登錄GET /api/artworks/ 獲取畫廊作品列表支持按畫師、標(biāo)簽、排序篩選POST /api/orders 發(fā)起約稿訂單PUT /api/orders/{id}/status 畫師更新訂單狀態(tài)GET /api/orders/{id}/messages 獲取訂單內(nèi)消息POST /api/orders/{id}/messages 發(fā)送消息認(rèn)證方案我選了 JWT用的是現(xiàn)成的 simplejwt 庫配置起來很省心。我把 Access Token 的過期時間設(shè)成了兩個小時Refresh Token 設(shè)成了七天。老實(shí)說這個配置在某些場景下會帶來麻煩比如客戶掛機(jī)一段時間回來之后 token 過期了但只要前端配合做靜默刷新體驗(yàn)上幾乎無感。權(quán)限控制是用 DRF 的權(quán)限類來做角色校驗(yàn)的。訂單創(chuàng)建接口要求登錄用戶畫師修改訂單狀態(tài)接口必須校驗(yàn)收當(dāng)前登錄者是不是該訂單所屬的畫師。這塊我特別強(qiáng)調(diào)權(quán)限校驗(yàn)絕對不能只靠前端隱藏按鈕那是不可靠的后端在每個寫操作前都必須重新校驗(yàn)身份。2.3 圖片上傳與對象存儲方案畫廊平臺有兩大物理問題繞不開圖片數(shù)量多交付文件大。我第一版簡單粗暴地把所有圖片存到自己的服務(wù)器上結(jié)果就是磁盤空間和帶寬很快告急。后來換成了對象存儲方案前端上傳文件時直接傳到存儲服務(wù)后端只保存返回的文件 URL。文件路徑我按目錄做了隔離格式是畫師ID/作品集/時間戳_隨機(jī)串.jpg。這樣既避免文件名沖突也方便之后做目錄級別的清理和遷移。圖片處理這塊我也做了一層優(yōu)化。原圖必須留一份因?yàn)楫嫀熜枰桓对次募o客戶但展示圖一定要有縮略圖否則前端瀑布流一下加載幾百張動輒幾 MB 的圖片頁面直接卡死??s略圖生成放在后端用 Python 圖像處理庫把大圖壓縮到合適尺寸前端滾動時只加載縮略圖。實(shí)測下來一張 2MB 原圖和一掌 100KB 縮略圖的加載體驗(yàn)差距完全是兩個世界的感受。2.4 訂單狀態(tài)機(jī)與支付對接訂單狀態(tài)是整個項(xiàng)目里最容易被改壞的部分所以我在動手寫接口之前先設(shè)計(jì)了一套狀態(tài)機(jī)。六個狀態(tài)流轉(zhuǎn)路徑清晰狀態(tài)碼含義可流轉(zhuǎn)到1待付款畫師已確認(rèn)需求22已付款33繪制中44待驗(yàn)收5 或回 35已完成66已完成且評價終態(tài)有了這套狀態(tài)機(jī)客戶取消訂單或者發(fā)生退款糾紛的時候系統(tǒng)只允許從特定狀態(tài)觸發(fā)取消邏輯而不是任何狀態(tài)都能躥到“已取消”。支付對接這里我踩過一個非常實(shí)在的坑。個人開發(fā)沒有企業(yè)資質(zhì)正規(guī)第三方支付通道很難直接接入。我最后的方案是先走“平臺訂單 線下支付確認(rèn)”的模式客戶下單后看到支付信息完成支付后提交支付憑證截圖平臺人工或半自動確認(rèn)到賬再把訂單狀態(tài)推到“已付款”。如果你也是個人開發(fā)者這個方案最務(wù)實(shí)。但務(wù)必把流程在界面上寫清楚避免客戶付完錢找不到確認(rèn)入口白等半天。3. Vue 前端實(shí)戰(zhàn)畫家畫廊與約稿全流程3.1 前端路由規(guī)劃與頁面結(jié)構(gòu)Vue 前端的路由我用 Vue Router 規(guī)劃了這樣一組頁面首頁熱門作品流、畫師推薦位畫師列表頁按畫風(fēng)標(biāo)簽篩選、搜索、排序畫師畫廊詳情頁作品大圖展示、個人簡介、接單規(guī)則、起價和排期信息創(chuàng)建約稿訂單頁選擇規(guī)格、填寫需求描述、上傳參考圖訂單中心區(qū)分“我發(fā)起的訂單”和“我接的訂單”兩個視角消息中心按訂單聚類的聊天界面路由守衛(wèi)我做得比較嚴(yán)格。涉及訂單、消息的頁面都必須登錄未登錄直接跳轉(zhuǎn)登錄頁。這里有一個細(xì)節(jié)很多人容易忽略路由守衛(wèi)里不能只看本地有沒有 token因?yàn)橐粡堖^期或偽造的 token 同樣能騙過前端判斷。穩(wěn)妥做法是在路由守衛(wèi)里不僅檢查 token 存在性還需要用它去請求一次用戶信息請求失敗就清掉本地狀態(tài)并跳登錄。3.2 axios 封裝與用戶登錄態(tài)管理我專門建了 request 模塊來封裝 axios所有與后端的數(shù)據(jù)請求都走這一個入口。封裝內(nèi)容包含三塊baseURL 根據(jù) Vite 的環(huán)境變量區(qū)分開發(fā)和生產(chǎn)環(huán)境請求攔截器統(tǒng)一把 Access Token 加到 Authorization 請求頭響應(yīng)攔截器統(tǒng)一處理錯誤格式遇到 401 會自動嘗試用 Refresh Token 刷新刷新失敗再退出登錄這里面最容易出問題的是“并發(fā)刷新”。用戶打開多個需要鑒權(quán)的請求時如果第一個 401 觸發(fā)了刷新其他幾個接口還拿著舊 token 請求就會出現(xiàn)連環(huán) 401、連環(huán)刷新最后把 Refresh Token 都刷失效。我最后的解法是維護(hù)一個刷新 Promise 變量第一個 401 觸發(fā)刷新時把這個 Promise 存起來后續(xù)接口直接復(fù)用這個 Promise刷新完成后統(tǒng)一重新發(fā)起請求。這個技巧雖然不起眼但它實(shí)打?qū)嵔鉀Q了線上用戶量稍大之后的一大類登錄異常。3.3 畫廊瀑布流與作品詳情畫廊是平臺的門面我用柵格布局做了第一版但畫作的寬高比差異實(shí)在太大等高的卡片會把很多圖裁剪得很難看。后來換成了瀑布流方案每次滾動加載一批作品每個卡片根據(jù)圖片自身的寬高比自適應(yīng)高度。技術(shù)實(shí)現(xiàn)其實(shí)不復(fù)雜。后端返回作品列表時同時返回圖片的 width 和 height 字段前端先按已知寬高比渲染占位區(qū)域圖片真正加載完再用真實(shí)高度做一次糾正。這樣用戶滾動時不會出現(xiàn)圖片加載完成后突然跳位子的情況。作品詳情頁我特別重視“從看到買”的轉(zhuǎn)化路徑。大圖預(yù)覽、畫師信息、作品標(biāo)簽這些是基礎(chǔ)項(xiàng)最重要的是要把“發(fā)起約稿”按鈕做短路徑。我直接在詳情頁展示起價和預(yù)計(jì)排期點(diǎn)擊按鈕后會自動攜帶該作品信息跳轉(zhuǎn)到訂單表單客戶不用重新填一遍風(fēng)格參考。很多平臺在這一點(diǎn)上做得很糟糕客戶看中了作品還要在畫廊和訂單頁面之間來回復(fù)制粘貼這體驗(yàn)太勸退了。3.4 約稿下單流程與前端狀態(tài)同步約稿下單是整個業(yè)務(wù)閉環(huán)的關(guān)鍵在表單設(shè)計(jì)上我下了不少功夫。需求描述支持多行文本同時提供一組可勾選的需求標(biāo)簽比如 Q 版、半身像、全身像、場景、多人等客戶勾選完再補(bǔ)充文字。參考圖支持多圖上傳上傳前會在前端做一次壓縮避免大圖直接懟到服務(wù)器。風(fēng)格選擇這部分我做了個貼心設(shè)計(jì)客戶可以從畫師作品集中直接點(diǎn)選參考風(fēng)格圖系統(tǒng)會把風(fēng)格信息和訂單綁定畫師打開訂單就能一眼看清客戶想要的畫風(fēng)傾向。訂單創(chuàng)建之后前端需要讓客戶和畫師都能及時看到狀態(tài)變化。我這里用了“輪詢 手動刷新”的組合方案。關(guān)鍵節(jié)點(diǎn)比如畫師上傳了交付圖、客戶確認(rèn)了驗(yàn)收前端每 30 秒輪詢一次訂單詳情普通查看場景則完全交給用戶下拉手動刷新。沒有把整個消息體系做成 WebSocket 實(shí)時推送因?yàn)閷χ行⌒推脚_來說成本高、收益低完全沒必要。4. 實(shí)操過程中的常見問題與排查記錄4.1 跨域、鑒權(quán)與反復(fù)出現(xiàn)的 401前后端分離項(xiàng)目幾乎都會碰到跨域配置。我的處理是在 Django 后端配置跨域白名單把開發(fā)環(huán)境的地址加進(jìn)白名單同時允許攜帶憑證。但真正消耗我時間的是鑒權(quán)問題。前端明明帶著 Token后端卻一直返回 401排查了挺久找到兩個原因。第一個是前端部分請求漏掉了請求頭因?yàn)橹坝袑懰赖倪壿嬆承┙涌谠诜庋b之外單獨(dú)請求攔截器沒有覆蓋到。第二個是 Token 字符串膨脹Refresh Token 有效期設(shè)得長payload 里又塞了一些可有可無的信息Token 太長之后超過了后端默認(rèn)的請求頭大小限制。解決辦法其實(shí)很簡單統(tǒng)一走 axios 攔截器添加請求頭Refresh Token 改放到 HttpOnly Cookie 里既能有效防止腳本讀取又不會擠占請求頭空間。4.2 大圖上傳慢、內(nèi)存溢出的處理第一版上傳功能很粗糙允許用戶直接選擇 20MB 的大圖然后上傳。結(jié)果就是前端頁面卡頓后端磁盤眼看著往下掉。后來做三輪優(yōu)化前端在文件選中之后用 Canvas 做一次縮放超出一定尺寸先壓到不大于 4000 像素寬后端對接收到的圖片做二次校驗(yàn)超大文件直接拒絕所有圖片上傳走對象存儲服務(wù)器不落盤圖片處理這里有個隱蔽的坑用圖像處理庫處理超大圖片時內(nèi)存占用高得驚人。在高并發(fā)下一次處理就可能把服務(wù)器的內(nèi)存打滿。我最后的做法是把圖片處理任務(wù)放進(jìn)后臺任務(wù)隊(duì)列主接口只返回“處理中”狀態(tài)客戶端輪詢獲取處理結(jié)果。這樣既保證用戶體驗(yàn)又保護(hù)了服務(wù)器穩(wěn)定性。4.3 訂單狀態(tài)變更不同步訂單狀態(tài)變更的“實(shí)時可見性”問題我在前期吃了不少苦。畫師上傳完交付圖、把訂單狀態(tài)改成待驗(yàn)收客戶那邊如果不刷新頁面就會一直停留在舊狀態(tài)誤以為畫師沒有動靜。這個問題本質(zhì)上不是狀態(tài)機(jī)錯了而是狀態(tài)變更沒有及時同步給另一方。我的解法是在后端給每個訂單維護(hù)一個狀態(tài)更新時間戳前端輪詢時先對比時間戳發(fā)現(xiàn)變化才重新拉取訂單詳情。同時所有狀態(tài)變更都寫入訂單動態(tài)記錄客戶和畫師都能看到類似“畫師已上傳交付圖”“客戶已驗(yàn)收”這樣的進(jìn)展。這一改動讓雙方溝通順暢了很多也省了兩個人反復(fù)發(fā)消息問進(jìn)度的隱私。4.4 部署上線時的環(huán)境坑部署階段踩的最多的坑可以歸納成一句話“本機(jī)跑得好好的服務(wù)器上卡半天?!蔽遗挪橄聛碇饕ㄎ坏饺齻€點(diǎn)靜態(tài)文件收集命令沒跑Django 的后臺頁面樣式全丟對象存儲的域名沒有加進(jìn) Django 安全配置的白名單圖片加載被攔服務(wù)器上的 Python 依賴版本和本地不一致部分庫的行為悄然變化解決方案其實(shí)也很樸素把環(huán)境做成可復(fù)現(xiàn)的。用依賴鎖文件導(dǎo)出所有依賴再在服務(wù)器上用虛擬環(huán)境統(tǒng)一安裝對應(yīng)版本保證和本地開發(fā)環(huán)境完全一致。這個習(xí)慣在多人協(xié)作或換新服務(wù)器時尤其重要。5. 部署上線與后續(xù)迭代建議5.1 前后端分離部署方案部署方案我選擇了一條輕量路線一臺云服務(wù)器Django 后端用 gunicorn 啟動Vue 前端打包成靜態(tài)文件交給 Nginx 托管Nginx 把以 /api/ 開頭的請求反向代理到后端端口。一條路徑就把前后端串起來沒有引入額外的容器化工具部署和排查都是直來直去對中小型項(xiàng)目來說很省心。如果后續(xù)并發(fā)量上來了可以先把對象存儲掛上內(nèi)容分發(fā)網(wǎng)絡(luò)再用負(fù)載均衡掛多臺后端。但現(xiàn)在剛上線階段過早優(yōu)化反而會拖慢迭代速度。5.2 服務(wù)器環(huán)境配置實(shí)例這里把我部署時的核心三步記錄下來裝好系統(tǒng)里的 Python 虛擬環(huán)境和 Node 環(huán)境把代碼拉下來。后端跑數(shù)據(jù)庫遷移收集靜態(tài)文件用 gunicorn 啟動。前端執(zhí)行構(gòu)建命令把生成的 dist 目錄放到 Nginx 站點(diǎn)目錄寫一份反向代理配置。反向代理配置里最需要注意的是 location 匹配順序。/ 路徑交給前端靜態(tài)文件/api/ 路徑反代給后端其余靜態(tài)資源走 Nginx 本地緩存。配錯的話最常見的現(xiàn)象是前端頁面能打開但接口全部 404而且這種錯誤從日志上不容易一眼看出來。5.3 進(jìn)入維護(hù)期后可以做的小優(yōu)化項(xiàng)目穩(wěn)定上線后我給自己排了三個優(yōu)先級最高的小迭代方向搜索和標(biāo)簽系統(tǒng)完善很多客戶找畫師是帶著關(guān)鍵詞來的比如“賽璐璐”“厚涂”“Q 版”支持多標(biāo)簽組合篩選對成單轉(zhuǎn)化非常有幫助。消息提醒外擴(kuò)除了站內(nèi)輪詢增加郵件或公眾號模板消息通知畫師有新訂單時能第一時間響應(yīng)減少流失。畫師數(shù)據(jù)看板在畫師主頁增加本月接單量、成交率、平均工時這些指標(biāo)既方便畫師管理自己的排期也能讓客戶更信任畫師的職業(yè)能力。這三個功能對技術(shù)的要求都不高但每一件都能實(shí)實(shí)在在提升平臺的專業(yè)感和用戶粘性。這次做完整套項(xiàng)目我的體會是全棧項(xiàng)目想跑通不難真正花時間的是把細(xì)節(jié)縫好。你可能在數(shù)據(jù)表設(shè)計(jì)和狀態(tài)機(jī)上的時間比寫頁面還多但正是這些“看不到”的地方?jīng)Q定了平臺后續(xù)能不能扛住真實(shí)用戶。技術(shù)選型上Python Vue 目前依然是中小型項(xiàng)目里性價比很高的組合Django 和 Flask 各有各的位置關(guān)鍵是先想清楚自己需要多少現(xiàn)成的輪子。如果讓我重新開工我依然不會把功能排得太滿而是先把“瀏覽作品、發(fā)起約稿、支付確認(rèn)、交付驗(yàn)收、評價”這條主線跑通再回頭補(bǔ)邊角。這條路我已經(jīng)替你踩過一遍了順著走下去能少繞的彎我都給你標(biāo)出來了。