feat: 视频

视频初步搭建
This commit is contained in:
ovo 2024-12-08 19:21:52 +08:00
parent 05963d7c16
commit 92ce67245d
7 changed files with 254 additions and 1 deletions

View File

@ -243,6 +243,13 @@
<version>4.1.94.Final</version>
</dependency>
<!-- Easy-Es -->
<dependency>
<groupId>cn.easyes</groupId>
<artifactId>easy-es-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>
<build>

View File

@ -0,0 +1,9 @@
package com.guwan.backend.config;
import cn.easyes.starter.register.EsMapperScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@EsMapperScan("com.guwan.backend.es.mapper")
public class EasyEsConfig {
}

View File

@ -0,0 +1,58 @@
package com.guwan.backend.es.document;
import cn.easyes.annotation.IndexField;
import cn.easyes.annotation.IndexId;
import cn.easyes.annotation.IndexName;
import cn.easyes.annotation.Score;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@IndexName("videos")
public class VideoDocument {
@IndexId
private Long id;
@IndexField(fieldType = "text", analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
private String title;
@IndexField(fieldType = "text", analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
private String description;
@IndexField(fieldType = "keyword")
private String url;
@IndexField(fieldType = "keyword")
private String coverUrl;
@IndexField(fieldType = "long")
private Long duration;
@IndexField(fieldType = "long")
private Long size;
@IndexField(fieldType = "keyword")
private String status;
@IndexField(fieldType = "long")
private Long userId;
@IndexField(fieldType = "keyword")
private String username;
@IndexField(fieldType = "date")
private LocalDateTime createdTime;
@IndexField(fieldType = "integer")
private Integer viewCount;
@IndexField(fieldType = "integer")
private Integer likeCount;
@IndexField(fieldType = "keyword")
private String tags;
@Score
private Float score;
}

View File

@ -0,0 +1,9 @@
package com.guwan.backend.es.mapper;
import cn.easyes.core.conditions.interfaces.BaseEsMapper;
import com.guwan.backend.es.document.VideoDocument;
import org.springframework.stereotype.Repository;
@Repository
public interface VideoEsMapper extends BaseEsMapper<VideoDocument> {
}

View File

@ -0,0 +1,133 @@
package com.guwan.backend.service;
import cn.easyes.core.conditions.LambdaEsQueryWrapper;
import cn.easyes.core.conditions.LambdaEsUpdateWrapper;
import com.guwan.backend.dto.video.VideoDTO;
import com.guwan.backend.entity.Video;
import com.guwan.backend.es.document.VideoDocument;
import com.guwan.backend.es.mapper.VideoEsMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Service
@RequiredArgsConstructor
public class VideoSearchService {
private final VideoEsMapper videoEsMapper;
/**
* 保存或更新视频文档
*/
public void saveOrUpdate(Video video) {
VideoDocument document = convertToDocument(video);
videoEsMapper.insert(document);
}
/**
* 删除视频文档
*/
public void delete(Long id) {
videoEsMapper.deleteById(id);
}
/**
* 更新视频观看次数
*/
public void updateViewCount(Long id, Integer viewCount) {
LambdaEsUpdateWrapper<VideoDocument> wrapper = new LambdaEsUpdateWrapper<>();
wrapper.eq(VideoDocument::getId, id)
.set(VideoDocument::getViewCount, viewCount);
videoEsMapper.update(null, wrapper);
}
/**
* 更新视频点赞次数
*/
public void updateLikeCount(Long id, Integer likeCount) {
LambdaEsUpdateWrapper<VideoDocument> wrapper = new LambdaEsUpdateWrapper<>();
wrapper.eq(VideoDocument::getId, id)
.set(VideoDocument::getLikeCount, likeCount);
videoEsMapper.update(null, wrapper);
}
/**
* 搜索视频
*/
public List<VideoDTO> search(String keyword) {
// 构建查询条件
LambdaEsQueryWrapper<VideoDocument> wrapper = new LambdaEsQueryWrapper<>();
wrapper.and(w -> w
.match(VideoDocument::getTitle, keyword)
.or()
.match(VideoDocument::getDescription, keyword)
.or()
.match(VideoDocument::getTags, keyword)
);
// 设置排序
wrapper.orderByDesc(VideoDocument::getScore)
.orderByDesc(VideoDocument::getCreatedTime);
// 执行查询
List<VideoDocument> documents = videoEsMapper.selectList(wrapper);
// 转换结果
return documents.stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
/**
* 推荐相似视频
*/
public List<VideoDTO> findSimilar(Long id, int limit) {
// 获取当前视频
VideoDocument current = videoEsMapper.selectById(id);
if (current == null) {
return List.of();
}
// 构建查询条件
LambdaEsQueryWrapper<VideoDocument> wrapper = new LambdaEsQueryWrapper<>();
wrapper.and(w -> w
.match(VideoDocument::getTags, current.getTags())
.or()
.match(VideoDocument::getTitle, current.getTitle())
.or()
.match(VideoDocument::getDescription, current.getDescription())
);
// 排除当前视频
wrapper.ne(VideoDocument::getId, id);
// 设置排序和限制
wrapper.orderByDesc(VideoDocument::getScore)
.limit(limit);
// 执行查询
List<VideoDocument> documents = videoEsMapper.selectList(wrapper);
// 转换结果
return documents.stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
private VideoDocument convertToDocument(Video video) {
VideoDocument document = new VideoDocument();
BeanUtils.copyProperties(video, document);
return document;
}
private VideoDTO convertToDTO(VideoDocument document) {
VideoDTO dto = new VideoDTO();
BeanUtils.copyProperties(document, dto);
return dto;
}
}

View File

@ -10,6 +10,7 @@ import com.guwan.backend.entity.VideoLike;
import com.guwan.backend.mapper.VideoLikeMapper;
import com.guwan.backend.mapper.VideoMapper;
import com.guwan.backend.service.VideoService;
import com.guwan.backend.service.VideoSearchService;
import com.guwan.backend.util.MinioUtil;
import com.guwan.backend.util.SecurityUtil;
import lombok.RequiredArgsConstructor;
@ -19,6 +20,8 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
@ -28,6 +31,7 @@ public class VideoServiceImpl implements VideoService {
private final MinioUtil minioUtil;
private final SecurityUtil securityUtil;
private final VideoLikeMapper videoLikeMapper;
private final VideoSearchService videoSearchService;
@Override
@Transactional
@ -58,6 +62,9 @@ public class VideoServiceImpl implements VideoService {
videoMapper.insert(video);
// 保存到ES
videoSearchService.saveOrUpdate(video);
return convertToDTO(video);
} catch (Exception e) {
log.error("上传视频失败", e);
@ -107,6 +114,9 @@ public class VideoServiceImpl implements VideoService {
// 删除数据库记录
videoMapper.deleteById(id);
// 从ES中删除
videoSearchService.delete(id);
}
@Override
@ -141,6 +151,9 @@ public class VideoServiceImpl implements VideoService {
if (video != null) {
video.setViewCount(video.getViewCount() + 1);
videoMapper.updateById(video);
// 更新ES中的观看次数
videoSearchService.updateViewCount(id, video.getViewCount());
}
}
@ -185,6 +198,9 @@ public class VideoServiceImpl implements VideoService {
}
videoMapper.updateById(video);
// 更新ES中的点赞次数
videoSearchService.updateLikeCount(id, video.getLikeCount());
}
// 添加新方法检查用户是否已点赞
@ -201,6 +217,16 @@ public class VideoServiceImpl implements VideoService {
return videoLikeMapper.selectCount(wrapper) > 0;
}
@Override
public List<VideoDTO> searchVideos(String keyword) {
return videoSearchService.search(keyword);
}
@Override
public List<VideoDTO> getSimilarVideos(Long id, int limit) {
return videoSearchService.findSimilar(id, limit);
}
private VideoDTO convertToDTO(Video video) {
if (video == null) {
return null;

View File

@ -129,4 +129,15 @@ config:
# SRS配置
srs:
server:
url: http://localhost:1985 # SRS HTTP API地址
url: http://localhost:1985 # SRS HTTP API地址
easy-es:
enable: true
address: localhost:9200
username: elastic # 如果有的话
password: elastic # 如果有的话
global-config:
process-index-mode: manual
print-dsl: true
distributed: false
response-log: true