@Async
  protected void notifyUsers(Topic topic, TopicReply reply, String cnt, int userId) {
    String replyAuthorName = dao.fetch(User.class, userId).getName();
    // 通知原本的作者
    if (topic.getUserId() != userId) {
      String alert = replyAuthorName + "回复了您的帖子";
      pushUser(
          topic.getUserId(),
          alert,
          topic.getId(),
          replyAuthorName,
          topic.getTitle(),
          PushService.PUSH_TYPE_REPLY);
    }

    Set<String> ats = findAt(cnt, 5);
    for (String at : ats) {
      User user = dao.fetch(User.class, at);
      if (user == null) continue;
      if (topic.getUserId() == user.getId()) continue; // 前面已经发过了
      if (userId == user.getId()) continue; // 自己@自己, 忽略
      String alert = replyAuthorName + "在帖子回复中@了你";
      pushUser(
          user.getId(),
          alert,
          topic.getId(),
          replyAuthorName,
          topic.getTitle(),
          PushService.PUSH_TYPE_AT);
    }
  }
 public NutMap _topic(Topic topic, Map<Integer, UserProfile> authors, String mdrender) {
   yvrService.fillTopic(topic, authors);
   NutMap tp = new NutMap();
   tp.put("id", topic.getId());
   tp.put("author_id", "" + topic.getAuthor().getUserId());
   tp.put("tab", topic.getType().toString());
   tp.put(
       "content",
       "false".equals(mdrender)
           ? topic.getContent()
           : Markdowns.toHtml(topic.getContent(), urlbase));
   tp.put("title", StringEscapeUtils.unescapeHtml(topic.getTitle()));
   if (topic.getLastComment() != null)
     tp.put("last_reply_at", _time(topic.getLastComment().getCreateTime()));
   tp.put("good", topic.isGood());
   tp.put("top", topic.isTop());
   tp.put("reply_count", topic.getReplyCount());
   tp.put("visit_count", topic.getVisitCount());
   tp.put("create_at", _time(topic.getCreateTime()));
   UserProfile profile = topic.getAuthor();
   if (profile != null) {
     profile.setScore(yvrService.getUserScore(topic.getUserId()));
   }
   tp.put("author", _author(profile));
   return tp;
 }
 @Aop("redis")
 public void fillTopic(Topic topic, Map<Integer, UserProfile> authors) {
   if (topic.getUserId() == 0) topic.setUserId(1);
   topic.setAuthor(_cacheFetch(authors, topic.getUserId()));
   Double reply_count = jedis().zscore(RKEY_REPLY_COUNT, topic.getId());
   topic.setReplyCount(reply_count == null ? 0 : reply_count.intValue());
   if (topic.getReplyCount() > 0) {
     String replyId = jedis().hget(RKEY_REPLY_LAST, topic.getId());
     TopicReply reply = dao.fetch(TopicReply.class, replyId);
     if (reply != null) {
       if (reply.getUserId() == 0) reply.setUserId(1);
       reply.setAuthor(_cacheFetch(authors, reply.getUserId()));
       topic.setLastComment(reply);
     }
   }
   Double visited = jedis().zscore(RKEY_TOPIC_VISIT, topic.getId());
   topic.setVisitCount((visited == null) ? 0 : visited.intValue());
 }