Commit c6722016 authored by 刘基明's avatar 刘基明

主题推荐算法重设计

parent bfc7e982
......@@ -11,7 +11,7 @@ public class ThemeListReq {
@NotNull(message = "主题类型不能为空")
@ApiModelProperty(value = "类型,1:推荐 2:关注 3:话题-热门 4:话题最新")
@ApiModelProperty(value = "类型,1:推荐 2:关注 3:话题-热门 4:话题-最新")
private Integer type;
@ApiModelProperty(value = "话题Id")
......
package com.tanpu.community.api.beans.resp;
import lombok.Data;
import java.util.List;
@Data
public class PythonResponse {
private List<String> attributes;
private String message;
private String statusCode;
private boolean success;
}
......@@ -10,6 +10,7 @@ import com.tanpu.community.api.beans.req.theme.*;
import com.tanpu.community.api.beans.resp.CreateThemeResp;
import com.tanpu.community.cache.RedisCache;
import com.tanpu.community.manager.ThemeManager;
import com.tanpu.community.service.RecommendService;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
......@@ -34,6 +35,9 @@ public class ThemeController {
@Autowired
private RedisCache redisCache;
@Autowired
private RecommendService recommendService;
@AuthLogin
@ApiOperation("发表主题")
@PostMapping(value = "/publish")
......@@ -147,7 +151,8 @@ public class ThemeController {
@ResponseBody
public CommonResp<Integer> test() {
String userId = userHolder.getUserId();
return CommonResp.success(themeManager.getFollowUpdateCount(userId));
System.out.println(recommendService.getRecommendThemes("",20,userId));
return CommonResp.success();
}
}
}
package com.tanpu.community.controller;
import com.tanpu.common.api.CommonResp;
import com.tanpu.common.auth.AuthLogin;
import com.tanpu.common.auth.UserHolder;
import com.tanpu.community.api.beans.qo.TopicRankQo;
import com.tanpu.community.api.beans.req.page.Page;
......@@ -25,6 +26,8 @@ public class TopicController {
private UserHolder userHolder;
@PostMapping(value = "/list")
@ApiOperation("APP全部话题页面,可搜索")
@ResponseBody
......@@ -32,6 +35,7 @@ public class TopicController {
return CommonResp.success(topicManager.getAllTopicBriefInfo(req));
}
@AuthLogin
@GetMapping(value = "/detailPage")
@ApiOperation("话题详情页顶部")
@ResponseBody
......
......@@ -80,6 +80,9 @@ public class ThemeManager {
@Autowired
private RedisCache redisCache;
@Autowired
private RecommendService recommendService;
public ThemeFullSearchResp themeFullSearch(String keyword, Integer pageNo, Integer pageSize, List<String> excludeIds, String userId) {
Integer from = (pageNo - 1) * pageSize;
ThemeFullSearchResp resp = new ThemeFullSearchResp();
......@@ -174,10 +177,15 @@ public class ThemeManager {
List<ThemeEntity> themeEntities = new ArrayList<>();
if (ThemeListTypeEnum.RECOMMEND.getCode().equals(req.getType())) {
//推荐
List<String> recommendThemeIds = rankService.getHotAndNewThemes(100, 100,userId);
List<String> recommendThemeIds = recommendService.getRecommendThemes(req.getLastId(), req.getPageSize(), userId);
themeEntities = themeService.queryByThemeIdsExcludeUser(recommendThemeIds, null, req.getLastId(), req.getPageSize());
themeEntities = RankUtils.sortThemeEntityByIds(themeEntities, recommendThemeIds);
} else if (ThemeListTypeEnum.FOLLOW.getCode().equals(req.getType())) {
//关注
if (StringUtils.isEmpty(req.getLastId())) {
visitSummaryService.addPageView(userId, userId, VisitTypeEnum.FOLLOW_THEME_VIEW);
}
//根据关注列表查询
List<String> fansList = followRelService.queryFansByFollowerId(userId);
themeEntities = themeService.queryByUserIds(fansList, req.getLastId(), req.getPageSize());
......@@ -187,12 +195,14 @@ public class ThemeManager {
if (StringUtils.isEmpty(req.getTopicId())) throw new BizException("TopicId为空");
List<String> rankThemeIds = rankService.getRankThemeListByTopic(req.getTopicId());
themeEntities = themeService.queryByThemeIdsExcludeUser(rankThemeIds, null, req.getLastId(), req.getPageSize());
themeEntities = RankUtils.sortThemeEntityByIds(themeEntities,rankThemeIds);
themeEntities = RankUtils.sortThemeEntityByIds(themeEntities, rankThemeIds);
} else if (ThemeListTypeEnum.TOPIC_LATEST.getCode().equals(req.getType())) {
//根据话题查询最新
if (StringUtils.isEmpty(req.getTopicId())) throw new BizException("TopicId为空");
themeEntities = themeService.queryByTopic(req.getTopicId(), req.getLastId(), req.getPageSize());
}
//组装详情
return convertEntityToQo(themeEntities, userId);
}
......@@ -204,7 +214,6 @@ public class ThemeManager {
}
// 返回用户发布、回复、点赞、收藏的主题列表
public List<ThemeQo> queryThemesByUser(QueryRecordThemeReq req, String userId) {
......@@ -233,6 +242,9 @@ public class ThemeManager {
//查询正文
public ThemeQo getDetail(String themeId, String userId) {
//进入详情
visitSummaryService.addPageView(userId, themeId, VisitTypeEnum.THEME_PAGE_VIEW);
ThemeEntity themeEntity = themeService.queryByThemeId(themeId);
if (themeEntity == null) {
throw new BizException("找不到帖子id:" + themeId);
......@@ -272,8 +284,6 @@ public class ThemeManager {
}
//关注用户是否有更新
public Integer getFollowUpdateCount(String userId) {
LocalDateTime lastViewTime = visitSummaryService.queryLatestViewFollow(userId);
......@@ -304,8 +314,6 @@ public class ThemeManager {
}
//主题Entity转QO
private List<ThemeQo> convertEntityToQo(List<ThemeEntity> themeEntities, String userId) {
//Entity转Qo
......@@ -374,7 +382,6 @@ public class ThemeManager {
}
/**
* 腾讯云-内容检测
*
......
package com.tanpu.community.manager;
import com.tanpu.common.auth.UserHolder;
import com.tanpu.common.exception.BizException;
import com.tanpu.community.api.beans.qo.TopicRankQo;
import com.tanpu.community.api.beans.req.page.Page;
......@@ -7,12 +8,14 @@ import com.tanpu.community.api.beans.req.topic.TopicSearchReq;
import com.tanpu.community.api.beans.vo.TopicDataAnalysDTO;
import com.tanpu.community.api.constants.RedisKeyConstant;
import com.tanpu.community.api.enums.CollectionTypeEnum;
import com.tanpu.community.api.enums.VisitTypeEnum;
import com.tanpu.community.dao.entity.community.TopicEntity;
import com.tanpu.community.service.*;
import com.tanpu.community.util.PageUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
......@@ -40,6 +43,9 @@ public class TopicManager {
@Autowired
private RankService rankService;
@Resource
private UserHolder userHolder;
//新增话题
public void insertTopic(String topicTitle, String userId) {
if (topicService.queryByTitile(topicTitle) == null) {
......@@ -62,6 +68,8 @@ public class TopicManager {
//话题详情页
public TopicRankQo getDetail(String topicId) {
//话题详情
visitSummaryService.addPageView(userHolder.getUserId(), topicId, VisitTypeEnum.TOPIC_PAGE_VIEW);
return rankService.getTopicDetail(topicId);
}
......
......@@ -27,9 +27,10 @@ public class RankService {
@Autowired
private VisitSummaryService visitSummaryService;
//最热
private List<ThemeAnalysDO> rankThemeList = new ArrayList<>();
private List<TopicRankQo> rankTopicList = new ArrayList<>();
private List<TopicRankQo> rankTopicListTop4 = new ArrayList<>();
......@@ -38,7 +39,8 @@ public class RankService {
* 计算主题热度排行
*/
public void rankThemes() {
List<ThemeEntity> themeEntities = themeService.queryRecent14days();
//7天内所有主题进行热度值排序
List<ThemeEntity> themeEntities = themeService.queryRecentdays(7);
List<ThemeAnalysDO> themeAnalysDOS = ConvertUtil.themeEntityToAnalysDOs(themeEntities);
for (ThemeAnalysDO theme : themeAnalysDOS) {
String themeId = theme.getThemeId();
......@@ -60,6 +62,7 @@ public class RankService {
}
/**
* 计算话题热度
*
......@@ -127,12 +130,7 @@ public class RankService {
}
public List<ThemeAnalysDO> getRankThemeListByTopic() {
if (this.rankThemeList.size()==0){
rankThemes();
}
return rankThemeList;
}
public List<TopicRankQo> getRankTopicList() {
if (this.rankTopicList.size()==0){
......@@ -148,6 +146,13 @@ public class RankService {
return rankTopicListTop4;
}
public List<ThemeAnalysDO> getRankThemeList() {
if (this.rankThemeList.size()==0){
rankThemes();
}
return rankThemeList;
}
public List<String> getRankThemeListByTopic(String topicId) {
if (this.rankThemeList.size()==0){
this.rankThemes();
......
package com.tanpu.community.service;
import com.tanpu.common.util.JsonUtil;
import com.tanpu.community.api.beans.qo.ThemeAnalysDO;
import com.tanpu.community.api.beans.resp.PythonResponse;
import com.tanpu.community.dao.entity.community.ThemeEntity;
import com.tanpu.community.util.ConvertUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
@Service
public class RecommendService {
@Value("${recommend.python.enable}")
public String enablePython;
@Value("${recommend.python.url}")
public String pythonUrl;
@Value("${recommend.ratio.hot}")
public Integer hotRatio;
@Value("${recommend.ratio.new}")
public Integer newRatio;
@Value("${recommend.ratio.python}")
public Integer pythonRatio;
@Autowired
private RankService rankService;
@Autowired
private ThemeService themeService;
//最新
private List<ThemeAnalysDO> recentThemeList = new ArrayList<>();
//推荐
private Map<String, List<String>> recommondList = new HashMap<>();
//
private Map<String, Set<String>> returnedIdsMap = new HashMap<>();
public List<String> getRecommendThemes(String lastId, Integer pageSize, String userId) {
//最热话题,剔除当前用户的主题
List<String> hotThemeIds = rankService.getRankThemeList().stream()
.filter(o -> !userId.equals(o.getAuthorId()))
.map(ThemeAnalysDO::getThemeId)
.collect(Collectors.toList());
//最新话题,剔除当前用户的主题
List<String> newThemeIds = this.getRecentThemeList().stream()
.filter(o -> !userId.equals(o.getAuthorId()))
.map(ThemeAnalysDO::getThemeId)
.collect(Collectors.toList());
//推荐话题
List<String> recThemeIds = getPythonRecommendList(userId);
//混合
Set<String> returnedIds = (StringUtils.isEmpty(lastId)) ? new HashSet<>() : returnedIdsMap.get(userId);
List<String> result = new ArrayList<>();
getResultList(hotThemeIds, 0, newThemeIds, 0, recThemeIds, 0, returnedIds, result, pageSize);
return result;
}
public List<ThemeAnalysDO> getRecentThemeList() {
if (recentThemeList.size() == 0) {
refreshNewestThemes();
}
return recentThemeList;
}
//从数据库查询最新主题
public void refreshNewestThemes() {
List<ThemeEntity> themeEntities = themeService.queryLatestThemes(100);
this.recentThemeList = ConvertUtil.themeEntityToAnalysDOs(themeEntities);
}
//查询python计算推荐列表
public List<String> getPythonRecommendList(String userId) {
if (recommondList.containsKey(userId)) {
return recommondList.get(userId);
} else {
return refreshPythonRecommendList(userId);
}
}
//HTTP查询python推荐主题
public List<String> refreshPythonRecommendList(String userId) {
if (!"true".equals(enablePython)) {
return Collections.emptyList();
}
RestTemplate restTemplate = new RestTemplate();
HashMap<String, String> param = new HashMap<>();
param.put("user_id", userId);
try {
String response = restTemplate.getForObject(pythonUrl, String.class, param);
PythonResponse pythonResponse = JsonUtil.toBean(response, PythonResponse.class);
recommondList.put(userId, pythonResponse.getAttributes());
return pythonResponse.getAttributes();
} catch (Exception e) {
log.error("调用python失败");
return Collections.emptyList();
}
}
//逐个插入
private void getResultList(List<String> hotThemeIds, Integer hotTag, List<String> newThemeIds, Integer newTag, List<String> recThemeIds, Integer recTag, Set<String> returnedIds, List<String> result, Integer pageSize) {
if (hotThemeIds.size() <= hotTag && newThemeIds.size() <= newTag && recThemeIds.size() <= recTag) {
//所有列表已循环结束,返回
return;
}
while (result.size() < pageSize * 1.5) {
int hotTimes = hotRatio;
int newTimes = newRatio;
int recTimes = pythonRatio;
String id;
while (hotTimes > 0 && hotThemeIds.size() > hotTag) {
id = hotThemeIds.get(hotTag);
if (!returnedIds.contains(id)) {
result.add(id);
returnedIds.add(id);
}
hotTag++;
hotTimes--;
}
while (newTimes > 0 && newThemeIds.size() > newTag) {
id = newThemeIds.get(newTag);
if (!returnedIds.contains(id)) {
result.add(id);
returnedIds.add(id);
}
newTag++;
newTimes--;
}
while (recTimes > 0 && recThemeIds.size() > recTag) {
id = recThemeIds.get(recTag);
if (!returnedIds.contains(id)) {
result.add(id);
returnedIds.add(id);
}
recTag++;
recTimes--;
}
}
//TODO 去重已看过(查看正文)
if (result.size() < pageSize) {
getResultList(hotThemeIds, hotTag, newThemeIds, newTag, recThemeIds, recTag, returnedIds, result, pageSize);
}
}
}
......@@ -50,11 +50,22 @@ public class ThemeService {
themeMapper.update(themeEntity, new LambdaUpdateWrapper<ThemeEntity>().eq(ThemeEntity::getThemeId, themeId));
}
//14天内所有主题
public List<ThemeEntity> queryRecent14days() {
//n天内所有主题
public List<ThemeEntity> queryRecentdays(Integer days) {
LambdaQueryWrapper<ThemeEntity> queryWrapper = new LambdaQueryWrapper<ThemeEntity>()
.eq(ThemeEntity::getDeleteTag, DeleteTagEnum.NOT_DELETED.getCode())
.gt(ThemeEntity::getCreateTime, TimeUtils.getDaysBefore(14));
.gt(ThemeEntity::getCreateTime, TimeUtils.getDaysBefore(days))
.orderByDesc(ThemeEntity::getId);
return themeMapper.selectList(queryWrapper);
}
//最新的n条主题
public List<ThemeEntity> queryLatestThemes(Integer n) {
LambdaQueryWrapper<ThemeEntity> queryWrapper = new LambdaQueryWrapper<ThemeEntity>()
.eq(ThemeEntity::getDeleteTag, DeleteTagEnum.NOT_DELETED.getCode())
.last("limit " + n)
.orderByDesc(ThemeEntity::getId);
return themeMapper.selectList(queryWrapper);
}
......@@ -73,6 +84,7 @@ public class ThemeService {
.orderByDesc(ThemeEntity::getId));
}
//根据用户id查询主题list
public List<ThemeEntity> queryThemesByUserId(String userId, String lastId, Integer pageSize) {
LambdaQueryWrapper<ThemeEntity> queryWrapper = new LambdaQueryWrapper<ThemeEntity>()
.eq(ThemeEntity::getAuthorId, userId)
......
package com.tanpu.community.util;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Calendar;
public class TimeUtils {
public static long getTimestampOfDateTime(LocalDateTime localDateTime) {
......@@ -46,13 +44,7 @@ public class TimeUtils {
//计算n天前的时间
public static LocalDateTime getDaysBefore(Integer number){
Calendar calendar = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
calendar.add(Calendar.DATE, number);
String three_days_after = sdf.format(calendar.getTime());
DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime ldt = LocalDateTime.parse(three_days_after,df);
return ldt;
return LocalDateTime.now().minusDays(number);
}
}
......@@ -103,3 +103,12 @@ tencent:
secretId: AKIDTjjV2IhK4ZKBm8z5g14vPedNSJuFnTIq
secretKey: PaVBZfeQwDVXKr7TZOzM6c9VZNwGJGyA
region: ap-shanghai
recommend:
ratio: #主题推荐比例(热门、最新、机器学习)
hot: 3
new: 2
python: 1
python:
enable: true
url: http://172.168.0.164:9000/api/get_recommend?user_id=2431614397151511
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment