feat: 初始化音乐项目后台管理系统- 新增后台管理相关页面和功能组件

- 实现管理员评论管理、数据统计等功能
- 添加后台布局和路由管理
-配置数据库和Redis连接
- 实现文件上传和访问路径配置
- 添加Sa-Token安全配置
This commit is contained in:
ikmkj
2025-04-26 19:32:45 +08:00
commit b275ffab5a
185 changed files with 32763 additions and 0 deletions

64
music-houduan/.gitattributes vendored Normal file
View File

@@ -0,0 +1,64 @@
### Example user template template
### Example user template
# IntelliJ project files
.idea
target
*.gz
*.log
.log
*.logs
.logs
*.zip
*.iml
out
gen
*/.vscode
.vscode
#忽略所有.svn目录
.svn/
#忽略所有target目录
target/
#忽略所有.idea目录及其内容
.idea/
.idea/*
#忽略所有.ipr文件
*.ipr
#忽略所有.iws文件
*.iws
#忽略所有.gradle目录
.gradle/
#忽略所有.settings目录
.settings/
#忽略所有.classpath文件
.classpath
#忽略所有.project文件
.project
#忽略所有.pydevproject文件
.pydevproject
#忽略所有.DS_Store文件
.DS_Store
#忽略所有.class文件
*.class
#忽略所有.log文件
*.log
#忽略所有.tmp文件
*.tmp
#忽略所有.swp文件
*.swp
node_modules/
/dist/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.git
# Editor directories and files
*.suo
*.ntvs*
*.njsproj
*.sln
dist/
npm-debug.log
.vscode/
README.md

39
music-houduan/.gitignore vendored Normal file
View File

@@ -0,0 +1,39 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
*.log
*.logs
*/*.log
*/.log
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
logs
node_modules

145
music-houduan/pom.xml Normal file
View File

@@ -0,0 +1,145 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.test</groupId>
<artifactId>music-houduan</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>music-houduan</name>
<description>音乐应用后端</description>
<properties>
<java.version>17</java.version>
<sa-token.version>1.38.0</sa-token.version>
</properties>
<dependencies>
<!-- Spring Boot 基础依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 开发工具 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- MySQL Connector -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Spring Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Spring Boot Starter Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Redis连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- Sa-Token 权限认证 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- Sa-Token 整合 Redis -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-jackson</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- 参数校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- WebSocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- 工具类 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- 文件上传 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.5</version>
</dependency>
<!-- 图片处理 -->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.20</version>
</dependency>
<!-- Knife4j API文档 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>4.5.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,21 @@
package com.test.musichouduan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* 音乐应用后端启动类
*/
@SpringBootApplication
@EnableTransactionManagement
@EnableScheduling
public class MusicHouduanApplication {
public static void main(String[] args) {
SpringApplication.run(MusicHouduanApplication.class, args);
System.out.println("\n---------- 音乐应用后端启动成功 ----------\n");
}
}

View File

@@ -0,0 +1,33 @@
package com.test.musichouduan.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
/**
* 跨域配置
*/
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
// 允许指定域名跨域调用
config.addAllowedOrigin("http://localhost:5173");
config.addAllowedOrigin("http://localhost:5174");
config.addAllowedOrigin("https://your-production-domain.com");
// 允许跨域发送cookie
config.setAllowCredentials(true);
// 放行全部原始头信息
config.addAllowedHeader("*");
// 允许所有请求方法跨域调用
config.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}

View File

@@ -0,0 +1,24 @@
package com.test.musichouduan.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Knife4j 配置类
*/
@Configuration
public class Knife4jConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 配置 Knife4j 的静态资源
registry.addResourceHandler("doc.html")
.addResourceLocations("classpath:/META-INF/resources/")
.resourceChain(false); // 使用默认的资源解析器
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/")
.resourceChain(false); // 使用默认的资源解析器
}
}

View File

@@ -0,0 +1,34 @@
package com.test.musichouduan.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* Redis配置
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
// 使用GenericJackson2JsonRedisSerializer来序列化和反序列化redis的value值
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
// Hash的key也采用StringRedisSerializer的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
// Hash的value也采用GenericJackson2JsonRedisSerializer的序列化方式
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
template.afterPropertiesSet();
return template;
}
}

View File

@@ -0,0 +1,55 @@
package com.test.musichouduan.config;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.stp.StpUtil;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
/**
* Sa-Token配置
*/
@Configuration
public class SaTokenConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册Sa-Token拦截器打开注解式鉴权功能
registry.addInterceptor(new SaInterceptor(handle -> {
// 打印调试信息
System.out.println("\n=== Sa-Token 拦截器检查登录状态 ===\n");
// 获取当前登录用户ID
Object loginId = StpUtil.getLoginIdDefaultNull();
System.out.println("当前登录用户ID: " + loginId);
// 获取当前用户角色
List<String> roleList = StpUtil.getRoleList();
System.out.println("当前用户角色: " + roleList);
// 检查登录状态
StpUtil.checkLogin();
}))
.addPathPatterns("/api/**")
.excludePathPatterns(
"/api/user/login",
"/api/user/register",
"/api/music/list",
"/api/music/latest",
"/api/music/hot",
"/api/music/*/play",
"/api/playlist/list",
"/api/playlist/latest",
"/api/playlist/hot",
"/api/playlist/official",
"/api/playlist/*/play",
"/api/singer/list",
"/api/singer/latest",
"/api/type/**",
"/api/banner/list",
"/api/comment/list"
);
}
}

View File

@@ -0,0 +1,55 @@
package com.test.musichouduan.config;
import cn.dev33.satoken.stp.StpInterface;
import com.test.musichouduan.entity.User;
import com.test.musichouduan.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* 自定义权限验证接口扩展
*/
@Component
public class StpInterfaceImpl implements StpInterface {
@Autowired
private UserRepository userRepository;
/**
* 返回一个账号所拥有的权限码集合
*/
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
// 暂时不需要权限码
return new ArrayList<>();
}
/**
* 返回一个账号所拥有的角色标识集合
*/
@Override
public List<String> getRoleList(Object loginId, String loginType) {
List<String> roles = new ArrayList<>();
// 获取用户ID
Long userId = Long.parseLong(loginId.toString());
// 查询用户
userRepository.findById(userId).ifPresent(user -> {
// 根据用户的role字段设置角色
if (user.getRole() == 1) {
roles.add("admin");
} else {
roles.add("user");
}
// 打印调试信息
System.out.println("用户ID: " + userId + ", 角色: " + roles);
});
return roles;
}
}

View File

@@ -0,0 +1,119 @@
package com.test.musichouduan.config;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.ExternalDocumentation;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
/**
* Knife4j配置
*/
@Configuration
public class SwaggerConfig {
@Bean
public OpenAPI openAPI() {
// 添加服务器信息
List<Server> servers = new ArrayList<>();
servers.add(new Server().url("/").description("本地服务"));
return new OpenAPI()
.info(new Info()
.title("音乐应用API文档")
.description("音乐应用后端API接口文档")
.version("v1.0.0")
.contact(new Contact()
.name("Admin")
.email("admin@example.com")
.url("https://example.com"))
.license(new License()
.name("Apache 2.0")
.url("https://www.apache.org/licenses/LICENSE-2.0.html")))
.externalDocs(new ExternalDocumentation()
.description("项目文档")
.url("https://example.com/docs"))
.servers(servers)
.addSecurityItem(new SecurityRequirement().addList("bearerAuth"))
.components(new Components()
.addSecuritySchemes("bearerAuth", new SecurityScheme()
.name("bearerAuth")
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")));
}
@Bean
public GroupedOpenApi userApi() {
return GroupedOpenApi.builder()
.group("用户接口")
.pathsToMatch("/api/user/**")
.build();
}
@Bean
public GroupedOpenApi musicApi() {
return GroupedOpenApi.builder()
.group("音乐接口")
.pathsToMatch("/api/music/**")
.build();
}
@Bean
public GroupedOpenApi singerApi() {
return GroupedOpenApi.builder()
.group("歌手接口")
.pathsToMatch("/api/singer/**")
.build();
}
@Bean
public GroupedOpenApi playlistApi() {
return GroupedOpenApi.builder()
.group("歌单接口")
.pathsToMatch("/api/playlist/**")
.build();
}
@Bean
public GroupedOpenApi commentApi() {
return GroupedOpenApi.builder()
.group("评论接口")
.pathsToMatch("/api/comment/**")
.build();
}
@Bean
public GroupedOpenApi typeApi() {
return GroupedOpenApi.builder()
.group("类型接口")
.pathsToMatch("/api/type/**")
.build();
}
@Bean
public GroupedOpenApi bannerApi() {
return GroupedOpenApi.builder()
.group("轮播图接口")
.pathsToMatch("/api/banner/**")
.build();
}
@Bean
public GroupedOpenApi adminApi() {
return GroupedOpenApi.builder()
.group("管理员接口")
.pathsToMatch("/api/admin/**")
.build();
}
}

View File

@@ -0,0 +1,25 @@
package com.test.musichouduan.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Web配置类
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
/**
* 跨域配置
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}

View File

@@ -0,0 +1,28 @@
package com.test.musichouduan.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Web MVC 配置
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Value("${file.upload-path}")
private String uploadPath;
@Value("${file.access-path}")
private String accessPath;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 配置上传文件资源映射
registry.addResourceHandler(accessPath + "**")
.addResourceLocations("file:" + uploadPath);
// 注意:前后端分离项目,不需要配置静态资源映射
}
}

View File

@@ -0,0 +1,33 @@
package com.test.musichouduan.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
// 配置消息代理,用于广播式消息(例如,/topic/ 前缀)
// 客户端订阅 "/topic/comments/{targetType}/{targetId}" 来接收特定目标的评论更新
config.enableSimpleBroker("/topic");
// 配置应用程序目标前缀,客户端发送消息到服务器的目标前缀(例如,/app/ 前缀)
// 这个项目我们主要用服务器主动推送,所以 /app 可能用不到,但先配置上
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// 注册一个 STOMP 端点,客户端将使用它连接到 WebSocket 服务器
// "/ws" 是连接的端点路径
// withSockJS() 启用 SockJS 后备选项,以便在 WebSocket 不可用时使用其他传输方式
// setAllowedOrigins("*") 允许所有来源的跨域连接,在生产环境中应配置为具体的来源
registry.addEndpoint("/ws")
.setAllowedOrigins("http://localhost:5173", "http://localhost:5174", "https://your-production-domain.com")
.withSockJS(); // 启用 SockJS 支持
}
}

View File

@@ -0,0 +1,153 @@
package com.test.musichouduan.controller;
import cn.dev33.satoken.annotation.SaCheckRole;
import com.test.musichouduan.dto.BannerDTO;
import com.test.musichouduan.dto.PageResult;
import com.test.musichouduan.dto.Result;
import com.test.musichouduan.service.BannerService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* 轮播图控制器
*/
@Tag(name = "轮播图接口", description = "轮播图的增删改查操作")
@RestController
@RequestMapping("/api/banner")
public class BannerController {
@Autowired
private BannerService bannerService;
/**
* 根据ID获取轮播图
*
* @param id 轮播图ID
* @return 响应结果
*/
@Operation(summary = "根据ID获取轮播图", description = "根据轮播图ID获取轮播图详情")
@GetMapping("/{id}")
public Result<BannerDTO> getBannerById(@Parameter(description = "轮播图ID") @PathVariable Long id) {
BannerDTO bannerDTO = bannerService.getBannerById(id);
return Result.success(bannerDTO);
}
/**
* 获取轮播图列表
*
* @return 响应结果
*/
@Operation(summary = "获取轮播图列表", description = "获取所有启用状态的轮播图列表")
@GetMapping("/list")
public Result<List<BannerDTO>> getBannerList() {
List<BannerDTO> bannerList = bannerService.getBannerList();
return Result.success(bannerList);
}
/**
* 分页获取轮播图列表
*
* @param page 页码
* @param size 每页大小
* @return 响应结果
*/
@Operation(summary = "分页获取轮播图列表", description = "分页获取轮播图列表,需要管理员权限")
@SaCheckRole("admin")
@GetMapping("/page")
public Result<PageResult<BannerDTO>> getBannerPage(
@Parameter(description = "页码", example = "1") @RequestParam(defaultValue = "1") Integer page,
@Parameter(description = "每页大小", example = "10") @RequestParam(defaultValue = "10") Integer size) {
PageResult<BannerDTO> pageResult = bannerService.getBannerPage(page, size);
return Result.success(pageResult);
}
/**
* 添加轮播图
*
* @param bannerDTO 轮播图DTO
* @param image 图片文件
* @return 响应结果
*/
@Operation(summary = "添加轮播图", description = "添加轮播图,需要管理员权限")
@SaCheckRole("admin")
@PostMapping("/add")
public Result<BannerDTO> addBanner(
@Parameter(description = "轮播图信息") @RequestPart("banner") BannerDTO bannerDTO,
@Parameter(description = "轮播图图片文件") @RequestPart("image") MultipartFile image) {
BannerDTO result = bannerService.addBanner(bannerDTO, image);
return Result.success("添加成功", result);
}
/**
* 更新轮播图
*
* @param id 轮播图ID
* @param bannerDTO 轮播图DTO
* @param image 图片文件
* @return 响应结果
*/
@Operation(summary = "更新轮播图", description = "更新轮播图信息,需要管理员权限")
@SaCheckRole("admin")
@PutMapping("/{id}")
public Result<BannerDTO> updateBanner(
@Parameter(description = "轮播图ID") @PathVariable Long id,
@Parameter(description = "轮播图信息") @RequestPart("banner") BannerDTO bannerDTO,
@Parameter(description = "轮播图图片文件,可选") @RequestPart(value = "image", required = false) MultipartFile image) {
BannerDTO result = bannerService.updateBanner(id, bannerDTO, image);
return Result.success("更新成功", result);
}
/**
* 删除轮播图
*
* @param id 轮播图ID
* @return 响应结果
*/
@Operation(summary = "删除轮播图", description = "删除轮播图,需要管理员权限")
@SaCheckRole("admin")
@DeleteMapping("/{id}")
public Result<Boolean> deleteBanner(@Parameter(description = "轮播图ID") @PathVariable Long id) {
boolean result = bannerService.deleteBanner(id);
return Result.success("删除成功", result);
}
/**
* 更新轮播图状态
*
* @param id 轮播图ID
* @param status 状态
* @return 响应结果
*/
@Operation(summary = "更新轮播图状态", description = "更新轮播图状态0-禁用1-启用,需要管理员权限")
@SaCheckRole("admin")
@PutMapping("/{id}/status")
public Result<Boolean> updateBannerStatus(
@Parameter(description = "轮播图ID") @PathVariable Long id,
@Parameter(description = "状态0-禁用1-启用", example = "1") @RequestParam Integer status) {
boolean result = bannerService.updateBannerStatus(id, status);
return Result.success("更新成功", result);
}
/**
* 更新轮播图排序
*
* @param id 轮播图ID
* @param orderNum 排序号
* @return 响应结果
*/
@Operation(summary = "更新轮播图排序", description = "更新轮播图排序号,数字越小越靠前,需要管理员权限")
@SaCheckRole("admin")
@PutMapping("/{id}/order")
public Result<Boolean> updateBannerOrder(
@Parameter(description = "轮播图ID") @PathVariable Long id,
@Parameter(description = "排序号,数字越小越靠前", example = "1") @RequestParam Integer orderNum) {
boolean result = bannerService.updateBannerOrder(id, orderNum);
return Result.success("更新成功", result);
}
}

View File

@@ -0,0 +1,29 @@
package com.test.musichouduan.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* 基本测试控制器
*/
@RestController
@RequestMapping("/basic-test")
public class BasicTestController {
/**
* 基本测试接口
*/
@GetMapping
public Map<String, Object> test() {
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("message", "基本测试成功");
result.put("data", "Hello, Basic Test!");
result.put("time", System.currentTimeMillis());
return result;
}
}

View File

@@ -0,0 +1,97 @@
package com.test.musichouduan.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.stp.StpUtil;
import com.test.musichouduan.dto.CommentDTO;
import com.test.musichouduan.dto.PageResult;
import com.test.musichouduan.dto.Result;
import com.test.musichouduan.service.CommentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* 评论控制器
*/
@RestController
@RequestMapping("/api/comment")
public class CommentController {
@Autowired
private CommentService commentService;
/**
* 根据ID获取评论
*
* @param id 评论ID
* @return 响应结果
*/
@GetMapping("/{id}")
public Result<CommentDTO> getCommentById(@PathVariable Long id) {
CommentDTO commentDTO = commentService.getCommentById(id);
return Result.success(commentDTO);
}
/**
* 分页获取评论列表
*
* @param targetId 目标ID
* @param targetType 目标类型
* @param page 页码
* @param size 每页大小
* @return 响应结果
*/
@GetMapping("/list")
public Result<PageResult<CommentDTO>> getCommentList(
@RequestParam Long targetId,
@RequestParam Integer targetType,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
PageResult<CommentDTO> pageResult = commentService.getCommentList(targetId, targetType, page, size);
return Result.success(pageResult);
}
/**
* 添加评论
*
* @param commentDTO 评论DTO
* @return 响应结果
*/
@SaCheckLogin
@PostMapping("/add")
public Result<CommentDTO> addComment(@RequestBody CommentDTO commentDTO) {
CommentDTO result = commentService.addComment(commentDTO);
return Result.success("评论成功", result);
}
/**
* 删除评论
*
* @param id 评论ID
* @return 响应结果
*/
@SaCheckLogin
@DeleteMapping("/{id}")
public Result<Boolean> deleteComment(@PathVariable Long id) {
boolean result = commentService.deleteComment(id);
return Result.success("删除成功", result);
}
/**
* 获取用户的评论列表
*
* @param page 页码
* @param size 每页大小
* @return 响应结果
*/
@SaCheckLogin
@GetMapping("/user")
public Result<PageResult<CommentDTO>> getUserCommentList(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
// 获取当前登录用户ID
Long userId = StpUtil.getLoginIdAsLong();
PageResult<CommentDTO> pageResult = commentService.getUserCommentList(userId, page, size);
return Result.success(pageResult);
}
}

View File

@@ -0,0 +1,36 @@
package com.test.musichouduan.controller;
import com.test.musichouduan.dto.Result;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* 回退控制器
* 处理所有未匹配的请求
* 使用Order注解指定优先级数值越大优先级越低
*/
@RestController
@Order(Integer.MAX_VALUE)
public class FallbackController {
/**
* 处理所有非API请求
*
* @param request HTTP请求
* @param response HTTP响应
* @return 响应结果
*/
@RequestMapping({"/1", "/2", "/3", "/4", "/5", "/6", "/7", "/8", "/9"})
public Result<String> handleFallback(HttpServletRequest request, HttpServletResponse response) {
// 设置HTTP状态码为404
response.setStatus(HttpStatus.NOT_FOUND.value());
// 返回错误信息
return Result.error(HttpStatus.NOT_FOUND.value(), "资源不存在: " + request.getRequestURI());
}
}

View File

@@ -0,0 +1,71 @@
package com.test.musichouduan.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import com.test.musichouduan.dto.Result;
import com.test.musichouduan.service.FileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
/**
* 文件控制器
*/
@RestController
@RequestMapping("/api/file")
public class FileController {
@Autowired
private FileService fileService;
/**
* 上传图片
*
* @param file 图片文件
* @return 响应结果
*/
@SaCheckLogin
@PostMapping("/upload/image")
public Result<String> uploadImage(@RequestParam("file") MultipartFile file) {
String url = fileService.uploadImage(file);
return Result.success("上传成功", url);
}
/**
* 上传音乐
*
* @param file 音乐文件
* @return 响应结果
*/
@SaCheckLogin
@PostMapping("/upload/music")
public Result<String> uploadMusic(@RequestParam("file") MultipartFile file) {
String url = fileService.uploadMusic(file);
return Result.success("上传成功", url);
}
/**
* 上传歌词
*
* @param file 歌词文件
* @return 响应结果
*/
@SaCheckLogin
@PostMapping("/upload/lyric")
public Result<String> uploadLyric(@RequestParam("file") MultipartFile file) {
String lyric = fileService.uploadLyric(file);
return Result.success("上传成功", lyric);
}
/**
* 删除文件
*
* @param url 文件URL
* @return 响应结果
*/
@SaCheckLogin
@DeleteMapping("/delete")
public Result<Boolean> deleteFile(@RequestParam String url) {
boolean result = fileService.deleteFile(url);
return Result.success("删除成功", result);
}
}

View File

@@ -0,0 +1,42 @@
package com.test.musichouduan.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* 简单的Hello控制器
*/
@RestController
@RequestMapping("/api")
public class HelloController {
/**
* 欢迎接口
*/
@GetMapping("/hello")
public Map<String, Object> hello() {
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("message", "Hello, Music API!");
result.put("data", "欢迎使用音乐应用API");
result.put("timestamp", System.currentTimeMillis());
return result;
}
/**
* 测试热部署接口
*/
@GetMapping("/test")
public Map<String, Object> test() {
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("message", "测试成功");
result.put("data", "这是一个测试接口,可以修改此处来测试热部署");
result.put("timestamp", System.currentTimeMillis());
return result;
}
}

View File

@@ -0,0 +1,216 @@
package com.test.musichouduan.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.stp.StpUtil;
import com.test.musichouduan.dto.MusicDTO;
import com.test.musichouduan.dto.PageResult;
import com.test.musichouduan.dto.Result;
import com.test.musichouduan.service.MusicService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* 音乐控制器
*/
@RestController
@RequestMapping("/api/music")
public class MusicController {
@Autowired
private MusicService musicService;
/**
* 根据ID获取音乐
*
* @param id 音乐ID
* @return 响应结果
*/
@GetMapping("/{id}")
public Result<MusicDTO> getMusicById(@PathVariable Long id) {
MusicDTO musicDTO = musicService.getMusicById(id);
if (musicDTO == null) {
// 如果音乐不存在,返回空数据而不是错误
return Result.success(null);
}
return Result.success(musicDTO);
}
/**
* 分页获取音乐列表
*
* @param page 页码
* @param size 每页大小
* @param keyword 关键字
* @param typeId 类型ID
* @return 响应结果
*/
@GetMapping("/list")
public Result<PageResult<MusicDTO>> getMusicList(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) Long typeId) {
PageResult<MusicDTO> pageResult = musicService.getMusicList(page, size, keyword, typeId);
return Result.success(pageResult);
}
/**
* 根据歌手ID获取音乐列表
*
* @param singerId 歌手ID
* @param page 页码
* @param size 每页大小
* @return 响应结果
*/
@GetMapping("/singer/{singerId}")
public Result<PageResult<MusicDTO>> getMusicListBySinger(
@PathVariable Long singerId,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
PageResult<MusicDTO> pageResult = musicService.getMusicListBySinger(singerId, page, size);
return Result.success(pageResult);
}
/**
* 获取最新音乐列表
*
* @param limit 限制数量
* @return 响应结果
*/
@GetMapping("/latest")
public Result<List<MusicDTO>> getLatestMusic(@RequestParam(defaultValue = "10") Integer limit) {
List<MusicDTO> musicList = musicService.getLatestMusic(limit);
return Result.success(musicList);
}
/**
* 获取热门音乐列表
*
* @param limit 限制数量
* @return 响应结果
*/
@GetMapping("/hot")
public Result<List<MusicDTO>> getHotMusic(@RequestParam(defaultValue = "10") Integer limit) {
List<MusicDTO> musicList = musicService.getHotMusic(limit);
return Result.success(musicList);
}
/**
* 搜索音乐
*
* @param keyword 关键字
* @param page 页码
* @param size 每页大小
* @return 响应结果
*/
@GetMapping("/search")
public Result<PageResult<MusicDTO>> searchMusic(
@RequestParam String keyword,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
PageResult<MusicDTO> pageResult = musicService.getMusicList(page, size, keyword, null);
return Result.success(pageResult);
}
/**
* 添加音乐
*
* @param musicDTO 音乐DTO
* @param file 音乐文件
* @param cover 封面文件
* @return 响应结果
*/
@SaCheckRole("admin")
@PostMapping("/add")
public Result<MusicDTO> addMusic(
@RequestPart("music") MusicDTO musicDTO,
@RequestPart(value = "file", required = false) MultipartFile file,
@RequestPart(value = "cover", required = false) MultipartFile cover) {
MusicDTO result = musicService.addMusic(musicDTO, file, cover);
return Result.success("添加成功", result);
}
/**
* 更新音乐
*
* @param id 音乐ID
* @param musicDTO 音乐DTO
* @param file 音乐文件
* @param cover 封面文件
* @return 响应结果
*/
@SaCheckRole("admin")
@PutMapping("/{id}")
public Result<MusicDTO> updateMusic(
@PathVariable Long id,
@RequestPart("music") MusicDTO musicDTO,
@RequestPart(value = "file", required = false) MultipartFile file,
@RequestPart(value = "cover", required = false) MultipartFile cover,
@RequestPart(value = "lyric", required = false) MultipartFile lyric) {
MusicDTO result = musicService.updateMusic(id, musicDTO, file, cover, lyric);
return Result.success("更新成功", result);
}
/**
* 删除音乐
*
* @param id 音乐ID
* @return 响应结果
*/
@SaCheckRole("admin")
@DeleteMapping("/{id}")
public Result<Boolean> deleteMusic(@PathVariable Long id) {
boolean result = musicService.deleteMusic(id);
return Result.success("删除成功", result);
}
/**
* 收藏/取消收藏音乐
*
* @param id 音乐ID
* @return 响应结果
*/
@SaCheckLogin
@PostMapping("/{id}/collect")
public Result<Boolean> toggleCollectMusic(@PathVariable Long id) {
// 获取当前登录用户ID
Long userId = StpUtil.getLoginIdAsLong();
boolean isCollected = musicService.toggleCollectMusic(id, userId);
String message = isCollected ? "收藏成功" : "取消收藏成功";
return Result.success(message, isCollected);
}
/**
* 获取用户收藏的音乐列表
*
* @param page 页码
* @param size 每页大小
* @return 响应结果
*/
@SaCheckLogin
@GetMapping("/collect")
public Result<PageResult<MusicDTO>> getCollectedMusic(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
// 获取当前登录用户ID
Long userId = StpUtil.getLoginIdAsLong();
PageResult<MusicDTO> pageResult = musicService.getCollectedMusic(userId, page, size);
return Result.success(pageResult);
}
/**
* 播放音乐
*
* @param id 音乐ID
* @return 响应结果
*/
@PostMapping("/{id}/play")
public Result<MusicDTO> playMusic(@PathVariable Long id) {
MusicDTO musicDTO = musicService.playMusic(id);
return Result.success(musicDTO);
}
}

View File

@@ -0,0 +1,256 @@
package com.test.musichouduan.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckRole;
import com.test.musichouduan.dto.PageResult;
import com.test.musichouduan.dto.PlaylistDTO;
import com.test.musichouduan.dto.Result;
import com.test.musichouduan.service.PlaylistService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* 歌单控制器
*/
@RestController
@RequestMapping("/api/playlist")
public class PlaylistController {
@Autowired
private PlaylistService playlistService;
/**
* 根据ID获取歌单
*
* @param id 歌单ID
* @return 响应结果
*/
@GetMapping("/{id}")
public Result<PlaylistDTO> getPlaylistById(@PathVariable Long id) {
PlaylistDTO playlistDTO = playlistService.getPlaylistById(id);
return Result.success(playlistDTO);
}
/**
* 分页获取歌单列表
*
* @param page 页码
* @param size 每页大小
* @param keyword 关键字
* @param typeId 类型ID
* @return 响应结果
*/
@GetMapping("/list")
public Result<PageResult<PlaylistDTO>> getPlaylistList(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) Long typeId) {
PageResult<PlaylistDTO> pageResult = playlistService.getPlaylistList(page, size, keyword, typeId);
return Result.success(pageResult);
}
/**
* 获取最新歌单列表
*
* @param limit 限制数量
* @return 响应结果
*/
@GetMapping("/latest")
public Result<List<PlaylistDTO>> getLatestPlaylist(@RequestParam(defaultValue = "10") Integer limit) {
List<PlaylistDTO> playlistList = playlistService.getLatestPlaylist(limit);
return Result.success(playlistList);
}
/**
* 获取热门歌单列表
*
* @param limit 限制数量
* @return 响应结果
*/
@GetMapping("/hot")
public Result<List<PlaylistDTO>> getHotPlaylist(@RequestParam(defaultValue = "10") Integer limit) {
List<PlaylistDTO> playlistList = playlistService.getHotPlaylist(limit);
return Result.success(playlistList);
}
/**
* 搜索歌单
*
* @param keyword 关键字
* @param page 页码
* @param size 每页大小
* @return 响应结果
*/
@GetMapping("/search")
public Result<PageResult<PlaylistDTO>> searchPlaylist(
@RequestParam String keyword,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
PageResult<PlaylistDTO> pageResult = playlistService.getPlaylistList(page, size, keyword, null);
return Result.success(pageResult);
}
/**
* 获取官方歌单列表
*
* @param page 页码
* @param size 每页大小
* @return 响应结果
*/
@GetMapping("/official")
public Result<PageResult<PlaylistDTO>> getOfficialPlaylist(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
PageResult<PlaylistDTO> pageResult = playlistService.getOfficialPlaylist(page, size);
return Result.success(pageResult);
}
/**
* 添加歌单
*
* @param playlistDTO 歌单DTO
* @param cover 封面文件
* @return 响应结果
*/
@SaCheckLogin
@PostMapping("/add")
public Result<PlaylistDTO> addPlaylist(
@RequestPart("playlist") PlaylistDTO playlistDTO,
@RequestPart(value = "cover", required = false) MultipartFile cover) {
PlaylistDTO result = playlistService.addPlaylist(playlistDTO, cover);
return Result.success("添加成功", result);
}
/**
* 更新歌单
*
* @param id 歌单ID
* @param playlistDTO 歌单DTO
* @param cover 封面文件
* @return 响应结果
*/
@SaCheckLogin
@PutMapping("/{id}")
public Result<PlaylistDTO> updatePlaylist(
@PathVariable Long id,
@RequestPart("playlist") PlaylistDTO playlistDTO,
@RequestPart(value = "cover", required = false) MultipartFile cover) {
PlaylistDTO result = playlistService.updatePlaylist(id, playlistDTO, cover);
return Result.success("更新成功", result);
}
/**
* 删除歌单
*
* @param id 歌单ID
* @return 响应结果
*/
@SaCheckLogin
@DeleteMapping("/{id}")
public Result<Boolean> deletePlaylist(@PathVariable Long id) {
boolean result = playlistService.deletePlaylist(id);
return Result.success("删除成功", result);
}
/**
* 收藏歌单
*
* @param id 歌单ID
* @return 响应结果
*/
@SaCheckLogin
@PostMapping("/{id}/collect")
public Result<Boolean> collectPlaylist(@PathVariable Long id) {
boolean result = playlistService.collectPlaylist(id);
return Result.success("收藏成功", result);
}
/**
* 取消收藏歌单
*
* @param id 歌单ID
* @return 响应结果
*/
@SaCheckLogin
@DeleteMapping("/{id}/collect")
public Result<Boolean> uncollectPlaylist(@PathVariable Long id) {
boolean result = playlistService.uncollectPlaylist(id);
return Result.success("取消收藏成功", result);
}
/**
* 获取用户收藏的歌单列表
*
* @param page 页码
* @param size 每页大小
* @return 响应结果
*/
@SaCheckLogin
@GetMapping("/collect")
public Result<PageResult<PlaylistDTO>> getCollectedPlaylist(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
PageResult<PlaylistDTO> pageResult = playlistService.getCollectedPlaylist(page, size);
return Result.success(pageResult);
}
/**
* 获取用户创建的歌单列表
*
* @param page 页码
* @param size 每页大小
* @return 响应结果
*/
@SaCheckLogin
@GetMapping("/user")
public Result<PageResult<PlaylistDTO>> getUserPlaylist(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
PageResult<PlaylistDTO> pageResult = playlistService.getUserPlaylist(page, size);
return Result.success(pageResult);
}
/**
* 向歌单添加音乐
*
* @param id 歌单ID
* @param musicId 音乐ID
* @return 响应结果
*/
@SaCheckLogin
@PostMapping("/{id}/music/{musicId}")
public Result<Boolean> addMusicToPlaylist(@PathVariable Long id, @PathVariable Long musicId) {
boolean result = playlistService.addMusicToPlaylist(id, musicId);
return Result.success("添加成功", result);
}
/**
* 从歌单移除音乐
*
* @param id 歌单ID
* @param musicId 音乐ID
* @return 响应结果
*/
@SaCheckLogin
@DeleteMapping("/{id}/music/{musicId}")
public Result<Boolean> removeMusicFromPlaylist(@PathVariable Long id, @PathVariable Long musicId) {
boolean result = playlistService.removeMusicFromPlaylist(id, musicId);
return Result.success("移除成功", result);
}
/**
* 播放歌单
*
* @param id 歌单ID
* @return 响应结果
*/
@PostMapping("/{id}/play")
public Result<PlaylistDTO> playPlaylist(@PathVariable Long id) {
PlaylistDTO playlistDTO = playlistService.playPlaylist(id);
return Result.success(playlistDTO);
}
}

