Первый раунд улучшений в приложении 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();
}
8. Проверьте, есть ли ссылка на Reddit
Далее - давайте посмотрим, как проверить, была ли ссылка уже отправлена ранее на Reddit.
Вот нашsubmissionForm.html:
И вот наш метод контроллера:
@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. Заключение
В этой четвертой части нашего тематического исследования основное внимание уделялось небольшим, но важным улучшениям. Если вы следили за мной, вы можете увидеть, как это превращается в интересное и полезное небольшое приложение.