整理声音管理模块,从专辑选择、腾讯云点播上传,到声音列表、删除、修改等接口的学习记录。

本篇要点

  • 分析声音与专辑的关系
  • 学习音频上传与保存流程
  • 梳理声音列表和维护接口

内容回顾

专辑管理模块

  • 保存专辑
  • 专辑列表

– 行变列

  • 删除专辑
  • 修改专辑

今天内容

声音管理模块

1、保存声音

1.1 分析

实现以下接口

  • 上传声音的接口(腾讯云云点播服务)
  • 上传图片接口(已经完成了)
  • 查询所有专辑接口
  • 保存声音的接口

image-20251022090930200

1.2 查询所有专辑接口

image-20251022091211961

AlbumInfoApiController

1
2
3
4
5
6
7
@Operation(summary = "获取当前用户全部专辑列表")
@GetMapping("findUserAllAlbumList")
public Result findUserAllAlbumList() {
Long userId = 1L; //TODO 再完善
List<AlbumInfo> list = albumInfoService.findUserAllAlbumList(userId);
return Result.ok(list);
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//获取当前用户专辑列表
//默认查询前100个专辑
@Override
public List<AlbumInfo> findUserAllAlbumList(Long userId) {
//创建page对象,传入当前页和每页记录数
Page<AlbumInfo> pageParam = new Page<>(1, 100);
// select id,album_title from album_info where user_id=1 order by id desc
LambdaQueryWrapper<AlbumInfo> wrapper = new LambdaQueryWrapper<>();
//指定查询字段
wrapper.select(AlbumInfo::getId,AlbumInfo::getAlbumTitle);
//根据userId查询
wrapper.eq(AlbumInfo::getUserId,userId);
//排序 专辑id desc
wrapper.orderByDesc(AlbumInfo::getId);

IPage<AlbumInfo> albumInfoPage =
albumInfoMapper.selectPage(pageParam, wrapper);
//从IPage对象里面获取数据list集合
List<AlbumInfo> list = albumInfoPage.getRecords();

return list;
}

1.3 上传声音接口

  • 上传音频文件到腾讯云云点播服务里面

开通腾讯云云点播服务

  • 第一步 打开腾讯云官网,注册账号,实名认证

https://cloud.tencent.com/

  • 第二步 开通云点播服务

搜索“云点播”,进入操作界面

image-20251022094056480

点击 应用管理

复制主应用id ,后续代码开发使用

image-20251022094214263

进入应用里面,点击媒资管理

image-20251022094300218

  • 第三步 创建当前账户id和秘钥,代码开发使用

image-20251022094955678

image-20251022095052739

新建秘钥,在创建时候一定复制,后续无法进行查看

image-20251022095215885

腾讯云云点播服务文档

https://cloud.tencent.com/document/product/266/10276

image-20251022102020977

image-20251022102036372

接口实现

TrackInfoApiController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Autowired
private VodService vodService;

/**
* 上传声音
* @param file
* @return
*/
@Operation(summary = "上传声音")
@PostMapping("uploadTrack")
public Result<Map<String,Object>> uploadTrack(MultipartFile file) {

// 调用服务层方法
// 上传之后,腾讯云返回两个值,文件在腾讯云id 和 生成音频文件播放地址
Map<String,Object> map = vodService.uploadTrack(file);
return Result.ok(map);
}
service
  • 配置文件添加

image-20251022103235579

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//上传声音
@Override
public Map<String, Object> uploadTrack(MultipartFile file) {

String tempPath =
UploadFileUtil.uploadTempPath(vodConstantProperties.getTempPath(),
file);
//创建 VodUploadClient,传递腾讯云账户id和秘钥
VodUploadClient client =
new VodUploadClient(vodConstantProperties.getSecretId(),
vodConstantProperties.getSecretKey());
//创建request对象,封装需要数据
VodUploadRequest request = new VodUploadRequest();

//设置文件临时路径
request.setMediaFilePath(tempPath);

//设置任务流
request.setProcedure(vodConstantProperties.getProcedure());
try {
//调用upload方法上传
VodUploadResponse response = client.upload(vodConstantProperties.getRegion(),
request);
// 创建map 对象
HashMap<String, Object> map = new HashMap<>();
map.put("mediaFileId",response.getFileId());
map.put("mediaUrl",response.getMediaUrl());
// 返回map 数据
return map;
} catch (Exception e) {
// 业务方进行异常处理
throw new GuiguException(ResultCodeEnum.DATA_ERROR);
}
}

1.4 保存声音接口

  • 保存声音,向两张表添加数据
  • 修改一张表,修改专辑专辑里面声音数量+1
1
2
3
4
5
track_info: 声音基本信息表

track_stat:声音四个统计数据

album_info:修改操作,专辑里面声音数量+1 include_track_count
  • 在声音表里面有排序序号,order_num,在一个专辑添加声音把之前最大序号+1

  • 声音里面,上传之后返回声音在腾讯云id和播放地址,没有其他值,其他值还需要调用腾讯云方法获取到,比如播放时长,码率等

接口实现

1
2
3
4
5
6
7
8
9
10
11
/**
* 保存声音
* @param trackInfoVo
* @return
*/
@Operation(summary = "新增声音")
@PostMapping("saveTrackInfo")
public Result saveTrackInfo(@RequestBody @Validated TrackInfoVo trackInfoVo) {
trackInfoService.saveTrackInfo(trackInfoVo);
return Result.ok();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
//保存声音
@Override
public void saveTrackInfo(TrackInfoVo trackInfoVo) {
//1 添加声音基本信息 track_info
TrackInfo trackInfo = new TrackInfo();
BeanUtils.copyProperties(trackInfoVo, trackInfo);

//需要手动设置
//用户id
trackInfo.setUserId(1L);

//声音在专辑中的排序值
//获取专辑下面最大order_num
//select order_num
// from track_info ti
// where album_id=1
// order by id desc
// limit 1
LambdaQueryWrapper<TrackInfo> wrapper = new LambdaQueryWrapper<TrackInfo>();
//设置查询字段 order_num
wrapper.select(TrackInfo::getOrderNum);
//专辑id查询
wrapper.eq(TrackInfo::getAlbumId, trackInfoVo.getAlbumId());
//根据声音id降序
wrapper.orderByDesc(TrackInfo::getId);
//获取第一条记录
wrapper.last(" limit 1 ");
//调用
TrackInfo trackInfo_ordernum = trackInfoMapper.selectOne(wrapper);

int orderNum = 1;
if(null != trackInfo_ordernum) {
orderNum = trackInfo_ordernum.getOrderNum() + 1;
}
//最大值+1
trackInfo.setOrderNum(orderNum);

//声音其他信息,比如时长,大小,类型等
//这些信息到腾讯云查询
//根据声音的mediaFileId调用腾讯云方法获取
TrackMediaInfoVo trackMediaInfoVo =
vodService.getmediaInfoByFileId(trackInfoVo.getMediaFileId());
//设置到trackInfo
trackInfo.setMediaDuration(trackMediaInfoVo.getDuration());
trackInfo.setMediaSize(trackMediaInfoVo.getSize());
trackInfo.setMediaUrl(trackMediaInfoVo.getMediaUrl());
trackInfo.setMediaType(trackMediaInfoVo.getType());
//调用方法添加
trackInfoMapper.insert(trackInfo);

//2 操作专辑表,修改专辑里面声音数量值,+1
//根据专辑id查询专辑原始数据
AlbumInfo albumInfo = albumInfoMapper.selectById(trackInfoVo.getAlbumId());
Integer includeTrackCount = albumInfo.getIncludeTrackCount();
//把原始数量+1
albumInfo.setIncludeTrackCount(includeTrackCount+1);
//调用方法更新
albumInfoMapper.updateById(albumInfo);

//3 添加声音四个统计数据,初始值0 track_stat
this.saveTrackStat(trackInfo.getId(),SystemConstant.TRACK_STAT_PLAY);
this.saveTrackStat(trackInfo.getId(),SystemConstant.TRACK_STAT_COLLECT);
this.saveTrackStat(trackInfo.getId(),SystemConstant.TRACK_STAT_PRAISE);
this.saveTrackStat(trackInfo.getId(),SystemConstant.TRACK_STAT_COMMENT);
}

/**
* 初始化统计数量
* @param trackId
* @param trackType
*/
private void saveTrackStat(Long trackId, String trackType) {
TrackStat trackStat = new TrackStat();
trackStat.setTrackId(trackId);
trackStat.setStatType(trackType);
trackStat.setStatNum(0);
this.trackStatMapper.insert(trackStat);
}

2、声音列表

行变列语句

1
2
3
4
5
6
7
8
9
10
11
12
13
SELECT 
album.id AS albumId,
album.album_title,
track.id,
track.track_title,
MAX(IF(stat.stat_type='0701',stat.stat_num,0)) play,
MAX(IF(stat.stat_type = '0702', stat.stat_num, 0)) collectStatNum,
MAX(IF(stat.stat_type = '0703', stat.stat_num, 0)) praiseStatNum,
MAX(IF(stat.stat_type = '0704', stat.stat_num, 0)) commentStatNum
FROM track_info track
INNER JOIN track_stat stat ON stat.track_id=track.id
INNER JOIN album_info album ON album.id = track.album_id
GROUP BY track.id

接口实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//声音列表
@Operation(summary = "获取当前用户声音分页列表")
@PostMapping("findUserTrackPage/{page}/{limit}")
public Result<IPage<TrackListVo>>
findUserTrackPage(@PathVariable Long page,
@PathVariable Long limit,
@RequestBody TrackInfoQuery trackInfoQuery){
trackInfoQuery.setUserId(1L);
Page<TrackListVo> pageParam = new Page<>(page, limit);
IPage<TrackListVo> trackListVoIPage =
trackInfoService.findUserTrackPage(pageParam,trackInfoQuery);
// 返回数据
return Result.ok(trackListVoIPage);
}
1
2
3
4
@Override
public IPage<TrackListVo> findUserTrackPage(Page<TrackListVo> pageParam, TrackInfoQuery trackInfoQuery) {
return trackInfoMapper.selectUserTrackPage(pageParam,trackInfoQuery);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.tingshu.album.mapper.TrackInfoMapper">
<select id="selectUserTrackPage" resultType="com.atguigu.tingshu.vo.album.TrackListVo">
select
album.id as albumId,
album.album_title,
track.id as trackId,
track.track_title,
track.media_duration,
if(track.cover_url is null or track.cover_url = '', album.cover_url, track.cover_url) as coverUrl,
track.status,
track.create_time as createTime,
MAX(IF(stat.stat_type = '0701', stat.stat_num, 0)) as playStatNum,
MAX(IF(stat.stat_type = '0702', stat.stat_num, 0)) as collectStatNum,
MAX(IF(stat.stat_type = '0703', stat.stat_num, 0)) as praiseStatNum,
MAX(IF(stat.stat_type = '0704', stat.stat_num, 0)) as commentStatNum
from track_info track
left join track_stat stat on stat.track_id = track.id
left join album_info album on album.id = track.album_id
<where>
<if test="vo.userId!=null">
track.user_id=#{vo.userId}
</if>
<if test="vo.status!=null and vo.status!=''">
and track.status = #{vo.status}
</if>
<if test="vo.trackTitle!=null and vo.trackTitle !=''">
and track.track_title like concat('%',#{vo.trackTitle},'%')
</if>
and track.is_deleted = 0
</where>
group by track.id
order by track.id desc
</select>
</mapper>

3、删除声音

分析

image-20251022151446371

1
2
3
4
5
6
7
track_info: 根据声音id删除基本信息

track_stat:根据声音id删除四个统计数据

album_info:修改声音所在 专辑 声音数量-1

删除腾讯云云点播声音文件

接口实现

1
2
3
4
5
6
//删除声音
@DeleteMapping("removeTrackInfo/{id}")
public Result removeTrackInfo(@PathVariable Long id) {
trackInfoService.removeTrackInfo(id);
return Result.ok();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//删除
@Override
public void removeTrackInfo(Long trackId) {
//根据声音id获取专辑id
TrackInfo trackInfo = trackInfoMapper.selectById(trackId);

//1 根据声音id删除声音基本信息
trackInfoMapper.deleteById(trackId);

//2 修改声音所在专辑声音数量-1
Long albumId = trackInfo.getAlbumId();
//根据专辑id查询专辑数据,把数量-1,进行更新
AlbumInfo albumInfo = albumInfoMapper.selectById(albumId);
Integer includeTrackCount = albumInfo.getIncludeTrackCount();
albumInfo.setIncludeTrackCount(includeTrackCount-1);
albumInfoMapper.updateById(albumInfo);

//3 删除声音统计数据
LambdaQueryWrapper<TrackStat> wrapper = new LambdaQueryWrapper<TrackStat>();
wrapper.eq(TrackStat::getTrackId, trackId);
trackStatMapper.delete(wrapper);

//4 删除腾讯云里面的声音文件
//删除声音媒体
vodService.removeTrack(trackInfo.getMediaFileId());
}

4、修改声音

分析

  • 有两个接口

**第一个接口:**根据声音id查询声音信息,进行数据回显,直接根据id查询就可以了,因为在声音基本信息表包含需要所有数据

**第二个接口:**修改声音接口

– 判断声音文件是否修改,如果声音文件修改,需要重新查询腾讯云获取到新的声音相关信息(时长等)

– 根据数据库里面存储声音fileid和前端传递过来的fileid比较,不相同,声音修改过

接口1:根据id查询声音

image-20251022154549099

1
2
3
4
5
@GetMapping("getTrackInfo/{trackId}")
public Result getTrackInfo(@PathVariable("trackId") Long trackId) {
TrackInfo trackInfo = trackInfoService.getById(trackId);
return Result.ok(trackInfo);
}

接口2:修改声音

image-20251022155103813

1
2
3
4
5
6
7
//修改声音
@PutMapping("updateTrackInfo/{trackId}")
public Result updateTrackInfo(@PathVariable("trackId") Long trackId,
@RequestBody @Validated TrackInfoVo trackInfoVo) {
trackInfoService.updateTrackInfo(trackId,trackInfoVo);
return Result.ok();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//修改声音
@Override
public void updateTrackInfo(Long trackId, TrackInfoVo trackInfoVo) {
//1 根据trackId获取声音原始数据
TrackInfo trackInfo = trackInfoMapper.selectById(trackId);
//获取原始的音频文件id fileid
String mediaFileId_database = trackInfo.getMediaFileId();
//设置修改值
BeanUtils.copyProperties(trackInfoVo, trackInfo);
//判断声音文件是否修改
//把前端传递过来的mediaFileId 和 数据库存储的mediaFileId进行比较
if(!trackInfoVo.getMediaFileId().equals(mediaFileId_database)) {
//重新调用腾讯云方法,根据新的mediaFileId查询音频文件最新信息
TrackMediaInfoVo trackMediaInfoVo =
vodService.getmediaInfoByFileId(trackInfoVo.getMediaFileId());
if (null==trackMediaInfoVo){
// 抛出异常
throw new GuiguException(ResultCodeEnum.VOD_FILE_ID_ERROR);
}
//最新获取音频信息设置到trackInfo
trackInfo.setMediaUrl(trackMediaInfoVo.getMediaUrl());
trackInfo.setMediaType(trackMediaInfoVo.getType());
trackInfo.setMediaDuration(trackMediaInfoVo.getDuration());
trackInfo.setMediaSize(trackMediaInfoVo.getSize());

//删除之前声音文件
vodService.removeTrack(mediaFileId_database);
}

//调用方法修改
trackInfoMapper.updateById(trackInfo);
}

5、后续功能说明

  • 用户登录
  • 专辑详情
  • 订单
  • 搜索

登录

  • 自定义注解
1
2
public @interface Log {
}
  • AOP

– 通俗描述:不改变源码或者很少改变源码增强功能

– 术语:切入点、通知(增强)、切面

切入点:实际增强的方法,切入点表达式

通知:五种类型,环绕通知

切面:把通知应用到切入点过程

– AOP底层使用动态代理,有接口情况使用jdk动态代理,没有接口使用cglib动态代理