这一章属于难点章节,包含视频的上传于弹幕系统的开发
fdfs:tracker-list:192.168.117.130:22122
public String getFileType(MultipartFile file){
if(file==null){
throw new ConditionalException("非法文件!");
}
//根据文件获取文件类型,读取file的最后一个参数
String fileName = file.getOriginalFilename();
int index = fileName.lastIndexOf(".");
return fileName.substring(index+1);
}
//上传
public String uploadCommonFile(MultipartFile file) throws IOException {
Set<MetaData>metaData = new HashSet<>();
String fileType = this.getFileType(file);
StorePath storePath = fastFileStorageClient.uploadFile(file.getInputStream(), file.getSize(), fileType, metaData);
return storePath.getPath();
}
@Autowired
private AppendFileStorageClient appendFileStorageClient;
//断点续传的功能
public String uploadAppendFile(MultipartFile file) throws IOException {
String fileName = file.getOriginalFilename();
String fileType = this.getFileType(file);
StorePath storePath = appendFileStorageClient.uploadAppenderFile(DEFAULT_GROUP, file.getInputStream(), file.getSize(), fileType);
return storePath.getPath();
}
//不会重复已经上传的
public void modifyAppendFile(MultipartFile file,String filePath,long offset) throws IOException {
appendFileStorageClient.modifyFile(DEFAULT_GROUP,filePath,file.getInputStream(),file.getSize(),offset);
}
//分片上传,及那个path,uploadNo,uploadSize存到我们的redis
public String uploadFileBySlices(MultipartFile file,String fileMD5,Integer slice,Integer totalSlice) throws IOException {
if(file==null || slice==null || totalSlice == null){
throw new ConditionalException("参数异常");
}
String pathKey = PATH_KEY + fileMD5;
String uploadNoKey = UPLOAD_NO_KEY + fileMD5;
String uploadSizeKey = UPLOAD_SIZE_KEY + fileMD5;
//获取上传大小
String uploadSizeStr = redisTemplate.opsForValue().get(uploadSizeKey);
Long uploadedSize = 0L;
if(!StringUtils.isNullOrEmpty(uploadSizeStr)){
uploadedSize = Long.valueOf(uploadSizeStr);
}
String fileType = this.getFileType(file);
//判断上传的是第一个分片还是其他分片
if(slice==1){
String path = this.uploadAppendFile(file);
if(StringUtils.isNullOrEmpty(path)){
throw new ConditionalException("上传失败! ");
}
redisTemplate.opsForValue().set(pathKey,path);
redisTemplate.opsForValue().set(uploadNoKey,"1");
}else{
String filePath = redisTemplate.opsForValue().get(pathKey);
if (StringUtils.isNullOrEmpty(filePath)){
throw new ConnectException("上传失败!");
}
this.modifyAppendFile(file,filePath,uploadedSize);
//更新分片
redisTemplate.opsForValue().increment(uploadNoKey);
}
//更新上传大小
uploadedSize +=file.getSize();
redisTemplate.opsForValue().set(uploadSizeKey,String.valueOf(uploadedSize));
//判断是否已经上传完,上传完后清除redis里面的key值
String uploadedNoStr = redisTemplate.opsForValue().get(uploadNoKey);
Integer uploadedNo = Integer.valueOf(uploadedNoStr);
String resultPath = "";
if(uploadedNo.equals(totalSlice)){
resultPath = redisTemplate.opsForValue().get(pathKey);
List<String> list = Arrays.asList(uploadNoKey, pathKey, uploadSizeKey);
redisTemplate.delete(list);
}
return resultPath;
}
//将multipart进行转换成io流中的file
public File multipartFileToFile(MultipartFile multipartFile) throws IOException {
String originalFilename = multipartFile.getOriginalFilename();
String[]fileName = originalFilename.split("\\.");
File file = File.createTempFile(fileName[0],"."+fileName[1]);
multipartFile.transferTo(file);
return file;
}
/**
* 指定文件切成多少片,那么需要用到IO流的关系,我们先读取文件的大小
* 确定好每次读取多少,比如SLICE_SIZE=1024*2,在指定我们写入的路径path
*
*/
public void convertFileToSlice(MultipartFile multipartFile) throws IOException {
String fileType = this.getFileType(multipartFile);
File file =this.multipartFileToFile(multipartFile);
long fileLength = file.length();
int count = 1;
for(int i = 0;i<fileLength;i+=SLICE_SIZE){
RandomAccessFile randomAccessFile = new RandomAccessFile(file,"r");
randomAccessFile.seek(i);
byte[]bytes = new byte[SLICE_SIZE];
int len = randomAccessFile.read();
String path = "E:\\WorkSpace\\tmp\\tmpfile"+count+"."+fileType;
File slice = new File(path);
FileOutputStream fos = new FileOutputStream(slice);
fos.write(bytes,0,len);
fos.close();
randomAccessFile.close();
count++;
}
file.delete();//删除临时文件
}
//删除
public void deleteFile(String filePath){
fastFileStorageClient.deleteFile(filePath);
}
DROP TABLE IF EXISTS `t_file`;
CREATE TABLE `t_file` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键id',
`url` VARCHAR(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '文件存储路径',
`type` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '文件类型',
`md5` VARCHAR(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '文件md5唯一标识串',
`createTime` DATETIME NULL DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = INNODB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表' ROW_FORMAT = DYNAMIC;
@PutMapping("/file-slices")
public JsonResponse<String>uploadFileByServices(MultipartFile slice,
String fileMd5,
Integer sliceNo,
Integer totalSlice) throws Exception {
String filePath = fileService.uploadFileBySlices(slice,fileMd5,sliceNo,totalSlice);
return new JsonResponse<>(filePath);
}
public String uploadFileBySlices(MultipartFile slice,
String fileMD5,
Integer sliceNo,
Integer totalSliceNo) throws Exception {
File dbFileMD5 = fileDao.getFileByMD5(fileMD5);
if(dbFileMD5 != null){
return dbFileMD5.getUrl();
}
String url = fastDFSUtil.uploadFileBySlices(slice, fileMD5, sliceNo, totalSliceNo);
if(!StringUtil.isNullOrEmpty(url)){
dbFileMD5 = new File();
dbFileMD5.setCreateTime(new Date());
dbFileMD5.setMd5(fileMD5);
dbFileMD5.setUrl(url);
dbFileMD5.setType(fastDFSUtil.getFileType(slice));
fileDao.addFile(dbFileMD5);
}
return url;
}
public String getFileMD5(MultipartFile file) throws Exception {
return MD5Util.getFileMD5(file);
}
@Mapper
public interface FileDao {
File getFileByMD5(String md5);
Integer addFile(File file);
}
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.imooc.bilibili.dao.FileDao">
<insert id="addFile" parameterType="com.imooc.bilibili.domain.File">
insert into
t_file(
url,
`type`,
md5,
createTime
)values (
#{url},
#{type},
#{md5},
#{createTime}
)
insert>
<select id="getFileByMD5" resultType="com.imooc.bilibili.domain.File" parameterType="java.lang.String">
select
*
from
t_file
where
md5 = #{md5}
select>
mapper>
@GetMapping("/videos")
public JsonResponse<PageResult<Video>>pageListVideos(Integer size,Integer no,String area){
PageResult<Video>result = videoService.pageListVideo(size,no,area);
return new JsonResponse<>(result);
}
/**
* 视频列表分页
* @param size
* @param no
* @param area
* @return
*/
public PageResult<Video>pageListVideo(Integer size,Integer no,String area){
if(size ==null || no == null){
throw new ConditionalException("异常参数");
}
Map<String,Object>params = new HashMap<>();
params.put("start",(no-1)*size);
params.put("limit",size);
params.put("area",area);
List<Video>list = new ArrayList<>();//查出的信息放在列表
Integer total = videoDao.pageCountVideos(params);
if(total>0){
list = videoDao.pageListVideos(params);
}
return new PageResult<>(total,list);
}
Integer pageCountVideos(Map<String, Object> params);
List<Video> pageListVideos(Map<String, Object> params);
<select id="pageCountVideos" resultType="java.lang.Integer" parameterType="java.util.Map">
select
count(1)
from
t_video
where
1=1
<if test="area != null and area != '' ">
and area = #{area}
if>
select>
<select id="pageListVideos" resultType="com.imooc.bilibili.domain.Video" parameterType="java.util.Map">
select
*
from
t_video
where
1=1
<if test="area !=null and area !='' ">
and area = #{area}
if>
order by id desc
limit #{start},#{limit}
select>
@PostMapping("/videos")
public JsonResponse<String>addVideos(@RequestBody Video video){
Long userId = userSupport.getCurrentUserId();
video.setUserId(userId);
videoService.addVideos(video);
return JsonResponse.success();
}
@Transactional
public void addVideos(Video video) {
Date now = new Date();
video.setCreateTime(new Date());
videoDao.addVideos(video);
Long videoId = video.getId();
List<VideoTag> tagList = video.getVideoTagList();
tagList.forEach(item->{
item.setCreateTime(new Date());
item.setVideoId(videoId);
});
videoDao.batchAddVideoTags(tagList);
}
<insert id="addVideos" parameterType="com.imooc.bilibili.domain.Video" keyProperty="id" useGeneratedKeys="true">
insert into
t_video(
id,
userId,
thumbnail,
title,
`type`,
duration,
area,
description,
createTime
)values(
#{id},
#{userId},
#{thumbnail},
#{title},
#{type},
#{duration},
#{area},
#{description},
#{createTime}
)
</insert>
<insert id="batchAddVideoTags" parameterType="java.util.List">
insert into
t_video_tag(
videoId,
tagId,
createTime
)values(
<foreach collection="tagList" item="videoTag" separator=",">
#{videoTag.videoId},
#{videoTag.tagId},
#{videoTag.createTime},
</foreach>
)
</insert>
@Data
public class Video {
private Long id;
private Long userId;//用户Id
private String url;//视频链接
private String thumbnail;//封面
private String title;//标题
private String type;//类型0 自制 1 转载
private String duration;//时长
private String area;//分区
private List<VideoTag>videoTagList;//标签列表
private String description;//简介
private Date createTime;
}
/**
* 查询对应的标签
* @param videoId
* @return
*/
@GetMapping("/video-tags")
public JsonResponse<List<Tag>>getVideoTags(@RequestParam Long videoId){
List<Tag>list = videoService.getVideoTags(videoId);
return new JsonResponse<>(list);
}
public List<Tag> getVideoTags(Long videoId){
//或者该视频的所有tagId
List<VideoTag>videoTagList = videoDao.getVideoTagList(videoId);
Set<Long> tagList = videoTagList.stream().map(VideoTag::getTagId).collect(Collectors.toSet());
List<Tag>tagInfoList = new ArrayList<>();
if(tagList.size() > 0){
tagInfoList = videoDao.getTagInfoList(tagList);
}
return tagInfoList;
}
List<VideoTag> getVideoTagList(Long videoId);
List<Tag> getTagInfoList(Set<Long> tagList);
<select id="getVideoTagList" resultType="com.imooc.bilibili.domain.VideoTag" parameterType="java.lang.Long">
select
*
from
t_video_tag
where
videoId = #{videoId};
select>
<select id="getTagInfoList" resultType="com.imooc.bilibili.domain.Tag">
select
*
from
t_tag
where
1=1
<if test="tagList !=null and tagList !=''">
and tagId in
<foreach collection="tagList" item="tagId" index="index" open="(" close=")" separator=",">
#{tagId}
foreach>
if>
select>
/**
* 删除视频标签
* @param params
* @return
*/
public JsonResponse<String>deleteVideoTags(@RequestBody JSONObject params){
String tagList = params.getString("tagIdList");
Long videoId = params.getLong("videoId");
videoService.deleteVideoTags(JSONArray.parseArray(tagList).toJavaList(Long.class),videoId);
return JsonResponse.success();
}
public void deleteVideoTags(List<Long> tagList, Long videoId) {
videoDao.deleteVideoTags(tagList,videoId);
}
Integer deleteVideoTags(@Param("tagList") List<Long> tagList
, @Param("videoId") Long videoId);
-XML层
<delete id="deleteVideoTags">
delete from
t_video_tag
where
videoId = #{videoId}
and
tagId in
<foreach collection="tagList" item="tagId" open="(" close=")" separator=",">
#{tagId}
foreach>
delete>
/**
* 视频点赞
* @param videoId
* @return
*/
@PostMapping("/video-like")
public JsonResponse<String>addVideoLike(@RequestParam Long videoId){
Long userId = userSupport.getCurrentUserId();
videoService.addVideoLike(videoId,userId);
return JsonResponse.success();
}
//点赞
public void addVideoLike(Long videoId, Long userId) {
Video video = videoDao.getVideoById(videoId);
if(video==null){
throw new ConditionalException("非法视频");
}
VideoLike videoLike = videoDao.getVideoLikeByVideoIdAndUserId(videoId,userId);
if(videoLike!=null){
throw new ConditionalException("视频已点赞");
}
videoLike = new VideoLike();
videoLike.setVideoId(videoId);
videoLike.setUserId(userId);
videoLike.setCreateTime(new Date());
videoDao.addVideoLike(videoLike);
}
<insert id="addVideoLike" parameterType="com.imooc.bilibili.domain.VideoLike" >
insert into
t_video_like(
videoId,
userId,
createTime
)values(
#{videoId},
#{userId},
#{createTime}
)
insert>
/**
* 取消点赞
* @param videoId
* @return
*/
@DeleteMapping("/video-likes")
public JsonResponse<String>deleteVideoLike(@RequestParam Long videoId){
Long userId = userSupport.getCurrentUserId();
videoService.deleteVideoLike(videoId,userId);
return JsonResponse.success();
}
//取消点赞
public void deleteVideoLike(Long videoId, Long userId) {
videoDao.deleteVideoLike(videoId,userId);
}
Integer deleteVideoLike(@Param("videoId") Long videoId
,@Param("userId") Long userId);
<delete id="deleteVideoLike">
delete from
t_video_like
where
videoId = #{videoId} and userId = #{userId};
delete>
/**
* 查询点赞数
* @param videoId
* @return
*/
@GetMapping("/video-likes")
public JsonResponse<Map<String,Object>>getVideoLike(@RequestParam Long videoId){
//需要进行游客和登陆用户的判断
Long userId = null;
try{
userId = userSupport.getCurrentUserId();
}catch (Exception ignore){}
Map<String,Object>map = videoService.getVideoLike(videoId,userId);
return new JsonResponse<>(map);
}
//查看点赞数
public Map<String, Object> getVideoLike(Long videoId, Long userId) {
//Map里面装的是like count
Long count = videoDao.getVideoLikes(videoId);
VideoLike videoLike = videoDao.getVideoLikeByVideoIdAndUserId(videoId, userId);
boolean like = videoLike!=null;
Map<String,Object>result = new HashMap<>();
result.put("count",count);
result.put("like",like);
return result;
}
Long getVideoLikes(Long videoId);
VideoLike getVideoLikeByVideoIdAndUserId(@Param("videoId")Long videoId, @Param("userId") Long userId);
<!--查询用户是否点赞-->
<select id="getVideoLikeByVideoIdAndUserId" resultType="com.imooc.bilibili.domain.VideoLike">
select
*
from
t_video_like
where
videoId = #{videoId} and userId = #{userId};
</select>
<!--查询用户点赞数的多少-->
<select id="getVideoLikes" resultType="java.lang.Long" parameterType="java.lang.Long">
select
count(1)
from
t_video_like
where
videoId = #{videoId};
</select>
/**
* 添加收藏
* @param videoCollection
* @return
*/
@PostMapping("/video-collections")
public JsonResponse<String>addVideoCollection(@RequestBody VideoCollection videoCollection){
Long userId = userSupport.getCurrentUserId();
videoCollection.setId(userId);
videoService.addVideoCollection(videoCollection,userId);
return JsonResponse.success();
}
@Transactional
public void addVideoCollection(VideoCollection videoCollection, Long userId) {
Long videoId = videoCollection.getVideoId();
Long groupId = videoCollection.getGroupId();
if(videoId==null || groupId==null){
throw new ConditionalException("异常参数");
}
//查询下video是否在
Video video = videoDao.getVideoById(videoId);
if(video==null){
throw new ConditionalException("非法视频");
}
//删除原有视频收藏
videoDao.deleteVideoCollection(videoId, userId);
//添加新的视频收藏
videoCollection.setUserId(userId);
videoCollection.setCreateTime(new Date());
videoDao.addVideoCollection(videoCollection);
}
Integer deleteVideoCollection(@Param("videoId") Long videoId,@Param("userId") Long userId);
Integer addVideoCollection(VideoCollection videoCollection);
-xml
<insert id="addVideoCollection" parameterType="com.imooc.bilibili.domain.VideoCollection">
insert into
t_video_collection(
id,
videoId,
userId,
groupId,
createTime
)values(
#{id},
#{videoId},
#{userId},
#{groupId},
#{createTime}
)
insert>
<delete id="deleteVideoCollection">
delete
from
t_video_collection
where
videoId = #{videoId}
and userId = #{userId};
delete>
//查询收藏数量
public Map getVideoCollections(Long videoId, Long userId) {
Long count = videoDao.getVideoCollections(videoId);
//查询是否收藏
VideoCollection videoCollection = videoDao.getVideoCollectionByVideoIdAndUserId(videoId, userId);
boolean like = videoCollection != null;
Map result = new HashMap<>();
result.put("count",count);
result.put("like",like);
return result;
}
Long getVideoCollections(Long videoId);
VideoCollection getVideoCollectionByVideoIdAndUserId(Long videoId, Long userId);
<select id="getVideoCollections" resultType="java.lang.Long" parameterType="java.lang.Long">
select
count(1)
from
t_video_collection
where
videoId = #{videoId};
select>
<select id="getVideoCollectionByVideoIdAndUserId" resultType="com.imooc.bilibili.domain.VideoCollection">
select
*
from
t_video_collection
where
videoId = #{videoId} and userId = #{userId};
select>
/**
* 添加视频投币
* @param videoCoin
* @return
*/
@PostMapping("/video-coins")
public JsonResponse<String>addVideoCoins(@RequestBody VideoCoin videoCoin){
Long userId = userSupport.getCurrentUserId();
videoService.addVideoCoins(videoCoin,userId);
return JsonResponse.success();
}
思路:对于视频的投币,根据视频参数的验证后,我们需要对其用户硬币数量进行校验,是否足够投,如果足够,那么视频是第一次还是第二次投币的问题
@Transactional
public void addVideoCoins(VideoCoin videoCoin, Long userId) {
Integer amount = videoCoin.getAmount();//要投币的数量
Long videoId = videoCoin.getVideoId();
if(userId==null || videoId==null){
throw new ConditionalException("非法参数");
}
Video video = videoDao.getVideoById(videoId);
if(video==null){
throw new ConditionalException("视频失效");
}
//查询当前用户硬币数量
Integer userCoinAmount = userCoinService.getUserCoinAmount(userId);
userCoinAmount = userCoinAmount==null? 0:userCoinAmount;
if(amount > userCoinAmount){
throw new ConditionalException("账户硬币余额不足");
}
//开始进行投币,已经排除游客投币,余额不足
//查询该用户对该视频投过多少币
VideoCoin dbVideoCoin = videoDao.getVideoCoinByVideoIdAndUserId(videoId, userId);
//新增投币数量
if(dbVideoCoin==null){
videoCoin.setUserId(userId);
videoCoin.setCreateTime(new Date());
videoDao.addVideoCoin(videoCoin);
}else{
Integer dbAmount = dbVideoCoin.getAmount();//视频硬币书
dbAmount+=amount;
videoCoin.setUserId(userId);
videoCoin.setAmount(dbAmount);
videoCoin.setUpdateTime(new Date());
videoDao.updateVideoCoin(videoCoin);
}
userCoinService.updateUserCoinsAmount(userId,(userCoinAmount-amount));
}
@Service
public class UserCoinService {
@Autowired
private UserCoinDao userCoinDao;
public Integer getUserCoinAmount(Long userId) {
return userCoinDao.getUserCoinAmount(userId);
}
public void updateUserCoinsAmount(Long userId, Integer amount) {
Date updateTime = new Date();
userCoinDao.updateUserCoinAmount(userId,amount,updateTime);
}
}
- userCoinDao
```java
@Mapper
public interface UserCoinDao {
Integer getUserCoinAmount(Long userId);
Integer updateUserCoinAmount(@Param("userId") Long userId
,@Param("amount") Integer amount
,@Param("updateTime") Date updateTime);
}
<mapper namespace="com.imooc.bilibili.dao.UserCoinDao">
<update id="updateUserCoinAmount">
update
t_user_coin
set
amount = #{amount},
updateTime = #{updateTime}
where
userId = #{userId}
update>
<select id="getUserCoinAmount" resultType="java.lang.Integer" parameterType="java.lang.Long">
select
amount
from
t_user_coin
where
userId = #{userId}
select>
mapper>
-----------------------
<select id="getVideoCoinByVideoIdAndUserId" resultType="com.imooc.bilibili.domain.VideoCoin">
select
*
from
t_video_coin
where videoId = #{videoId} and userId = #{userId};
select>
<select id="getVideoCoinsAmount" resultType="java.lang.Long" parameterType="java.lang.Long">
select
sum(amount)
from
t_video_coin
where
videoId = #{videoId};
select>
<update id="updateVideoCoin" parameterType="com.imooc.bilibili.domain.VideoCoin">
update
t_video_coin
set
amount = #{amount},
updateTime = #{updateTime}
where videoId = #{videoId}
and userId = #{userId};
update>
//查询硬币数量
public Map<String, Object> getVideoCoins(Long videoId, Long userId) {
Long count = videoDao.getVideoCoinsAmount(videoId);
VideoCoin videoCollection = videoDao.getVideoCoinByVideoIdAndUserId(videoId, userId);
boolean like = videoCollection != null;
Map<String, Object> result = new HashMap<>();
result.put("count", count);
result.put("like", like);
return result;
}
@Data
public class VideoComment {
private Long id;
private Long videoId;
private Long userId;
private String comment;
private Long replyUserId;
private Long rootId;
private Date createTime;
private Date updateTime;
private List<VideoComment>childList;
private UserInfo userInfo;
private UserInfo replyUserInfo;
}
@GetMapping("/video-comments")
public JsonResponse<PageResult<VideoComment>>pageListVideoComments(@RequestParam Integer size,
@RequestParam Integer no,
@RequestParam Long videoId){
PageResult<VideoComment>result = videoService.pageListVideoComments(size,no,videoId);
return new JsonResponse<>(result);
B站用户的评论,分为1级和二级评论,每个评论下有多个二级评论,我们的需求是查出评论下的全部信息,这是一个列表
/**
* 评论分页
* @param size
* @param no
* @param videoId
* @return
*/
public PageResult<VideoComment> pageListVideoComments(Integer size, Integer no, Long videoId) {
Video video = videoDao.getVideoById(videoId);
if(video==null){
throw new ConditionalException("非法视频!");
}
Map<String,Object>params = new HashMap<>();
params.put("start",(no-1)*size);
params.put("limit",size);
params.put("videoId",videoId);
//计算评论总数
Integer total = videoDao.pageCountVideoComments(params);
List<VideoComment>list = new ArrayList<>();
if(total > 0){
//1.获得评论分页数据
list = videoDao.pageListVideoComments(params);
//2.批量查询二级评论
List<Long>parentIdList = list.stream().map(VideoComment::getId).collect(Collectors.toList());
List<VideoComment>childCommentList = videoDao.batchGetVideoCommentsByRootIds(parentIdList);
//批量查询用户信息
Set<Long> userIdList = list.stream().map(VideoComment::getUserId).collect(Collectors.toSet());
// Set replyUserId = childCommentList.stream().map(VideoComment::getUserId).collect(Collectors.toSet());
Set<Long> replyUserIdList = childCommentList.stream().map(VideoComment::getReplyUserId).collect(Collectors.toSet());
userIdList.addAll(replyUserIdList);
// userIdList.addAll(childUserIdList);
//根据评论下的各个userId查到对应的详细信息
List<UserInfo>userInfoList = userService.batchGetUserInfoByUserIds(userIdList);
Map<Long, UserInfo> userInfoMap = userInfoList.stream().collect(Collectors.toMap(UserInfo::getUserId, userInfo -> userInfo));
//将一级评论下的二级评论也查出来
list.forEach(comment->{
Long id = comment.getId();
List<VideoComment>childList = new ArrayList<>();
childCommentList.forEach(child->{
if(id.equals(child.getRootId())){
child.setUserInfo(userInfoMap.get(child.getUserId()));
child.setReplyUserInfo(userInfoMap.get(child.getReplyUserId()));
childList.add(child);
}
});
comment.setChildList(childList);
comment.setUserInfo(userInfoMap.get(comment.getUserId()));
});
}
return new PageResult<>(total,list);
}
Integer pageCountVideoComments(Map<String, Object> params);
List<VideoComment> pageListVideoComments(Map<String, Object> params);
List<VideoComment> batchGetVideoCommentsByRootIds(@Param("rootIdList") List<Long> rootIdList);
Video getVideoDetails(Long id);
<select id="pageCountVideoComments" resultType="java.lang.Integer" parameterType="java.util.Map">
select
count(1)
from
t_video_comment
where
videoId = #{videoId}
and rootId is null;
select>
<select id="pageListVideoComments" resultType="com.imooc.bilibili.domain.VideoComment" parameterType="java.util.Map">
select
*
from
t_video_comment
where
videoId = #{videoId}
and rootId is null
order by id desc
limit #{start},#{limit}
select>
<select id="batchGetVideoCommentsByRootIds" resultType="com.imooc.bilibili.domain.VideoComment" parameterType="java.util.List">
select
*
from
t_video_comments
where rootId in
<foreach collection="rootIdList" item="rootId" open="(" close=")" separator=",">
#{rootId}
foreach>
order by id;
select>
<select id="batchGetUserInfoByUserIds" resultType="com.imooc.bilibili.domain.UserInfo" >
select
*
from
t_user_info
where
userId in
<foreach collection="userIdList" item="userId" open="(" close=")" separator=",">
#{userId}
foreach>
select>
@Component
@ServerEndpoint("/imserver/{token}")
public class WebSocketService {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private static final AtomicInteger ONLINE_COUNT = new AtomicInteger(0);//安全性高
public static final ConcurrentHashMap<String,WebSocketService>WEBSOCKET_MAP = new ConcurrentHashMap<>();//保证线程安全
private Long userId;
private Session session;
private String sessionId;
//构建多实例的socket bean
private static ApplicationContext APPLICATION_CONTEXT;
public static void setApplicationContext(ApplicationContext applicationContext){
WebSocketService.APPLICATION_CONTEXT = applicationContext;
}
@OnOpen
public void openConnection(Session session, @PathParam("token") String token){
try {
userId = TokenUtils.verifyToken(token);
}catch (Exception ignored){}
RedisTemplate<String,String>redisTemplate = (RedisTemplate)WebSocketService.APPLICATION_CONTEXT.getBean("redisTemplate");
redisTemplate.opsForValue().get("sakura");
this.sessionId = session.getId();
this.session = session;
if(WEBSOCKET_MAP.containsKey(sessionId)){
WEBSOCKET_MAP.remove(sessionId);
WEBSOCKET_MAP.put(sessionId,this);
}else{
WEBSOCKET_MAP.put(sessionId,this);
ONLINE_COUNT.getAndIncrement();
}
logger.info("用户连接成功:"+sessionId + ",当前在线人数为:" + ONLINE_COUNT.get());
try{
this.sendMessage("0");
}catch(Exception e){
logger.error("连接异常");
}
}
@OnClose
public void closeConnection(){
if(WEBSOCKET_MAP.containsKey(sessionId)){
WEBSOCKET_MAP.remove(sessionId);
ONLINE_COUNT.getAndDecrement();
}
logger.info("用户退出"+sessionId+"当前在线人数为:"+ONLINE_COUNT.get());
}
@OnMessage
public void onMessage(String message){
logger.info("用户信息:" + sessionId + ",报文:" + message);
if(!StringUtils.isNullOrEmpty(message)){
try{
//群发消息
for(Map.Entry<String, WebSocketService> entry : WEBSOCKET_MAP.entrySet()){
WebSocketService webSocketService = entry.getValue();
//获得生产者
DefaultMQProducer danmusProducer = (DefaultMQProducer) APPLICATION_CONTEXT.getBean("danmusProducer");
JSONObject jsonObject = new JSONObject();
jsonObject.put("message",message);
jsonObject.put("sessionId",webSocketService.getSessionId());
Message msg = new Message(UserMomentsConstant.TOPIC_DANMUS, jsonObject.toJSONString().getBytes(StandardCharsets.UTF_8));
RocketMQUtil.asyncSengMsg(danmusProducer,msg);
}
if(this.userId != null){
//保存弹幕到数据库
Danmu danmu = JSONObject.parseObject(message, Danmu.class);
danmu.setUserId(userId);
danmu.setCreateTime(new Date());
DanmuService danmuService = (DanmuService)APPLICATION_CONTEXT.getBean("danmuService");
danmuService.asyncAddDanmu(danmu);
//保存弹幕到redis
danmuService.addDanmusToRedis(danmu);
}
}catch (Exception e){
logger.error("弹幕接收出现问题");
e.printStackTrace();
}
}
}
@Scheduled(fixedRate = 5000)
public void noticeOnlineCount0() throws IOException {
for(Map.Entry<String,WebSocketService>entry:WEBSOCKET_MAP.entrySet()){
WebSocketService webSocketService = entry.getValue();
if(webSocketService.getSession().isOpen()){
JSONObject jsonObject =new JSONObject();
jsonObject.put("onlineCount",ONLINE_COUNT.get());
jsonObject.put("msg","当前在线人数"+ONLINE_COUNT.get());
webSocketService.sendMessage(jsonObject.toString());
}
}
}
@OnError
public void onError(Throwable error){
}
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
public Session getSession() {
return session;
}
public String getSessionId() {
return sessionId;
}
}
@RestController
public class DanmuApi {
@Autowired
private UserSupport userSupport;
@Autowired
private DanmuService danmuService;
@GetMapping("/danmus")
public JsonResponse<List<Danmu>>getDanmus(@RequestParam Long videoId,
String startTime,
String endTime) throws Exception {
List<Danmu>list;
try{
userSupport.getCurrentUserId();
list = danmuService.getDanmus(videoId,startTime,endTime);
}catch(Exception ignored){
list = danmuService.getDanmus(videoId,null,null);
}
return new JsonResponse<>(list);
}
}
@Service
public class DanmuService {
@Autowired
private DanmuDao danmuDao;
@Autowired
private RedisTemplate<String,String>redisTemplate;
public static final String DANMU_KEY = "danmu-video-";
public void addDanmu(Danmu danmu){
danmuDao.addDanmu(danmu);
}
@Async
public void asyncAddDanmu(Danmu danmu){
danmuDao.addDanmu(danmu);
}
/**
* 查询策略,应优先查询redis的弹幕数据
* 如果reids没有在查询数据库
* @param videoId
* @param startTime
* @param endTime
* @return
*/
public List<Danmu>getDanmus(Long videoId,
String startTime,
String endTime) throws Exception {
String key = DANMU_KEY + videoId;
String value = redisTemplate.opsForValue().get(key);
List<Danmu>list;
if(!StringUtils.isNullOrEmpty(value)){
//通过redis获取到对应的list
list = JSONArray.parseArray(value,Danmu.class);
if(!StringUtils.isNullOrEmpty(startTime) && !StringUtils.isNullOrEmpty(endTime)){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date startDate = sdf.parse(startTime);
Date endDate = sdf.parse(endTime);
List<Danmu>childList = new ArrayList<>();
for(Danmu danmu:list){
Date createTime = danmu.getCreateTime();
if(createTime.after(startDate) && createTime.before(endDate)){
childList.add(danmu);
}
}
list = childList;
}
}else{
Map<String,Object>params = new HashMap<>();
params.put("videoId",videoId);
params.put("startTime",startTime);
params.put("endTime",endTime);
list = danmuDao.getDanmus(params);
redisTemplate.opsForValue().set(key,JSONObject.toJSONString(list));
}
return list;
}
public void addDanmusToRedis(Danmu danmu) {
String key = DANMU_KEY+danmu.getVideoId();
String value = redisTemplate.opsForValue().get(key);
List<Danmu>list = new ArrayList<>();
if(!StringUtils.isNullOrEmpty(value)){
list = JSONArray.parseArray(value,Danmu.class);
}
list.add(danmu);
redisTemplate.opsForValue().set(key, JSONObject.toJSONString(danmu));
}
}
@Mapper
public interface DanmuDao {
Integer addDanmu(Danmu danmu);
List<Danmu> getDanmus(Map<String, Object> params);
}
<mapper>
<insert id="addDanmu" parameterType="com.imooc.bilibili.domain.Danmu">
insert into
t_danmu(
userId,
videoId,
content,
danmuTime,
createTime
)values(
#{userId},
#{videoId},
#{content},
#{danmuTime},
#{createTime}
)
insert>
<select id="getDanmus" resultType="com.imooc.bilibili.domain.Danmu" parameterType="java.util.Map">
select
*
from
t_danmu
where
videoId = #{videoId}
<if test="startTime != null and startTime != '' ">
and createTime =]]> #{startTime}
if>
<if test="endTime != null and endTime != '' ">
and createTime #{endTime}
if>
select>
mapper>