Tweeting StackExchange Questions with Spring Social – 用Spring Social推送StackExchange问题

最后修改: 2013年 5月 3日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

This is the third and final article about a small side-project – a bot that automatically tweets Questions from various Q&A StackExchange sites on specialized accounts (full list at the end of the article).

这是第三篇也是最后一篇关于一个小的副业项目的文章–一个自动在专门的账户上推送各种Q&A StackExchange网站上的问题的机器人(完整列表见文末)。

The first article discussed building a simple client for the StackExchange REST API. In the second article we set up the interaction with Twitter using Spring Social.

第一篇文章讨论了为StackExchange REST API构建简单客户端>。在第二篇文章中,我们使用Spring Social设置了与Twitter的互动。

This article will describe the final part of the implementation – the part responsible with the interaction between the Stackexchange client and the TwitterTemplate.

这篇文章将描述实现的最后部分–负责Stackexchange客户端和TwitterTemplate之间的交互。

2. The Tweet Stackexchange Service

2.Tweet Stackexchange服务

The interaction between the Stackexchange Client – exposing the raw Questions, and the TwitterTemplate – fully set up and able to tweet – is a very simple service – the TweetStackexchangeService. The API published by this is:

Stackexchange客户端–暴露原始问题,和TwitterTemplate–完全设置好并能发推文–之间的互动是一个非常简单的服务–TweetStackexchangeService。这所发布的API是。

public void tweetTopQuestionBySite(String site, String twitterAccount){ ... }
public void tweetTopQuestionBySiteAndTag(String site, String twitterAccount, String tag){ ... }

The functionality is simple – these APIs will keep reading Questions from the Stackexchange REST API (through the client), until one is found that has not been tweeted before on that particular account.

功能很简单–这些API将不断从Stackexchange REST API(通过客户端)读取问题,直到找到一个以前没有在该特定账户上发过推的问题。

When that question is found, it is tweeted via the TwitterTemplate corresponding to that account, and a very simple Question entity is saved locally. This entity is only storing the id of the Question and the Twitter Account that it has been tweeted on.

当找到这个问题时,它就会通过与该账户相对应的TwitterTemplate被推送,并在本地保存一个非常简单的Question实体。这个实体只存储问题的ID和被推送的Twitter账户。

For example the following question:

例如以下问题。

Binding a list in @RequestParam

在@RequestParam中绑定一个列表

Has been tweeted on the SpringTip account.

已经在SpringTip账户上发布了推文。

The Question entity simply contains:

问题实体仅仅包含。

  • the id of the Question – 4596351 in this case
  • the Twitter Account on which the question has been tweeted – SpringAtSO
  • the Stackexcange Site from which the question originates – Stackoverflow

We need to keep track of this information so that we know which questions have already been tweeted and which haven’t.

我们需要跟踪这些信息,以便我们知道哪些问题已经在推特上发布,哪些还没有。

3. The Scheduler

3.调度器

The scheduler makes use of the Spring’s scheduled task capabilities – these are enabled via the Java configuration:

调度器利用了Spring的计划任务功能–这些功能通过Java配置启用。

@Configuration
@EnableScheduling
public class ContextConfig {
   //
}

The actual scheduler is relativelly simple:

实际的调度程序相对简单。

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

There are two tweet operations configured above – one tweets from StackOverflow questions which are tagged with “clojure” on the Best Of Clojure twitter account.

上面配置了两种推文操作–一种是来自StackOverflow问题的推文,这些问题在Best Of Clojure twitter账户上被标记为 “clojure” 。

The other operation tweets questions tagged with “bash” – and because these kind of questions actually appear on multiple sites from the Stackexchange network: StackOverflow, SuperUser and AskUbuntu, there is first a quick selection process to pick one of these sites, after which the question is tweeted.

另一个操作是推送标记为 “bash “的问题–而且因为这类问题实际上出现在Stackexchange网络的多个网站上。StackOverflow, SuperUserAskUbuntu,首先有一个快速的选择过程,从这些网站中挑选一个,之后将问题推送出去。

Finally, the cron job is scheduled to run at 1 AM and 5 AM each day.

最后,cron作业被安排在每天凌晨1点和5点运行

4. Setup

4.设置

This being a pet project, it started out with a very simple database structure – it’s still simple now, but it was even more so. So one of the main goals was to be able to easily change the database structure – there are of course several tools for database migrations, but they’re all overkill for such a simple project.

