基于spring+mysql实现的音乐推荐系统

person 匿名

发布日期: 2021-08-12 11:00:00 浏览量: 201
评分:
star star star star star star star star star star_border
*转载请注明来自write-bug.com

1.项目简介

本音乐网站的开发主要利用 spring 框架开发后台,前端采用 Boostrap+jsp 实现,数据库使用的是 MySQL。

1.1 系统功能模块结构

1.2 用户模块功能描述

其中,用户信息管理以及订单是禁止匿名用户访问的内容。

  • 用户信息模块:注册新用户、登录、用户修改密码、用户个人资料管理

  • 用户留言模块:用户可以在喜欢的歌曲下留言与其他听众交流

  • 歌曲清单模块:热门歌曲浏览、新歌浏览(首页显示最新发表的歌曲列表)、歌曲分类浏览、按歌曲名称搜索、商品详细信息

  • 喜欢的歌曲模块:添加喜欢的歌曲到自己专属的歌单

1.3 管理模块功能描述

  • 用户管理:登录;查询用户、删除用户

  • 歌曲管理:添加、修改、删除歌曲信息

2.数据库设计

2.1 表结构

收藏列表表

下载信息表

喜欢表

播放表

个性化表

最近播放表

权限表

用户表

歌曲表

用户权限表

2.2 E-R图

3.项目实现

3.1 加密处理

  1. public class MD5Util {
  2. /**
  3. * MD5 生成32位md5码
  4. * @param inStr
  5. * 需要加密的字符串
  6. * @return
  7. */
  8. public static String string2MD5(String inStr){
  9. MessageDigest md5 = null;
  10. try{
  11. md5 = MessageDigest.getInstance("MD5");
  12. }catch (Exception e){
  13. System.out.println(e.toString());
  14. e.printStackTrace();
  15. return "";
  16. }
  17. char[] charArray = inStr.toCharArray();
  18. byte[] byteArray = new byte[charArray.length];
  19. for (int i = 0; i < charArray.length; i++)
  20. byteArray[i] = (byte) charArray[i];
  21. byte[] md5Bytes = md5.digest(byteArray);
  22. StringBuffer hexValue = new StringBuffer();
  23. for (int i = 0; i < md5Bytes.length; i++){
  24. int val = ((int) md5Bytes[i]) & 0xff;
  25. if (val < 16)
  26. hexValue.append("0");
  27. hexValue.append(Integer.toHexString(val));
  28. }
  29. return hexValue.toString();
  30. }
  31. }

3.2 定时任务配置

  1. public class Static {
  2. /**
  3. * 用于记录,两张个性化推荐列表,isFromA为true表示,从表A中读取数据;
  4. * 否则从表B中读取.
  5. * 每天早上6点,两张表交替更新
  6. */
  7. public static volatile boolean isFromA=true;
  8. /**
  9. * 更新个性化推荐列表的时间间隔(这里每天更新一次)
  10. */
  11. public static final long PERIOD_DAY = 24 * 60 * 60 * 1000;
  12. /**
  13. * 更新开始的时间:时
  14. */
  15. public static final int START_HOUR = 6;
  16. /**
  17. * 更新开始的时间:分
  18. */
  19. public static final int START_MINUTE = 0;
  20. /**
  21. * 更新开始的时间:秒
  22. */
  23. public static final int START_SECOND = 0;
  24. /**
  25. * 更新是否从明天开始
  26. */
  27. public static final boolean IS_START_TOMORROW = false;
  28. /**
  29. * KNN k值
  30. * 目前系统用户很少
  31. */
  32. public static final int K = 2;
  33. /**
  34. * 基于最近邻用户的协同过滤给用户推荐歌曲的数量 n值
  35. * 歌曲很少
  36. */
  37. public static final int N = 10;
  38. /**
  39. * 排行榜的,每日一词
  40. */
  41. public static final String [] RANKING_WORD_ARRAY= {"百尺竿头","步步高升","精益求精", "登堂入室","登峰造极","泰山北斗","功成名就","大展鸿图","炉火纯青"};
  42. /**
  43. * 我的音乐的,每日一词
  44. */
  45. public static final String [] MY_MUSIC_WORD_ARRAY= {"四面楚歌","余音绕梁","靡靡之音", "扣人心弦","高山流水","四面楚歌","曲高和寡","余音袅袅","一唱三叹","四面楚歌","绕梁三日","游鱼出听"};
  46. /**
  47. * 搜索结果,每次一词
  48. */
  49. public static final String [] SEARCH_WORD_ARRAY= {"众里寻他","计获事足","望眼欲穿", "踏破铁鞋","如愿以偿","东寻西觅","摸索门径","寻踪觅迹"};
  50. /**
  51. * 是否开启混合模式,英文歌词文件较少的情况下建议关闭
  52. * 基于最近邻用户的协同过滤 + 基于异构文本网络的嵌入
  53. * 这里采取简单拼接的混合策略
  54. */
  55. public static final boolean IS_HYBRID=false;
  56. /**
  57. * 基于异构文本网络给用户推荐歌曲的数量 n值
  58. * 歌曲很少很少
  59. */
  60. public static final int N_HYBRID = 1;
  61. }