View File

@@ -0,0 +1,215 @@
package com.test.musichouduan.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckRole;
import com.test.musichouduan.dto.PageResult;
import com.test.musichouduan.dto.Result;
import com.test.musichouduan.dto.SingerDTO;
import com.test.musichouduan.service.SingerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* 歌手控制器
*/
@RestController
@RequestMapping("/api/singer")
public class SingerController {
@Autowired
private SingerService singerService;
/**
* 根据ID获取歌手
*
* @param id 歌手ID
* @return 响应结果
*/
@GetMapping("/{id}")
public Result<SingerDTO> getSingerById(@PathVariable Long id) {
SingerDTO singerDTO = singerService.getSingerById(id);
return Result.success(singerDTO);
}
/**
* 分页获取歌手列表
*
* @param page 页码
* @param size 每页大小
* @param keyword 关键字
* @param typeId 类型ID
* @return 响应结果
*/
@GetMapping("/list")
public Result<PageResult<SingerDTO>> getSingerList(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) Long typeId) {
PageResult<SingerDTO> pageResult = singerService.getSingerList(page, size, keyword, typeId);
return Result.success(pageResult);
}
/**
* 搜索歌手
*
* @param keyword 关键字
* @param page 页码
* @param size 每页大小
* @return 响应结果
*/
@GetMapping("/search")
public Result<PageResult<SingerDTO>> searchSinger(
@RequestParam String keyword,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
PageResult<SingerDTO> pageResult = singerService.getSingerList(page, size, keyword, null);
return Result.success(pageResult);
}
/**
* 获取最新歌手列表
*
* @param limit 限制数量
* @return 响应结果
*/
@GetMapping("/latest")
public Result<List<SingerDTO>> getLatestSinger(@RequestParam(defaultValue = "10") Integer limit) {
List<SingerDTO> singerList = singerService.getLatestSinger(limit);
return Result.success(singerList);
}
/**
* 获取热门歌手列表
*
* @param limit 限制数量
* @return 响应结果
*/
@GetMapping("/hot-list")
public Result<List<SingerDTO>> getHotSinger(@RequestParam(defaultValue = "10") Integer limit) {
List<SingerDTO> singerList = singerService.getHotSinger(limit);
return Result.success(singerList);
}
/**
* 添加歌手
*
* @param singerDTO 歌手DTO
* @param avatar 头像文件
* @return 响应结果
*/
@SaCheckRole("admin")
@PostMapping("/add")
public Result<SingerDTO> addSinger(
@RequestPart("singer") SingerDTO singerDTO,
@RequestPart(value = "avatar", required = false) MultipartFile avatar) {
SingerDTO result = singerService.addSinger(singerDTO, avatar);
return Result.success("添加成功", result);
}
/**
* 更新歌手
*
* @param id 歌手ID
* @param singerDTO 歌手DTO
* @param avatar 头像文件
* @return 响应结果
*/
@SaCheckRole("admin")
@PutMapping("/{id}")
public Result<SingerDTO> updateSinger(
@PathVariable Long id,
@RequestPart("singer") SingerDTO singerDTO,
@RequestPart(value = "avatar", required = false) MultipartFile avatar) {
SingerDTO result = singerService.updateSinger(id, singerDTO, avatar);
return Result.success("更新成功", result);
}
/**
* 删除歌手
*
* @param id 歌手ID
* @return 响应结果
*/
@SaCheckRole("admin")
@DeleteMapping("/{id}")
public Result<Boolean> deleteSinger(@PathVariable Long id) {
boolean result = singerService.deleteSinger(id);
return Result.success("删除成功", result);
}
/**
* 收藏歌手
*
* @param id 歌手ID
* @return 响应结果
*/
@SaCheckLogin
@PostMapping("/{id}/collect")
public Result<Boolean> collectSinger(@PathVariable Long id) {
boolean result = singerService.collectSinger(id);
return Result.success("收藏成功", result);
}
/**
* 取消收藏歌手
*
* @param id 歌手ID
* @return 响应结果
*/
@SaCheckLogin
@DeleteMapping("/{id}/collect")
public Result<Boolean> uncollectSinger(@PathVariable Long id) {
boolean result = singerService.uncollectSinger(id);
return Result.success("取消收藏成功", result);
}
/**
* 获取用户收藏的歌手列表
*
* @param page 页码
* @param size 每页大小
* @return 响应结果
*/
@SaCheckLogin
@GetMapping("/collect")
public Result<PageResult<SingerDTO>> getCollectedSinger(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
PageResult<SingerDTO> pageResult = singerService.getCollectedSinger(page, size);
return Result.success(pageResult);
}
/**
* 添加音乐到歌手
* @param singerId 歌手ID
* @param musicId 音乐ID
* @return 响应结果
*/
@SaCheckRole("admin")
@PostMapping("/{singerId}/music/{musicId}")
public Result<Boolean> addMusicToSinger(
@PathVariable Long singerId,
@PathVariable Long musicId) {
boolean result = singerService.addMusicToSinger(singerId, musicId);
return Result.success("添加成功", result);
}
/**
* 从歌手移除音乐
* @param singerId 歌手ID
* @param musicId 音乐ID
* @return 响应结果
*/
@SaCheckRole("admin")
@DeleteMapping("/{singerId}/music/{musicId}")
public Result<Boolean> removeMusicFromSinger(
@PathVariable Long singerId,
@PathVariable Long musicId) {
boolean result = singerService.removeMusicFromSinger(singerId, musicId);
return Result.success("移除成功", result);
}
}

View File

@@ -0,0 +1,94 @@
package com.test.musichouduan.controller;
import cn.dev33.satoken.annotation.SaCheckRole;
import com.test.musichouduan.dto.Result;
import com.test.musichouduan.service.StatService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* 统计控制器
*/
@RestController
@RequestMapping("/api/stat")
@SaCheckRole("admin")
public class StatController {
@Autowired
private StatService statService;
/**
* 获取总体统计数据
*
* @return 响应结果
*/
@GetMapping("/total")
public Result<Map<String, Object>> getTotalStats() {
Map<String, Object> stats = statService.getTotalStats();
return Result.success(stats);
}
/**
* 获取用户增长趋势
*
* @param days 天数
* @return 响应结果
*/
@GetMapping("/user/growth")
public Result<Map<String, Object>> getUserGrowthTrend(@RequestParam(defaultValue = "7") Integer days) {
Map<String, Object> stats = statService.getUserGrowthTrend(days);
return Result.success(stats);
}
/**
* 获取音乐类型分布
*
* @return 响应结果
*/
@GetMapping("/music/type")
public Result<Map<String, Object>> getMusicTypeDistribution() {
Map<String, Object> stats = statService.getMusicTypeDistribution();
return Result.success(stats);
}
/**
* 获取播放量统计
*
* @param days 天数
* @return 响应结果
*/
@GetMapping("/play/count")
public Result<Map<String, Object>> getPlayCountStats(@RequestParam(defaultValue = "7") Integer days) {
Map<String, Object> stats = statService.getPlayCountStats(days);
return Result.success(stats);
}
/**
* 获取最新用户列表
*
* @param limit 限制数量
* @return 响应结果
*/
@GetMapping("/user/latest")
public Result<Map<String, Object>> getLatestUsers(@RequestParam(defaultValue = "5") Integer limit) {
Map<String, Object> stats = statService.getLatestUsers(limit);
return Result.success(stats);
}
/**
* 获取最新音乐列表
*
* @param limit 限制数量
* @return 响应结果
*/
@GetMapping("/music/latest")
public Result<Map<String, Object>> getLatestMusics(@RequestParam(defaultValue = "5") Integer limit) {
Map<String, Object> stats = statService.getLatestMusics(limit);
return Result.success(stats);
}
}

View File

@@ -0,0 +1,227 @@
package com.test.musichouduan.controller;
import cn.dev33.satoken.annotation.SaCheckRole;
import com.test.musichouduan.dto.*;
import com.test.musichouduan.service.TypeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 类型控制器
*/
@RestController
@RequestMapping("/api/type")
public class TypeController {
@Autowired
private TypeService typeService;
/**
* 获取音乐类型列表
*
* @return 响应结果
*/
@GetMapping("/music")
public Result<List<MusicTypeDTO>> getMusicTypeList() {
List<MusicTypeDTO> typeList = typeService.getMusicTypeList();
return Result.success(typeList);
}
/**
* 分页获取音乐类型列表
*
* @param page 页码
* @param size 每页大小
* @param keyword 关键字
* @return 响应结果
*/
@SaCheckRole("admin")
@GetMapping("/music/page")
public Result<PageResult<MusicTypeDTO>> getMusicTypePage(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String keyword) {
PageResult<MusicTypeDTO> pageResult = typeService.getMusicTypePage(page, size, keyword);
return Result.success(pageResult);
}
/**
* 添加音乐类型
*
* @param musicTypeDTO 音乐类型DTO
* @return 响应结果
*/
@SaCheckRole("admin")
@PostMapping("/music/add")
public Result<MusicTypeDTO> addMusicType(@RequestBody MusicTypeDTO musicTypeDTO) {
MusicTypeDTO result = typeService.addMusicType(musicTypeDTO);
return Result.success("添加成功", result);
}
/**
* 更新音乐类型
*
* @param id 音乐类型ID
* @param musicTypeDTO 音乐类型DTO
* @return 响应结果
*/
@SaCheckRole("admin")
@PutMapping("/music/{id}")
public Result<MusicTypeDTO> updateMusicType(@PathVariable Long id, @RequestBody MusicTypeDTO musicTypeDTO) {
MusicTypeDTO result = typeService.updateMusicType(id, musicTypeDTO);
return Result.success("更新成功", result);
}
/**
* 删除音乐类型
*
* @param id 音乐类型ID
* @return 响应结果
*/
@SaCheckRole("admin")
@DeleteMapping("/music/{id}")
public Result<Boolean> deleteMusicType(@PathVariable Long id) {
boolean result = typeService.deleteMusicType(id);
return Result.success("删除成功", result);
}
/**
* 获取歌手类型列表
*
* @return 响应结果
*/
@GetMapping("/singer")
public Result<List<SingerTypeDTO>> getSingerTypeList() {
List<SingerTypeDTO> typeList = typeService.getSingerTypeList();
return Result.success(typeList);
}
/**
* 分页获取歌手类型列表
*
* @param page 页码
* @param size 每页大小
* @param keyword 关键字
* @return 响应结果
*/
@SaCheckRole("admin")
@GetMapping("/singer/page")
public Result<PageResult<SingerTypeDTO>> getSingerTypePage(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String keyword) {
PageResult<SingerTypeDTO> pageResult = typeService.getSingerTypePage(page, size, keyword);
return Result.success(pageResult);
}
/**
* 添加歌手类型
*
* @param singerTypeDTO 歌手类型DTO
* @return 响应结果
*/
@SaCheckRole("admin")
@PostMapping("/singer/add")
public Result<SingerTypeDTO> addSingerType(@RequestBody SingerTypeDTO singerTypeDTO) {
SingerTypeDTO result = typeService.addSingerType(singerTypeDTO);
return Result.success("添加成功", result);
}
/**
* 更新歌手类型
*
* @param id 歌手类型ID
* @param singerTypeDTO 歌手类型DTO
* @return 响应结果
*/
@SaCheckRole("admin")
@PutMapping("/singer/{id}")
public Result<SingerTypeDTO> updateSingerType(@PathVariable Long id, @RequestBody SingerTypeDTO singerTypeDTO) {
SingerTypeDTO result = typeService.updateSingerType(id, singerTypeDTO);
return Result.success("更新成功", result);
}
/**
* 删除歌手类型
*
* @param id 歌手类型ID
* @return 响应结果
*/
@SaCheckRole("admin")
@DeleteMapping("/singer/{id}")
public Result<Boolean> deleteSingerType(@PathVariable Long id) {
boolean result = typeService.deleteSingerType(id);
return Result.success("删除成功", result);
}
/**
* 获取歌单类型列表
*
* @return 响应结果
*/
@GetMapping("/playlist")
public Result<List<PlaylistTypeDTO>> getPlaylistTypeList() {
List<PlaylistTypeDTO> typeList = typeService.getPlaylistTypeList();
return Result.success(typeList);
}
/**
* 分页获取歌单类型列表
*
* @param page 页码
* @param size 每页大小
* @param keyword 关键字
* @return 响应结果
*/
@SaCheckRole("admin")
@GetMapping("/playlist/page")
public Result<PageResult<PlaylistTypeDTO>> getPlaylistTypePage(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String keyword) {
PageResult<PlaylistTypeDTO> pageResult = typeService.getPlaylistTypePage(page, size, keyword);
return Result.success(pageResult);
}
/**
* 添加歌单类型
*
* @param playlistTypeDTO 歌单类型DTO
* @return 响应结果
*/
@SaCheckRole("admin")
@PostMapping("/playlist/add")
public Result<PlaylistTypeDTO> addPlaylistType(@RequestBody PlaylistTypeDTO playlistTypeDTO) {
PlaylistTypeDTO result = typeService.addPlaylistType(playlistTypeDTO);
return Result.success("添加成功", result);
}
/**
* 更新歌单类型
*
* @param id 歌单类型ID
* @param playlistTypeDTO 歌单类型DTO
* @return 响应结果
*/
@SaCheckRole("admin")
@PutMapping("/playlist/{id}")
public Result<PlaylistTypeDTO> updatePlaylistType(@PathVariable Long id, @RequestBody PlaylistTypeDTO playlistTypeDTO) {
PlaylistTypeDTO result = typeService.updatePlaylistType(id, playlistTypeDTO);
return Result.success("更新成功", result);
}
/**
* 删除歌单类型
*
* @param id 歌单类型ID
* @return 响应结果
*/
@SaCheckRole("admin")
@DeleteMapping("/playlist/{id}")
public Result<Boolean> deletePlaylistType(@PathVariable Long id) {
boolean result = typeService.deletePlaylistType(id);
return Result.success("删除成功", result);
}
}

View File

@@ -0,0 +1,227 @@
package com.test.musichouduan.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.stp.StpUtil;
import com.test.musichouduan.dto.*;
import com.test.musichouduan.service.FileService;
import com.test.musichouduan.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
/**
* 用户控制器
*/
@Tag(name = "用户接口", description = "用户的注册、登录、信息管理等操作")
@RestController
@RequestMapping("/api/user")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private FileService fileService;
/**
* 用户注册
*
* @param request 注册请求
* @return 响应结果
*/
@Operation(summary = "用户注册", description = "新用户注册接口")
@PostMapping("/register")
public Result<UserDTO> register(@Parameter(description = "注册信息") @Validated @RequestBody RegisterRequest request) {
UserDTO userDTO = userService.register(request);
return Result.success("注册成功", userDTO);
}
/**
* 用户登录
*
* @param request 登录请求
* @return 响应结果
*/
@Operation(summary = "用户登录", description = "用户登录接口,登录成功后返回用户信息和令牌")
@PostMapping("/login")
public Result<LoginResultDTO> login(@Parameter(description = "登录信息") @Validated @RequestBody LoginRequest request) {
LoginResultDTO resultDTO = userService.login(request);
return Result.success("登录成功", resultDTO);
}
/**
* 用户登出
*
* @return 响应结果
*/
@Operation(summary = "用户登出", description = "用户登出接口,需要登录状态")
@SaCheckLogin
@PostMapping("/logout")
public Result<String> logout() {
StpUtil.logout();
return Result.success("登出成功", null);
}
/**
* 获取当前登录用户
*
* @return 响应结果
*/
@Operation(summary = "获取当前登录用户", description = "获取当前登录用户的信息,需要登录状态")
@SaCheckLogin
@GetMapping("/current")
public Result<UserDTO> getCurrentUser() {
UserDTO userDTO = userService.getCurrentUser();
return Result.success(userDTO);
}
/**
* 更新用户信息
*
* @param request 用户信息更新请求
* @return 响应结果
*/
@Operation(summary = "更新用户信息", description = "更新当前登录用户的信息,需要登录状态")
@SaCheckLogin
@PutMapping("/update")
public Result<UserDTO> updateUser(@Parameter(description = "用户信息更新请求") @Validated @RequestBody UserUpdateRequest request) {
UserDTO userDTO = userService.updateUser(request);
return Result.success("更新成功", userDTO);
}
/**
* 修改密码
*
* @param request 修改密码请求
* @return 响应结果
*/
@Operation(summary = "修改密码", description = "修改当前登录用户的密码,需要登录状态")
@SaCheckLogin
@PutMapping("/password")
public Result<Boolean> updatePassword(@Parameter(description = "密码修改请求") @Validated @RequestBody PasswordUpdateRequest request) {
boolean result = userService.updatePassword(request);
return Result.success("修改成功", result);
}
/**
* 获取用户统计数据
*
* @return 响应结果
*/
@Operation(summary = "获取用户统计数据", description = "获取当前登录用户的统计数据,包括收藏数量等,需要登录状态")
@SaCheckLogin
@GetMapping("/stats")
public Result<UserDTO> getUserStats() {
UserDTO userDTO = userService.getUserStats(StpUtil.getLoginIdAsLong());
return Result.success(userDTO);
}
/**
* 分页获取用户列表
*
* @param page 页码
* @param size 每页大小
* @param keyword 关键字
* @return 响应结果
*/
@Operation(summary = "分页获取用户列表", description = "分页获取用户列表,支持关键字搜索,需要管理员权限")
@SaCheckRole("admin")
@GetMapping("/list")
public Result<PageResult<UserDTO>> getUserList(
@Parameter(description = "页码", example = "1") @RequestParam(defaultValue = "1") Integer page,
@Parameter(description = "每页大小", example = "10") @RequestParam(defaultValue = "10") Integer size,
@Parameter(description = "搜索关键字", example = "用户名") @RequestParam(required = false) String keyword) {
PageResult<UserDTO> pageResult = userService.getUserList(page, size, keyword);
return Result.success(pageResult);
}
/**
* 根据ID获取用户
*
* @param id 用户ID
* @return 响应结果
*/
@Operation(summary = "根据ID获取用户", description = "根据用户ID获取用户信息需要管理员权限")
@SaCheckRole("admin")
@GetMapping("/{id}")
public Result<UserDTO> getUserById(@Parameter(description = "用户ID", example = "1") @PathVariable Long id) {
UserDTO userDTO = userService.getUserById(id);
return Result.success(userDTO);
}
/**
* 删除用户
*
* @param id 用户ID
* @return 响应结果
*/
@Operation(summary = "删除用户", description = "根据用户ID删除用户需要管理员权限")
@SaCheckRole("admin")
@DeleteMapping("/{id}")
public Result<Boolean> deleteUser(@Parameter(description = "用户ID", example = "1") @PathVariable Long id) {
boolean result = userService.deleteUser(id);
return Result.success("删除成功", result);
}
/**
* 更新用户状态
*
* @param id 用户ID
* @param status 状态
* @return 响应结果
*/
@Operation(summary = "更新用户状态", description = "更新用户状态0-禁用1-启用,需要管理员权限")
@SaCheckRole("admin")
@PutMapping("/{id}/status")
public Result<Boolean> updateUserStatus(
@Parameter(description = "用户ID", example = "1") @PathVariable Long id,
@Parameter(description = "状态0-禁用1-启用", example = "1") @RequestParam Integer status) {
boolean result = userService.updateUserStatus(id, status);
return Result.success("更新成功", result);
}
/**
* 管理员更新用户信息
*
* @param id 用户ID
* @param request 用户信息更新请求
* @return 响应结果
*/
@Operation(summary = "管理员更新用户信息", description = "管理员更新用户信息,需要管理员权限")
@SaCheckRole("admin")
@PutMapping("/{id}")
public Result<UserDTO> adminUpdateUser(
@Parameter(description = "用户ID", example = "1") @PathVariable Long id,
@Parameter(description = "用户信息更新请求") @Validated @RequestBody UserUpdateRequest request) {
UserDTO userDTO = userService.adminUpdateUser(id, request);
return Result.success("更新成功", userDTO);
}
/**
* 上传用户头像
*
* @param file 头像文件
* @return 响应结果
*/
@Operation(summary = "上传用户头像", description = "上传当前登录用户的头像,需要登录状态")
@SaCheckLogin
@PostMapping("/upload/avatar")
public Result<String> uploadAvatar(@Parameter(description = "头像文件") @RequestParam("file") MultipartFile file) {
// 上传头像
String avatarUrl = fileService.uploadImage(file);
// 用户ID已经通过 SaCheckLogin 注解验证
// 更新用户头像
UserUpdateRequest request = new UserUpdateRequest();
request.setAvatar(avatarUrl);
userService.updateUser(request);
return Result.success("上传成功", avatarUrl);
}
}

