<commit_before>package net.wendal.nutzbook.service.yvr;

import static net.wendal.nutzbook.bean.CResult._fail;
import static net.wendal.nutzbook.bean.CResult._ok;
import static net.wendal.nutzbook.util.RedisInterceptor.jedis;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.FieldFilter;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.Daos;
import org.nutz.integration.zbus.ZBusProducer;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Files;
import org.nutz.lang.Strings;
import org.nutz.lang.random.R;
import org.nutz.lang.util.NutMap;
import org.nutz.log.Log;
import org.nutz.log.Logs;
import org.nutz.mvc.Mvcs;
import org.nutz.mvc.annotation.Param;
import org.nutz.mvc.upload.TempFile;

import net.wendal.nutzbook.bean.CResult;
import net.wendal.nutzbook.bean.User;
import net.wendal.nutzbook.bean.UserProfile;
import net.wendal.nutzbook.bean.yvr.Topic;
import net.wendal.nutzbook.bean.yvr.TopicReply;
import net.wendal.nutzbook.bean.yvr.TopicTag;
import net.wendal.nutzbook.bean.yvr.TopicType;
import net.wendal.nutzbook.service.PushService;
import net.wendal.nutzbook.util.RedisKey;
import net.wendal.nutzbook.util.Toolkit;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Response;

@IocBean(create="init")
public class YvrService implements RedisKey {
	
	private static final Log log = Logs.get();

	@Inject 
	protected Dao dao;
	
	// 用于查询Topic和TopicReply时不查询content属性
	protected Dao daoNoContent;
	
	@Inject
	protected TopicSearchService topicSearchService;
	
	@Inject
	protected PushService pushService;
	
	// TODO 改成发布-订阅模式
	@Inject("java:$zbus.getProducer('topic-watch')")
	protected ZBusProducer topicWatchProducer;

	@Inject("java:$conf.get('topic.image.dir')")
	protected String imageDir;
	
	@Inject("java:$conf.get('topic.global.watchers')")
	protected String topicGlobalWatchers;
	
	protected Set<Integer> globalWatcherIds = new HashSet<>();
	