3.3 编写功能实现代码

  1. @Service("songService")
  2. public class SongServiceImpl implements SongService{
  3. @Autowired
  4. private SongDao songDao;
  5. @Autowired
  6. private UserDao userDao;
  7. @Autowired
  8. private TrendingRecDao trendingRecDao;
  9. public List<Integer> getAllSongIdRecords() {
  10. return songDao.selectAllSongId();
  11. }
  12. public Song getSongById(int songId) {
  13. return songDao.selectSongById(songId);
  14. }
  15. public Song getSongByIdWithCollectionFlag(HttpServletRequest request, int songId) {
  16. //获取对应Id的歌曲
  17. Song song=songDao.selectSongById(songId);
  18. if(song==null) {
  19. return null;
  20. }
  21. //获取对应Id的歌曲的流行度
  22. int trendingCoefficient=songDao.selectCoefficientById(songId);
  23. song.setTrendingCoefficient(trendingCoefficient);
  24. //获取用户的收藏列表
  25. List<Collection> collectionList=new ArrayList<Collection>();
  26. User user=userDao.selectByUser(Request.getUserFromHttpServletRequest(request));
  27. collectionList=trendingRecDao.getCollection(user);
  28. if(collectionList!=null) {
  29. for(Collection c:collectionList) {
  30. if(c.getSongId()==songId) {
  31. song.setWhetherCollected(true);
  32. break;
  33. }
  34. }
  35. }
  36. return song;
  37. }
  38. public void batchDeleteById(HttpServletRequest request,int[] songIds) {
  39. if(songIds==null) {
  40. return;
  41. }
  42. for(int id:songIds) {
  43. Song song=songDao.selectSongById(id);
  44. if(song!=null) {
  45. String realSongPath=request.getServletContext().getRealPath(song.getSongAddress());
  46. File fileSong=new File(realSongPath);
  47. fileSong.delete();
  48. if(song.getLyricAddress()!=null) {
  49. String realLyricPath=request.getServletContext().getRealPath(song.getLyricAddress());
  50. File fileLyric=new File(realLyricPath);
  51. fileLyric.delete();
  52. }
  53. }
  54. }
  55. songDao.deleteByIds(songIds);
  56. }
  57. /**
  58. * 这里由于在前端文件上传的时候,已经做过验证了,所以后端这里不再次验证了
  59. * 由于时间关系,这里默认前端的验证是可靠的,虽然有时候这是一种危险的做法
  60. */
  61. public boolean checkFormat(MultipartFile song, MultipartFile lyric) {
  62. return true;
  63. }
  64. public boolean addSong(HttpServletRequest request,MultipartFile song, MultipartFile lyric) {
  65. String name=song.getOriginalFilename();
  66. //歌曲名称需去掉.mp3的后缀
  67. String songName=name.substring(0, name.lastIndexOf("."));
  68. String songAddress="track/song/"+name;
  69. boolean isInsertSuccessful=false;
  70. int affectedRows=-1;
  71. //歌词文件是可选的
  72. if(lyric.isEmpty()) {
  73. affectedRows=songDao.insertOnlySong(new Song(songName,songAddress));
  74. //保存歌曲文件
  75. saveFile(song,request.getServletContext().getRealPath(songAddress));
  76. }else {
  77. //这里的歌曲名称仍旧保留.lrc的后缀
  78. String lyricName=lyric.getOriginalFilename();
  79. String lyricAddress="track/lyric/"+lyricName;
  80. affectedRows=songDao.insertSongWithLyric(new Song(songName,songAddress,lyricName,lyricAddress));
  81. //保存歌曲文件
  82. saveFile(song,request.getServletContext().getRealPath(songAddress));
  83. //保存歌词文件
  84. saveFile(lyric,request.getServletContext().getRealPath(lyricAddress));
  85. }
  86. if(affectedRows>0) {
  87. isInsertSuccessful=true;
  88. }
  89. return isInsertSuccessful;
  90. }
  91. private void saveFile(MultipartFile multipartFile, String realFilePath) {
  92. try {
  93. InputStream inputStream=multipartFile.getInputStream();
  94. FileOutputStream fileOutputStream = new FileOutputStream(realFilePath);
  95. try {
  96. int b = 0;
  97. while ((b = inputStream.read()) != -1) {
  98. fileOutputStream.write(b);
  99. }
  100. }finally{
  101. inputStream.close();
  102. fileOutputStream.close();
  103. }
  104. } catch (IOException e) {
  105. throw new RuntimeException(e);
  106. }
  107. }
  108. public List<Song> getAllSongRecordsWithLyric() {
  109. return songDao.selectAllSongsWithLyric();
  110. }
  111. }