View File

@@ -0,0 +1,76 @@
package com.test.musichouduan.controller.admin;
import cn.dev33.satoken.annotation.SaCheckRole;
import com.test.musichouduan.dto.CommentDTO;
import com.test.musichouduan.dto.PageResult;
import com.test.musichouduan.dto.Result;
import com.test.musichouduan.service.CommentService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* 管理员评论控制器
*/
@Tag(name = "管理员评论接口", description = "提供管理员评论管理功能")
@RestController
@RequestMapping("/api/admin/comment")
@SaCheckRole("admin")
public class AdminCommentController {
@Autowired
private CommentService commentService;
/**
* 分页获取所有评论
*
* @param page 页码
* @param size 每页大小
* @param keyword 关键字
* @param targetType 目标类型
* @param status 状态
* @return 响应结果
*/
@Operation(summary = "分页获取所有评论", description = "管理员分页获取所有评论")
@GetMapping("/list")
public Result<PageResult<CommentDTO>> getCommentList(
@Parameter(description = "页码", example = "1") @RequestParam(defaultValue = "1") Integer page,
@Parameter(description = "每页大小", example = "10") @RequestParam(defaultValue = "10") Integer size,
@Parameter(description = "关键字") @RequestParam(required = false) String keyword,
@Parameter(description = "目标类型1-音乐2-歌单3-歌手") @RequestParam(required = false) Integer targetType,
@Parameter(description = "状态0-禁用1-正常") @RequestParam(required = false) Integer status) {
PageResult<CommentDTO> pageResult = commentService.getAdminCommentList(page, size, keyword, targetType, status);
return Result.success(pageResult);
}
/**
* 更新评论状态
*
* @param id 评论ID
* @param status 状态0-禁用1-正常
* @return 响应结果
*/
@Operation(summary = "更新评论状态", description = "管理员更新评论状态")
@PutMapping("/{id}/status")
public Result<Boolean> updateCommentStatus(
@Parameter(description = "评论ID") @PathVariable Long id,
@Parameter(description = "状态0-禁用1-正常", example = "1") @RequestParam Integer status) {
boolean result = commentService.updateCommentStatus(id, status);
return Result.success("更新成功", result);
}
/**
* 删除评论
*
* @param id 评论ID
* @return 响应结果
*/
@Operation(summary = "删除评论", description = "管理员删除评论")
@DeleteMapping("/{id}")
public Result<Boolean> deleteComment(@Parameter(description = "评论ID") @PathVariable Long id) {
boolean result = commentService.adminDeleteComment(id);
return Result.success("删除成功", result);
}
}

View File

@@ -0,0 +1,240 @@
package com.test.musichouduan.controller.admin;
import cn.dev33.satoken.annotation.SaCheckRole;
import com.test.musichouduan.dto.Result;
import com.test.musichouduan.dto.admin.DashboardDataDTO;
import com.test.musichouduan.dto.admin.MusicAdminDTO;
import com.test.musichouduan.dto.admin.StatItemDTO;
import com.test.musichouduan.dto.admin.UserAdminDTO;
import com.test.musichouduan.entity.Music;
import com.test.musichouduan.entity.PlaylistType;
import com.test.musichouduan.entity.User;
import com.test.musichouduan.repository.MusicRepository;
import com.test.musichouduan.repository.PlayHistoryRepository;
import com.test.musichouduan.repository.PlaylistRepository;
import com.test.musichouduan.repository.SingerRepository;
import com.test.musichouduan.repository.UserRepository;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* 后台管理控制台控制器
*/
@Tag(name = "后台管理控制台接口", description = "提供后台管理控制台所需的统计数据")
@RestController
@RequestMapping("/api/admin/dashboard")
@SaCheckRole("admin")
public class AdminDashboardController {
@Autowired
private UserRepository userRepository;
@Autowired
private MusicRepository musicRepository;
@Autowired
private PlaylistRepository playlistRepository;
@Autowired
private SingerRepository singerRepository;
@Autowired
private PlayHistoryRepository playHistoryRepository;
@PersistenceContext
private EntityManager entityManager;
/**
* 获取控制台统计数据
*
* @return 统计数据
*/
@Operation(summary = "获取控制台统计数据", description = "获取用户数量、音乐数量、歌单数量、歌手数量等统计数据")
@GetMapping("/stats")
public Result<DashboardDataDTO> getStats() {
try {
DashboardDataDTO data = new DashboardDataDTO();
// 统计基础数据
long userCount = userRepository.count();
long musicCount = musicRepository.count();
long playlistCount = playlistRepository.count();
long singerCount = singerRepository.count();
long playCount = musicRepository.sumPlayCount();
data.setUserCount(userCount);
data.setMusicCount(musicCount);
data.setPlaylistCount(playlistCount);
data.setSingerCount(singerCount);
data.setPlayCount(playCount);
// 获取最近7天的用户增长数据
data.setUserGrowth(getUserGrowthData());
// 获取音乐类型分布数据
data.setMusicTypeDistribution(getTypeDistribution());
// 获取最近7天的播放量数据
data.setPlayCountData(getPlayCountData());
// 获取最新用户
List<User> users = userRepository.findLatestUsers(PageRequest.of(0, 5));
List<UserAdminDTO> latestUsers = users.stream()
.map(UserAdminDTO::fromEntity)
.collect(Collectors.toList());
data.setLatestUsers(latestUsers);
// 获取最新音乐
List<Music> musics = musicRepository.findLatest(PageRequest.of(0, 5));
List<MusicAdminDTO> latestMusics = musics.stream()
.map(MusicAdminDTO::fromEntity)
.collect(Collectors.toList());
data.setLatestMusics(latestMusics);
return Result.success(data);
} catch (Exception e) {
e.printStackTrace();
return Result.error("获取控制台统计数据失败");
}
}
/**
* 获取最近7天的用户增长数据
*
* @return 用户增长数据
*/
private List<StatItemDTO> getUserGrowthData() {
List<StatItemDTO> result = new ArrayList<>();
LocalDate today = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM-dd");
// 获取最近7天的日期
for (int i = 6; i >= 0; i--) {
LocalDate date = today.minusDays(i);
LocalDateTime startOfDay = date.atStartOfDay();
LocalDateTime endOfDay = date.plusDays(1).atStartOfDay().minusNanos(1);
// 统计当天注册的用户数量
long count = userRepository.countByCreateTimeBetween(startOfDay, endOfDay);
StatItemDTO item = new StatItemDTO();
item.setName(date.format(formatter));
item.setValue(count);
result.add(item);
}
return result;
}
/**
* 获取音乐、歌单、歌手类型分布数据
*
* @return 类型分布数据
*/
private List<StatItemDTO> getTypeDistribution() {
List<StatItemDTO> result = new ArrayList<>();
// 音乐类型分布
StatItemDTO musicTypeStat = new StatItemDTO();
musicTypeStat.setName("音乐类型");
try {
List<Object[]> musicTypeData = musicRepository.countByMusicType();
List<StatItemDTO> musicTypeChildren = musicTypeData.stream().map(item -> {
StatItemDTO child = new StatItemDTO();
child.setName((String) item[0]);
child.setValue(((Number) item[1]).longValue());
return child;
}).collect(Collectors.toList());
musicTypeStat.setChildren(musicTypeChildren);
} catch (Exception e) {
e.printStackTrace();
// 如果查询失败,返回空列表
musicTypeStat.setChildren(new ArrayList<>());
}
result.add(musicTypeStat);
// 歌单类型分布
StatItemDTO playlistTypeStat = new StatItemDTO();
playlistTypeStat.setName("歌单类型");
// 使用原生 SQL 查询或者手动聚合数据
List<PlaylistType> playlistTypes = entityManager.createQuery("SELECT DISTINCT pt FROM PlaylistType pt", PlaylistType.class).getResultList();
List<StatItemDTO> playlistTypeChildren = new ArrayList<>();
for (PlaylistType type : playlistTypes) {
Long count = playlistRepository.countByType(type);
StatItemDTO child = new StatItemDTO();
child.setName(type.getName());
child.setValue(count);
playlistTypeChildren.add(child);
}
playlistTypeStat.setChildren(playlistTypeChildren);
result.add(playlistTypeStat);
// 歌手类型分布
StatItemDTO singerTypeStat = new StatItemDTO();
singerTypeStat.setName("歌手类型");
// 使用原生 SQL 查询或者手动聚合数据
List<com.test.musichouduan.entity.SingerType> singerTypes = entityManager.createQuery("SELECT DISTINCT st FROM SingerType st", com.test.musichouduan.entity.SingerType.class).getResultList();
List<StatItemDTO> singerTypeChildren = new ArrayList<>();
for (com.test.musichouduan.entity.SingerType type : singerTypes) {
Long count = singerRepository.countByType(type);
StatItemDTO child = new StatItemDTO();
child.setName(type.getName());
child.setValue(count);
singerTypeChildren.add(child);
}
singerTypeStat.setChildren(singerTypeChildren);
result.add(singerTypeStat);
return result;
}
/**
* 获取最近7天的播放量数据
*
* @return 播放量数据
*/
private List<StatItemDTO> getPlayCountData() {
List<StatItemDTO> result = new ArrayList<>();
LocalDate today = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM-dd");
// 获取最近7天的日期
for (int i = 6; i >= 0; i--) {
LocalDate date = today.minusDays(i);
LocalDateTime startOfDay = date.atStartOfDay();
LocalDateTime endOfDay = date.plusDays(1).atStartOfDay().minusNanos(1);
// 统计当天的播放量
long count = playHistoryRepository.countByPlayTimeBetween(startOfDay, endOfDay);
StatItemDTO item = new StatItemDTO();
item.setName(date.format(formatter));
item.setValue(count);
result.add(item);
}
return result;
}
}

View File

@@ -0,0 +1,57 @@
package com.test.musichouduan.dto;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 轮播图数据传输对象
*/
@Data
public class BannerDTO {
/**
* 轮播图ID
*/
private Long id;
/**
* 标题
*/
private String title;
/**
* 图片URL
*/
private String imageUrl;
/**
* 链接URL
*/
private String linkUrl;
/**
* 目标ID
*/
private Long targetId;
/**
* 目标类型1-音乐2-歌单3-歌手
*/
private Integer targetType;
/**
* 排序号
*/
private Integer orderNum;
/**
* 状态0-禁用1-正常
*/
private Integer status;
/**
* 创建时间
*/
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,53 @@
package com.test.musichouduan.dto;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
/**
* 评论数据传输对象
*/
@Data
public class CommentDTO {
/**
* 评论ID
*/
private Long id;
/**
* 评论内容
*/
private String content;
/**
* 评论用户
*/
private UserDTO user;
/**
* 评论目标ID
*/
private Long targetId;
/**
* 评论目标类型1-音乐2-歌单3-歌手
*/
private Integer targetType;
/**
* 父评论ID
*/
private Long parentId;
/**
* 子评论列表
*/
private List<CommentDTO> children;
/**
* 创建时间
*/
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,23 @@
package com.test.musichouduan.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
* 登录请求
*/
@Data
public class LoginRequest {
/**
* 用户名
*/
@NotBlank(message = "用户名不能为空")
private String username;
/**
* 密码
*/
@NotBlank(message = "密码不能为空")
private String password;
}

View File

@@ -0,0 +1,30 @@
package com.test.musichouduan.dto;
import lombok.Data;
/**
* 登录结果数据传输对象
*/
@Data
public class LoginResultDTO {
/**
* 用户信息
*/
private UserDTO user;
/**
* 访问令牌
*/
private String token;
/**
* 令牌名称(用于请求头)
*/
private String tokenName;
/**
* 令牌头前缀
*/
private String tokenPrefix;
}

View File

@@ -0,0 +1,110 @@
package com.test.musichouduan.dto;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
/**
* 音乐数据传输对象
*/
@Data
public class MusicDTO {
/**
* 音乐ID
*/
private Long id;
/**
* 音乐名称
*/
private String name;
/**
* 封面URL
*/
private String cover;
/**
* 音乐文件URL
*/
private String url;
/**
* 歌词
*/
private String lyric;
/**
* 时长(秒)
*/
private Integer duration;
/**
* 专辑
*/
private String album;
/**
* 播放次数
*/
private Long playCount;
/**
* 歌手列表
*/
private List<SingerDTO> singers;
/**
* 歌手ID列表
*/
private List<Long> singerIds;
/**
* 歌手名称列表
*/
private List<String> singerNames;
/**
* 歌手名称(字符串)
*/
private String singer;
/**
* 音乐类型列表
*/
private List<MusicTypeDTO> types;
/**
* 音乐类型ID列表
*/
private List<Long> typeIds;
/**
* 音乐类型名称列表
*/
private List<String> typeNames;
/**
* 是否已收藏
*/
private Boolean isCollected;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 收藏时间
*/
private LocalDateTime collectTime;
/**
* 设置是否已收藏
*/
public void setCollected(boolean collected) {
this.isCollected = collected;
}
}

View File

@@ -0,0 +1,25 @@
package com.test.musichouduan.dto;
import lombok.Data;
/**
* 音乐类型数据传输对象
*/
@Data
public class MusicTypeDTO {
/**
* 类型ID
*/
private Long id;
/**
* 类型名称
*/
private String name;
/**
* 类型描述
*/
private String description;
}

View File

@@ -0,0 +1,55 @@
package com.test.musichouduan.dto;
import lombok.Data;
import java.util.List;
/**
* 分页结果
*
* @param <T> 数据类型
*/
@Data
public class PageResult<T> {
/**
* 数据列表
*/
private List<T> list;
/**
* 总数
*/
private Long total;
/**
* 当前页
*/
private Integer page;
/**
* 每页大小
*/
private Integer size;
/**
* 总页数
*/
private Integer totalPages;
/**
* 构造方法
*
* @param list 数据列表
* @param total 总数
* @param page 当前页
* @param size 每页大小
*/
public PageResult(List<T> list, Long total, Integer page, Integer size) {
this.list = list;
this.total = total;
this.page = page;
this.size = size;
this.totalPages = (int) Math.ceil((double) total / size);
}
}

View File

@@ -0,0 +1,31 @@
package com.test.musichouduan.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
/**
* 修改密码请求
*/
@Data
public class PasswordUpdateRequest {
/**
* 旧密码
*/
@NotBlank(message = "旧密码不能为空")
private String oldPassword;
/**
* 新密码
*/
@NotBlank(message = "新密码不能为空")
@Size(min = 6, max = 20, message = "密码长度应为6-20个字符")
private String newPassword;
/**
* 确认新密码
*/
@NotBlank(message = "确认新密码不能为空")
private String confirmPassword;
}

View File

@@ -0,0 +1,73 @@
package com.test.musichouduan.dto;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
/**
* 歌单数据传输对象
*/
@Data
public class PlaylistDTO {
/**
* 歌单ID
*/
private Long id;
/**
* 歌单名称
*/
private String name;
/**
* 封面URL
*/
private String cover;
/**
* 歌单描述
*/
private String description;
/**
* 创建者
*/
private UserDTO creator;
/**
* 歌单类型
*/
private PlaylistTypeDTO type;
/**
* 是否官方歌单0-否1-是
*/
private Integer isOfficial;
/**
* 播放次数
*/
private Long playCount;
/**
* 歌单中的音乐
*/
private List<MusicDTO> musics;
/**
* 音乐数量
*/
private Integer musicCount;
/**
* 是否已收藏
*/
private Boolean isCollected;
/**
* 创建时间
*/
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,25 @@
package com.test.musichouduan.dto;
import lombok.Data;
/**
* 歌单类型数据传输对象
*/
@Data
public class PlaylistTypeDTO {
/**
* 类型ID
*/
private Long id;
/**
* 类型名称
*/
private String name;
/**
* 类型描述
*/
private String description;
}

View File

@@ -0,0 +1,45 @@
package com.test.musichouduan.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
/**
* 注册请求
*/
@Data
public class RegisterRequest {
/**
* 用户名
*/
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度应为3-20个字符")
private String username;
/**
* 密码
*/
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 20, message = "密码长度应为6-20个字符")
private String password;
/**
* 确认密码
*/
@NotBlank(message = "确认密码不能为空")
private String confirmPassword;
/**
* 邮箱
*/
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
/**
* 昵称
*/
private String nickname;
}

View File

@@ -0,0 +1,84 @@
package com.test.musichouduan.dto;
import lombok.Data;
/**
* 通用响应结果
*
* @param <T> 数据类型
*/
@Data
public class Result<T> {
/**
* 状态码
*/
private Integer code;
/**
* 消息
*/
private String message;
/**
* 数据
*/
private T data;
/**
* 成功
*
* @param data 数据
* @param <T> 数据类型
* @return 响应结果
*/
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage("success");
result.setData(data);
return result;
}
/**
* 成功
*
* @param message 消息
* @param data 数据
* @param <T> 数据类型
* @return 响应结果
*/
public static <T> Result<T> success(String message, T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage(message);
result.setData(data);
return result;
}
/**
* 失败
*
* @param code 状态码
* @param message 消息
* @param <T> 数据类型
* @return 响应结果
*/
public static <T> Result<T> error(Integer code, String message) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMessage(message);
return result;
}
/**
* 失败
*
* @param message 消息
* @param <T> 数据类型
* @return 响应结果
*/
public static <T> Result<T> error(String message) {
return error(500, message);
}
}

View File

@@ -0,0 +1,63 @@
package com.test.musichouduan.dto;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
/**
* 歌手数据传输对象
*/
@Data
public class SingerDTO {
/**
* 歌手ID
*/
private Long id;
/**
* 歌手名称
*/
private String name;
/**
* 歌手头像URL
*/
private String avatar;
/**
* 歌手简介
*/
private String introduction;
/**
* 歌手类型
*/
private SingerTypeDTO type;
/**
* 音乐数量
*/
private Long musicCount;
/**
* 粉丝数量
*/
private Long fansCount;
/**
* 是否已收藏
*/
private Boolean isCollected;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 关联音乐列表
*/
private List<MusicDTO> musics;
}

View File

@@ -0,0 +1,25 @@
package com.test.musichouduan.dto;
import lombok.Data;
/**
* 歌手类型数据传输对象
*/
@Data
public class SingerTypeDTO {
/**
* 类型ID
*/
private Long id;
/**
* 类型名称
*/
private String name;
/**
* 类型描述
*/
private String description;
}

View File

@@ -0,0 +1,87 @@
package com.test.musichouduan.dto;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 用户数据传输对象
*/
@Data
public class UserDTO {
/**
* 用户ID
*/
private Long id;
/**
* 用户名
*/
private String username;
/**
* 昵称
*/
private String nickname;
/**
* 头像URL
*/
private String avatar;
/**
* 性别0-未知1-男2-女
*/
private Integer gender;
/**
* 邮箱
*/
private String email;
/**
* 手机号
*/
private String phone;
/**
* 个人简介
*/
private String introduction;
/**
* 角色0-普通用户1-管理员
*/
private Integer role;
/**
* 状态0-禁用1-正常
*/
private Integer status;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 收藏音乐数量
*/
private Long collectMusicCount;
/**
* 收藏歌单数量
*/
private Long collectPlaylistCount;
/**
* 收藏歌手数量
*/
private Long collectSingerCount;
/**
* 创建歌单数量
*/
private Long createPlaylistCount;
}

View File

@@ -0,0 +1,46 @@
package com.test.musichouduan.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Size;
import lombok.Data;
/**
* 用户信息更新请求
*/
@Data
public class UserUpdateRequest {
/**
* 昵称
*/
@Size(max = 50, message = "昵称长度不能超过50个字符")
private String nickname;
/**
* 头像URL
*/
private String avatar;
/**
* 性别0-未知1-男2-女
*/
private Integer gender;
/**
* 邮箱
*/
@Email(message = "邮箱格式不正确")
private String email;
/**
* 手机号
*/
@Size(max = 20, message = "手机号长度不能超过20个字符")
private String phone;
/**
* 个人简介
*/
@Size(max = 500, message = "个人简介长度不能超过500个字符")
private String introduction;
}

View File

@@ -0,0 +1,63 @@
package com.test.musichouduan.dto.admin;
// 不需要导入同包下的类
import lombok.Data;
import java.util.List;
/**
* 控制台数据传输对象
*/
@Data
public class DashboardDataDTO {
/**
* 用户数量
*/
private long userCount;
/**
* 音乐数量
*/
private long musicCount;
/**
* 歌单数量
*/
private long playlistCount;
/**
* 歌手数量
*/
private long singerCount;
/**
* 总播放量
*/
private long playCount;
/**
* 用户增长数据
*/
private List<StatItemDTO> userGrowth;
/**
* 音乐类型分布数据
*/
private List<StatItemDTO> musicTypeDistribution;
/**
* 播放量数据
*/
private List<StatItemDTO> playCountData;
/**
* 最新用户
*/
private List<UserAdminDTO> latestUsers;
/**
* 最新音乐
*/
private List<MusicAdminDTO> latestMusics;
}

View File

@@ -0,0 +1,69 @@
package com.test.musichouduan.dto.admin;
import com.test.musichouduan.entity.Music;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 音乐管理数据传输对象
*/
@Data
public class MusicAdminDTO {
/**
* ID
*/
private Long id;
/**
* 音乐名称
*/
private String name;
/**
* 歌手名称
*/
private String singerName;
/**
* 封面URL
*/
private String coverUrl;
/**
* 播放量
*/
private Long playCount;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 将实体转换为DTO
*
* @param music 音乐实体
* @return 音乐DTO
*/
public static MusicAdminDTO fromEntity(Music music) {
if (music == null) {
return null;
}
MusicAdminDTO dto = new MusicAdminDTO();
dto.setId(music.getId());
dto.setName(music.getName());
// 获取歌手名称
String singerName = null;
if (music.getSingers() != null && !music.getSingers().isEmpty()) {
singerName = music.getSingers().iterator().next().getName();
}
dto.setSingerName(singerName);
dto.setCoverUrl(music.getCover());
dto.setPlayCount(music.getPlayCount());
dto.setCreateTime(music.getCreateTime());
return dto;
}
}

View File

@@ -0,0 +1,26 @@
package com.test.musichouduan.dto.admin;
import lombok.Data;
import java.util.List;
/**
* 统计项数据传输对象
*/
@Data
public class StatItemDTO {
/**
* 名称
*/
private String name;
/**
* 值
*/
private long value;
/**
* 子项列表
*/
private List<StatItemDTO> children;
}

View File

@@ -0,0 +1,88 @@
package com.test.musichouduan.dto.admin;
import com.test.musichouduan.entity.User;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 用户管理数据传输对象
*/
@Data
public class UserAdminDTO {
/**
* ID
*/
private Long id;
/**
* 用户名
*/
private String username;
/**
* 昵称
*/
private String nickname;
/**
* 头像URL
*/
private String avatar;
/**
* 性别0-未知1-男2-女
*/
private Integer gender;
/**
* 邮箱
*/
private String email;
/**
* 手机号
*/
private String phone;
/**
* 角色0-普通用户1-管理员
*/
private Integer role;
/**
* 状态0-禁用1-正常
*/
private Integer status;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 将实体转换为DTO
*
* @param user 用户实体
* @return 用户DTO
*/
public static UserAdminDTO fromEntity(User user) {
if (user == null) {
return null;
}
UserAdminDTO dto = new UserAdminDTO();
dto.setId(user.getId());
dto.setUsername(user.getUsername());
dto.setNickname(user.getNickname());
dto.setAvatar(user.getAvatar());
dto.setGender(user.getGender());
dto.setEmail(user.getEmail());
dto.setPhone(user.getPhone());
dto.setRole(user.getRole());
dto.setStatus(user.getStatus());
dto.setCreateTime(user.getCreateTime());
return dto;
}
}

View File

