ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于SSM的在线音乐平台:从框架整合到JWT认证的完整毕业设计

基于SSM的在线音乐平台:从框架整合到JWT认证的完整毕业设计 1. 为什么是SSM而不是Spring Boot很多人看到在云端在线音乐平台这个题目第一反应是都什么年代了还在用SSM直接上Spring Boot不香吗如果这是一篇纯技术博客我也建议用Spring Boot。但这是毕业论文考虑问题的角度完全不一样。一个SSM项目虽然老但它的价值恰恰在于——SSM把Web开发中最经典、最底层的组件拆得清清楚楚Spring管对象、Spring MVC管请求分发、MyBatis管数据库操作。每一条请求从Tomcat到Controller再到Service再到Mapper整条链路是明明白白能看到、能写进论文里的。而Spring Boot把这些全封装成了自动配置你写上几个注解项目就跑起来了画架构图的时候反而不知道该怎么讲。所以这篇论文的技术路线是用SSM搭建服务端骨架用RESTful接口对接前端页面用JWT做无状态登录认证。既保住了SSM看得见摸得着的教学价值又没有完全抛弃现代Web开发的常用实践。我实际做下来麻雀虽小五脏俱全这个项目把音乐播放、收藏、歌单、评论、歌手专辑这些功能全串起来之后代码量到了六千行往上用它来做毕业设计的核心模块完全够撑场面。提示如果你的学校允许用Spring Boot当然也可以用Boot重新实现这套设计业务逻辑层和数据库层的代码几乎不用动只改配置类和依赖即可。但论文里框架原理分析那一章用SSM会好写得多。2. 项目整体脉络从需求到表结构2.1 用户侧与管理侧的功能划分在线音乐平台常见的功能其实大家心里都有数但在论文里写功能需求不能只是罗列要做成用例模型。我把整个系统分成两类角色前台用户听众注册、登录、个人信息维护浏览推荐歌曲、歌手、专辑、歌单按歌名、歌手、专辑关键词搜索播放歌曲、收藏歌曲、收藏歌单创建歌单向歌单中添加/移除歌曲对歌曲、歌单发表评论回复评论个人中心查看收藏记录和评论记录后台管理员管理员登录走独立权限拦截由于没有后台页面也可以做成纯API形式歌曲管理新增歌曲含歌词上传、编辑歌曲信息、上下架歌手管理新增歌手、编辑歌手信息专辑管理维护专辑和歌曲的归属关系歌单管理对异常歌单进行下架用户管理禁用/恢复用户账号评论管理删除违规评论这一步建议画一张UML用例图角色两个用例十来个不用太复杂老师要看到的是你会用用例图表达需求不是真的把每个按钮都画出来。2.2 数据库设计六张核心表之间的关系数据库是整篇论文里最好写也最好拿分的部分。我设计了6张表不算多但表间关系设计得好后面写代码会非常省事。表名说明关键字段tb_user用户表id, username, password, nickname, avatar, statustb_singer歌手表id, name, avatar, intro, create_timetb_album专辑表id, title, singer_id, cover, publish_timetb_song歌曲表id, name, singer_id, album_id, cover, url, lyric, play_counttb_sheet歌单表id, name, creator_id, cover, description, statustb_sheet_song歌单-歌曲关联表id, sheet_id, song_id, add_time外加评论表、收藏表tb_commentid, song_id或sheet_id用type字段区分评论的是歌曲还是歌单, user_id, content, parent_id支持楼中楼回复, create_timetb_favoriteid, user_id, target_type, target_id, create_time我的一个经验是歌曲表和歌单的关系用一条关联表来实现。很多同学想省事直接在歌单表里加一个song_ids字段存1,2,3,4听起来简单但一旦要做从歌单删除一首歌或者歌单里同款歌去重要么你写一串字符串拆分逻辑要么根本做不了。用关联表多写一个实体类SQL却简单干净后期维护也省心。另外歌曲表中冗余了singer_id和singer_name两个字段一张歌曲表同时存了歌手ID和歌手名字。严格按三范式来说这是冗余但在实际查询时能省掉一次JOIN在论文里你可以解释成适度反范式设计用空间换查询性能这也是答辩老师能接受的说法。2.3 建库脚本的核心SQL数据库用MySQL 5.7或8.0都可以编码格式务必设置utf8mb4否则中文歌词、表情符号会乱码或者存不进去。核心建表语句长这样CREATE TABLE tb_song ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL COMMENT 歌曲名, singer_id int(11) NOT NULL COMMENT 歌手ID, album_id int(11) DEFAULT NULL COMMENT 专辑ID, cover varchar(255) DEFAULT NULL COMMENT 封面图路径, url varchar(255) NOT NULL COMMENT 音频文件路径, lyric text COMMENT 歌词内容, play_count int(11) DEFAULT 0 COMMENT 播放次数, status tinyint(1) DEFAULT 1 COMMENT 0下架 1上架, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_singer (singer_id) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4;注意给singer_id加了索引因为后续按歌手查歌曲按歌手查专辑都是高频查询。不过表数据量就几万条的情况下这个索引主要体现的还是设计规范别指望性能有多大差别但论文里写通过索引优化高频查询路径这个说法是成立的。3. SSM整合这套组合到底是怎么跑起来的SSM整合的配置细节在各种博客里已经烂大街了但作为论文项目你不能只是粘贴配置要能讲清楚每个配置在干什么。我按自己项目里最终跑通的三层配置来讲。3.1 Spring层让对象不再是new出来的Spring容器负责管理Service和Dao层的组件。配置核心是applicationContext.xml里面做了三件事开启注解扫描、配置数据源、交给MyBatis接管Mapper扫描。一个关键点是数据源连接池我用的是Alibaba的Druid它的initialSize5, minIdle5, maxActive20这几个参数是写论文时很好的分析素材为什么初始连接要预建5个因为Web项目第一次请求时再创建连接要经历TCP握手、认证等步骤首次访问容易变慢为什么最大连接数设20因为单台开发机Tomcat默认200线程如果200个请求同时进来都去拿连接只会让数据库连接耗尽不如把连接数限制住让请求在获取连接处排队。bean iddataSource classcom.alibaba.druid.pool.DruidDataSource destroy-methodclose property namedriverClassName valuecom.mysql.cj.jdbc.Driver/ property nameurl valuejdbc:mysql://localhost:3306/cloud_music?useSSLfalseamp;characterEncodingutf8amp;serverTimezoneAsia/Shanghai/ property nameusername valueroot/ property namepassword value123456/ property nameinitialSize value5/ property nameminIdle value5/ property namemaxActive value20/ /bean3.2 Spring MVC层请求是怎么找到Controller的Spring MVC的核心是前端控制器DispatcherServlet在web.xml里配置。这里有个坑我第一版把DispatcherServlet的url-pattern配成了/结果静态资源CSS、JS、图片全被拦截了。后来在spring-mvc.xml里补了mvc:default-servlet-handler/和mvc:resources映射才解决。Controller层的写法上我没有用老式的ModelAndView返回JSP页面而是全部用RestController返回JSON数据前端用Vue Axios来渲染。这在技术路线里需要交代清楚SSM只管后端API前端是另一套静态页面两边通过JSON交互。这在论文里称为前后端分离思想的轻量级实现既保留SSM的教学价值又顺应现代开发趋势答辩时这是一个加分项。3.3 MyBatis层SQL写在XML里还是注解里MyBatis有两种写法一种是把SQL直接写在Mapper接口的方法注解里比如Select(select * from tb_song where id#{id})另一种是写在XML映射文件中。我的建议是——复杂SQL写XML简单查询用注解。比如按关键词搜索歌曲要同时匹配歌名、歌手名、专辑名并且还要按播放量排序这个SQL在注解里写会很挤放在XML里则清爽得多select idsearchSongs resultTypecom.cloud.entity.Song SELECT * FROM tb_song s LEFT JOIN tb_singer si ON s.singer_id si.id WHERE s.name LIKE CONCAT(%, #{keyword}, %) OR si.name LIKE CONCAT(%, #{keyword}, %) ORDER BY s.play_count DESC /select注意这里用了LEFT JOIN千万不要只在歌曲表里搜。用户搜周杰伦他期望得到的结果是周杰伦的所有歌如果你只查song.name必然搜不到。这也是这个搜索功能在论文里值得写的一点。3.4 一个容易被忽视的点事务配置只要是涉及写操作——收藏、评论、歌单加歌、播放次数1——都需要事务。Spring声明式事务用Transactional注解最简单但我发现很多同学只在Service方法上加了注解却忘了在Spring配置里开启事务管理器bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource/ /bean tx:annotation-driven transaction-managertransactionManager/没有第二行配置Transactional根本不会生效默认是自动提交模式一旦中间步骤出错前面已经写入的数据就会残留下来。我当时写收藏歌曲接口时是先判断是否已收藏再插入收藏记录、给收藏数1三步操作如果不加事务很可能出现收藏记录有了但计数没加的情况。论文里把这种事无巨细的踩坑写进去实际是很好的系统测试与问题分析章节素材。4. 核心模块的系统设计与实现4.1 用户登录JWT令牌认证怎么做传统SSM项目登录成功后通常把用户信息存进Session前端靠Cookie里的JSESSIONID维持会话。我这个项目选择了**JWTJSON Web Token**方案登录成功后后端返回一个加密的Token字符串前端存在localStorage里每次请求在Header中带上Authorization: Bearer token。为什么选JWT因为我们的前端页面是静态文件音频、图片走静态服务器API走Tomcat前后端分开部署Session的方案天然有跨域问题。JWT的无状态特性正好适合这种轻量级分离架构。实现上我没用第三方shiro或spring security而是用了jjwt这个轻量级库自己写了一个拦截器JwtInterceptor。JWT的生成逻辑核心代码String token Jwts.builder() .setSubject(userId.toString()) // 主题用户ID .claim(username, user.getUsername()) .claim(role, user) // 自定义字段 .setExpiration(new Date(System.currentTimeMillis() 30 * 60 * 1000)) // 30分钟过期 .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact();解析逻辑则是在拦截器的preHandle方法里校验Token的签名和有效期。这里论文里可以重点写一个点Token过期了怎么办我的方案是让前端在请求时收到401状态码后自动跳转到登录页重新登录。也有更友好的方案是双Token但复杂度会上升毕业设计一般用单Token就够了。4.2 权限控制拦截器区分用户和管理员用户登录拦截器好写难的是同一个接口既允许登录用户调用又允许管理员调用还要拒绝未登录用户。我做了两级拦截AuthInterceptor校验Header里的JWT是否有效有效就放行无效返回401。所有需要登录的接口都走它。AdminInterceptor校验Token里role字段是否为admin非管理员返回403。只有/admin/**前缀的接口走它。这里有个细节值得写进论文拦截器只拦截Controller层的URL如果用户绕过前端直接拼URL请求接口照样会被拦截这说明后端权限控制的正确位置是在HTTP层而不是在前端按钮上。4.3 歌曲上传文件存储与元数据分离管理员上传歌曲涉及两个东西音频文件本体和歌曲信息。我的做法是先通过/admin/song/upload接口把MP3文件写到服务器的/upload/music目录返回文件访问路径再把路径连同歌名、歌手、专辑等信息一起提交到/admin/song/save接口。这一步在论文里体现的是文件存储与元数据分离的思想。要注意两个实操点音频文件命名用UUID 原始后缀避免中文文件名和重名覆盖问题。上传大小限制Tomcat默认单次请求10MB一首高品质MP3动辄十几MB必须在spring-mvc.xml里把maxFileSize调大。我当时直接调成了50MB但建议压到20MB以内否则前端播放器加载会卡。bean idmultipartResolver classorg.springframework.web.multipart.commons.CommonsMultipartResolver property namemaxUploadSize value20971520/ /bean4.4 播放器行为播放量计数不能被刷音乐平台最基础的数据指标是播放量。最初我的方案很暴力前端每次点击播放就调一次接口play_count1。上线测试时发现用户拖动进度条都可能触发播放事件一个3分钟的歌唱3次播放量就虚高到离谱。后来改成了简单防刷策略在Redis里存用户ID 歌曲ID为key60秒内重复播放同一首歌不重复计数。没有Redis的SSM项目怎么做我用了一个更朴素的方案——用数据库存last_play_time一分钟内重复播放直接返回不计次数。SQL加个条件就行UPDATE tb_song SET play_count play_count 1 WHERE id #{songId} AND (last_play_time IS NULL OR last_play_time DATE_SUB(NOW(), INTERVAL 1 MINUTE));UPDATE影响行数为0就说明还没到一分钟这招够用而且论文里讲出来思路很清晰。4.5 评论楼中楼用parent_id实现两层级联评论功能的要点是支持回复评论。我的tb_comment表有parent_id字段回复某条评论时parent_id指向那条评论的ID查评论列表时一次查出所有评论在Java层面通过parent_id组装成父子结构// 伪代码 ListCommentVO topList new ArrayList(); MapInteger, ListCommentVO childMap new HashMap(); for (Comment c : allComments) { CommentVO vo convert(c); if (c.getParentId() 0) { topList.add(vo); } else { childMap.computeIfAbsent(c.getParentId(), k - new ArrayList()).add(vo); } }注意论文里要交代清楚只做两层不搞无限套娃。父子都最多两级这样数据结构简单前端渲染也方便。评论排序按时间升序这是最自然的阅读顺序。5. 前端页面与交互逻辑5.1 页面结构与路由前端我用了Vue 2 Vue Router Axios Element UI。页面路径设计路由页面是否需登录/首页推荐歌单 热门歌曲否/singer歌手列表否/singer/:id歌手详情 歌曲列表否/song/:id歌曲播放页 评论是评论需要登录/sheet/:id歌单详情否/user/sheet我的歌单是/user/favorite我的收藏是/login登录/注册否首页的推荐逻辑是首次加载取播放量前10的歌曲作为热门推荐再取最近新增的6个歌单作为新歌单推荐。这种推荐不算智能但在论文中定位为基于简单规则的冷启动推荐策略可以引出后续可以引入协同过滤算法的研究展望也算是一个升华点。5.2 音频播放一行HTML但有一堆细节音频播放用audio标签配合原生API就行但细节在全局单例播放器。如果每个页面都放一个audio切路由时音乐会断。我的方案是在App根组件里放一个全局audio实例通过Vuex状态管理当前播放歌曲的ID、名称、封面、播放状态所有页面通过调用this.$store.commit(playSong, song)来播放。这里有一个开发环境才会遇到的问题Vue DevTools里能看到Vuex状态没变但页面上播放器不更新。原因是audio元素的src改了之后要调用this.$refs.audio.play()才会真正播放。所以提交mutation后还要在组件里用watch监听currentSong变化手动触发play()和设置currentTime。5.3 静态资源的跨域为什么图片能显示但SSM接口通不了前端在8080端口后端在8081端口开发时跨域问题几乎必然出现。如果在后端Controller上加CrossOrigin注解简单GET请求能通但复杂请求如自定义Header携带JWT的请求会触发CORS预检OPTIONS请求还是会被拦。我的做法是在web.xml里加一个CORS过滤器统一响应头response.setHeader(Access-Control-Allow-Origin, http://localhost:8080); response.setHeader(Access-Control-Allow-Methods, GET, POST, PUT, DELETE, OPTIONS); response.setHeader(Access-Control-Allow-Headers, Content-Type, Authorization); response.setHeader(Access-Control-Allow-Credentials, true);注意Access-Control-Allow-Origin不要写成*因为*和Allow-Credentialstrue是不能同时使用的这是浏览器规范的限制。这个小细节在答辩时被问到跨域问题能答得特别专业。6. SSM注解梳理与高频问题排查6.1 常用注解速查表论文里通常需要一套SSM常用注解的整理这也是面试常考点。我按三层结构归纳如下层级注解作用组件标注Component/Service/Repository把类注册为Spring Bean依赖注入Autowired/Resource按类型/名称注入依赖Controller层Controller/RestController标注控制器URL映射RequestMapping/GetMapping/PostMapping绑定HTTP方法与URL参数绑定RequestParam/PathVariable/RequestBody绑定请求参数到方法参数事务Transactional声明事务边界切面Aspect/Before/After面向切面编程6.2Autowired注入失败最常见的三个原因写这篇论文的过程中我调试过的注入失败问题有三大类放在这里给还没做完的系统排雷第一种Mapper接口没有扫描到。在applicationContext.xml里配置mybatis:scan base-packagecom.cloud.mapper/或者用MapperScan注解。如果忘了Spring容器里根本没有Mapper代理对象Controller里Autowired直接报NoSuchBeanDefinitionException。第二种同一个接口有多个实现类。比如我一开始设计了UserService接口后来为了管理员用户和普通用户不同的逻辑写了两个实现类忘了给实现类指定Service(userService)Autowired就不知道注入哪个。解决办法要么用Qualifier指定要么直接删掉一个实现。第三种配置了事务但没配事务管理器。这个前面说过了Transactional静默失效不报错但事务不生效是最隐蔽的问题。6.3 Java 8和MyBatis版本踩坑我的开发环境是JDK 1.8 Tomcat 9 MySQL 8.0。MySQL 8.0的驱动类名不再是com.mysql.jdbc.Driver而是com.mysql.cj.jdbc.DriverURL也要加serverTimezoneAsia/Shanghai不然查时间字段会报时区错误。另外一个潜在坑是MyBatis 3.5.6之前的版本对JDK高版本支持不好如果你的JDK版本比8高建议直接把MyBatis升到3.5.9以上否则可能遇到奇怪的ClassNotFound异常。7. 系统测试哪些Bug值得写进论文7.1 功能性测试清单毕业答辩时老师最常问的问题之一就是你怎么保证系统是正确的所以测试部分不能敷衍。我用如下清单做了功能性验证这张表打印出来贴在项目报告里答辩时直接翻给老师看测试功能操作步骤预期结果实际结果用户注册填入非法邮箱/密码少于6位前端提示格式错误通过重复注册使用已存在的用户名后端返回用户名已存在通过登录成功正确账号密码返回JWT跳转首页通过登录失败错误密码前端提示不清空输入框通过搜索歌曲关键词晴天返回包含周杰伦《晴天》的所有歌曲通过收藏歌曲登录后点击心形图标心形变红收藏列表出现该歌曲通过创建歌单空名称提交提示必填不创建通过评论区间输入500字符超出最大长度前端截断/提示通过7.2 一个典型的并发问题收藏重复提交用JMeter并发测试收藏接口时我复现了一个经典的数据库重复插入问题前端连点两下收藏按钮浏览器发出了两个几乎同时到达的请求后端两次select都没查到收藏记录于是插入两条。解决思路有两个方案一数据库唯一索引。给tb_favorite表加UNIQUE KEY uk_user_target (user_id, target_type, target_id)第二次插入直接报错捕获DuplicateKeyException后返回已收藏即可。方案二先删后插/先查后插加事务。前端防抖逻辑也能解决一部分问题但后端必须有兜底。这在论文中的价值在于演示了一个真实的多线程/并发场景比笼统写系统具有较好的并发处理能力有说服力得多。7.3 性能测试索引有没有用的实测对比为了佐证索引的合理性我在论文里比较了两组SQL的执行计划不加索引的WHERE singer_id12全表扫描需要扫描3万多行加上索引后只用扫描几十行。用EXPLAIN命令输出截图直接放论文附录这是硬核算力比任何文字都直观。8. 部署与演示环境准备演示给答辩老师看最怕的就是现场环境出问题。建议提前准备两类环境。第一类本地一键启动。用Tomcat的本地运行方式数据库脚本放到项目doc目录下写清楚建库、导入、修改db.properties连接信息的README。所有修改要提前测一遍要确保断网状态也能跑因为答辩现场的网络状况不可控。第二类云服务器演示。如果能在云服务器上部署把Tomcat和MySQL都装好再配合Nginx做静态资源服务。音频文件过大时一定要走Nginx静态目录别塞进Tomcat的webapps里否则Tomcat处理大文件IO会成为瓶颈。我实际部署时还遇到一个问题云服务器的安全组默认只开了80和22端口8080/8081死活访问不了。这个看起来弱智的问题恰恰是很多同学在答辩前夜被卡住的原因。提前把端口放行规则在文档里写清楚。9. 论文写作SSM项目最适合的章节结构最后说一下论文怎么写。一个SSM项目的毕业论文通常采用选题背景→核心技术→需求分析→系统设计→系统实现→系统测试→总结与展望的七章结构。第三章写核心技术介绍时要讲清Spring、Spring MVC、MyBatis、JWT四个核心技术的原理和关系这部分内容要跟项目结合着写。比如写MyBatis的时候用本系统的歌曲搜索功能中使用MyBatis的动态SQL拼接关键词查询条件来举例不要泛泛而谈框架是什么。第四章系统设计里架构图我画的是浏览器/Vue前端 → Nginx/Tomcat → Spring MVC Controller → Service → MyBatis Mapper → MySQL的分层图配合数据表E-R图和UML用例图这三张图是整篇论文的骨架。第五章系统实现按功能模块逐个展开每个模块写清楚界面截图核心代码操作流程。注意界面上传截图时要把脏数据、调试信息清理干净字体统一窗口不要有乱七八糟的浏览器标签页。最后说说答辩时最容易被问的几个问题提前准备答案为什么不用Spring Boot答为了更清晰展示框架底层原理团队/课程要求密码存的是什么答应对MD5加密后存储绝不能明文这是个加分回答推荐算法怎么做的答目前基于播放量/热度规则以后可引入协同过滤并发场景处理了吗答收藏功能重复提交的处理方案索引优化等。这些问题答好了答辩基本稳了。
返回列表