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

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 支持
}
}