1. 概述
Reddit 網頁應用程序案例研究進展順利——小型應用程序正在成型並逐漸變得可用。
在本篇中,我們將對現有功能進行小幅改進——有些是面向外部的,有些則不是——並且總體上提升應用程序的性能。
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 註解,以便在依賴注入過程完成後鈎入應用程序的生命週期。
目標如下:
- 檢查我們是否擁有訪問 Reddit API 所需的所有屬性
- 檢查持久化層是否正常工作(通過發出簡單的 findAll 調用)
如果失敗,我們會在早期失敗。
3. Reddit API 請求過多問題
Reddit API 對未發送包含唯一“User-Agent”的請求非常嚴格,會進行速率限制。
因此,我們需要在我們的 redditRestTemplate 中添加這個唯一的 User-Agent 頭部,使用自定義 Interceptor:
3.1. 創建自定義攔截器
以下是我們的自定義攔截器 – 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<ClientHttpRequestInterceptor> list = new ArrayList<ClientHttpRequestInterceptor>();
list.add(new UserAgentInterceptor());
template.setInterceptors(list);
return template;
}4. 配置 H2 數據庫用於測試
接下來,讓我們設置一個內存數據庫 – H2 – 用於測試。我們需要將此依賴項添加到我們的 <em pom.xml</em>> 中:
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.187</version>
</dependency>並定義一個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=update5. 切換到 Thymeleaf
JSP 已被淘汰,Thymeleaf 正在使用中。
5.1. 修改 pom.xml
首先,我們需要將這些依賴項添加到我們的 pom.xml 文件中:
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>2.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring4</artifactId>
<version>2.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity3</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>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
快速修改主頁:
<html>
<head>
<title>Schedule to Reddit</title>
</head>
<body>
<div class="container">
<h1>Welcome, <small><span sec:authentication="principal.username">Bob</span></small></h1>
<br/>
<a href="posts" >My Scheduled Posts</a>
<a href="post" >Post to Reddit</a>
<a href="postSchedule" >Schedule Post to Reddit</a>
</div>
</body>
</html>6. 註銷
現在,讓我們進行一些對最終用户可見的改進。我們首先從註銷功能開始。
我們通過修改我們的安全配置,將一個簡單的註銷選項添加到應用程序中:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.....
.and()
.logout()
.deleteCookies("JSESSIONID")
.logoutUrl("/logout")
.logoutSuccessUrl("/");
}7. 子論壇自動補全
接下來,讓我們實現一個簡單的自動補全功能,用於填寫子論壇;手動輸入並非最佳選擇,因為存在很大的出錯風險。
我們先從客户端開始:
<input id="sr" name="sr"/>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.2/jquery-ui.min.js"></script>
<script>
$(function() {
$( "#sr" ).autocomplete({
source: "/subredditAutoComplete"
});
});
</script>簡單來説,就這麼簡單。現在,我們來談談服務端:
@RequestMapping(value = "/subredditAutoComplete")
@ResponseBody
public String subredditAutoComplete(@RequestParam("term") String term) {
MultiValueMap<String, String> param = new LinkedMultiValueMap<String, String>();
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:
<input name="url" />
<input name="sr">
<a href="#" onclick="checkIfAlreadySubmitted()">Check if already submitted</a>
<span id="checkResult" style="display:none"></span>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script>
$(function() {
$("input[name='url'],input[name='sr']").focus(function (){
$("#checkResult").hide();
});
});
function checkIfAlreadySubmitted(){
var url = $("input[name='url']").val();
var sr = $("input[name='sr']").val();
if(url.length >3 && sr.length > 3){
$.post("checkIfAlreadySubmitted",{url: url, sr: sr}, function(data){
var result = JSON.parse(data);
if(result.length == 0){
$("#checkResult").show().html("Not submitted before");
}else{
$("#checkResult").show().html(
'Already submitted <b><a target="_blank" href="http://www.reddit.com'
+result[0].data.permalink+'">here</a></b>');
}
});
}
else{
$("#checkResult").show().html("Too short url and/or subreddit");
}
}
</script>以下是我們的控制器方法:
@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 插件添加到 pom.xml 中:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.3</version>
<executions>
<execution>
<phase>package</phase>
<goals><goal>copy</goal></goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>com.github.jsimone</groupId>
<artifactId>webapp-runner</artifactId>
<version>7.0.57.2</version>
<destFileName>webapp-runner.jar</destFileName>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>注意 – 我們將使用 Web Runner 在 Heroku 上啓動我們的應用程序。
我們將使用 Postgresql 在 Heroku 上 – 因此我們需要安裝驅動程序依賴項:
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.4-1201-jdbc41</version>
</dependency>9.2. Procfile
我們需要在服務器上運行的進程定義在 Procfile 中 – 如下所示:
web: java $JAVA_OPTS -jar target/dependency/webapp-runner.jar --port $PORT target/*.war9.3. 創建 Heroku 應用
要從你的項目創建 Heroku 應用,我們只需:
cd path_to_your_project
heroku login
heroku create9.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 儀表板獲取數據庫詳細信息,包括主機名、數據庫名稱、用户名和密碼。
另外,就像大多數情況下一樣,“user” 關鍵字在 Postgres 中是一個 保留字,因此我們需要更改我們的 “User” 實體表名:
@Entity
@Table(name = "APP_USER")
public class User { .... }9.5. 將代碼推送到 Heoku
現在,讓我們將代碼推送到 Heoku:
git add .
git commit -m "init"
git push heroku master10. 結論
在本案例研究的第四部分中,重點在於一些小但重要的改進。如果您一直跟隨我們的進展,您可以看到這正在成型為一個有趣且有用的小應用程序。