1. Обзор
Давайте продолжим наше текущее тематическое исследование веб-приложения Reddit с новым раундом улучшений с целью сделать приложение более удобным для пользователя и простым в использовании.
2. Разбиение на страницы Запланированных Сообщений
Во – первых , давайте перечислим запланированные сообщения с разбиением на страницы , чтобы все это было легче просматривать и понимать.
2.1. Операции С Разбиением На Страницы
Мы будем использовать данные Spring для создания нужной нам операции, хорошо используя интерфейс Pageable для извлечения запланированных сообщений пользователя:
public interface PostRepository extends JpaRepository{ Page findByUser(User user, Pageable pageable); }
И вот наш метод контроллера получить запланированные сообщения() :
private static final int PAGE_SIZE = 10; @RequestMapping("/scheduledPosts") @ResponseBody public ListgetScheduledPosts( @RequestParam(value = "page", required = false) int page) { User user = getCurrentUser(); Page posts = postReopsitory.findByUser(user, new PageRequest(page, PAGE_SIZE)); return posts.getContent(); }
2.2. Отображение Сообщений С Разбивкой По Страницам
Теперь – давайте реализуем простой контроль разбиения на страницы в интерфейсе:
Post title |
---|
И вот как мы загружаем страницы с помощью простого jQuery:
$(function(){ loadPage(0); }); var currentPage = 0; function loadNext(){ loadPage(currentPage+1); } function loadPrev(){ loadPage(currentPage-1); } function loadPage(page){ currentPage = page; $('table').children().not(':first').remove(); $.get("api/scheduledPosts?page="+page, function(data){ $.each(data, function( index, post ) { $('.table').append(''); }); }); } '+post.title+'
По мере продвижения вперед эта ручная таблица будет быстро заменена более зрелым табличным плагином, но пока это работает просто отлично.
3. Покажите страницу входа для пользователей, не вошедших в систему
Когда пользователь обращается к корневому каталогу, они должны получать разные страницы, если они вошли в систему или нет .
Если пользователь вошел в систему, он должен увидеть свою домашнюю страницу/панель мониторинга. Если они не вошли в систему – они должны увидеть страницу входа в систему:
@RequestMapping("/") public String homePage() { if (SecurityContextHolder.getContext().getAuthentication() != null) { return "home"; } return "index"; }
4. Дополнительные параметры для повторной отправки
Удаление и повторная отправка сообщений в Reddit-это полезная и высокоэффективная функция. Тем не менее, мы хотим быть осторожными с этим и иметь полный контроль над тем, когда мы должны и когда мы не должны этого делать.
Например, мы можем не захотеть удалять сообщение, если в нем уже есть комментарии. В конце концов, комментарии важны, и мы хотим уважать платформу и людей, комментирующих этот пост.
Итак, это первая небольшая, но очень полезная функция, которую мы добавим – новая опция, которая позволит нам удалять сообщение только в том случае, если в нем нет комментариев.
Еще один очень интересный вопрос, на который нужно ответить: если сообщение повторно отправляется сколько угодно раз, но все еще не получает необходимой тяги – оставляем ли мы его включенным после последней попытки или нет? Ну, как и на все интересные вопросы, ответ здесь – “это зависит”. Если это обычный пост, мы могли бы просто назвать его днем и оставить его. Однако, если это очень важный пост, и мы действительно хотим убедиться, что он получит некоторую поддержку, мы можем удалить его в конце.
Итак, это вторая небольшая, но очень удобная функция, которую мы создадим здесь.
Наконец, как насчет спорных постов? Сообщение может иметь 2 голоса на reddit, потому что там оно должно иметь положительные голоса, или потому, что оно имеет 100 положительных и 98 отрицательных голосов. Первый вариант означает, что он не получает тяги, в то время как второй означает, что он получает много тяги и что голосование разделено.
Итак, это третья небольшая функция, которую мы собираемся добавить – новая опция, позволяющая учитывать соотношение голосов вверх и вниз при определении того, нужно ли нам удалять сообщение или нет.
4.1. Почтовая Организация
Во-первых, нам нужно изменить наш Post объект:
@Entity public class Post { ... private int minUpvoteRatio; private boolean keepIfHasComments; private boolean deleteAfterLastAttempt; }
Вот 3 поля:
- min Upvote Ratio : Минимальное соотношение голосов, которого пользователь хочет достичь своим сообщением – соотношение голосов представляет, сколько % от общего числа голосов набирает ara,]
- keep If HasComments : Определите, хочет ли пользователь сохранить свой пост, если у него есть комментарии, несмотря на то, что он не набрал необходимого балла.
- удалить после последней попытки : Определите, хочет ли пользователь удалить сообщение после окончания последней попытки, не набрав необходимого балла.
4.2. Планировщик
Давайте теперь интегрируем эти интересные новые опции в планировщик:
@Scheduled(fixedRate = 3 * 60 * 1000) public void checkAndDeleteAll() { Listsubmitted = postReopsitory.findByRedditIDNotNullAndNoOfAttemptsAndDeleteAfterLastAttemptTrue(0); for (Post post : submitted) { checkAndDelete(post); } }
На более интересной части – фактическая логика checkAndDelete() :
private void checkAndDelete(Post post) { if (didIntervalPass(post.getSubmissionDate(), post.getTimeInterval())) { if (didPostGoalFail(post)) { deletePost(post.getRedditID()); post.setSubmissionResponse("Consumed Attempts without reaching score"); post.setRedditID(null); postReopsitory.save(post); } else { post.setNoOfAttempts(0); post.setRedditID(null); postReopsitory.save(post); } } }
И вот didPostGoalFail() реализация – проверка, не достигло ли сообщение заданной цели/результата :
private boolean didPostGoalFail(Post post) { PostScores postScores = getPostScores(post); int score = postScores.getScore(); int upvoteRatio = postScores.getUpvoteRatio(); int noOfComments = postScores.getNoOfComments(); return (((score < post.getMinScoreRequired()) || (upvoteRatio < post.getMinUpvoteRatio())) && !((noOfComments > 0) && post.isKeepIfHasComments())); }
Нам также необходимо изменить логику, которая извлекает информацию Post из Reddit, чтобы убедиться, что мы собираем больше данных:
public PostScores getPostScores(Post post) { JsonNode node = restTemplate.getForObject( "http://www.reddit.com/r/" + post.getSubreddit() + "/comments/" + post.getRedditID() + ".json", JsonNode.class); PostScores postScores = new PostScores(); node = node.get(0).get("data").get("children").get(0).get("data"); postScores.setScore(node.get("score").asInt()); double ratio = node.get("upvote_ratio").asDouble(); postScores.setUpvoteRatio((int) (ratio * 100)); postScores.setNoOfComments(node.get("num_comments").asInt()); return postScores; }
Мы используем простой объект value для представления результатов по мере их извлечения из API Reddit:
public class PostScores { private int score; private int upvoteRatio; private int noOfComments; }
Наконец, нам нужно изменить check И ReSubmit () , чтобы установить их успешно повторно отправленный идентификатор reddit в null :
private void checkAndReSubmit(Post post) { if (didIntervalPass(post.getSubmissionDate(), post.getTimeInterval())) { if (didPostGoalFail(post)) { deletePost(post.getRedditID()); resetPost(post); } else { post.setNoOfAttempts(0); post.setRedditID(null); postReopsitory.save(post); } } }
Обратите внимание, что:
- проверьте и удалите все() : выполняется каждые 3 минуты, чтобы узнать, израсходовали ли какие-либо сообщения свои попытки и могут быть удалены
- получить баллы за публикацию() : вернуть {балл за публикацию, соотношение голосов, количество комментариев}
4.3. Измените Страницу Расписания
Нам нужно добавить новые модификации в наш schedulePostForm.html :
5. Отправляйте по Электронной почте Важные Журналы
Затем мы реализуем быструю, но очень полезную настройку в нашей конфигурации обратной связи – отправка по электронной почте важных журналов ( ОШИБКА уровень) . Это, конечно, очень удобно, чтобы легко отслеживать ошибки на ранних этапах жизненного цикла приложения.
Во-первых, мы добавим несколько необходимых зависимостей к вашему pom.xml :
javax.activation activation 1.1.1 javax.mail 1.4.1
Затем мы добавим SMTPAppender к вашему logback.xml :
ERROR ACCEPT DENY smtp.example.com [email protected] [email protected] [email protected] password %logger{20} - %m
И это все – теперь развернутое приложение будет отправлять по электронной почте любую проблему, когда это произойдет.
6. Кэширование Субреддитов
Оказывается, автоматическое заполнение субреддитов дорого . Каждый раз, когда пользователь начинает вводить субреддит при планировании публикации – нам нужно нажать на API Reddit, чтобы получить эти субреддиты и показать пользователю некоторые предложения. Не идеально.
Вместо вызова API Reddit – мы просто кэшируем популярные субреддиты и используем их для автозаполнения.
6.1. Получение Субредитов
Во-первых, давайте извлекем самые популярные субреддиты и сохраним их в обычный файл:
public void getAllSubreddits() { JsonNode node; String srAfter = ""; FileWriter writer = null; try { writer = new FileWriter("src/main/resources/subreddits.csv"); for (int i = 0; i < 20; i++) { node = restTemplate.getForObject( "http://www.reddit.com/" + "subreddits/popular.json?limit=100&after=" + srAfter, JsonNode.class); srAfter = node.get("data").get("after").asText(); node = node.get("data").get("children"); for (JsonNode child : node) { writer.append(child.get("data").get("display_name").asText() + ","); } try { Thread.sleep(3000); } catch (InterruptedException e) { logger.error("Error while getting subreddits", e); } } writer.close(); } catch (Exception e) { logger.error("Error while getting subreddits", e); } }
Является ли это зрелой реализацией? Нет. Нужно ли нам что-нибудь еще? Нет, мы этого не делаем. Нам нужно двигаться дальше.
6.2. Автозаполнение Субреддита
Далее, давайте убедимся, что субреддиты загружаются в память при запуске приложения – с помощью реализации службы InitializingBean :
public void afterPropertiesSet() { loadSubreddits(); } private void loadSubreddits() { subreddits = new ArrayList(); try { Resource resource = new ClassPathResource("subreddits.csv"); Scanner scanner = new Scanner(resource.getFile()); scanner.useDelimiter(","); while (scanner.hasNext()) { subreddits.add(scanner.next()); } scanner.close(); } catch (IOException e) { logger.error("error while loading subreddits", e); } }
Теперь, когда все данные субреддита загружены в память, мы можем искать по субреддитам, не обращаясь к API Reddit :
public ListsearchSubreddit(String query) { return subreddits.stream(). filter(sr -> sr.startsWith(query)). limit(9). collect(Collectors.toList()); }
API, предоставляющий предложения subreddit, конечно, остается прежним:
@RequestMapping(value = "/subredditAutoComplete") @ResponseBody public ListsubredditAutoComplete(@RequestParam("term") String term) { return service.searchSubreddit(term); }
7. Метрики
Наконец, мы интегрируем некоторые простые метрики в приложение. Для получения более подробной информации о построении такого рода метрик я подробно описал их здесь .
7.1. Фильтр сервлетов
Здесь простой Метрический фильтр :
@Component public class MetricFilter implements Filter { @Autowired private IMetricService metricService; @Override public void doFilter( ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = ((HttpServletRequest) request); String req = httpRequest.getMethod() + " " + httpRequest.getRequestURI(); chain.doFilter(request, response); int status = ((HttpServletResponse) response).getStatus(); metricService.increaseCount(req, status); } }
Нам также нужно добавить его в наш ServletInitializer :
@Override public void onStartup(ServletContext servletContext) throws ServletException { super.onStartup(servletContext); servletContext.addListener(new SessionListener()); registerProxyFilter(servletContext, "oauth2ClientContextFilter"); registerProxyFilter(servletContext, "springSecurityFilterChain"); registerProxyFilter(servletContext, "metricFilter"); }
7.2. Метрическая служба
А вот наш Метрический сервис :
public interface IMetricService { void increaseCount(String request, int status); Map getFullMetric(); Map getStatusMetric(); Object[][] getGraphData(); }
7.3. Метрический контроллер
И вот основной контроллер, ответственный за предоставление этих метрик через HTTP:
@Controller public class MetricController { @Autowired private IMetricService metricService; // @RequestMapping(value = "/metric", method = RequestMethod.GET) @ResponseBody public Map getMetric() { return metricService.getFullMetric(); } @RequestMapping(value = "/status-metric", method = RequestMethod.GET) @ResponseBody public Map getStatusMetric() { return metricService.getStatusMetric(); } @RequestMapping(value = "/metric-graph-data", method = RequestMethod.GET) @ResponseBody public Object[][] getMetricGraphData() { Object[][] result = metricService.getGraphData(); for (int i = 1; i < result[0].length; i++) { result[0][i] = result[0][i].toString(); } return result; } }
8. Заключение
Это тематическое исследование хорошо развивается. На самом деле приложение начиналось как простой учебник по работе с OAuth с помощью API Reddit; теперь оно превращается в полезный инструмент для опытных пользователей Reddit, особенно в отношении параметров планирования и повторной отправки.
Наконец, с тех пор, как я использую его, похоже, что мои собственные материалы в Reddit, как правило, набирают гораздо больше пара, так что это всегда приятно видеть.