@@ -0,0 +1,77 @@
package com.test.musichouduan.entity;
import jakarta.persistence.*;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
/**
* 轮播图实体类
*/
@Data
@Entity
@Table(name = "banner")
public class Banner {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 标题
*/
@Column(length = 100)
private String title;
/**
* 图片URL
*/
@Column(nullable = false, length = 255)
private String imageUrl;
/**
* 链接URL
*/
@Column(length = 255)
private String linkUrl;
/**
* 目标ID
*/
@Column
private Long targetId;
/**
* 目标类型1-音乐2-歌单3-歌手
*/
@Column
private Integer targetType;
/**
* 排序号
*/
@Column
private Integer orderNum;
/**
* 状态0-禁用1-正常
*/
@Column
private Integer status;
/**
* 创建时间
*/
@CreationTimestamp
@Column(nullable = false, updatable = false)
private LocalDateTime createTime;
/**
* 更新时间
*/
@UpdateTimestamp
@Column(nullable = false)
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,72 @@
package com.test.musichouduan.entity;
import jakarta.persistence.*;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
/**
* 评论实体类
*/
@Data
@Entity
@Table(name = "comment")
public class Comment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 评论内容
*/
@Column(nullable = false, length = 500)
private String content;
/**
* 评论用户
*/
@ManyToOne
@JoinColumn(name = "user_id", nullable = false)
private User user;
/**
* 评论目标ID
*/
@Column(nullable = false)
private Long targetId;
/**
* 评论目标类型1-音乐2-歌单3-歌手
*/
@Column(nullable = false)
private Integer targetType;
/**
* 父评论ID
*/
@Column
private Long parentId;
/**
* 状态0-禁用1-正常
*/
@Column
private Integer status;
/**
* 创建时间
*/
@CreationTimestamp
@Column(nullable = false, updatable = false)
private LocalDateTime createTime;
/**
* 更新时间
*/
@UpdateTimestamp
@Column(nullable = false)
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,97 @@
package com.test.musichouduan.entity;
import jakarta.persistence.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Set;
/**
* 音乐实体类
*/
@Data
@Entity
@Table(name = "music")
public class Music {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 音乐名称
*/
@Column(nullable = false, length = 100)
private String name;
/**
* 封面URL
*/
@Column(length = 255)
private String cover;
/**
* 音乐文件URL
*/
@Column(nullable = false, length = 255)
private String url;
/**
* 歌词
*/
@Column(columnDefinition = "TEXT")
private String lyric;
/**
* 时长(秒)
*/
@Column
private Integer duration;
/**
* 专辑
*/
@Column(length = 100)
private String album;
/**
* 播放次数
*/
@Column
private Long playCount;
/**
* 歌手列表
*/
@ManyToMany(mappedBy = "musics")
private Set<Singer> singers = new HashSet<>();
/**
* 音乐类型列表
*/
@ManyToMany
@JoinTable(
name = "music_type_relation",
joinColumns = @JoinColumn(name = "music_id"),
inverseJoinColumns = @JoinColumn(name = "type_id")
)
private Set<MusicType> types = new HashSet<>();
/**
* 创建时间
*/
@CreationTimestamp
@Column(nullable = false, updatable = false)
private LocalDateTime createTime;
/**
* 更新时间
*/
@UpdateTimestamp
@Column(nullable = false)
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,47 @@
package com.test.musichouduan.entity;
import jakarta.persistence.*;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
/**
* 音乐类型实体类
*/
@Data
@Entity
@Table(name = "music_type")
public class MusicType {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 类型名称
*/
@Column(nullable = false, unique = true, length = 50)
private String name;
/**
* 类型描述
*/
@Column(length = 255)
private String description;
/**
* 创建时间
*/
@CreationTimestamp
@Column(nullable = false, updatable = false)
private LocalDateTime createTime;
/**
* 更新时间
*/
@UpdateTimestamp
@Column(nullable = false)
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,41 @@
package com.test.musichouduan.entity;
import jakarta.persistence.*;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import java.time.LocalDateTime;
/**
* 播放历史实体类
*/
@Data
@Entity
@Table(name = "play_history")
public class PlayHistory {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 用户
*/
@ManyToOne
@JoinColumn(name = "user_id", nullable = false)
private User user;
/**
* 音乐
*/
@ManyToOne
@JoinColumn(name = "music_id", nullable = false)
private Music music;
/**
* 播放时间
*/
@CreationTimestamp
@Column(nullable = false)
private LocalDateTime playTime;
}

View File

@@ -0,0 +1,92 @@
package com.test.musichouduan.entity;
import jakarta.persistence.*;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Set;
/**
* 歌单实体类
*/
@Data
@Entity
@Table(name = "playlist")
public class Playlist {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 歌单名称
*/
@Column(nullable = false, length = 100)
private String name;
/**
* 封面URL
*/
@Column(length = 255)
private String cover;
/**
* 歌单描述
*/
@Column(length = 500)
private String description;
/**
* 创建者
*/
@ManyToOne
@JoinColumn(name = "creator_id")
private User creator;
/**
* 歌单类型
*/
@ManyToOne
@JoinColumn(name = "type_id")
private PlaylistType type;
/**
* 是否官方歌单0-否1-是
*/
@Column
private Integer isOfficial;
/**
* 播放次数
*/
@Column
private Long playCount;
/**
* 歌单中的音乐
*/
@ManyToMany
@JoinTable(
name = "playlist_music",
joinColumns = @JoinColumn(name = "playlist_id"),
inverseJoinColumns = @JoinColumn(name = "music_id")
)
private Set<Music> musics = new HashSet<>();
/**
* 创建时间
*/
@CreationTimestamp
@Column(nullable = false, updatable = false)
private LocalDateTime createTime;
/**
* 更新时间
*/
@UpdateTimestamp
@Column(nullable = false)
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,47 @@
package com.test.musichouduan.entity;
import jakarta.persistence.*;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
/**
* 歌单类型实体类
*/
@Data
@Entity
@Table(name = "playlist_type")
public class PlaylistType {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 类型名称
*/
@Column(nullable = false, unique = true, length = 50)
private String name;
/**
* 类型描述
*/
@Column(length = 255)
private String description;
/**
* 创建时间
*/
@CreationTimestamp
@Column(nullable = false, updatable = false)
private LocalDateTime createTime;
/**
* 更新时间
*/
@UpdateTimestamp
@Column(nullable = false)
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,75 @@
package com.test.musichouduan.entity;
import jakarta.persistence.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
import java.util.Set;
import java.util.HashSet;
/**
* 歌手实体类
*/
@Data
@Entity
@Table(name = "singer")
@EqualsAndHashCode(exclude = {"musics"})
public class Singer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 歌手名称
*/
@Column(nullable = false, length = 100)
private String name;
/**
* 歌手头像URL
*/
@Column(length = 255)
private String avatar;
/**
* 歌手简介
*/
@Column(columnDefinition = "TEXT")
private String introduction;
/**
* 歌手类型ID
*/
@ManyToOne
@JoinColumn(name = "type_id")
private SingerType type;
/**
* 创建时间
*/
@CreationTimestamp
@Column(nullable = false, updatable = false)
private LocalDateTime createTime;
/**
* 更新时间
*/
@UpdateTimestamp
@Column(nullable = false)
private LocalDateTime updateTime;
/**
* 歌手关联的音乐
*/
@ManyToMany
@JoinTable(
name = "singer_music",
joinColumns = @JoinColumn(name = "singer_id"),
inverseJoinColumns = @JoinColumn(name = "music_id")
)
private Set<Music> musics = new HashSet<>();
}

View File

@@ -0,0 +1,47 @@
package com.test.musichouduan.entity;
import jakarta.persistence.*;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
/**
* 歌手类型实体类
*/
@Data
@Entity
@Table(name = "singer_type")
public class SingerType {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 类型名称
*/
@Column(nullable = false, unique = true, length = 50)
private String name;
/**
* 类型描述
*/
@Column(length = 255)
private String description;
/**
* 创建时间
*/
@CreationTimestamp
@Column(nullable = false, updatable = false)
private LocalDateTime createTime;
/**
* 更新时间
*/
@UpdateTimestamp
@Column(nullable = false)
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,95 @@
package com.test.musichouduan.entity;
import jakarta.persistence.*;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
/**
* 用户实体类
*/
@Data
@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 用户名
*/
@Column(nullable = false, unique = true, length = 50)
private String username;
/**
* 密码
*/
@Column(nullable = false, length = 100)
private String password;
/**
* 昵称
*/
@Column(length = 50)
private String nickname;
/**
* 头像URL
*/
@Column(length = 255)
private String avatar;
/**
* 性别0-未知1-男2-女
*/
@Column
private Integer gender;
/**
* 邮箱
*/
@Column(length = 100)
private String email;
/**
* 手机号
*/
@Column(length = 20)
private String phone;
/**
* 个人简介
*/
@Column(length = 500)
private String introduction;
/**
* 角色0-普通用户1-管理员
*/
@Column
private Integer role;
/**
* 状态0-禁用1-正常
*/
@Column
private Integer status;
/**
* 创建时间
*/
@CreationTimestamp
@Column(nullable = false, updatable = false)
private LocalDateTime createTime;
/**
* 更新时间
*/
@UpdateTimestamp
@Column(nullable = false)
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,41 @@
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_collect_music")
public class UserCollectMusic {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 用户
*/
@ManyToOne
@JoinColumn(name = "user_id", nullable = false)
private User user;
/**
* 音乐
*/
@ManyToOne
@JoinColumn(name = "music_id", nullable = false)
private Music music;
/**
* 创建时间
*/
@CreationTimestamp
@Column(nullable = false, updatable = false)
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,41 @@
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_collect_playlist")
public class UserCollectPlaylist {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 用户
*/
@ManyToOne
@JoinColumn(name = "user_id", nullable = false)
private User user;
/**
* 歌单
*/
@ManyToOne
@JoinColumn(name = "playlist_id", nullable = false)
private Playlist playlist;
/**
* 创建时间
*/
@CreationTimestamp
@Column(nullable = false, updatable = false)
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,41 @@
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_collect_singer")
public class UserCollectSinger {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 用户
*/
@ManyToOne
@JoinColumn(name = "user_id", nullable = false)
private User user;
/**
* 歌手
*/
@ManyToOne
@JoinColumn(name = "singer_id", nullable = false)
private Singer singer;
/**
* 创建时间
*/
@CreationTimestamp
@Column(nullable = false, updatable = false)
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,44 @@
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_music")
public class UserMusic {
/**
* ID
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 用户
*/
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
/**
* 音乐
*/
@ManyToOne
@JoinColumn(name = "music_id")
private Music music;
/**
* 收藏时间
*/
@CreationTimestamp
@Column(name = "collect_time", updatable = false)
private LocalDateTime collectTime;
}

View File

@@ -0,0 +1,44 @@
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_playlist")
public class UserPlaylist {
/**
* ID
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 用户
*/
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
/**
* 歌单
*/
@ManyToOne
@JoinColumn(name = "playlist_id")
private Playlist playlist;
/**
* 收藏时间
*/
@CreationTimestamp
@Column(name = "collect_time", updatable = false)
private LocalDateTime collectTime;
}

View File

@@ -0,0 +1,44 @@
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_singer")
public class UserSinger {
/**
* ID
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 用户
*/
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
/**
* 歌手
*/
@ManyToOne
@JoinColumn(name = "singer_id")
private Singer singer;
/**
* 收藏时间
*/
@CreationTimestamp
@Column(name = "collect_time", updatable = false)
private LocalDateTime collectTime;
}

View File

@@ -0,0 +1,59 @@
package com.test.musichouduan.exception;
/**
* 业务异常
*/
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 1L;
/**
* 错误码
*/
private Integer code;
/**
* 错误消息
*/
private String message;
/**
* 构造方法
*
* @param message 错误消息
*/
public BusinessException(String message) {
this.code = 500;
this.message = message;
}
/**
* 构造方法
*
* @param code 错误码
* @param message 错误消息
*/
public BusinessException(Integer code, String message) {
this.code = code;
this.message = message;
}
/**
* 获取错误码
*
* @return 错误码
*/
public Integer getCode() {
return code;
}
/**
* 获取错误消息
*
* @return 错误消息
*/
@Override
public String getMessage() {
return message;
}
}

View File

@@ -0,0 +1,132 @@
package com.test.musichouduan.exception;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import com.test.musichouduan.dto.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.resource.NoResourceFoundException;
import jakarta.servlet.http.HttpServletRequest;
/**
* 全局异常处理器
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 处理自定义异常
*/
@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusinessException(BusinessException e) {
logger.error(e.getMessage(), e);
return Result.error(e.getCode(), e.getMessage());
}
/**
* 处理未登录异常
*/
@ExceptionHandler(NotLoginException.class)
public Result<Void> handleNotLoginException(NotLoginException e) {
logger.error(e.getMessage(), e);
return Result.error(401, "未登录或登录已过期,请重新登录");
}
/**
* 处理无权限异常
*/
@ExceptionHandler(NotPermissionException.class)
public Result<Void> handleNotPermissionException(NotPermissionException e) {
logger.error(e.getMessage(), e);
return Result.error(403, "无权限访问");
}
/**
* 处理无角色异常
*/
@ExceptionHandler(NotRoleException.class)
public Result<Void> handleNotRoleException(NotRoleException e) {
logger.error(e.getMessage(), e);
return Result.error(403, "无权限访问");
}
/**
* 处理参数校验异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
logger.error(e.getMessage(), e);
BindingResult bindingResult = e.getBindingResult();
StringBuilder sb = new StringBuilder("参数校验失败:");
for (FieldError fieldError : bindingResult.getFieldErrors()) {
sb.append(fieldError.getField()).append("").append(fieldError.getDefaultMessage()).append(", ");
}
String msg = sb.toString();
if (msg.endsWith(", ")) {
msg = msg.substring(0, msg.length() - 2);
}
return Result.error(400, msg);
}
/**
* 处理参数绑定异常
*/
@ExceptionHandler(BindException.class)
public Result<Void> handleBindException(BindException e) {
logger.error(e.getMessage(), e);
BindingResult bindingResult = e.getBindingResult();
StringBuilder sb = new StringBuilder("参数绑定失败:");
for (FieldError fieldError : bindingResult.getFieldErrors()) {
sb.append(fieldError.getField()).append("").append(fieldError.getDefaultMessage()).append(", ");
}
String msg = sb.toString();
if (msg.endsWith(", ")) {
msg = msg.substring(0, msg.length() - 2);
}
return Result.error(400, msg);
}
/**
* 处理文件上传大小超限异常
*/
@ExceptionHandler(MaxUploadSizeExceededException.class)
public Result<Void> handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e) {
logger.error(e.getMessage(), e);
return Result.error(400, "上传文件大小超出限制");
}
/**
* 处理资源未找到异常
*/
@ExceptionHandler(NoResourceFoundException.class)
public Result<Void> handleNoResourceFoundException(NoResourceFoundException e, HttpServletRequest request) {
// 如果是 favicon.ico 的请求,则不记录日志
String requestURI = request.getRequestURI();
if (requestURI.endsWith("/favicon.ico")) {
return Result.error(404, "Resource not found");
}
// 其他资源未找到的情况,记录日志
logger.error(e.getMessage(), e);
return Result.error(404, "Resource not found");
}
/**
* 处理其他异常
*/
@ExceptionHandler(Exception.class)
public Result<Void> handleException(Exception e) {
logger.error(e.getMessage(), e);
return Result.error(500, "服务器内部错误");
}
}

View File

@@ -0,0 +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);
}
}
}
}

View File

@@ -0,0 +1,22 @@
package com.test.musichouduan.repository;
import com.test.musichouduan.entity.Banner;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* 轮播图数据访问层
*/
@Repository
public interface BannerRepository extends JpaRepository<Banner, Long> {
/**
* 根据状态查询轮播图
*
* @param status 状态
* @return 轮播图列表
*/
List<Banner> findByStatusOrderByOrderNumAsc(Integer status);
}

View File

@@ -0,0 +1,54 @@
package com.test.musichouduan.repository;
import com.test.musichouduan.entity.Comment;
import com.test.musichouduan.entity.User;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* 评论数据访问层
*/
@Repository
public interface CommentRepository extends JpaRepository<Comment, Long>, JpaSpecificationExecutor<Comment> {
/**
* 根据目标ID和目标类型查询评论
*
* @param targetId 目标ID
* @param targetType 目标类型
* @param pageable 分页
* @return 评论列表
*/
Page<Comment> findByTargetIdAndTargetTypeAndParentIdIsNullOrderByCreateTimeDesc(Long targetId, Integer targetType, Pageable pageable);
/**
* 根据父评论ID查询子评论
*
* @param parentId 父评论ID
* @return 子评论列表
*/
List<Comment> findByParentIdOrderByCreateTimeAsc(Long parentId);
/**
* 根据用户查询评论
*
* @param user 用户
* @param pageable 分页
* @return 评论列表
*/
Page<Comment> findByUserOrderByCreateTimeDesc(User user, Pageable pageable);
/**
* 根据目标ID和目标类型统计评论数量
*
* @param targetId 目标ID
* @param targetType 目标类型
* @return 评论数量
*/
long countByTargetIdAndTargetType(Long targetId, Integer targetType);
}

View File

@@ -0,0 +1,116 @@
package com.test.musichouduan.repository;
import com.test.musichouduan.entity.Music;
import com.test.musichouduan.entity.MusicType;
import com.test.musichouduan.entity.Singer;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* 音乐数据访问层
*/
@Repository
public interface MusicRepository extends JpaRepository<Music, Long>, JpaSpecificationExecutor<Music> {
/**
* 根据名称模糊查询音乐
*
* @param name 名称
* @param pageable 分页
* @return 音乐列表
*/
Page<Music> findByNameContaining(String name, Pageable pageable);
/**
* 根据类型查询音乐
*
* @param type 类型
* @param pageable 分页
* @return 音乐列表
*/
@Query("SELECT m FROM Music m JOIN m.types t WHERE t = :type")
Page<Music> findByType(MusicType type, Pageable pageable);
/**
* 根据歌手查询音乐
*
* @param singer 歌手
* @param pageable 分页
* @return 音乐列表
*/
@Query("SELECT m FROM Music m JOIN m.singers s WHERE s = :singer")
Page<Music> findBySinger(Singer singer, Pageable pageable);
/**
* 根据歌手查询音乐
*
* @param singer 歌手
* @param pageable 分页
* @return 音乐列表
*/
Page<Music> findBySingersContaining(Singer singer, Pageable pageable);
/**
* 统计歌手的音乐数量
*
* @param singer 歌手
* @return 音乐数量
*/
Long countBySingersContaining(Singer singer);
/**
* 检查是否存在指定类型的音乐
*
* @param type 音乐类型
* @return 是否存在
*/
boolean existsByTypesContaining(MusicType type);
/**
* 查询最新的音乐
*
* @param pageable 分页
* @return 音乐列表
*/
@Query("SELECT m FROM Music m ORDER BY m.createTime DESC")
List<Music> findLatest(Pageable pageable);
/**
* 查询播放次数最多的音乐
*
* @param pageable 分页
* @return 音乐列表
*/
@Query("SELECT m FROM Music m ORDER BY m.playCount DESC")
List<Music> findMostPlayed(Pageable pageable);
/**
* 统计包含指定类型的音乐数量
*
* @param type 音乐类型
* @return 音乐数量
*/
long countByTypesContaining(MusicType type);
/**
* 统计所有音乐的总播放量
*
* @return 总播放量
*/
@Query("SELECT COALESCE(SUM(m.playCount), 0) FROM Music m")
long sumPlayCount();
/**
* 统计各个音乐类型的音乐数量
*
* @return 音乐类型及其对应的音乐数量
*/
@Query("SELECT t.name, COUNT(m) FROM Music m JOIN m.types t GROUP BY t.name ORDER BY COUNT(m) DESC")
List<Object[]> countByMusicType();
}

View File

@@ -0,0 +1,31 @@
package com.test.musichouduan.repository;
import com.test.musichouduan.entity.MusicType;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/**
* 音乐类型数据访问层
*/
@Repository
public interface MusicTypeRepository extends JpaRepository<MusicType, Long>, JpaSpecificationExecutor<MusicType> {
/**
* 根据名称查找音乐类型
*
* @param name 名称
* @return 音乐类型
*/
Optional<MusicType> findByName(String name);
/**
* 判断名称是否存在
*
* @param name 名称
* @return 是否存在
*/
boolean existsByName(String name);
}

View File

@@ -0,0 +1,79 @@
package com.test.musichouduan.repository;
import com.test.musichouduan.entity.Music;
import com.test.musichouduan.entity.PlayHistory;
import com.test.musichouduan.entity.User;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.Optional;
/**
* 播放历史数据访问层
*/
@Repository
public interface PlayHistoryRepository extends JpaRepository<PlayHistory, Long> {
/**
/**
/**
* 根据用户查询播放历史
*
* @param user 用户
* @param pageable 分页
* @return 播放历史列表
*/
Page<PlayHistory> findByUserOrderByPlayTimeDesc(User user, Pageable pageable);
/**
* 根据用户和音乐查询播放历史
*
* @param user 用户
* @param music 音乐
* @return 播放历史
*/
Optional<PlayHistory> findByUserAndMusic(User user, Music music);
/**
* 根据用户统计播放历史数量
*
* @param user 用户
* @return 播放历史数量
*/
long countByUser(User user);
/**
* 根据音乐统计播放次数
*
* @param music 音乐
* @return 播放次数
*/
long countByMusic(Music music);
/**
* 统计指定时间范围内的播放次数
*
* @param start 开始时间
* @param end 结束时间
* @return 播放次数
*/
long countByPlayTimeBetween(LocalDateTime start, LocalDateTime end);
/**
*记录播放历史
* @param userId 用户ID
* @param musicId 音乐ID
*/
@Modifying
@Transactional
@Query(value = "INSERT INTO play_history (user_id, music_id, play_time) VALUES (?1, ?2, NOW()) " +
"ON DUPLICATE KEY UPDATE play_time = NOW()", nativeQuery = true)
void recordPlayHistory(Long userId, Long musicId);
}

View File

@@ -0,0 +1,100 @@
package com.test.musichouduan.repository;
import com.test.musichouduan.entity.Playlist;
import com.test.musichouduan.entity.PlaylistType;
import com.test.musichouduan.entity.User;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* 歌单数据访问层
*/
@Repository
public interface PlaylistRepository extends JpaRepository<Playlist, Long>, JpaSpecificationExecutor<Playlist> {
@Query("SELECT pt.name, COUNT(p) FROM Playlist p JOIN p.type pt GROUP BY pt.name")
List<Object[]> countByPlaylistType();
/**
* 根据类型统计歌单数量
*
* @param type 歌单类型
* @return 歌单数量
*/
long countByType(PlaylistType type);
/**
* 根据名称模糊查询歌单
*
* @param name 名称
* @param pageable 分页
* @return 歌单列表
*/
Page<Playlist> findByNameContaining(String name, Pageable pageable);
/**
* 根据类型查询歌单
*
* @param type 类型
* @param pageable 分页
* @return 歌单列表
*/
Page<Playlist> findByType(PlaylistType type, Pageable pageable);
/**
* 根据创建者查询歌单
*
* @param creator 创建者
* @param pageable 分页
* @return 歌单列表
*/
Page<Playlist> findByCreator(User creator, Pageable pageable);
/**
* 查询官方歌单
*
* @param pageable 分页
* @return 歌单列表
*/
Page<Playlist> findByIsOfficial(Integer isOfficial, Pageable pageable);
/**
* 查询最新的歌单
*
* @param pageable 分页
* @return 歌单列表
*/
@Query("SELECT p FROM Playlist p ORDER BY p.createTime DESC")
List<Playlist> findLatest(Pageable pageable);
/**
* 查询播放次数最多的歌单
*
* @param pageable 分页
* @return 歌单列表
*/
@Query("SELECT p FROM Playlist p ORDER BY p.playCount DESC")
List<Playlist> findMostPlayed(Pageable pageable);
/**
* 统计用户创建的歌单数量
*
* @param creator 创建者
* @return 歌单数量
*/
long countByCreator(User creator);
/**
* 检查是否存在指定类型的歌单
*
* @param type 歌单类型
* @return 是否存在
*/
boolean existsByType(PlaylistType type);
}

View File

@@ -0,0 +1,31 @@
package com.test.musichouduan.repository;
import com.test.musichouduan.entity.PlaylistType;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/**
* 歌单类型数据访问层
*/
@Repository
public interface PlaylistTypeRepository extends JpaRepository<PlaylistType, Long>, JpaSpecificationExecutor<PlaylistType> {
/**
* 根据名称查找歌单类型
*
* @param name 名称
* @return 歌单类型
*/
Optional<PlaylistType> findByName(String name);
/**
* 判断名称是否存在
*
* @param name 名称
* @return 是否存在
*/
boolean existsByName(String name);
}

View File

@@ -0,0 +1,83 @@
package com.test.musichouduan.repository;
import com.test.musichouduan.entity.Singer;
import com.test.musichouduan.entity.SingerType;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
/**
* 歌手数据访问层
*/
@Repository
public interface SingerRepository extends JpaRepository<Singer, Long>, JpaSpecificationExecutor<Singer> {
@Query("SELECT st.name, COUNT(s) FROM Singer s JOIN s.type st GROUP BY st.name")
List<Object[]> countBySingerType();
/**
* 根据类型统计歌手数量
*
* @param type 歌手类型
* @return 歌手数量
*/
long countByType(SingerType type);
/**
* 根据名称查找歌手
*
* @param name 名称
* @return 歌手
*/
Optional<Singer> findByName(String name);
/**
* 根据类型查找歌手
*
* @param type 类型
* @param pageable 分页
* @return 歌手列表
*/
Page<Singer> findByType(SingerType type, Pageable pageable);
/**
* 根据名称模糊查询歌手
*
* @param name 名称
* @param pageable 分页
* @return 歌手列表
*/
Page<Singer> findByNameContaining(String name, Pageable pageable);
/**
* 查询最新的歌手
*
* @param limit 限制数量
* @return 歌手列表
*/
@Query(value = "SELECT s FROM Singer s ORDER BY s.createTime DESC")
List<Singer> findLatest(Pageable pageable);
/**
* 查询热门歌手(按照音乐数量排序)
*
* @param pageable 分页
* @return 歌手列表
*/
@Query(value = "SELECT s.* FROM singer s LEFT JOIN (SELECT singer_id, COUNT(*) as music_count FROM music_singer GROUP BY singer_id) ms ON s.id = ms.singer_id ORDER BY COALESCE(ms.music_count, 0) DESC", nativeQuery = true)
List<Singer> findHotSingers(Pageable pageable);
/**
* 检查是否存在指定类型的歌手
*
* @param type 歌手类型
* @return 是否存在
*/
boolean existsByType(SingerType type);
}

View File

@@ -0,0 +1,31 @@
package com.test.musichouduan.repository;
import com.test.musichouduan.entity.SingerType;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/**
* 歌手类型数据访问层
*/
@Repository
public interface SingerTypeRepository extends JpaRepository<SingerType, Long>, JpaSpecificationExecutor<SingerType> {
/**
* 根据名称查找歌手类型
*
* @param name 名称
* @return 歌手类型
*/
Optional<SingerType> findByName(String name);
/**
* 判断名称是否存在
*
* @param name 名称
* @return 是否存在
*/
boolean existsByName(String name);
}

View File