	@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 = daoNoContent.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());
	}
	
	protected UserProfile _cacheFetch(Map<Integer, UserProfile> authors, int userId) {
		if (authors == null)
			return null;
		UserProfile author = authors.get(userId);
		if (author == null) {
			author = dao.fetch(UserProfile.class, userId);
			authors.put(userId, author);
		}
		return author;
	}
	
	@Aop("redis")
	public String accessToken(UserProfile profile) {
		String loginname = profile.getLoginname();
		String at = jedis().hget(RKEY_USER_ACCESSTOKEN, loginname);
		if (at == null) {
			// 双向绑定
			at = R.UU32();
			jedis().hset(RKEY_USER_ACCESSTOKEN, loginname, at);
			jedis().hset(RKEY_USER_ACCESSTOKEN2, at, loginname);
			jedis().hset(RKEY_USER_ACCESSTOKEN3, at, ""+profile.getUserId());
		}
		return at;
	}
	
	@Aop("redis")
	public void resetAccessToken(String loginname) {
		String at = jedis().hget(RKEY_USER_ACCESSTOKEN, loginname); {
			jedis().hdel(RKEY_USER_ACCESSTOKEN, loginname);
			jedis().hdel(RKEY_USER_ACCESSTOKEN2, at);
			jedis().hdel(RKEY_USER_ACCESSTOKEN3, at);
		}
	}
	
	@Aop("redis")
	public int getUserByAccessToken(String at) {
		String uid_str = jedis().hget(RKEY_USER_ACCESSTOKEN3, at);
		if (uid_str == null)
			return -1;
		return Integer.parseInt(uid_str);
	}

	@Aop("redis")
	public CResult add(Topic topic, int userId) {
		if (userId < 1) {
			return _fail("请先登录");
		}
		if (Strings.isBlank(topic.getTitle()) || topic.getTitle().length() > 100 || topic.getTitle().length() < 5) {
			return _fail("标题长度不合法");
		}
		if (Strings.isBlank(topic.getContent()) || topic.getContent().length() > 20000) {
			return _fail("内容不合法");
		}
		if (topic.getTags() != null && topic.getTags().size() > 10) {
			return _fail("最多只能有10个tag");
		}
		if (0 != dao.count(Topic.class, Cnd.where("title", "=", topic.getTitle().trim()))) {
			return _fail("相同标题已经发过了");
		}
		topic.setTitle(Strings.escapeHtml(topic.getTitle().trim()));
		topic.setUserId(userId);
		topic.setTop(false);
		topic.setTags(new HashSet<>());
		if (topic.getType() == null)
			topic.setType(TopicType.ask);
		topic.setContent(Toolkit.filteContent(topic.getContent()));
		dao.insert(topic);
		try {
			topicSearchService.add(topic);
		} catch (Exception e) {
		}
		// 如果是ask类型,把帖子加入到 "未回复"列表
		Pipeline pipe = jedis().pipelined();
		if (TopicType.ask.equals(topic.getType())) {
			pipe.zadd(RKEY_TOPIC_NOREPLY, System.currentTimeMillis(), topic.getId());
		}
		pipe.zadd(RKEY_TOPIC_UPDATE + topic.getType(), System.currentTimeMillis(), topic.getId());
		pipe.zincrby(RKEY_USER_SCORE, 100, ""+userId);
		pipe.sync();
		for (Integer watcherId : globalWatcherIds) {
			if (watcherId != userId)
				pushUser(watcherId, "新帖:" + topic.getTitle(), topic.getId());
		}
		return _ok(topic.getId());
	}

	@Aop("redis")
	public CResult addReply(final String topicId, final TopicReply reply, final int userId) {
		if (userId < 1)
			return _fail("请先登录");
		if (reply == null || reply.getContent() == null || reply.getContent().trim().isEmpty()) {
			return _fail("内容不能为空");
		}
		final String cnt = reply.getContent().trim();
		if (cnt.length() < 2 || cnt.length() > 10000) {
			return _fail("内容太长或太短了");
		}
		final Topic topic = daoNoContent.fetch(Topic.class, topicId); // TODO 改成只fetch出type属性
		if (topic == null) {
			return _fail("主题不存在");
		}
		reply.setTopicId(topicId);
		reply.setUserId(userId);
		reply.setContent(Toolkit.filteContent(reply.getContent()));
		dao.insert(reply);
		// 更新topic的时间戳
		Pipeline pipe = jedis().pipelined();
		if (topic.isTop()) {
			pipe.zadd(RKEY_TOPIC_TOP, reply.getCreateTime().getTime(), topicId);
		} else {
			pipe.zadd(RKEY_TOPIC_UPDATE + topic.getType(), reply.getCreateTime().getTime(), topicId);
		}
		pipe.zrem(RKEY_TOPIC_NOREPLY, topicId);
		if (topic.getTags() != null) {
			for (String tag : topic.getTags()) {
				pipe.zadd(RKEY_TOPIC_TAG+tag.toLowerCase().trim(), reply.getCreateTime().getTime(), topicId);
				pipe.zincrby(RKEY_TOPIC_TAG_COUNT, 1, tag.toLowerCase().trim());
			}
		}
		pipe.hset(RKEY_REPLY_LAST, topicId, reply.getId());
		pipe.zincrby(RKEY_REPLY_COUNT, 1, topicId);
		pipe.zincrby(RKEY_USER_SCORE, 10, ""+userId);
		pipe.sync();
		<commit_msg>fix: 回复的时候不应该为tag的统计值+1啊<commit_after>package net.wendal.nutzbook.service.yvr;

import static net.wendal.nutzbook.bean.CResult._fail;
import static net.wendal.nutzbook.bean.CResult._ok;
import static net.wendal.nutzbook.util.RedisInterceptor.jedis;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.FieldFilter;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.Daos;
import org.nutz.integration.zbus.ZBusProducer;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Files;
import org.nutz.lang.Strings;
import org.nutz.lang.random.R;
import org.nutz.lang.util.NutMap;
import org.nutz.log.Log;
import org.nutz.log.Logs;
import org.nutz.mvc.Mvcs;
import org.nutz.mvc.annotation.Param;
import org.nutz.mvc.upload.TempFile;

import net.wendal.nutzbook.bean.CResult;
import net.wendal.nutzbook.bean.User;
import net.wendal.nutzbook.bean.UserProfile;
import net.wendal.nutzbook.bean.yvr.Topic;
import net.wendal.nutzbook.bean.yvr.TopicReply;
import net.wendal.nutzbook.bean.yvr.TopicTag;
import net.wendal.nutzbook.bean.yvr.TopicType;
import net.wendal.nutzbook.service.PushService;
import net.wendal.nutzbook.util.RedisKey;
import net.wendal.nutzbook.util.Toolkit;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Response;

@IocBean(create="init")
public class YvrService implements RedisKey {
	
	private static final Log log = Logs.get();

	@Inject 
	protected Dao dao;
	
	// 用于查询Topic和TopicReply时不查询content属性
	protected Dao daoNoContent;
	
	@Inject
	protected TopicSearchService topicSearchService;
	
	@Inject
	protected PushService pushService;
	
	// TODO 改成发布-订阅模式
	@Inject("java:$zbus.getProducer('topic-watch')")
	protected ZBusProducer topicWatchProducer;

	@Inject("java:$conf.get('topic.image.dir')")
	protected String imageDir;
	
	@Inject("java:$conf.get('topic.global.watchers')")
	protected String topicGlobalWatchers;
	
	protected Set<Integer> globalWatcherIds = new HashSet<>();
	
	@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 = daoNoContent.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());
	}
	
	protected UserProfile _cacheFetch(Map<Integer, UserProfile> authors, int userId) {
		if (authors == null)
			return null;
		UserProfile author = authors.get(userId);
		if (author == null) {
			author = dao.fetch(UserProfile.class, userId);
			authors.put(userId, author);
		}
		return author;
	}
	
	@Aop("redis")
	public String accessToken(UserProfile profile) {
		String loginname = profile.getLoginname();
		String at = jedis().hget(RKEY_USER_ACCESSTOKEN, loginname);
		if (at == null) {
			// 双向绑定
			at = R.UU32();
			jedis().hset(RKEY_USER_ACCESSTOKEN, loginname, at);
			jedis().hset(RKEY_USER_ACCESSTOKEN2, at, loginname);
			jedis().hset(RKEY_USER_ACCESSTOKEN3, at, ""+profile.getUserId());
		}
		return at;
	}
	
	@Aop("redis")
	public void resetAccessToken(String loginname) {
		String at = jedis().hget(RKEY_USER_ACCESSTOKEN, loginname); {
			jedis().hdel(RKEY_USER_ACCESSTOKEN, loginname);
			jedis().hdel(RKEY_USER_ACCESSTOKEN2, at);
			jedis().hdel(RKEY_USER_ACCESSTOKEN3, at);
		}
	}
	
	@Aop("redis")
	public int getUserByAccessToken(String at) {
		String uid_str = jedis().hget(RKEY_USER_ACCESSTOKEN3, at);
		if (uid_str == null)
			return -1;
		return Integer.parseInt(uid_str);
	}

	@Aop("redis")
	public CResult add(Topic topic, int userId) {
		if (userId < 1) {
			return _fail("请先登录");
		}
		if (Strings.isBlank(topic.getTitle()) || topic.getTitle().length() > 100 || topic.getTitle().length() < 5) {
			return _fail("标题长度不合法");
		}
		if (Strings.isBlank(topic.getContent()) || topic.getContent().length() > 20000) {
			return _fail("内容不合法");
		}
		if (topic.getTags() != null && topic.getTags().size() > 10) {
			return _fail("最多只能有10个tag");
		}
		if (0 != dao.count(Topic.class, Cnd.where("title", "=", topic.getTitle().trim()))) {
			return _fail("相同标题已经发过了");
		}
		topic.setTitle(Strings.escapeHtml(topic.getTitle().trim()));
		topic.setUserId(userId);
		topic.setTop(false);
		topic.setTags(new HashSet<>());
		if (topic.getType() == null)
			topic.setType(TopicType.ask);
		topic.setContent(Toolkit.filteContent(topic.getContent()));
		dao.insert(topic);
		try {
			topicSearchService.add(topic);
		} catch (Exception e) {
		}
		// 如果是ask类型,把帖子加入到 "未回复"列表
		Pipeline pipe = jedis().pipelined();
		if (TopicType.ask.equals(topic.getType())) {
			pipe.zadd(RKEY_TOPIC_NOREPLY, System.currentTimeMillis(), topic.getId());
		}
		pipe.zadd(RKEY_TOPIC_UPDATE + topic.getType(), System.currentTimeMillis(), topic.getId());
		pipe.zincrby(RKEY_USER_SCORE, 100, ""+userId);
		pipe.sync();
		for (Integer watcherId : globalWatcherIds) {
			if (watcherId != userId)
				pushUser(watcherId, "新帖:" + topic.getTitle(), topic.getId());
		}
		return _ok(topic.getId());
	}

	@Aop("redis")
	public CResult addReply(final String topicId, final TopicReply reply, final int userId) {
		if (userId < 1)
			return _fail("请先登录");
		if (reply == null || reply.getContent() == null || reply.getContent().trim().isEmpty()) {
			return _fail("内容不能为空");
		}
		final String cnt = reply.getContent().trim();
		if (cnt.length() < 2 || cnt.length() > 10000) {
			return _fail("内容太长或太短了");
		}
		final Topic topic = daoNoContent.fetch(Topic.class, topicId); // TODO 改成只fetch出type属性
		if (topic == null) {
			return _fail("主题不存在");
		}
		reply.setTopicId(topicId);
		reply.setUserId(userId);
		reply.setContent(Toolkit.filteContent(reply.getContent()));
		dao.insert(reply);
		// 更新topic的时间戳
		Pipeline pipe = jedis().pipelined();
		if (topic.isTop()) {
			pipe.zadd(RKEY_TOPIC_TOP, reply.getCreateTime().getTime(), topicId);
		} else {
			pipe.zadd(RKEY_TOPIC_UPDATE + topic.getType(), reply.getCreateTime().getTime(), topicId);
		}
		pipe.zrem(RKEY_TOPIC_NOREPLY, topicId);
		if (topic.getTags() != null) {
			for (String tag : topic.getTags()) {
				pipe.zadd(RKEY_TOPIC_TAG+tag.toLowerCase().trim(), reply.getCreateTime().getTime(), topicId);
			}
		}
		pipe.hset(RKEY_REPLY_LAST, topicId, reply.getId());
		pipe.zincrby(RKEY_REPLY_COUNT, 1, topicId);
		pipe.zincrby(RKEY_USER_SCORE, 10, ""+userId);
		pipe.sync();
		
{'fraction_non_alphanumeric': 0.15541752933057282, 'fraction_numerical': 0.006073153899240856, 'mean_word_length': 4.791766586730615, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6646057', 'n_tokens_mistral': 5887, 'n_tokens_neox': 5623, 'n_words': 914}