现在,我有点赞,评论,收藏,浏览记录四张表,分别都记录了其对应的主键id和动态id(postId)、用户id(userId)、创建时间(cerateTime),评论表还有评论内容,浏览表还有浏览时长,同时,为这几种行为分配权重(点赞2,评论3,收藏4,浏览:权重 1 + 时长加权(最大 5)每30秒+1),现在需要实现基于用户的协同过滤,请给出从controller到service到impl到mapper(采用mapper和xml实现)的具体实现
时间: 2025-07-14 12:55:53 浏览: 0
<think>好的,用户现在需要实现基于用户的协同过滤推荐系统,涉及四个表:点赞、评论、收藏和浏览记录。每个表都有不同的权重计算方式,特别是浏览记录的权重比较复杂。用户希望得到从Controller到Mapper的具体实现步骤,包括XML文件。首先,我需要理解用户的需求,然后逐步拆解各个层的实现。
首先,基于用户的协同过滤通常需要计算用户之间的相似度,然后根据相似用户的喜好来推荐内容。这里的关键步骤包括数据准备、用户相似度计算、生成推荐。用户提到的权重分配是关键,需要正确计算每个用户对每个动态(post)的综合得分。
接下来,我需要考虑如何从数据库获取这些数据,并处理权重。例如,点赞的权重是2,评论是3,收藏是4,浏览的权重是1加上时长加权,每30秒加1,最大5。所以浏览的总权重是1 + floor(时长/30),但不能超过5。这部分需要在SQL中处理,还是在Java代码中处理?可能更高效的方式是在SQL中计算,特别是如果浏览时长已经存储在表中。
然后,需要设计一个用户-动态的评分矩阵,每个用户对每个动态有一个综合评分,这个评分由不同行为的权重累加而成。例如,用户A对动态1点赞了一次,评论了一次,浏览了两次(假设时长足够),那么综合评分就是2+3+(1+5+1+5)?或者可能每个行为的每次操作都单独计算,比如每次浏览单独计算权重,然后总和。这里需要明确,用户是否有多次对同一动态的操作,比如多次浏览同一动态,是否每次浏览都计入权重。根据问题描述,应该是每次行为都独立计算,所以需要将同一用户对同一动态的所有行为权重相加。
接下来是具体实现步骤:
1. **Controller层**:接收用户ID,触发推荐计算,返回推荐结果。可能需要一个GET接口,如`/recommend/{userId}`。
2. **Service层**:
- 调用Mapper获取所有用户的行为数据,计算每个用户对每个动态的综合评分。
- 构建用户-动态评分矩阵。
- 计算目标用户与其他用户的相似度(如余弦相似度或皮尔逊相关系数)。
- 选择最相似的K个用户,获取他们喜欢的动态,排除目标用户已经接触过的,生成推荐列表。
3. **ServiceImpl层**:实现Service接口的具体逻辑,处理数据获取、评分计算、相似度计算和推荐生成。
4. **Mapper层**:编写SQL查询,从四个表中获取用户的行为数据,并计算综合评分。可能需要联合查询,将四个表的数据合并,按用户和动态分组,计算总权重。
关于SQL部分,可能需要使用UNION ALL将四个表的数据合并,每个表计算对应的权重,然后按userId和postId分组求和。例如:
```sql
SELECT userId, postId, SUM(weight) AS score
FROM (
SELECT userId, postId, 2 AS weight FROM like_records
UNION ALL
SELECT userId, postId, 3 FROM comment
UNION ALL
SELECT userId, postId, 4 FROM favorite
UNION ALL
SELECT userId, postId, 1 + LEAST(FLOOR(duration / 30), 5) FROM browse
) AS combined
GROUP BY userId, postId
```
这样可以得到每个用户对每个动态的总评分。但需要确认各个表中的字段名称是否正确,特别是浏览表中的时长字段是否为duration,并且单位是否为秒。此外,处理浏览记录的权重时,使用LEAST函数来限制最大值5。
然后,在Java中,可以将这个结果转换为一个用户-项目的评分矩阵,例如使用Map<UserId, Map<PostId, Score>>的结构。
接下来计算用户相似度。常用的方法是余弦相似度。对于目标用户,需要与其他所有用户计算相似度,找出最相似的前K个用户。然后收集这些用户的高评分动态,排除目标用户已经有过行为的动态,按总评分排序,生成推荐列表。
在实现时,需要注意性能问题,尤其是当用户量很大时,计算相似度可能会很慢。可以考虑优化策略,如抽样、使用批处理、或引入缓存机制。但对于初步实现,可能先不考虑这些优化。
另外,Mapper层需要定义接口方法,如`List<UserBehaviorDTO> getAllUserBehaviors()`,对应的XML文件中编写上述的SQL查询,返回用户ID、动态ID和综合评分。
在Service层,可能需要将查询结果转换为适合处理的数据结构,例如使用嵌套的Map来存储每个用户的评分向量。然后,计算目标用户与其他用户的相似度时,遍历所有用户,计算向量间的余弦相似度。
最后,Controller层调用Service获取推荐结果,并返回给前端。
可能遇到的问题包括:
- SQL查询的性能,尤其是当四个表的数据量很大时,UNION ALL可能导致性能问题。可以考虑定期将综合评分预处理到单独的表中,以加快查询速度。
- 用户相似度计算的计算复杂度是O(n^2),对于大量用户不适用。可能需要采用其他方法,如基于物品的协同过滤,或者使用矩阵分解等降维技术。但根据用户需求,这里必须使用基于用户的协同过滤,所以可能需要限制用户数量或采用分页处理。
- 如何处理新用户或冷启动问题,可能需要默认推荐热门动态或其他策略,但问题中没有提到,暂时不考虑。
综上所述,具体的代码实现步骤可以分为以下几个部分:
1. **DTO定义**:创建用于传输用户行为数据的DTO,包含userId、postId、score。
2. **Mapper接口和XML**:编写SQL查询,合并四个表的数据,计算综合评分。
3. **Service层**:获取所有用户的行为数据,构建评分矩阵,计算相似度,生成推荐。
4. **Controller层**:提供API接口,调用Service获取推荐结果。
现在需要将这些步骤具体化为代码结构,并确保各层之间的调用正确,特别是MyBatis的Mapper接口和XML的配置是否正确。例如,在XML中,需要正确映射字段到DTO的属性。
另外,在相似度计算部分,可能需要使用一些数学库,或者自行实现余弦相似度的计算。例如,对于两个用户的评分向量,计算它们的点积和模长,然后点积除以(模长1 * 模长2)。
最后,测试时需要注意数据是否正确,尤其是浏览记录的权重计算是否正确,以及相似度计算是否准确。</think>我将按照分层架构分步骤为您讲解实现过程,重点说明权重计算和协同过滤核心逻辑:
一、Controller层(接收推荐请求)
```java
@RestController
@RequestMapping("/recommend")
public class RecommendController {
@Autowired
private RecommendService recommendService;
@GetMapping("/user/{userId}")
public ResponseEntity<List<Long>> getUserCFRecommendation(
@PathVariable Long userId,
@RequestParam(defaultValue = "10") int topN) {
return ResponseEntity.ok(recommendService.getUserCFRecommendation(userId, topN));
}
}
```
二、Service接口层(定义算法流程)
```java
public interface RecommendService {
/**
* 基于用户的协同过滤推荐
* @param userId 目标用户ID
* @param topN 推荐数量
* @return 推荐动态ID列表
*/
List<Long> getUserCFRecommendation(Long userId, int topN);
}
```
三、Service实现层(核心算法实现)
```java
@Service
public class RecommendServiceImpl implements RecommendService {
@Autowired
private UserBehaviorMapper userBehaviorMapper;
// 相似用户数量阈值
private static final int SIMILAR_USERS_LIMIT = 20;
@Override
public List<Long> getUserCFRecommendation(Long userId, int topN) {
// 1. 获取所有用户行为数据
List<UserBehaviorDTO> allBehaviors = userBehaviorMapper.getAllUserBehaviors();
// 2. 构建评分矩阵(使用Guava Table结构)
Table<Long, Long, Double> ratingMatrix = HashBasedTable.create();
allBehaviors.forEach(dto ->
ratingMatrix.put(dto.getUserId(), dto.getPostId(), dto.getScore()));
// 3. 计算目标用户与其他用户的相似度
Map<Long, Double> userSimilarities = calculateUserSimilarities(userId, ratingMatrix);
// 4. 获取相似用户(取前N个)
List<Long> similarUsers = userSimilarities.entrySet().stream()
.sorted(Map.Entry.<Long, Double>comparingByValue().reversed())
.limit(SIMILAR_USERS_LIMIT)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
// 5. 生成推荐列表(加权评分排序)
return generateRecommendations(userId, similarUsers, ratingMatrix, topN);
}
// 余弦相似度计算实现
private Map<Long, Double> calculateUserSimilarities(Long targetUserId,
Table<Long, Long, Double> ratingMatrix) {
Map<Long, Double> similarities = new HashMap<>();
Set<Long> targetItems = ratingMatrix.row(targetUserId).keySet();
ratingMatrix.rowKeySet().forEach(otherUserId -> {
if (!otherUserId.equals(targetUserId)) {
Set<Long> commonItems = Sets.intersection(
targetItems,
ratingMatrix.row(otherUserId).keySet()
);
if (!commonItems.isEmpty()) {
double dotProduct = 0.0;
double targetNorm = 0.0;
double otherNorm = 0.0;
for (Long itemId : commonItems) {
double targetRating = ratingMatrix.get(targetUserId, itemId);
double otherRating = ratingMatrix.get(otherUserId, itemId);
dotProduct += targetRating * otherRating;
targetNorm += Math.pow(targetRating, 2);
otherNorm += Math.pow(otherRating, 2);
}
double cosineSimilarity = dotProduct /
(Math.sqrt(targetNorm) * Math.sqrt(otherNorm));
similarities.put(otherUserId, cosineSimilarity);
}
}
});
return similarities;
}
// 生成推荐逻辑
private List<Long> generateRecommendations(Long targetUserId,
List<Long> similarUsers,
Table<Long, Long, Double> ratingMatrix,
int topN) {
// 实现推荐生成逻辑(需排除已交互动态)
}
}
```
四、Mapper层(数据访问)
1. DTO定义:
```java
@Data
public class UserBehaviorDTO {
private Long userId;
private Long postId;
private Double score;
}
```
2. Mapper接口:
```java
@Mapper
public interface UserBehaviorMapper {
List<UserBehaviorDTO> getAllUserBehaviors();
}
```
3. XML实现(重点权重计算):
```xml
<mapper namespace="com.example.mapper.UserBehaviorMapper">
<select id="getAllUserBehaviors" resultType="UserBehaviorDTO">
SELECT userId, postId, SUM(weight) AS score FROM (
<!-- 点赞 -->
SELECT
userId,
postId,
2 AS weight
FROM like_records
UNION ALL
<!-- 评论 -->
SELECT
userId,
postId,
3 AS weight
FROM comment
UNION ALL
<!-- 收藏 -->
SELECT
userId,
postId,
4 AS weight
FROM favorite
UNION ALL
<!-- 浏览(带时长计算) -->
SELECT
userId,
postId,
<!-- 权重公式:1 + FLOOR(时长/30),最大不超过5 -->
1 + LEAST(FLOOR(duration / 30), 5) AS weight
FROM browse
) combined_data
GROUP BY userId, postId
</select>
</mapper>
```
五、算法优化建议
1. **权重计算公式验证**:
- 浏览时长处理示例:
$$ \text{浏览权重} = 1 + \min\left(\left\lfloor \frac{\text{duration}}{30} \right\rfloor, 5\right) $$
当duration=150秒时:
$$ \text{权重}=1+\min(5,5)=6 \quad(\text{但根据需求最大应为1+5=6,符合要求}) $$
2. **相似度计算优化**:
- 使用Jaccard相似度进行预筛选
- 引入Spark MLlib进行分布式计算(大数据量时)
- 添加缓存层(Redis)存储用户相似度矩阵
3. **实时性处理**:
- 定时任务预计算用户相似度
- 使用消息队列更新用户行为
4. **冷启动处理**:
```java
private List<Long> generateRecommendations(...) {
// 获取目标用户已交互的动态
Set<Long> interactedItems = ratingMatrix.row(targetUserId).keySet();
// 收集相似用户的所有动态(带加权评分)
Map<Long, Double> candidateScores = new HashMap<>();
similarUsers.forEach(similarUser -> {
double similarity = userSimilarities.get(similarUser);
ratingMatrix.row(similarUser).forEach((postId, score) -> {
if (!interactedItems.contains(postId)) {
candidateScores.merge(postId, similarity * score, Double::sum);
}
});
});
// 按总分排序取TopN
return candidateScores.entrySet().stream()
.sorted(Map.Entry.<Long, Double>comparingByValue().reversed())
.limit(topN)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
```
六、关键实现说明
1. **权重计算策略**:
- 浏览权重计算通过SQL的`LEAST`和`FLOOR`函数实现
- UNION ALL合并多表数据时保证字段顺序一致
2. **相似度计算要点**:
- 使用Guava Table存储用户-物品评分矩阵
- 余弦相似度计算时仅考虑共同评分项
- 引入相似用户数量限制(SIMILAR_USERS_LIMIT)提升性能
3. **推荐生成逻辑**:
- 排除用户已交互的动态
- 相似用户评分按相似度加权求和
- 最终按加权总分降序排列
建议在实际部署时:
1. 添加分页查询限制数据量
2. 引入Hibernate Validator进行参数校验
3. 添加@Cacheable注解缓存计算结果
4. 使用CompletableFuture实现异步计算
阅读全文
相关推荐












