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
- 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 — вероятно, с использованием страниц, а не учетных записей.
В результате этого проекта создано и работает 14 аккаунтов в Твиттере, которые фокусируются на различных темах и производят малообъемный и, надеюсь, высококачественный контент (идеи для других тегов, которые заслуживают отдельного аккаунта в Твиттере, приветствуются в комментариях).