文字版头条项目

功能概览:

首页展示, 登录注册功能,页面访问(拦截器,匿名用户,网址分析)问题功能,评论, feed流, 关注, 点赞功能, 站内信功能

首页展示:

1 首页就是显示问题 那就是跟从数据库选出是个最新的问题; 简单的sql语句 根据用户在不在取出是不是该用户的问题 用户在就是该用户的10个问题 用户不在就是所有问题的前十个

2 显示后你可以点进去查看问题,这个涉及到问题模块

3 点击个人头像还可以显示个人基本信息 比如我的评论 关注 回答 点赞都能 这是调用不同的服务最后封装出来

 

注册

接收前端传来的 用户名和密码

对二者做判断 都不能为空,再判断用户名是否存在。

接着创建一个userDao数据来存入相关数据,调用addUser

数据里面密码的盐值是由uuid随机生成然后去前五位组成新密码

然后新密码经过MD5加密得到

并返回一个ticket,ticket也是用uuid生成的 只不过把省的uuid中的-替换成“”; ticket会加到cookie cookie加到map返回给用户

如果失败了就在map里面加信息给controller,成功的话map里面是ticket

 

uuid:UUID 是 通用唯一识别码(Universally Unique Identifier)的缩写,由一组32位数的16进制数字所构成。

UUID由以下几部分的组合:

(1)当前日期和时间,UUID的第一个部分与时间有关,如果你在生成一个UUID之后,过几秒又生成一个UUID,则第一个部分不同,其余相同。

(2)时钟序列。

(3)全局唯一的IEEE机器识别号,如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得。

 

MD5

什么是MD5算法

MD5讯息摘要演算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码杂凑函数,可以产生出一个128位元(16位元组)的散列值(hash value),用于确保信息传输完整一致。

MD5功能

输入任意长度的信息,经过处理,输出为128位的信息(数字指纹); 
不同的输入得到的不同的结果(唯一性);

MD5属不属于加密算法

认为不属于的人是因为他们觉得不能从密文(散列值)反过来得到原文,即没有解密算法,所以这部分人认为MD5只能属于算法,不能称为加密算法; 
认为属于的人是因为他们觉得经过MD5处理后看不到原文,即已经将原文加密,所以认为MD5属于加密算法;我个人支持前者,正如认为BASE64算法只能算编码一样。

MD5算法过程

对MD5算法简要的叙述可以为:MD5以512位分组来处理输入的信息,且每一分组又被划分为16个32位子分组,经过了一系列的处理后,算法的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位散列值。

 

登录 登出功能

登录:

controller

根据service返回map 判断有没有ticket 有的话

根据返回的map,判断里面有没有ticket,有的话新建一个cookie写到cookie里面,再把cookie写到response里面给用户

返回到之前访问的页面 我们直接访问的relogin 他记录next的之后才会跳到login

service

拿到用户名和密码,用用户名去在数据库查找,userDao.selectUserById,找到是否存在这个用户

存在的话 取出salt salt+上前段的面 然后MD5加密进行比较 看看密码是否正确

不存在的话就map返回错误

正取的话创建新的ticket 加到map返回

登出

controller

service

把token的状态设置为0

登录 注册都包含重定向 redirect+next next是写在model里面的参数

 

页面访问

拦截器

两个拦截器:1.判断带没带tooken进而获取用户

                      2. 根据第一个拦截器有没有用户登录 登录就没啥了 没登录就强制要求登录

拦截器基本实现

1.注册 2.完成三个方法的调用

我们先注册这两个拦截器 配置文件继承了WebMvcConfigurerAdapter 重写addInterceptors方法 完成配置

注意两个拦截器的顺序

@Component
public class WendaWebConfiguration extends WebMvcConfigurerAdapter {
    @Autowired
    PassportInterceptor passportInterceptor; //把我们的拦截器定义一下

    @Autowired
    LoginRequredInterceptor loginRequredInterceptor;


    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(passportInterceptor); //把拦截器真正注册到整个链路上
        registry.addInterceptor(loginRequredInterceptor).addPathPatterns("/user/*"); //这个拦截器要加载第一个拦截器之后
        // 因为这个拦截器用到了hostholder变量,得先有第一个拦截器得到hostholder的内容,当访问user页面的时候 需要使用这个拦截器
        super.addInterceptors(registry);
    }
}

拦截器实现

以第一个方法拦截器获取user为例

