1. 序章

これは、小さなサイドプロジェクトに関する3番目で最後の記事です。これは、特殊なアカウントでさまざまなQ&A StackExchangeサイトからの質問を自動的にツイートするボットです(記事の最後にある完全なリスト)。

最初の記事では、StackExchangeRESTAPI用のシンプルクライアントの構築について説明しました。 2番目の記事では、SpringSocialを使用してTwitterとのインタラクションを設定しました。

この記事では、実装の最後の部分、つまり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は、特定のアカウントでの前にツイートされていないことが判明するまで、Stackexchange REST APIから(クライアントを介して)質問を読み続けます。

その質問が見つかると、そのアカウントに対応するTwitterTemplate を介してツイートされ、非常に単純なQuestionエンティティがローカルに保存されます。 このエンティティは、質問のIDとツイートされた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);
   }
}

上記で構成された2つのツイート操作があります。1つはBestOfClojureTwitterアカウントで「clojure」とタグ付けされたStackOverflowの質問からのツイートです。

他の操作は、「bash」でタグ付けされた質問をツイートします。これらの種類の質問は、実際にはStackexchangeネットワークの複数のサイトに表示されるためです: StackOverflow SuperUser AskUbuntu 、最初にこれらのサイトの1つを選択するためのクイック選択プロセスがあり、その後、質問がツイートされます。

最後に、cronジョブは毎日午前1時と午前5時にを実行するようにスケジュールされています。

4. 設定

これはペットのプロジェクトであり、非常に単純なデータベース構造から始まりました。現在でも単純ですが、さらに単純です。 したがって、主な目標の1つは、データベース構造を簡単に変更できるようにすることでした。もちろん、データベース移行用のいくつかのツールがありますが、そのような単純なプロジェクトではすべてやり過ぎです。

そこで、セットアップデータをシンプルなテキスト形式で保持することにしました。これは半自動で更新されます。

セットアップには2つの主要なステップがあります。

  • 各Twitterアカウントでツイートされた質問のIDが取得され、テキストファイルに保存されます
  • データベーススキーマが削除され、アプリケーションが再起動されます。これにより、スキーマが再度作成され、テキストファイルから新しいデータベースにすべてのデータがセットアップされます。

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

この単純なプロセスにより、DB構造を簡単に更新できます。データは完全に消去され、完全に再作成されるため、実際の移行を行う必要はありません。

5. アカウントの完全なリスト

Twitterアカウントの完全なリストは次のとおりです。

これらのアカウントのそれぞれで1日あたり2つのツイートが作成され、特定の主題について最も評価の高い質問が付けられます。

6. 結論

この3番目の記事は、StackOverflowや他のStackExchangeサイトと統合してREST APIを介して質問を取得し、TwitterやSpringSocialと統合してこれらの質問をツイートするシリーズを終了します。 検討する価値のある潜在的な方向性は、Google Plusでも同じことを行うことです。おそらく、アカウントではなくページを使用します。

14のTwitterアカウントは、このプロジェクトの結果として稼働しています。さまざまなトピックに焦点を当て、少量で高品質のコンテンツを作成しています(独自のTwitterアカウントに値する他のタグのアイデアはコメントで歓迎されます) 。