Перейти к основному содержимому

Публикация вопросов StackExchange в Twitter с помощью Spring Social

· 6 мин. чтения

1. Введение

Это третья и последняя статья о небольшом сайд-проекте — боте, автоматически публикующем вопросы с различных сайтов Q&A StackExchange на специализированных аккаунтах (полный список в конце статьи).

В первой статье обсуждалось создание простого клиента для StackExchange REST API. Во второй статье мы настроили взаимодействие с Twitter с помощью Spring Social.

В этой статье будет описана заключительная часть реализации — часть, отвечающая за взаимодействие между клиентом Stackexchange и шаблоном TwitterTemplate .

2. Сервис твитов Stackexchange

Взаимодействие между клиентом Stackexchange, отображающим необработанные вопросы, и TwitterTemplate , полностью настроенным и способным публиковать твиты, представляет собой очень простую службу — 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 .

Сущность вопроса просто содержит:

  • id вопроса – 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» — и поскольку такого рода вопросы на самом деле появляются на нескольких сайтах из сети Stackexchange: StackOverflow , SuperUser и AskUbuntu , сначала проводится быстрый процесс выбора одного из этих сайтов, после чего вопрос в твиттере.

Наконец, задание 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;
}
}
}

Наконец, вопросы извлекаются из файла setup.properties и создаются заново:

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);
}
}

Этот простой процесс позволяет легко обновлять структуру БД — поскольку данные полностью стираются и полностью создаются заново, нет необходимости выполнять какую-либо реальную миграцию .

5. Полный список учетных записей

Полный список учетных записей Twitter :

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

В результате этого проекта создано и работает 14 аккаунтов в Твиттере, которые фокусируются на различных темах и производят малообъемный и, надеюсь, высококачественный контент (идеи для других тегов, которые заслуживают отдельного аккаунта в Твиттере, приветствуются в комментариях).