总结下来就是: 1.prehandler :他是出于所有http请求的最前面prehandle 该方法最后得到一个用户User user = userDAO.selectById(loginTicket.getUserId());
/把用户放到hostHolder.setUsers(user)里面 这样保证后面的请求能用到这个user,hostHolder里面包含了多种方法,比如clear就是在使用完user以后清除掉
2.psothandler:  所谓模板model就是你返回给前端的东西 比如templates里面的html对应于controller类里面的参数model
 你在渲染之前modelAndView.addObject("user", hostHolder.getUser());这句话会把user加入到templates里面所有的模板中(那些html文件 理解为替你写了前端的代码
3.afterCompletion: 请求结束之后的收尾操作 这里我们实现了吧hostholder清除掉

第一个拦截器实现的功能:

prehandler:根据request请求除去里面所有的cookies 从每个cookie里面找有没有ticket这一项(我们无论注册还是登陆时候都把这个加到response里面返回个客户端了),有的话就可以取出来了,然后拿着ticket这个取数据库里面,把tikcet实体取出来,取出来之后实体里面报错了useid 再去取出user实体交给hostholder;

host... afete

hostholder是由 内部是一个threadLocal 泛型 user的实现  可以理解成一个hashmap 每个user是一个key 但是get也会得到user

package com.nowcoder.interceptor;

import com.nowcoder.dao.LoginTicketDAO;
import com.nowcoder.dao.UserDAO;
import com.nowcoder.model.HostHolder;
import com.nowcoder.model.LoginTicket;
import com.nowcoder.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;


//关于这个拦截器的综述:
// 1.prehandler :他是出于所有http请求的最前面prehandle 该方法最后得到一个用户User user = userDAO.selectById(loginTicket.getUserId());
//把用户放到hostHolder.setUsers(user)里面 这样保证后面的请求能用到这个user,hostHolder里面包含了多种方法,比如clear就是在使用完user以后清除掉
//2.psothandler:  所谓模板model就是你返回给前端的东西 比如templates里面的html对应于controller类里面的参数model
// 你在渲染之前modelAndView.addObject("user", hostHolder.getUser());这句话会把user加入到templates里面所有的模板中(那些html文件
//理解为替你写了前端的代码
@Component
public class PassportInterceptor implements HandlerInterceptor {
    @Autowired
    private LoginTicketDAO loginTicketDAO;

    @Autowired
    private UserDAO userDAO;//你拦截器引用这个之后 拦截器之后的所有请求都可以访问这个变量

    @Autowired
    private HostHolder hostHolder;

    @Override //请求开始前调用的函数,返回false的话到此为止
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        String ticket = null;
        if(httpServletRequest.getCookies() != null){  //从request中获取到ticket
            for(Cookie cookie : httpServletRequest.getCookies()){
                if(cookie.getName().equals("ticket")){
                    ticket = cookie.getValue();
                    break;
                }
            }
        }

        if(ticket != null){ //说明经过上面的if找到了ticket,在检查是否过期或者状态不对
            LoginTicket loginTicket  = loginTicketDAO.selectByTicket(ticket);
            if (loginTicket == null || loginTicket.getExpired().before(new Date()) || loginTicket.getStatus() != 0){
                return true;
            }
            User user = userDAO.selectById(loginTicket.getUserId());
            hostHolder.setUser(user);
        }
        return true;
    }


    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
        if(modelAndView != null && hostHolder.getUser() != null){
            modelAndView.addObject("user", hostHolder.getUser());//user放入到渲染的上下文

        }
    }

    @Override //渲染结束后才进行
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
        hostHolder.clear();//用不到这个hostholder就清除
    }
}

第二个拦截器:

只进行prehandle功能: 如果hosthodler里面没有用于 那我就强转到登录页面

这里利用了response的重定向功能  直接跳转的对应的requestURI页面

@Component
public class LoginRequredInterceptor implements HandlerInterceptor {

    @Autowired
    private HostHolder hostHolder;

    @Override //打开页面判断用户有没有 这个页面强制要求用户登录的,没登录就跳转到登录页面
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        if(hostHolder.getUser() == null){
            //sendRedirect是直接请求重定向的操作 直接跳转的对应的requestURI页面
            httpServletResponse.sendRedirect("/reglogin?next=" +httpServletRequest.getRequestURI());
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override //渲染结束后才进行
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

    }
}

如果cookie失效了:

问题功能

1 完成问题的添加:

用户提供了title和content 我们只需要完成数据库的添加操作即可。但是 title和content是要进行敏感词过滤的

2. 问题详情

我们根据问题的id能得到问题的实体,实体里面还包括userid, user可以拿出这个问题的主人,取出主人信息

通过评论的功能 查找对于此问题的评论 因为评论的数据库实体包含了我评论的实体和id 一直问题id 去查出来封装完成

敏感词过滤实现

需要双指针加一个树结构节点 子节点用hashmap存储 hashmap的key是子节点的字符,value是子节点

树结构:

private class TrieNode {

        /**
         * true 关键词的终结 ; false 继续, 表示是否是关键词的结尾
         */
        private boolean end = false;

        /**
         * key下一个字符,value是对应的节点
         */
        private Map<Character, TrieNode> subNodes = new HashMap<>();

        /**
         * 向指定位置添加节点树
         */
        public void addSubNode(Character key, TrieNode node) {
            subNodes.put(key, node);
        }

        /**
         * 获取下个节点
         */
        TrieNode getSubNode(Character key) {

            return subNodes.get(key);
        }

        boolean isKeywordEnd() {

            return end;
        }

        void setKeywordEnd(boolean end) {

            this.end = end;
        }

        public int getSubNodeCount() {

            return subNodes.size();
        }


    }

过滤方法:

1 准备指针 begin 元指针和position偏移指针 while(position < length) char c = s.charat(position)

2 如果空格 我们就直接begin++; ___abcdef色情      

3 如果是字符的话 我们就让(前缀树的遍历)子节点往下遍历: 两个截至条件 为空或者end为true

不断获取子节点position此时也++ ,节点根据position位置的值去取子节点node.getSubNode(c) 

子节点的end如果是true 就替换掉这个从begin到position,之后begin变成positon position=begin

如果子节点为空代表不是,那么添加begin处的字符 并且begin = begin+1 position=begin

        TrieNode tempNode = rootNode;
        int begin = 0; // 回滚数
        int position = 0; // 当前比较的位置

        while (position < text.length()) {
            char c = text.charAt(position);

            tempNode = tempNode.getSubNode(c);
            // 空格直接跳过    空格位置不是重点我们就不看了

          //当前位置的匹配结束
            if (tempNode == null) { //树节点刚开始的时候,也就是找到不到敏感词第一个头字符或者最后敏感词不成立
                // 以begin开始的字符串不存在敏感词
                result.append(text.charAt(begin));
                // 跳到下一个字符开始测试
                position = begin + 1;
                begin = position;
                // 回到树初始节点
                tempNode = rootNode;
            } else if (tempNode.isKeywordEnd()) {
                // 发现敏感词, 从begin到position的位置用replacement替换掉
                result.append(replacement);
                position = position + 1;
                begin = position;
                tempNode = rootNode;
            } else {
                position++;
            }
        }

        result.append(text.substring(begin));

        return result.toString();
    }

 

 

//增加关键词
private void addWord(String lineTxt) { //以abc为例
    TrieNode tempNode = rootNode;
    // 循环每个字节
    for (int i = 0; i < lineTxt.length(); i++) {
        Character c = lineTxt.charAt(i);
        // 过滤空格
        if (isSymbol(c)) {
            continue;
        }
        TrieNode node = tempNode.getSubNode(c);//看有没有子节点

        if (node == null) { // 没初始化,还没有根节点 把a设为根节点
            node = new TrieNode();
            tempNode.addSubNode(c, node);
        }

        tempNode = node;

        if (i == lineTxt.length() - 1) {
            // 关键词结束, 设置结束标志
            tempNode.setKeywordEnd(true);
        }
    }
}

评论功能

controller层的功能:

增加一个评论:我们这里只实现了给问题增加评论

首先给问题增加评论需要问题的编号 这个在你页面进入相关问题的时候url中就存在了,然后我们需要评论的内容,controller层封装一个comment的实体,里面对了数据库的实体名称,我们把userid(hosthodler获取),内容,问题的id 评论的类型也就是问题的类型,时间 ,选出某个id的评论,service拿到这个实体对内容进行敏感词过滤,url敏感词过滤 完成之后给dao层添加到数据库,添加成功的话返回1 根据判断是否大于0 来返回评论的id或者0;之后还得显示这个问题需要的评论有多少,不是返回了评论的id嘛,那这个id和questiontype去数据库找评论的个数,更新到问题的表之中