CollaborativeFiltering.java

  1. public class CollaborativeFiltering {
  2. /**
  3. * 基于最近邻用户产生协同过滤的推荐结果
  4. * @param userIdList
  5. * 用户Id列表
  6. * @param userKNNMatrix
  7. * 用户KNN矩阵
  8. * @param user2songRatingMatrix
  9. * 用户歌曲“评分”矩阵
  10. * @param songIdList
  11. * 歌曲Id列表
  12. * @param n
  13. * 推荐的前n首歌曲
  14. * @return
  15. * 用户歌曲推荐结果矩阵.userId,[recSongId1,recSongId2...recSongIdn]
  16. */
  17. public static Map<Integer, Integer[]> userKNNBasedCF(List<Integer> userIdList,
  18. final Map<Integer, Integer[]> userKNNMatrix, final Map<Integer, float[]> user2songRatingMatrix,
  19. final List<Integer> songIdList, final int n) {
  20. // TODO Auto-generated method stub
  21. final Map<Integer,Integer[]> user2songRecMatrix=new HashMap<Integer, Integer[]>();
  22. userIdList.forEach(new Consumer<Integer>() {
  23. public void accept(Integer curUserId) {
  24. // TODO Auto-generated method stub
  25. Integer[] knnIdArray=userKNNMatrix.get(curUserId);
  26. /**
  27. * 对于每一首当前用户没有听过的歌曲
  28. * 协同得分为:
  29. * 其k个最近邻用户对该歌曲的“评分”的聚合
  30. */
  31. float[] curUserRatings=user2songRatingMatrix.get(curUserId);
  32. //为用户建立一个最小堆来存放最高的前n首歌曲
  33. MininumHeap mininumHeap=new MininumHeap(n);
  34. for(int i=0;i<curUserRatings.length;i++) {
  35. //对于没有听过的歌曲
  36. /**
  37. * 这里需要注意的是,浮点数不能用==来比较...之前竟然犯了这个低级的错误...
  38. * 故这里用 curUserRatings[i]<0.01f 来表示 curUserRatings[i]==0f
  39. */
  40. if(curUserRatings[i]<0.01f) {
  41. for(int knnIndex=0;knnIndex<knnIdArray.length;knnIndex++) {
  42. int knnId=knnIdArray[knnIndex];
  43. float[] knnUserRatings=user2songRatingMatrix.get(knnId);
  44. curUserRatings[i]+=knnUserRatings[i];
  45. }
  46. //这里的聚合策略取均值
  47. curUserRatings[i]/=knnIdArray.length;
  48. int curSongId=songIdList.get(i);
  49. //放入堆中
  50. mininumHeap.addElement(new TreeNode(curSongId,curUserRatings[i]));
  51. }
  52. }
  53. /**
  54. * 对该用户没有听过的歌曲,协同得分完成,选取n个得分最高的项目作为推荐
  55. */
  56. int trueNumber=n;
  57. //如果推荐的歌曲少于计划推荐的n首(处理歌曲很少的情况)
  58. if(mininumHeap.getCurHeapSize()<n) {
  59. trueNumber=mininumHeap.getCurHeapSize();
  60. }
  61. Integer[] curUserRecSongId=new Integer[trueNumber];
  62. for(int i=0;i<trueNumber;i++) {
  63. int recSongId=mininumHeap.getArray()[i].id;
  64. curUserRecSongId[i]=recSongId;
  65. }
  66. user2songRecMatrix.put(curUserId, curUserRecSongId);
  67. });
  68. return user2songRecMatrix;
  69. }
  70. }

3.5 功能实现

  • 音乐播放

  • 用户登录注册

  • 用户信息编辑、头像修改

  • 歌曲、歌单搜索

  • 歌单打分

  • 歌单、歌曲评论

  • 歌单列表、歌手列表分页显示

  • 歌词同步显示

  • 音乐收藏、下载、拖动控制、音量控制

  • 后台对用户、歌曲、歌手、歌单信息的管理

4.项目展示

首页

登录与注册

5.安装

5.1 下载数据库中记录的资源

去【链接: https://pan.baidu.com/s/1Qv0ohAIPeTthPK_CDwpfWg 提取码: gwa4 】下载网站依赖的歌曲及图片,将 data 夹里的文件直接放到 music-server 文件夹下。

5.2 修改配置文件

  • 创建数据库

    • 将 music/music-server/sql 文件夹中的 tp_music.sql 文件导入数据库。
  • 修改后端配置文件

    • 在 music/music-server/src/main/resources 这个目录下的文件里修改自己的 spring.datasource.username 和 spring.datasource.password
上传的附件 cloud_download 音乐推荐系统.zip ( 2.30mb, 6次下载 ) cloud_download 开发文档.pdf ( 5.60mb, 6次下载 )
error_outline 下载需要12点积分
eject