基于多平台应用的音乐共享社区

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

创建日期

email

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.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/97791.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/97791.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/97791.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

贪心算法在物联网能耗优化中的应用

Java中的贪心算法在物联网能耗优化中的应用 贪心算法是一种在每一步选择中都采取当前状态下最优决策的算法策略&#xff0c;它在物联网(IoT)能耗优化问题中有着广泛的应用。下面我将全面详细地讲解如何使用Java实现贪心算法来解决物联网中的能耗优化问题。 一、物联网能耗优化问…

59.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--新增功能--MinIO对象存储服务

在孢子记账中我们需要存储用户的头像、账单的图片等文件&#xff0c;这些文件的存储我们可以使用MinIO对象存储服务&#xff0c; MinIO提供了高性能、可扩展的对象存储解决方案&#xff0c;能够帮助我们轻松管理这些文件资源。通过MinIO&#xff0c;我们可以将用户上传的图片文…

ESP32三种主流的开发环境

ESP32三种主流的开发环境 1. ESP-IDF (Espressif IoT Development Framework) 这是乐鑫官方提供的专业开发框架&#xff0c;基于FreeRTOS实时操作系统。 特点&#xff1a; 功能最全面&#xff0c;性能最优支持所有ESP32硬件特性使用C/C编程专业级调试工具完整的组件库和API 适合…

HarmonyOS图形处理:Canvas绘制与动画开发实战

本文将全面介绍HarmonyOS 5中Canvas组件的使用方法和动画开发技巧&#xff0c;通过详细的代码示例和最佳实践&#xff0c;帮助您掌握图形绘制和动态效果实现的核心技能。 1. Canvas组件基础与核心API Canvas是HarmonyOS中用于2D图形绘制的重要组件&#xff0c;提供了丰富的绘图…

CCAFusion:用于红外与可见光图像融合的跨模态坐标注意力网络

CCAFusion&#xff1a;用于红外与可见光图像融合的跨模态坐标注意力网络 CCAFusion: Cross-Modal Coordinate Attention Network for Infrared and Visible Image Fusion 摘要 红外与可见光图像融合旨在生成一幅包含全面信息的图像&#xff0c;该图像既能保留丰富的纹理特征&a…

ESP32-P4小智编译历险记:从“编译失败“到“成功烧录“的奇幻之旅,xiaozhi智能聊天机器人编译避坑心得

🚀 ESP32-P4:AI小智编译历险记:从"编译失败"到"成功烧录"的奇幻之旅 要编译其他芯片esp32s3-s2-c3,遇到问题也可以在这里交流 “每一个编译错误都是成长的机会,每一次成功都是坚持的胜利!” —— 某位被编译器折磨的程序员 源码地址:https://githu…

DIFY 项目中通过 Makefile 调用 Dockerfile 并使用 sudo make build-web 命令构建 web 镜像的方法和注意事项

DIFY 项目中通过 Makefile 调用 Dockerfile 并使用 sudo make build-web 命令构建 web 镜像的场景,以下是具体方法和注意事项总结: 一、通过 sudo make build-web 构建 web 镜像的核心方法 1. 理解 Makefile 与 Dockerfile 的关联 Makefile 的作用:DIFY 的 Makefile 中定义…

重学前端015 --- 响应式网页设计 CSS变换

文章目录skew()transformcursortransition.arm .left {} 和 .arm.left {} 区别skew() skew 倾斜变换函数&#xff0c;该函数有两个参数。第一个是剪切x轴的角度&#xff0c;第二个是剪切y轴的角度。 transform: skew(0deg, 44deg);transform .arm.left {top: 35%;left: 5%;t…

【GMX v1实战】时序风险结算与资本成本:深度解析 GMX 永续合约的资金费率机制

在去中心化衍生品交易平台GMX中&#xff0c;当你准备开立杠杆合约仓位&#xff08;无论是做多还是做空某个资产&#xff09;时&#xff0c;系统会默默执行一个关键前置动作——调用名为 ​​updateCumulativeFundingRate​​ 的函数。这个看似普通的“准备工作”&#xff0c;实…

中宇联云计算SD-WAN的售后服务怎么样

