新鲜事开发
类似于微博的最新动态,最常见的就是在个人页看到自己关注的人的动态
xxx给xxx点了个赞、xxx转发了xxxx
像这样的
这时候我们要新建一个新鲜事的表用来保存新鲜事
属性有id,用来确定当前记录的唯一性
created_date用来进行排序
user_id是触发新鲜事的人,可以是你关注的人
data中的数据用json格式保存
type代表新鲜事的类型,是评论还是点赞
model层
@Data public class Feed { private int id; private int type; private int userId; private Date createdDate; private String data; private JSONObject dataJson = null; public void setData(String data) { this.data = data; dataJson = JSONObject.parseObject(data); } public String get(String key) { return dataJson == null ? null : dataJson.getString(key); }
dao层
增加feed功能、通过id获取feed、通过user_id获取feed列表
@Mapper public interface FeedDAO { String TABLE_NAME = " feed "; String INSERT_FIELDS = " user_id, data, created_date, type "; String SELECT_FIELDS = " id, " + INSERT_FIELDS; @Insert({"insert into ", TABLE_NAME, "(", INSERT_FIELDS, ") values (#{userId},#{data},#{createdDate},#{type})"}) int addFeed(Feed feed); @Select({"select", SELECT_FIELDS, " from ", TABLE_NAME, " where id=#{id}"}) Feed getFeedById(int id); @SelectProvider(type = FeedDynaSqlProvider.class, method = "selectUserFeeds") List<Feed> selectUserFeeds(@Param("maxId") int maxId, @Param("userIds") List<Integer> userIds, @Param("count") int count); }
public class FeedDynaSqlProvider { public String selectUserFeeds(@Param("maxId") int maxId, @Param("userIds") List<Integer> userIds, @Param("count") int count) { int num = userIds.size(); StringBuffer sb = new StringBuffer(); sb.append(" select "); sb.append(" id, created_date,user_id, data, type "); sb.append(" from "); sb.append(" feed where 1=1 and "); sb.append(" id < " + maxId); if (userIds.size() != 0) { sb.append(" and user_id in "); sb.append(" ( "); for (Integer userId : userIds) { if (num != 1) { sb.append("" + userId + ","); } else if (num == 1) { sb.append("" + userId); } num--; } sb.append(" ) "); } sb.append("ORDER BY id DESC LIMIT "+ count); System.out.println(sb.toString()); return sb.toString(); } }
service层
@Service public class FeedService { @Autowired FeedDAO feedDAO; public List<Feed> getUserFeeds(int maxId, List<Integer> userIds, int count) { return feedDAO.selectUserFeeds(maxId, userIds, count); } public boolean addFeed(Feed feed) { feedDAO.addFeed(feed); return feed.getId() > 0; } public Feed getById(int id) { return feedDAO.getFeedById(id); } }
controller层
这里是获取feed的两种模式
一种是推,一种是拉
推,简单来说就是在一个事件触发之后,主动将这个事件推给自己的粉丝,这样确实比较直接,但是也会带来很大的困扰,像谢娜那种1亿粉丝的,一推就推给1亿人,对服务器的压力太大了,而且1亿人中活跃的粉丝占比也不会太高,这样就白白浪费掉了服务器资源
拉,就是自己的关注者发了一个动态,不会第一时间就放到我的动态里,要我主动的去获取,才能获得到这个feed
大部分网页都是推拉结合的
@Controller public class FeedController { private static final Logger logger = LoggerFactory.getLogger(FeedController.class); @Autowired FeedService feedService; @Autowired FollowService followService; @Autowired HostHolder hostHolder; @Autowired JedisAdapter jedisAdapter; @RequestMapping(path = {"/pushfeeds"}, method = {RequestMethod.GET, RequestMethod.POST}) public String getPushFeeds(Model model) { int localUserId = hostHolder.getUser() != null ? hostHolder.getUser().getId() : 0; //获取当前用户存储在redis中的feed集合 List<String> feedIds = jedisAdapter.lrange(RedisKeyUtil.getTimelineKey(localUserId), 0, 10); List<Feed> feeds = new ArrayList<Feed>(); for (String feedId : feedIds) { //将feed通过feed的id查询出来,放进集合中,然后转发到页面中 Feed feed = feedService.getById(Integer.parseInt(feedId)); if (feed != null) { feeds.add(feed); } } model.addAttribute("feeds", feeds); return "feeds"; } @RequestMapping(path = {"/pullfeeds"}, method = {RequestMethod.GET, RequestMethod.POST}) public String getPullFeeds(Model model) { System.out.println(hostHolder.getUser().getId()); int localUserId = hostHolder.getUser() != null ? hostHolder.getUser().getId() : 0; List<Integer> followees = new ArrayList<>(); if (localUserId != 0) { // 关注的人 followees = followService.getFollowees(localUserId, EntityType.ENTITY_USER, Integer.MAX_VALUE); } List<Feed> feeds = feedService.getUserFeeds(Integer.MAX_VALUE, followees, 10); model.addAttribute("feeds", feeds); return "feeds"; } }
FeedHandler
/** * 所有feed都会通过feedHandler的事件*** * 当监听到事件,就会把事件传递到数据库里面 * 显示数据的时候先找自己关注的人,看他发生了多少事件 * 然后统一拉取出来,渲染页面 */ private String buildFeedData(EventModel model) { Map<String, String> map = new HashMap<String ,String>(); // 触发用户是通用的 User actor = userService.getUser(model.getActorId()); if (actor == null) { return null; } map.put("userId", String.valueOf(actor.getId())); map.put("userHead", actor.getHeadUrl()); map.put("userName", actor.getName()); if (model.getType() == EventType.COMMENT || (model.getType() == EventType.FOLLOW && model.getEntityType() == EntityType.ENTITY_QUESTION)) { Question question = questionService.getById(model.getEntityId()); if (question == null) { return null; } map.put("questionId", String.valueOf(question.getId())); map.put("questionTitle", question.getTitle()); return JSONObject.toJSONString(map); } return null; } @Override public void doHandle(EventModel model) { // 构造一个新鲜事 Feed feed = new Feed(); feed.setCreatedDate(new Date()); feed.setType(model.getType().getValue()); feed.setUserId(model.getActorId()); feed.setData(buildFeedData(model)); if (feed.getData() == null) { // 不支持的feed return; } feedService.addFeed(feed); // 获得所有粉丝 List<Integer> followers = followService.getFollowers(EntityType.ENTITY_USER, model.getActorId(), Integer.MAX_VALUE); // 系统队列 followers.add(0); // 给所有粉丝推事件 for (int follower : followers) { String timelineKey = RedisKeyUtil.getTimelineKey(follower); jedisAdapter.lpush(timelineKey, String.valueOf(feed.getId())); // 限制最长长度,如果timelineKey的长度过大,就删除后面的新鲜事 } } @Override public List<EventType> getSupportEventTypes() { return Arrays.asList(new EventType[]{EventType.COMMENT, EventType.FOLLOW}); }
待完善功能
- 多好友合并去重
- 关联实体删除清理
- 取消/新增关注实时更新
- 分时段存储
- 增加更多种类的新鲜事
- 推拉结合模式