Schedule Post mit Reddit zu Reddit

Planen Sie den Post mit Spring nach Reddit

1. Überblick

Inearlierparts dieser Fallstudie haben wir eine einfache App und einen OAuth-Authentifizierungsprozess mit der Reddit-API eingerichtet.

Lassen Sie uns jetzt etwas Nützliches mit Reddit erstellen - Unterstützung fürscheduling Posts für letzteres.

2. DieUser und die Post

Zuerst erstellen wir die beiden Hauptentitäten -User undPost. DieUser verfolgen den Benutzernamen sowie einige zusätzliche Oauth-Informationen:

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(nullable = false)
    private String username;

    private String accessToken;
    private String refreshToken;
    private Date tokenExpiration;

    private boolean needCaptcha;

    // standard setters and getters
}

Als nächstes - die EntitätPost - enthält die Informationen, die zum Senden eines Links an Reddit erforderlich sind:title,URL,subreddit usw.

@Entity
public class Post {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(nullable = false) private String title;
    @Column(nullable = false) private String subreddit;
    @Column(nullable = false) private String url;
    private boolean sendReplies;

    @Column(nullable = false) private Date submissionDate;

    private boolean isSent;

    private String submissionResponse;

    @ManyToOne
    @JoinColumn(name = "user_id", nullable = false)
    private User user;
    // standard setters and getters
}

3. Die Persistenzschicht

Wir werdenSpring Data JPA for persistence verwenden, daher gibt es hier nicht viel zu sehen, außer den bekannten Schnittstellendefinitionen für unsere Repositorys:

  • UserRepository:

public interface UserRepository extends JpaRepository {

    User findByUsername(String username);

    User findByAccessToken(String token);
}
  • PostRepository:

public interface PostRepository extends JpaRepository {

    List findBySubmissionDateBeforeAndIsSent(Date date, boolean isSent);

    List findByUser(User user);
}

4. Ein Scheduler

Für die Planungsaspekte der App werden wir auch die sofort einsatzbereite Spring-Unterstützung nutzen.

Wir definieren eine Aufgabe, die jede Minute ausgeführt werden soll. Dies sucht einfach nachPosts that are due to be submitted für Reddit:

public class ScheduledTasks {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    private OAuth2RestTemplate redditRestTemplate;

    @Autowired
    private PostRepository postReopsitory;

    @Scheduled(fixedRate = 1 * 60 * 1000)
    public void reportCurrentTime() {
        List posts =
          postReopsitory.findBySubmissionDateBeforeAndIsSent(new Date(), false);
        for (Post post : posts) {
            submitPost(post);
        }
    }