前言在数字化转型浪潮中&#xff0c;企业选择SD-WAN服务商不仅关注技术性能&#xff0c;更看重售后服务的质量与可靠性。中宇联云计算作为行业领先者&#xff0c;其SD-WAN售后服务体系已成为行业标杆。随着全球数字化进程加速&#xff0c;企业对广域网&#xff08;WAN&#xff…

【Kubernetes】K8s 集群外服务配置 Service 访问

在 Kubernetes 集群中&#xff0c;内部服务可通过 Service-name 进行访问。那么&#xff0c;对于集群外的服务&#xff0c;Pod 应该如何通过 Service 进行访问呢&#xff1f;一起来看看吧&#xff01;此处举例以 Pod 访问集群外部的 Mysql 数据库1、创建 Service# 创建 Service…

Linux 开发工具(1)

从开始讲Linux&#xff0c;我们的目标绝不止于写几个命令这么简单。我们的目的是在Linux系统上做开发。因此学习Linux的开发工具也是必不可少的。本章将重点讲解&#xff1a;包管理器apt(CentOS叫yum&#xff0c;这里用ubuntu举例)&#xff0c;vim编辑器。一.包管理器apt1.安装…

redis面试点记录

1、主从复制psync-> runid->runid是&#xff1f;则是全量->返回fullresync和runid和复制进度->bgsave命令准备RDB文件->之后的命令写入replication_buffer->发送RDB->发送replication_buffer的信息repl_backlog_buffer环型缓冲区&#xff0c;主节点只有一…

Elastic APM 与 Elasticsearch 集成:构建完整可观测性栈

引言 Elastic APM 深度依赖 Elasticsearch 作为数据后端&#xff0c;但正确集成可以解锁更强大的功能&#xff0c;如自定义查询、聚合分析和与其它 Elastic 工具的协同。本文探讨 APM 与 Elasticsearch 的集成细节&#xff0c;包括数据流、索引管理以及高级用例&#xff0c;帮助…

开源模型应用落地-基于DPO的Qwen3-4B意图理解精准对齐实践(二十)

一、前言 在大模型技术蓬勃发展的今天,如何让AI真正“理解”用户意图,而非仅仅生成流畅文本,已成为落地应用的核心瓶颈。尤其是在客服、搜索、智能助手等场景中,模型对用户query的深层语义解析能力,直接决定了交互体验的成败。然而,经过标准SFT(监督微调)训练的模型,往…

23种设计模式案例

一、创建型模式 1. 单例模式 (Singleton Pattern) 应用场景: 全局状态管理、全局配置、共享资源访问 // 全局状态管理器 class Store {constructor() {if (Store.instance) return Store.instance;this.state {};Store.instance this;}getState(key) { return this.state[key…

ctfshow_web13-----------文件上传.user.ini

打开题目发现是一个文件上传题扫描后发现存在upload.php.bak.bak是备份文件拿到源码正则过滤了php&#xff0c;文件大小<24,文件名小于9经尝试&#xff0c;改后缀php5,ptml均不行&#xff0c;使用.htaccess文件也不成功上传上传.user.ini&#xff0c;在文件中写上auto_prepe…

图像拼接案例,抠图案例

目录 一.图像拼接案例 1.图像拼接项目介绍 2.核心步骤 ①计算图片特征点及描述符 ②匹配特征点&#xff0c;使用暴力匹配器 ③筛选有效匹配 ④计算透视变换矩阵 ⑤应用变换和拼接 二.抠图案例 1.缩放旋转处理 2.转化为灰度图并二值化 3.找出所有轮廓&#xff0c;并在…

【左程云算法笔记016】双端队列-双链表和固定数组实现

目录 1&#xff09;双端队列的介绍 2&#xff09;双端队列用双链表的实现代码演示 3&#xff09;双端队列用固定数组的实现 代码演示 视频 【算法讲解016【入门】双端队列-双链表和固定数组实现】 Leecode leecode641 设计循环双端队列 1&#xff09;双端队列的介绍 可以…

ffplay视频输出和尺寸变换

视频输出模块 视频输出初始化的主要流程 我们开始分析视频&#xff08;图像&#xff09;的显示。 因为使⽤了SDL&#xff0c;⽽video的显示也依赖SDL的窗⼝显示系统&#xff0c;所以先从main函数的SDL初始化看起&#xff08;节选&#xff09;&#xff1a; int main(int argc, c…