@@ -0,0 +1,69 @@
package com.test.musichouduan.repository;
import com.test.musichouduan.entity.Music;
import com.test.musichouduan.entity.User;
import com.test.musichouduan.entity.UserCollectMusic;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/**
* 用户收藏音乐数据访问层
*/
@Repository
public interface UserCollectMusicRepository extends JpaRepository<UserCollectMusic, Long> {
/**
* 根据用户查询收藏的音乐
*
* @param user 用户
* @param pageable 分页
* @return 收藏的音乐列表
*/
Page<UserCollectMusic> findByUser(User user, Pageable pageable);
/**
* 根据用户和音乐查询收藏记录
*
* @param user 用户
* @param music 音乐
* @return 收藏记录
*/
Optional<UserCollectMusic> findByUserAndMusic(User user, Music music);
/**
* 判断用户是否收藏了音乐
*
* @param user 用户
* @param music 音乐
* @return 是否收藏
*/
boolean existsByUserAndMusic(User user, Music music);
/**
* 根据用户统计收藏的音乐数量
*
* @param user 用户
* @return 收藏的音乐数量
*/
long countByUser(User user);
/**
* 根据音乐统计被收藏的次数
*
* @param music 音乐
* @return 被收藏的次数
*/
long countByMusic(Music music);
/**
* 根据用户和音乐删除收藏记录
*
* @param user 用户
* @param music 音乐
*/
void deleteByUserAndMusic(User user, Music music);
}

View File

@@ -0,0 +1,69 @@
package com.test.musichouduan.repository;
import com.test.musichouduan.entity.Playlist;
import com.test.musichouduan.entity.User;
import com.test.musichouduan.entity.UserCollectPlaylist;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/**
* 用户收藏歌单数据访问层
*/
@Repository
public interface UserCollectPlaylistRepository extends JpaRepository<UserCollectPlaylist, Long> {
/**
* 根据用户查询收藏的歌单
*
* @param user 用户
* @param pageable 分页
* @return 收藏的歌单列表
*/
Page<UserCollectPlaylist> findByUser(User user, Pageable pageable);
/**
* 根据用户和歌单查询收藏记录
*
* @param user 用户
* @param playlist 歌单
* @return 收藏记录
*/
Optional<UserCollectPlaylist> findByUserAndPlaylist(User user, Playlist playlist);
/**
* 判断用户是否收藏了歌单
*
* @param user 用户
* @param playlist 歌单
* @return 是否收藏
*/
boolean existsByUserAndPlaylist(User user, Playlist playlist);
/**
* 根据用户统计收藏的歌单数量
*
* @param user 用户
* @return 收藏的歌单数量
*/
long countByUser(User user);
/**
* 根据歌单统计被收藏的次数
*
* @param playlist 歌单
* @return 被收藏的次数
*/
long countByPlaylist(Playlist playlist);
/**
* 根据用户和歌单删除收藏记录
*
* @param user 用户
* @param playlist 歌单
*/
void deleteByUserAndPlaylist(User user, Playlist playlist);
}

View File

@@ -0,0 +1,69 @@
package com.test.musichouduan.repository;
import com.test.musichouduan.entity.Singer;
import com.test.musichouduan.entity.User;
import com.test.musichouduan.entity.UserCollectSinger;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/**
* 用户收藏歌手数据访问层
*/
@Repository
public interface UserCollectSingerRepository extends JpaRepository<UserCollectSinger, Long> {
/**
* 根据用户查询收藏的歌手
*
* @param user 用户
* @param pageable 分页
* @return 收藏的歌手列表
*/
Page<UserCollectSinger> findByUser(User user, Pageable pageable);
/**
* 根据用户和歌手查询收藏记录
*
* @param user 用户
* @param singer 歌手
* @return 收藏记录
*/
Optional<UserCollectSinger> findByUserAndSinger(User user, Singer singer);
/**
* 判断用户是否收藏了歌手
*
* @param user 用户
* @param singer 歌手
* @return 是否收藏
*/
boolean existsByUserAndSinger(User user, Singer singer);
/**
* 根据用户统计收藏的歌手数量
*
* @param user 用户
* @return 收藏的歌手数量
*/
long countByUser(User user);
/**
* 根据歌手统计被收藏的次数
*
* @param singer 歌手
* @return 被收藏的次数
*/
long countBySinger(Singer singer);
/**
* 根据用户和歌手删除收藏记录
*
* @param user 用户
* @param singer 歌手
*/
void deleteByUserAndSinger(User user, Singer singer);
}

View File

@@ -0,0 +1,69 @@
package com.test.musichouduan.repository;
import com.test.musichouduan.entity.Music;
import com.test.musichouduan.entity.User;
import com.test.musichouduan.entity.UserMusic;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
/**
* 用户音乐关联数据访问层
*/
@Repository
public interface UserMusicRepository extends JpaRepository<UserMusic, Long> {
/**
* 判断用户是否已收藏音乐
*
* @param user 用户
* @param music 音乐
* @return 是否已收藏
*/
boolean existsByUserAndMusic(User user, Music music);
/**
* 判断用户是否已收藏音乐
*
* @param userId 用户ID
* @param musicId 音乐ID
* @return 是否已收藏
*/
boolean existsByUserIdAndMusicId(Long userId, Long musicId);
/**
* 收藏音乐
*
* @param userId 用户ID
* @param musicId 音乐ID
*/
@Modifying
@Transactional
@Query(value = "INSERT INTO user_music (user_id, music_id, collect_time) VALUES (?1, ?2, NOW())", nativeQuery = true)
void collectMusic(Long userId, Long musicId);
/**
* 取消收藏音乐
*
* @param userId 用户ID
* @param musicId 音乐ID
*/
@Modifying
@Transactional
@Query(value = "DELETE FROM user_music WHERE user_id = ?1 AND music_id = ?2", nativeQuery = true)
void uncollectMusic(Long userId, Long musicId);
/**
* 获取用户收藏的音乐
*
* @param userId 用户ID
* @param pageable 分页
* @return 用户音乐关联分页
*/
@Query("SELECT um FROM UserMusic um WHERE um.user.id = ?1")
Page<UserMusic> findCollectedMusic(Long userId, Pageable pageable);
}

View File

@@ -0,0 +1,69 @@
package com.test.musichouduan.repository;
import com.test.musichouduan.entity.Playlist;
import com.test.musichouduan.entity.User;
import com.test.musichouduan.entity.UserPlaylist;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
/**
* 用户歌单关联数据访问层
*/
@Repository
public interface UserPlaylistRepository extends JpaRepository<UserPlaylist, Long> {
/**
* 判断用户是否已收藏歌单
*
* @param user 用户
* @param playlist 歌单
* @return 是否已收藏
*/
boolean existsByUserAndPlaylist(User user, Playlist playlist);
/**
* 判断用户是否已收藏歌单
*
* @param userId 用户ID
* @param playlistId 歌单ID
* @return 是否已收藏
*/
boolean existsByUserIdAndPlaylistId(Long userId, Long playlistId);
/**
* 收藏歌单
*
* @param userId 用户ID
* @param playlistId 歌单ID
*/
@Modifying
@Transactional
@Query(value = "INSERT INTO user_playlist (user_id, playlist_id, collect_time) VALUES (?1, ?2, NOW())", nativeQuery = true)
void collectPlaylist(Long userId, Long playlistId);
/**
* 取消收藏歌单
*
* @param userId 用户ID
* @param playlistId 歌单ID
*/
@Modifying
@Transactional
@Query(value = "DELETE FROM user_playlist WHERE user_id = ?1 AND playlist_id = ?2", nativeQuery = true)
void uncollectPlaylist(Long userId, Long playlistId);
/**
* 获取用户收藏的歌单
*
* @param userId 用户ID
* @param pageable 分页
* @return 歌单分页
*/
@Query("SELECT up.playlist FROM UserPlaylist up WHERE up.user.id = ?1")
Page<Playlist> findCollectedPlaylist(Long userId, Pageable pageable);
}

View File

@@ -0,0 +1,71 @@
package com.test.musichouduan.repository;
import com.test.musichouduan.entity.User;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.time.LocalDateTime;
import java.util.Optional;
/**
* 用户数据访问层
*/
@Repository
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
/**
* 根据用户名查找用户
*
* @param username 用户名
* @return 用户
*/
Optional<User> findByUsername(String username);
/**
* 根据用户名或邮箱查找用户
*
* @param username 用户名
* @param email 邮箱
* @return 用户
*/
Optional<User> findByUsernameOrEmail(String username, String email);
/**
* 判断用户名是否存在
*
* @param username 用户名
* @return 是否存在
*/
boolean existsByUsername(String username);
/**
* 判断邮箱是否存在
*
* @param email 邮箱
* @return 是否存在
*/
boolean existsByEmail(String email);
/**
* 统计指定时间范围内创建的用户数量
*
* @param start 开始时间
* @param end 结束时间
* @return 用户数量
*/
long countByCreateTimeBetween(LocalDateTime start, LocalDateTime end);
/**
* 查询最新注册的用户
*
* @param pageable 分页
* @return 用户列表
*/
@Query("SELECT u FROM User u ORDER BY u.createTime DESC")
List<User> findLatestUsers(Pageable pageable);
}

View File

@@ -0,0 +1,69 @@
package com.test.musichouduan.repository;
import com.test.musichouduan.entity.Singer;
import com.test.musichouduan.entity.User;
import com.test.musichouduan.entity.UserSinger;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
/**
* 用户歌手关联数据访问层
*/
@Repository
public interface UserSingerRepository extends JpaRepository<UserSinger, Long> {
/**
* 判断用户是否已收藏歌手
*
* @param user 用户
* @param singer 歌手
* @return 是否已收藏
*/
boolean existsByUserAndSinger(User user, Singer singer);
/**
* 判断用户是否已收藏歌手
*
* @param userId 用户ID
* @param singerId 歌手ID
* @return 是否已收藏
*/
boolean existsByUserIdAndSingerId(Long userId, Long singerId);
/**
* 收藏歌手
*
* @param userId 用户ID
* @param singerId 歌手ID
*/
@Modifying
@Transactional
@Query(value = "INSERT INTO user_singer (user_id, singer_id, collect_time) VALUES (?1, ?2, NOW())", nativeQuery = true)
void collectSinger(Long userId, Long singerId);
/**
* 取消收藏歌手
*
* @param userId 用户ID
* @param singerId 歌手ID
*/
@Modifying
@Transactional
@Query(value = "DELETE FROM user_singer WHERE user_id = ?1 AND singer_id = ?2", nativeQuery = true)
void uncollectSinger(Long userId, Long singerId);
/**
* 获取用户收藏的歌手
*
* @param userId 用户ID
* @param pageable 分页
* @return 歌手分页
*/
@Query("SELECT us.singer FROM UserSinger us WHERE us.user.id = ?1")
Page<Singer> findCollectedSinger(Long userId, Pageable pageable);
}

View File

@@ -0,0 +1,82 @@
package com.test.musichouduan.service;
import com.test.musichouduan.dto.BannerDTO;
import com.test.musichouduan.dto.PageResult;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* 轮播图服务接口
*/
public interface BannerService {
/**
* 根据ID获取轮播图
*
* @param id 轮播图ID
* @return 轮播图DTO
*/
BannerDTO getBannerById(Long id);
/**
* 获取轮播图列表
*
* @return 轮播图列表
*/
List<BannerDTO> getBannerList();
/**
* 分页获取轮播图列表
*
* @param page 页码
* @param size 每页大小
* @return 分页结果
*/
PageResult<BannerDTO> getBannerPage(Integer page, Integer size);
/**
* 添加轮播图
*
* @param bannerDTO 轮播图DTO
* @param image 图片文件
* @return 轮播图DTO
*/
BannerDTO addBanner(BannerDTO bannerDTO, MultipartFile image);
/**
* 更新轮播图
*
* @param id 轮播图ID
* @param bannerDTO 轮播图DTO
* @param image 图片文件
* @return 轮播图DTO
*/
BannerDTO updateBanner(Long id, BannerDTO bannerDTO, MultipartFile image);
/**
* 删除轮播图
*
* @param id 轮播图ID
* @return 是否成功
*/
boolean deleteBanner(Long id);
/**
* 更新轮播图状态
*
* @param id 轮播图ID
* @param status 状态
* @return 是否成功
*/
boolean updateBannerStatus(Long id, Integer status);
/**
* 更新轮播图排序
*
* @param id 轮播图ID
* @param orderNum 排序号
* @return 是否成功
*/
boolean updateBannerOrder(Long id, Integer orderNum);
}

View File

@@ -0,0 +1,84 @@
package com.test.musichouduan.service;
import com.test.musichouduan.dto.CommentDTO;
import com.test.musichouduan.dto.PageResult;
/**
* 评论服务接口
*/
public interface CommentService {
/**
* 根据ID获取评论
*
* @param id 评论ID
* @return 评论DTO
*/
CommentDTO getCommentById(Long id);
/**
* 分页获取评论列表
*
* @param targetId 目标ID
* @param targetType 目标类型
* @param page 页码
* @param size 每页大小
* @return 分页结果
*/
PageResult<CommentDTO> getCommentList(Long targetId, Integer targetType, Integer page, Integer size);
/**
* 添加评论
*
* @param commentDTO 评论DTO
* @return 评论DTO
*/
CommentDTO addComment(CommentDTO commentDTO);
/**
* 删除评论
*
* @param id 评论ID
* @return 是否成功
*/
boolean deleteComment(Long id);
/**
* 获取用户的评论列表
*
* @param userId 用户ID
* @param page 页码
* @param size 每页大小
* @return 分页结果
*/
PageResult<CommentDTO> getUserCommentList(Long userId, Integer page, Integer size);
/**
* 管理员获取所有评论列表
*
* @param page 页码
* @param size 每页大小
* @param keyword 关键字
* @param targetType 目标类型
* @param status 状态
* @return 分页结果
*/
PageResult<CommentDTO> getAdminCommentList(Integer page, Integer size, String keyword, Integer targetType, Integer status);
/**
* 更新评论状态
*
* @param id 评论ID
* @param status 状态
* @return 是否成功
*/
boolean updateCommentStatus(Long id, Integer status);
/**
* 管理员删除评论
*
* @param id 评论ID
* @return 是否成功
*/
boolean adminDeleteComment(Long id);
}

View File

@@ -0,0 +1,41 @@
package com.test.musichouduan.service;
import org.springframework.web.multipart.MultipartFile;
/**
* 文件服务接口
*/
public interface FileService {
/**
* 上传图片
*
* @param file 图片文件
* @return 图片URL
*/
String uploadImage(MultipartFile file);
/**
* 上传音乐
*
* @param file 音乐文件
* @return 音乐URL
*/
String uploadMusic(MultipartFile file);
/**
* 上传歌词
*
* @param file 歌词文件
* @return 歌词内容
*/
String uploadLyric(MultipartFile file);
/**
* 删除文件
*
* @param url 文件URL
* @return 是否成功
*/
boolean deleteFile(String url);
}

View File

@@ -0,0 +1,124 @@
package com.test.musichouduan.service;
import com.test.musichouduan.dto.MusicDTO;
import com.test.musichouduan.dto.PageResult;
import com.test.musichouduan.entity.Music;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* 音乐服务接口
*/
public interface MusicService {
/**
* 根据ID获取音乐
*
* @param id 音乐ID
* @return 音乐DTO
*/
MusicDTO getMusicById(Long id);
/**
* 分页获取音乐列表
*
* @param page 页码
* @param size 每页大小
* @param keyword 关键字
* @param typeId 类型ID
* @return 分页结果
*/
PageResult<MusicDTO> getMusicList(Integer page, Integer size, String keyword, Long typeId);
/**
* 根据歌手ID获取音乐列表
*
* @param singerId 歌手ID
* @param page 页码
* @param size 每页大小
* @return 分页结果
*/
PageResult<MusicDTO> getMusicListBySinger(Long singerId, Integer page, Integer size);
/**
* 获取最新音乐列表
*
* @param limit 限制数量
* @return 音乐列表
*/
List<MusicDTO> getLatestMusic(Integer limit);
/**
* 获取热门音乐列表
*
* @param limit 限制数量
* @return 音乐列表
*/
List<MusicDTO> getHotMusic(Integer limit);
/**
* 添加音乐
*
* @param musicDTO 音乐DTO
* @param file 音乐文件
* @param cover 封面文件
* @return 音乐DTO
*/
MusicDTO addMusic(MusicDTO musicDTO, MultipartFile file, MultipartFile cover);
/**
* 更新音乐
*
* @param id 音乐ID
* @param musicDTO 音乐DTO
* @param file 音乐文件
* @param cover 封面文件
* @param lyric 歌词文件
* @return 音乐DTO
*/
MusicDTO updateMusic(Long id, MusicDTO musicDTO, MultipartFile file, MultipartFile cover, MultipartFile lyric);
/**
* 删除音乐
*
* @param id 音乐ID
* @return 是否成功
*/
boolean deleteMusic(Long id);
/**
* 收藏/取消收藏音乐
*
* @param id 音乐ID
* @param userId 用户ID
* @return 是否收藏成功
*/
boolean toggleCollectMusic(Long id, Long userId);
/**
* 获取用户收藏的音乐列表
*
* @param userId 用户ID
* @param page 页码
* @param size 每页大小
* @return 分页结果
*/
PageResult<MusicDTO> getCollectedMusic(Long userId, Integer page, Integer size);
/**
* 播放音乐
*
* @param id 音乐ID
* @return 音乐DTO
*/
MusicDTO playMusic(Long id);
/**
* 根据ID获取音乐实体
*
* @param id 音乐ID
* @return 音乐实体
*/
Music getById(Long id);
}

View File

@@ -0,0 +1,153 @@
package com.test.musichouduan.service;
import com.test.musichouduan.dto.PageResult;
import com.test.musichouduan.dto.PlaylistDTO;
import com.test.musichouduan.entity.Playlist;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* 歌单服务接口
*/
public interface PlaylistService {
/**
* 根据ID获取歌单
*
* @param id 歌单ID
* @return 歌单DTO
*/
PlaylistDTO getPlaylistById(Long id);
/**
* 分页获取歌单列表
*
* @param page 页码
* @param size 每页大小
* @param keyword 关键字
* @param typeId 类型ID
* @return 分页结果
*/
PageResult<PlaylistDTO> getPlaylistList(Integer page, Integer size, String keyword, Long typeId);
/**
* 获取最新歌单列表
*
* @param limit 限制数量
* @return 歌单列表
*/
List<PlaylistDTO> getLatestPlaylist(Integer limit);
/**
* 获取热门歌单列表
*
* @param limit 限制数量
* @return 歌单列表
*/
List<PlaylistDTO> getHotPlaylist(Integer limit);
/**
* 获取官方歌单列表
*
* @param page 页码
* @param size 每页大小
* @return 分页结果
*/
PageResult<PlaylistDTO> getOfficialPlaylist(Integer page, Integer size);
/**
* 添加歌单
*
* @param playlistDTO 歌单DTO
* @param cover 封面文件
* @return 歌单DTO
*/
PlaylistDTO addPlaylist(PlaylistDTO playlistDTO, MultipartFile cover);
/**
* 更新歌单
*
* @param id 歌单ID
* @param playlistDTO 歌单DTO
* @param cover 封面文件
* @return 歌单DTO
*/
PlaylistDTO updatePlaylist(Long id, PlaylistDTO playlistDTO, MultipartFile cover);
/**
* 删除歌单
*
* @param id 歌单ID
* @return 是否成功
*/
boolean deletePlaylist(Long id);
/**
* 收藏歌单
*
* @param id 歌单ID
* @return 是否成功
*/
boolean collectPlaylist(Long id);
/**
* 取消收藏歌单
*
* @param id 歌单ID
* @return 是否成功
*/
boolean uncollectPlaylist(Long id);
/**
* 获取用户收藏的歌单列表
*
* @param page 页码
* @param size 每页大小
* @return 分页结果
*/
PageResult<PlaylistDTO> getCollectedPlaylist(Integer page, Integer size);
/**
* 获取用户创建的歌单列表
*
* @param page 页码
* @param size 每页大小
* @return 分页结果
*/
PageResult<PlaylistDTO> getUserPlaylist(Integer page, Integer size);
/**
* 向歌单添加音乐
*
* @param id 歌单ID
* @param musicId 音乐ID
* @return 是否成功
*/
boolean addMusicToPlaylist(Long id, Long musicId);
/**
* 从歌单移除音乐
*
* @param id 歌单ID
* @param musicId 音乐ID
* @return 是否成功
*/
boolean removeMusicFromPlaylist(Long id, Long musicId);
/**
* 播放歌单
*
* @param id 歌单ID
* @return 歌单DTO
*/
PlaylistDTO playPlaylist(Long id);
/**
* 根据ID获取歌单实体
*
* @param id 歌单ID
* @return 歌单实体
*/
Playlist getById(Long id);
}

View File

@@ -0,0 +1,125 @@
package com.test.musichouduan.service;
import com.test.musichouduan.dto.PageResult;
import com.test.musichouduan.dto.SingerDTO;
import com.test.musichouduan.entity.Singer;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* 歌手服务接口
*/
public interface SingerService {
/**
* 根据ID获取歌手
*
* @param id 歌手ID
* @return 歌手DTO
*/
SingerDTO getSingerById(Long id);
/**
* 分页获取歌手列表
*
* @param page 页码
* @param size 每页大小
* @param keyword 关键字
* @param typeId 类型ID
* @return 分页结果
*/
PageResult<SingerDTO> getSingerList(Integer page, Integer size, String keyword, Long typeId);
/**
* 获取最新歌手列表
*
* @param limit 限制数量
* @return 歌手列表
*/
List<SingerDTO> getLatestSinger(Integer limit);
/**
* 获取热门歌手列表
*
* @param limit 限制数量
* @return 歌手列表
*/
List<SingerDTO> getHotSinger(Integer limit);
/**
* 添加歌手
*
* @param singerDTO 歌手DTO
* @param avatar 头像文件
* @return 歌手DTO
*/
SingerDTO addSinger(SingerDTO singerDTO, MultipartFile avatar);
/**
* 更新歌手
*
* @param id 歌手ID
* @param singerDTO 歌手DTO
* @param avatar 头像文件
* @return 歌手DTO
*/
SingerDTO updateSinger(Long id, SingerDTO singerDTO, MultipartFile avatar);
/**
* 删除歌手
*
* @param id 歌手ID
* @return 是否成功
*/
boolean deleteSinger(Long id);
/**
* 收藏歌手
*
* @param id 歌手ID
* @return 是否成功
*/
boolean collectSinger(Long id);
/**
* 取消收藏歌手
*
* @param id 歌手ID
* @return 是否成功
*/
boolean uncollectSinger(Long id);
/**
* 获取用户收藏的歌手列表
*
* @param page 页码
* @param size 每页大小
* @return 分页结果
*/
PageResult<SingerDTO> getCollectedSinger(Integer page, Integer size);
/**
* 添加音乐到歌手
* @param singerId 歌手ID
* @param musicId 音乐ID
* @return 是否成功
*/
boolean addMusicToSinger(Long singerId, Long musicId);
/**
* 从歌手移除音乐
* @param singerId 歌手ID
* @param musicId 音乐ID
* @return 是否成功
*/
boolean removeMusicFromSinger(Long singerId, Long musicId);
/**
* 根据ID获取歌手实体
*
* @param id 歌手ID
* @return 歌手实体
*/
Singer getById(Long id);
}

View File

@@ -0,0 +1,55 @@
package com.test.musichouduan.service;
import java.util.Map;
/**
* 统计服务接口
*/
public interface StatService {
/**
* 获取总体统计数据
*
* @return 统计数据
*/
Map<String, Object> getTotalStats();
/**
* 获取用户增长趋势
*
* @param days 天数
* @return 统计数据
*/
Map<String, Object> getUserGrowthTrend(Integer days);
/**
* 获取音乐类型分布
*
* @return 统计数据
*/
Map<String, Object> getMusicTypeDistribution();
/**
* 获取播放量统计
*
* @param days 天数
* @return 统计数据
*/
Map<String, Object> getPlayCountStats(Integer days);
/**
* 获取最新用户列表
*
* @param limit 限制数量
* @return 统计数据
*/
Map<String, Object> getLatestUsers(Integer limit);
/**
* 获取最新音乐列表
*
* @param limit 限制数量
* @return 统计数据
*/
Map<String, Object> getLatestMusics(Integer limit);
}

View File

@@ -0,0 +1,140 @@
package com.test.musichouduan.service;
import com.test.musichouduan.dto.MusicTypeDTO;
import com.test.musichouduan.dto.PageResult;
import com.test.musichouduan.dto.PlaylistTypeDTO;
import com.test.musichouduan.dto.SingerTypeDTO;
import java.util.List;
/**
* 类型服务接口
*/
public interface TypeService {
/**
* 获取音乐类型列表
*
* @return 音乐类型列表
*/
List<MusicTypeDTO> getMusicTypeList();
/**
* 分页获取音乐类型列表
*
* @param page 页码
* @param size 每页大小
* @param keyword 关键字
* @return 分页结果
*/
PageResult<MusicTypeDTO> getMusicTypePage(Integer page, Integer size, String keyword);
/**
* 添加音乐类型
*
* @param musicTypeDTO 音乐类型DTO
* @return 音乐类型DTO
*/
MusicTypeDTO addMusicType(MusicTypeDTO musicTypeDTO);
/**
* 更新音乐类型
*
* @param id 音乐类型ID
* @param musicTypeDTO 音乐类型DTO
* @return 音乐类型DTO
*/
MusicTypeDTO updateMusicType(Long id, MusicTypeDTO musicTypeDTO);
/**
* 删除音乐类型
*
* @param id 音乐类型ID
* @return 是否成功
*/
boolean deleteMusicType(Long id);
/**
* 获取歌手类型列表
*
* @return 歌手类型列表
*/
List<SingerTypeDTO> getSingerTypeList();
/**
* 分页获取歌手类型列表
*
* @param page 页码
* @param size 每页大小
* @param keyword 关键字
* @return 分页结果
*/
PageResult<SingerTypeDTO> getSingerTypePage(Integer page, Integer size, String keyword);
/**
* 添加歌手类型
*
* @param singerTypeDTO 歌手类型DTO
* @return 歌手类型DTO
*/
SingerTypeDTO addSingerType(SingerTypeDTO singerTypeDTO);
/**
* 更新歌手类型
*
* @param id 歌手类型ID
* @param singerTypeDTO 歌手类型DTO
* @return 歌手类型DTO
*/
SingerTypeDTO updateSingerType(Long id, SingerTypeDTO singerTypeDTO);
/**
* 删除歌手类型
*
* @param id 歌手类型ID
* @return 是否成功
*/
boolean deleteSingerType(Long id);
/**
* 获取歌单类型列表
*
* @return 歌单类型列表
*/
List<PlaylistTypeDTO> getPlaylistTypeList();
/**
* 分页获取歌单类型列表
*
* @param page 页码
* @param size 每页大小
* @param keyword 关键字
* @return 分页结果
*/
PageResult<PlaylistTypeDTO> getPlaylistTypePage(Integer page, Integer size, String keyword);
/**
* 添加歌单类型
*
* @param playlistTypeDTO 歌单类型DTO
* @return 歌单类型DTO
*/
PlaylistTypeDTO addPlaylistType(PlaylistTypeDTO playlistTypeDTO);
/**
* 更新歌单类型
*
* @param id 歌单类型ID
* @param playlistTypeDTO 歌单类型DTO
* @return 歌单类型DTO
*/
PlaylistTypeDTO updatePlaylistType(Long id, PlaylistTypeDTO playlistTypeDTO);
/**
* 删除歌单类型
*
* @param id 歌单类型ID
* @return 是否成功
*/
boolean deletePlaylistType(Long id);
}

