設計與落地實踐)
weixin117新聞資訊系統(tǒng)設計做微信生態(tài)相關的開發(fā)久了你會發(fā)現(xiàn)“資訊類”項目永遠有需求但真正能跑起來的卻不多。weixin117這個項目就是一個典型的微信生態(tài)新聞資訊系統(tǒng)設計目標是在微信小程序和公眾號H5里搭建一套完整的資訊閱讀與運營管理平臺。我把它拆開來看涉及的東西其實不少前端交互、后端接口、數(shù)據(jù)庫建模、內容審核、消息推送甚至還有后續(xù)的數(shù)據(jù)分析。這篇文章就圍繞weixin117的整體設計來聊從需求拆解到表結構從接口設計到上線避坑把一套可落地的方案完整講透適合正在做微信端資訊產品、或者準備接類似外包項目的同學參考。可能有人會問新聞資訊系統(tǒng)聽起來很簡單不就是“列表詳情頁”嗎真做起來完全不是這么回事。資訊系統(tǒng)的難點不在展示而在內容的生產、審核、分發(fā)、互動這一整條鏈路怎么設計得順暢以及微信生態(tài)里那些特有的規(guī)則和限制怎么去適配。weixin117這個項目的出發(fā)點就是把這條鏈路打通做成一個可以直接復用的模板而不是又臭又長的一次性代碼。1. 系統(tǒng)定位與整體設計思路1.1 項目背景與命名由來先說說“weixin117”這個名字。我最早接這個項目時需求方給的代號是“wx-news”后來內部為了方便區(qū)分不同版本就按迭代序號命名117正好是第117個版本迭代計劃。很多團隊會把這種資訊類項目統(tǒng)一叫“weixin編號”一是方便歸檔二是因為整個系統(tǒng)構建在微信生態(tài)之上前端形態(tài)以微信小程序為主配合公眾號H5作為輔助入口。名字本身不復雜但背后反映的研發(fā)習慣值得借鑒資訊類項目迭代頻繁版本號管理一定要從一開始就規(guī)范起來?;氐叫枨蟊旧磉@個系統(tǒng)要解決的核心問題有三點第一內容運營人員需要一個高效的后臺去發(fā)布和排版資訊第二普通用戶需要一個體驗順暢的小程序去閱讀、評論和分享第三管理者需要看到閱讀量、用戶增長、互動數(shù)據(jù)這些運營指標。這三層需求對應到系統(tǒng)架構上就是管理端、用戶端、數(shù)據(jù)統(tǒng)計三大部分。1.2 核心功能模塊拆解weixin117的功能清單如果起草大概會分成這樣幾個模塊資訊內容模塊資訊的發(fā)布、編輯、圖文混排、定時上架、下線歸檔。分類與標簽模塊支持多級分類資訊可以打多個標簽用于信息流和搜索篩選。用戶模塊微信登錄授權、用戶信息管理、歷史閱讀記錄、收藏與點贊。評論互動模塊富文本評論、回復、點贊、評論審核。推送觸達模塊微信訂閱消息、模板消息通知用戶有新資訊或評論回復。數(shù)據(jù)統(tǒng)計模塊閱讀量、分享量、用戶留存、資訊熱度排行。這套功能清單看起來中規(guī)中矩但每個模塊都有一些容易忽視的細節(jié)。比如分類和標簽很多系統(tǒng)做著做著就混亂了原因就是沒在數(shù)據(jù)庫設計時把層級關系定清楚。再比如評論審核直接展示的評論如果不做過濾后續(xù)會有大量內容安全方面的麻煩。這些細節(jié)在后面實操部分我會逐一展開。1.3 技術選型與架構決策技術選型上weixin117采用的是典型的“前后端分離輕量部署”方案。用戶端選擇微信小程序原生框架加TypeScript管理后臺選擇Vue 3加Element Plus后端服務使用Spring Boot數(shù)據(jù)庫用MySQL 8.0緩存用Redis對象存儲接阿里云OSS域名和HTTPS證書走騰訊云。這套組合不算新潮但勝在穩(wěn)定團隊招人容易出了問題網上資料也多。有人可能會問為什么用戶端不直接做H5或者React Native原因很簡單微信小程序在微信里的用戶體驗最順暢不需要擔心瀏覽器兼容性分享到聊天和朋友圈的卡片效果也好。而且資訊類C端產品用戶主要是通過微信掃一掃或好友分享進入小程序是最短路徑。H5可以作為補充入口保留但核心功能必須優(yōu)先保證小程序端。架構上我特意分成四層接入層、應用層、服務層、數(shù)據(jù)層。接入層負責小程序端的請求接入和登錄態(tài)校驗應用層承載各個業(yè)務模塊的接口邏輯服務層把通用能力抽出來比如文件上傳、消息推送、內容安全檢測數(shù)據(jù)層做MySQL讀寫分離和Redis緩存。分層的好處是后期加新功能不用反復改別人的代碼每個模塊的責任邊界清晰出了問題也容易定位。2. 數(shù)據(jù)庫設計與后端接口規(guī)劃2.1 核心數(shù)據(jù)表結構設計數(shù)據(jù)表設計是資訊系統(tǒng)的地基這一塊如果設計不當后期改起來會非常痛苦。weixin117的數(shù)據(jù)庫主要包含這些核心表資訊表、分類表、標簽表、用戶表、評論表、收藏表、點贊表、閱讀記錄表、推送記錄表、操作日志表。以資訊表為例關鍵字段我列一下CREATE TABLE news_article ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 資訊ID, title VARCHAR(200) NOT NULL COMMENT 資訊標題, summary VARCHAR(500) DEFAULT NULL COMMENT 摘要, cover_image VARCHAR(500) DEFAULT NULL COMMENT 封面圖URL, content LONGTEXT NOT NULL COMMENT 資訊正文HTML, category_id BIGINT NOT NULL COMMENT 所屬分類ID, status TINYINT NOT NULL DEFAULT 0 COMMENT 狀態(tài):0草稿,1待審核,2已發(fā)布,3已下線, author_id BIGINT DEFAULT NULL COMMENT 作者用戶ID, view_count INT DEFAULT 0 COMMENT 瀏覽次數(shù), like_count INT DEFAULT 0 COMMENT 點贊次數(shù), publish_time DATETIME DEFAULT NULL COMMENT 發(fā)布時間, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 創(chuàng)建時間, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新時間, KEY idx_category_status (category_id, status), KEY idx_publish_time (publish_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT資訊表;這里有幾個設計要點。第一是status字段狀態(tài)流轉一定要用數(shù)字而不是字符串這樣查詢和索引效率都高。第二是publish_time單獨拎出來和create_time區(qū)分開因為“創(chuàng)建”和“發(fā)布”往往是兩個時間點運營可能會創(chuàng)建草稿后幾天才發(fā)布。第三是聯(lián)合索引idx_category_status這是查詢列表頁最常用的條件組合“某個分類下已發(fā)布的內容”建了聯(lián)合索引之后查詢速度會快很多。分類表的設計是另一個容易踩坑的地方。我采用了鄰接表模型就是傳統(tǒng)的parent_id方式支持無限層級CREATE TABLE news_category ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 分類ID, name VARCHAR(50) NOT NULL COMMENT 分類名稱, parent_id BIGINT DEFAULT 0 COMMENT 父分類ID,0表示頂級, sort_order INT DEFAULT 0 COMMENT 排序權重, status TINYINT NOT NULL DEFAULT 1 COMMENT 狀態(tài):1啟用,0停用, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT資訊分類表;很多新人喜歡把分類做成固定層級加個level字段存1級、2級、3級這種做法看著直觀但一旦需要調整層級就非常麻煩。鄰接表配合遞歸查詢雖然在數(shù)據(jù)量大時性能有壓力但對于資訊類系統(tǒng)完全夠用而且實現(xiàn)簡單維護成本幾乎為零。評論表的設計容易被忽略的是關聯(lián)字段和狀態(tài)字段CREATE TABLE news_comment ( id BIGINT PRIMARY KEY AUTO_INCREMENT, article_id BIGINT NOT NULL COMMENT 資訊ID, user_id BIGINT NOT NULL COMMENT 評論用戶ID, parent_id BIGINT DEFAULT 0 COMMENT 父評論ID,0為頂級評論, reply_to_user_id BIGINT DEFAULT NULL COMMENT 被回復的用戶ID, content VARCHAR(1000) NOT NULL COMMENT 評論內容, status TINYINT NOT NULL DEFAULT 0 COMMENT 狀態(tài):0待審核,1正常,2屏蔽, like_count INT DEFAULT 0, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_article_status (article_id, status), KEY idx_parent (parent_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT評論表;parent_id用來支持樓中樓式回復reply_to_user_id用來記錄被回復人這樣前端展示時可以直接顯示“回復 某某”。status字段默認待審核小程序的用戶評論先進入待審狀態(tài)后臺審核通過后才會在前端展示。2.2 后端接口設計要點后端接口設計遵循RESTful風格但也不死板業(yè)務優(yōu)先。weixin117的核心接口大致有這些接口路徑方法說明/api/news/listGET資訊列表支持分類、關鍵字、分頁參數(shù)/api/news/{id}GET資訊詳情返回正文和基礎信息/api/news/{id}/commentsGET評論列表分頁返回/api/news/{id}/likePOST點贊或取消點贊/api/news/{id}/collectPOST收藏或取消收藏/api/comment/addPOST提交評論/api/user/profileGET獲取用戶信息和收藏列表/api/user/historyGET獲取閱讀歷史列表接口我習慣用PageResult統(tǒng)一封裝返回結構包含total、pageNum、pageSize、list四項。前端拿到這四項直接渲染分頁組件不用再做特殊適配。詳情接口要注意一點文章正文是富文本HTML內容可能包含圖片、視頻、代碼塊接口返回時要保證content字段的長度和格式校驗避免前端解析出錯。登錄相關的接口我用的是微信小程序典型的code換token方案。前端調用wx.login拿到code傳給后端后端拿著code調微信接口獲取openid再生成自定義token返回給前端。這個token用JWT實現(xiàn)過期時間設成7天用戶每次請求都帶上后端通過攔截器校驗。用戶第一次進來自動注冊第二次進來直接登錄整個流程對用戶完全無感。攔截器里要做一層用戶態(tài)的校驗但要注意區(qū)分“必須登錄”和“可以游客訪問”的接口。資訊列表和詳情是游客可看的這樣用戶分享給好友后好友不需要登錄就能打開閱讀體驗會好很多。點贊、收藏、評論這些寫操作則必須登錄。2.3 微信端集成與登錄狀態(tài)管理微信小程序的集成是整個項目的重頭戲。首先是登錄授權我的方案是“靜默登錄優(yōu)先主動授權兜底”。用戶打開小程序先走wx.login靜默登錄拿到用戶身份然后直接展示資訊內容。只有當用戶要評論、點贊或收藏時才彈出授權框引導用戶完善頭像和昵稱信息。現(xiàn)在的微信生態(tài)對用戶隱私保護要求嚴格那種一進來就彈窗要授權的做法用戶流失率極高能不做就不做。用戶信息存儲也要注意脫敏。手機號、微信昵稱、頭像這些屬于敏感信息數(shù)據(jù)庫里存的是加密字段日志和接口返回里絕對不能出現(xiàn)完整手機號。頭像和昵稱建議直接存微信返回的URL和字符串不要額外上傳到自己的OSS省流量也省存儲。再就是消息推送。微信早就把模板消息改成了訂閱消息機制用戶需要主動訂閱而且一次性訂閱只能推送一次。weixin117在評論回復通知里用的就是訂閱消息用戶在發(fā)表評論后彈窗讓用戶授權訂閱“回復通知”等別人回復了他就通過微信服務端接口推送一條訂閱消息。注意一個限制一次性訂閱消息只能發(fā)一次如果用戶下次還想接收通知需要再次訂閱。這個交互細節(jié)要在產品設計階段就明確否則運營會以為消息推不出去是程序出bug了。3. 管理后臺與內容生產流程設計3.1 資訊發(fā)布與多級審核機制管理后臺是運營每天待得最久的地方體驗好不好直接影響效率。weixin117的管理后臺我設計了以下幾個頁面工作臺數(shù)據(jù)概覽、資訊管理、分類管理、評論管理、用戶管理、推送管理、系統(tǒng)設置。資訊發(fā)布是整個后臺的核心。編輯頁采用富文本編輯器我選了wangEditor這個開源組件它有圖片上傳、視頻嵌入、代碼塊、表格等常用功能并且支持自定義上傳邏輯可以對接OSS。資訊發(fā)布流程上是運營填寫標題、封面、摘要、正文、分類、標簽點擊保存草稿草稿確認無誤后提交審核審核員查看內容通過后系統(tǒng)根據(jù)publish_time定時發(fā)布如果publish_time為空則立即發(fā)布發(fā)布后運營可以在列表頁選擇下線下線后前端不再展示。多級審核機制我覺得在團隊規(guī)模稍大時非常有必要。一級審核通過后進入待發(fā)布狀態(tài)二級審核主要針對重大內容或指定分類。如果你們是外包項目也可以把審核流程簡化成單級審核只要在系統(tǒng)配置里做一個開關就行不要讓審核流程寫死在代碼里。3.2 二維碼生成與分享鏈路資訊系統(tǒng)的傳播主要靠分享。在小程序中分享主要通過button組件的open-typeshare實現(xiàn)分享出去的卡片包括標題、封面圖和路徑。這里有個細節(jié)分享圖的比例要適配微信要求推薦是5:4圖片大小不要超過128KB否則分享卡生成會失敗。如果封面圖太大最好后端壓縮后再返回給前端。除了小程序內分享還有一張二維碼的傳播鏈路。weixin117在后臺為每篇資訊生成了帶參數(shù)的小程序碼用戶用微信掃碼后直接進入對應資訊詳情頁。生成小程序碼用的是微信的getwxacodeunlimit接口這個接口支持scene參數(shù)可以把資訊ID編進去比如scenearticle_1001。需要注意scene參數(shù)限制為32個可見字符只支持數(shù)字、大小寫英文以及部分特殊字符不能放URL。另外這個接口每天調用上限是100萬次對資訊類項目完全夠用但調接口時要做好緩存同一篇資訊的碼只用生成一次存到OSS上。分享鏈路的數(shù)據(jù)追蹤也要提前設計好。分享來源我用scene參數(shù)里的channel字段區(qū)分比如來自朋友圈、群聊、公眾號菜單。后端在資訊詳情接口里記錄下用戶的來源渠道后面做數(shù)據(jù)分析時就能知道哪種傳播路徑效果最好。一開始沒埋點的話后面想追數(shù)據(jù)就追不回來了。3.3 定時任務與推送觸達設計定時任務在資訊系統(tǒng)里有兩個典型場景定時發(fā)布資訊和定時推送運營通知。定時發(fā)布我在Spring Boot里用Scheduled注解實現(xiàn)每分鐘掃描一次待發(fā)布狀態(tài)的資訊如果當前時間大于等于publish_time就把狀態(tài)改成已發(fā)布。這個方案的優(yōu)點是簡單可靠缺點是無法精確到秒級但對資訊發(fā)布來說分鐘級精度足夠。定時推送運營通知要復雜一些因為涉及微信訂閱消息的發(fā)送。我的方案是把推送任務抽象成一張推送記錄表包含推送用戶、推送內容、推送狀態(tài)、發(fā)送時間這些字段。運營在后臺創(chuàng)建推送任務時可以先選擇一個用戶分組或者全部用戶系統(tǒng)通過異步任務逐條發(fā)送發(fā)送成功或失敗都在記錄表里打標。注意微信訂閱消息接口有頻率限制單個用戶在短時間內不能收到過多推送所以推送前要做去重和頻控防止把用戶打擾跑了。4. 實操中的高頻問題與排錯實錄4.1 接口響應慢與緩存策略我做過的資訊類系統(tǒng)第一個容易出問題的就是列表接口響應慢。原因基本是數(shù)據(jù)庫每次都要全表掃描再加ORDER BY排序。weixin117上線初期資訊總數(shù)還不到一萬條列表接口響應就超過800毫秒這個體驗完全不合格。排查后發(fā)現(xiàn)SQL走了全表。后來做了兩步優(yōu)化第一步是加索引把WHERE條件里的category_id、status、publish_time全部組合進索引里第二步是加Redis緩存列表接口的響應結果緩存5分鐘緩存key按“分類ID頁碼”拼接。緩存命中后接口響應時間直接降到50毫秒以內數(shù)據(jù)庫壓力也小了很多。緩存更新策略我采用Cache Aside模式讀請求先查緩存查不到再查數(shù)據(jù)庫然后把結果寫入緩存寫請求先更新數(shù)據(jù)庫再刪除緩存。這里有個很經典的坑不能先刪緩存再更新數(shù)據(jù)庫否則在并發(fā)場景下會出現(xiàn)舊數(shù)據(jù)回填緩存的問題。正確順序永遠是先更新庫、后刪緩存。4.2 微信登錄態(tài)過期與token刷新小程序登錄態(tài)過期也是高頻問題。wx.login拿到的code一次性使用后端換取的openid長期不變但我自己簽發(fā)的JWT token有7天過期時間。用戶正常打開小程序后7天內不用重復登錄但超過7天再打開接口就會返回401未登錄。解決思路是靜默刷新token。每次接口校驗發(fā)現(xiàn)token過期時不直接返回錯誤而是通過一個特殊標記讓前端主動調用wx.login重新授權后端再簽發(fā)新的token。這樣用戶感知不到登錄過期體驗非常平滑。要注意刷新token的操作要加并發(fā)鎖防止前端多個請求同時觸發(fā)刷新導致token被重復簽發(fā)。4.3 內容安全與合規(guī)檢查資訊系統(tǒng)的內容安全是必須認真對待的環(huán)節(jié)。我做了三道防線第一道是用戶輸入側的過濾評論和昵稱等用戶生成內容在寫入數(shù)據(jù)庫前調用內容安全檢測接口攔截明顯違規(guī)的文字第二道是資訊審核流程運營發(fā)布前人工審核審核時也會提示命中風險的內容第三道是定期抽檢定時任務掃描歷史內容防止漏網之魚在后續(xù)時間被判定為風險內容。技術實現(xiàn)上文本檢測可以用微信自帶的內容安全接口也可以接入其他文本審核服務關鍵是接口超時和失敗要有兜底邏輯。我的做法是調用失敗時“先放行、進審核隊列”讓運營人工復核避免因為接口抖動導致正常用戶評論發(fā)不出去。圖片檢測也一樣。用戶上傳頭像、資訊封面上傳時除了校驗文件類型和大小還要做圖片違規(guī)檢測防止不合規(guī)圖片進到公網展示。這塊很多人會忽視等收到通知才來補代價就大了。圖片壓縮和格式轉換可以交給OSS的圖片處理服務在URL上帶參數(shù)就行不需要后端寫任何圖像處理代碼。4.4 部署上線與性能調優(yōu)筆記部署方案我是這樣做的后端服務打成jar包跑在一臺2核4G的云服務器上MySQL和Redis先部署在同一臺機器上等用戶量上來再拆分小程序靜態(tài)資源走微信的CDN圖片和視頻走OSS的CDN管理后臺構建后部署到Nginx的靜態(tài)目錄通過反向代理轉發(fā)API請求。性能調優(yōu)方面上線后的第一次壓測暴露出不少問題。用兩三百個并發(fā)用戶去請求資訊列表接口后端Tomcat默認線程池很快被打滿。調整參數(shù)如下server.tomcat.threads.max從默認200調到400連接數(shù)accept-count調到1000數(shù)據(jù)庫連接池HikariCP的maximum-pool-size從默認10調到30。單機扛個幾千QPS的讀請求沒問題寫請求再單獨做限流。JVM參數(shù)也不能忽視。啟動命令里加上-Xms512m -Xmx1024m參數(shù)避免堆內存頻繁擴容和回收。線上觀察一段時間后把新生代和老年代的比例調成了1:2主要考慮到資訊系統(tǒng)的特點是“讀多寫少”老年代太容易堆積長生命周期對象。這里不同業(yè)務特征需要不同調優(yōu)方向不能照抄別人的參數(shù)最好用JDK自帶的JVisualVM觀察一下再動手。5. 從設計到落地的擴展思考weixin117這套系統(tǒng)上線穩(wěn)定運行了一段時間后我復盤了一下覺得有幾個方向是可以繼續(xù)深化和擴展的。個性化推薦是目前最值得加的功能。資訊列表如果一直是運營手動排序用戶刷幾屏就會失去興趣。最簡單的方案是接一個基于標簽的協(xié)同過濾推薦給用戶分群把相同標簽下的高頻資訊優(yōu)先推給相似行為的用戶。不一定要上深度學習模型用簡單的Apriori關聯(lián)規(guī)則或者余弦相似度算法就能把推薦體驗提升不少。直播和短視頻也可以加進來?,F(xiàn)在純圖文資訊的停留時長越來越短如果在資訊詳情里嵌入直播預約入口或者把重點資訊做成短視頻配套展示整個產品的天花板會高很多。技術上主要還是對接微信的直播組件后端在資訊表里增加直播ID和視頻ID字段即可不需要重構現(xiàn)有架構。面向創(chuàng)作者開放也是一個方向。目前的資訊都是內部運營發(fā)布如果未來想讓外部用戶投稿需要額外開發(fā)投稿入口和稿費結算體系。這個改動涉及用戶角色、權限、結算流水表工作量不小但能極大豐富內容供給。建議在設計初期就把用戶表里的role字段預留出來避免后面加字段時的遷移麻煩。再有一點多端復用的問題。小程序端做好之后如果還要做App端或者PC端后端接口基本可以復用但前端的資訊富文本渲染在App里要用web-view或者原生組件重寫這里容易踩坑的是圖片適配和字體大小。提前把富文本內容清洗干凈統(tǒng)一圖片樣式多端適配會省很多事。我個人的體會是資訊類系統(tǒng)的設計和開發(fā)最能拉開差距的不是技術棧多新而是對業(yè)務鏈路的理解深度。從內容生產、審核、分發(fā)、觸達到數(shù)據(jù)回流每一環(huán)都要提前想清楚。很多項目做到一半推倒重來不是因為代碼寫得差而是最初的流程設計就埋了雷。weixin117這套設計也許不算驚艷但它把每個環(huán)節(jié)都落在實處該建索引的地方建索引該做緩存的地方做緩存該留擴展點的地方留擴展點——這些樸素的堅持才是系統(tǒng)能長期穩(wěn)定運行的真正原因。最后再分享一個小技巧資訊系統(tǒng)上線前一定要準備一套完整的數(shù)據(jù)初始化腳本包含分類數(shù)據(jù)、測試賬號、示例資訊這樣才能在聯(lián)調和演示時節(jié)省大量時間。別小看這個準備工作它能讓你從“開發(fā)完”到“能演示”的流程從半天縮短到十分鐘。