功能概览:
首页展示, 登录注册功能,页面访问(拦截器,匿名用户,网址分析)问题功能,评论, 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