View File

@@ -0,0 +1,117 @@
package com.test.musichouduan.service;
import com.test.musichouduan.dto.*;
import com.test.musichouduan.entity.User;
/**
* 用户服务接口
*/
public interface UserService {
/**
* 用户注册
*
* @param request 注册请求
* @return 用户DTO
*/
UserDTO register(RegisterRequest request);
/**
* 用户登录
*
* @param request 登录请求
* @return 登录结果DTO包含用户信息和令牌信息
*/
LoginResultDTO login(LoginRequest request);
/**
* 获取当前登录用户
*
* @return 用户DTO
*/
UserDTO getCurrentUser();
/**
* 根据ID获取用户
*
* @param id 用户ID
* @return 用户DTO
*/
UserDTO getUserById(Long id);
/**
* 根据用户名获取用户
*
* @param username 用户名
* @return 用户DTO
*/
UserDTO getUserByUsername(String username);
/**
* 更新用户信息
*
* @param request 用户信息更新请求
* @return 用户DTO
*/
UserDTO updateUser(UserUpdateRequest request);
/**
* 修改密码
*
* @param request 修改密码请求
* @return 是否成功
*/
boolean updatePassword(PasswordUpdateRequest request);
/**
* 获取用户统计数据
*
* @param userId 用户ID
* @return 用户DTO
*/
UserDTO getUserStats(Long userId);
/**
* 分页获取用户列表
*
* @param page 页码
* @param size 每页大小
* @param keyword 关键字
* @return 分页结果
*/
PageResult<UserDTO> getUserList(Integer page, Integer size, String keyword);
/**
* 根据ID删除用户
*
* @param id 用户ID
* @return 是否成功
*/
boolean deleteUser(Long id);
/**
* 更新用户状态
*
* @param id 用户ID
* @param status 状态
* @return 是否成功
*/
boolean updateUserStatus(Long id, Integer status);
/**
* 管理员更新用户信息
*
* @param id 用户ID
* @param request 用户信息更新请求
* @return 用户DTO
*/
UserDTO adminUpdateUser(Long id, UserUpdateRequest request);
/**
* 根据ID获取用户实体
*
* @param id 用户ID
* @return 用户实体
*/
User getById(Long id);
}

View File

