package com.xiaozhi.blog.mongo; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Update; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.support.atomic.RedisAtomicLong; import org.springframework.data.redis.support.collections.DefaultRedisList; import org.springframework.data.redis.support.collections.RedisList; import org.springframework.stereotype.Repository; import com.xiaozhi.blog.utils.Collections3; import com.xiaozhi.blog.utils.KeyUtils; import com.xiaozhi.blog.utils.MentionUtil; import com.xiaozhi.blog.utils.SortList; import com.xiaozhi.blog.vo.Post; import com.xiaozhi.blog.vo.Range; import com.xiaozhi.blog.vo.User; import com.xiaozhi.blog.vo.WebPost; /** * Twitter-clone on top of Redis. * * @author Costin Leau */ @Repository public class MongoBlogDao { private static Log logger = LogFactory.getLog(MongoBlogDao.class); @Autowired private MongoOperations mongoTemplate; @Autowired private StringRedisTemplate template; @Autowired private MongoUserDao mongoUserDao; @Autowired private MentionUtil mentionUtil; /** * 根据微博id获取微博信息(不包括转发) * @param pid * @return */ public WebPost getBlogById(String pid){ Post post = this.mongoTemplate.findOne(new Query(Criteria.where("pid").is(pid)), Post.class); if(post==null || post.getUid()==null){ WebPost wPost = new WebPost(); wPost.setPid(Integer.valueOf(pid)); return wPost; } WebPost wPost = new WebPost(post); wPost.setUser(mongoUserDao.getUserById(post.getUid())); return wPost; } /** * 根据微薄id获取微博信息(包括转发) * @param pid * @return */ public WebPost getBlogWithForwardById(String pid) { Post post = this.mongoTemplate.findOne(new Query(Criteria.where("pid").is(pid)), Post.class); return convertPost(post); } /** * 获取微博列表 * @param uid * @param range * @return */ public List<WebPost> getPosts(List<String> keys, Range range) { return convertPidsToPosts(keys, range); } /** * 获取微博id列表 * @param uid * @return */ public List<String> getPostsIdList(String uid,String key){ return this.template.opsForList().range(key, 0,-1); } /** * 加入收藏 * @param uid * @param pid * @return */ public boolean collectBlog(String uid,String pid){ try { template.opsForList().remove(KeyUtils.collect(uid), 0, pid); collect(uid).addFirst(pid);//加入收藏 return true; } catch (Exception e) { // TODO: handle exception } return false; } /** * 删除收藏 * @param uid * @param pid * @return */ public boolean removeCollectBlog(String uid, String pid) { try { template.opsForList().remove(KeyUtils.collect(uid), 0, pid); return true; } catch (Exception e) { // TODO: handle exception } return false; } /** * 发布微搏 * @param username * @param post */ public String post(String uid, WebPost post ,boolean isForword) { Post p = post.asPost(); p.setUid(uid); String pid = String.valueOf(new RedisAtomicLong(KeyUtils.globalPid(), template.getConnectionFactory()).incrementAndGet()); p.setPid(pid); handleMentions(p);//处理提及的人和提及人的链接 //add post try { this.mongoTemplate.save(p); this.mongoTemplate.updateFirst(new Query(Criteria.where("id").is(uid)), new Update().inc("blogNum", 1),User.class);//对自己微博数+1操作 if(isForword){ this.mongoTemplate.updateFirst(new Query(Criteria.where("pid").is(p.getTransmitid())), new Update().inc("transmitNum", 1),Post.class);//对微博转发数+1操作 } } catch (Exception e) { logger.error("==================> post mogodb error :"+e.toString()); return null; // TODO: handle exception } //add links try { posts(uid).addFirst(pid);//自己发的微薄列表 timeline(uid).addFirst(pid);//加入自己可见的时间线(包括能看到你追随的人发的微薄) } catch (Exception e) { //call back this.mongoTemplate.remove(new Query(Criteria.where("pid").is(pid)),Post.class); this.mongoTemplate.updateFirst(new Query(Criteria.where("id").is(uid)), new Update().inc("blogNum", -1),User.class);//对自己微博数-1操作 if(isForword){ this.mongoTemplate.updateFirst(new Query(Criteria.where("pid").is(p.getTransmitid())), new Update().inc("transmitNum", -1),Post.class);//对微博转发数-1操作 } logger.error("==================> post redis error :"+e.toString()); return null; } //update followers 推模式更新粉丝time line for (String follower : followers(uid)) { timeline(follower).addFirst(pid); } return pid; } /** * 删除自己的微博 * @param username * @param pid */ public boolean removeBlogByMe(final String uid, final String pid) { final Post post; try { post = this.mongoTemplate.findAndRemove(new Query(Criteria.where("pid").is(pid)),Post.class); //this.mongoTemplate.remove(new Query(Criteria.where("pid").is(pid)),Post.class); } catch (Exception e) { return false; } boolean result = false; try { result = (Boolean) template.execute(new RedisCallback<Object>() { @Override public Object doInRedis(RedisConnection connection)throws DataAccessException { try { connection.multi();//事务开启 connection.lRem(KeyUtils.posts(uid).getBytes(), 0, pid.getBytes()); connection.lRem(KeyUtils.timeline(uid).getBytes(), 0, pid.getBytes()); connection.lRem(KeyUtils.timeline().getBytes(), 0, pid.getBytes()); //connection.del(KeyUtils.post(pid).getBytes()); connection.del(KeyUtils.commentByBlog(pid).getBytes());/**删除微博评论列表(不做发出、收到评论的列表和评论对象本身删除动作)*/ //connection.hIncrBy(KeyUtils.uid(uid).getBytes(), "blogNum".getBytes(), -1); connection.exec(); return true; } catch (Exception e) { //call back mongoTemplate.save(post); logger.error("==================> removeBlogByMe error :"+e.toString()); }finally{ connection.close(); } return false; } }); } catch (Exception e) { // TODO: handle exception } return result; } /** * 别人删除你的微薄残余 * @param uid * @param pid * @return */ public boolean removeBlogByOther(String uid, String pid) { try { template.opsForList().remove(KeyUtils.timeline(uid), 0, pid); return true; } catch (Exception e) { // TODO: handle exception } return false; } /** * 删除提及我的微博残余 * @param uid * @param pid * @return */ public boolean removeMention(String uid, String pid) { try { mentions(uid).remove(pid); //template.opsForList().remove(KeyUtils.timeline(uid), 0, pid); return true; } catch (Exception e) { // TODO: handle exception } return false; } /** * 关注的时候更新本人TimeLine * @param uid * @param targetId */ public boolean updateTimeLineForFllowing(final String uid,String targetId){ List<String> targetTimeLine = posts(targetId).range(0, -1); List<String> utimeLine = timeline(uid).range(0, -1); utimeLine.addAll(targetTimeLine); final List<Long> newList = new ArrayList<Long>(); for(String pid:utimeLine){ newList.add(Long.valueOf(pid)); } SortList<Long> sortList = new SortList<Long>(); sortList.sort(newList, "longValue", "Long","desc");//重新按pid排序 boolean result = (Boolean) template.execute(new RedisCallback<Object>() { @Override public Object doInRedis(RedisConnection connection)throws DataAccessException { try { connection.multi();//事务开启 connection.del(KeyUtils.timeline(uid).getBytes()); for(Long pid:newList){ connection.rPush(KeyUtils.timeline(uid).getBytes(), pid.toString().getBytes()); } connection.exec(); return true; } catch (Exception e) { logger.error("==================> updateTimeLineForFllowing error :"+e.toString()); }finally{ connection.close(); } return false; } }); return result; } /** * 取消关注时候剔除被关注人微博信息 * @param uid * @param targetId * @return */ public boolean delTimeLineForFllowing(final String uid,String targetId){ final List<String> targetTimeLine = posts(targetId).range(0, -1); final List<String> utimeLine = timeline(uid).range(0, -1); boolean result = (Boolean) template.execute(new RedisCallback<Object>() { @Override public Object doInRedis(RedisConnection connection)throws DataAccessException { try { connection.multi();//事务开启 for(String pid:utimeLine){ if(targetTimeLine.contains(pid)){ connection.lRem(KeyUtils.timeline(uid).getBytes(), 0, pid.getBytes()); } } connection.exec(); return true; } catch (Exception e) { logger.error("==================> updateTimeLineForFllowing error :"+e.toString()); }finally{ connection.close(); } return false; } }); return result; } /** * 获取微薄列表 */ private List<WebPost> convertPidsToPosts(List<String> keys, Range range) { if(keys==null || keys.isEmpty())return new ArrayList<WebPost>(); List<String> pagekeysList=keys.subList(range.being, (range.end+1)>keys.size()?keys.size():(range.end+1)); Query query = new Query(Criteria.where("pid").in(pagekeysList.toArray())); //query.sort().on("pid",Order.DESCENDING);//按pid倒序 List<Post> posts = this.mongoTemplate.find(query,Post.class); List<WebPost> webPosts= new ArrayList<WebPost>(); Map<String, Post> postMap= new ConcurrentHashMap<String, Post>(); List<String> uids = new ArrayList<String>();//定义当前页所有用户id集合 List<String> relayPids = new ArrayList<String>();//定义当前页所有转发微博的转发id集合 for(Post post :posts){ uids.add(post.getUid()); if(post.getTransmitid()!=null)relayPids.add(post.getTransmitid()); postMap.put(post.getPid(), post); } //获取转发微博集合 Map<String,WebPost> subWebPosts = convertrelayPidsToPosts(relayPids); //获取当前页微博所属用户集合 Map<String, User> userMap = this.mongoUserDao.getUsersByIds(uids); //将用户信息和转发微博添加进去 for(String key :pagekeysList){ Post post = postMap.get(key); WebPost webPost = new WebPost(); webPost.setPid(Integer.valueOf(key)); if(post!=null)webPost = convertPost(post,userMap.get(post.getUid())); if(post!=null && post.getTransmitid()!=null){ webPost.setWebPost(subWebPosts.get(post.getTransmitid())); } webPosts.add(webPost); } return webPosts; } /** * 批量获得当前页的转发微博的微博内容。 * @param keys * @param range * @return */ public Map<String,WebPost> convertrelayPidsToPosts(List<String> keys) { if(null==keys || keys.isEmpty())return new ConcurrentHashMap<String, WebPost>(); List<Post> posts = this.mongoTemplate.find(new Query(Criteria.where("pid").in(keys.toArray())),Post.class); Map<String,WebPost> webPosts= new ConcurrentHashMap<String, WebPost>(); List<String> uids = Collections3.extractToList(posts, "uid");//当前页所有用户id集合 //将用户信息添加进去 Map<String, User> userMap = this.mongoUserDao.getUsersByIds(uids); for(Post post :posts){ webPosts.put(post.getPid(),convertPost(post,userMap.get(post.getUid()))); } return webPosts; } /** * 转换微博信息 * @param post * @return */ private WebPost convertPost(Post post,User user) { if(post==null || post.getUid()==null){ WebPost wPost = new WebPost(); wPost.setPid(Integer.valueOf(post.getPid())); return wPost; } WebPost wPost = new WebPost(post); wPost.setPid(Integer.valueOf(post.getPid())); wPost.setUser(user); return wPost; } private WebPost convertPost(Post post) { if(post==null || post.getUid()==null){ WebPost wPost = new WebPost(); wPost.setPid(Integer.valueOf(post.getPid())); return wPost; } WebPost wPost = new WebPost(post); wPost.setUser(this.mongoUserDao.getUserById(post.getUid())); if(null!=post.getTransmitid()){//转发微博 wPost.setWebPost(this.getBlogById(post.getTransmitid())); } return wPost; } /** * 处理提及自己的事宜 * @param post * @param pid */ private void handleMentions(Post post) { // find mentions List<String> mentions = mentionUtil.findMentions(post.getContent()); post.setContent(mentionUtil.replaceMentions(post.getContent()));//为提及的人加链接 //将此微博加入提及人的被@列表中 Map<String, User> map = this.mongoUserDao.getUsersByNickNames(mentions); for (String mention : mentions) { User user = map.get(mention); if (user != null) { mentions(user.getId()).addFirst(post.getPid()); } } } // collections mapping the core data structures private RedisList<String> collect(String uid) { return new DefaultRedisList<String>(KeyUtils.collect(uid), template); } private RedisList<String> timeline(String uid) { return new DefaultRedisList<String>(KeyUtils.timeline(uid), template); } private RedisList<String> mentions(String uid) { return new DefaultRedisList<String>(KeyUtils.mentions(uid), template); } private RedisList<String> posts(String uid) { return new DefaultRedisList<String>(KeyUtils.posts(uid), template); } private RedisList<String> followers(String uid) { return new DefaultRedisList<String>(KeyUtils.followers(uid), template); } }