目录
前言
项目功能
技术栈
后端
前端
开发环境
项目展示
前台-首页-展示
前台-首页-代码
前台-歌单-展示
前台-歌单-代码
前台-歌手-展示
前台-歌手-代码
前台-其他页面展示
后台-登录-展示
后台-登录-代码
后台-首页-展示
首台-首页-代码
后台-其他页面-展示
视频展示
详细步骤及代码
Pom.xml导包
1.启动类
2.过滤器
3.Redis配置
4.application.properties配置文件
5.log4j.properties配置文件
源码获取
前言
本音乐网站的客户端和管理端使用 Vue 框架来实现,服务端使用 Spring Boot + MyBatis 来实现,数据库使用了 MySQL。
项目功能
- 音乐播放
- 用户登录注册
- 用户信息编辑、头像修改
- 歌曲、歌单搜索
- 歌单打分
- 歌单、歌曲评论
- 歌单列表、歌手列表分页显示
- 歌词同步显示
- 音乐收藏、下载、拖动控制、音量控制
- 后台对用户、歌曲、歌手、歌单信息的管理
技术栈
后端
SpringBoot + MyBatis + Redis
前端
Vue3.0 + TypeScript + Vue-Router + Vuex + Axios + ElementPlus + Echarts
开发环境
JDK: jdk-8u141
mysql:mysql-5.7.21-1-macos10.13-x86_64(或者更高版本)
redis:5.0.8
node:14.17.3
IDE:IntelliJ IDEA 2018、VSCode
项目展示
前台-首页-展示
前台-首页-代码
<template>
<el-container>
<el-header>
<yin-header></yin-header>
</el-header>
<el-main>
<router-view />
<yin-current-play></yin-current-play>
<yin-play-bar></yin-play-bar>
<yin-scroll-top></yin-scroll-top>
<yin-audio></yin-audio>
</el-main>
<el-footer>
<yin-footer></yin-footer>
</el-footer>
</el-container>
</template>
<script lang="ts" setup>
import { getCurrentInstance } from "vue";
import YinHeader from "@/components/layouts/YinHeader.vue";
import YinCurrentPlay from "@/components/layouts/YinCurrentPlay.vue";
import YinPlayBar from "@/components/layouts/YinPlayBar.vue";
import YinScrollTop from "@/components/layouts/YinScrollTop.vue";
import YinFooter from "@/components/layouts/YinFooter.vue";
import YinAudio from "@/components/layouts/YinAudio.vue";
const { proxy } = getCurrentInstance();
if (sessionStorage.getItem("dataStore")) {
proxy.$store.replaceState(Object.assign({}, proxy.$store.state, JSON.parse(sessionStorage.getItem("dataStore"))));
}
window.addEventListener("beforeunload", () => {
sessionStorage.setItem("dataStore", JSON.stringify(proxy.$store.state));
});
</script>
<style lang="scss" scoped>
@import "@/assets/css/var.scss";
@import "@/assets/css/global.scss";
.el-container {
min-height: calc(100% - 60px);
}
.el-header {
padding: 0;
}
.el-main {
padding-left: 0;
padding-right: 0;
}
</style>
前台-歌单-展示
前台-歌单-代码
<template>
<div class="play-list-container">
<yin-nav :styleList="songStyle" :activeName="activeName" @click="handleChangeView"></yin-nav>
<play-list :playList="data" path="song-sheet-detail"></play-list>
<el-pagination
class="pagination"
background
layout="total, prev, pager, next"
:current-page="currentPage"
:page-size="pageSize"
:total="allPlayList.length"
@current-change="handleCurrentChange"
>
</el-pagination>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, computed } from "vue";
import YinNav from "@/components/layouts/YinNav.vue";
import PlayList from "@/components/PlayList.vue";
import { SONGSTYLE } from "@/enums";
import { HttpManager } from "@/api";
export default defineComponent({
components: {
YinNav,
PlayList,
},
setup() {
const activeName = ref("全部歌单");
const pageSize = ref(15); // 页数
const currentPage = ref(1); // 当前页
const songStyle = ref(SONGSTYLE); // 歌单导航栏类别
const allPlayList = ref([]); // 歌单
const data = computed(() => allPlayList.value.slice((currentPage.value - 1) * pageSize.value, currentPage.value * pageSize.value));
// 获取全部歌单
async function getSongList() {
allPlayList.value = ((await HttpManager.getSongList()) as ResponseBody).data;
currentPage.value = 1;
}
// 通过类别获取歌单
async function getSongListOfStyle(style) {
allPlayList.value = ((await HttpManager.getSongListOfStyle(style)) as ResponseBody).data;
currentPage.value = 1;
}
try {
getSongList();
} catch (error) {
console.error(error);
}
// 获取歌单
async function handleChangeView(item) {
activeName.value = item.name;
allPlayList.value = [];
try {
if (item.name === "全部歌单") {
await getSongList();
} else {
await getSongListOfStyle(item.name);
}
} catch (error) {
console.error(error);
}
}
// 获取当前页
function handleCurrentChange(val) {
currentPage.value = val;
}
return {
activeName,
songStyle,
pageSize,
currentPage,
allPlayList,
data,
handleChangeView,
handleCurrentChange,
};
},
});
</script>
前台-歌手-展示
前台-歌手-代码
<template>
<div class="play-list-container">
<yin-nav :styleList="singerStyle" :activeName="activeName" @click="handleChangeView"></yin-nav>
<play-list :playList="data" path="singer-detail"></play-list>
<el-pagination
class="pagination"
background
layout="total, prev, pager, next"
:current-page="currentPage"
:page-size="pageSize"
:total="allPlayList.length"
@current-change="handleCurrentChange"
>
</el-pagination>
</div>
</template>
<script lang="ts" setup>
import { ref, computed } from "vue";
import YinNav from "@/components/layouts/YinNav.vue";
import PlayList from "@/components/PlayList.vue";
import { singerStyle } from "@/enums";
import { HttpManager } from "@/api";
// data
const activeName = ref("全部歌手");
const pageSize = ref(15); // 页数
const currentPage = ref(1); // 当前页
const allPlayList = ref([]);
// computed
const data = computed(() => {
return allPlayList.value.slice((currentPage.value - 1) * pageSize.value, currentPage.value * pageSize.value);
});
// 获取所有歌手
async function getAllSinger() {
const result = (await HttpManager.getAllSinger()) as ResponseBody;
currentPage.value = 1;
allPlayList.value = result.data;
}
getAllSinger();
// 获取当前页
function handleCurrentChange(val) {
currentPage.value = val;
}
function handleChangeView(item) {
activeName.value = item.name;
allPlayList.value = [];
if (item.name === "全部歌手") {
getAllSinger();
} else {
getSingerSex(item.type);
}
}
// 通过性别对歌手分类
async function getSingerSex(sex) {
const result = (await HttpManager.getSingerOfSex(sex)) as ResponseBody;
currentPage.value = 1;
allPlayList.value = result.data;
}
</script>
前台-其他页面展示
后台-登录-展示
后台-登录-代码
<template>
<div class="login-container">
<div class="title">{{ nusicName }}</div>
<div class="login">
<el-form :model="ruleForm" :rules="rules">
<el-form-item prop="username">
<el-input v-model="ruleForm.username" placeholder="username"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input type="password" placeholder="password" v-model="ruleForm.password" @keyup.enter="submitForm"></el-input>
</el-form-item>
<el-form-item>
<el-button class="login-btn" type="primary" @click="submitForm">登录</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, getCurrentInstance, ref, reactive } from "vue";
import mixin from "@/mixins/mixin";
import { HttpManager } from "@/api/index";
import { RouterName, MUSICNAME } from "@/enums";
export default defineComponent({
setup() {
const { proxy } = getCurrentInstance();
const { routerManager } = mixin();
const nusicName = ref(MUSICNAME);
const ruleForm = reactive({
username: "admin",
password: "123",
});
const rules = reactive({
username: [{ required: true, message: "请输入用户名", trigger: "blur" }],
password: [{ required: true, message: "请输入密码", trigger: "blur" }],
});
async function submitForm() {
let username = ruleForm.username;
let password = ruleForm.password;
const result = (await HttpManager.getLoginStatus({username,password})) as ResponseBody;
(proxy as any).$message({
message: result.message,
type: result.type,
});
if (result.success) routerManager(RouterName.Info, { path: RouterName.Info });
}
return {
nusicName,
ruleForm,
rules,
submitForm,
};
},
});
</script>
<style scoped>
.login-container {
position: relative;
background: url("../assets/images/background.jpg");
background-attachment: fixed;
background-position: center;
background-size: cover;
width: 100%;
height: 100%;
}
.title {
position: absolute;
top: 50%;
width: 100%;
margin-top: -230px;
text-align: center;
font-size: 30px;
font-weight: 600;
color: #fff;
}
.login {
position: absolute;
left: 50%;
top: 50%;
width: 300px;
margin: -150px 0 0 -190px;
padding: 40px;
border-radius: 5px;
background: #fff;
}
.login-btn {
width: 100%;
}
</style>
后台-首页-展示
首台-首页-代码
<template>
<yin-header></yin-header>
<yin-aside></yin-aside>
<div class="content-box" :class="{ 'content-collapse': collapse }">
<router-view></router-view>
</div>
<yin-audio></yin-audio>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import YinHeader from "@/components/layouts/YinHeader.vue";
import YinAudio from "@/components/layouts/YinAudio.vue";
import YinAside from "@/components/layouts/YinAside.vue";
import emitter from "@/utils/emitter";
const collapse = ref(false);
emitter.on("collapse", (msg) => {
collapse.value = msg as boolean;
});
</script>
<style scoped>
.content-box {
position: absolute;
left: 150px;
right: 0;
top: 60px;
bottom: 0;
overflow-y: scroll;
transition: left 0.3s ease-in-out;
padding: 20px;
}
.content-collapse {
left: 65px;
}
</style>
后台-其他页面-展示
视频展示
音乐网站
详细步骤及代码
Pom.xml导包
<?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 http://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>2.6.2</version>
<relativePath/>
</parent>
<artifactId>music-server</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<!--web启动 内嵌tomcat-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--引入测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--mysql-connector-java就是帮助java程序操作mysql的驱动程序。通过与mysql服务端建立连接,发送sql语句并且获取结果集-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<!--mybatis用来和数据库进行交互-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!--这里面有点小工具啥的-->
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<!-- Log4j -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j</artifactId>
<version>1.3.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
</dependency>
<!-- 热部署模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.6.6</version>
</dependency>
<!--<spring2.X集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.11.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 加上这段代码 打包插件 因为启动不起来 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>
</project>
1.启动类
package com.example.yin;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.example.yin.mapper")
public class YinMusicApplication {
public static void main(String[] args) {
SpringApplication.run(YinMusicApplication.class, args);
}
}
2.过滤器
package com.example.yin.config;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class CorsInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x_requested_with,x-requested-with,Authorization,Content-Type,token");
response.setHeader("Access-Control-Allow-Credentials", "true");
return true;
}
}
3.Redis配置
package com.example.yin.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@EnableCaching //开启缓存注解
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
}
4.application.properties配置文件
mybatis.typeAliasesPackage=com.example.yin.model.domain
mybatis.mapperLocations=classpath:mapper/*.xml
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
server.port=8888
#热部署生效
spring.devtools.restart.enabled=false
#设置重启的目录
spring.devtools.restart.additional-paths=src/main/java
#classpath目录下的WEB-INF文件夹内容修改不重启
spring.devtools.restart.exclude=WEB-INF/**
# 关闭CONDITIONS EVALUATION REPORT及自动配置内容向控制台的输出
logging.level.org.springframework.boot.autoconfigure=ERROR
# redis相应的地址 还有一些配置
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database=0
spring.redis.timeout=1800000
spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=-1
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0
spring.profiles.active=dev
5.log4j.properties配置文件
# LOG4J
log4j.rootCategory=INFO, stdout,file
# print to console
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yy-MM-dd HH:mm:ss,SSS} %5p %c{1}:%L - %m%n
# print to file
log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.DatePattern='-'yyyy-MM-dd'.log'
log4j.appender.file.File=./logs/musicWebsite
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} %p [%c]: %m%n
源码获取
✨还可以关注宫纵号《编程乐学》,菜单栏有很多优质的开源项目以及更多的编程资料等你来学习。