之后发送一个问题被评论的事件,事件触发者是用户,类型是评论,时间

 

站内信功能

第一个功能 实现页面访问

SELECT * FROM message WHERE from_id = 12 OR to_id = 12
GROUP BY conversation_id ORDER BY created_date DESC;  就可以 

判断用户登录 然后去fromid或者toid等于用户id  再从这些数据中 按orderby conversation_id 来分类 这样一类只会出现一个,按时间排序 再选出limit 0, 10 搞定 

一个orderby就是上面的一个类 最后只显示一个 

取出来的是一个list 每个list存一个message 存后

我们把用户的id 根据list取出来 如果是本用户发的 那么就fromid 其他用户发的就 找toid 再去找用户信息

之后 我们添加一个viewobject里面 一个message 对应一个object 搞定

未读信息conversationid, toid为当前用户看unread = 0;查

@Select({"select count(id) from ", TABLE_NAME, " where has_read=0 and to_id=#{userId} and conversation_id=#{conversationId}"})
int getConversationUnreadCount(@Param("userId") int userId, @Param("conversationId") String conversationId);

第二个功能 完成私信详细功能 就是点进去会看多少个私信

@Select({"select ", SELECT_FIELDS, " from ", TABLE_NAME,
        " where conversation_id=#{conversationId} order by created_date desc limit #{offset}, #{limit}"})
List<Message> getConversationDetail(@Param("conversationId") String conversationId,
                                    @Param("offset") int offset,
                                    @Param("limit") int limit);

把需要的都显示出来了再更新已读

@Update({"update message set has_read = 1 where conversation_id = #{conversationId}"})
void updateHasRead(@Param("conversationId") String conversationId);

第三个就是添加私信

String TABLE_NAME = " message ";
String INSERT_FIELDS = " from_id, to_id, content, has_read, conversation_id, created_date ";
String SELECT_FIELDS = " id, " + INSERT_FIELDS;

@Insert({"insert into ", TABLE_NAME, "(", INSERT_FIELDS,
        ") values (#{fromId},#{toId},#{content},#{hasRead},#{conversationId},#{createdDate})"})
int addMessage(Message message);

Redis实现赞踩功能

redis有两种数据库存储方式 RDB 和 AOF

RDB是某一个时刻数据库当前的状态我保存下来 我只是保存那一时刻的值,保存最终结果

AOF 我不记录最终结果 我记录你所有的操作 那么我保存结果就直接进行所有操作得到结果

 

没啥可说的 关键是redis的数据结构

还有就是设置键值

我们用的集合操作 喜欢就给他加上这个用户 sadd 把不喜欢去掉 srem

不喜欢也加上 顺便把喜欢的去掉

至于键值 like / dilike加 实体类型(评论) + id

异步队列修改一下功能:

点赞 添加问题 关注 feed流

我们可以用阻塞队列来实现队列 这里不用 

用redis实现队列

事件实体就分为 事件类型 触发者 触发的载体 触发的id 载体的拥有者(就是我们要发信息给的那个人)

eventProducer:

首先就是把事件这个对象序列化为json串 就是字符串加到redis list中 键值是我们设计好的

EventConsumer

afterPropertiesSet()这个函数
1 这其实就是一个初始化的函数 名字就是在配置以后,对应这个初始化接口的实现我们建立一个map--beans存储handler 先从得到容器里面所有的handler(我们所有实现的handler)