    private void submitPost(Post post) {
        try {
            User user = post.getUser();
            DefaultOAuth2AccessToken token =
              new DefaultOAuth2AccessToken(user.getAccessToken());
            token.setRefreshToken(new DefaultOAuth2RefreshToken((user.getRefreshToken())));
            token.setExpiration(user.getTokenExpiration());
            redditRestTemplate.getOAuth2ClientContext().setAccessToken(token);

            UsernamePasswordAuthenticationToken userAuthToken =
              new UsernamePasswordAuthenticationToken(
              user.getUsername(), token.getValue(),
              Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")));
            SecurityContextHolder.getContext().setAuthentication(userAuthToken);

            MultiValueMap param = new LinkedMultiValueMap();
            param.add("api_type", "json");
            param.add("kind", "link");
            param.add("resubmit", "true");
            param.add("then", "comments");
            param.add("title", post.getTitle());
            param.add("sr", post.getSubreddit());
            param.add("url", post.getUrl());
            if (post.isSendReplies()) {
                param.add(RedditApiConstants.SENDREPLIES, "true");
            }

            JsonNode node = redditRestTemplate.postForObject(
              "https://oauth.reddit.com/api/submit", param, JsonNode.class);
            JsonNode errorNode = node.get("json").get("errors").get(0);
            if (errorNode == null) {
                post.setSent(true);
                post.setSubmissionResponse("Successfully sent");
                postReopsitory.save(post);
            } else {
                post.setSubmissionResponse(errorNode.toString());
                postReopsitory.save(post);
            }
        } catch (Exception e) {
            logger.error("Error occurred", e);
        }
    }
}

Beachten Sie, dass im Falle eines Fehlers der Beitrag nicht alssent - alsothe next cycle will try to submit it again after one minute markiert wird.

5. Der Anmeldevorgang

Bei der neuen Benutzerentität, die einige sicherheitsspezifische Informationen enthält, müssenmodify our simple login process to store that information:

@RequestMapping("/login")
public String redditLogin() {
    JsonNode node = redditRestTemplate.getForObject(
      "https://oauth.reddit.com/api/v1/me", JsonNode.class);
    loadAuthentication(node.get("name").asText(), redditRestTemplate.getAccessToken());
    return "redirect:home.html";
}

UndloadAuthentication():

private void loadAuthentication(String name, OAuth2AccessToken token) {
    User user = userReopsitory.findByUsername(name);
    if (user == null) {
        user = new User();
        user.setUsername(name);
    }

    if (needsCaptcha().equalsIgnoreCase("true")) {
        user.setNeedCaptcha(true);
    } else {
        user.setNeedCaptcha(false);
    }

    user.setAccessToken(token.getValue());
    user.setRefreshToken(token.getRefreshToken().getValue());
    user.setTokenExpiration(token.getExpiration());
    userReopsitory.save(user);

    UsernamePasswordAuthenticationToken auth =
      new UsernamePasswordAuthenticationToken(user, token.getValue(),
      Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")));
    SecurityContextHolder.getContext().setAuthentication(auth);
}

Beachten Sie, wie der Benutzer automatisch erstellt wird, wenn er noch nicht vorhanden ist. Dadurch wird beim ersten Anmelden beim "Anmelden mit Reddit" ein lokaler Benutzer im System erstellt.

6. Die Zeitplanseite

Weiter - Schauen wir uns die Seite an, auf der neue Posts geplant werden können:

@RequestMapping("/postSchedule")
public String showSchedulePostForm(Model model) {
    boolean isCaptchaNeeded = getCurrentUser().isCaptchaNeeded();
    if (isCaptchaNeeded) {
        model.addAttribute("msg", "Sorry, You do not have enought karma");
        return "submissionResponse";
    }
    return "schedulePostForm";
}
private User getCurrentUser() {
    return (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}

schedulePostForm.html:

Beachten Sie, wie wir das Captcha überprüfen müssen. Dies liegt daran, dass -if the user has less than 10 karma - sie keinen Beitrag planen können, ohne das Captcha auszufüllen.

7. POSTING

Wenn das Zeitplanformular gesendet wird, müssenthe post info is simply validated and persisted später vom Planer abgeholt werden:

@RequestMapping(value = "/api/scheduledPosts", method = RequestMethod.POST)
@ResponseBody
public Post schedule(@RequestBody Post post) {
    if (submissionDate.before(new Date())) {
        throw new InvalidDateException("Scheduling Date already passed");
    }

    post.setUser(getCurrentUser());
    post.setSubmissionResponse("Not sent yet");
    return postReopsitory.save(post);
}

8. Die Liste der geplanten Beiträge

Implementieren wir jetzt eine einfache REST-API zum Abrufen der geplanten Posts:

@RequestMapping(value = "/api/scheduledPosts")
@ResponseBody
public List getScheduledPosts() {
    User user = getCurrentUser();
    return postReopsitory.findByUser(user);
}

Und ein einfacher und schneller Weg zudisplay these scheduled posts on the front end:

Post titleSubmission Date

9. Bearbeiten Sie einen geplanten Beitrag

Weiter - mal sehen, wie wir einen geplanten Beitrag bearbeiten können.

Wir beginnen mit dem Front-End - zunächst der sehr einfachen MVC-Operation:

@RequestMapping(value = "/editPost/{id}", method = RequestMethod.GET)
public String showEditPostForm() {
    return "editPostForm";
}

Nach der einfachen API verbraucht das Front-End Folgendes:

Schauen wir uns nun dieREST API an:

@RequestMapping(value = "/api/scheduledPosts/{id}", method = RequestMethod.GET)
@ResponseBody
public Post getPost(@PathVariable("id") Long id) {
    return postReopsitory.findOne(id);
}

@RequestMapping(value = "/api/scheduledPosts/{id}", method = RequestMethod.PUT)
@ResponseStatus(HttpStatus.OK)
public void updatePost(@RequestBody Post post, @PathVariable Long id) {
    if (post.getSubmissionDate().before(new Date())) {
        throw new InvalidDateException("Scheduling Date already passed");
    }
    post.setUser(getCurrentUser());
    postReopsitory.save(post);
}

10. Unschedule/Delete a Post

Wir bieten auch einen einfachen Löschvorgang für alle geplanten Posts an:

@RequestMapping(value = "/api/scheduledPosts/{id}", method = RequestMethod.DELETE)
@ResponseStatus(HttpStatus.OK)
public void deletePost(@PathVariable("id") Long id) {
    postReopsitory.delete(id);
}

So nennen wir es von Client-Seite:

Delete

11. Fazit

In diesem Teil unserer Reddit-Fallstudie haben wir mithilfe der Reddit-API - Planen von Posts - die erste nicht triviale Funktionalität erstellt.

Dies ist eine äußerst nützliche Funktion für einen ernsthaften Reddit-Benutzer, insbesondere unter Berücksichtigung vonhow time-sensitive Reddit submissions are.

Als Nächstes werden wir eine noch hilfreichere Funktionalität entwickeln, um die Aktualisierung von Inhalten auf Reddit zu erleichtern - Vorschläge für maschinelles Lernen.

Diefull implementation dieses Tutorials finden Sie inthe github project - dies ist ein Eclipse-basiertes Projekt, daher sollte es einfach zu importieren und auszuführen sein.