實踐與解析)
做這類小程序Spring Boot的文旅宣傳項目我前后接觸過好幾個河湟文化旅游景點宣傳系統(tǒng)算是里面比較典型的一個。河湟地區(qū)的歷史底蘊和民族文化特色非常鮮明景點分散、類型多樣面向的游客群體又偏年輕化用微信小程序做宣傳入口比純網(wǎng)頁或者公眾號H5都要順手得多。這個項目我在實際開發(fā)中完整走過一遍從需求梳理、技術(shù)選型、數(shù)據(jù)庫設(shè)計到前后端聯(lián)調(diào)、部署上線踩了不少坑也沉淀了不少經(jīng)驗。這篇就把整個系統(tǒng)的設(shè)計思路、核心實現(xiàn)和調(diào)試經(jīng)驗一次講透給要做類似文旅小程序的朋友一個可以直接參考的完整方案。1. 項目設(shè)計拆解河湟旅游宣傳為什么選小程序這套組合1.1 景區(qū)宣傳的痛點與小程序的優(yōu)勢河湟地區(qū)不是單一景點而是由多個文化遺址、民俗村落、宗教建筑、自然風(fēng)光組成的集合型旅游區(qū)域。做宣傳系統(tǒng)之前我們和當(dāng)?shù)氐奈穆貌块T做過一輪調(diào)研發(fā)現(xiàn)傳統(tǒng)宣傳方式有幾個繞不開的問題第一信息更新慢。紙質(zhì)手冊、官網(wǎng)專題頁這些形式景點開放時間一變、臨時閉園或者新增體驗項目很難及時同步游客到了現(xiàn)場才發(fā)現(xiàn)跑空。第二展示形式單薄。河湟文化里最有價值的是歷史故事、民族手工藝、建筑細(xì)節(jié)光靠幾張圖和幾段文字根本講不透游客掃碼看個簡介就走了留不下深度印象。第三互動能力幾乎為零。傳統(tǒng)宣傳是單向信息輸出游客沒有渠道收藏感興趣的景點、提交游覽反饋文旅部門也拿不到用戶偏好數(shù)據(jù)后續(xù)運營沒有抓手。微信小程序恰好能補齊這些短板。它的分發(fā)邏輯是即用即走用戶掃個碼、搜個名字就能打開不需要下載安裝十分貼合游客在景區(qū)現(xiàn)場的碎片化使用場景。而且小程序的開放能力很成熟定位、地圖導(dǎo)航、微信支付、訂閱消息都能直接調(diào)用省去了很多自建底層功能的成本。在項目里我們最后確定的技術(shù)路線是前端用原生微信小程序后端用 Java Spring Boot數(shù)據(jù)庫用 MySQL接口通過 RESTful API 對接管理端做成網(wǎng)頁后臺。為什么不用前后端分離的 Vue Spring Boot 或者直接用 uni-app 跨端我們討論過這個問題。原生小程序雖然寫起來啰嗦但勝在穩(wěn)定各種 API 調(diào)用最直接不需要處理多端兼容的額外復(fù)雜度。河湟宣傳系統(tǒng)本身頁面數(shù)量有限核心就是展示、搜索、收藏、留言幾件事原生語法完全夠用沒必要引入跨端框架增加心智負(fù)擔(dān)。管理端因為操作頻率高、信息密度大用網(wǎng)頁實現(xiàn)最合理小程序里只做游客面向的內(nèi)容。1.2 系統(tǒng)功能邊界用戶端能做什么管理端管什么這個系統(tǒng)不是單純的景點信息展示而是圍繞游客游覽前、游覽中、游覽后三個環(huán)節(jié)設(shè)計功能閉環(huán)。游客在小程序端能做的事包括瀏覽輪播推薦位和景點分類入口按文化遺址、民俗體驗、自然風(fēng)光等分類查看景點列表進入景點詳情頁查看圖文介紹、開放時間、門票信息、交通指引和語音講解收藏感興趣的景點形成自己的心愿單在詳情頁底部提交留言或游覽感受在個人中心看到自己的收藏記錄和瀏覽足跡通過搜索框快速查找景點或關(guān)鍵詞。管理端對應(yīng)的是內(nèi)容維護和運營能力管理員登錄后可以維護景點分類和景點信息管理輪播圖發(fā)布文化活動公告處理游客留言查看收藏量、瀏覽量的基礎(chǔ)統(tǒng)計數(shù)據(jù)。這個后臺做得不算特別重但足夠管理人員日常更新內(nèi)容了畢竟宣傳系統(tǒng)的核心是內(nèi)容保鮮后臺好不好用直接影響運營效率。這里有個設(shè)計上的取舍值得說一下我們沒有在小程序里做完整的在線購票和酒店預(yù)訂功能只保留了門票信息的展示和咨詢?nèi)肟凇T蚝芎唵魏愉覅^(qū)域內(nèi)不同的景點分屬不同運營主體票務(wù)系統(tǒng)各自獨立短時間不可能統(tǒng)一接口。強行做購票功能反而會讓系統(tǒng)背上復(fù)雜的訂單和支付邏輯拖慢上線節(jié)奏。宣傳系統(tǒng)的定位就是把游客引過來、看得懂、想去看購票是后續(xù)生態(tài)的事不能一步到位。2. 技術(shù)框架與核心架構(gòu)解析2.1 后端技術(shù)選型與層級設(shè)計后端用 Java Spring Boot版本我們選了 2.7.x 系列。這個版本兼容性強社區(qū)資源多遇到問題在網(wǎng)上基本都能查到解決方案對中小型項目來說是很穩(wěn)妥的選擇。項目結(jié)構(gòu)按經(jīng)典的三層架構(gòu)來組織src/main/java/com/huanghe/travel/ ├── controller/ // 接口層接收請求、參數(shù)校驗 ├── service/ // 業(yè)務(wù)邏輯層處理具體業(yè)務(wù)流程 ├── mapper/ // 數(shù)據(jù)訪問層MyBatis-Plus 接口 ├── entity/ // 數(shù)據(jù)庫實體類 ├── dto/ // 數(shù)據(jù)傳輸對象避免直接暴露實體 ├── config/ // 配置類比如跨域、攔截器、WebMvc ├── common/ // 通用類統(tǒng)一返回結(jié)果、異常處理 └── utils/ // 工具類JWT、日期處理等這種分層的好處是職責(zé)單一controller 只負(fù)責(zé)接收參數(shù)和返回結(jié)果業(yè)務(wù)判斷都收斂在 service 層后續(xù)改需求的時候不會牽一發(fā)動全身。比如景點詳情的瀏覽量加一這個操作在 service 里做就很自然controller 不用關(guān)心。ORM 選擇了 MyBatis-Plus主要看中它的代碼生成和條件構(gòu)造器。這個項目里單表查詢占了絕大多數(shù)MyBatis-Plus 的 LambdaQueryWrapper 寫起來非常簡潔比如按分類查景點列表ListScenicSpot list scenicSpotMapper.selectList( new LambdaQueryWrapperScenicSpot() .eq(ScenicSpot::getCategoryId, categoryId) .eq(ScenicSpot::getStatus, 1) .orderByAsc(ScenicSpot::getSort) );省去了大量手寫 XML 的工作。遇到多表聯(lián)查的情況比如景點詳情要同時帶出圖片和標(biāo)簽我們就在 mapper 里寫自定義 SQL結(jié)合 PageHelper 做分頁性能足夠用。JWT 做身份令牌是當(dāng)前的主流做法。用戶在小程序端授權(quán)登錄后后端生成 token 返回給前端小程序把 token 存在 storage 里后續(xù)請求放到 header 中。服務(wù)端寫一個攔截器統(tǒng)一校驗 token除了登錄和首頁列表這兩個接口放行其余都要求攜帶合法 token。這種無狀態(tài)鑒權(quán)方式非常適合這種輕量級系統(tǒng)不需要在服務(wù)端維護 session也方便后續(xù)做接口擴展。2.2 小程序端架構(gòu)和目錄組織小程序端沒有用任何第三方框架就是微信官方原生的 WXML、WXSS、JS配合小程序原生組件和 API。目錄結(jié)構(gòu)是這樣的miniprogram/ ├── pages/ │ ├── index/ // 首頁輪播分類推薦 │ ├── spotList/ // 景點列表頁 │ ├── spotDetail/ // 景點詳情頁 │ ├── food/ // 美食推薦頁 │ ├── culture/ // 民俗文化活動頁 │ ├── mine/ // 個人中心 │ ├── favorite/ // 我的收藏 │ └── search/ // 搜索頁 ├── components/ // 自定義組件如圖片輪播 ├── utils/ │ ├── request.js // 封裝 wx.request統(tǒng)一處理 token │ └── config.js // 接口地址配置 ├── app.js ├── app.json └── app.wxssutils/request.js這個小封裝很有必要它統(tǒng)一處理了請求頭拼接、token 過期跳轉(zhuǎn)、錯誤碼提示這些邏輯頁面里不用每次重復(fù)寫 wx.request 的那一坨回調(diào)?;A(chǔ)版本大概長這樣const request (url, method GET, data {}) { return new Promise((resolve, reject) { const token wx.getStorageSync(token) wx.request({ url: baseUrl url, method, data, header: { Authorization: token ? Bearer ${token} : }, success: (res) { if (res.statusCode 200) { if (res.data.code 200) resolve(res.data.data) else if (res.data.code 401) { wx.removeStorageSync(token) wx.navigateTo({ url: /pages/login/login }) reject(res.data.msg) } else { wx.showToast({ title: res.data.msg, icon: none }) reject(res.data.msg) } } else { reject(請求失敗) } }, fail: (err) reject(err) }) }) }首頁的輪播圖、分類宮格、推薦景點列表都通過頁面 onLoad 里并行發(fā)請求拉取。這里有個小技巧首頁多個接口同時請求時可以用Promise.all合并等待減少白屏等待時間。不過如果項目數(shù)據(jù)量大了建議還是單獨維護一個聚合接口減少請求往返。2.3 數(shù)據(jù)庫設(shè)計要點與表結(jié)構(gòu)關(guān)系數(shù)據(jù)庫設(shè)計是這個項目里最需要提前想清楚的環(huán)節(jié)。文旅景點宣傳系統(tǒng)的核心實體是景點圍繞景點延伸出分類、圖片、評論、收藏這些關(guān)聯(lián)數(shù)據(jù)。表結(jié)構(gòu)設(shè)計如下去掉了無關(guān)字段t_admin管理員表字段包括 id、username、passwordMD5 加密存儲、real_name、last_login_time。t_user用戶表字段包括 id、openid微信唯一標(biāo)識、nickname、avatar、phone、create_time。openid 是最關(guān)鍵的字段小程序每次登錄都會拿到不同的 code后端用 code 換 openid如果查不到就自動注冊。t_category景點分類表字段包括 id、name、icon、sort、status。對應(yīng)文化遺址、民俗體驗、自然風(fēng)光、美食小吃等分類。t_spot景點表這是整個系統(tǒng)的主表字段相對多id category_id // 關(guān)聯(lián)分類 name // 景點名稱 cover_image // 封面圖 intro // 簡短介紹 content // 富文本詳情 address // 詳細(xì)地址 open_time // 開放時間 ticket_price // 門票價格 traffic_info // 交通指引 audio_url // 語音講解地址 longitude // 經(jīng)度配合地圖定位 latitude // 緯度 status // 上下線狀態(tài) sort // 排序權(quán)重 view_count // 瀏覽量 create_timet_spot_image景點圖集表字段包括 id、spot_id、image_url、sort。一個景點對應(yīng)多張圖片方便詳情頁圖片畫廊展示。t_favorite收藏表字段包括 id、user_id、spot_id、create_time。聯(lián)合唯一索引uk_user_spot防止重復(fù)收藏后續(xù)統(tǒng)計收藏量直接 count 這個表。t_comment評論留言表字段包括 id、spot_id、user_id、content、parent_id支持回復(fù)、create_time、status。t_banner輪播圖表字段包括 id、image_url、link_type、link_param、sort、status。運營人員可以隨時調(diào)整首頁輪播不用改代碼。t_announcement公告表字段包括 id、title、content、publish_time、status。在設(shè)計景點表時我特別加了一個audio_url字段。河湟文化中有大量口述史、民間傳說、民俗儀式光用文字展示不夠生動語音講解能顯著提升游客的沉浸感。這個音頻文件可以存放在云存儲上數(shù)據(jù)庫只存 URL這樣表結(jié)構(gòu)保持簡潔資源擴展也不受限制。另外關(guān)于經(jīng)緯度字段看起來不起眼但實際價值很高。景區(qū)小程序如果能提供地圖上的景點分布和定位導(dǎo)航游客會方便很多。項目里雖然第一版沒做地圖集成但這個字段提前留了后面接地圖 SDK 就不用改表。3. 核心功能與交互細(xì)節(jié)實現(xiàn)3.1 微信登錄與 Token 鑒權(quán)完整流程微信小程序的登錄邏輯是這個系統(tǒng)的入口也是很多新手容易搞混的地方。正確流程是第一步小程序端調(diào)用wx.login獲取臨時 code。這個 code 五分鐘內(nèi)有效且只能使用一次必須立即發(fā)給后端。wx.login({ success: async (res) { const code res.code const loginRes await request(/api/user/login, POST, { code }) wx.setStorageSync(token, loginRes.token) } })第二步后端收到 code 后調(diào)用微信的接口憑證服務(wù)用 code 換取 openid 和 session_key。這里需要 appid 和 appsecret配置在 application.yml 里。第三步根據(jù) openid 查用戶表存在則直接登錄生成 token不存在則自動創(chuàng)建新用戶。這里注意新用戶第一次進入小程序時頭像和昵稱是拿不到的需要后續(xù)引導(dǎo)用戶在小程序內(nèi)手動授權(quán)填寫不能依賴登錄時返回的數(shù)據(jù)。Token 的生成我用的是 JWT把 userId 和 openid 放進 token 里設(shè)置 7 天有效期。游客使用小程序的頻率不會特別高有效期太短會導(dǎo)致頻繁重新登錄影響體驗太長又有安全風(fēng)險7 天是折中值。有一個容易忽略的細(xì)節(jié)接口層統(tǒng)一接收 token 后攔截器里解析出 userId 存入 ThreadLocal后續(xù)業(yè)務(wù)邏輯直接使用。這樣 controller 和 service 不用反復(fù)傳 userId 參數(shù)代碼干凈很多。項目里 ThreadLocal 的清理一定要在 finally 里做否則 Tomcat 線程復(fù)用時會串?dāng)?shù)據(jù)。3.2 景點列表與詳情頁的數(shù)據(jù)聯(lián)動景點列表頁面對的典型場景是游客點了一個分類看到該分類下所有上線狀態(tài)的景點卡片往下滑動可以加載更多點擊卡片跳轉(zhuǎn)到詳情頁。這里有個交互細(xì)節(jié)值得強調(diào)小程序頁面間傳參用 URL 拼接是最簡單的方式但景點 id 這種參數(shù)傳過去了詳情頁再回頭重新發(fā)起請求獲取最新數(shù)據(jù)才能保證數(shù)據(jù)第一時間準(zhǔn)確。有些偷懶的寫法會把景點標(biāo)題和封面圖片直接通過事件傳過去頁面會閃一下又跳回體驗很差而且如果列表數(shù)據(jù)更新了詳情頁拿到的還是舊數(shù)據(jù)。我的建議是列表頁只傳 spotId所有展示數(shù)據(jù)都靠詳情頁自己拉取。景點詳情頁是內(nèi)容展示的核心陣地。為了把河湟文化的內(nèi)容講透詳情頁的布局分了幾個區(qū)塊基本信息區(qū)展示名稱、地址、開放時間和門票價格。圖片畫廊區(qū)用 swiper 組件輪播景點多角度的實拍圖并在圖片底部增加小圓點指示器。介紹內(nèi)容區(qū)展示富文本圖文內(nèi)容這個富文本從小程序端展示有講究服務(wù)端返回的內(nèi)容如果是 HTML需要用rich-text組件解析但rich-text不支持部分標(biāo)簽樣式圖片自適應(yīng)寬度、段間距這些都要靠內(nèi)聯(lián)樣式兜底。音頻導(dǎo)覽區(qū)展示語音講解按鈕點擊播放音頻文件這個功能對體現(xiàn)文化旅游深度幫助極大。交互按鈕區(qū)是收藏和留言入口。收藏按鈕的狀態(tài)要和當(dāng)前用戶是否已收藏數(shù)據(jù)進行聯(lián)動頁面加載時同時拉取景點詳情和收藏狀態(tài)兩個接口。onLoad(options) { const spotId options.id this.getSpotDetail(spotId) this.checkIsFavorite(spotId) }其中checkIsFavorite接口返回布爾值用于給收藏按鈕切換樣式。收藏動作本身做成防抖避免用戶手滑連續(xù)點擊導(dǎo)致重復(fù)請求。3.3 收藏與評論模塊的業(yè)務(wù)邏輯收藏功能雖然簡單但在代碼設(shè)計上有幾個容易出錯的地方第一收藏接口要校驗景點是否存在且處于上線狀態(tài)否則用戶收藏了一個已經(jīng)下架的景點后續(xù)訪問詳情會得到不友好的空頁面。第二收藏接口建議設(shè)計成冪等操作即用戶重復(fù)點擊收藏只插入一次通過唯一索引兜底。第三取消收藏返回的數(shù)據(jù)結(jié)構(gòu)要和收藏時保持一致方便前端統(tǒng)一處理邏輯。評論功能相對復(fù)雜一點。游客可以在詳情頁提交評論也可以對別人的評論進行回復(fù)。數(shù)據(jù)庫設(shè)計上通過parent_id區(qū)分根評論和子評論。評論展示的時候默認(rèn)查根評論并按時間倒序每條根評論下面嵌套查它的子評論。這里有一個性能和體驗的平衡問題如果一次性把某個景點下的所有評論包括子評論全查出來數(shù)據(jù)量大了頁面會卡但如果每條根評論都單獨查一次子評論數(shù)據(jù)庫請求次數(shù)又多。我們這個項目采用兩步方案第一步查根評論分頁第二步根據(jù)這一頁根評論的 id 集合用IN查詢把所有子評論一次性查出來然后在內(nèi)存中組裝成樹形結(jié)構(gòu)。這樣數(shù)據(jù)庫請求次數(shù)控制在兩次代碼復(fù)雜度也可以接受。評論內(nèi)容需要做關(guān)鍵詞過濾嗎嚴(yán)格來說作為開放的游客留言功能還是加一層簡單的敏感詞過濾更好我們在后端維護了一個關(guān)鍵詞列表評論提交時做包含判斷命中的詞替換成星號避免審核壓力。3.4 后臺管理的核心頁面與接口管理端是一個獨立網(wǎng)頁用 Vue Element UI 快速搭建。它和小程序端共用同一套后端接口但接口路徑上做了區(qū)分小程序端用/api/user/開頭管理端用/api/admin/開頭后端有對應(yīng)的攔截器做不同的權(quán)限校驗。后臺最核心的頁面是景點管理頁左側(cè)是景點分類樹右側(cè)是對應(yīng)分類下的景點表格。表格里的操作按鈕包括編輯、上架/下架、刪除。編輯頁面是一個大表單字段比較多分塊組織基礎(chǔ)信息、圖文詳情、圖片上傳、音頻上傳。圖片上傳使用云存儲的直傳方式前端拿到文件后先傳給后端接口換取臨時憑證再直傳到對象存儲避免所有文件都經(jīng)過后端轉(zhuǎn)發(fā)減輕后端帶寬壓力。輪播圖管理頁做了一套簡單的可視化排序通過拖拽調(diào)整輪播順序排序值實時保存到數(shù)據(jù)庫。這個功能看似簡單但運營人員非常依賴因為首頁效果直接影響游客的第一印象。公告管理頁用于發(fā)布文化活動通知、臨時閉園公告等。發(fā)布后小程序首頁的公告欄就會展示最新的三條。這個模塊邏輯不復(fù)雜但要注意公告的時效性后臺列表里要有狀態(tài)標(biāo)識過期的公告自動置為失效不再展示。數(shù)據(jù)統(tǒng)計頁展示了基礎(chǔ)的數(shù)據(jù)看板今日新增用戶、總用戶數(shù)、景點收藏總數(shù)、評論總數(shù)以及每個景點的瀏覽量排行 Top 10。在第一版里只做柱狀圖和表格展示不涉及復(fù)雜的報表分析但已經(jīng)能給運營決策提供有效參考。4. 運行調(diào)試與部署實錄4.1 本地環(huán)境搭建與啟動流程本地運行這套系統(tǒng)需要準(zhǔn)備的基礎(chǔ)環(huán)境是JDK 1.8 或 11、Maven 3.6、MySQL 5.7 及以上版本、微信開發(fā)者工具。整體啟動流程我整理成了一份操作順序照著做基本不會有問題。第一步用前端腳手架生成一個 Spring Boot 項目Java 版本選 1.8依賴引入 Spring Web、MyBatis-Plus、MySQL Driver、Lombok、JWT 相關(guān)包。Spring Boot 2.7 搭配 MyBatis-Plus 3.5.x 是成熟穩(wěn)定的組合。第二步創(chuàng)建數(shù)據(jù)庫hehuang_travel字符集用 utf8mb4因為要存儲表情符號和生僻字河湟地區(qū)的民族詞匯里部分字在 utf8 下會亂碼。執(zhí)行項目里的 SQL 腳本自動建表并插入初始數(shù)據(jù)。第三步改application.yml重點核對數(shù)據(jù)庫賬號密碼、微信小程序配置的 appid 和 secret。server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/hehuang_travel?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver servlet: multipart: max-file-size: 20MB max-request-size: 50MB mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 wx: appid: 你的小程序appid secret: 你的小程序secret第四步啟動后端服務(wù)訪問http://localhost:8080/admin/index確認(rèn)管理端可以正常打開。第五步用微信開發(fā)者工具導(dǎo)入小程序前端項目在utils/config.js里把 baseUrl 改成http://localhost:8080前提是開發(fā)者工具里開啟了不校驗合法域名選項否則本地調(diào)試時請求會被攔截。第六步在小程序端模擬登錄檢查控制臺有無報錯。如果能正常拉取首頁數(shù)據(jù)并完成登錄說明整個鏈路已經(jīng)打通。4.2 常見問題排查與解決方案開發(fā)過程中我踩過的坑不少挑幾個最有代表性的講。第一個是跨域問題。小程序真機調(diào)試時請求的是 HTTPS 正式域名而本地開發(fā)時請求的是 localhost 或局域網(wǎng) IP。Spring Boot 后端必須配置適合小程序的跨域策略否則小程序端會報url not in domain list或者請求被瀏覽器攔截。小程序真機的合法域名校驗一定要在開發(fā)期間就設(shè)置正確或者在開發(fā)者工具里臨時關(guān)閉校驗。后端跨域配置用 WebMvcConfigurerConfiguration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }第二個是圖片加載不出來。小程序端的圖片對域名有要求正式上線必須使用 HTTPS且域名要配置到小程序后臺的 downloadFile 合法域名里。如果是本地開發(fā)圖片 URL 是帶有端口的 http 地址開發(fā)者工具里需要關(guān)閉合法域名校驗才能顯示。我在實際開發(fā)里建議把圖片統(tǒng)一放到對象存儲開發(fā)階段用測試域名上線前換正式域名這樣圖片切換只改一個配置不用動代碼。第三個是 MyBatis-Plus 的分頁失效。這是因為分頁攔截器沒有注冊。MyBatis-Plus 3.4 之后分頁插件需要手動配置Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }如果沒加這個配置Page查詢返回的總數(shù)永遠(yuǎn)是 0列表頁的加載更多功能直接失效。第四個是微信登錄時后端請求微信接口超時。微信接口請求需要用專門的服務(wù)端調(diào)用不能讓它走正常的業(yè)務(wù)超時設(shè)置。我們在項目中單獨維護一個微信 HTTP 客戶端設(shè)了單獨的連接超時和讀超時參數(shù)連不上時給出明確提示避免阻塞整個登錄接口。第五個是富文本內(nèi)容在小程序端顯示格式錯亂。這個問題的根因是rich-text組件對標(biāo)簽支持有限像section、自定義的 class 樣式都會被忽略。我們的解決方案是后端在錄入內(nèi)容時嚴(yán)格控制格式只允許基礎(chǔ)標(biāo)簽段落、圖片、鏈接圖片全部轉(zhuǎn)成 base64 或完整 URL避免相對路徑在移動端打不開。4.3 部署上線的一些經(jīng)驗項目從開發(fā)到上線部署環(huán)境差異帶來的問題往往比開發(fā)階段更隱蔽。我們用的是云服務(wù)器加域名的方式部署微信瀏覽器要求小程序請求的接口域名必須是 HTTPS 且備案過的這一步前置條件不解決好后面所有功能都白搭。部署流程大致是這樣先在服務(wù)器上配置好 Java 環(huán)境和 MySQL 數(shù)據(jù)庫導(dǎo)入正式環(huán)境的 SQL 腳本。然后把項目打成 jar 包用java -jar啟動。為了管理方便推薦使用 Systemd 寫一個服務(wù)文件這樣進程崩潰后能自動重啟日志也有統(tǒng)一的地方看。域名和證書配置方面在 Nginx 里做反向代理把 443 端口的 HTTPS 流量轉(zhuǎn)發(fā)到后端服務(wù)。HTTP 請求全部 301 重定向到 HTTPS同時配置好靜態(tài)資源的緩存策略。小程序端正式環(huán)境的 baseUrl 必須是https://你的域名/注意這個 URL 末尾的斜杠很有講究后端如果做了上下文路徑或者 Nginx 做了路徑轉(zhuǎn)發(fā)斜杠缺失會導(dǎo)致路徑拼接錯誤。數(shù)據(jù)庫上線前要做一次完整的數(shù)據(jù)清理開發(fā)期間的測試數(shù)據(jù)全部清掉并且給關(guān)鍵表加上合適的索引。比如景點表的 category_id 和 status 字段組合索引評論表的 spot_id 和 create_time 索引。不加索引的情況下數(shù)據(jù)量一上來詳情頁的評論加載就會明顯變慢。另外備份策略不能少。我習(xí)慣每天凌晨用 crontab 自動備份數(shù)據(jù)庫保留最近 7 天的備份文件。小程序系統(tǒng)的內(nèi)容維護頻率高萬一運營誤刪數(shù)據(jù)或者代碼升級出了兼容性問題備份能救命。5. 實際操作中的一些體會這套系統(tǒng)從立項到交付我最深的體感是文旅宣傳系統(tǒng)看起來功能簡單但內(nèi)容運營的分量遠(yuǎn)比功能開發(fā)重。景點、文化活動、民俗故事這些信息是持續(xù)變化的系統(tǒng)把展示位、管理流程、審核機制都搭好了才有后面的內(nèi)容保鮮。誰也不想看到一個宣傳景區(qū)的小程序里最新一條公告還是半年前的。開發(fā)過程中有一個環(huán)節(jié)值得反復(fù)確認(rèn)就是小程序端的審核細(xì)節(jié)。微信小程序的類目選擇和信息錄入要提前查清楚文旅宣傳類有沒有額外的資質(zhì)要求不同類目對應(yīng)不同的審核規(guī)則如果等開發(fā)完再提交審核發(fā)現(xiàn)類目不對改起來會非常被動。再說一個容易被低估的功能點搜索。景區(qū)宣傳系統(tǒng)的搜索不能只做景點名稱精確匹配用戶往往是記不全名字才來搜的。比如用戶搜青稞他可能想找的是青稞酒坊或青稞文化體驗館搜古堡可能對應(yīng)的是好幾個景點。在搜索接口里加一層關(guān)鍵詞模糊匹配加同義詞聯(lián)想用小表存搜索詞庫效果提升很明顯。第一版別急著上 ES數(shù)據(jù)庫 LIKE 加詞庫擴展中小規(guī)模完全夠用。最后再分享一個小技巧景點詳情的瀏覽量統(tǒng)計不要每次都去更新數(shù)據(jù)庫的 count 字段這樣高并發(fā)下會有鎖競爭??梢栽?Redis 里維護一個鍵值對每次訪問詳情就INCR一次定時任務(wù)每十分鐘把累計值同步回 MySQL。項目規(guī)模小不用做得特別重但這樣順手的設(shè)計能避免以后流量上來后的隱患。這個項目做完之后我最大的收獲不是技術(shù)上的突破而是理解了技術(shù)要為內(nèi)容服務(wù)這句話的含義。小程序只是一種媒介形式真正吸引游客的是里面呈現(xiàn)的文化深度和游覽體驗。后面如果繼續(xù)迭代我傾向在語音講解的互動性上做文章比如加入地圖導(dǎo)覽點位的自動觸發(fā)或者根據(jù)游客當(dāng)前位置推薦附近的景點和特色美食。遇到同樣在做文旅宣傳項目的朋友歡迎一起交流實際經(jīng)驗這套系統(tǒng)里可以擴展的玩法還有不少。