diff --git a/.idea/.cache/.Apifox_Helper/.toolWindow.db b/.idea/.cache/.Apifox_Helper/.toolWindow.db
index 1bfb44b..7b57fac 100644
Binary files a/.idea/.cache/.Apifox_Helper/.toolWindow.db and b/.idea/.cache/.Apifox_Helper/.toolWindow.db differ
diff --git a/video-recommend/pom.xml b/video-recommend/pom.xml
index 3e8e77f..365b81e 100644
--- a/video-recommend/pom.xml
+++ b/video-recommend/pom.xml
@@ -12,24 +12,123 @@
video-recommend
+
com.nuaa.video
video-common
${project.version}
-
+
- org.apache.spark
- spark-mllib_2.12
- 3.4.1
+ org.springframework.boot
+ spring-boot-starter-web
-
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-discovery
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-config
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-openfeign
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-loadbalancer
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-sentinel
+
+
+
+
+ com.mysql
+ mysql-connector-j
+ runtime
+
+
+
+
+ com.baomidou
+ mybatis-plus-spring-boot3-starter
+ 3.5.11
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+
org.apache.commons
commons-math3
3.6.1
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+
+ cn.hutool
+ hutool-all
+ 5.8.26
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+
\ No newline at end of file
diff --git a/video-recommend/src/main/java/com/nuaa/video/recommend/RecommendApplication.java b/video-recommend/src/main/java/com/nuaa/video/recommend/RecommendApplication.java
index 0e12137..c021228 100644
--- a/video-recommend/src/main/java/com/nuaa/video/recommend/RecommendApplication.java
+++ b/video-recommend/src/main/java/com/nuaa/video/recommend/RecommendApplication.java
@@ -1,11 +1,20 @@
package com.nuaa.video.recommend;
+import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+import org.springframework.scheduling.annotation.EnableScheduling;
+/**
+ * 推荐服务启动类
+ */
@SpringBootApplication
@EnableDiscoveryClient
+@EnableFeignClients
+@EnableScheduling
+@MapperScan("com.nuaa.video.recommend.mapper")
public class RecommendApplication {
public static void main(String[] args) {
SpringApplication.run(RecommendApplication.class, args);
diff --git a/video-recommend/src/main/java/com/nuaa/video/recommend/config/SecurityConfig.java b/video-recommend/src/main/java/com/nuaa/video/recommend/config/SecurityConfig.java
new file mode 100644
index 0000000..20b5e78
--- /dev/null
+++ b/video-recommend/src/main/java/com/nuaa/video/recommend/config/SecurityConfig.java
@@ -0,0 +1,42 @@
+package com.nuaa.video.recommend.config;
+
+import com.nuaa.video.recommend.filter.JwtAuthenticationFilter;
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+/**
+ * 安全配置类
+ */
+@Configuration
+@EnableWebSecurity
+@RequiredArgsConstructor
+public class SecurityConfig {
+
+ private final JwtAuthenticationFilter jwtAuthenticationFilter;
+
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+ http
+ .csrf(csrf -> csrf.disable())
+ .cors(cors -> cors.disable())
+ .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+ .authorizeHttpRequests(auth -> auth
+ .requestMatchers(
+ "/recommend/hot/**",
+ "/recommend/tag/**",
+ "/actuator/**",
+ "/error"
+ ).permitAll()
+ .anyRequest().authenticated()
+ )
+ .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
+
+ return http.build();
+ }
+}
\ No newline at end of file
diff --git a/video-recommend/src/main/java/com/nuaa/video/recommend/controller/RecommendController.java b/video-recommend/src/main/java/com/nuaa/video/recommend/controller/RecommendController.java
new file mode 100644
index 0000000..fea78f6
--- /dev/null
+++ b/video-recommend/src/main/java/com/nuaa/video/recommend/controller/RecommendController.java
@@ -0,0 +1,104 @@
+package com.nuaa.video.recommend.controller;
+
+import com.nuaa.video.common.model.vo.Result;
+import com.nuaa.video.recommend.dto.UserBehaviorDTO;
+import com.nuaa.video.recommend.service.RecommendService;
+import com.nuaa.video.recommend.vo.VideoRecommendVO;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 推荐控制器
+ */
+@Slf4j
+@RestController
+@RequestMapping("/recommend")
+@RequiredArgsConstructor
+public class RecommendController {
+
+ private final RecommendService recommendService;
+
+ /**
+ * 获取首页推荐
+ */
+ @GetMapping("/home")
+ public Result> getHomeRecommend(
+ @RequestParam(value = "page", defaultValue = "1") Integer page,
+ @RequestParam(value = "size", defaultValue = "20") Integer size,
+ HttpServletRequest request) {
+ Long userId = getUserIdFromRequest(request);
+ return recommendService.getHomeRecommend(userId, page, size);
+ }
+
+ /**
+ * 获取热门推荐
+ */
+ @GetMapping("/hot")
+ public Result> getHotRecommend(
+ @RequestParam(value = "periodType", defaultValue = "1") Integer periodType,
+ @RequestParam(value = "size", defaultValue = "20") Integer size) {
+ return recommendService.getHotRecommend(periodType, size);
+ }
+
+ /**
+ * 获取相似视频推荐
+ */
+ @GetMapping("/similar/{videoId}")
+ public Result> getSimilarVideos(
+ @PathVariable Long videoId,
+ @RequestParam(value = "size", defaultValue = "10") Integer size) {
+ return recommendService.getSimilarVideos(videoId, size);
+ }
+
+ /**
+ * 获取标签视频推荐
+ */
+ @GetMapping("/tag/{tagId}")
+ public Result> getTagVideos(
+ @PathVariable Long tagId,
+ @RequestParam(value = "size", defaultValue = "20") Integer size) {
+ return recommendService.getTagVideos(tagId, size);
+ }
+
+ /**
+ * 记录用户行为
+ */
+ @PostMapping("/behavior")
+ public Result recordUserBehavior(
+ @RequestBody @Valid UserBehaviorDTO behaviorDTO,
+ HttpServletRequest request) {
+ Long userId = getUserIdFromRequest(request);
+ if (userId == null) {
+ return Result.error("未登录");
+ }
+ return recommendService.recordUserBehavior(userId, behaviorDTO);
+ }
+
+ /**
+ * 更新用户兴趣
+ */
+ @PostMapping("/interest/update")
+ public Result updateUserInterests(HttpServletRequest request) {
+ Long userId = getUserIdFromRequest(request);
+ if (userId == null) {
+ return Result.error("未登录");
+ }
+ return recommendService.updateUserInterests(userId);
+ }
+
+ /**
+ * 从请求中获取用户ID
+ */
+ private Long getUserIdFromRequest(HttpServletRequest request) {
+ Object userIdObj = request.getAttribute("userId");
+ if (userIdObj instanceof Long) {
+ return (Long) userIdObj;
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/video-recommend/src/main/java/com/nuaa/video/recommend/dto/UserBehaviorDTO.java b/video-recommend/src/main/java/com/nuaa/video/recommend/dto/UserBehaviorDTO.java
new file mode 100644
index 0000000..b446b1e
--- /dev/null
+++ b/video-recommend/src/main/java/com/nuaa/video/recommend/dto/UserBehaviorDTO.java
@@ -0,0 +1,26 @@
+package com.nuaa.video.recommend.dto;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 用户行为记录DTO
+ */
+@Data
+public class UserBehaviorDTO implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 视频ID
+ */
+ @NotNull(message = "视频ID不能为空")
+ private Long videoId;
+
+ /**
+ * 行为类型(1:浏览,2:点赞,3:收藏,4:评论,5:分享,6:完整观看)
+ */
+ @NotNull(message = "行为类型不能为空")
+ private Integer behaviorType;
+}
\ No newline at end of file
diff --git a/video-recommend/src/main/java/com/nuaa/video/recommend/entity/RecommendHotVideo.java b/video-recommend/src/main/java/com/nuaa/video/recommend/entity/RecommendHotVideo.java
new file mode 100644
index 0000000..bda4df1
--- /dev/null
+++ b/video-recommend/src/main/java/com/nuaa/video/recommend/entity/RecommendHotVideo.java
@@ -0,0 +1,59 @@
+package com.nuaa.video.recommend.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 热门视频实体
+ */
+@Data
+@TableName("recommend_hot_video")
+public class RecommendHotVideo implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 主键ID
+ */
+ @TableId(value = "id", type = IdType.AUTO)
+ private Long id;
+
+ /**
+ * 视频ID
+ */
+ private Long videoId;
+
+ /**
+ * 热度分数
+ */
+ private Float hotScore;
+
+ /**
+ * 周期类型(1:天,2:周,3:月)
+ */
+ private Integer periodType;
+
+ /**
+ * 创建人
+ */
+ private String createdBy;
+
+ /**
+ * 更新人
+ */
+ private String updatedBy;
+
+ /**
+ * 创建时间
+ */
+ private LocalDateTime createTime;
+
+ /**
+ * 更新时间
+ */
+ private LocalDateTime updateTime;
+}
\ No newline at end of file
diff --git a/video-recommend/src/main/java/com/nuaa/video/recommend/entity/RecommendUserBehavior.java b/video-recommend/src/main/java/com/nuaa/video/recommend/entity/RecommendUserBehavior.java
new file mode 100644
index 0000000..4b9f47a
--- /dev/null
+++ b/video-recommend/src/main/java/com/nuaa/video/recommend/entity/RecommendUserBehavior.java
@@ -0,0 +1,54 @@
+package com.nuaa.video.recommend.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 用户行为记录实体
+ */
+@Data
+@TableName("recommend_user_behavior")
+public class RecommendUserBehavior implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 主键ID
+ */
+ @TableId(value = "id", type = IdType.AUTO)
+ private Long id;
+
+ /**
+ * 用户ID
+ */
+ private Long userId;
+
+ /**
+ * 视频ID
+ */
+ private Long videoId;
+
+ /**
+ * 行为类型(1:浏览,2:点赞,3:收藏,4:评论,5:分享,6:完整观看)
+ */
+ private Integer behaviorType;
+
+ /**
+ * 行为权重
+ */
+ private Float behaviorWeight;
+
+ /**
+ * 创建人
+ */
+ private String createdBy;
+
+ /**
+ * 创建时间
+ */
+ private LocalDateTime createTime;
+}
\ No newline at end of file
diff --git a/video-recommend/src/main/java/com/nuaa/video/recommend/entity/RecommendUserInterest.java b/video-recommend/src/main/java/com/nuaa/video/recommend/entity/RecommendUserInterest.java
new file mode 100644
index 0000000..b0341fd
--- /dev/null
+++ b/video-recommend/src/main/java/com/nuaa/video/recommend/entity/RecommendUserInterest.java
@@ -0,0 +1,59 @@
+package com.nuaa.video.recommend.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 用户兴趣标签实体
+ */
+@Data
+@TableName("recommend_user_interest")
+public class RecommendUserInterest implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 主键ID
+ */
+ @TableId(value = "id", type = IdType.AUTO)
+ private Long id;
+
+ /**
+ * 用户ID
+ */
+ private Long userId;
+
+ /**
+ * 标签ID
+ */
+ private Long tagId;
+
+ /**
+ * 权重
+ */
+ private Float weight;
+
+ /**
+ * 创建人
+ */
+ private String createdBy;
+
+ /**
+ * 更新人
+ */
+ private String updatedBy;
+
+ /**
+ * 创建时间
+ */
+ private LocalDateTime createTime;
+
+ /**
+ * 更新时间
+ */
+ private LocalDateTime updateTime;
+}
\ No newline at end of file
diff --git a/video-recommend/src/main/java/com/nuaa/video/recommend/entity/RecommendUserRecommendation.java b/video-recommend/src/main/java/com/nuaa/video/recommend/entity/RecommendUserRecommendation.java
new file mode 100644
index 0000000..fc56649
--- /dev/null
+++ b/video-recommend/src/main/java/com/nuaa/video/recommend/entity/RecommendUserRecommendation.java
@@ -0,0 +1,64 @@
+package com.nuaa.video.recommend.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 用户推荐实体
+ */
+@Data
+@TableName("recommend_user_recommendation")
+public class RecommendUserRecommendation implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 主键ID
+ */
+ @TableId(value = "id", type = IdType.AUTO)
+ private Long id;
+
+ /**
+ * 用户ID
+ */
+ private Long userId;
+
+ /**
+ * 视频ID
+ */
+ private Long videoId;
+
+ /**
+ * 推荐类型(1:基于内容,2:协同过滤,3:热门推荐,4:新视频推荐)
+ */
+ private Integer recommendType;
+
+ /**
+ * 推荐分数
+ */
+ private Float score;
+
+ /**
+ * 创建人
+ */
+ private String createdBy;
+
+ /**
+ * 更新人
+ */
+ private String updatedBy;
+
+ /**
+ * 创建时间
+ */
+ private LocalDateTime createTime;
+
+ /**
+ * 更新时间
+ */
+ private LocalDateTime updateTime;
+}
\ No newline at end of file
diff --git a/video-recommend/src/main/java/com/nuaa/video/recommend/entity/RecommendVideoSimilarity.java b/video-recommend/src/main/java/com/nuaa/video/recommend/entity/RecommendVideoSimilarity.java
new file mode 100644
index 0000000..e7e5735
--- /dev/null
+++ b/video-recommend/src/main/java/com/nuaa/video/recommend/entity/RecommendVideoSimilarity.java
@@ -0,0 +1,59 @@
+package com.nuaa.video.recommend.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 视频相似度实体
+ */
+@Data
+@TableName("recommend_video_similarity")
+public class RecommendVideoSimilarity implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 主键ID
+ */
+ @TableId(value = "id", type = IdType.AUTO)
+ private Long id;
+
+ /**
+ * 视频ID
+ */
+ private Long videoId;
+
+ /**
+ * 相似视频ID
+ */
+ private Long similarVideoId;
+
+ /**
+ * 相似度
+ */
+ private Float similarity;
+
+ /**
+ * 创建人
+ */
+ private String createdBy;
+
+ /**
+ * 更新人
+ */
+ private String updatedBy;
+
+ /**
+ * 创建时间
+ */
+ private LocalDateTime createTime;
+
+ /**
+ * 更新时间
+ */
+ private LocalDateTime updateTime;
+}
\ No newline at end of file
diff --git a/video-recommend/src/main/java/com/nuaa/video/recommend/feign/ContentFeignClient.java b/video-recommend/src/main/java/com/nuaa/video/recommend/feign/ContentFeignClient.java
new file mode 100644
index 0000000..7c4d5b7
--- /dev/null
+++ b/video-recommend/src/main/java/com/nuaa/video/recommend/feign/ContentFeignClient.java
@@ -0,0 +1,72 @@
+package com.nuaa.video.recommend.feign;
+
+import com.nuaa.video.common.model.vo.Result;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import java.util.List;
+
+/**
+ * 内容服务Feign客户端
+ */
+@FeignClient(name = "video-content", fallbackFactory = ContentFeignClientFallbackFactory.class)
+public interface ContentFeignClient {
+
+ /**
+ * 获取视频详情
+ *
+ * @param id 视频ID
+ * @return 视频信息
+ */
+ @GetMapping("/content/video/{id}")
+ Result