基于多平臺應用的音樂共享社區
1 緒論
1.1 課題依據及意義
隨著互聯網娛樂項目的日益增多,內容也日漸豐富,加之網絡便利性的增強,越來越多的用戶喜歡在網上聽音樂。但是各平臺音樂資源殘次不齊,也包含了許多假無損音樂,無法滿足發燒友們的需求,建立以用戶為本的社交型音樂分享平臺則可以彌補這一缺陷,這就是本平臺搭建的初衷。發燒友用戶們非常需要一個跨平臺的,并且擁有智能推薦、各端體驗一致的音樂分享平臺 0。
現有很多跨平臺應用,都存在用戶體驗不一致、性能低下、代碼不能多次復用等問題,例如 ReactNative、Uni-App 等實現方案,他們都是基于 JavaScript 語言實現的跨平臺開發框架,多少都需要與原生 API 進行通信,無法實現一處編寫處處運行。而由 Google 牽頭的 Flutter 采用獨立的 Dart 語言進行開發,Flutter 從底層實現了一套自己的框架,同時與原生代碼高度契合,全部使用 Widget 進行開發,開發者只需開發 Widget 即可,完成了接近原生的操作體驗。
1.2 國內外研究現狀
移動端跨平臺開發從最初的 Hybrid 到 React-Native,再到最近 Google 新推出的 Flutter 移動 UI 框架,體驗和性能越來越接近原生應用[2]。
1.2.1 國外研究現狀
(1)Hybrid
以往最早的以 Cordova 為代表的 Hybrid 開發[3],主要依賴于 WebView。但是 WebView 是一個很重的控件,容易造成內存泄露,而且有些復雜的用戶界面會降低 WebView 的顯示性能。這是因為 JavaScript 與原生 Native 代碼之間需要使用 JSBridge 進行通信,同時還要進行上下文切換,所以性能會因此降低。
(2)ReactNative
ReactNative 技術拋開了 WebView,利用 JavaScriptCore 來做橋接[4],將 JS 調用轉為 native 調用,在跨平臺開發中只犧牲少量性能,這是一個巨大的進步。但因為仍然存在從 JS 代碼轉換為本地代碼的過程,所以如果界面 UI 操作頻繁,則可能導致性能問題。
(3)Flutter
Flutter 對跨平臺的實現采用更全面的方案[5]。與采用 WebView 或 JavaScriptCore 不同,它不采用 JavaScriptFramework,而是實現了自己的 UI 框架,然后在系統的底層繪制系統的 UI 所以他采用的開發語言不是 JS,而是 Dart。據稱 Dart 語言可以編成原生代碼。
(4)總結
Hybrid 基于 WebView 容器內部運行的 HTML 和 JS,通過 Cordova 提供的接口與硬件進行通信;因此,它的路徑天生就是有限的,并且受不同廠商對 Webkit 內核支持的影響。
由于將 View 編譯成本地 View,ReactNative 的效率大大高于基于 Cordova 的 HTML5,但它仍然存在效率問題。ReactNative 的渲染機制基于前端框架的考慮,而復雜的 UI 渲染則需要依賴于多種視圖疊加。例如,我們呈現了一個復雜的 ListView,每個小控件都是一個靜態視圖,然后它們互相疊加。考慮一下現在如果我們的 list 需要滑動刷新,那么需要渲染的對象數量是多少,所以它的列表模式并不友好。
Flutter 吸取了前兩種方法的教訓,并在繪制技術上選擇了自己的實現(GDI),由于更易控制,使用了新的語言 Dart,從而避免了 ReactNative 通過橋接器與 JavaScript 進行通信帶來的效率低下問題,因此在性能方面要優于 ReactNative[6]。在安卓手機開發者選項中打開顯示界面,你會發現 Flutter 的布局是一個整體,表明 Flutter 的渲染沒有使用本地控件。
1.2.2 國內研究現狀
(1)uni-app
這個框架使用 Vue.js 來開發所有前端應用,開發者編寫了一套可以發布到 iOS,Android,H5 的代碼,以及各種小程序的代碼 7,一些平臺,如快應用。
(2)總結
uni-app 遵循的是 Vue.js 的語法規范,基于模板和數據綁定,更加現代化的開發方法,比純 H5+ 的代碼更少,開發效率更高。
但是 uni-app 不支持 DOM,因為 DOM 受到限制,采用 Vue 模式可以獲得更高的性能體驗。大量的 Web 庫不可用,基于 DOM 的庫也不能直接使用。
國內還有其他類似的跨平臺框架,但大多為了適配小程序,原生開發效率較低。
1.3 論文的主要工作
本題旨在實現一個開放的跨平臺分享系統,音樂則是主要分享對象,所有音樂數據均由用戶自行上傳,同時還可以發送簡單的動態來進行音樂交流,為此初衷為目的制作一個多平臺的音樂分享 APP。前端部分采用由 Google 自主開發的基于 Dart 語言的跨平臺應用 SDKFlutter,同時兼容 iOS 和 Android 兩端。后端服務器采用基于 JavaEE 的 SpringBoot 框架搭建,ORM 層使用 SpringDataJPA,數據庫使用 MySQL,使用 Shiro 作為權限管理,并且打包為 Docker 鏡像方便部署和升級。
1.4 論文的組織結構
本文主要基于多平臺應用的音樂共享社區 APP 進行簡要敘述,文章分為七章節。具體如下:
第一章,緒論。本章節針對本課題在國內外的發展現狀、研究目的和意義進行了討論。
第二章,系統分析。本章節重點分析了共享平臺系統的可行性、需求,并對方案選擇進行了概括和分享。
第三章,系統的設計。本章節主要分析了系統框架的設計、數據庫的設計,重點講解了系統的主要流程。
第四章,系統的實現。本章節主要展示了實現系統初衷設計的核心代碼,重點分析了部分模塊的算法。
第五章,系統運行與效果分析。本章節主要對系統的 UI 外觀進行了展示,并針對各項功能一一對應解釋。
第六章,系統測試。本章節主要使用實例的方式重點解說了測試方案、測試方法等事項。
第七章,總結與展望。本章節主要列示了本系統的不足點,提出了差異性優化方案,并對項目的可拓展性和發展方向給出了展望。
1.5 本章小結
本章節一是對本課題研究意義和目的進行了展示,二是對當今多平臺應用框架在國內外的發展進行了描述,三是對論文的整體架構予以總結,重點內容加以闡述。
2 系統分析
基于多平臺應用的音樂共享社區前端部分主要基于 Flutter 框架進行開發,并實現近似于原生效率的跨平臺應用。同時后端的推薦系統會對登錄用戶的歷史聽歌記錄及收藏的歌曲與歌單進行分析,推薦用戶可能喜歡的歌曲。如樣本數據過少則會推薦當日熱門歌曲。
2.1 可行性分析
本章節對多平臺應用的音樂共享社區的可行性進行了分析和總結,并就其原因進行了詳盡闡述。
2.1.1 技術可行性
本系統采用了全棧式的開發方式。系統分為前端的 App 以及后端的服務端。前端 App 使用 Google 開發的跨平臺框架 Flutter 進行搭建,兼容 Android 和 iOS 平臺,并在未來有望兼容桌面端。服務器使 SpringBoot+SpringDataJPA+Shiro 權限管理框架搭建,通過 Thymeleaf 模板動態生成后臺管理面版,并且系統會定時進行推薦。
此系統使用 AndroidStudio 和 IntelliJIDEA 開發工具在 MacOS 下實施開發,使用 AndroidSimulator 和 iOSsimulator 開展調試,Android 端采用一加 8pro 手、iOS 采用 iPhone11 機型真機調試。通過精密分析,本系統應用結構清楚,使用架框成熟,滿足開發環境的要求,故此在技術上可行。
2.1.2 經濟可行性
本系統是封閉式資源分享系統,使用開源框架作為技術框架,開發軟件均采用 EDU 授權版本,無額外付費軟件,同時對硬件環境要求不高,系統開發周期適中,系統整體的開發成本較低,系統穩定性良好且易于維護管理,由于采用了 SpringBoot 進行開發,使項目拓展性大大提高。從經濟效益方面講,本系統僅針對注冊用戶提供音樂分享,作為交流平臺,不存在版權問題。同時,系統還預留了交易接口,今后有望推出付費交流服務,系統可以靠手續費進行營利。綜上所述,系統的經濟效益大于成本,可獲創利潤可觀,故此在經濟上可行。
2.1.3 對環境及可持續性發展的影響
本系統采用前后端分離的方法進行開發,使用 SpringBoot 框架來管理 JavaBean 的生命周期,相對與普通的 MVC 框架比減少了對內存的開支。同時在推薦系統上采用了高效的推薦算法,無需進行大量復雜的運算即可實現可觀的效果。同時,本項目還打包成了 Docker 容器,無需復雜的硬件配置,和繁瑣的人工配置環境環節,系統即可簡單的部署在云主機上,對計算機和人力資源消耗很小。
同時,本系統采用 Flutter 進行開發,Flutter 在渲染技術上,選擇了自己實現的(GDI),由于有更好的可控性,使用了新的語言 Dart,避免了 ReactNative 的那種通過橋接器與 JavaScript 通訊導致效率低下的問題,所以在性能方面比 ReactNative 更高一籌。可以有效的解決在性能低下設備上的卡頓,并節省開發時間。
綜上,本系統對環境影響很小,并具有可持續發展的可行性。
2.2 需求分析
經過一定階段的市場調研,收集并分析了重要的信息,對本系統的功能和性能上的需求予以確定。
2.2.1 需求描述
本題旨在實現一個開放的多平臺音樂分享系統,并以音樂為分享目標,同時給用戶提供簡單的交流平臺,所有音樂信息均由注冊用戶提供,后交給管理員進行驗證,為此制作了一個移動端的音樂分享社區 App。
根據分析本系統 10 個模塊主要分為兩個角色:瀏覽用戶、管理員對于瀏覽用戶,有以下 6 個模塊:
(1)動態分享模塊站內分享
可將歌單及歌曲分享到論壇中的各個分區以及自己的個人動態中,站內用戶打開后會打開相對應的的歌單或歌曲站外分享可將歌曲分享到微信 QQ 等站外軟件,用戶打開后會跳轉到 app 內查看
(2)上傳模塊主要用來處理靜態資源的上傳,包括圖片,音樂,和歌詞文件
(3)下載模塊主要用來處理靜態資源的下載,包括圖片,音樂,和歌詞文件,并且可以通過用戶的權限,攔截部分靜態資源的訪問
(4)音樂播放模塊用來播放收藏的音樂或歌單,可以拖動進度條,顯示專輯封面,如果有歌詞可以顯示歌詞
(5)歌單模塊用戶可以從現有曲庫中分享歌單給所有用戶,并且可以自行對歌單中的歌曲進行修改
(6)評論模塊對分享的音樂進行評論,進行留言和回復,可以收藏和分享自己喜歡的歌單,或者評論歌單
(7)搜索模塊對所有用戶上傳的音樂資源進行搜索
對于管理員,有以下 3 個模塊:
(1)評論審核模塊對用戶舉報的評論進行審核,如果存在違規行為,管理員進行刪除
(2)音樂審核模塊對被舉報的的音樂進行審核,如存在違規信息,管理員可以刪除
(3)公告模塊在公告欄中發送公告
2.2.2 角色和用例分析建模
(1)用例分析
圖 2-1 為系統用例圖。系統分為用戶和管理員 2 種種角色,因為是封閉式論壇,避免出現版權糾紛,本軟件只開放給注冊用戶。
注冊用戶可以查看其他用戶發送的動態,歌單,音樂等。系統會根據用戶的聽歌記錄來推薦用戶可能個喜歡的歌曲,推薦系統僅記錄用戶的聽歌喜好,不會記錄敏感數據。用戶還可以舉報他們認為的違規動態或評論,交給管理員來鑒別。
用戶還可以自由的在動態里添加他們想要的元素,例如歌單,音樂,圖片等。同時還可以進行搜索,來查找他們喜歡歌曲與歌單,并將它們添加到喜愛列表中。
管理員可以管理所有用戶的狀態,可以針對性的禁言某一用戶。還可以根據用戶舉報的記錄來刪除特定的動態和評論推薦系統則會根據用戶平時聽歌喜好來推薦貼近用戶聽歌習慣的音樂或歌單,而所有的用戶的聽歌記錄均做了脫敏處理。
圖 2-1 系統用例圖
(2)用例描述
表 2-1 動態用例描述
用例名稱 | 動態 |
描述 | 用戶發送動態 |
標識符 | UC1 |
角色 | 注冊用戶 |
前置事件流 | 用戶登錄 |
主事件流 | 1.注冊用戶登錄賬號 2.用戶從首頁進入動態頁面 3.用戶點擊發送按鈕 4.彈出動態發送界面 5.用戶點擊發送,提示發送成功 6.用例結束 |
其他事件流 | 1.網絡異常,提示發送失敗 |
后置事件流 | 1.對于發送的內容,用戶可以在我的頁面進行查看 |
表 2-2 用戶注冊用例描述
用例名稱 | 用戶注冊 |
描述 | 用戶進行注冊 |
標識符 | UC2 |
角色 | 任何人 |
前置事件流 | 無 |
主事件流 | 1.用戶點擊注冊 2.輸入電話號碼 3.等待驗證碼 4.輸入其他信息 5.注冊完成 6.用例結束 |
其他事件流 | 其他事件流:1.點擊發送驗證碼按鈕,返回驗證碼 |
后置事件流 | 后置事件流:1.用戶可以登錄 |
表 2-3 上傳音樂用例描述
用例名稱 | 上傳音樂 |
描述 | 用戶上傳音樂 |
標識符 | UC3 |
角色 | 用戶 |
前置事件流 | 用戶登錄且上傳音樂 |
主事件流 | 1.用戶點擊上傳音樂 2.用戶根據提示填寫上傳音樂信息 3.用戶上傳音樂 4.用例結束 |
其他事件流 | 其他事件流:1.上傳的音樂同時發送動態 |
后置事件流 | 后置事件流:無 |
2.2.3 系統非功能需求
(1)性能需求
在用戶不多,多數網絡請求可在 500ms 內完成響應,大量用戶訪問時,網絡
請求可在 2000ms 內完成響應。
(2)兼容性需求
前端 APP 支持 iOS 和 Android 系統,并且應對舊版本系統進行一定的適配。
(3)交互性需求
系統設計和配色均遵循 MetalDesign,后端應對大多異常操作進行過處理,前端在網絡請求失敗,操作失敗時應能正確進行錯誤提示,在進行敏感操作時彈出提示框作為警示,在數據異常時進行一定的展示優化。
2.3 方案比選
目前移動互聯網全面普及,近幾年發展尤其迅速,在移動終端的開發工具鏈日漸成熟,相比之前被原生應用通知的移動市場,在大前端時代,原生開發已經失去了昔日的優勢,移動 APP 已經被各種跨平臺的前端框架所主導。目前大廠用的比較多的前端跨平臺框架有 ReactNative 和 Flutter,后端主流的框架有 Python 的 Django 和 Flask 框架,Java 的 SpringBoot 框架。三者各有其特點。本部分對 ReactNative+Django 框架和 Flutter+SpringBoot 框架兩種方案進行比選。
2.3.1 方案一:ReactNative+Django
(1)ReactNative 框架
圖 2-2ReactNative 框架結構圖
ReactNative 是 Facebook 在 2015 年發布的跨平臺框架,支持 iOS 和 Android 兩種系統。以 React 為基礎,他完全繼承了 React 組件開發的特點,提高了代碼重用率[8],各個平臺功能代碼都可以重用,官方數據顯示,iOS 和 Android 功能代碼重用率可達 90% 以上。不用 WebView,就可以完全避免交互和性能問題。應用熱更新可實現 app 功能升級和問題修復,提高 app 迭代速度和開發效率。由于基于 JS 框架 React,前端程序員不需要太多學習成本即可輕松上手,是目前使用較為廣泛的跨平臺解決方案。由于 React 社區活躍,開源輪子非常多,很多互聯網巨頭選擇使用 ReactNative 作為跨平臺的選擇方案,國外比較著名的案例有 Facebook、Instagram、Airbnb 等,國內比較著名的有京東、QQ、攜程。圖 2-2 展示了 ReactNative 的框架結構,從圖中可知,內置的 JavaScript 引擎通過編譯所寫的 JavaScript 代碼,并調用原生的視圖組件,達到應用構建的效果。
(2)Django 框架
Django 是 Python 編寫的 Web 應用框架,它采用開放源代碼。Django 最初是為新聞類站點而設計的,它有快速的開發需求,目的是實現簡單、快捷的網站開發。
經過十幾年的發展和完善,Django 擁有完善的功能和要素,并且在 Django 的 Model 層上自帶數據庫 ORM 組件,因此,開發人員無需學習其他數據庫訪問技術。Django 通過正則表達式靈活地管理 URL 映射,并具有豐富的模板語言。除此之外,自帶自由后臺管理系統,只需簡單的幾行配置和代碼即可實現一個完整的后臺數據管理平臺。
在 Django 中,Python 的程序開發人員只需編寫少量代碼就能輕松地完成正式網站所需的大部分工作,并進一步開發全功能 Web 服務 Django 本身是基于 MVC 模型的,MVC 模式簡化了后續程序的修改和擴展,使程序的某個部分得以重復利用[10]。
圖 2-3 展示了 Django 工作流程。所有的 HTTP 請求均會被 URLS 模塊捕獲,并映射到相應的視圖,在視圖層對模型進行讀和寫的操作,并響應至前端頁面。
圖 2-3Django 工作流程圖
2.3.2 方案二:Flutter+SringBoot
(1)Flutter 框架
Google 開放源碼移動應用程序 SDKFlutter,一個代碼就能同時生成兩個高性能、高保真的應用程序 iOS 和 Android[9]。通過 Dart 語言開發的代碼,可以在 iOS,Android 等平臺上運行,Web 端現在也可以在 dev 分支上運行,Flutter 團隊也宣布不久將支持桌面端。
因為 Flutter 選擇 Dart 作為其開發語言,所以 Dart 可以由 AOT(AheadOfTime)或 JIT(JustInTime)編譯,它的 JIT 編譯特性使得 Flutter 可以在開發階段達到亞秒級別的有狀態熱重載,從而大大提高開發效率。
接口繪制方面,采用自帶的高性能渲染引擎(Skia)自繪制,渲染速度和用戶體驗都不亞于本機。
Flutter 還內置了 MaterialDesign 和 Cupertino 風格的 widget(iOS 風格),開發人員可以根據不同的平臺需求,快速構建出符合相應平臺風格的良好用戶界面。
在圖 2-4 中,Flutter 正式給出了一個系統框架,我們可以看到,Flutter 框架分為三層:框架層、引擎層和 Embedder 層。
框架層是由 Dart 實現的,它包括了許多 AndroidMaterial 風格和 iOSCupertino 風格的 Widgets 小部件,以及渲染、動畫、繪圖和手勢等等。
圖 2-4Flutter 框架結構圖
使用 C/C++ 實現的 Engine 層是 Flutter 的核心引擎,主要包括 Skia 圖形引擎,Dart 運行時環境 DartVM,文本渲染引擎等等。
Embedder 層主要處理與平臺相關的一些操作,比如 Surface 渲染設置,本地插件,打包,線程設置等等。
(2)SpringBoot 框架
SpringBoot 是基于 Spring 開發的所有項目的起始點[11]。與名字一樣,SpringBoot 的目的是簡化之前 Spring 框架繁瑣的配置 XML 過程,采用了慣例大于配置的概念,這與 Mavan 有著異曲同工之處。SpringBoot 官方提供了多種 start 包,如 Spring-web-starter 就包含了 Springweb 開發所需要的 SpringMVC 包
圖 2-5SpringBoot 工作流程圖
和其他一些必備的開源包。這就是 Spring 的封裝版本,因此理所當然地繼承了 Spring 特有的特性,如 IoC(控制反轉),DI(依賴注入),AOP(面向切面編程)。2.3.3 比選結論
(1)Flutter 與 Native 對比
表 2-4 詳細的展示了 Flutter 與 ReactNative 在性能、成熟度等特性方面的對比。
表 2-4Flutter 與 ReactNative 對比表
特性 | Flutter | ReactNative |
發布時間 | 2017 | 2015 |
語言 | Dart | JavaScript |
UI 組件 | 自己實現的 Widget | 對應系統的 Native 組件 |
運行速度 | 快 | 中等 |
熱重載 | 支持 | 支持 |
文檔 | 完整 | 較完整 |
配置 | 簡單 | 較復雜 |
成熟度 | 較、成熟 | 成熟 |
支持平臺 | iOS、Android、Web 等 | iOS 和 Android |
(2)Django 與 SpringBoot 對比
表 2-5 詳細的展示了 Django 與 SpringBoot 在性能、拓展性等方面的對比。
表 2-5Django 與 SpringBoot 對比表
Django | SpringBoot | |
性能 | 高 | 高 |
可拓展性 | 強 | 強 |
ORM | 自帶 | 高 |
數據庫管理 | 自帶 | 高 |
插件 | 豐富 | 非常豐富 |
文檔 | 完整 | 非常完整 |
配置 | 簡單 | 一般 |
(3)結論
表 2-4 針對 Flutter 與 Native 在語言、性能、開發效率和組件等多方面進行了對比。可以得出,Flutter 框架雖然較新,但相對 ReactNative 并不成熟,但 Flutter 的組件豐富,能夠專注于核心業務的開發,同時配置簡單,官方文檔豐富,支持 Web 甚至是桌面端,并且在性能方面相對 ReactNative 有較大優勢。因此本系統選擇 Flutter 作為客戶端框架。
表 2-5 針對 Django 和 SpringBoot 在性能、可拓展性和插件等方面進行了對比。雖然 Django 的性能基本與 SpringBoot 相同,但 SpringBoot 具有極高的可拓展性,且各種解決方案非常成熟,有著完整的技術棧和生態圈,熟悉 Java 便于上手。綜上所述,本系統選擇 Flutter+SpringBoot 框架。
2.4 本章小結
本章節主要闡述了系統的可行性分析與需求性分析,并就系統選用的框架展開對比分析,確定了系統設計方案。
3 系統的設計
3.1 軟件體系結構
圖 3-1 系統功能結構圖
圖 3-1 對本系統的功能結構進行了展示,把業務進行模塊化的拆分之后,各個模塊之前的強依賴關系沒有了,每個模塊實現的業務相互獨立了出來,實現對系統功能點的一個解耦。這樣每個模塊如果要增添新的功能點,無需對其整個系統進行改動,只要修改特定模塊接口的實現類即可。
本系統主要由用戶動態分享、音樂播放、歌單、評論、搜索、積分、推薦等 6 大模塊組成。其中本系統的核心模塊推薦模塊,只針對注冊用戶,如果用戶之前沒有聽過任何歌曲,或者用戶聽取的歌曲樣本數量過少,推薦頁面會顯示系統每天自動生成的公共推薦列表,所有推薦的歌曲和歌單用戶均可點擊圖標進行查看。
3.2 功能設計
(1)類圖設計
圖 3-2 主要對本系統實體類的屬性以及實體類之間的關系進行了展示,系與系統核心功能動態分享和音樂分享相關的實體類有 5 個,分別是 User、Role、Reply 以及 Post,其中用戶與動態 Post 為多對多的關系,用戶可發送多個動態。每個上傳的資源都會視為一個動態,便于管理。
同時,對與音樂線管的各種靜態資源也進行了實例化,如圖 3-3 將音樂信息分為 Song、Singer、Album、Tag、SongComment 五個實體類。同為靜態資源的 Image 則單獨拿出來,便于今后搜尋。
圖 3-2 論壇類圖
圖 3-3 靜態資源類圖
2)時序圖設計
系統的亮點是短信驗證快捷登錄功能。如圖 3-4 用戶登錄時可選擇手機快捷登錄功能,向服務器請求驗證碼。后臺服務器接到請求后,會調用阿里云 SDK 發送短信驗證碼,并以手機號為鍵,驗證碼為值存入 Redis 緩存中,并設置過期時間 2 分鐘[12]。如圖 3-5 用戶再將驗證碼和手機號提交到后臺的登錄接口,后臺向 Redis 驗證驗證碼的合法性。
系統的核心功能為歌曲上傳系統,用戶選擇上傳的同時會調用服務器的 API。如圖 3-6 后臺會根據用戶上傳的內容進行判斷是否有相同文件,如沒有,則寫入用戶指定的文件夾。
圖 3-4 請求驗證碼時序圖
圖 3-5 快捷登錄時序圖
圖 3-6 歌曲上傳時序圖
(3)活動圖設計
圖 3-4 展示了動態分享的活動圖。對于用戶的權限驗證,本系統使用了 JWTtoken 進行身份驗證,所有的認證信息不會保存在服務端,而是以令牌的形式將 Token 保存在用戶端,每次請求時在請求頭 Header 中攜帶該 token 即可,服務端只在用戶第一次登錄時進行了數據庫的讀取,之后的請求只需驗證 Token 的合法性即可,大大減少了服務器的內存開支,并且實現了服務段的無狀態,非常適合 APP 使用[13]。。
同時本項目還使用了 Shiro 作為權限驗證框架,在每次請求時基于 SpringAOP 的攔截器都會攔截請求,并通過自己實現的 Realm 進行權限驗證。保證了每個接口甚至靜態資源不會被無權限的用戶訪問。
用戶選擇分享動態時,可以選擇圖片,音樂甚至是歌單。如果用戶選擇音樂,或歌單,系統會提示從已存在的歌單或者音樂中選擇,如果用戶選擇新建歌單或音樂,系統會引導用戶到新建界面。
圖 3-7 動態模塊活動圖
3.3 持久化設計
3.3.1 數據庫邏輯關系
圖 3-8 展示了本系統社交部分的用戶、角色、個人空間、動態、回復之間的關系。用戶與動態和回復是一對多的關系,體現了系統的核心需求。用戶通過發送動態及回復進行系統核心的社交功能。
3.3.2 數據庫表設計
圖 3-8 社交系統 E-R 圖
圖 3-9 數據庫表圖
在實現系統時,數據庫的設計往往決定了系統性能的上限,好的數據庫設計可以有效地簡化多表查詢的次數,顯著提升 QPS 效率。本系統數據庫使用的是 MySQL 數據庫,由于音樂的數據量較多,為了提升效率,在字段上進行了一些冗余,并且采用了可變長度字符來節約空間。所有字段均采用 long 值作為 id,避免發生鍵值溢出的情況,也方便 MySQL 在查詢的時候自動建立索引。本系統使用 SpringBoot 搭建后臺,使用同為 Spring 全家桶的 SpringDataJPA 作為 ORM 框架,數據庫表直接由模型生成,同時 JPA 也提供了一些簡單的接口,方便開發時節省不必要的基本增刪改查 SQL 語句。圖 3-9 展示了本系統數據表屬性及關聯關系。
核心表的詳細結構及其字段約束信息如下:
(1)用戶表
表 3-1 用戶
列名 | 數據類型 | 字段類型 | 長度 | 是否為空 | 默認值 | 備注 |
ban | bit(1) | bit | YES | 無 | 是否被 ban | |
create_date | datetime(6) | datetime | YES | 無 | 創建日期 | |
| varchar(255) | varchar | 255 | YES | 無 | 郵箱 |
password | varchar(255) | varchar | 255 | YES | 無 | 密碼 |
phone | varchar(255) | varchar | 255 | YES | 無 | 手機 |
role_id | bigint | bigint | YES | 無 | 角色 id 外鍵 | |
user_space_id | bigint | bigint | YES | 無 | 用戶空間外鍵 | |
username | varchar(255) | varchar | 255 | YES | 無 | 用戶名 |
(2)動態表
表 3-2 動態表
列名 | 數據類型 | 字段類型 | 長度 | 是否為空 | 默認值 | 備注 |
content | varchar(255) | varchar | 255 | NO | 無 | 內容 |
date | datetime(6) | datetime | YES | 無 | 日期 | |
id | bigint | bigint | NO | 無 | id | |
like_count | bigint | bigint | NO | 無 | 點贊次數所在板塊外 | |
posted_board_id | bigint | bigint | NO | 無 | 鍵 | |
poster_id | bigint | bigint | NO | 無 | 發送者外鍵 | |
(3)回復表 |
3.4 針對社會、健康、安全、法律等因素的相關設計
本系統是一個音樂共享平臺,系統內容完全由用戶上傳,不存在違規行為。在社會健康的設計上,本系統作為工具,為使用者提供資源交流和推薦功能,節約用戶對時間,用戶并不會長期沉迷于應用,保護使用者的身心健康。另外,所有音樂資源均會經管理員審核后進行發布,如有不符合社會主義核心價值觀的內容,審核不予通過,不會傳播可能對社會造成負面影響的資源。在法律方面,在系統所用音樂資源完全由用戶自主上傳,本平臺僅起到交流及分享作用,如用戶上傳的任何資源涉及到侵權,本網站會立即刪除該資源并對涉嫌用戶進行警告處理,嚴重侵權將會取消其會員資格,不會存在法律糾紛。開發軟件均采用 EDU 授權版本,無需對軟件額外付費,系統的整個設計均為本人自創,屬于正常的社交軟件,不存在專利和版權糾紛。同時本系統所含的推薦功能不會收集用戶的敏感信息,所有采集的數據均進行脫敏處理,用戶的隱私安全可以得到完全保證。
3.5 本章小結
本章節簡要介紹了系統的功能結構、功能設計和持久化設計,輔以繪制的圖形和文字說明來對本系統的設計思路進行概括,最后對社會健康、文化以及法律相關設計進行闡述。
4 系統的實現
4.1 多平臺架構實現
4.1.1 前端架構
(1)設計思路
主要采用 Flutter 框架進行多平臺的開發,通過 AndroidStudio 對 Flutter 整個項目的目錄進行管理,由 Flutter 自帶的引擎解析 Dart 代碼,編譯成 TargetSystem 的原生代碼。本項目主要編譯了 Android 和 iOS2 種平臺的 Native 代碼,Android 編譯成 Java,BuildTarget=SDK29;iOS 編譯成 Swift,MinimizeRequirement=iOS12.0。
Dart 代碼基本遵循了目前前端流行的 MVVM 架構,即 Model-View-ViewModel 的設計方法,設計上參考了開源 AppNeteaseCloudMusicd 的思路。具體思路是先獨立編寫各個頁面,即向用戶展示的 View,View 之間的跳轉通過路由來完成,由于目前版本 Flutter 對路由的支持不是很好,所以采用了自己實現的 RouteUtil 來完成路由功能。
各個頁面之間的數據傳遞使用 Flutter 自帶的 Provider 來實現,通過繼承 ChangeNotifier 來把 ViewModel 注冊成一個通知。當頁面需要請求時調用自己實現的 NetUtils 來向后端獲取 Model 對象,接收到 Model 對象后即返回到 Provider 封裝成 ViewModel,在通過發送通知的方式將 ViewModel 傳回前端頁面,至此即為前端 App 頁面與數據交互的大致思路,圖 4-1 為整個交互邏輯的協作圖。
圖 4-1 前端交互協作圖
(2)實現代碼
class UserModel with ChangeNotifier {User _user;String _token;User get user = >_user;/// 初始化 User void initUser() {if (Application.sp.containsKey('user')) {String s = Application.sp.getString('user');_user = User.fromJson(json.decode(s));
}
if (Application.sp.containsKey('token')) {String s = Application.sp.getString('token');_token = s;
}
}/// 登錄
Future < User > login(BuildContext context, String phone, String pwd) async {ResponseData responseData = await NetUtils1.login(context, phone, pwd);if (responseData.code != 200) {Utils.showToast(responseData.msg ? ?'登錄失敗,請檢查賬號密碼');return null;}Utils.showToast('登錄成功');saveToken(responseData.data);User user = await NetUtils1.getUserInfo(context);_saveUserInfo(user);return user;
}
4.1.2 后端架構
(1)設計思路
主要遵循了傳統的 MVC 架構進行后端開發,即 Model-View-Controller。在此程度上為了方便今后對 Web 端進行適配,增加接口適用性,所有接口均返回 JSON 對象,同時對返回的對象進行了標準化。
整個后端交互的時序圖如圖 4-2 所示,對所有不分頁請求的均返回自己實現的 ResponeData 對象,對于需要分頁的請求,為了便于前端進行開發統一返回 ResponeData 的實現類 PageResponeData。這個個對象均封裝了 code 狀態碼、msg 提示消息和 data 數據,PageResponeData 還包括了 page、num、total 等分頁相關的數據。通過枚舉類實現狀態碼 code 和提示消息 msg 的規范化,讓代碼看起來更加整潔,可讀性更強。
圖 4-2 后端交互時序圖
4.1 前端跨平臺狀態管理
本系統采用 Token 的方式來驗證用戶登錄狀態,在服務器端不會保存用戶的登錄狀態,減少了服務器內存開支。同時前端組件也大量采用了 StatefulWidget 進行開發,方便代碼復用。
4.1.1 登陸態保持及權限管理
(1)登錄態算法思路分析
登錄態即指登錄的狀態,目前 Web 常用的登錄態保持機制有 Session 會話和 JWTToken[13]。
Session 會話是指服務器端對每一個用戶都生成一個會話對象,在將 sessionId 寫入到用戶的 cookie 中來判斷用戶對應的會話窗口。由于此方式是將所有會話數據保存到服務器端的內存中,當用戶量比較龐大時會嚴重消耗服務器資源,同時 session 只對應一臺服務器,當整個服務采用微服務集群的方式部署時,用戶的登錄態無法在各個服務間傳播,所以拓展性很差。JWTtoken 是用戶在第一次登錄時獲取的一個由服務器端秘鑰進行簽名的一串字符串,類似于令牌。用戶今后的請求只需攜帶該令牌即可,不會在服務器端額外保存一段數據,可以大大減少不必要的內存開銷和數據庫讀取。所以本客戶端選擇 Token 來作為驗證的方式,用戶第一次輸入用戶密碼登錄時,將服務器返回的 Token 存儲在本地的 SharedPreferences,每次請求時通過 dart 包 Dio 在請求頭附帶 Token 信息進行驗證。
(2)權限細分思路分析
本系統采用 Shiro 框架進行權限管理,同時整合 JWT,實現 Token 驗證的同時,也能進行完整的權限管理。
ApacheShiro 是具有許多功能的全面的程序流安全性體系結構。Shiro 提供了一個簡單而直觀的 API,可以輕松解決身份驗證,授予管理權,公司會話管理方法和數據加密的問題。此外,Shiro 易于應用和理解,其功能也非常強大,它可以驗證用戶的真實身份,對用戶執行訪問控制,區分是否為用戶分配了明確的安全角色,以及區分用戶是否被允許做某事。在 Shiro 中,Session 的功能是建立相對使用的會話編程范例。范式和工具互不相關,因此,即使沒有 Web 或 EJB 組件,Shiro 也適合在所有自然環境中應用 SessionAPI。在身份認證訪問控制的整個過程中,Shiro 還可以響應惡意事件,或者在會話的生存期內。Apacheshiro 的體系結構包含三個主要概念:主題概念,主題管理器和領域。在這些內容中,主題是一個相對抽象的概念。我們通常將 Subject 對象理解為可以與應用程序交互的任何“用戶”,但是它也可以是一個由三方組成的程序,以及可以與系統交互的任何“事物”。“所有主題。使用 Shiro 框架中的 Subject 來完成諸如登錄,注銷,驗證權限和獲取會話之類的操作。SecurityManager 是 Shiro 的核心,也是 Shiro 的核心組件;所有特定的安全操作均由 SecurityManager 控制;SecurityManager 管理所有主題,并且與主題相關的所有操作都由 SecurityManager 進行交互;領域充當 Shiro 與應用程序之間的“橋梁”或“連接器”,用于 Shiro 的用戶身份驗證和授權;Realms 實際上是特定的安全 DAO:封裝數據源的連接詳細信息,以便能夠獲取所需的 Shiro 相關數據。[14]。
通過用戶自定義實現 AuthorizingRealm 接口,Shiro 可進行粒度更細的權限分割。本項目實現了 doGetAuthenticationInfo 方法,用于提取 token 中的用戶信息,通過和數據庫中用戶擁有的角色作比對,從而獲得用戶的權限。
同時,本項目還實現了 doGetAuthentizationInfo 方法,將已剝離出的用戶角色再進行細分,與數據庫每個角色擁有的精確到每部操作的具體權限進行對比,并寫入 Shiro 的 AuthenticationInfo,以此完成從角色到具體操作的權限細分[17]。
(3)實現代碼
首先實現了一個自己的 realme,用來做最終的權限和角色驗證
package wxm.example.comical_music_server.shiro;
@Service public class MyRealm extends AuthorizingRealm {private static final Logger LOGGER = LogManager.getLogger(MyRealm.class);@Autowired private UserService userService;/**
* 大坑!,必須重寫此方法,不然 Shiro 會報錯
*/@Override public boolean supports(AuthenticationToken token) {return token instanceof JWTToken;}/**
* 只 有 當 需要 檢測 用戶權限的時 候才 會 調 用此方法, 例 如
checkRole,checkPermission 之類的
*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) throws AuthenticationException {SimpleAuthorizationInfo();// Role role=user.getRole();User user = (User) principals.getPrimaryPrincipal();Role role = user.getRole();if (role != null) {simpleAuthorizationInfo.addRole(role.getName());Set < String > permission = new HashSet < String > (Arrays.asList(role.getPermission().split(",")));simpleAuthorizationInfo.addStringPermissions(permission);}return simpleAuthorizationInfo;}
4.1.2 前端組件狀態管理
(1)算法思路分析
組件化思想在如今的大前端環境中非常流行,像 React、Vue 等前端 JS 框架均支持組件化,可以將許多頁面中相同的部分抽離做成組件。但是當頁面越來越龐大,組件被不斷組合和服用,這使得組件的狀態管理變得十分困難。為未來而生的 Flutter 在設計之初就考慮到了這一點,它的核心原則就是“EverythingisaWidget”,即“一切皆為組件”。而 Flutter 中只有兩種組件,StatefulWidget(狀態化組件)和 StatelessWidget(無狀態組件),這樣的設計使得管理組件狀態異常的方便和直觀。
本系統使用類似 React 的路由管理系統,通過路由管理各個頁面的跳轉,并在前端收到后端返回的 JSON 對象后,實時繪制每個部分的組件(Widget)。由此實現動態生成動態帖子的數據。
(2)實現代碼
class Routes {static String root = "/";static String home = "/home";static String login = "/login";static String dailySongs = "/daily_songs";static String playList = "/play_list";static String topList = "/top_list";static String playSongs = "/play_songs";static String comment = "/comment";static String search = "/search";static String lookImg = "/look_img";static void configureRoutes(Router router) {router.notFoundHandler = new Handler(handlerFunc: (BuildContext context, Map < String, List < String >>params) {}print("ROUTE WAS NOT FOUND !!!");return LoginPage();});router.define(root, handler: splashHandler);router.define(login, handler: loginHandler);router.define(home, handler: homeHandler);router.define(dailySongs, handler: dailySongsHandler);router.define(playList, handler: playListHandler);router.define(topList, handler: topListHandler);router.define(playSongs, handler: playSongsHandler);router.define(comment, handler: commentHandler);router.define(search, handler: searchHandler);router.define(lookImg, handler: lookImgHandler);}
4.2 數據請求
4.2.1 音樂上傳及動態分享請求
(1)算法思路本系統所有相關數據均來自用戶上傳,用戶再上傳音樂的時候可以指定歌手,專輯,歌曲類型。所有接口均嚴格按照 RESTFULAPI 進行編寫。用戶通過 form-data 進行 post 請求,上傳音樂文件劉,同時后端通過 MultipartFile 對象進行解析為 IO 流,生成隨機的 UUID 覆蓋每個文件原本的文件名。動態分享模塊中上傳圖片也是同樣的思路。
(2)實現代碼
public class FileService {//TODO 可將文件 md5 存入緩存,避免重復上傳@Autowired private ImageDao imageDao;public Image uploadImg(MultipartFile file) {if (file.isEmpty()) {return null;}String fileName = file.getOriginalFilename();if (!FileUtil.isImage(fileName)) {return null;}File realFile = FileUtil.upload(file, Constant.RESOURCE_PATH + Constant.IMG_PATH, UUID.randomUUID().toString().replace("-", "") + "." + FileUtil.getSuffix(fileName));if (realFile == null) {return null;}Image image = new Image(realFile.getName());image = imageDao.saveAndFlush(image);return image;}
}
4.2.2 靜態資源請求
(1)算法思路本系統為音樂分享系統,未避免版權糾紛,所有音樂數據僅為內部交流。需要對靜態資源進行權限隔離,未登錄的用戶將不能訪問音樂的靜態資源。
總體思路是把用戶所有上傳的靜態資源以 Resource 的形式加載[16],用 controller 攔截 static 路徑下的所有請求,再根據 url 的路徑名把真實的靜態資源解析過去,返回文件流(類似文件下載服務)。中間鑒權步驟通過 Shiro 自帶的工具類實現,讀取已經解析好的用戶權限信息。
(2)實現代碼
@Service public class FileService {public Resource loadFileAsResource(String path) throws IOException {String realPath = Constant.RESOURCE_PATH + path;try {Resource resource = new FileSystemResource(new File(realPath));if (resource.exists()) {return resource;} else {throw new IOException("File not found " + realPath);}} catch(Exception e) {throw new IOException("File not found " + realPath);}}
}@RestController@RequestMapping(Constant.STATIC_URL_PATH) public class StaticAPIController {@Autowired private FileService fileService;@GetMapping("/{type}/{name}") public ResponseEntity < Resource > getStatic(@PathVariable String type, @PathVariable String name) throws NotFoundException {try {if (type.equals("audio")) { //如果是音頻,檢查是否有權限 if(!SecurityUtils.getSubject().isAuthenticated()){throw new AuthorizationException();}}Resource resource = fileService.loadFileAsResource("/" + type + "/" + name);String contentType = FileUtil.getMimeType(resource.getFile());if (contentType == null) {contentType = "application/octet-stream";}return ResponseEntity.ok().contentType(MediaType.parseMediaType(contentType)).body(resource);} catch(Exception e) {e.printStackTrace();throw new NotFoundException();
}
}
}
4.3 后端異常處理
4.3.1 局部異常處理
(1)算法思路
所有接口均在 service 層實現了對異常的處理,對于所有請求在 Controller 層均返回固定的 ResponeData 格式 JSON 對象,并使用枚舉類對所有可能出現的異常狀態進行了枚舉并自定義了狀態碼和錯誤提示消息。盡可能的 Controller 層對可能出現的異常進行了處理,并根據情況返回特定的狀態碼。
同時為了彌補 Spring 中@RequestParam 注解只能判斷傳入參數是否存在,無法判斷參數的類型是否合法的 BUG(例如不能判斷傳入的 int 值是否為空)。本項目基于 AOP 攔截器實現了一個簡單的參數驗證功能,只需在參入的參數前面加入特定的注解即可檢驗參數的合法性
(2)實現代碼
由于基本所有接口都實現了局部異常處理,此處僅列舉用 AOP 注解判斷參數是否合法的代碼,以及幾個典型示范。通過 AOP 攔截器實現參數檢驗
package wxm.example.comical_music_server.aop;/**
* @author Alex Wang
* @date 2020/05/12
*/
@Around("checkParam()") public Object doAround(ProceedingJoinPoint pjp) throws Throwable {MethodSignature signature = ((MethodSignature) pjp.getSignature());//得到攔截的方法Method method = signature.getMethod();//獲取方法參數注解,返回二維數組是因為某些參數可能存在多個注解Annotation[][] parameterAnnotations = method.getParameterAnnotations();if (parameterAnnotations == null || parameterAnnotations.length == 0) {return pjp.proceed();}//獲取方法參數名String[] paramNames = signature.getParameterNames();//獲取參數值Object[] paranValues = pjp.getArgs();//獲取方法參數類型Class < ?>[] parameterTypes = method.getParameterTypes();for (int i = 0; i < parameterAnnotations.length; i++) {for (int j = 0; j < parameterAnnotations[i].length; j++) {//如果該參數前面的注解是 ParamCheck 的實例,并且notNull() = true,則進行非空校驗if (parameterAnnotations[i][j] != null && parameterAnnotations[i][j] instanceof ParamCheck && ((ParamCheck) parameterAnnotations[i][j]).notNull()) {paramIsNull(paramNames[i], paranValues[i], parameterTypes[i] == null ? null: parameterTypes[i].getName());break;}}}return pjp.proceed();
}
4.3.3 全局異常處理
(1)算法思路
通過 Spring 自帶的 RestControllerAdvicer 捕捉特定類型的異常,并同意返回自己實現的 ResponeData 對象[17]。對于特定錯誤來說的來說,通過 ExceptionHandler 注解進行捕捉異常類,在此處對上一節未在 controller 處理的異常以及 AOP 出現的異常進行捕捉。
(2)實現代碼
package wxm.example.comical_music_server.controller;
@RestControllerAdvice
public class ExceptionController {/* 捕捉 UnauthorizedException */@ResponseStatus( HttpStatus.UNAUTHORIZED )@ExceptionHandler( UnauthorizedException.class )public ResponseData handle401( UnauthorizedException e ){/* System.out.println(11); */return(new ResponseData( 401, e.getMessage(), null ) );}@ResponseStatus( HttpStatus.BAD_REQUEST )@ExceptionHandler( MultipartException.class ) public ResponseData handleMultipartException(){return(new ResponseData( HttpStatus.BAD_REQUEST.value(), " 文件大小超出限制", null ) );}@ResponseStatus( HttpStatus.NOT_FOUND )@ExceptionHandler( { NotFoundException.class } ) public ResponseData handle404(){return(new ResponseData( StatusCode.NOT_FOUND, null ) );}/* 捕捉其他所有異常 */@ExceptionHandler( Exception.class )@ResponseStatus( HttpStatus.BAD_REQUEST )public ResponseData globalException( HttpServletRequest request, Throwable ex ){/* ex.printStackTrace(); */return(new ResponseData( getStatus( request ).value(), ex.getMessage(),null ) );}
4.4 智能推薦
4.4.1 音樂推薦
(1)算法思路本系統實現了分用戶推薦歌曲的功能,主要是基于用戶喜歡的音樂分類進行推薦。主要根據用戶播放過的歌曲標簽進行類似歌曲的推薦,哪個種類的歌曲播放越多,在常聽標簽里占得權值越大。同時進行影響推薦歌曲的其他變量也有歌曲播放量,歌曲是否被用戶收藏,和歌曲的上傳時間。
采用的計算公式如下:
單個標簽權值=(單個標簽聆聽數/總標簽聆聽數)* 10 (4-1)
歌曲權值=∑ 所有標簽(標簽*標簽權值)+ 歌曲播放總量/100+(30-上次
被播放時距離現在的天數)/10 +(被用戶收藏?10:0) (4-2)
系統在用戶進行請求后,會根據以上公式計算出所有歌曲對該用戶的權值,并選取權值最高的 8 首歌曲返回給用戶。同時會將該結果存入 Redis 緩存,過期時間為一天,這樣用戶再一天內多次請求就不會再消耗大量時間進行推薦算法的計算了。這樣整個系統就完成了被動生成用戶每日推薦的功能。
(2)實現代碼
package wxm.example.comical_music_server.controller;
@RestControllerAdvice
public class ExceptionController {/* 捕捉 UnauthorizedException */@ResponseStatus( HttpStatus.UNAUTHORIZED )@ExceptionHandler( UnauthorizedException.class )public ResponseData handle401( UnauthorizedException e ){/* System.out.println(11); */return(new ResponseData( 401, e.getMessage(), null ) );}@ResponseStatus( HttpStatus.BAD_REQUEST )@ExceptionHandler( MultipartException.class ) public ResponseData handleMultpublic List<Song> getUserRecommandSong( User user ){String key = Constant.PREFIX_USER_TAG + user.getId();List<Song> songs = songDao.findAllByExist( true );Map<Object, Object> map = redisUtil.hmget( key );Map<String, Integer> scores = new HashMap<>();Map<Song, Integer> songScores = new HashMap<>(); int total = 0;for ( Object o : map.keySet() ){String s = (String) o;Integer num = (Integer) map.get( o );total += num;scores.put( s, num );}for ( String s :scores.keySet() ){Integer i = scores.get( s );scores.put( s, i * 10 / total );}for ( Song s :songs ){Integer score = 0;Set<Tag> ts = s.getTags(); for ( Tag tag : ts ){Integer sc = scores.get( tag.getName() ); if ( sc != null ){score += sc;}}score += (int) s.getPlayCount() / 10; songScores.put( s, score );}Map<Song, Integer> sortedScores = sortMapByValue( songScores );}partException(){return(new ResponseData( HttpStatus.BAD_REQUEST.value(), " 文件大小超出限制", null ) );}@ResponseStatus( HttpStatus.NOT_FOUND )@ExceptionHandler( { NotFoundException.class } ) public ResponseData handle404(){return(new ResponseData( StatusCode.NOT_FOUND, null ) );}/* 捕捉其他所有異常 */@ExceptionHandler( Exception.class )@ResponseStatus( HttpStatus.BAD_REQUEST )public ResponseData globalException( HttpServletRequest request, Throwable ex ){/* ex.printStackTrace(); */return(new ResponseData( getStatus( request ).value(), ex.getMessage(),null ) );}
4.5 本章小結
本章對系統狀態管理、數據請求、異常處理和音樂推薦的算法進行了分析,并且將其部分核心代碼進行展示。
5 系統運行與效果分析
5.1 界面設計概要
本系統遵循 Google 倡導的 MetalDesign,使用了 Flutter 原生的 MetalDesign 組件進行開發,所有組件均采用 MetalDesign 顏色上色。同時部分界面參考了網易云音樂,UI 簡潔明了,UX 清晰直觀,音樂播放部分和市場上同類產品交互體驗類似,大大減少了用戶的學習成本。
5.2 用戶信息校驗
5.2.1 用戶登陸
本系統僅限注冊用戶登錄,非注冊用戶無法使用系統中的任一功能。圖 5-1 展示了系統的登錄界面,登錄時采用 token 驗證,app 會將服務器返回的 token 存入本地,每次請求使用 Dio 包在請求頭中攜帶 token 信息,用戶只需登錄一次即可。
圖 5-1 用戶登錄界面圖
5.3 主界面展示
5.3.1 系統動態展示
5.3.2 音樂詳情展示
圖 5-2 用戶動態圖
圖 5-3 音樂詳情界面圖
5.3.3 個人詳情界面
圖 5-4 個人詳情圖
5.4 本章小結
本章主要通過 app 運行時的系統效果截圖進行展示,同時對這些截圖進行文字說明,解釋各個界面的功能
6 系統測試
6.1 測試方法
常用的測試方法有黑盒測試和白盒測試[18]。
黑盒測試把被測物看成是黑盒子,無法打開。在測試過程中,測試人員完全不需要考慮箱內的邏輯結構和具體操作,只需要根據程序的需求規格說明書,檢查程序的功能是否符合其功能說明,檢查輸出結果是否正確。
白盒測試與黑盒測試相反,它將測試對象視為一個開放的透明盒子。在測試過程中,測試員利用程序內部的邏輯結構和相關信息,通過對各點的程序狀態進行檢測,來判斷程序中的每一條通道是否能夠按照預定的要求正常工作。該系統主要采用黑盒測試,以發現系統設計中的異常
6.2 測試方案及計劃
6.2.1 系統功能介紹
本系統是一個音樂分享交流應用,完成音樂分享交流的同時,提供音樂播放功能,同時分析用戶的音樂播放行為,進行用戶可能感興趣的音樂推薦。測試部分主要包括用戶登錄注冊、音樂歌單搜索、音樂播放、動態發送、音樂上傳、歌單上傳。
6.2.2 測試目的
(1)測試交互邏輯是否有不足。
(2)測試重要需求點是否完成。
(3)測試系統整體的安全級別。
(4)測試系統對不同平臺的兼容性。
6.2.3 測試范圍
針對測試的系統模塊,測試范圍如表 6-1 所示,其中說明:優先級 1 表示在一期測試,為 2 表示在二期測試。
表 6-1 系統模塊測試
模塊名稱 | 描述 | 優先級 |
用戶登錄 | 注冊用戶輸入賬號和密碼進行登錄 | 1 |
用戶注冊 | 用戶注冊為論壇的注冊用戶 | 1 |
發送動態 | 用戶發送動態 | 1 |
搜索 | 用戶進行搜索 | 1 |
音樂上傳 | 用戶上傳音樂 | 1 |
音樂信息查看 | 用戶查看音樂并播放 | 2 |
上傳歌單 | 用戶上傳歌單 | 2 |
音樂推薦 | 系統推薦音樂 | 1 |
6.2.4 測試進度安排
針對測試中不同的階段,測試進度安排如表 6-2 所示。
表 6-2 測試進度安排表
測試過程 | 計劃開始日期 | 實際開始日期 | 實際結束日期 |
熟悉系統設計需求 | 20200414 | 20200414 | 20200417 |
制定測試計劃 | 20200417 | 20200417 | 20200422 |
制定測試方案 | 20200422 | 20200422 | 20200428 |
設計測試用例 | 20200428 | 20200428 | 20200503 |
模塊、集成測試(一期) | 20200503 | 20200503 | 20200510 |
模塊、集成測試(二期) | 20200510 | 20200510 | 20200511 |
系統測試 | 20200512 | 220200512 | 20200513 |
在線測試 | 20200513 | 20200513 | 20200514 |
系統測試報告 | 20200514 | 20200514 | 20200515 |
6.3 測試過程及結果分析
6.3.1 測試用例設計
系統測試用例設計如表 6-3 所示。
表 6-3 系統功能測試用例
6.3.2 測試結果分析
通過測試,檢測到在歌單上傳時沒有去重,后續將后端實體類中吧歌曲列表的類型由 List 改為 Set,問題解決。
同時系統也有些不足,例如當頭像為空時,應該使用占位圖片,否則會造成空指針異常。初測之外系統應應該進行壓力測試來測試系統 QPS 的上線,并且還要進行性能測試來進行系統最大的資源消耗,同時這也是本系統應該優化的方向。
圖 6-1 系統異常圖
6.4 本章小結
本章詳細介紹了系統功能實現和系統測試的方法。首先介紹了測試時使用的測試方法,并且用表格展示了測試的計劃和方案,并且設計了測試用例來進行各個模塊的功能測試,在最后又對測試結果進行了分析與總結
7 總結與展望
7.1 總結
完全由用戶主導的多平臺音樂分享系統,用戶可以在平臺上自由發言,并且上傳自己喜歡的歌曲。同時,本系統也實現了類似微博一樣的動態分享功能,大家分享音樂的同時,還能進行社交。本文最先介紹了國內外的多平臺應用發展情況的不同。并詳細分析了系統的業務流盒模型設計,并且對其進行了詳細的描述,同時在詳細設計部分介紹了系統狀態管理與權限管理的實現,在系統運行部分展示了系統各個部分的運行效果,對整個系統采用的 Flutter 框架進行了詳細的分析和展示,完全兼容 iOS 和 Android 兩端,同時對不同系統的系統交互進型了不同程度的適配,界面簡潔,操作友好,實現了查看動態,上傳音樂,查看歌曲信息,聆聽歌曲和音樂推薦等功能,UI 簡潔明了,UX 清晰直觀,是個不可多得的優質音樂分享平臺。
7.2 展望
本系統是一個全棧式開發項目,涉及多種到技術,同時借用了 GitHub 上的各種開源項目資源。但由于時間和技術問題,有一些還未實現:
(1)應該加入積分收費制度,某些歌曲上傳者可以指定價格(積分),就像其他音樂平臺的付費歌曲一樣,本平臺可以用積分代替真實貨幣。
(2)系統后臺有些服務沒有采用如 RocketMQ 一樣的 JavaMQ 隊列,當某
個查詢或請求消耗時間過長時,會造成服務器卡頓。
(3)兼容性不是很好,主要因為 Flutter 在每個平臺 build 對應的 TargetSDK
版本較高,希望 Flutter 團隊今后能歐改進
(4)推薦算法比較簡單,沒有針對用戶更多的個性化需求進行調整,應該繼續進行優化。
(5)界面做得不夠友好,應該做的更為美觀
參考文獻
[1]譚青.基于用戶評論的音樂推薦系統的研究[D].安徽理工大學,2018.
[2]鄧皓瀚.基于 Flutter 的跨平臺移動 APP 開發前景研究[J].信息與電腦(理論版),2019(15):197-199.
[3]王閱蓁.移動應用的 Web 與 native 混合編程模式研究與實現[D].電子科技大學,2015.
[4]李一然.基于 ReactNative 架構的客戶端開發與實現[D].北京郵電大學,2019.
[5]PayneR.DevelopinginFlutter[M]//BeginningAppDevelopmentwithFlutter.Apress,Berkeley,CA,2019:9-27.
[6]GuangmangCui,XiaojieYe,JufengZhao,LiyaoZhu,YingChen.Multi-framemotiondeblurringusingcodedexposureimagingwithcomplementaryflutteringsequences[J].OpticsandLaserTechnology,2020,126.
[7]陳思,冷雪.微信小程序開發方式對比[J].電子制作,2020(02):52-53+22.
[8]ReactNative 官網[EB/OL].https://facebook.github.io/react-native/,2020-5-16.[9]Flutter 官網[EB/OL].https://flutter.dev/,2020-5-16.
[10]Django 官網[EB/OL].https://www.djangoproject.com/,2020-5-16.
[11]Spring 官網[EB/OL].https://spring.io/,2020-5-16.
[12]莊學松,張智,黃可望.基于 SpringBoot 的短信服務的設計與實現[J].無錫職業技術學院學報,2020,19(02):41-44.
[13]周虎.一種基于 JWT 認證 token 刷新機制研究[J].軟件工程,2019,22(12):18-20.
[14]王杉文.基于 SpringBoot+Shiro 的權限管理實現[J].電腦編程技巧與維護,2019(09):160-161+173.
[15]Django 官網[EB/OL].https://www.djangoproject.com/,2020-5-16.
[16]請叫我的全名 cv 工程師.spring 上傳文件和下載文件
[EB/OL].https://www.jianshu.com/p/cba3e0706849,2020-03-08.
[17]Smith-Cruise.Shiro 基于 SpringBoot +JWT 搭建簡單的 RESTFul 服務
[EB/OL].[https://github.com/Smith-Cruise/Spring-Boot-Shiro,2020-02-29.18]軟件測試方法和技術[M].清華大學出版社有限公司,2005.