package com.xiaozhi.blog.redis;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.BulkMapper;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.query.SortQuery;
import org.springframework.data.redis.core.query.SortQueryBuilder;
import org.springframework.data.redis.hash.DecoratingStringHashMapper;
import org.springframework.data.redis.hash.HashMapper;
import org.springframework.data.redis.hash.JacksonHashMapper;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.data.redis.support.collections.DefaultRedisList;
import org.springframework.data.redis.support.collections.DefaultRedisMap;
import org.springframework.data.redis.support.collections.DefaultRedisSet;
import org.springframework.data.redis.support.collections.RedisList;
import org.springframework.data.redis.support.collections.RedisMap;
import org.springframework.data.redis.support.collections.RedisSet;
import org.springframework.stereotype.Component;
import com.xiaozhi.blog.utils.KeyUtils;
import com.xiaozhi.blog.utils.MentionUtil;
import com.xiaozhi.blog.utils.SortList;
import com.xiaozhi.blog.vo.Page;
import com.xiaozhi.blog.vo.Post;
import com.xiaozhi.blog.vo.Range;
import com.xiaozhi.blog.vo.Video;
import com.xiaozhi.blog.vo.WebPost;
/**
* Twitter-clone on top of Redis.
*
* @author Costin Leau
*/
@Component
public class BlogDao {
private static Log logger = LogFactory.getLog(BlogDao.class);
@Autowired
private StringRedisTemplate template;
@Autowired
private RetwisRepository repository;
@Autowired
private MentionUtil mentionUtil;
//global timeline
private RedisList<String> timeline;
private final HashMapper<Post, String, String> postMapper = new DecoratingStringHashMapper<Post>(new JacksonHashMapper<Post>(Post.class) {
});
@PostConstruct
public void init () {
timeline = new DefaultRedisList<String>(KeyUtils.timeline(), template);
}
/**
* 为收到或者发出评论获取微博信息
* @param pid
* @param isAddUser
* @return
*/
public WebPost getBlogForCommentINfo(String pid,boolean isAddUser){
Post post = postMapper.fromHash(post(pid));
if(post==null || post.getUid()==null){
WebPost wPost = new WebPost();
wPost.setPid(Integer.valueOf(pid));
return wPost;
}
WebPost wPost = new WebPost(post);
wPost.setPid(Integer.valueOf(pid));
if(isAddUser)wPost.setUser(repository.getUserById(post.getUid()));
wPost.setContent(mentionUtil.replaceMentions(post.getContent()));
return wPost;
}
/**
* 根据微薄id获取微薄信息(不包括转发)
* @param pid
* @return
*/
public WebPost getBlogById(String pid,boolean isreplaceName){
Post post = postMapper.fromHash(post(pid));
if(post==null || post.getUid()==null){
WebPost wPost = new WebPost();
wPost.setPid(Integer.valueOf(pid));
return wPost;
}
WebPost wPost = new WebPost(post);
wPost.setPid(Integer.valueOf(pid));
wPost.setUser(repository.getUserById(post.getUid()));
if(isreplaceName){
wPost.setContent(mentionUtil.replaceMentions(post.getContent()));
}else{
wPost.setContent(post.getContent());
}
// try {
// if(post.getVideoJson()!=null)wPost.setVideo(new ObjectMapper().readValue(post.getVideoJson(),Video.class));
// if(post.getPageJson()!=null)wPost.setPage(new ObjectMapper().readValue(post.getPageJson(),Page.class));
// } catch (Exception e) {
// e.printStackTrace();
// }
return wPost;
}
/**
* 根据微薄id获取微薄信息(包括转发)
* @param pid
* @return
*/
public WebPost getPost(String pid) {
return convertPost(pid, post(pid));
}
/**
* 获取当前用户发布了的微薄列表
* @param uid
* @param range
* @return
*/
public List<WebPost> getPosts(String uid, Range range) {
return convertPidsToPosts(KeyUtils.posts(uid), range);
}
/**
* 获取当前用户发布了的微薄总数
* @param uid
* @return
*/
public int getPostsNum(String uid){
int num = template.opsForList().size(KeyUtils.posts(uid)).intValue();
return num;
}
/**
* 获得自己的timeline(包括关注了的人发的微搏)
* @param uid
* @param range
* @return
*/
public List<WebPost> getTimeline(String uid, Range range) {
return convertPidsToPosts(KeyUtils.timeline(uid), range);
}
/**
* 获得自己的timeline总数(包括关注了的人发的微搏)
* @param uid
* @return
*/
public int getTimelineNum(String uid){
return template.opsForList().size(KeyUtils.timeline(uid)).intValue();
}
/**
* 获得微薄提及列表
* @param uid
* @param range
* @return
*/
public List<WebPost> getMentions(String uid, Range range) {
return convertPidsToPosts(KeyUtils.mentions(uid), range);
}
public int getMentionsNum(String uid){
return template.opsForList().size(KeyUtils.mentions(uid)).intValue();
}
/**
* 获取所有微博
* @param range
* @return
*/
public List<WebPost> getAlltimeline(Range range) {
return convertPidsToPosts(KeyUtils.timeline(), range);
}
/**
* 获取所有微博数
* @param uid
* @return
*/
public int getAllTimelineNum(){
return template.opsForList().size(KeyUtils.timeline()).intValue();
}
/**
* 加入收藏
* @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 uid
* @param range
* @return
*/
public List<WebPost> getCollectBlogs(String uid, Range range) {
List<WebPost> list = convertPidsToPosts(KeyUtils.collect(uid), range);
return list;
}
/**
* 获取收藏微博数
* @param uid
* @return
*/
public int getCollectBlogsNum(String uid){
return template.opsForList().size(KeyUtils.collect(uid)).intValue();
}
/**
* 发布微搏
* @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
post(pid).putAll(postMapper.toHash(p));
if(isForword){
template.opsForHash().increment(KeyUtils.post(p.getTransmitid()), "transmitNum", 1);//对微博转发数+1操作
}
//add links
posts(uid).addFirst(pid);//自己发的微薄列表
timeline.addFirst(pid);//总时间线
timeline(uid).addFirst(pid);//加入自己可见的时间线(包括能看到你追随的人发的微薄)
template.opsForHash().increment(KeyUtils.uid(uid), "blogNum", 1);//对自己微博数+1操作
//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) {
boolean 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) {
logger.error("==================> removeBlogByMe error :"+e.toString());
}finally{
connection.close();
}
return false;
}
});
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;
}
/**
* 关注的时候更新本人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(String key, Range range) {
String pid = "pid:*->";
final String pidKey = "#";
final String uid = "uid";
final String content = "content";
final String replyPid = "replyPid";
final String replyUid = "replyUid";
final String time = "time";
final String commentNum = "commentNum";
final String transmitNum = "transmitNum";
final String transmitid = "transmitid";
final String videoJson = "videoJson";
final String pageJson = "pageJson";
final String pic = "pic";
SortQuery<String> query = SortQueryBuilder.sort(key).noSort()
.get(pidKey)
.get(pid + uid)
.get(pid + content)
.get(pid + replyPid)
.get(pid + replyUid)
.get(pid + time)
.get(pid + commentNum)
.get(pid + transmitNum)
.get(pid + transmitid)
.get(pid + videoJson)
.get(pid + pageJson)
.get(pid + pic)
.limit(range.being, range.end-range.being+1).build();
BulkMapper<WebPost, String> hm = new BulkMapper<WebPost, String>() {
public WebPost mapBulk(List<String> bulk) {
Map<String, String> map = new LinkedHashMap<String, String>();
//Iterator<String> iterator = bulk.iterator();
String pid = bulk.get(0);
map.put(uid, bulk.get(1));
map.put(content, bulk.get(2));
map.put(replyPid, bulk.get(3));
map.put(replyUid, bulk.get(4));
map.put(time, bulk.get(5));
map.put(commentNum, bulk.get(6));
map.put(transmitNum, bulk.get(7)==null?"0":bulk.get(7));
map.put(transmitid, bulk.get(8));
map.put(videoJson, bulk.get(9)==null?null:bulk.get(9));
map.put(pageJson, bulk.get(10)==null?null:bulk.get(10));
map.put(pic, bulk.get(11)==null?"":bulk.get(11));
return convertPost(pid, map);
}
};
List<WebPost> sort = template.sort(query, hm);
return sort;
}
private WebPost convertPost(String pid, Map<String,String> hash) {
Post post = postMapper.fromHash(hash);
if(post==null || post.getUid()==null){
WebPost wPost = new WebPost();
wPost.setPid(Integer.valueOf(pid));
return wPost;
}
WebPost wPost = new WebPost(post);
if(null!=post.getTransmitid() && !"".equals(post.getTransmitid())){//转发微薄
wPost.setWebPost(this.getBlogById(post.getTransmitid(),true));
}
wPost.setPid(Integer.valueOf(pid));
wPost.setUser(repository.getUserById(post.getUid()));
wPost.setContent(mentionUtil.replaceMentions(post.getContent()));
// try {
// if(post.getVideoJson()!=null)wPost.setVideo(new ObjectMapper().readValue(post.getVideoJson(),Video.class));
// if(post.getPageJson()!=null)wPost.setPage(new ObjectMapper().readValue(post.getPageJson(),Page.class));
// } catch (Exception e) {
// e.printStackTrace();
// }
return wPost;
}
/**
* 处理提及自己的事宜
* @param post
* @param pid
*/
private void handleMentions(Post post) {
// find mentions
Collection<String> mentions = mentionUtil.findMentions(post.getContent());
if(mentions.isEmpty()&& post.getTransmitid()!=null){
Post post1 = postMapper.fromHash(post(post.getTransmitid()));
mentions = mentionUtil.findMentions(post1.getContent());
}
for (String mention : mentions) {
if(logger.isDebugEnabled()){
logger.debug("********************** mention :"+mention);
}
String uid = repository.findUidByNickname(mention);
if(logger.isDebugEnabled()){
logger.debug("********************** uid :"+uid);
}
if (uid != null) {
mentions(uid).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 RedisMap<String, String> post(String pid) {
return new DefaultRedisMap<String, String>(KeyUtils.post(pid), template);
}
private RedisList<String> posts(String uid) {
return new DefaultRedisList<String>(KeyUtils.posts(uid), template);
}
private RedisSet<String> followers(String uid) {
return new DefaultRedisSet<String>(KeyUtils.followers(uid), template);
}
}