«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 — вероятно, с использованием страниц, а не учетных записей.