feat(music): 添加音乐点赞功能及前端交互

- 后端新增点赞接口 /music/{id}/like,支持用户登录后点赞/取消点赞音乐,记录点赞数据到 user_like_music 表
- 后端修改 MusicController.getMusicById 接口,支持传入用户ID以判断当前音乐是否被该用户点赞
- 后端 MusicDTO 新增 isLiked 字段,用于前端展示点赞状态
- 数据库新增 user_like_music 表,记录用户与音乐的点赞关系,包含唯一约束防止重复点赞
- 前端 MusicDetail.vue 页面新增点赞按钮及状态展示,支持点击切换点赞状态并实时更新 UI 与点赞数
- 前端新增 toggleLikeMusic API 调用及相应交互逻辑,成功后展示操作提示信息
- 评论表 comment 新增 target_type 字段注释及状态字段注释,播放历史与各关联表新增索引优化与外键约束微调
This commit is contained in:
ikmkj
2025-07-25 23:25:45 +08:00
parent 59b6b251db
commit 4435315acf
11 changed files with 431 additions and 248 deletions

View File

@@ -31,7 +31,12 @@ public class MusicController {
*/
@GetMapping("/{id}")
public Result<MusicDTO> getMusicById(@PathVariable Long id) {
MusicDTO musicDTO = musicService.getMusicById(id);
Long userId = null;
// 检查用户是否登录,但不强制要求登录
if (StpUtil.isLogin()) {
userId = StpUtil.getLoginIdAsLong();
}
MusicDTO musicDTO = musicService.getMusicById(id, userId);
if (musicDTO == null) {
// 如果音乐不存在,返回空数据而不是错误
return Result.success(null);
@@ -213,4 +218,20 @@ public class MusicController {
MusicDTO musicDTO = musicService.playMusic(id);
return Result.success(musicDTO);
}
/**
* 点赞/取消点赞音乐
*
* @param id 音乐ID
* @return 响应结果
*/
@SaCheckLogin
@PostMapping("/{id}/like")
public Result<Boolean> toggleLikeMusic(@PathVariable Long id) {
// 获取当前登录用户ID
Long userId = StpUtil.getLoginIdAsLong();
boolean isLiked = musicService.toggleLikeMusic(id, userId);
String message = isLiked ? "点赞成功" : "取消点赞成功";
return Result.success(message, isLiked);
}
}

View File

@@ -91,6 +91,11 @@ public class MusicDTO {
*/
private Boolean isCollected;
/**
* 是否已点赞
*/
private Boolean isLiked;
/**
* 创建时间
*/
@@ -107,4 +112,11 @@ public class MusicDTO {
public void setCollected(boolean collected) {
this.isCollected = collected;
}
/**
* 设置是否已点赞
*/
public void setLiked(boolean liked) {
this.isLiked = liked;
}
}

View File

@@ -0,0 +1,29 @@
package com.test.musichouduan.entity;
import jakarta.persistence.*;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import java.time.LocalDateTime;
@Data
@Entity
@Table(name = "user_like_music", uniqueConstraints = {
@UniqueConstraint(columnNames = {"userId", "musicId"})
})
public class UserLikeMusic {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private Long userId;
@Column(nullable = false)
private Long musicId;
@CreationTimestamp
@Column(nullable = false, updatable = false)
private LocalDateTime createTime;
}

View File

@@ -1,109 +1,109 @@
package com.test.musichouduan.init;
import cn.dev33.satoken.secure.BCrypt;
import com.test.musichouduan.entity.*;
import com.test.musichouduan.repository.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.List;
/**
* 数据初始化器
*/
@Component
public class DataInitializer implements CommandLineRunner {
@Autowired
private UserRepository userRepository;
@Autowired
private MusicTypeRepository musicTypeRepository;
@Autowired
private SingerTypeRepository singerTypeRepository;
@Autowired
private PlaylistTypeRepository playlistTypeRepository;
@Override
@Transactional
public void run(String... args) throws Exception {
// 初始化管理员用户
initAdminUser();
// 初始化音乐类型
initMusicTypes();
// 初始化歌手类型
initSingerTypes();
// 初始化歌单类型
initPlaylistTypes();
}
/**
* 初始化管理员用户
*/
private void initAdminUser() {
if (!userRepository.existsByUsername("admin")) {
User admin = new User();
admin.setUsername("admin");
admin.setPassword(BCrypt.hashpw("123456"));
admin.setNickname("管理员");
admin.setGender(0);
admin.setRole(1);
admin.setStatus(1);
userRepository.save(admin);
}
}
/**
* 初始化音乐类型
*/
private void initMusicTypes() {
if (musicTypeRepository.count() == 0) {
List<String> typeNames = Arrays.asList("流行", "摇滚", "民谣", "电子", "古典", "嘻哈", "爵士", "乡村");
for (String name : typeNames) {
MusicType type = new MusicType();
type.setName(name);
type.setDescription(name + "音乐");
musicTypeRepository.save(type);
}
}
}
/**
* 初始化歌手类型
*/
private void initSingerTypes() {
if (singerTypeRepository.count() == 0) {
List<String> typeNames = Arrays.asList("华语男歌手", "华语女歌手", "华语组合", "欧美男歌手", "欧美女歌手", "欧美组合", "日韩男歌手", "日韩女歌手", "日韩组合");
for (String name : typeNames) {
SingerType type = new SingerType();
type.setName(name);
type.setDescription(name);
singerTypeRepository.save(type);
}
}
}
/**
* 初始化歌单类型
*/
private void initPlaylistTypes() {
if (playlistTypeRepository.count() == 0) {
List<String> typeNames = Arrays.asList("流行", "摇滚", "民谣", "电子", "古典", "嘻哈", "爵士", "乡村", "轻音乐", "ACG");
for (String name : typeNames) {
PlaylistType type = new PlaylistType();
type.setName(name);
type.setDescription(name + "歌单");
playlistTypeRepository.save(type);
}
}
}
}
//package com.test.musichouduan.init;
//
//import cn.dev33.satoken.secure.BCrypt;
//import com.test.musichouduan.entity.*;
//import com.test.musichouduan.repository.*;
//
//import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.boot.CommandLineRunner;
//import org.springframework.stereotype.Component;
//import org.springframework.transaction.annotation.Transactional;
//
//import java.util.Arrays;
//import java.util.List;
//
///**
// * 数据初始化器
// */
//@Component
//public class DataInitializer implements CommandLineRunner {
//
// @Autowired
// private UserRepository userRepository;
//
// @Autowired
// private MusicTypeRepository musicTypeRepository;
//
// @Autowired
// private SingerTypeRepository singerTypeRepository;
//
// @Autowired
// private PlaylistTypeRepository playlistTypeRepository;
//
// @Override
// @Transactional
// public void run(String... args) throws Exception {
// // 初始化管理员用户
// initAdminUser();
//
// // 初始化音乐类型
// initMusicTypes();
//
// // 初始化歌手类型
// initSingerTypes();
//
// // 初始化歌单类型
// initPlaylistTypes();
// }
//
// /**
// * 初始化管理员用户
// */
// private void initAdminUser() {
// if (!userRepository.existsByUsername("admin")) {
// User admin = new User();
// admin.setUsername("admin");
// admin.setPassword(BCrypt.hashpw("123456"));
// admin.setNickname("管理员");
// admin.setGender(0);
// admin.setRole(1);
// admin.setStatus(1);
// userRepository.save(admin);
// }
// }
//
// /**
// * 初始化音乐类型
// */
// private void initMusicTypes() {
// if (musicTypeRepository.count() == 0) {
// List<String> typeNames = Arrays.asList("流行", "摇滚", "民谣", "电子", "古典", "嘻哈", "爵士", "乡村");
// for (String name : typeNames) {
// MusicType type = new MusicType();
// type.setName(name);
// type.setDescription(name + "音乐");
// musicTypeRepository.save(type);
// }
// }
// }
//
// /**
// * 初始化歌手类型
// */
// private void initSingerTypes() {
// if (singerTypeRepository.count() == 0) {
// List<String> typeNames = Arrays.asList("华语男歌手", "华语女歌手", "华语组合", "欧美男歌手", "欧美女歌手", "欧美组合", "日韩男歌手", "日韩女歌手", "日韩组合");
// for (String name : typeNames) {
// SingerType type = new SingerType();
// type.setName(name);
// type.setDescription(name);
// singerTypeRepository.save(type);
// }
// }
// }
//
// /**
// * 初始化歌单类型
// */
// private void initPlaylistTypes() {
// if (playlistTypeRepository.count() == 0) {
// List<String> typeNames = Arrays.asList("流行", "摇滚", "民谣", "电子", "古典", "嘻哈", "爵士", "乡村", "轻音乐", "ACG");
// for (String name : typeNames) {
// PlaylistType type = new PlaylistType();
// type.setName(name);
// type.setDescription(name + "歌单");
// playlistTypeRepository.save(type);
// }
// }
// }
//}

View File

@@ -43,6 +43,15 @@ public interface UserCollectMusicRepository extends JpaRepository<UserCollectMus
*/
boolean existsByUserAndMusic(User user, Music music);
/**
* 判断用户是否收藏了音乐
*
* @param userId 用户ID
* @param musicId 音乐ID
* @return 是否收藏
*/
boolean existsByUserIdAndMusicId(Long userId, Long musicId);
/**
* 根据用户统计收藏的音乐数量
*

View File

@@ -0,0 +1,29 @@
package com.test.musichouduan.repository;
import com.test.musichouduan.entity.UserLikeMusic;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserLikeMusicRepository extends JpaRepository<UserLikeMusic, Long> {
/**
* 根据用户ID和音乐ID查找点赞记录
*
* @param userId 用户ID
* @param musicId 音乐ID
* @return 点赞记录
*/
Optional<UserLikeMusic> findByUserIdAndMusicId(Long userId, Long musicId);
/**
* 检查用户是否点赞了某音乐
*
* @param userId 用户ID
* @param musicId 音乐ID
* @return 是否存在
*/
boolean existsByUserIdAndMusicId(Long userId, Long musicId);
}

View File

@@ -13,6 +13,15 @@ import java.util.concurrent.CompletableFuture;
*/
public interface MusicService {
/**
* 根据ID获取音乐并根据用户ID判断是否收藏和点赞
*
* @param id 音乐ID
* @param userId 用户ID (可以为null)
* @return 音乐DTO
*/
MusicDTO getMusicById(Long id, Long userId);
/**
* 根据ID获取音乐
*
@@ -133,4 +142,13 @@ public interface MusicService {
* @return CompletableFuture 包装的音乐DTO列表
*/
CompletableFuture<List<MusicDTO>> updateHotMusicCache();
/**
* 切换音乐点赞状态
*
* @param musicId 音乐ID
* @param userId 用户ID
* @return 是否点赞成功 (true: 点赞, false: 取消点赞)
*/
boolean toggleLikeMusic(Long musicId, Long userId);
}

View File

@@ -7,6 +7,8 @@ import com.test.musichouduan.entity.Music;
import com.test.musichouduan.entity.MusicType;
import com.test.musichouduan.entity.Singer;
import com.test.musichouduan.entity.User;
import com.test.musichouduan.entity.UserCollectMusic;
import com.test.musichouduan.entity.UserLikeMusic;
import com.test.musichouduan.entity.UserMusic;
import com.test.musichouduan.exception.BusinessException;
import com.fasterxml.jackson.core.type.TypeReference;
@@ -72,7 +74,10 @@ public class MusicServiceImpl implements MusicService {
private UserRepository userRepository;
@Autowired
private UserMusicRepository userMusicRepository;
private UserCollectMusicRepository userCollectMusicRepository;
@Autowired
private UserLikeMusicRepository userLikeMusicRepository;
@Autowired
private PlayHistoryRepository playHistoryRepository;
@@ -81,13 +86,19 @@ public class MusicServiceImpl implements MusicService {
private FileService fileService;
@Override
public MusicDTO getMusicById(Long id) {
public MusicDTO getMusicById(Long id, Long userId) {
// 查找音乐如果不存在则返回null
return musicRepository.findById(id)
.map(this::convertToDTO)
.map(music -> convertToDTO(music, userId))
.orElse(null);
}
@Override
public MusicDTO getMusicById(Long id) {
// 对于未登录用户或不需要用户状态的场景直接调用新方法并传入null
return getMusicById(id, null);
}
@Override
public PageResult<MusicDTO> getMusicList(Integer page, Integer size, String keyword, Long typeId) {
// 构建查询条件
@@ -463,35 +474,37 @@ public class MusicServiceImpl implements MusicService {
@Override
@Transactional
public boolean toggleCollectMusic(Long id, Long userId) {
// 获取用户
Optional<User> userOpt = userRepository.findById(userId);
if (!userOpt.isPresent()) {
// 如果用户不存在返回false
return false;
}
User user = userOpt.get();
public boolean toggleCollectMusic(Long musicId, Long userId) {
// 直接通过ID判断是否存在避免查询完整实体
boolean isCollected = userCollectMusicRepository.existsByUserIdAndMusicId(userId, musicId);
// 获取音乐
Optional<Music> musicOpt = musicRepository.findById(id);
if (!musicOpt.isPresent()) {
// 如果音乐不存在返回false
return false;
}
Music music = musicOpt.get();
// 检查是否已收藏
boolean isCollected = userMusicRepository.existsByUserAndMusic(user, music);
Music music = musicRepository.findById(musicId)
.orElseThrow(() -> new BusinessException("音乐不存在"));
if (isCollected) {
// 如果已收藏,取消收藏
userMusicRepository.uncollectMusic(user.getId(), music.getId());
} else {
// 如果未收藏,则收藏
userMusicRepository.collectMusic(user.getId(), music.getId());
}
// 已收藏,取消收藏
User user = userRepository.findById(userId).orElseThrow(() -> new BusinessException("用户不存在"));
UserCollectMusic collect = userCollectMusicRepository.findByUserAndMusic(user, music)
.orElseThrow(() -> new BusinessException("收藏记录不存在"));
userCollectMusicRepository.delete(collect);
return !isCollected; // 返回当前收藏状态true表示已收藏false表示未收藏
int currentCollectCount = music.getCollectCount() != null ? music.getCollectCount() : 0;
music.setCollectCount(Math.max(0, currentCollectCount - 1));
musicRepository.save(music);
return false; // 返回 "未收藏" 状态
} else {
// 未收藏,执行收藏
User user = userRepository.findById(userId).orElseThrow(() -> new BusinessException("用户不存在"));
UserCollectMusic newCollect = new UserCollectMusic();
newCollect.setUser(user);
newCollect.setMusic(music);
userCollectMusicRepository.save(newCollect);
int currentCollectCount = music.getCollectCount() != null ? music.getCollectCount() : 0;
music.setCollectCount(currentCollectCount + 1);
musicRepository.save(music);
return true; // 返回 "已收藏" 状态
}
}
@Override
@@ -501,24 +514,24 @@ public class MusicServiceImpl implements MusicService {
.orElseThrow(() -> new BusinessException("用户不存在"));
// 获取用户收藏的音乐
Page<UserMusic> userMusicPage = userMusicRepository.findCollectedMusic(
user.getId(),
PageRequest.of(page - 1, size, Sort.by(Sort.Direction.DESC, "collectTime"))
Page<UserCollectMusic> userCollectMusicPage = userCollectMusicRepository.findByUser(
user,
PageRequest.of(page - 1, size, Sort.by(Sort.Direction.DESC, "createTime"))
);
// 转换为DTO
List<MusicDTO> musicDTOs = userMusicPage.getContent().stream()
.map(userMusic -> {
MusicDTO musicDTO = convertToDTO(userMusic.getMusic());
List<MusicDTO> musicDTOs = userCollectMusicPage.getContent().stream()
.map(userCollectMusic -> {
MusicDTO musicDTO = convertToDTO(userCollectMusic.getMusic());
// 设置收藏时间
musicDTO.setCollectTime(userMusic.getCollectTime());
musicDTO.setCollectTime(userCollectMusic.getCreateTime());
return musicDTO;
})
.collect(Collectors.toList());
return new PageResult<>(
musicDTOs,
userMusicPage.getTotalElements(),
userCollectMusicPage.getTotalElements(),
page,
size
);
@@ -539,8 +552,6 @@ public class MusicServiceImpl implements MusicService {
// 增加播放次数
long currentPlayCount = music.getPlayCount() != null ? music.getPlayCount() : 0;
music.setPlayCount(currentPlayCount + 1);
// 根据要求,将点赞数与播放次数同步
music.setLikeCount((int) (currentPlayCount + 1));
musicRepository.save(music);
// 如果用户已登录,记录播放历史
@@ -560,6 +571,34 @@ public class MusicServiceImpl implements MusicService {
return musicRepository.findById(id).orElse(null);
}
@Override
@Transactional
public boolean toggleLikeMusic(Long musicId, Long userId) {
Music music = musicRepository.findById(musicId)
.orElseThrow(() -> new BusinessException("音乐不存在"));
Optional<UserLikeMusic> likeOpt = userLikeMusicRepository.findByUserIdAndMusicId(userId, musicId);
if (likeOpt.isPresent()) {
// 已点赞,取消点赞
userLikeMusicRepository.delete(likeOpt.get());
int currentLikeCount = music.getLikeCount() != null ? music.getLikeCount() : 0;
music.setLikeCount(Math.max(0, currentLikeCount - 1));
musicRepository.save(music);
return false; // 返回 "未点赞" 状态
} else {
// 未点赞,执行点赞
UserLikeMusic newLike = new UserLikeMusic();
newLike.setUserId(userId);
newLike.setMusicId(musicId);
userLikeMusicRepository.save(newLike);
int currentLikeCount = music.getLikeCount() != null ? music.getLikeCount() : 0;
music.setLikeCount(currentLikeCount + 1);
musicRepository.save(music);
return true; // 返回 "已点赞" 状态
}
}
/**
* 将实体转换为DTO
*
@@ -567,52 +606,49 @@ public class MusicServiceImpl implements MusicService {
* @return 音乐DTO
*/
private MusicDTO convertToDTO(Music music) {
// 保留此方法以兼容不需要用户状态的转换
return convertToDTO(music, null);
}
/**
* 将实体转换为DTO并根据用户ID判断是否收藏和点赞
*
* @param music 音乐实体
* @param userId 用户ID (可以为null)
* @return 音乐DTO
*/
private MusicDTO convertToDTO(Music music, Long userId) {
MusicDTO musicDTO = new MusicDTO();
BeanUtils.copyProperties(music, musicDTO);
// 确保专辑字段正确复制
musicDTO.setAlbum(music.getAlbum());
// 设置类型ID列表
if (music.getTypes() != null) {
List<Long> typeIds = music.getTypes().stream()
.map(MusicType::getId)
.collect(Collectors.toList());
musicDTO.setTypeIds(typeIds);
// 设置类型名称列表
List<String> typeNames = music.getTypes().stream()
.map(MusicType::getName)
.collect(Collectors.toList());
musicDTO.setTypeNames(typeNames);
// 设置类型ID和名称列表
if (!CollectionUtils.isEmpty(music.getTypes())) {
musicDTO.setTypeIds(music.getTypes().stream().map(MusicType::getId).collect(Collectors.toList()));
musicDTO.setTypeNames(music.getTypes().stream().map(MusicType::getName).collect(Collectors.toList()));
}
// 设置歌手ID列表
if (music.getSingers() != null) {
List<Long> singerIds = music.getSingers().stream()
.map(Singer::getId)
.collect(Collectors.toList());
musicDTO.setSingerIds(singerIds);
// 设置歌手名称列表
List<String> singerNames = music.getSingers().stream()
.map(Singer::getName)
.collect(Collectors.toList());
// 设置歌手ID和名称列表
if (!CollectionUtils.isEmpty(music.getSingers())) {
musicDTO.setSingerIds(music.getSingers().stream().map(Singer::getId).collect(Collectors.toList()));
List<String> singerNames = music.getSingers().stream().map(Singer::getName).collect(Collectors.toList());
musicDTO.setSingerNames(singerNames);
// 设置歌手名称字符串(用于前端显示)
if (!singerNames.isEmpty()) {
musicDTO.setSinger(String.join(", ", singerNames));
}
musicDTO.setSinger(String.join("", singerNames));
}
// 检查当前登录用户是否已收藏该音乐
if (StpUtil.isLogin()) {
Long userId = StpUtil.getLoginIdAsLong();
boolean isCollected = userMusicRepository.existsByUserIdAndMusicId(userId, music.getId());
// 如果传入了用户ID则查询该用户的收藏和点赞状态
if (userId != null) {
boolean isCollected = userCollectMusicRepository.existsByUserIdAndMusicId(userId, music.getId());
musicDTO.setCollected(isCollected);
boolean isLiked = userLikeMusicRepository.existsByUserIdAndMusicId(userId, music.getId());
musicDTO.setLiked(isLiked);
} else {
// 如果未传入用户ID则默认为未收藏和未点赞
musicDTO.setCollected(false);
musicDTO.setLiked(false);
}
return musicDTO;

View File

@@ -116,6 +116,15 @@ export function toggleCollectMusic(id) {
return api.post(`/music/${id}/collect`)
}
/**
* 点赞/取消点赞音乐
* @param {number} id 音乐ID
* @returns {Promise<any>}
*/
export function toggleLikeMusic(id) {
return api.post(`/music/${id}/like`)
}
/**
* 获取收藏的音乐列表
* @param {number} page 页码

View File

@@ -73,6 +73,14 @@
{{ music.collected ? '已收藏' : '收藏' }}
</el-button>
<el-button
:type="music.liked ? 'success' : 'default'"
@click="toggleLike"
>
<el-icon><component :is="music.liked ? 'Check' : 'Pointer'" /></el-icon>
{{ music.liked ? '已点赞' : '点赞' }} ({{ music.likeCount || 0 }})
</el-button>
<el-button
@click="addToPlaylist"
icon="Plus"
@@ -194,7 +202,7 @@ import { ref, computed, onMounted, watch, onUnmounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { usePlayerStore } from '../stores/player'
import { useUserStore } from '../stores/user'
import { getMusicById, toggleCollectMusic, getHotMusic } from '../api/music'
import { getMusicById, toggleCollectMusic, getHotMusic, toggleLikeMusic } from '../api/music'
import { getCommentList, addComment, deleteComment as apiDeleteComment } from '../api/comment'
import { connect, subscribe, unsubscribe, disconnect } from '../utils/websocket'
import { ElMessage, ElMessageBox } from 'element-plus'
@@ -399,8 +407,9 @@ const toggleCollect = async () => {
try {
const res = await toggleCollectMusic(music.value.id)
if (res.code === 200) {
music.value.collected = !music.value.collected
ElMessage.success(music.value.collected ? '收藏成功' : '已取消收藏')
// 后端直接返回了最新的收藏状态 (true/false),直接赋值即可
music.value.collected = res.data
ElMessage.success(res.message || (music.value.collected ? '收藏成功' : '已取消收藏'))
} else {
ElMessage.error(res.message || '操作失败')
}
@@ -410,6 +419,29 @@ const toggleCollect = async () => {
}
}
// 切换点赞状态
const toggleLike = async () => {
try {
const res = await toggleLikeMusic(music.value.id);
if (res.code === 200) {
// 后端直接返回了最新的点赞状态 (true/false),直接赋值
music.value.liked = res.data;
// 更新点赞数
if (music.value.liked) {
music.value.likeCount = (music.value.likeCount || 0) + 1;
} else {
music.value.likeCount = Math.max(0, (music.value.likeCount || 0) - 1);
}
ElMessage.success(res.message || (music.value.liked ? '点赞成功' : '已取消点赞'));
} else {
ElMessage.error(res.message || '操作失败');
}
} catch (error) {
console.error('点赞操作失败:', error);
ElMessage.error('操作失败,请稍后重试');
}
};
// 添加到播放列表
const addToPlaylist = () => {
playerStore.addToPlaylist(music.value)

View File

@@ -11,7 +11,7 @@
Target Server Version : 80041 (8.0.41)
File Encoding : 65001
Date: 23/07/2025 22:59:43
Date: 25/07/2025 22:22:53
*/
SET NAMES utf8mb4;
@@ -44,42 +44,37 @@ CREATE TABLE `comment` (
`content` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '评论内容',
`user_id` bigint NOT NULL COMMENT '评论用户ID',
`target_id` bigint NOT NULL COMMENT '评论目标ID',
`target_type` int NOT NULL,
`target_type` int NOT NULL COMMENT '评论目标类型1-音乐2-歌单3-歌手',
`parent_id` bigint NULL DEFAULT NULL COMMENT '父评论ID',
`status` int NULL DEFAULT NULL,
`status` int NULL DEFAULT NULL COMMENT '状态0-禁用1-正常',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_user_id`(`user_id` ASC) USING BTREE,
INDEX `idx_target`(`target_id` ASC, `target_type` ASC) USING BTREE,
INDEX `idx_parent_id`(`parent_id` ASC) USING BTREE,
CONSTRAINT `FK8kcum44fvpupyw6f5baccx25c` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 28 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '评论表' ROW_FORMAT = Dynamic;
INDEX `idx_parent_id`(`parent_id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 56 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '评论表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for music
-- ----------------------------
DROP TABLE IF EXISTS `music`;
CREATE TABLE `music` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '音乐ID',
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '音乐名称',
`cover` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '封面URL',
`url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '音乐文件URL',
`lyric` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '歌词',
`duration` int NULL DEFAULT 0 COMMENT '时长(秒)',
`album` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
-- Interaction and Hot Score Fields
`play_count` bigint NULL DEFAULT 0 COMMENT '播放次数',
`like_count` int NULL DEFAULT 0 COMMENT '点赞数',
`collect_count` int NULL DEFAULT 0 COMMENT '收藏数',
`comment_count` int NULL DEFAULT 0 COMMENT '评论数',
`hot_score` double NULL DEFAULT 0.0 COMMENT '热度分数',
-- Timestamps
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '音乐ID',
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '音乐名称',
`cover` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '封面URL',
`url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '音乐文件URL',
`lyric` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '歌词',
`duration` int NULL DEFAULT 0 COMMENT '时长(秒)',
`play_count` bigint NULL DEFAULT 0 COMMENT '播放次数',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`album` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`like_count` int NULL DEFAULT NULL COMMENT '点赞数',
`collect_count` int NULL DEFAULT NULL COMMENT '收藏数',
`comment_count` int NULL DEFAULT NULL COMMENT '评论数',
`hot_score` double NULL DEFAULT NULL COMMENT '热度分数',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '音乐表' ROW_FORMAT = Dynamic;
-- ----------------------------
@@ -121,9 +116,7 @@ CREATE TABLE `music_type_relation` (
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_music_type`(`music_id` ASC, `type_id` ASC) USING BTREE,
INDEX `idx_type_id`(`type_id` ASC) USING BTREE,
CONSTRAINT `FK2i7xdrmrh0l6thp9u2r16fdcm` FOREIGN KEY (`music_id`) REFERENCES `music` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `FKjtxip1yvwn66p4cbpycekychq` FOREIGN KEY (`type_id`) REFERENCES `music_type` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
INDEX `idx_type_id`(`type_id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 12 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '音乐-类型关联表' ROW_FORMAT = Dynamic;
-- ----------------------------
@@ -137,10 +130,8 @@ CREATE TABLE `play_history` (
`play_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '播放时间',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_user_id`(`user_id` ASC) USING BTREE,
INDEX `idx_music_id`(`music_id` ASC) USING BTREE,
CONSTRAINT `FKm05jb66wc5epn38vuwm55o933` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `FKt6cp1qv95qi4abyn69soyvibk` FOREIGN KEY (`music_id`) REFERENCES `music` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 43 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '播放历史表' ROW_FORMAT = Dynamic;
INDEX `idx_music_id`(`music_id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 51 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '播放历史表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for playlist
@@ -159,10 +150,8 @@ CREATE TABLE `playlist` (
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_creator_id`(`creator_id` ASC) USING BTREE,
INDEX `idx_type_id`(`type_id` ASC) USING BTREE,
CONSTRAINT `FKi45kpenvb301c9yb1labp8b4j` FOREIGN KEY (`creator_id`) REFERENCES `user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `FKttnmgs0esm9njqa0g52mbp48` FOREIGN KEY (`type_id`) REFERENCES `playlist_type` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '歌单表' ROW_FORMAT = Dynamic;
INDEX `idx_type_id`(`type_id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '歌单表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for playlist_music
@@ -176,9 +165,7 @@ CREATE TABLE `playlist_music` (
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_playlist_music`(`playlist_id` ASC, `music_id` ASC) USING BTREE,
INDEX `idx_music_id`(`music_id` ASC) USING BTREE,
CONSTRAINT `FK5g0xtl5e89uycye0jo1ll65sq` FOREIGN KEY (`music_id`) REFERENCES `music` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `FKq9o07ljjk03aeeqt0q9lwhndk` FOREIGN KEY (`playlist_id`) REFERENCES `playlist` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
INDEX `idx_music_id`(`music_id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '歌单-音乐关联表' ROW_FORMAT = Dynamic;
-- ----------------------------
@@ -208,8 +195,7 @@ CREATE TABLE `singer` (
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_type_id`(`type_id` ASC) USING BTREE,
CONSTRAINT `FKdwhlvo45iuspxcjg0gax5exwm` FOREIGN KEY (`type_id`) REFERENCES `singer_type` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
INDEX `idx_type_id`(`type_id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '歌手表' ROW_FORMAT = Dynamic;
-- ----------------------------
@@ -220,9 +206,7 @@ CREATE TABLE `singer_music` (
`singer_id` bigint NOT NULL,
`music_id` bigint NOT NULL,
PRIMARY KEY (`singer_id`, `music_id`) USING BTREE,
INDEX `FKk0fdv208e2ulhcxd9gge63h6j`(`music_id` ASC) USING BTREE,
CONSTRAINT `FKf33uki3h3ftg9ne16xr4n1srp` FOREIGN KEY (`singer_id`) REFERENCES `singer` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `FKk0fdv208e2ulhcxd9gge63h6j` FOREIGN KEY (`music_id`) REFERENCES `music` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
INDEX `FKk0fdv208e2ulhcxd9gge63h6j`(`music_id` ASC) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
@@ -289,10 +273,8 @@ CREATE TABLE `user_collect_music` (
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_user_music`(`user_id` ASC, `music_id` ASC) USING BTREE,
INDEX `idx_music_id`(`music_id` ASC) USING BTREE,
CONSTRAINT `FK1bm2wy4dgmhdfvp6k9fhqpf73` FOREIGN KEY (`music_id`) REFERENCES `music` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `FKt8w479d40yle3jxmqiovm6quo` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户收藏音乐表' ROW_FORMAT = Dynamic;
INDEX `idx_music_id`(`music_id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户收藏音乐表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for user_collect_playlist
@@ -305,10 +287,8 @@ CREATE TABLE `user_collect_playlist` (
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_user_playlist`(`user_id` ASC, `playlist_id` ASC) USING BTREE,
INDEX `idx_playlist_id`(`playlist_id` ASC) USING BTREE,
CONSTRAINT `FKe4f1odrrb8yq287v9gwsbq5px` FOREIGN KEY (`playlist_id`) REFERENCES `playlist` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `FKqxg6amofxpxuxf6uwjebb5jak` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户收藏歌单表' ROW_FORMAT = Dynamic;
INDEX `idx_playlist_id`(`playlist_id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户收藏歌单表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for user_collect_singer
@@ -321,10 +301,22 @@ CREATE TABLE `user_collect_singer` (
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_user_singer`(`user_id` ASC, `singer_id` ASC) USING BTREE,
INDEX `idx_singer_id`(`singer_id` ASC) USING BTREE,
CONSTRAINT `FKlvx6pnysffce263knm6iivjjj` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `FKn8hu81ynti2nku5p7nsh6mk1k` FOREIGN KEY (`singer_id`) REFERENCES `singer` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户收藏歌手表' ROW_FORMAT = Dynamic;
INDEX `idx_singer_id`(`singer_id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户收藏歌手表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for user_like_music
-- ----------------------------
DROP TABLE IF EXISTS `user_like_music`;
CREATE TABLE `user_like_music` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`music_id` bigint NOT NULL COMMENT '音乐ID',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '点赞时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_user_music`(`user_id` ASC, `music_id` ASC) USING BTREE,
INDEX `idx_music_id`(`music_id` ASC) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户点赞音乐表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Table structure for user_music
@@ -337,10 +329,8 @@ CREATE TABLE `user_music` (
`user_id` bigint NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `FKb53nc2rlyaotl1socy90u0v8`(`music_id` ASC) USING BTREE,
INDEX `FKq3jkqxxm4lxvoaauy93dq1hjl`(`user_id` ASC) USING BTREE,
CONSTRAINT `FKb53nc2rlyaotl1socy90u0v8` FOREIGN KEY (`music_id`) REFERENCES `music` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `FKq3jkqxxm4lxvoaauy93dq1hjl` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
INDEX `FKq3jkqxxm4lxvoaauy93dq1hjl`(`user_id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for user_playlist
@@ -353,8 +343,7 @@ CREATE TABLE `user_playlist` (
`user_id` bigint NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `FK7k6xercebs1wg014xb49hw3h0`(`playlist_id` ASC) USING BTREE,
INDEX `FKmofufj03yw1wqauec6vrdnfw1`(`user_id` ASC) USING BTREE,
CONSTRAINT `FKmofufj03yw1wqauec6vrdnfw1` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
INDEX `FKmofufj03yw1wqauec6vrdnfw1`(`user_id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
@@ -368,8 +357,7 @@ CREATE TABLE `user_singer` (
`user_id` bigint NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `FKacl5uu14x3io8qygn0cp27nvb`(`singer_id` ASC) USING BTREE,
INDEX `FKnogaivkt6y4jy5vbt85tu8qj0`(`user_id` ASC) USING BTREE,
CONSTRAINT `FKnogaivkt6y4jy5vbt85tu8qj0` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
INDEX `FKnogaivkt6y4jy5vbt85tu8qj0`(`user_id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;