Первый раунд улучшений в приложении Reddit

Первый раунд улучшений в приложении Reddit

1. обзор

The Reddit web application Case Study успешно продвигается вперед - небольшое веб-приложение формируется и постепенно становится пригодным для использования.

В этом выпуске мы собираемся внести небольшие улучшения в существующую функциональность - некоторые внешние, некоторые нет - и, как правило,making the app better.

2. Проверка настроек

Начнем с простых, но полезных проверок, которые необходимо выполнить при загрузке приложения:

@Autowired
private UserRepository repo;

@PostConstruct
public void startupCheck() {
    if (StringUtils.isBlank(accessTokenUri) ||
      StringUtils.isBlank(userAuthorizationUri) ||
      StringUtils.isBlank(clientID) || StringUtils.isBlank(clientSecret)) {
        throw new RuntimeException("Incomplete reddit properties");
    }
    repo.findAll();
}

Обратите внимание, как здесь мы используем аннотацию@PostConstruct, чтобы подключиться к жизненному циклу приложения после завершения процесса внедрения зависимостей.

Простые цели:

  • проверьте, есть ли у нас все свойства, необходимые для доступа к API Reddit

  • проверьте, что уровень сохраняемости работает (с помощью простого вызоваfindAll)

Если мы терпим неудачу - мы делаем это рано.

3. Проблема Reddit «Слишком много запросов»

Reddit API агрессивно ограничивает количество запросов, которые не отправляют уникальный «User-Agent».

Итак - нам нужно добавить этот уникальный заголовокUser-Agent в нашredditRestTemplate - используя собственныйInterceptor:

3.1. Создать собственныйInterceptor

Вот наш собственный перехватчик -UserAgentInterceptor:

public class UserAgentInterceptor implements ClientHttpRequestInterceptor {

    @Override
    public ClientHttpResponse intercept(
      HttpRequest request, byte[] body,
      ClientHttpRequestExecution execution) throws IOException {

        HttpHeaders headers = request.getHeaders();
        headers.add("User-Agent", "Schedule with Reddit");
        return execution.execute(request, body);
    }
}

3.2. НастроитьredditRestTemplate

Нам, конечно, нужно настроить этот перехватчик с использованиемredditRestTemplate:

@Bean
public OAuth2RestTemplate redditRestTemplate(OAuth2ClientContext clientContext) {
    OAuth2RestTemplate template = new OAuth2RestTemplate(reddit(), clientContext);
    List list = new ArrayList();
    list.add(new UserAgentInterceptor());
    template.setInterceptors(list);
    return template;
}

4. Настроить базу данных H2 для тестирования

Далее - давайте продолжим и настроим БД в памяти - H2 - для тестирования. Нам нужно добавить эту зависимость к нашемуpom.xml:


    com.h2database
    h2
    1.4.187

И определитеpersistence-test.properties:

## DataSource Configuration ###
jdbc.driverClassName=org.h2.Driver
jdbc.url=jdbc:h2:mem:oauth_reddit;DB_CLOSE_DELAY=-1
jdbc.user=sa
jdbc.pass=
## Hibernate Configuration ##
hibernate.dialect=org.hibernate.dialect.H2Dialect
hibernate.hbm2ddl.auto=update

5. Переключиться на Тимелеафа

JSP отсутствует, а Thymeleaf включен.

5.1. Изменитьpom.xml

Во-первых, нам нужно добавить эти зависимости в наш pom.xml:


    org.thymeleaf
    thymeleaf
    2.1.4.RELEASE


    org.thymeleaf
    thymeleaf-spring4
    2.1.4.RELEASE


    org.thymeleaf.extras
    thymeleaf-extras-springsecurity3
    2.1.2.RELEASE

5.2. СоздатьThymeleafConfig

Далее - простойThymeleafConfig:

@Configuration
public class ThymeleafConfig {
    @Bean
    public TemplateResolver templateResolver() {
        ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver();
        templateResolver.setPrefix("/WEB-INF/jsp/");
        templateResolver.setSuffix(".jsp");
        return templateResolver;
    }

    @Bean
    public SpringTemplateEngine templateEngine() {
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(templateResolver());
        templateEngine.addDialect(new SpringSecurityDialect());
        return templateEngine;
    }

    @Bean
    public ViewResolver viewResolver() {
        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
        viewResolver.setTemplateEngine(templateEngine());
        viewResolver.setOrder(1);
        return viewResolver;
    }
}

И добавляем его к нашемуServletInitializer:

@Override
protected WebApplicationContext createServletApplicationContext() {
    AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
    context.register(PersistenceJPAConfig.class, WebConfig.class,
      SecurityConfig.class, ThymeleafConfig.class);
    return context;
}

5.3. Изменитьhome.html

И быстрая модификация домашней страницы:



Schedule to Reddit




6. Выйти

А теперь давайте внесем некоторые улучшения, которые действительно видны конечному пользователю приложения. Начнем с выхода.

Мы добавляем в приложение простой вариант выхода, изменив конфигурацию безопасности:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .....
        .and()
        .logout()
        .deleteCookies("JSESSIONID")
        .logoutUrl("/logout")
        .logoutSuccessUrl("/");
}

7. Автозаполнение субреддита

Далее - реализуемa simple autocomplete functionality для заполнения сабреддита; писать его вручную - не лучший вариант, так как есть шанс ошибиться.

Начнем со стороны клиента:




Достаточно просто. Теперь на стороне сервера:

@RequestMapping(value = "/subredditAutoComplete")
@ResponseBody
public String subredditAutoComplete(@RequestParam("term") String term) {
    MultiValueMap param = new LinkedMultiValueMap();
    param.add("query", term);
    JsonNode node = redditRestTemplate.postForObject(
      "https://oauth.reddit.com//api/search_reddit_names", param, JsonNode.class);
    return node.get("names").toString();
}

Далее - давайте посмотрим, как проверить, была ли ссылка уже отправлена ​​ранее на Reddit.

Вот нашsubmissionForm.html:




Check if already submitted



И вот наш метод контроллера:

@RequestMapping(value = "/checkIfAlreadySubmitted", method = RequestMethod.POST)
@ResponseBody
public String checkIfAlreadySubmitted(
  @RequestParam("url") String url, @RequestParam("sr") String sr) {
    JsonNode node = redditRestTemplate.getForObject(
      "https://oauth.reddit.com/r/" + sr + "/search?q=url:" + url + "&restrict_sr=on", JsonNode.class);
    return node.get("data").get("children").toString();
}

9. Развертывание в Heroku

Наконец, мы собираемся настроить развертывание на Heroku и использовать их бесплатный уровень для работы с примером приложения.

9.1. Изменитьpom.xml

Во-первых, нам нужно добавить этотWeb Runner plugin кpom.xml:


    org.apache.maven.plugins
    maven-dependency-plugin
    2.3
    
        
            package
            copy
            
                
                    
                        com.github.jsimone
                        webapp-runner
                        7.0.57.2
                        webapp-runner.jar
                    
                
            
        
    

Обратите внимание - мы будем использовать Web Runner для запуска нашего приложения на Heroku.

Мы собираемся использовать Postgresql на Heroku, поэтому нам потребуется зависимость от драйвера:


    org.postgresql
    postgresql
    9.4-1201-jdbc41

9.2. Procfile

Нам нужно определить процесс, который будет запускаться на сервере вProcfile - следующим образом:

web:    java $JAVA_OPTS -jar target/dependency/webapp-runner.jar --port $PORT target/*.war

9.3. Создать приложение Heroku

Чтобы создать приложение Heroku на основе вашего проекта, мы просто:

cd path_to_your_project
heroku login
heroku create

9.4. Конфигурация базы данных

Далее - нам нужно настроить нашу базу данных, используя свойства базы данныхPostgres нашего приложения.

Например, вот файл persistence-prod.properties:

## DataSource Configuration ##
jdbc.driverClassName=org.postgresql.Driver
jdbc.url=jdbc:postgresql://hostname:5432/databasename
jdbc.user=xxxxxxxxxxxxxx
jdbc.pass=xxxxxxxxxxxxxxxxx

## Hibernate Configuration ##
hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
hibernate.hbm2ddl.auto=update

Обратите внимание, что нам нужно получить подробные сведения о базе данных [имя хоста, имя базы данных, имя пользователя и пароль] из панели Heroku.

Также - как и в большинстве случаев, ключевое слово «пользователь» - этоreserved word in Postgres, поэтому нам нужно изменить имя нашей таблицы сущностей «User»:

@Entity
@Table(name = "APP_USER")
public class User { .... }

9.5. Отправить код в Heoku

А теперь давайте отправим код в Heroku:

git add .
git commit -m "init"
git push heroku master

10. Заключение

В этой четвертой части нашего тематического исследования основное внимание уделялось небольшим, но важным улучшениям. Если вы следили за мной, вы можете увидеть, как это превращается в интересное и полезное небольшое приложение.