新鲜事开发

类似于微博的最新动态,最常见的就是在个人页看到自己关注的人的动态
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});
    }

待完善功能

  • 多好友合并去重
  • 关联实体删除清理
  • 取消/新增关注实时更新
  • 分时段存储
  • 增加更多种类的新鲜事
  • 推拉结合模式