作为一个宠物项目,它一开始有一个非常简单的数据库结构–它现在仍然很简单,但以前更简单。因此,主要目标之一是能够轻松地改变数据库结构–当然也有数据库迁移的几个工具,但它们对于这样一个简单的项目来说都是多余的。

So I decided to keep setup data in a simple text format – that is going to be updated semi-automatically.

因此,我决定将设置数据保存在一种简单的文本格式中 – 这将会被半自动地更新。

Setup has two main steps:

设置有两个主要步骤。

  • the ids of the questions tweeted on each twitter account are retrieved and stored in a text file
  • the database schema is dropped and the application restarted – this will create the schema again and setup all data from the text file back into the new database

4.1. The Raw Setup Data

4.1.原始设置数据

The process of retrieving the data the existing database is simple enough with JDBC; first we define a RowMapper:

用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 "";
   }
}

This will build a list of Questions for each Twitter account.

这将为每个Twitter账户建立一个问题列表。

Next, we’re going to use this in a simple test:

接下来,我们将在一个简单的测试中使用这个。

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

After retrieving the Questions for an account, the test will simply list them out; for example:

检索到一个账户的问题后,测试将简单地列出它们;例如。

SpringAtSO=3652090,1079114,5908466,...

4.2. Restoring the Setup Data

4.2.恢复设置数据

The lines of data generated by the previous step are stored in a setup.properties file which is made available to Spring:

上一步生成的数据行被存储在setup.properties文件中,该文件被提供给Spring使用。

@Configuration
@PropertySource({ "classpath:setup.properties" })
public class PersistenceJPAConfig {
   //
}

When the application starts up, the setup process is performed. This simple process uses a Spring ApplicationListener, listening on a ContextRefreshedEvent:

当应用程序启动时,会进行设置过程。这个简单的过程使用一个Spring ApplicationListener,监听ContextRefreshedEvent

@Component
public class StackexchangeSetup implements ApplicationListener<ContextRefreshedEvent> {
    private boolean setupDone;

    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (!setupDone) {
            recreateAllQuestionsOnAllTwitterAccounts();
            setupDone = true;
        }
    }
}

Finally, the questions are retrieved from the setup.properties file and recreated:

最后,从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);
   }
}

This simple process allows easy updates of the DB structure – since the data is fully erased and fully re-created, there is no need to do any actual migration whatsoever.

这个简单的过程允许轻松更新数据库结构–因为数据被完全清除并完全重新创建,所以不需要做任何实际迁移

5. Full List of Accounts

5.完整的账户清单

The full list of Twitter accounts is:

完整的Twitter账户列表是。

  • SpringTipSpring questions from StackOverflow
  • JavaTopSO Java questions from StackOverflow
  • RESTDailyREST questions from StackOverflow
  • BestJPA JPA questions from StackOverflow
  • MavenFactMaven questions from StackOverflow
  • BestGit Git questions from StackOverflow
  • AskUbuntuBest AskUbuntu best overal questions (all topics)
  • ServerFaultBestServerFault best questions (all topics)
  • BestBashbest Bash questions from StackOverflow, ServerFault and AskUbuntu
  • ClojureFact – Clojure questions from StackOverflow
  • ScalaFact – Scala questions from StackOverflow
  • EclipseFacts – Eclipse questions from StackOverflow
  • jQueryDaily – jQuery questions from StackOverflow
  • BestAlgorithms – Algorithm questions from StackOverflow

2 tweets per day are created on each of these accounts, with the highest rated questions on their specific subject.

这些账户中的每个账户每天创建2条推文,并在其特定的主题上提供评分最高的问题。

6. Conclusion

6.结论

This third article finishes the series about integrating with StackOverflow and other StackExchange sites to retrieve questions through their REST API, and integrating with Twitter and Spring Social to tweet these questions. A potential direction worth exporing is doing the same with Google Plus – probably using Pages, not accounts.

这第三篇文章完成了与StackOverflow和其他StackExchange网站的整合,通过他们的REST API检索问题,并与Twitter和Spring Social整合来推送这些问题。一个值得探讨的潜在方向是与Google Plus做同样的事情–可能使用页面,而不是账户。

14 Twitter accounts are up and running as a result of this project – focusing on various topics and producing low volume and hopefully high quality content (ideas for other tags that deserves their own Twitter account are welcome in the comments).

14个推特账户作为这个项目的结果已经建立并运行–专注于各种主题,并生产低数量的、希望是高质量的内容(欢迎在评论中提出值得自己的推特账户的其他标签的想法)。