1. Обзор
В более ранних частях этого примера мы создали простое приложение и процесс проверки подлинности OAuth с помощью API Reddit.
Давайте сейчас построим что-то полезное с Reddit – поддержку планирование Сообщений для последнего.
2. Пользователь и почта
Во-первых, давайте создадим два основных сущности – Пользователь и Почтовые . Пользователь будет отслеживать имя пользователя плюс некоторые дополнительные Oauth информация:
@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 }
Далее – Почтовые лицо – проведение информации, необходимой для отправки ссылки на Reddit: титульный , URL , subreddit , … и так далее.
@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. Слой настойчивости
Мы будем использовать Весенние данные JPA для сохранения , так что не так много, чтобы посмотреть здесь, кроме известных определений интерфейса для наших хранилищ:
- ПользовательРепозиторий:
public interface UserRepository extends JpaRepository{ User findByUsername(String username); User findByAccessToken(String token); }
- Пострепозиторий:
public interface PostRepository extends JpaRepository{ List findBySubmissionDateBeforeAndIsSent(Date date, boolean isSent); List findByUser(User user); }
4. Планировщик
Для аспектов планирования приложения, мы также собираемся сделать хорошее использование вне коробки Поддержка Весна.
Мы определяем задачу для запуска каждую минуту; это будет просто искать Сообщения, которые должны быть представлены на 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() { Listposts = 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); } } }
Обратите внимание, что в случае, если что-то пойдет не так, Сообщение не будет помечено как отправлено – так следующий цикл будет пытаться представить его снова через одну минуту .
5. Процесс входа
С новой сущностью пользователя, держащей некоторую конкретную информацию о безопасности, мы должны изменить наш простой процесс входа, чтобы хранить эту информацию :
@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"; }
И нагрузкаАкенция () :
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); }
Обратите внимание, как пользователь автоматически создается, если он еще не существует. Это делает процесс “Войти с Reddit” создать локального пользователя в системе на первом входе.
6. Страница расписания
Далее – давайте посмотрим на страницу, которая позволяет планировать новые сообщения:
@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(); }
расписаниеПостФорма.html :
Обратите внимание, как мы должны проверить Captcha. Это потому, что – если пользователь имеет менее 10 карма- – они не могут запланировать должность без заполнения Captcha.
7. POSTing
При отправлении формы Расписание информация о должности просто проверяется и сохраняется быть подобраны планировщик позже:
@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. Список запланированных сообщений
Давайте теперь реализуем простой API REST для получения запланированных сообщений у нас есть:
@RequestMapping(value = "/api/scheduledPosts") @ResponseBody public ListgetScheduledPosts() { User user = getCurrentUser(); return postReopsitory.findByUser(user); }
И простой, быстрый способ отображать эти запланированные сообщения на переднем конце :
Post title | Submission Date |
---|
9. Редактировать запланированный пост
Далее – давайте посмотрим, как мы можем редактировать запланированный пост.
Начнем с переднего конца – во-первых, очень простой операции MVC:
@RequestMapping(value = "/editPost/{id}", method = RequestMethod.GET) public String showEditPostForm() { return "editPostForm"; }
После простого API, вот передний конец потребления его:
Теперь давайте посмотрим на ОТДЫХ API :
@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. Внеплановые/Удалить сообщение
Мы также предоставим простую операцию удаления для любого из запланированных сообщений:
@RequestMapping(value = "/api/scheduledPosts/{id}", method = RequestMethod.DELETE) @ResponseStatus(HttpStatus.OK) public void deletePost(@PathVariable("id") Long id) { postReopsitory.delete(id); }
Вот как мы называем это со стороны клиента:
11. Заключение
В этой части нашего кейс-исследования Reddit мы создали первый нетривиальный бит функциональности с помощью API Reddit – планирования сообщений.
Это супер-полезная функция для серьезного пользователя Reddit, особенно с учетом как чувствительные к времени материалы Reddit .
Далее – мы будем строить еще более полезную функциональность, чтобы помочь с получением содержания upvoted на Reddit – машинного обучения предложения.
полная реализация этого учебника можно найти в проект github – это проект на основе Eclipse, поэтому он должен быть легким для импорта и запуска, как она есть.