diff --git a/pom.xml b/pom.xml
index b981831..8cbe2ac 100644
--- a/pom.xml
+++ b/pom.xml
@@ -243,6 +243,13 @@
4.1.94.Final
+
+
+ cn.easyes
+ easy-es-boot-starter
+ 1.1.1
+
+
diff --git a/src/main/java/com/guwan/backend/config/EasyEsConfig.java b/src/main/java/com/guwan/backend/config/EasyEsConfig.java
new file mode 100644
index 0000000..f63ad93
--- /dev/null
+++ b/src/main/java/com/guwan/backend/config/EasyEsConfig.java
@@ -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 {
+}
\ No newline at end of file
diff --git a/src/main/java/com/guwan/backend/es/document/VideoDocument.java b/src/main/java/com/guwan/backend/es/document/VideoDocument.java
new file mode 100644
index 0000000..36982ec
--- /dev/null
+++ b/src/main/java/com/guwan/backend/es/document/VideoDocument.java
@@ -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;
+}
\ No newline at end of file
diff --git a/src/main/java/com/guwan/backend/es/mapper/VideoEsMapper.java b/src/main/java/com/guwan/backend/es/mapper/VideoEsMapper.java
new file mode 100644
index 0000000..b38e301
--- /dev/null
+++ b/src/main/java/com/guwan/backend/es/mapper/VideoEsMapper.java
@@ -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 {
+}
\ No newline at end of file
diff --git a/src/main/java/com/guwan/backend/service/VideoSearchService.java b/src/main/java/com/guwan/backend/service/VideoSearchService.java
new file mode 100644
index 0000000..0a53ad3
--- /dev/null
+++ b/src/main/java/com/guwan/backend/service/VideoSearchService.java
@@ -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 wrapper = new LambdaEsUpdateWrapper<>();
+ wrapper.eq(VideoDocument::getId, id)
+ .set(VideoDocument::getViewCount, viewCount);
+ videoEsMapper.update(null, wrapper);
+ }
+
+ /**
+ * 更新视频点赞次数
+ */
+ public void updateLikeCount(Long id, Integer likeCount) {
+ LambdaEsUpdateWrapper wrapper = new LambdaEsUpdateWrapper<>();
+ wrapper.eq(VideoDocument::getId, id)
+ .set(VideoDocument::getLikeCount, likeCount);
+ videoEsMapper.update(null, wrapper);
+ }
+
+ /**
+ * 搜索视频
+ */
+ public List search(String keyword) {
+ // 构建查询条件
+ LambdaEsQueryWrapper 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 documents = videoEsMapper.selectList(wrapper);
+
+ // 转换结果
+ return documents.stream()
+ .map(this::convertToDTO)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * 推荐相似视频
+ */
+ public List findSimilar(Long id, int limit) {
+ // 获取当前视频
+ VideoDocument current = videoEsMapper.selectById(id);
+ if (current == null) {
+ return List.of();
+ }
+
+ // 构建查询条件
+ LambdaEsQueryWrapper 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 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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/guwan/backend/service/impl/VideoServiceImpl.java b/src/main/java/com/guwan/backend/service/impl/VideoServiceImpl.java
index 1ca67da..a4fd690 100644
--- a/src/main/java/com/guwan/backend/service/impl/VideoServiceImpl.java
+++ b/src/main/java/com/guwan/backend/service/impl/VideoServiceImpl.java
@@ -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 searchVideos(String keyword) {
+ return videoSearchService.search(keyword);
+ }
+
+ @Override
+ public List getSimilarVideos(Long id, int limit) {
+ return videoSearchService.findSimilar(id, limit);
+ }
+
private VideoDTO convertToDTO(Video video) {
if (video == null) {
return null;
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index c9f9643..bd7027d 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -129,4 +129,15 @@ config:
# SRS配置
srs:
server:
- url: http://localhost:1985 # SRS HTTP API地址
\ No newline at end of file
+ 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
\ No newline at end of file