每个Handel都通过 函数 getSupportEventTypes() 可以得到他处理的事件类型 那我们的目的就是处理器有一个configure 是map类型的,他存储事件类型和对应的handler。我们之前只知道handler和其对应的事件类型 但是一个事件类型和有几个handler我们不知道,configure就是这个作,已经实现了handler.class类从容器中取出来,接下来for循环每个handelr,调用其getSupportEventTypes()方法,然后遍历其中的事件类型 每个事件类型都加到configure的map集合里面 configure<EventType, ArrayList<Handler>>, 因此 如果没有这个事件 就给这个configure.put(EventType, new ArrayList> 有的话就给得到对应arraylist然后add这个handler即可

context得到handler, dandler取出对应的type 然后放到Map<type, handler> 里面

2 只开启一个线程 线程不断循环判断有没有事件进来 通过事件键值去在list中的事件字符串,由于brop弹出一个list和队列第一个值,接着我们根据事件反序列化得到EventMOdel,根据eventmodel的类型在config里面找需要的处理器 然后处理器一个个处理 搞定  dohandle

取出事件 事件得到EVENTMODEL , 然后model交给对应handler处理

点赞功能

点赞也是与评论功能一样 我们把点赞功能封装成一个实体 这个实体可以针对任何对象进行点赞 这里我们只对问题进行点赞

我们选取set集合作为存储标准 

根据上述dohandler中 发站内信就是 根据事件model创建一个message 只不过发件人是系统

在点在的实现曾会eventproducer,设置好这个eventModel

再捋一遍 controller层点赞后触发producer produrcer创建一个点赞类型的事件(EventModel), 事件被放到redis 的 键为 事件队列 值为list的集合中

这个集合存着无数事件 然后consumer拿到这个集合 遍历结合里面的事件,把事件取出来反序列化得到model的信息,根据这些信息来创建message法给被点赞的人

这个点赞功能 在执行到eventproducer之后就不管这个eventproducer执行完了木有,直接往下走 要是不用异步的话我们就自己写一个发站内信给谁的功能 这个就是同步 得等这个结束之后再往下进行 异步的话这个eventproducer执行之后就不管了 整个controller自动往下走,就结束这个点赞功能了。此时由于初始化的设置consumer已经运行了再不断的while循环等待producer产生消费品 producer递交之后就开始进行handler处理。 记住这个异步的重要功能 异步也适用于解耦 把相关的操作区分开

所以这种耗时的操作可以交给异步处理

关注功能 

关注和被关注的key都是在redis里面

一 关注服务

为了完成这个功能:

从最底层的数据库入手 这里选择 redis的 zset或者list

我有我的粉丝列表list,但是粉丝要按关注时间排序,list就不好用了 但是zset里面有个score参数,设置为创建时间即可,分析之后使用zset

总结一下redis 

以关注功能为例:

1 实现InittializingBean接口

redis要实现一个接口

public Jedis getJedis() {
    return pool.getResource();
}

每次redis资源池返回一个资源

2 新开始一个事物

内部有一个transaction变量 会给他新建对象然后返回jedis

 

3 利用zadd有序集合

tx看作是一个redis的资源即可

zadd 添加键值 时间作为分数 userid的string形式作为存储元素

把我这个用户加入到我关注的东西的粉丝列表里面

把这个东西加入到我的关注列表里面

4 执行事务判断是否完成

 

取关功能就是找到之后从列表中清除,最后的判断功能也是

他会把删除的结果给出来

 

其他功能例如查看粉丝列表啥的都是 先根据实体和实体id获得键值 redis根据键值去查询结果 得到结果后是一个set集合

我们会把它转成list  就是写一个arrayList然后for循环添加

还有 类似这种zrang函数我们都封装了  因为每次调用是要求从资源池之中获得资源的

目的是用完就返回 不会产生浪费

来看controller层 以关注功能为例

其实这个方法是post或者get都行 因为你浏览那个url的时候 被你关注的实体id是会显示的 @Responsebody 注解表示该方法的返回的结果直接写入 HTTP 响应正文

很简单 判断等没登陆  发送一个事件过去 (站内信)然后调用follow 功能看看成没成功 返回json数据
 

@RequestMapping(path = {"/followuser"}, method = {RequestMethod.POST})
    @ResponseBody
    public String follow(@RequestParam("userId") int userId){
        if(hostHolder.getUser() == null){
            return WendaUtil.getJsonString(999);
        }
        //完成关注的功能
        boolean ret = followService.follow(hostHolder.getUser().getId(), EntityType.ENTITY_USER, userId);
        //再把关注的事件发出去
        eventProducer.fireEvent(new EventModel(EventType.FOLLOW)
                .setActorId(hostHolder.getUser().getId()).setEntityId(userId)
        .setEntityType(EntityType.ENTITY_USER).setEntityOwnerId(userId));
        //返回有多少人关注 返回code 和 message
        return WendaUtil.getJsonString(ret ? 0 : 1, String.valueOf(followService.getFolloweesCount(hostHolder.getUser().getId(),EntityType.ENTITY_USER)));

    }

成功添加code值为0 否则返回1 这个json的形式就 我们这里是第二个 就是

code: 0

msg: 粉丝个数

c

事件类型是关注事件 触发这是这个访问的用户 实体里面信息是被关注的用户 之后就是消息队列

dohandle就是提取处事件的信息封装一个站内信然后发送消息(数据库添加这个消息就成)

这里还要判断事件是关注问题还是关注人 判断一下实体类型

粉丝列表实现

太简单了 判断登录 然后把需要的信息从数据库选出来就行

新鲜事的实现

redis存的是事务的id

新鲜事的产生:

比如某个id是带明星 我关注了谁或者我评论了谁那么我就产生一个新鲜事 也就是关注或者评论出发一个事件

我这个feed的 dohanlde的事件是包括 关注和评论的

dohandle是怎么做的呢

构造一个feed对象 然后存到数据库里面

获取我的粉丝列表 然后把这个feed再mysql里面的id 存到redis的每个粉丝的新鲜事列表里面 每个列表的对应的键值就是 timeline:id

然后这个feedid就加到redis里面了

当然新鲜事feed 包括 时间 事件的类型 事件的触发者是谁, 新鲜事的内容(如果是关注问题)看下面的代码

产生新鲜事人的id 头像 名称 如果是他关注了问题 那么我们就是返回这个问题的id 和问题的标题 其实可以分开写的 很简单就不管了 记住用了hashmap 每个键值存数据 然后把这个hashmap转成jsonObject返回给dohandle 最后被存到mysql里面了

推的模式:

步骤: redis的 list集合中存储着新鲜事的键值(可以想象新鲜事的键值是跟用户有关的 所以构成就是timeline : userid)

从list集合中取出 我们定义的是取出10个 然后 去sql里面寻找新鲜事,取出来之后组装到一个arraylist里面即可

推的模式已经知道是给粉丝推送事件 那么dohandler是重点 之前也出现过 feed在触发一次相关事件之后会存储在mysql里面

但是在redis里面 根据不同的粉丝生成不同的键值, 给这些粉丝存储这个feed的id(mysql的id)

在controller里面触发pushfeeds的url之后

//采用推的模式
    @RequestMapping(path = {"/pushfeeds"}, method = {RequestMethod.GET, RequestMethod.POST})
    private String getPushFeeds(Model model) {
        int localUserId = hostHolder.getUser() != null ? hostHolder.getUser().getId() : 0;
        //从jedis中取出feedid 从这里面看一下key
        List<String> feedIds = jedisAdapter.lrange(RedisKeyUtil.getTimelineKey(localUserId), 0, 10);
        List<Feed> feeds = new ArrayList<Feed>();
        for (String feedId : feedIds) {
            Feed feed = feedService.getById(Integer.parseInt(feedId));
            if (feed != null) {
                feeds.add(feed);
            }
        }
        model.addAttribute("feeds", feeds);
        return "feeds";
    }

可以看出 当我们一个用户收获新鲜事的时候,推模式下  从redis里面取出10个feed,这些feed都是之前别的我们关注的用户存到我这个用户对应的redis里面的 现在把这个feedid取出来 然后去mysql里面找 

拉模式:

这个更简单 都没有redis 直接从mysql 取出我关注的人 然后把我关注的人的feed找出来

这里关键就是这个feeddao的实现 推模式的一些实现也在这里面

 

maxid 我们给的就是Integer的最大值 uerid是一个列表 count是取出的个数

mybatis实现: 选择函数 返回值类型 select from where id < maxId(就是所有的情况) 分为用户登没登陆就是userid为不为0

集合遍历每个userIds 得到结果后按id降序 去前十个 这sql好好学习一下

     //拉的模式
    @RequestMapping(path = {"/pullfeeds"}, method = {RequestMethod.GET, RequestMethod.POST})
    private String getPullFeeds(Model model) {
        //本人的用户id
        int localUserId = hostHolder.getUser() != null ? hostHolder.getUser().getId() : 0;
        //查看我关注了多少人
        List<Integer> followees = new ArrayList<>();
        if (localUserId != 0) {//==0表示不是登录状态
            // 关注的人
            followees = followService.getFollowees(localUserId, EntityType.ENTITY_USER, Integer.MAX_VALUE);
        }
        //取出关注人的新鲜事 最后拿10个
        List<Feed> feeds = feedService.getUserFeeds(Integer.MAX_VALUE, followees, 10);
        model.addAttribute("feeds", feeds);
        return "feeds";
    }

更新 现在我们只是根据页面进行推 或者 拉 能不能,根据重定向实现不同推拉模式的跳转;判断用户是不是活跃用户 是的话跳转到push 不是的话跳转到pull

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值