@@ -0,0 +1,186 @@
package com.test.musichouduan.service.impl;
import com.test.musichouduan.dto.BannerDTO;
import com.test.musichouduan.dto.PageResult;
import com.test.musichouduan.entity.Banner;
import com.test.musichouduan.exception.BusinessException;
import com.test.musichouduan.repository.BannerRepository;
import com.test.musichouduan.service.BannerService;
import com.test.musichouduan.service.FileService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import java.util.stream.Collectors;
/**
* 轮播图服务实现类
*/
@Service
public class BannerServiceImpl implements BannerService {
@Autowired
private BannerRepository bannerRepository;
@Autowired
private FileService fileService;
@Override
public BannerDTO getBannerById(Long id) {
Banner banner = bannerRepository.findById(id)
.orElseThrow(() -> new BusinessException("轮播图不存在"));
return convertToDTO(banner);
}
@Override
public List<BannerDTO> getBannerList() {
// 只获取启用状态的轮播图,并按排序号排序
List<Banner> banners = bannerRepository.findByStatusOrderByOrderNumAsc(1);
return banners.stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
@Override
public PageResult<BannerDTO> getBannerPage(Integer page, Integer size) {
Page<Banner> bannerPage = bannerRepository.findAll(
PageRequest.of(page - 1, size, Sort.by(Sort.Direction.ASC, "orderNum"))
);
List<BannerDTO> bannerDTOs = bannerPage.getContent().stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
return new PageResult<>(
bannerDTOs,
bannerPage.getTotalElements(),
page,
size
);
}
@Override
@Transactional
public BannerDTO addBanner(BannerDTO bannerDTO, MultipartFile image) {
// 上传图片
String imageUrl = fileService.uploadImage(image);
// 创建轮播图实体
Banner banner = new Banner();
BeanUtils.copyProperties(bannerDTO, banner);
banner.setImageUrl(imageUrl);
banner.setStatus(banner.getStatus() != null ? banner.getStatus() : 1);
// 保存轮播图
Banner savedBanner = bannerRepository.save(banner);
return convertToDTO(savedBanner);
}
@Override
@Transactional
public BannerDTO updateBanner(Long id, BannerDTO bannerDTO, MultipartFile image) {
// 获取轮播图
Banner banner = bannerRepository.findById(id)
.orElseThrow(() -> new BusinessException("轮播图不存在"));
// 如果有新图片则上传并更新URL
if (image != null && !image.isEmpty()) {
// 删除旧图片
if (banner.getImageUrl() != null) {
fileService.deleteFile(banner.getImageUrl());
}
// 上传新图片
String imageUrl = fileService.uploadImage(image);
banner.setImageUrl(imageUrl);
}
// 更新轮播图信息
banner.setTitle(bannerDTO.getTitle());
banner.setTargetType(bannerDTO.getTargetType());
banner.setTargetId(bannerDTO.getTargetId());
banner.setLinkUrl(bannerDTO.getLinkUrl());
if (bannerDTO.getOrderNum() != null) {
banner.setOrderNum(bannerDTO.getOrderNum());
}
if (bannerDTO.getStatus() != null) {
banner.setStatus(bannerDTO.getStatus());
}
// 保存轮播图
Banner updatedBanner = bannerRepository.save(banner);
return convertToDTO(updatedBanner);
}
@Override
@Transactional
public boolean deleteBanner(Long id) {
// 获取轮播图
Banner banner = bannerRepository.findById(id)
.orElseThrow(() -> new BusinessException("轮播图不存在"));
// 删除图片
if (banner.getImageUrl() != null) {
fileService.deleteFile(banner.getImageUrl());
}
// 删除轮播图
bannerRepository.delete(banner);
return true;
}
@Override
@Transactional
public boolean updateBannerStatus(Long id, Integer status) {
// 获取轮播图
Banner banner = bannerRepository.findById(id)
.orElseThrow(() -> new BusinessException("轮播图不存在"));
// 更新状态
banner.setStatus(status);
// 保存轮播图
bannerRepository.save(banner);
return true;
}
@Override
@Transactional
public boolean updateBannerOrder(Long id, Integer orderNum) {
// 获取轮播图
Banner banner = bannerRepository.findById(id)
.orElseThrow(() -> new BusinessException("轮播图不存在"));
// 更新排序号
banner.setOrderNum(orderNum);
// 保存轮播图
bannerRepository.save(banner);
return true;
}
/**
* 将实体转换为DTO
*
* @param banner 轮播图实体
* @return 轮播图DTO
*/
private BannerDTO convertToDTO(Banner banner) {
BannerDTO bannerDTO = new BannerDTO();
BeanUtils.copyProperties(banner, bannerDTO);
// 处理字段名不一致的情况
// 不需要手动设置因为BeanUtils会自动匹配相同名称的属性
return bannerDTO;
}
}

View File

@@ -0,0 +1,268 @@
package com.test.musichouduan.service.impl;
import cn.dev33.satoken.stp.StpUtil;
import com.test.musichouduan.dto.CommentDTO;
import com.test.musichouduan.dto.PageResult;
import com.test.musichouduan.dto.UserDTO;
import com.test.musichouduan.entity.Comment;
import com.test.musichouduan.entity.User;
import com.test.musichouduan.exception.BusinessException;
import com.test.musichouduan.repository.CommentRepository;
import com.test.musichouduan.repository.UserRepository;
import com.test.musichouduan.service.CommentService;
import org.springframework.messaging.simp.SimpMessagingTemplate; // 添加导入
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import jakarta.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* 评论服务实现类
*/
@Service
public class CommentServiceImpl implements CommentService {
@Autowired
private CommentRepository commentRepository;
@Autowired
private UserRepository userRepository;
@Autowired
private SimpMessagingTemplate messagingTemplate; // 注入 SimpMessagingTemplate
@Override
public CommentDTO getCommentById(Long id) {
Comment comment = commentRepository.findById(id)
.orElseThrow(() -> new BusinessException("评论不存在"));
return convertToDTO(comment);
}
@Override
public PageResult<CommentDTO> getCommentList(Long targetId, Integer targetType, Integer page, Integer size) {
// 获取父评论
Page<Comment> commentPage = commentRepository.findByTargetIdAndTargetTypeAndParentIdIsNullOrderByCreateTimeDesc(
targetId, targetType, PageRequest.of(page - 1, size)
);
// 转换为DTO
List<CommentDTO> commentDTOs = commentPage.getContent().stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
// 获取子评论
for (CommentDTO commentDTO : commentDTOs) {
List<Comment> children = commentRepository.findByParentIdOrderByCreateTimeAsc(commentDTO.getId());
if (!children.isEmpty()) {
commentDTO.setChildren(children.stream()
.map(this::convertToDTO)
.collect(Collectors.toList()));
} else {
commentDTO.setChildren(new ArrayList<>());
}
}
return new PageResult<>(
commentDTOs,
commentPage.getTotalElements(),
page,
size
);
}
@Override
@Transactional
public CommentDTO addComment(CommentDTO commentDTO) {
// 获取当前登录用户
Long userId = StpUtil.getLoginIdAsLong();
User user = userRepository.findById(userId)
.orElseThrow(() -> new BusinessException("用户不存在"));
// 创建评论实体
Comment comment = new Comment();
comment.setContent(commentDTO.getContent());
comment.setUser(user);
comment.setTargetId(commentDTO.getTargetId());
comment.setTargetType(commentDTO.getTargetType());
comment.setParentId(commentDTO.getParentId());
comment.setStatus(1); // 默认状态为正常
// 保存评论
Comment savedComment = commentRepository.save(comment);
// 将新评论转换为DTO
CommentDTO savedCommentDTO = convertToDTO(savedComment);
// 构建 WebSocket 主题路径
String destination = String.format("/topic/comments/%d/%d", savedComment.getTargetType(), savedComment.getTargetId());
// 通过 WebSocket 发送新评论
messagingTemplate.convertAndSend(destination, savedCommentDTO);
return savedCommentDTO; // 返回转换后的 DTO
}
@Override
@Transactional
public boolean deleteComment(Long id) {
// 获取评论
Comment comment = commentRepository.findById(id)
.orElseThrow(() -> new BusinessException("评论不存在"));
// 获取当前登录用户
Long userId = StpUtil.getLoginIdAsLong();
// 检查是否是评论作者或管理员
if (!comment.getUser().getId().equals(userId) && !StpUtil.hasRole("admin")) {
throw new BusinessException("无权删除该评论");
}
// 删除评论
commentRepository.delete(comment);
// 如果是父评论,同时删除子评论
if (comment.getParentId() == null) {
List<Comment> children = commentRepository.findByParentIdOrderByCreateTimeAsc(id);
if (!children.isEmpty()) {
commentRepository.deleteAll(children);
}
}
return true;
}
@Override
public PageResult<CommentDTO> getUserCommentList(Long userId, Integer page, Integer size) {
// 获取用户
User user = userRepository.findById(userId)
.orElseThrow(() -> new BusinessException("用户不存在"));
// 获取用户评论
Page<Comment> commentPage = commentRepository.findByUserOrderByCreateTimeDesc(
user, PageRequest.of(page - 1, size)
);
// 转换为DTO
List<CommentDTO> commentDTOs = commentPage.getContent().stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
return new PageResult<>(
commentDTOs,
commentPage.getTotalElements(),
page,
size
);
}
@Override
public PageResult<CommentDTO> getAdminCommentList(Integer page, Integer size, String keyword, Integer targetType, Integer status) {
// 构建查询条件
Specification<Comment> spec = (root, query, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
// 关键字搜索
if (keyword != null && !keyword.isEmpty()) {
predicates.add(criteriaBuilder.like(root.get("content"), "%" + keyword + "%"));
}
// 目标类型筛选
if (targetType != null) {
predicates.add(criteriaBuilder.equal(root.get("targetType"), targetType));
}
// 状态筛选
if (status != null) {
predicates.add(criteriaBuilder.equal(root.get("status"), status));
}
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};
// 执行查询
Page<Comment> commentPage = commentRepository.findAll(
spec, PageRequest.of(page - 1, size, Sort.by(Sort.Direction.DESC, "createTime"))
);
// 转换为DTO
List<CommentDTO> commentDTOs = commentPage.getContent().stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
return new PageResult<>(
commentDTOs,
commentPage.getTotalElements(),
page,
size
);
}
@Override
@Transactional
public boolean updateCommentStatus(Long id, Integer status) {
// 获取评论
Comment comment = commentRepository.findById(id)
.orElseThrow(() -> new BusinessException("评论不存在"));
// 更新状态
comment.setStatus(status);
commentRepository.save(comment);
return true;
}
@Override
@Transactional
public boolean adminDeleteComment(Long id) {
// 获取评论
Comment comment = commentRepository.findById(id)
.orElseThrow(() -> new BusinessException("评论不存在"));
// 删除评论
commentRepository.delete(comment);
// 如果是父评论,同时删除子评论
if (comment.getParentId() == null) {
List<Comment> children = commentRepository.findByParentIdOrderByCreateTimeAsc(id);
if (!children.isEmpty()) {
commentRepository.deleteAll(children);
}
}
return true;
}
/**
* 将实体转换为DTO
*
* @param comment 评论实体
* @return 评论DTO
*/
private CommentDTO convertToDTO(Comment comment) {
CommentDTO commentDTO = new CommentDTO();
BeanUtils.copyProperties(comment, commentDTO);
// 设置用户信息
UserDTO userDTO = new UserDTO();
BeanUtils.copyProperties(comment.getUser(), userDTO);
// 确保头像URL是完整路径
if (userDTO.getAvatar() != null && !userDTO.getAvatar().startsWith("http")) {
userDTO.setAvatar("http://localhost:8085" + userDTO.getAvatar());
}
commentDTO.setUser(userDTO);
return commentDTO;
}
}

View File

@@ -0,0 +1,168 @@
package com.test.musichouduan.service.impl;
import com.test.musichouduan.exception.BusinessException;
import com.test.musichouduan.service.FileService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
/**
* 文件服务实现类
*/
@Service
public class FileServiceImpl implements FileService {
private static final Logger logger = LoggerFactory.getLogger(FileServiceImpl.class);
@Value("${file.upload-path}")
private String uploadPath;
@Value("${file.access-path}")
private String accessPath;
@Value("${file.allowed-types}")
private String allowedTypes;
@Override
public String uploadImage(MultipartFile file) {
// 检查文件是否为空
if (file.isEmpty()) {
throw new BusinessException("上传文件不能为空");
}
// 检查文件类型
String originalFilename = file.getOriginalFilename();
String extension = getFileExtension(originalFilename);
List<String> imageTypes = Arrays.asList("jpg", "jpeg", "png", "gif");
if (!imageTypes.contains(extension.toLowerCase())) {
throw new BusinessException("只能上传jpg、jpeg、png、gif格式的图片");
}
// 上传文件
return uploadFile(file, "images");
}
@Override
public String uploadMusic(MultipartFile file) {
// 检查文件是否为空
if (file.isEmpty()) {
throw new BusinessException("上传文件不能为空");
}
// 检查文件类型
String originalFilename = file.getOriginalFilename();
String extension = getFileExtension(originalFilename);
List<String> musicTypes = Arrays.asList("mp3", "wav");
if (!musicTypes.contains(extension.toLowerCase())) {
throw new BusinessException("只能上传mp3、wav格式的音乐");
}
// 上传文件
return uploadFile(file, "musics");
}
@Override
public String uploadLyric(MultipartFile file) {
// 检查文件是否为空
if (file.isEmpty()) {
throw new BusinessException("上传文件不能为空");
}
// 检查文件类型
String originalFilename = file.getOriginalFilename();
String extension = getFileExtension(originalFilename);
List<String> lyricTypes = Arrays.asList("lrc", "txt");
if (!lyricTypes.contains(extension.toLowerCase())) {
throw new BusinessException("只能上传lrc、txt格式的歌词");
}
try {
// 读取文件内容
byte[] bytes = file.getBytes();
String content = new String(bytes, StandardCharsets.UTF_8);
return content;
} catch (IOException e) {
logger.error("读取歌词文件失败", e);
throw new BusinessException("读取歌词文件失败");
}
}
@Override
public boolean deleteFile(String url) {
if (StringUtils.isEmpty(url)) {
return false;
}
// 从URL中提取文件路径
String filePath = url.replace(accessPath, "");
Path path = Paths.get(uploadPath, filePath);
try {
// 删除文件
return Files.deleteIfExists(path);
} catch (IOException e) {
logger.error("删除文件失败", e);
return false;
}
}
/**
* 上传文件
*
* @param file 文件
* @param directory 目录
* @return 文件URL
*/
private String uploadFile(MultipartFile file, String directory) {
// 生成文件名
String originalFilename = file.getOriginalFilename();
String extension = getFileExtension(originalFilename);
String fileName = UUID.randomUUID().toString() + "." + extension;
// 创建目录
File dir = new File(uploadPath + directory);
if (!dir.exists()) {
dir.mkdirs();
}
// 保存文件
try {
Path path = Paths.get(uploadPath, directory, fileName);
Files.write(path, file.getBytes());
return "http://localhost:8085" + accessPath + directory + "/" + fileName;
} catch (IOException e) {
logger.error("上传文件失败", e);
throw new BusinessException("上传文件失败");
}
}
/**
* 获取文件扩展名
*
* @param fileName 文件名
* @return 扩展名
*/
private String getFileExtension(String fileName) {
if (fileName == null) {
return "";
}
int dotIndex = fileName.lastIndexOf(".");
if (dotIndex == -1) {
return "";
}
return fileName.substring(dotIndex + 1);
}
}

View File

@@ -0,0 +1,504 @@
package com.test.musichouduan.service.impl;
import cn.dev33.satoken.stp.StpUtil;
import com.test.musichouduan.dto.MusicDTO;
import com.test.musichouduan.dto.PageResult;
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.UserMusic;
import com.test.musichouduan.exception.BusinessException;
import com.test.musichouduan.repository.*;
import com.test.musichouduan.service.FileService;
import com.test.musichouduan.service.MusicService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.util.Optional;
import jakarta.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 音乐服务实现类
*/
@Service
public class MusicServiceImpl implements MusicService {
@Autowired
private MusicRepository musicRepository;
@Autowired
private MusicTypeRepository musicTypeRepository;
@Autowired
private SingerRepository singerRepository;
@Autowired
private UserRepository userRepository;
@Autowired
private UserMusicRepository userMusicRepository;
@Autowired
private PlayHistoryRepository playHistoryRepository;
@Autowired
private FileService fileService;
@Override
public MusicDTO getMusicById(Long id) {
// 查找音乐如果不存在则返回null
return musicRepository.findById(id)
.map(this::convertToDTO)
.orElse(null);
}
@Override
public PageResult<MusicDTO> getMusicList(Integer page, Integer size, String keyword, Long typeId) {
// 构建查询条件
Specification<Music> spec = (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
// 关键字查询
if (StringUtils.hasText(keyword)) {
predicates.add(cb.like(root.get("name"), "%" + keyword + "%"));
}
// 类型查询
if (typeId != null) {
predicates.add(cb.isMember(
musicTypeRepository.findById(typeId).orElse(null),
root.get("types")
));
}
return cb.and(predicates.toArray(new Predicate[0]));
};
// 执行查询
Page<Music> musicPage = musicRepository.findAll(
spec,
PageRequest.of(page - 1, size, Sort.by(Sort.Direction.DESC, "createTime"))
);
// 转换为DTO
List<MusicDTO> musicDTOs = musicPage.getContent().stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
return new PageResult<>(
musicDTOs,
musicPage.getTotalElements(),
page,
size
);
}
@Override
public PageResult<MusicDTO> getMusicListBySinger(Long singerId, Integer page, Integer size) {
// 获取歌手
Singer singer = singerRepository.findById(singerId)
.orElseThrow(() -> new BusinessException("歌手不存在"));
// 查询歌手的音乐
Page<Music> musicPage = musicRepository.findBySingersContaining(
singer,
PageRequest.of(page - 1, size, Sort.by(Sort.Direction.DESC, "createTime"))
);
// 转换为DTO
List<MusicDTO> musicDTOs = musicPage.getContent().stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
return new PageResult<>(
musicDTOs,
musicPage.getTotalElements(),
page,
size
);
}
@Override
public List<MusicDTO> getLatestMusic(Integer limit) {
// 查询最新音乐
List<Music> musics = musicRepository.findLatest(PageRequest.of(0, limit));
// 转换为DTO
return musics.stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
@Override
public List<MusicDTO> getHotMusic(Integer limit) {
// 查询热门音乐
List<Music> musics = musicRepository.findMostPlayed(PageRequest.of(0, limit));
// 转换为DTO
return musics.stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
@Override
@Transactional
public MusicDTO addMusic(MusicDTO musicDTO, MultipartFile file, MultipartFile cover) {
// 上传音乐文件
String musicUrl = fileService.uploadMusic(file);
// 上传封面
String coverUrl = fileService.uploadImage(cover);
// 创建音乐实体
Music music = new Music();
BeanUtils.copyProperties(musicDTO, music);
music.setUrl(musicUrl);
music.setCover(coverUrl);
music.setPlayCount(0L);
// 设置歌词
if (StringUtils.hasText(musicDTO.getLyric())) {
music.setLyric(musicDTO.getLyric());
}
// 设置音乐类型
if (musicDTO.getTypeIds() != null && !musicDTO.getTypeIds().isEmpty()) {
Set<MusicType> types = new HashSet<>();
for (Long typeId : musicDTO.getTypeIds()) {
MusicType type = musicTypeRepository.findById(typeId)
.orElseThrow(() -> new BusinessException("音乐类型不存在"));
types.add(type);
}
music.setTypes(types);
}
// 设置歌手
if (musicDTO.getSingerIds() != null && !musicDTO.getSingerIds().isEmpty()) {
// 如果有歌手ID列表优先使用
Set<Singer> singers = new HashSet<>();
for (Long singerId : musicDTO.getSingerIds()) {
Singer singer = singerRepository.findById(singerId)
.orElseThrow(() -> new BusinessException("歌手不存在"));
singers.add(singer);
}
music.setSingers(singers);
} else if (StringUtils.hasText(musicDTO.getSinger())) {
// 如果有歌手名称字符串,则处理歌手名称
String singerName = musicDTO.getSinger();
// 查找是否已存在该歌手
Optional<Singer> existingSinger = singerRepository.findByName(singerName);
if (existingSinger.isPresent()) {
// 如果歌手已存在,直接使用
Set<Singer> singers = new HashSet<>();
singers.add(existingSinger.get());
music.setSingers(singers);
} else {
// 如果歌手不存在,创建新歌手
Singer newSinger = new Singer();
newSinger.setName(singerName);
Singer savedSinger = singerRepository.save(newSinger);
Set<Singer> singers = new HashSet<>();
singers.add(savedSinger);
music.setSingers(singers);
}
}
// 保存音乐
Music savedMusic = musicRepository.save(music);
return convertToDTO(savedMusic);
}
@Override
@Transactional
public MusicDTO updateMusic(Long id, MusicDTO musicDTO, MultipartFile file, MultipartFile cover, MultipartFile lyric) {
// 获取音乐
Music music = musicRepository.findById(id)
.orElseThrow(() -> new BusinessException("音乐不存在"));
// 如果有新的音乐文件则上传并更新URL
if (file != null && !file.isEmpty()) {
// 删除旧文件
if (music.getUrl() != null) {
fileService.deleteFile(music.getUrl());
}
// 上传新文件
String musicUrl = fileService.uploadMusic(file);
music.setUrl(musicUrl);
}
// 如果有新的封面则上传并更新URL
if (cover != null && !cover.isEmpty()) {
// 删除旧封面
if (music.getCover() != null) {
fileService.deleteFile(music.getCover());
}
// 上传新封面
String coverUrl = fileService.uploadImage(cover);
music.setCover(coverUrl);
}
// 如果有新的歌词文件,则读取歌词内容
if (lyric != null && !lyric.isEmpty()) {
try {
String lyricContent = new String(lyric.getBytes(), "UTF-8");
music.setLyric(lyricContent);
} catch (Exception e) {
throw new BusinessException("读取歌词文件失败");
}
} else if (StringUtils.hasText(musicDTO.getLyric())) {
// 如果没有歌词文件,但有歌词内容,则直接使用歌词内容
music.setLyric(musicDTO.getLyric());
}
// 更新音乐信息
music.setName(musicDTO.getName());
music.setAlbum(musicDTO.getAlbum());
music.setDuration(musicDTO.getDuration());
// 更新音乐类型
if (musicDTO.getTypeIds() != null) {
Set<MusicType> types = new HashSet<>();
for (Long typeId : musicDTO.getTypeIds()) {
MusicType type = musicTypeRepository.findById(typeId)
.orElseThrow(() -> new BusinessException("音乐类型不存在"));
types.add(type);
}
music.setTypes(types);
}
// 更新歌手
if (musicDTO.getSingerIds() != null && !musicDTO.getSingerIds().isEmpty()) {
// 如果有歌手ID列表优先使用
Set<Singer> singers = new HashSet<>();
for (Long singerId : musicDTO.getSingerIds()) {
Singer singer = singerRepository.findById(singerId)
.orElseThrow(() -> new BusinessException("歌手不存在"));
singers.add(singer);
}
music.setSingers(singers);
} else if (StringUtils.hasText(musicDTO.getSinger())) {
// 如果有歌手名称字符串,则处理歌手名称
String singerName = musicDTO.getSinger();
// 查找是否已存在该歌手
Optional<Singer> existingSinger = singerRepository.findByName(singerName);
if (existingSinger.isPresent()) {
// 如果歌手已存在,直接使用
Set<Singer> singers = new HashSet<>();
singers.add(existingSinger.get());
music.setSingers(singers);
} else {
// 如果歌手不存在,创建新歌手
Singer newSinger = new Singer();
newSinger.setName(singerName);
Singer savedSinger = singerRepository.save(newSinger);
Set<Singer> singers = new HashSet<>();
singers.add(savedSinger);
music.setSingers(singers);
}
}
// 保存音乐
Music updatedMusic = musicRepository.save(music);
return convertToDTO(updatedMusic);
}
@Override
@Transactional
public boolean deleteMusic(Long id) {
// 获取音乐
Music music = musicRepository.findById(id)
.orElseThrow(() -> new BusinessException("音乐不存在"));
// 删除音乐文件
if (music.getUrl() != null) {
fileService.deleteFile(music.getUrl());
}
// 删除封面
if (music.getCover() != null) {
fileService.deleteFile(music.getCover());
}
// 删除音乐
musicRepository.delete(music);
return true;
}
@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();
// 获取音乐
Optional<Music> musicOpt = musicRepository.findById(id);
if (!musicOpt.isPresent()) {
// 如果音乐不存在返回false
return false;
}
Music music = musicOpt.get();
// 检查是否已收藏
boolean isCollected = userMusicRepository.existsByUserAndMusic(user, music);
if (isCollected) {
// 如果已收藏,则取消收藏
userMusicRepository.uncollectMusic(user.getId(), music.getId());
} else {
// 如果未收藏,则收藏
userMusicRepository.collectMusic(user.getId(), music.getId());
}
return !isCollected; // 返回当前收藏状态true表示已收藏false表示未收藏
}
@Override
public PageResult<MusicDTO> getCollectedMusic(Long userId, Integer page, Integer size) {
// 获取用户
User user = userRepository.findById(userId)
.orElseThrow(() -> new BusinessException("用户不存在"));
// 获取用户收藏的音乐
Page<UserMusic> userMusicPage = userMusicRepository.findCollectedMusic(
user.getId(),
PageRequest.of(page - 1, size, Sort.by(Sort.Direction.DESC, "collectTime"))
);
// 转换为DTO
List<MusicDTO> musicDTOs = userMusicPage.getContent().stream()
.map(userMusic -> {
MusicDTO musicDTO = convertToDTO(userMusic.getMusic());
// 设置收藏时间
musicDTO.setCollectTime(userMusic.getCollectTime());
return musicDTO;
})
.collect(Collectors.toList());
return new PageResult<>(
musicDTOs,
userMusicPage.getTotalElements(),
page,
size
);
}
@Override
@Transactional
public MusicDTO playMusic(Long id) {
// 获取音乐
Optional<Music> musicOpt = musicRepository.findById(id);
if (!musicOpt.isPresent()) {
// 如果音乐不存在返回null
return null;
}
Music music = musicOpt.get();
// 增加播放次数
music.setPlayCount(music.getPlayCount() + 1);
musicRepository.save(music);
// 如果用户已登录,记录播放历史
if (StpUtil.isLogin()) {
Long userId = StpUtil.getLoginIdAsLong();
User user = userRepository.findById(userId).orElse(null);
if (user != null) {
playHistoryRepository.recordPlayHistory(user.getId(), music.getId());
}
}
return convertToDTO(music);
}
@Override
public Music getById(Long id) {
return musicRepository.findById(id).orElse(null);
}
/**
* 将实体转换为DTO
*
* @param music 音乐实体
* @return 音乐DTO
*/
private MusicDTO convertToDTO(Music music) {
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 (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());
musicDTO.setSingerNames(singerNames);
// 设置歌手名称字符串(用于前端显示)
if (!singerNames.isEmpty()) {
musicDTO.setSinger(String.join(", ", singerNames));
}
}
// 检查当前登录用户是否已收藏该音乐
if (StpUtil.isLogin()) {
Long userId = StpUtil.getLoginIdAsLong();
boolean isCollected = userMusicRepository.existsByUserIdAndMusicId(userId, music.getId());
musicDTO.setCollected(isCollected);
} else {
musicDTO.setCollected(false);
}
return musicDTO;
}
}

View File

@@ -0,0 +1,479 @@
package com.test.musichouduan.service.impl;
import cn.dev33.satoken.stp.StpUtil;
import com.test.musichouduan.dto.MusicDTO;
import com.test.musichouduan.dto.PageResult;
import com.test.musichouduan.dto.PlaylistDTO;
import com.test.musichouduan.dto.UserDTO;
import com.test.musichouduan.entity.Music;
import com.test.musichouduan.entity.Playlist;
import com.test.musichouduan.entity.PlaylistType;
import com.test.musichouduan.entity.User;
import com.test.musichouduan.exception.BusinessException;
import com.test.musichouduan.repository.*;
import com.test.musichouduan.service.FileService;
import com.test.musichouduan.service.PlaylistService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import jakarta.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* 歌单服务实现类
*/
@Service
public class PlaylistServiceImpl implements PlaylistService {
@Autowired
private PlaylistRepository playlistRepository;
@Autowired
private PlaylistTypeRepository playlistTypeRepository;
@Autowired
private MusicRepository musicRepository;
@Autowired
private UserRepository userRepository;
@Autowired
private UserPlaylistRepository userPlaylistRepository;
@Autowired
private PlayHistoryRepository playHistoryRepository;
@Autowired
private FileService fileService;
@Override
public PlaylistDTO getPlaylistById(Long id) {
Playlist playlist = playlistRepository.findById(id)
.orElseThrow(() -> new BusinessException("歌单不存在"));
return convertToDTO(playlist);
}
@Override
public PageResult<PlaylistDTO> getPlaylistList(Integer page, Integer size, String keyword, Long typeId) {
// 构建查询条件
Specification<Playlist> spec = (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
// 关键字查询
if (StringUtils.hasText(keyword)) {
predicates.add(cb.like(root.get("name"), "%" + keyword + "%"));
}
// 类型查询
if (typeId != null) {
predicates.add(cb.equal(root.get("type").get("id"), typeId));
}
return cb.and(predicates.toArray(new Predicate[0]));
};
// 执行查询
Page<Playlist> playlistPage = playlistRepository.findAll(
spec,
PageRequest.of(page - 1, size, Sort.by(Sort.Direction.DESC, "createTime"))
);
// 转换为DTO
List<PlaylistDTO> playlistDTOs = playlistPage.getContent().stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
return new PageResult<>(
playlistDTOs,
playlistPage.getTotalElements(),
page,
size
);
}
@Override
public List<PlaylistDTO> getLatestPlaylist(Integer limit) {
// 查询最新歌单
List<Playlist> playlists = playlistRepository.findLatest(PageRequest.of(0, limit));
// 转换为DTO
return playlists.stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
@Override
public List<PlaylistDTO> getHotPlaylist(Integer limit) {
// 查询热门歌单
List<Playlist> playlists = playlistRepository.findMostPlayed(PageRequest.of(0, limit));
// 转换为DTO
return playlists.stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
@Override
public PageResult<PlaylistDTO> getOfficialPlaylist(Integer page, Integer size) {
// 查询官方歌单
Page<Playlist> playlistPage = playlistRepository.findByIsOfficial(
1,
PageRequest.of(page - 1, size, Sort.by(Sort.Direction.DESC, "createTime"))
);
// 转换为DTO
List<PlaylistDTO> playlistDTOs = playlistPage.getContent().stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
return new PageResult<>(
playlistDTOs,
playlistPage.getTotalElements(),
page,
size
);
}
@Override
@Transactional
public PlaylistDTO addPlaylist(PlaylistDTO playlistDTO, MultipartFile cover) {
// 获取当前登录用户
Long userId = StpUtil.getLoginIdAsLong();
User user = userRepository.findById(userId)
.orElseThrow(() -> new BusinessException("用户不存在"));
// 上传封面
String coverUrl = null;
if (cover != null && !cover.isEmpty()) {
coverUrl = fileService.uploadImage(cover);
}
// 创建歌单实体
Playlist playlist = new Playlist();
playlist.setName(playlistDTO.getName());
playlist.setDescription(playlistDTO.getDescription());
playlist.setCover(coverUrl);
playlist.setCreator(user);
playlist.setPlayCount(0L);
playlist.setIsOfficial(0); // 默认为非官方歌单
// 设置歌单类型
if (playlistDTO.getType() != null && playlistDTO.getType().getId() != null) {
PlaylistType type = playlistTypeRepository.findById(playlistDTO.getType().getId())
.orElseThrow(() -> new BusinessException("歌单类型不存在"));
playlist.setType(type);
}
// 保存歌单
Playlist savedPlaylist = playlistRepository.save(playlist);
return convertToDTO(savedPlaylist);
}
@Override
@Transactional
public PlaylistDTO updatePlaylist(Long id, PlaylistDTO playlistDTO, MultipartFile cover) {
// 获取歌单
Playlist playlist = playlistRepository.findById(id)
.orElseThrow(() -> new BusinessException("歌单不存在"));
// 获取当前登录用户
Long userId = StpUtil.getLoginIdAsLong();
// 检查是否是歌单创建者或管理员
if (!playlist.getCreator().getId().equals(userId) && !StpUtil.hasRole("admin")) {
throw new BusinessException("无权修改该歌单");
}
// 如果有新的封面则上传并更新URL
if (cover != null && !cover.isEmpty()) {
// 删除旧封面
if (playlist.getCover() != null) {
fileService.deleteFile(playlist.getCover());
}
// 上传新封面
String coverUrl = fileService.uploadImage(cover);
playlist.setCover(coverUrl);
}
// 更新歌单信息
playlist.setName(playlistDTO.getName());
playlist.setDescription(playlistDTO.getDescription());
// 更新是否官方字段
System.out.println("接收到的isOfficial值: " + playlistDTO.getIsOfficial());
playlist.setIsOfficial(playlistDTO.getIsOfficial());
System.out.println("更新后的歌单isOfficial值: " + playlist.getIsOfficial());
// 更新歌单类型
if (playlistDTO.getType() != null && playlistDTO.getType().getId() != null) {
PlaylistType type = playlistTypeRepository.findById(playlistDTO.getType().getId())
.orElseThrow(() -> new BusinessException("歌单类型不存在"));
playlist.setType(type);
}
// 保存歌单
Playlist updatedPlaylist = playlistRepository.save(playlist);
return convertToDTO(updatedPlaylist);
}
@Override
@Transactional
public boolean deletePlaylist(Long id) {
// 获取歌单
Playlist playlist = playlistRepository.findById(id)
.orElseThrow(() -> new BusinessException("歌单不存在"));
// 获取当前登录用户
Long userId = StpUtil.getLoginIdAsLong();
// 检查是否是歌单创建者或管理员
if (!playlist.getCreator().getId().equals(userId) && !StpUtil.hasRole("admin")) {
throw new BusinessException("无权删除该歌单");
}
// 删除封面
if (playlist.getCover() != null) {
fileService.deleteFile(playlist.getCover());
}
// 删除歌单
playlistRepository.delete(playlist);
return true;
}
@Override
@Transactional
public boolean collectPlaylist(Long id) {
// 获取当前登录用户
Long userId = StpUtil.getLoginIdAsLong();
User user = userRepository.findById(userId)
.orElseThrow(() -> new BusinessException("用户不存在"));
// 获取歌单
Playlist playlist = playlistRepository.findById(id)
.orElseThrow(() -> new BusinessException("歌单不存在"));
// 检查是否已收藏
if (userPlaylistRepository.existsByUserAndPlaylist(user, playlist)) {
throw new BusinessException("已收藏该歌单");
}
// 收藏歌单
userPlaylistRepository.collectPlaylist(user.getId(), playlist.getId());
return true;
}
@Override
@Transactional
public boolean uncollectPlaylist(Long id) {
// 获取当前登录用户
Long userId = StpUtil.getLoginIdAsLong();
User user = userRepository.findById(userId)
.orElseThrow(() -> new BusinessException("用户不存在"));
// 获取歌单
Playlist playlist = playlistRepository.findById(id)
.orElseThrow(() -> new BusinessException("歌单不存在"));
// 检查是否已收藏
if (!userPlaylistRepository.existsByUserAndPlaylist(user, playlist)) {
throw new BusinessException("未收藏该歌单");
}
// 取消收藏歌单
userPlaylistRepository.uncollectPlaylist(user.getId(), playlist.getId());
return true;
}
@Override
public PageResult<PlaylistDTO> getCollectedPlaylist(Integer page, Integer size) {
// 获取当前登录用户
Long userId = StpUtil.getLoginIdAsLong();
// 获取用户收藏的歌单
Page<Playlist> playlistPage = userPlaylistRepository.findCollectedPlaylist(
userId,
PageRequest.of(page - 1, size, Sort.by(Sort.Direction.DESC, "collectTime"))
);
// 转换为DTO
List<PlaylistDTO> playlistDTOs = playlistPage.getContent().stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
return new PageResult<>(
playlistDTOs,
playlistPage.getTotalElements(),
page,
size
);
}
@Override
public PageResult<PlaylistDTO> getUserPlaylist(Integer page, Integer size) {
// 获取当前登录用户
Long userId = StpUtil.getLoginIdAsLong();
User user = userRepository.findById(userId)
.orElseThrow(() -> new BusinessException("用户不存在"));
// 获取用户创建的歌单
Page<Playlist> playlistPage = playlistRepository.findByCreator(
user,
PageRequest.of(page - 1, size, Sort.by(Sort.Direction.DESC, "createTime"))
);
// 转换为DTO
List<PlaylistDTO> playlistDTOs = playlistPage.getContent().stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
return new PageResult<>(
playlistDTOs,
playlistPage.getTotalElements(),
page,
size
);
}
@Override
@Transactional
public boolean addMusicToPlaylist(Long id, Long musicId) {
// 获取歌单
Playlist playlist = playlistRepository.findById(id)
.orElseThrow(() -> new BusinessException("歌单不存在"));
// 获取当前登录用户
Long userId = StpUtil.getLoginIdAsLong();
// 检查是否是歌单创建者
if (!playlist.getCreator().getId().equals(userId)) {
throw new BusinessException("无权修改该歌单");
}
// 获取音乐
Music music = musicRepository.findById(musicId)
.orElseThrow(() -> new BusinessException("音乐不存在"));
// 检查音乐是否已在歌单中
if (playlist.getMusics().contains(music)) {
throw new BusinessException("该音乐已在歌单中");
}
// 添加音乐到歌单
playlist.getMusics().add(music);
playlistRepository.save(playlist);
return true;
}
@Override
@Transactional
public boolean removeMusicFromPlaylist(Long id, Long musicId) {
// 获取歌单
Playlist playlist = playlistRepository.findById(id)
.orElseThrow(() -> new BusinessException("歌单不存在"));
// 获取当前登录用户
Long userId = StpUtil.getLoginIdAsLong();
// 检查是否是歌单创建者
if (!playlist.getCreator().getId().equals(userId)) {
throw new BusinessException("无权修改该歌单");
}
// 获取音乐
Music music = musicRepository.findById(musicId)
.orElseThrow(() -> new BusinessException("音乐不存在"));
// 检查音乐是否在歌单中
if (!playlist.getMusics().contains(music)) {
throw new BusinessException("该音乐不在歌单中");
}
// 从歌单中移除音乐
playlist.getMusics().remove(music);
playlistRepository.save(playlist);
return true;
}
@Override
@Transactional
public PlaylistDTO playPlaylist(Long id) {
// 获取歌单
Playlist playlist = playlistRepository.findById(id)
.orElseThrow(() -> new BusinessException("歌单不存在"));
// 增加播放次数
playlist.setPlayCount(playlist.getPlayCount() + 1);
playlistRepository.save(playlist);
return convertToDTO(playlist);
}
@Override
public Playlist getById(Long id) {
return playlistRepository.findById(id)
.orElseThrow(() -> new BusinessException("歌单不存在"));
}
/**
* 将实体转换为DTO
*
* @param playlist 歌单实体
* @return 歌单DTO
*/
private PlaylistDTO convertToDTO(Playlist playlist) {
PlaylistDTO playlistDTO = new PlaylistDTO();
BeanUtils.copyProperties(playlist, playlistDTO);
// 设置创建者信息
if (playlist.getCreator() != null) {
UserDTO creatorDTO = new UserDTO();
BeanUtils.copyProperties(playlist.getCreator(), creatorDTO);
playlistDTO.setCreator(creatorDTO);
}
// 设置音乐列表
if (playlist.getMusics() != null && !playlist.getMusics().isEmpty()) {
List<MusicDTO> musicDTOs = playlist.getMusics().stream()
.map(music -> {
MusicDTO musicDTO = new MusicDTO();
BeanUtils.copyProperties(music, musicDTO);
return musicDTO;
})
.collect(Collectors.toList());
playlistDTO.setMusics(musicDTOs);
playlistDTO.setMusicCount(musicDTOs.size());
} else {
playlistDTO.setMusicCount(0);
}
// 检查当前登录用户是否已收藏该歌单
if (StpUtil.isLogin()) {
Long userId = StpUtil.getLoginIdAsLong();
boolean isCollected = userPlaylistRepository.existsByUserIdAndPlaylistId(userId, playlist.getId());
playlistDTO.setIsCollected(isCollected);
} else {
playlistDTO.setIsCollected(false);
}
return playlistDTO;
}
}

View File

@@ -0,0 +1,389 @@
package com.test.musichouduan.service.impl;
import cn.dev33.satoken.stp.StpUtil;
import com.test.musichouduan.dto.PageResult;
import com.test.musichouduan.dto.SingerDTO;
import com.test.musichouduan.dto.SingerTypeDTO;
import com.test.musichouduan.dto.MusicDTO;
import com.test.musichouduan.entity.Singer;
import com.test.musichouduan.entity.SingerType;
import com.test.musichouduan.entity.User;
import com.test.musichouduan.entity.Music;
import com.test.musichouduan.exception.BusinessException;
import com.test.musichouduan.repository.MusicRepository;
import com.test.musichouduan.repository.SingerRepository;
import com.test.musichouduan.repository.SingerTypeRepository;
import com.test.musichouduan.repository.UserRepository;
import com.test.musichouduan.repository.UserSingerRepository;
import com.test.musichouduan.service.FileService;
import com.test.musichouduan.service.SingerService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import jakarta.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* 歌手服务实现类
*/
@Service
public class SingerServiceImpl implements SingerService {
@Autowired
private SingerRepository singerRepository;
@Autowired
private SingerTypeRepository singerTypeRepository;
@Autowired
private MusicRepository musicRepository;
@Autowired
private UserRepository userRepository;
@Autowired
private UserSingerRepository userSingerRepository;
@Autowired
private FileService fileService;
@Override
public SingerDTO getSingerById(Long id) {
Singer singer = singerRepository.findById(id)
.orElseThrow(() -> new BusinessException("歌手不存在"));
return convertToDTO(singer);
}
@Override
public PageResult<SingerDTO> getSingerList(Integer page, Integer size, String keyword, Long typeId) {
// 构建查询条件
Specification<Singer> spec = (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
// 关键字查询
if (StringUtils.hasText(keyword)) {
predicates.add(cb.like(root.get("name"), "%" + keyword + "%"));
}
// 类型查询
if (typeId != null) {
predicates.add(cb.equal(root.get("type").get("id"), typeId));
}
return cb.and(predicates.toArray(new Predicate[0]));
};
// 执行查询
Page<Singer> singerPage = singerRepository.findAll(
spec,
PageRequest.of(page - 1, size, Sort.by(Sort.Direction.DESC, "createTime"))
);
// 转换为DTO
List<SingerDTO> singerDTOs = singerPage.getContent().stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
return new PageResult<>(
singerDTOs,
singerPage.getTotalElements(),
page,
size
);
}
@Override
public List<SingerDTO> getLatestSinger(Integer limit) {
// 查询最新歌手
List<Singer> singers = singerRepository.findLatest(PageRequest.of(0, limit));
// 转换为DTO
return singers.stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
@Override
public List<SingerDTO> getHotSinger(Integer limit) {
// 查询热门歌手
List<Singer> singers = singerRepository.findHotSingers(PageRequest.of(0, limit));
// 转换为DTO
return singers.stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
@Override
@Transactional
public SingerDTO addSinger(SingerDTO singerDTO, MultipartFile avatar) {
// 检查歌手名称是否已存在
if (singerRepository.findByName(singerDTO.getName()).isPresent()) {
throw new BusinessException("歌手名称已存在");
}
// 上传头像
String avatarUrl = null;
if (avatar != null && !avatar.isEmpty()) {
avatarUrl = fileService.uploadImage(avatar);
}
// 创建歌手实体
Singer singer = new Singer();
singer.setName(singerDTO.getName());
singer.setAvatar(avatarUrl);
singer.setIntroduction(singerDTO.getIntroduction());
// 设置歌手类型
if (singerDTO.getType() != null && singerDTO.getType().getId() != null) {
SingerType type = singerTypeRepository.findById(singerDTO.getType().getId())
.orElseThrow(() -> new BusinessException("歌手类型不存在"));
singer.setType(type);
}
// 保存歌手
Singer savedSinger = singerRepository.save(singer);
return convertToDTO(savedSinger);
}
@Override
@Transactional
public SingerDTO updateSinger(Long id, SingerDTO singerDTO, MultipartFile avatar) {
// 获取歌手
Singer singer = singerRepository.findById(id)
.orElseThrow(() -> new BusinessException("歌手不存在"));
// 检查歌手名称是否已存在
if (!singer.getName().equals(singerDTO.getName()) &&
singerRepository.findByName(singerDTO.getName()).isPresent()) {
throw new BusinessException("歌手名称已存在");
}
// 如果有新的头像则上传并更新URL
if (avatar != null && !avatar.isEmpty()) {
// 删除旧头像
if (singer.getAvatar() != null) {
fileService.deleteFile(singer.getAvatar());
}
// 上传新头像
String avatarUrl = fileService.uploadImage(avatar);
singer.setAvatar(avatarUrl);
}
// 更新歌手信息
singer.setName(singerDTO.getName());
singer.setIntroduction(singerDTO.getIntroduction());
// 更新歌手类型
if (singerDTO.getType() != null && singerDTO.getType().getId() != null) {
SingerType type = singerTypeRepository.findById(singerDTO.getType().getId())
.orElseThrow(() -> new BusinessException("歌手类型不存在"));
singer.setType(type);
}
// 保存歌手
Singer updatedSinger = singerRepository.save(singer);
return convertToDTO(updatedSinger);
}
@Override
@Transactional
public boolean deleteSinger(Long id) {
// 获取歌手
Singer singer = singerRepository.findById(id)
.orElseThrow(() -> new BusinessException("歌手不存在"));
// 删除头像
if (singer.getAvatar() != null) {
fileService.deleteFile(singer.getAvatar());
}
// 删除歌手
singerRepository.delete(singer);
return true;
}
@Override
@Transactional
public boolean collectSinger(Long id) {
// 获取当前登录用户
Long userId = StpUtil.getLoginIdAsLong();
User user = userRepository.findById(userId)
.orElseThrow(() -> new BusinessException("用户不存在"));
// 获取歌手
Singer singer = singerRepository.findById(id)
.orElseThrow(() -> new BusinessException("歌手不存在"));
// 检查是否已收藏
if (userSingerRepository.existsByUserAndSinger(user, singer)) {
throw new BusinessException("已收藏该歌手");
}
// 收藏歌手
userSingerRepository.collectSinger(user.getId(), singer.getId());
return true;
}
@Override
@Transactional
public boolean uncollectSinger(Long id) {
// 获取当前登录用户
Long userId = StpUtil.getLoginIdAsLong();
User user = userRepository.findById(userId)
.orElseThrow(() -> new BusinessException("用户不存在"));
// 获取歌手
Singer singer = singerRepository.findById(id)
.orElseThrow(() -> new BusinessException("歌手不存在"));
// 检查是否已收藏
if (!userSingerRepository.existsByUserAndSinger(user, singer)) {
throw new BusinessException("未收藏该歌手");
}
// 取消收藏歌手
userSingerRepository.uncollectSinger(user.getId(), singer.getId());
return true;
}
@Override
public PageResult<SingerDTO> getCollectedSinger(Integer page, Integer size) {
// 获取当前登录用户
Long userId = StpUtil.getLoginIdAsLong();
// 获取用户收藏的歌手
Page<Singer> singerPage = userSingerRepository.findCollectedSinger(
userId,
PageRequest.of(page - 1, size, Sort.by(Sort.Direction.DESC, "collectTime"))
);
// 转换为DTO
List<SingerDTO> singerDTOs = singerPage.getContent().stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
return new PageResult<>(
singerDTOs,
singerPage.getTotalElements(),
page,
size
);
}
@Override
@Transactional
public boolean addMusicToSinger(Long singerId, Long musicId) {
// 获取歌手
Singer singer = singerRepository.findById(singerId)
.orElseThrow(() -> new BusinessException("歌手不存在"));
// 获取音乐
Music music = musicRepository.findById(musicId)
.orElseThrow(() -> new BusinessException("音乐不存在"));
// 检查是否已关联(双向检查)
if (singer.getMusics().contains(music) || music.getSingers().contains(singer)) {
throw new BusinessException("该音乐已关联到歌手");
}
// 添加关联并保存
singer.getMusics().add(music);
singerRepository.save(singer);
return true;
}
@Override
@Transactional
public boolean removeMusicFromSinger(Long singerId, Long musicId) {
// 获取歌手
Singer singer = singerRepository.findById(singerId)
.orElseThrow(() -> new BusinessException("歌手不存在"));
// 获取音乐
Music music = musicRepository.findById(musicId)
.orElseThrow(() -> new BusinessException("音乐不存在"));
// 检查是否已关联
if (!singer.getMusics().contains(music)) {
throw new BusinessException("该音乐未关联到歌手");
}
// 移除关联
singer.getMusics().remove(music);
singerRepository.save(singer);
return true;
}
@Override
public Singer getById(Long id) {
return singerRepository.findById(id)
.orElseThrow(() -> new BusinessException("歌手不存在"));
}
/**
* 将实体转换为DTO
*
* @param singer 歌手实体
* @return 歌手DTO
*/
private SingerDTO convertToDTO(Singer singer) {
SingerDTO singerDTO = new SingerDTO();
BeanUtils.copyProperties(singer, singerDTO);
// 设置类型信息
if (singer.getType() != null) {
SingerTypeDTO typeDTO = new SingerTypeDTO();
BeanUtils.copyProperties(singer.getType(), typeDTO);
singerDTO.setType(typeDTO);
}
// 设置音乐数量
Long musicCount = musicRepository.countBySingersContaining(singer);
singerDTO.setMusicCount(musicCount);
// 设置粉丝数量
Long fansCount = userSingerRepository.count();
singerDTO.setFansCount(fansCount);
// 检查当前登录用户是否已收藏该歌手
if (StpUtil.isLogin()) {
Long userId = StpUtil.getLoginIdAsLong();
boolean isCollected = userSingerRepository.existsByUserIdAndSingerId(userId, singer.getId());
singerDTO.setIsCollected(isCollected);
} else {
singerDTO.setIsCollected(false);
}
// 设置关联音乐列表
if (singer.getMusics() != null) {
singerDTO.setMusics(singer.getMusics().stream()
.map(music -> {
MusicDTO musicDTO = new MusicDTO();
BeanUtils.copyProperties(music, musicDTO);
return musicDTO;
})
.collect(Collectors.toList()));
}
return singerDTO;
}
}

View File

@@ -0,0 +1,175 @@
package com.test.musichouduan.service.impl;
import com.test.musichouduan.dto.MusicDTO;
import com.test.musichouduan.dto.MusicTypeDTO;
import com.test.musichouduan.dto.UserDTO;
import com.test.musichouduan.entity.Music;
import com.test.musichouduan.entity.MusicType;
import com.test.musichouduan.entity.User;
import com.test.musichouduan.repository.*;
import com.test.musichouduan.service.StatService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
/**
* 统计服务实现类
*/
@Service
public class StatServiceImpl implements StatService {
@Autowired
private UserRepository userRepository;
@Autowired
private MusicRepository musicRepository;
@Autowired
private PlaylistRepository playlistRepository;
@Autowired
private SingerRepository singerRepository;
@Autowired
private MusicTypeRepository musicTypeRepository;
@Autowired
private PlayHistoryRepository playHistoryRepository;
@Override
public Map<String, Object> getTotalStats() {
Map<String, Object> stats = new HashMap<>();
stats.put("userCount", userRepository.count());
stats.put("musicCount", musicRepository.count());
stats.put("playlistCount", playlistRepository.count());
stats.put("singerCount", singerRepository.count());
// 计算总播放量
long totalPlayCount = 0;
List<Music> allMusic = musicRepository.findAll();
for (Music music : allMusic) {
totalPlayCount += music.getPlayCount() != null ? music.getPlayCount() : 0;
}
stats.put("playCount", totalPlayCount);
return stats;
}
@Override
public Map<String, Object> getUserGrowthTrend(Integer days) {
Map<String, Object> stats = new HashMap<>();
List<String> dateList = new ArrayList<>();
List<Long> countList = new ArrayList<>();
// 获取日期列表
LocalDate endDate = LocalDate.now();
LocalDate startDate = endDate.minusDays(days - 1);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM-dd");
for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) {
dateList.add(date.format(formatter));
// 计算当天注册用户数
LocalDateTime start = LocalDateTime.of(date, LocalTime.MIN);
LocalDateTime end = LocalDateTime.of(date, LocalTime.MAX);
long count = userRepository.countByCreateTimeBetween(start, end);
countList.add(count);
}
stats.put("dates", dateList);
stats.put("counts", countList);
return stats;
}
@Override
public Map<String, Object> getMusicTypeDistribution() {
Map<String, Object> stats = new HashMap<>();
List<String> nameList = new ArrayList<>();
List<Long> countList = new ArrayList<>();
// 获取所有音乐类型
List<MusicType> types = musicTypeRepository.findAll();
for (MusicType type : types) {
nameList.add(type.getName());
long count = musicRepository.countByTypesContaining(type);
countList.add(count);
}
stats.put("names", nameList);
stats.put("counts", countList);
return stats;
}
@Override
public Map<String, Object> getPlayCountStats(Integer days) {
Map<String, Object> stats = new HashMap<>();
List<String> dateList = new ArrayList<>();
List<Long> countList = new ArrayList<>();
// 获取日期列表
LocalDate endDate = LocalDate.now();
LocalDate startDate = endDate.minusDays(days - 1);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM-dd");
for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) {
dateList.add(date.format(formatter));
// 计算当天播放次数
LocalDateTime start = LocalDateTime.of(date, LocalTime.MIN);
LocalDateTime end = LocalDateTime.of(date, LocalTime.MAX);
long count = playHistoryRepository.countByPlayTimeBetween(start, end);
countList.add(count);
}
stats.put("dates", dateList);
stats.put("counts", countList);
return stats;
}
@Override
public Map<String, Object> getLatestUsers(Integer limit) {
Map<String, Object> stats = new HashMap<>();
// 获取最新用户
List<User> users = userRepository.findAll(
PageRequest.of(0, limit, Sort.by(Sort.Direction.DESC, "createTime"))
).getContent();
List<UserDTO> userDTOList = users.stream().map(user -> {
UserDTO userDTO = new UserDTO();
BeanUtils.copyProperties(user, userDTO);
return userDTO;
}).collect(Collectors.toList());
stats.put("users", userDTOList);
return stats;
}
@Override
public Map<String, Object> getLatestMusics(Integer limit) {
Map<String, Object> stats = new HashMap<>();
// 获取最新音乐
List<Music> musics = musicRepository.findAll(
PageRequest.of(0, limit, Sort.by(Sort.Direction.DESC, "createTime"))
).getContent();
List<MusicDTO> musicDTOList = musics.stream().map(music -> {
MusicDTO musicDTO = new MusicDTO();
BeanUtils.copyProperties(music, musicDTO);
return musicDTO;
}).collect(Collectors.toList());
stats.put("musics", musicDTOList);
return stats;
}
}

View File

@@ -0,0 +1,395 @@
package com.test.musichouduan.service.impl;
import com.test.musichouduan.dto.MusicTypeDTO;
import com.test.musichouduan.dto.PageResult;
import com.test.musichouduan.dto.PlaylistTypeDTO;
import com.test.musichouduan.dto.SingerTypeDTO;
import com.test.musichouduan.entity.MusicType;
import com.test.musichouduan.entity.PlaylistType;
import com.test.musichouduan.entity.SingerType;
import com.test.musichouduan.exception.BusinessException;
import com.test.musichouduan.repository.MusicRepository;
import com.test.musichouduan.repository.MusicTypeRepository;
import com.test.musichouduan.repository.PlaylistRepository;
import com.test.musichouduan.repository.PlaylistTypeRepository;
import com.test.musichouduan.repository.SingerRepository;
import com.test.musichouduan.repository.SingerTypeRepository;
import com.test.musichouduan.service.TypeService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import jakarta.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* 类型服务实现类
*/
@Service
public class TypeServiceImpl implements TypeService {
@Autowired
private MusicTypeRepository musicTypeRepository;
@Autowired
private SingerTypeRepository singerTypeRepository;
@Autowired
private PlaylistTypeRepository playlistTypeRepository;
@Autowired
private MusicRepository musicRepository;
@Autowired
private SingerRepository singerRepository;
@Autowired
private PlaylistRepository playlistRepository;
@Override
public List<MusicTypeDTO> getMusicTypeList() {
List<MusicType> types = musicTypeRepository.findAll(Sort.by(Sort.Direction.ASC, "id"));
return types.stream()
.map(this::convertToMusicTypeDTO)
.collect(Collectors.toList());
}
@Override
public PageResult<MusicTypeDTO> getMusicTypePage(Integer page, Integer size, String keyword) {
// 构建查询条件
Specification<MusicType> spec = (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
// 关键字查询
if (StringUtils.hasText(keyword)) {
predicates.add(cb.like(root.get("name"), "%" + keyword + "%"));
}
return cb.and(predicates.toArray(new Predicate[0]));
};
// 执行查询
Page<MusicType> typePage = musicTypeRepository.findAll(
spec,
PageRequest.of(page - 1, size, Sort.by(Sort.Direction.ASC, "id"))
);
// 转换为DTO
List<MusicTypeDTO> typeDTOs = typePage.getContent().stream()
.map(this::convertToMusicTypeDTO)
.collect(Collectors.toList());
return new PageResult<>(
typeDTOs,
typePage.getTotalElements(),
page,
size
);
}
@Override
@Transactional
public MusicTypeDTO addMusicType(MusicTypeDTO musicTypeDTO) {
// 检查名称是否已存在
if (musicTypeRepository.existsByName(musicTypeDTO.getName())) {
throw new BusinessException("音乐类型名称已存在");
}
// 创建音乐类型实体
MusicType musicType = new MusicType();
musicType.setName(musicTypeDTO.getName());
musicType.setDescription(musicTypeDTO.getDescription());
// 保存音乐类型
MusicType savedMusicType = musicTypeRepository.save(musicType);
return convertToMusicTypeDTO(savedMusicType);
}
@Override
@Transactional
public MusicTypeDTO updateMusicType(Long id, MusicTypeDTO musicTypeDTO) {
// 获取音乐类型
MusicType musicType = musicTypeRepository.findById(id)
.orElseThrow(() -> new BusinessException("音乐类型不存在"));
// 检查名称是否已存在
if (!musicType.getName().equals(musicTypeDTO.getName()) &&
musicTypeRepository.existsByName(musicTypeDTO.getName())) {
throw new BusinessException("音乐类型名称已存在");
}
// 更新音乐类型信息
musicType.setName(musicTypeDTO.getName());
musicType.setDescription(musicTypeDTO.getDescription());
// 保存音乐类型
MusicType updatedMusicType = musicTypeRepository.save(musicType);
return convertToMusicTypeDTO(updatedMusicType);
}
@Override
@Transactional
public boolean deleteMusicType(Long id) {
// 获取音乐类型
MusicType musicType = musicTypeRepository.findById(id)
.orElseThrow(() -> new BusinessException("音乐类型不存在"));
// 检查是否有音乐使用该类型
if (musicRepository.existsByTypesContaining(musicType)) {
throw new BusinessException("该音乐类型下有音乐,无法删除");
}
// 删除音乐类型
musicTypeRepository.delete(musicType);
return true;
}
@Override
public List<SingerTypeDTO> getSingerTypeList() {
List<SingerType> types = singerTypeRepository.findAll(Sort.by(Sort.Direction.ASC, "id"));
return types.stream()
.map(this::convertToSingerTypeDTO)
.collect(Collectors.toList());
}
@Override
public PageResult<SingerTypeDTO> getSingerTypePage(Integer page, Integer size, String keyword) {
// 构建查询条件
Specification<SingerType> spec = (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
// 关键字查询
if (StringUtils.hasText(keyword)) {
predicates.add(cb.like(root.get("name"), "%" + keyword + "%"));
}
return cb.and(predicates.toArray(new Predicate[0]));
};
// 执行查询
Page<SingerType> typePage = singerTypeRepository.findAll(
spec,
PageRequest.of(page - 1, size, Sort.by(Sort.Direction.ASC, "id"))
);
// 转换为DTO
List<SingerTypeDTO> typeDTOs = typePage.getContent().stream()
.map(this::convertToSingerTypeDTO)
.collect(Collectors.toList());
return new PageResult<>(
typeDTOs,
typePage.getTotalElements(),
page,
size
);
}
@Override
@Transactional
public SingerTypeDTO addSingerType(SingerTypeDTO singerTypeDTO) {
// 检查名称是否已存在
if (singerTypeRepository.existsByName(singerTypeDTO.getName())) {
throw new BusinessException("歌手类型名称已存在");
}
// 创建歌手类型实体
SingerType singerType = new SingerType();
singerType.setName(singerTypeDTO.getName());
singerType.setDescription(singerTypeDTO.getDescription());
// 保存歌手类型
SingerType savedSingerType = singerTypeRepository.save(singerType);
return convertToSingerTypeDTO(savedSingerType);
}
@Override
@Transactional
public SingerTypeDTO updateSingerType(Long id, SingerTypeDTO singerTypeDTO) {
// 获取歌手类型
SingerType singerType = singerTypeRepository.findById(id)
.orElseThrow(() -> new BusinessException("歌手类型不存在"));
// 检查名称是否已存在
if (!singerType.getName().equals(singerTypeDTO.getName()) &&
singerTypeRepository.existsByName(singerTypeDTO.getName())) {
throw new BusinessException("歌手类型名称已存在");
}
// 更新歌手类型信息
singerType.setName(singerTypeDTO.getName());
singerType.setDescription(singerTypeDTO.getDescription());
// 保存歌手类型
SingerType updatedSingerType = singerTypeRepository.save(singerType);
return convertToSingerTypeDTO(updatedSingerType);
}
@Override
@Transactional
public boolean deleteSingerType(Long id) {
// 获取歌手类型
SingerType singerType = singerTypeRepository.findById(id)
.orElseThrow(() -> new BusinessException("歌手类型不存在"));
// 检查是否有歌手使用该类型
if (singerRepository.existsByType(singerType)) {
throw new BusinessException("该歌手类型下有歌手,无法删除");
}
// 删除歌手类型
singerTypeRepository.delete(singerType);
return true;
}
@Override
public List<PlaylistTypeDTO> getPlaylistTypeList() {
List<PlaylistType> types = playlistTypeRepository.findAll(Sort.by(Sort.Direction.ASC, "id"));
return types.stream()
.map(this::convertToPlaylistTypeDTO)
.collect(Collectors.toList());
}
@Override
public PageResult<PlaylistTypeDTO> getPlaylistTypePage(Integer page, Integer size, String keyword) {
// 构建查询条件
Specification<PlaylistType> spec = (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
// 关键字查询
if (StringUtils.hasText(keyword)) {
predicates.add(cb.like(root.get("name"), "%" + keyword + "%"));
}
return cb.and(predicates.toArray(new Predicate[0]));
};
// 执行查询
Page<PlaylistType> typePage = playlistTypeRepository.findAll(
spec,
PageRequest.of(page - 1, size, Sort.by(Sort.Direction.ASC, "id"))
);
// 转换为DTO
List<PlaylistTypeDTO> typeDTOs = typePage.getContent().stream()
.map(this::convertToPlaylistTypeDTO)
.collect(Collectors.toList());
return new PageResult<>(
typeDTOs,
typePage.getTotalElements(),
page,
size
);
}
@Override
@Transactional
public PlaylistTypeDTO addPlaylistType(PlaylistTypeDTO playlistTypeDTO) {
// 检查名称是否已存在
if (playlistTypeRepository.existsByName(playlistTypeDTO.getName())) {
throw new BusinessException("歌单类型名称已存在");
}
// 创建歌单类型实体
PlaylistType playlistType = new PlaylistType();
playlistType.setName(playlistTypeDTO.getName());
playlistType.setDescription(playlistTypeDTO.getDescription());
// 保存歌单类型
PlaylistType savedPlaylistType = playlistTypeRepository.save(playlistType);
return convertToPlaylistTypeDTO(savedPlaylistType);
}
@Override
@Transactional
public PlaylistTypeDTO updatePlaylistType(Long id, PlaylistTypeDTO playlistTypeDTO) {
// 获取歌单类型
PlaylistType playlistType = playlistTypeRepository.findById(id)
.orElseThrow(() -> new BusinessException("歌单类型不存在"));
// 检查名称是否已存在
if (!playlistType.getName().equals(playlistTypeDTO.getName()) &&
playlistTypeRepository.existsByName(playlistTypeDTO.getName())) {
throw new BusinessException("歌单类型名称已存在");
}
// 更新歌单类型信息
playlistType.setName(playlistTypeDTO.getName());
playlistType.setDescription(playlistTypeDTO.getDescription());
// 保存歌单类型
PlaylistType updatedPlaylistType = playlistTypeRepository.save(playlistType);
return convertToPlaylistTypeDTO(updatedPlaylistType);
}
@Override
@Transactional
public boolean deletePlaylistType(Long id) {
// 获取歌单类型
PlaylistType playlistType = playlistTypeRepository.findById(id)
.orElseThrow(() -> new BusinessException("歌单类型不存在"));
// 检查是否有歌单使用该类型
if (playlistRepository.existsByType(playlistType)) {
throw new BusinessException("该歌单类型下有歌单,无法删除");
}
// 删除歌单类型
playlistTypeRepository.delete(playlistType);
return true;
}
/**
* 将音乐类型实体转换为DTO
*
* @param musicType 音乐类型实体
* @return 音乐类型DTO
*/
private MusicTypeDTO convertToMusicTypeDTO(MusicType musicType) {
MusicTypeDTO musicTypeDTO = new MusicTypeDTO();
BeanUtils.copyProperties(musicType, musicTypeDTO);
return musicTypeDTO;
}
/**
* 将歌手类型实体转换为DTO
*
* @param singerType 歌手类型实体
* @return 歌手类型DTO
*/
private SingerTypeDTO convertToSingerTypeDTO(SingerType singerType) {
SingerTypeDTO singerTypeDTO = new SingerTypeDTO();
BeanUtils.copyProperties(singerType, singerTypeDTO);
return singerTypeDTO;
}
/**
* 将歌单类型实体转换为DTO
*
* @param playlistType 歌单类型实体
* @return 歌单类型DTO
*/
private PlaylistTypeDTO convertToPlaylistTypeDTO(PlaylistType playlistType) {
PlaylistTypeDTO playlistTypeDTO = new PlaylistTypeDTO();
BeanUtils.copyProperties(playlistType, playlistTypeDTO);
return playlistTypeDTO;
}
}

View File

@@ -0,0 +1,355 @@
package com.test.musichouduan.service.impl;
import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import com.test.musichouduan.dto.*;
import com.test.musichouduan.entity.User;
import com.test.musichouduan.repository.*;
import com.test.musichouduan.service.UserService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import jakarta.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* 用户服务实现类
*/
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private UserCollectMusicRepository userCollectMusicRepository;
@Autowired
private UserCollectPlaylistRepository userCollectPlaylistRepository;
@Autowired
private UserCollectSingerRepository userCollectSingerRepository;
@Autowired
private PlaylistRepository playlistRepository;
@Override
@Transactional
public UserDTO register(RegisterRequest request) {
// 检查用户名是否已存在
if (userRepository.existsByUsername(request.getUsername())) {
throw new RuntimeException("用户名已存在");
}
// 检查邮箱是否已存在
if (userRepository.existsByEmail(request.getEmail())) {
throw new RuntimeException("邮箱已存在");
}
// 检查两次密码是否一致
if (!request.getPassword().equals(request.getConfirmPassword())) {
throw new RuntimeException("两次密码不一致");
}
// 创建用户
User user = new User();
user.setUsername(request.getUsername());
user.setPassword(BCrypt.hashpw(request.getPassword()));
user.setEmail(request.getEmail());
user.setNickname(request.getNickname() != null ? request.getNickname() : request.getUsername());
user.setGender(0);
user.setRole(0);
user.setStatus(1);
// 保存用户
user = userRepository.save(user);
// 返回用户DTO
return convertToDTO(user);
}
@Override
public LoginResultDTO login(LoginRequest request) {
// 根据用户名查找用户
User user = userRepository.findByUsername(request.getUsername())
.orElseThrow(() -> new RuntimeException("用户名或密码错误"));
// 检查用户状态
if (user.getStatus() == 0) {
throw new RuntimeException("账号已被禁用");
}
// 检查密码
if (!BCrypt.checkpw(request.getPassword(), user.getPassword())) {
throw new RuntimeException("用户名或密码错误");
}
// 登录
StpUtil.login(user.getId());
// 设置角色
if (user.getRole() == 1) {
// 打印调试信息
System.out.println("设置用户ID: " + user.getId() + " 为admin角色");
}
// 获取令牌信息
String token = StpUtil.getTokenValue();
String tokenName = StpUtil.getTokenName();
// 创建登录结果DTO
LoginResultDTO resultDTO = new LoginResultDTO();
resultDTO.setUser(convertToDTO(user));
resultDTO.setToken(token);
resultDTO.setTokenName(tokenName);
resultDTO.setTokenPrefix("");
// 返回登录结果DTO
return resultDTO;
}
@Override
public UserDTO getCurrentUser() {
// 获取当前登录用户ID
Long userId = StpUtil.getLoginIdAsLong();
// 根据ID查找用户
User user = userRepository.findById(userId)
.orElseThrow(() -> new RuntimeException("用户不存在"));
// 返回用户DTO
return getUserStats(userId);
}
@Override
public UserDTO getUserById(Long id) {
// 根据ID查找用户
User user = userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("用户不存在"));
// 返回用户DTO
return convertToDTO(user);
}
@Override
public UserDTO getUserByUsername(String username) {
// 根据用户名查找用户
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new RuntimeException("用户不存在"));
// 返回用户DTO
return convertToDTO(user);
}
@Override
@Transactional
public UserDTO updateUser(UserUpdateRequest request) {
// 获取当前登录用户ID
Long userId = StpUtil.getLoginIdAsLong();
// 根据ID查找用户
User user = userRepository.findById(userId)
.orElseThrow(() -> new RuntimeException("用户不存在"));
// 更新用户信息
if (request.getNickname() != null) {
user.setNickname(request.getNickname());
}
if (request.getAvatar() != null) {
user.setAvatar(request.getAvatar());
}
if (request.getGender() != null) {
user.setGender(request.getGender());
}
if (request.getEmail() != null) {
user.setEmail(request.getEmail());
}
if (request.getPhone() != null) {
user.setPhone(request.getPhone());
}
if (request.getIntroduction() != null) {
user.setIntroduction(request.getIntroduction());
}
// 保存用户
user = userRepository.save(user);
// 返回用户DTO
return convertToDTO(user);
}
@Override
@Transactional
public boolean updatePassword(PasswordUpdateRequest request) {
// 获取当前登录用户ID
Long userId = StpUtil.getLoginIdAsLong();
// 根据ID查找用户
User user = userRepository.findById(userId)
.orElseThrow(() -> new RuntimeException("用户不存在"));
// 检查旧密码
if (!BCrypt.checkpw(request.getOldPassword(), user.getPassword())) {
throw new RuntimeException("旧密码错误");
}
// 检查两次密码是否一致
if (!request.getNewPassword().equals(request.getConfirmPassword())) {
throw new RuntimeException("两次密码不一致");
}
// 更新密码
user.setPassword(BCrypt.hashpw(request.getNewPassword()));
// 保存用户
userRepository.save(user);
return true;
}
@Override
public UserDTO getUserStats(Long userId) {
// 根据ID查找用户
User user = userRepository.findById(userId)
.orElseThrow(() -> new RuntimeException("用户不存在"));
// 转换为DTO
UserDTO userDTO = convertToDTO(user);
// 获取统计数据
userDTO.setCollectMusicCount(userCollectMusicRepository.countByUser(user));
userDTO.setCollectPlaylistCount(userCollectPlaylistRepository.countByUser(user));
userDTO.setCollectSingerCount(userCollectSingerRepository.countByUser(user));
userDTO.setCreatePlaylistCount(playlistRepository.countByCreator(user));
return userDTO;
}
@Override
public PageResult<UserDTO> getUserList(Integer page, Integer size, String keyword) {
// 创建分页对象
Pageable pageable = PageRequest.of(page - 1, size, Sort.by(Sort.Direction.DESC, "createTime"));
// 创建查询条件
Specification<User> specification = (root, query, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
// 关键字查询
if (keyword != null && !keyword.isEmpty()) {
predicates.add(criteriaBuilder.or(
criteriaBuilder.like(root.get("username"), "%" + keyword + "%"),
criteriaBuilder.like(root.get("nickname"), "%" + keyword + "%"),
criteriaBuilder.like(root.get("email"), "%" + keyword + "%")
));
}
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};
// 查询
Page<User> userPage = userRepository.findAll(specification, pageable);
// 转换为DTO
List<UserDTO> userDTOList = userPage.getContent().stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
// 返回分页结果
return new PageResult<>(userDTOList, userPage.getTotalElements(), page, size);
}
@Override
@Transactional
public boolean deleteUser(Long id) {
// 检查用户是否存在
if (!userRepository.existsById(id)) {
throw new RuntimeException("用户不存在");
}
// 删除用户
userRepository.deleteById(id);
return true;
}
@Override
@Transactional
public boolean updateUserStatus(Long id, Integer status) {
// 根据ID查找用户
User user = userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("用户不存在"));
// 更新状态
user.setStatus(status);
// 保存用户
userRepository.save(user);
return true;
}
@Override
public UserDTO adminUpdateUser(Long id, UserUpdateRequest request) {
User user = getById(id);
// 更新用户信息
if (request.getNickname() != null) {
user.setNickname(request.getNickname());
}
if (request.getAvatar() != null) {
user.setAvatar(request.getAvatar());
}
if (request.getGender() != null) {
user.setGender(request.getGender());
}
if (request.getEmail() != null) {
user.setEmail(request.getEmail());
}
if (request.getPhone() != null) {
user.setPhone(request.getPhone());
}
if (request.getIntroduction() != null) {
user.setIntroduction(request.getIntroduction());
}
// 保存用户
User savedUser = userRepository.save(user);
return convertToDTO(savedUser);
}
@Override
public User getById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("用户不存在"));
}
/**
* 将用户实体转换为DTO
*
* @param user 用户实体
* @return 用户DTO
*/
private UserDTO convertToDTO(User user) {
UserDTO userDTO = new UserDTO();
BeanUtils.copyProperties(user, userDTO);
// 确保头像URL是完整路径
if (userDTO.getAvatar() != null && !userDTO.getAvatar().startsWith("http")) {
userDTO.setAvatar("http://localhost:8085" + userDTO.getAvatar());
}
return userDTO;
}
}

Some files were not shown because too many files have changed in this diff Show More