«1. Введение
Это третья и последняя статья о небольшом сайд-проекте — боте, который автоматически публикует вопросы с различных сайтов вопросов и ответов StackExchange на специализированных аккаунтах (полный список в конце статьи).
В первой статье обсуждалось создание простого клиента для StackExchange REST API. Во второй статье мы настроили взаимодействие с Twitter с помощью Spring Social.
В этой статье будет описана заключительная часть реализации — часть, отвечающая за взаимодействие между клиентом Stackexchange и TwitterTemplate.
2. Сервис Stackexchange для твитов
Взаимодействие между клиентом Stackexchange, предоставляющим необработанные вопросы, и шаблоном Twitter, полностью настроенным и способным публиковать твиты, представляет собой очень простой сервис. Служба TweetStackexchangeService. API, опубликованный этим:
public void tweetTopQuestionBySite(String site, String twitterAccount){ ... }
public void tweetTopQuestionBySiteAndTag(String site, String twitterAccount, String tag){ ... }
Функциональность проста — эти API будут продолжать читать вопросы из API Stackexchange REST (через клиент), пока не будет найден вопрос, который ранее не публиковался в этом конкретном Счет.
Когда этот вопрос найден, он публикуется в Твиттере через TwitterTemplate, соответствующий этой учетной записи, и локально сохраняется очень простой объект вопроса. Этот объект хранит только идентификатор вопроса и учетную запись Twitter, в которой он был опубликован.
Например, следующий вопрос:
Привязка списка в @RequestParam
Был опубликован в твиттере учетной записи SpringTip.
Сущность вопроса просто содержит:
-
идентификатор вопроса — 4596351 в данном случае учетную запись Twitter, в которой вопрос был опубликован в Твиттере — SpringAtSO сайт Stackexcange, с которого возник вопрос — Stackoverflow
Нам нужно отслеживать эту информацию, чтобы знать, какие вопросы уже публиковались в Твиттере, а какие нет.
3. Планировщик
Планировщик использует возможности запланированных задач Spring – они включаются через конфигурацию Java:
@Configuration
@EnableScheduling
public class ContextConfig {
//
}
Фактический планировщик относительно прост:
@Component
@Profile(SpringProfileUtil.DEPLOYED)
public class TweetStackexchangeScheduler {
@Autowired
private TweetStackexchangeService service;
// API
@Scheduled(cron = "0 0 1,5 * * *")
public void tweetStackExchangeTopQuestion() throws JsonProcessingException, IOException {
service.tweetTopQuestionBySiteAndTag("StackOverflow", Tag.clojure.name(), "BestClojure", 1);
String randomSite = StackexchangeUtil.pickOne("SuperUser", "StackOverflow");
service.tweetTopQuestionBySiteAndTag(randomSite, Tag.bash.name(), "BestBash", 1);
}
}
Есть две операции с твитами, настроенные выше: одна твитит из вопросов StackOverflow, которые помечены «clojure» в твиттер-аккаунте Best Of Clojure.
Другая операция отправляет в Твиттере вопросы, помеченные «bash». эти сайты, после чего вопрос в твиттере.
Наконец, задание cron запланировано на 1:00 и 5:00 каждый день.
4. Установка
Это был небольшой проект, он начинался с очень простой структуры базы данных – она по-прежнему проста и сейчас, но стала еще более простой. Таким образом, одной из основных целей была возможность легко изменить структуру базы данных — конечно, есть несколько инструментов для миграции баз данных, но все они избыточны для такого простого проекта.
Поэтому я решил сохранить данные настройки в простом текстовом формате — он будет обновляться полуавтоматически.
Установка состоит из двух основных шагов:
-
идентификаторы вопросов, опубликованных в твиттере для каждой учетной записи Twitter, извлекаются и сохраняются в текстовом файле схема базы данных удаляется, а приложение перезапускается – это снова создаст схему и настроит все данные из текстового файла обратно в новую базу данных
4.1. Необработанные данные настройки
Процесс извлечения данных из существующей базы данных достаточно прост с JDBC; сначала мы определяем RowMapper:
class TweetRowMapper implements RowMapper<String> {
private Map<String, List<Long>> accountToQuestions;
public TweetRowMapper(Map<String, List<Long>> accountToQuestions) {
super();
this.accountToQuestions = accountToQuestions;
}
public String mapRow(ResultSet rs, int line) throws SQLException {
String questionIdAsString = rs.getString("question_id");
long questionId = Long.parseLong(questionIdAsString);
String account = rs.getString("account");
if (accountToQuestions.get(account) == null) {
accountToQuestions.put(account, Lists.<Long> newArrayList());
}
accountToQuestions.get(account).add(questionId);
return "";
}
}
Это создаст список вопросов для каждой учетной записи Twitter.
Далее мы собираемся использовать это в простом тесте:
@Test
public void whenQuestionsAreRetrievedFromTheDB_thenNoExceptions() {
Map<String, List<Long>> accountToQuestionsMap = Maps.newHashMap();
jdbcTemplate.query
("SELECT * FROM question_tweet;", new TweetRowMapper(accountToQuestionsMap));
for (String accountName : accountToQuestionsMap.keySet()) {
System.out.println
(accountName + "=" + valuesAsCsv(accountToQuestionsMap.get(accountName)));
}
}
После получения вопросов для учетной записи тест просто перечислит их; например:
SpringAtSO=3652090,1079114,5908466,...
4.2. Восстановление данных настройки
Строки данных, сгенерированные на предыдущем шаге, сохраняются в файле setup.properties, доступном для Spring:
@Configuration
@PropertySource({ "classpath:setup.properties" })
public class PersistenceJPAConfig {
//
}
Когда приложение запускается, выполняется процесс установки. В этом простом процессе используется Spring ApplicationListener, прослушивающий ContextRefreshedEvent:
@Component
public class StackexchangeSetup implements ApplicationListener<ContextRefreshedEvent> {
private boolean setupDone;
public void onApplicationEvent(ContextRefreshedEvent event) {
if (!setupDone) {
recreateAllQuestionsOnAllTwitterAccounts();
setupDone = true;
}
}
}
«
private void recreateAllQuestionsOnTwitterAccount(String twitterAccount) {
String tweetedQuestions = env.getProperty(twitterAccount.name();
String[] questionIds = tweetedQuestions.split(",");
recreateQuestions(questionIds, twitterAccount);
}
void recreateQuestions(String[] questionIds, String twitterAccount) {
List<String> stackSitesForTwitterAccount = twitterAccountToStackSites(twitterAccount);
String site = stackSitesForTwitterAccount.get(0);
for (String questionId : questionIds) {
QuestionTweet questionTweet = new QuestionTweet(questionId, twitterAccount, site);
questionTweetDao.save(questionTweet);
}
}
Наконец, вопросы извлекаются из файла setup.properties и создаются заново:
Этот простой процесс позволяет легко обновлять структуру БД — поскольку данные полностью стираются и полностью создаются заново, нет необходимости выполнять какую-либо фактическую миграцию.
5. Полный список учетных записей
-
Полный список учетных записей Twitter:
SpringTip — вопросы Spring от StackOverflow JavaTopSO — вопросы Java от StackOverflow RESTDaily — вопросы REST от StackOverflow BestJPA — « Вопросы JPA от StackOverflow MavenFact — вопросы Maven от StackOverflow BestGit — вопросы Git от StackOverflow AskUbuntuBest — лучшие общие вопросы AskUbuntu (все темы) ServerFaultBest — лучшие вопросы ServerFault (все темы) BestBash — лучшие вопросы Bash вопросы из StackOverflow, ServerFault и AskUbuntu ClojureFact — вопросы по Clojure из StackOverflow ScalaFact — вопросы по Scala из StackOverflow EclipseFacts — вопросы Eclipse из StackOverflow jQueryDaily — вопросы jQuery из StackOverflow BestAlgorithms — вопросы по алгоритмам из StackOverflow
На каждой из этих учетных записей создается 2 твита в день с вопросами с наивысшим рейтингом по конкретной теме.
6. Заключение
Эта третья статья завершает серию статей об интеграции со StackOverflow и другими сайтами StackExchange для получения вопросов через их REST API, а также об интеграции с Twitter и Spring Social для публикации этих вопросов в Твиттере. Потенциальное направление, которое стоит исследовать, — это сделать то же самое с Google Plus — вероятно, с использованием страниц, а не учетных записей.