做了一个博客网站,基本功能就是实现用户查找文章,查看文章内容。后面想到增加实现能够根据用户的喜好程度不同来推荐不同的文章,希望看完能对你有所帮助。
简述一下本网站实现推荐的过程是如何进行的。
首先定义好推荐数据的依据,对文章数据库中设置keyWord字段,用于存储文章的话题或者标签,每个文章存储的话题有多个由发布文章时由作者设定,以下为数据库存储文章标签的字段。
其次设计一个喜好的程度的方案,用户对文章在不同时期对文章做的不同操作表现出用户对该类型的文章喜好程序不一样,例如用户对文章的浏览、点赞、收藏、评论的行为加分权值为0.1、0.3、0.5、0.3,评论的加分权值可根据用户的内容来评判这里不作为详细处理统计评论就是加0.3分。由于用户存在喜新厌旧的情况,因此在收集用户的行为记录时需要按时间收集并且时间越长的行为权值越低,比如一个月以内的点赞权值为0.3*1,一个月后的点赞权值为0.3*0.6.
建立用户喜好分析模型UserAnalysisModel,该模型用于分析用户的某种(点赞评论等)行为的喜好偏好表,key为喜好标签名,value为权重值
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.yiqingxuan_article.content.bean.Article;//自定义实体类
import com.example.yiqingxuan_article.content.dao.ArticleMapper;//自定义DAO类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @Author: 曾敏
* @Description: 用户喜好分析模型
* @DateTime: 2023/5/26 13:36
**/
@Service
public class UserAnalysisModel {
//Mapper层,用于从数据库中提取文章内容
@Autowired
private ArticleMapper articleMapper;
//redis
@Autowired
RedisTemplate redisTemplate;
public Map<String,Double> WeightTable;
private List<Integer> listone=new ArrayList<>();//用户第一个月的行为记录列表
private List<Integer> listtow=new ArrayList<>();//用户第二个月的行为记录列表
private List<Integer> listtree=new ArrayList<>();//用户第三个月及其以后的行为记录列表
List<Integer> listsum;//三个时间节点的文章id总和
List<Article> listarticles;//存放用户的浏览记录文章
Map<Integer,List<String>> id_keymap;//存放文章号代表的关键字对照表
private Integer userid;
private double Weight;//当前分享的行为权值表
private double ONE_MONTH=1;
private double TOW_MONTH=0.6;
private double TREE_MONTH=0.4;
public Map<String,Double> execute(Integer uid,String type){
if (type.equals("点赞")){
Weight=0.5;
}else if (type.equals("收藏")){
Weight=0.3;
}else if (type.equals("浏览")){
Weight=0.1;
}else if (type.equals("评论")){
Weight=0.3;
}else {
return null;
}
userid=uid;
//开始业务分析
System.out.println("当前行为分析:"+type);
GetMonthLists();
GetArticleAndKeyTable();
createWeightTable();
Calculate();
return WeightTable;
}
/**
* 将三时期各种行为记录的set集合存入列表
*/
private void GetMonthLists(){
String key;
if (Weight==0.5){
key="user_like_";
}else if (Weight==0.4){
key="user_store_";
}else if (Weight==0.3){
key="user_comment_";
}else if (Weight==0.1){
key="user_read_";
}else {
return;
}
//一个月内
Set<String> membersone= (Set<String>) redisTemplate.opsForZSet().rangeByScore(key+userid, GetCurrentTimestamp(1), GetCurrentTimestamp(0)).stream().map(Object::toString).collect(Collectors.toSet());
//一月到二月间
Set<String> memberstwo= (Set<String>) redisTemplate.opsForZSet().rangeByScore(key+userid, GetCurrentTimestamp(2), GetCurrentTimestamp(1)).stream().map(Object::toString).collect(Collectors.toSet());
//二月后
Set<String> memberstree= (Set<String>) redisTemplate.opsForZSet().rangeByScore(key+userid, Double.MIN_VALUE, GetCurrentTimestamp(2)).stream().map(Object::toString).collect(Collectors.toSet());
System.out.println("第一时期的"+Weight+"记录:"+membersone);
if (membersone.size()>0||membersone!=null){
for (String s:membersone){
if (Weight==0.1||Weight==0.3){
String [] parms=s.split("_");
String result=parms[0];
listone.add(Integer.valueOf(result));
}else {
listone.add(Integer.valueOf(s));
}
}
while (listone.size() > 500) {//将数据采集量限制大小
Random random = new Random();
int indexToRemove = random.nextInt(listone.size());
listone.remove(indexToRemove);
}
}//将set集合存入列表中
if (membersone.size()>0||membersone!=null){
for (String s:memberstwo){
if (Weight==0.1||Weight==0.3){
String [] parms=s.split("_");
String result=parms[0];
listone.add(Integer.valueOf(result));
}else {
listone.add(Integer.valueOf(s));
}
}
while (listtow.size() > 400) {
Random random = new Random();
int indexToRemove = random.nextInt(listtow.size());
listtow.remove(indexToRemove);
}
}
if (membersone.size()>0||membersone!=null){
for (String s:memberstree){
if (Weight==0.1||Weight==0.3){
String [] parms=s.split("_");
String result=parms[0];
listone.add(Integer.valueOf(result));
}else {
listone.add(Integer.valueOf(s));
}
}
while (listtree.size() > 300) {
Random random = new Random();
int indexToRemove = random.nextInt(listtree.size());
listtree.remove(indexToRemove);
}
}
// 合并列表,只合并非空列表
System.out.println("三个时期的表记录值:"+listone+"==="+listtow+"==="+listtree);
listsum = Stream.of(listone, listtow, listtree)
.filter(list -> !list.isEmpty())
.flatMap(list -> list.stream())
.collect(Collectors.toList());
}
/**
* 得到文章号与其关键字的对照表
*/
private void GetArticleAndKeyTable(){
System.out.println(listsum);
QueryWrapper<Article> wrapper = new QueryWrapper<>();
wrapper.select("id", "keyWords").in("id", listsum);
listarticles=articleMapper.selectList(wrapper);//获取结果集合
System.out.println("用户看过的文章记录表:"+listarticles);
Map<Integer,List<String>> map1=new HashMap<>();
for (Article article:listarticles){
String keyWord=article.getKeywords();
String [] keyWords=keyWord.split("#");
int n=keyWords.length;
String first = keyWords[0];
for (int i=1;i<n;i++) {
keyWords[i-1]=keyWords[i];
}
keyWords[n-1]=first;
// 移除最后一个元素
String[] newArray=new String[n-1];
for (int i=0;i<n-1;i++) {
newArray[i]=keyWords[i];
}
keyWords = newArray; // 更新原数组
List<String> list = Arrays.asList(keyWords);
map1.put(article.getId(),list);
}//得到文章号与其关键字的对照表
System.out.println(map1.size());
id_keymap=map1;
}
/**
* 初始化用户喜好权值表,获取用户浏览过的标签生成map
*/
private void createWeightTable(){
List<String> listkeysum=new ArrayList<>();//当前用户的所有喜好集合
Set<Integer> set=id_keymap.keySet();
for (Integer element : set) {
List<String> list=id_keymap.get(element);//得到当前文章号的标签集合
Set<String> tempset=new HashSet<String>();
tempset.addAll(listkeysum);
tempset.addAll(list);
listkeysum=new ArrayList<>(tempset);
}
Map<String,Double> map1=new HashMap<>();
for (String s:listkeysum){
map1.put(s,0.0);
}
WeightTable=map1;
}
/**
* 为用户权值表计算
*/
private void Calculate(){
if (listone.size()>0){
System.out.println("用户浏览过的文章关键字合集:"+id_keymap);
for (Integer i:listone){//一个月内的计算
List<String> listKeys=id_keymap.get(i);//得到一个关键字集合
for (String s:listKeys){//
Double initial=WeightTable.get(s);
Double newvalue=initial+(Weight*ONE_MONTH);
WeightTable.put(s,newvalue);
}
}
}
if (listtow.size()>0){
for (Integer i:listtow){//一个月内的计算
List<String> listKeys=id_keymap.get(i);//得到一个关键字集合
for (String s:listKeys){//
Double initial=WeightTable.get(s);
Double newvalue=initial+(Weight*TOW_MONTH);
WeightTable.put(s,newvalue);
}
}
}
if (listtree.size()>0){
for (Integer i:listtree){//一个月内的计算
List<String> listKeys=id_keymap.get(i);//得到一个关键字集合
for (String s:listKeys){//
Double initial=WeightTable.get(s);
Double newvalue=initial+(Weight*TREE_MONTH);
WeightTable.put(s,newvalue);
}
}
}
System.out.println("权值表:"+WeightTable);
}
/**
* 输入获取的月份返回时间戳
* @param i
* @return
*/
public static long GetCurrentTimestamp(int i){
// 获取当前时间戳
long currentTimestamp = System.currentTimeMillis();
// 获取一个月前的时间戳
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date(currentTimestamp));
calendar.add(Calendar.MONTH, -1); // 减去一个月
long oneMonthAgoTimestamp = calendar.getTimeInMillis();
// 获取两个月前的时间戳
calendar.add(Calendar.MONTH, -1); // 再减去一个月
long twoMonthsAgoTimestamp = calendar.getTimeInMillis();
if (i==0){
return currentTimestamp;
}else if (i==1){
return oneMonthAgoTimestamp;
}else if (i==2){
return twoMonthsAgoTimestamp;
}else {
return 0;
}
}
}
以上代码用于获取用户某个行为喜好权重表,具体推荐由以下代码进行
package com.example.yiqingxuan_article.content.service;
import com.example.yiqingxuan_article.content.bean.Article;
import com.example.yiqingxuan_article.content.dao.ArticleMapper;
import com.example.yiqingxuan_article.content.dao.CategoryMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
import static jdk.nashorn.internal.objects.Global.Infinity;
/**
* @Author: 曾敏
* @Description: TODO
* @DateTime: 2023/5/26 1:21
**/
@Service
public class Recommend {
@Autowired
private ArticleMapper articleMapper;
@Autowired
RedisTemplate redisTemplate;
@Autowired
private CategoryMapper categoryMapper;
@Autowired
private UserAnalysisModel UserLike1;
@Autowired
private UserAnalysisModel UserLike2;
@Autowired
private UserAnalysisModel UserLike3;
@Autowired
private UserAnalysisModel UserLike4;
/**
* 获取用户喜好列表内容
* @param userid
* @return
*/
public List<String> GetUserLike(Integer userid){
Map<String,Double> readmap=UserLike1.execute(userid,"浏览");
Map<String,Double> givemap=UserLike2.execute(userid,"点赞");
Map<String,Double> collecttmap=UserLike3.execute(userid,"收藏");
Map<String,Double> commentmap=UserLike4.execute(userid,"评论");
List<Map<String, Double>> maps=new ArrayList<>();
maps.add(readmap);
maps.add(givemap);
maps.add(collecttmap);
maps.add(commentmap);
Map<String, Double> mergeMaps=mergeMaps(maps);
List<Map.Entry<String, Double>> entries = new ArrayList<>(mergeMaps.entrySet());
// 按Map中的值排序
Collections.sort(entries, new Comparator<Map.Entry<String, Double>>() {
public int compare(Map.Entry<String, Double> o1, Map.Entry<String, Double> o2) {
return o2.getValue().compareTo(o1.getValue());
}
});
List<String> listKeyWords=new ArrayList<>();
// 打印四个最大值的键
for (int i = 0; i < entries.size(); i++) {
if (i==4){
break;
}
listKeyWords.add(entries.get(i).getKey());
}
return listKeyWords;
}
/**
* 根据用户偏好标签查询文章,并通过协同过滤返回推荐文章的id号
* @param list
* @param userid
* @return
*/
public List<Integer> itemFilter(List<String> list,Integer userid){
//根据list中的关键字数据查询到文章集合
System.out.println("根据list中的关键字数据查询到文章集合:"+articleMapper.selectByKeywords(list));
List<Article> articleList=articleMapper.selectByKeywords(list);
while (articleList.size() > 100) {//将数据采集量限制大小
Random random = new Random();
int indexToRemove = random.nextInt(articleList.size());
articleList.remove(indexToRemove);
}
Set<String> memberstree= (Set<String>) redisTemplate.opsForZSet().rangeByScore("user_read_"+userid, Double.MIN_VALUE, System.currentTimeMillis()).stream().map(Object::toString).collect(Collectors.toSet());
List<String> readlists=new ArrayList<>();//用户抽取的部分浏览记录,含文章号浏览的id
if (memberstree.size()>0||memberstree!=null){
List<String> templist = new ArrayList<>(memberstree);
templist=parmeruni(templist);
System.out.println(templist);
while (templist.size() > 100) {//将数据采集量限制大小
Random random = new Random();
int indexToRemove = random.nextInt(templist.size());
templist.remove(indexToRemove);
}
readlists=templist;
}//将set集合存入列表中
Double [][] matrix = new Double[readlists.size()][articleList.size()];
for (int i=0;i<readlists.size();i++){
for (int j=0;j<articleList.size();j++){
matrix[i][j]=0.0;
}
}
int i=0,j=0;
for (String s:readlists){
i=0;
//小表中的某个文章
Set<String> s2= (Set<String>) redisTemplate.opsForZSet().rangeByScore("article_read_"+s, Double.MIN_VALUE,System.currentTimeMillis()).stream().map(Object::toString).collect(Collectors.toSet());
List<String> l2=new ArrayList<>(s2);
l2=parmeruni(l2);//得到浏览过该文章的用户id列表,用户浏览记录
for (Article article:articleList){
//大表中的某个文章
Set<String> s1= (Set<String>) redisTemplate.opsForZSet().rangeByScore("article_read_"+article.getId(), Double.MIN_VALUE,System.currentTimeMillis()).stream().map(Object::toString).collect(Collectors.toSet());//
List<String> l1=new ArrayList<>(s1);
l1=parmeruni(l1);//得到浏览过该文章的用户id列表,大表记录
// 计算交集大小
Set<String> intersection = new HashSet<>(l1);
intersection.retainAll(l2);
Double intersectionSize = Double.valueOf(intersection.size());
// 计算并集大小
Set<String> union = new HashSet<>(l1);
union.addAll(l2);
Double unionSize = Double.valueOf(union.size());
Double weight;
if (unionSize==0){
weight=0.0;
}else {
weight=unionSize/intersectionSize;
}
if (weight==Infinity){
weight=0.01;
}
matrix[j][i]=weight;
i++;
}
j++;
}
//显示矩阵
for (int q = 0; q < matrix.length; q++) {
for (int w = 0; w < matrix[q].length; w++) {
System.out.print(matrix[q][w] + " ");
}
System.out.println(); // 换行
}
//矩阵计算前十的坐标
List<Integer> coords=findTopTenCoordinates(matrix);
List<Integer> listid=new ArrayList<>();
for (Integer integer:coords){
Integer artid=articleList.get(integer).getId();
listid.add(artid);
}
//list1列表中存储着推荐的文章号id
System.out.println("推荐的文章号id集合:"+listid);
return listid;
}
/**
* 传入用户id返回推荐文章列表
* @param userid
* @return
*/
public List<Article> SelectRecommend(Integer userid){
List<String> list=GetUserLike(userid);
if (list==null||list.size()==0){
return null;
}
List<Integer> ArticleidList=itemFilter(list,userid);
List<Article> articleList=articleMapper.selectByArticleIds(ArticleidList);
for (Article article:articleList){
Integer commenttotal=getArticleComments(article.getId());
if (commenttotal==null){
article.setCommentTotal(0);
}else {
article.setCommentTotal(commenttotal);
}
article.setContent(null);
article.setName(categoryMapper.selectById(article.getCategoryid()).getName());
article.setReadcnt((int) GetLookArticle(article.getId()));
article.setAgreecnt((int) GetGiveArticle(article.getId()));
}
return articleList;//返回推荐结果
}
/**
* 将含有后缀的列表规整,并元素唯一
* @param list
* @return
*/
public List<String> parmeruni(List<String> list ){
int i=0;
for (String s:list){
String [] parms=s.split("_");
String result=parms[0];
list.set(i,result);
i++;
}
Set<String> set=new HashSet<>(list);
List<String> temp = new ArrayList<>(set);
return temp;
}
public static Map<String, Double> mergeMaps(List<Map<String, Double>> maps) {
Map<String, Double> result = new HashMap<>();
for (Map<String, Double> map : maps) {
for (Map.Entry<String, Double> entry : map.entrySet()) {
String key = entry.getKey();
Double value = entry.getValue();
result.merge(key, value, Double::sum);
}
}
return result;
}
/**
* 传入一个二维矩阵,获得纵轴坐标集
* @param matrix
* @return
*/
public static List<Integer> findTopTenCoordinates(Double[][] matrix) {
List<Integer> coordinates = new ArrayList<>();
TreeSet<Double> uniqueValues = new TreeSet<>(Collections.reverseOrder());
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
uniqueValues.add(matrix[i][j]);
}
}
int count = 0;
for (double value : uniqueValues) {
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
if (matrix[i][j] == value) {
if (!coordinates.contains(j)) {
coordinates.add(j);
coordinates.add(i);
count++;
}
}
if (count == 10) {
Set<Integer> set=new HashSet<>(coordinates);
List<Integer> temp = new ArrayList<>(set);
while (temp.size() > 10) {//将数据采集量限制大小
Random random = new Random();
int indexToRemove = random.nextInt(temp.size());
temp.remove(indexToRemove);
}
return temp;
}
}
}
}
Set<Integer> set=new HashSet<>(coordinates);
List<Integer> temp = new ArrayList<>(set);
while (temp.size() > 10) {//将数据采集量限制大小
Random random = new Random();
int indexToRemove = random.nextInt(temp.size());
temp.remove(indexToRemove);
}
return temp;
}
/**
* 获取某个文章的评论量
*/
public Integer getArticleComments(Integer id){
return articleMapper.selectRanking(id);
}
public long GetLookArticle(Integer artid) {
Long zcard= Long.valueOf(redisTemplate.opsForSet().members("article_show_"+artid).size());
return zcard;
}
/**
* 获取到文章的点赞量
* @param artid
* @return
*/
public long GetGiveArticle(Integer artid) {
Long zcard= Long.valueOf(redisTemplate.opsForSet().members("article_give_"+artid).size());
return zcard;
}
}
在控制层使用样式
@RestController
@RequestMapping("/selectarticle")
public class ArticleSelectControll {
@Autowired
private Recommend recommend;
/**
* 传入用户id,返回推荐文章列表
* @param userid
* @return
*/
@GetMapping("/recommend")
public jsonModel Recommend(@RequestParam Integer userid){
jsonModel jsonModel=new jsonModel();
System.out.println("推荐查询:"+userid);
List<Article> list=recommend.SelectRecommend(userid);
System.out.println(list);
if (list.size()>0&&list!=null){
jsonModel.setData(list);
jsonModel.setCode(1);
}else {
jsonModel.setMsg("推荐失败");
jsonModel.setCode(0);
}
return jsonModel;
}
}
使用推荐算法时,后台算法细节