1. Обзор
В этой статье мы собираемся продолжить тематическое исследование и добавить новую функцию в приложение Reddit , чтобы значительно упростить планирование статей.
Вместо того, чтобы медленно добавлять каждую статью вручную в пользовательский интерфейс расписания, пользователь теперь может просто иметь несколько любимых сайтов для публикации статей в Reddit. Для этого мы будем использовать RSS.
2. Сущность Сайта
Во – первых, давайте создадим объект для представления сайта:
@Entity
public class Site {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String url;
@ManyToOne
@JoinColumn(name = "user_id", nullable = false)
private User user;
}Обратите внимание, что поле url представляет URL-адрес RSS – канала сайта .
3. Репозиторий и Служба
Далее – позволяет создать репозиторий для работы с новой сущностью сайта:
public interface SiteRepository extends JpaRepository{ List findByUser(User user); }
И обслуживание:
public interface ISiteService {
List getSitesByUser(User user);
void saveSite(Site site);
Site findSiteById(Long siteId);
void deleteSiteById(Long siteId);
} @Service
public class SiteService implements ISiteService {
@Autowired
private SiteRepository repo;
@Override
public List getSitesByUser(User user) {
return repo.findByUser(user);
}
@Override
public void saveSite(Site site) {
repo.save(site);
}
@Override
public Site findSiteById(Long siteId) {
return repo.findOne(siteId);
}
@Override
public void deleteSiteById(Long siteId) {
repo.delete(siteId);
}
} 4. Загрузите данные из ленты
Теперь давайте посмотрим, как загрузить сведения о статьях из ленты веб – сайта с помощью библиотеки Rome.
Сначала нам нужно будет добавить аромат в ваш pom.xml :
com.rometools rome 1.5.0
А затем используйте его для анализа каналов сайтов:
public ListgetArticlesFromSite(Long siteId) { Site site = repo.findOne(siteId); return getArticlesFromSite(site); } List getArticlesFromSite(Site site) { List entries; try { entries = getFeedEntries(site.getUrl()); } catch (Exception e) { throw new FeedServerException("Error Occurred while parsing feed", e); } return parseFeed(entries); } private List getFeedEntries(String feedUrl) throws IllegalArgumentException, FeedException, IOException { URL url = new URL(feedUrl); SyndFeed feed = new SyndFeedInput().build(new XmlReader(url)); return feed.getEntries(); } private List parseFeed(List entries) { List articles = new ArrayList (); for (SyndEntry entry : entries) { articles.add(new SiteArticle( entry.getTitle(), entry.getLink(), entry.getPublishedDate())); } return articles; }
Наконец, вот простой DTO, который мы собираемся использовать в ответе:
public class SiteArticle {
private String title;
private String link;
private Date publishDate;
}5. Обработка исключений
Обратите внимание, как при разборе ленты мы оборачиваем всю логику разбора в блок try-catch и – в случае исключения (любого исключения) – оборачиваем его и выбрасываем.
Причина этого проста – нам нужно контролировать тип исключения, которое выбрасывается из процесса синтаксического анализа – чтобы затем мы могли обработать это исключение и предоставить надлежащий ответ клиенту API:
@ExceptionHandler({ FeedServerException.class })
public ResponseEntity6. Страница Сайтов
6.1. Отображение Сайтов
Во-первых, мы увидим, как показать список сайтов, принадлежащих зарегистрированному пользователю:
@RequestMapping(value = "/sites") @ResponseBody public ListgetSitesList() { return service.getSitesByUser(getCurrentUser()); }
А вот очень простая передняя часть:
| Site Name | Feed URL | Actions |
|---|
6.2. Добавить новый сайт
Далее давайте посмотрим, как пользователь может создать новый любимый сайт:
@RequestMapping(value = "/sites", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.OK)
public void addSite(Site site) {
if (!service.isValidFeedUrl(site.getUrl())) {
throw new FeedServerException("Invalid Feed Url");
}
site.setUser(getCurrentUser());
service.saveSite(site);
}А вот – опять же очень простая – клиентская сторона:
6.3. Проверка ленты
Проверка нового канала – это немного дорогостоящая операция-нам нужно фактически извлечь канал и проанализировать его, чтобы полностью проверить его. Вот простой метод обслуживания:
public boolean isValidFeedUrl(String feedUrl) {
try {
return getFeedEntries(feedUrl).size() > 0;
} catch (Exception e) {
return false;
}
}6.3. Удалить сайт
Теперь давайте посмотрим, как пользователь может удалить сайт из своего списка любимых сайтов :
@RequestMapping(value = "/sites/{id}", method = RequestMethod.DELETE)
@ResponseStatus(HttpStatus.OK)
public void deleteSite(@PathVariable("id") Long id) {
service.deleteSiteById(id);
}И здесь – опять же очень простой – метод уровня обслуживания:
public void deleteSiteById(Long siteId) {
repo.delete(siteId);
}7. Запланируйте публикацию с сайта
Теперь давайте на самом деле начнем использовать эти сайты и реализуем базовый способ, с помощью которого пользователь может запланировать выход новой публикации в Reddit не вручную, а путем загрузки статьи с существующего сайта.
7.1. Изменить Форму Планирования
Давайте начнем с клиентского сайта и изменим существующий schedulePostForm.html – мы собираемся добавить:
Обратите внимание, что мы добавили:
- кнопка – ” Загрузить с моих сайтов ” – для запуска процесса
- всплывающее окно – отображение списка сайтов и их статей
7.2. Загрузка сайтов
Загрузка сайтов во всплывающем окне относительно проста с небольшим количеством javascript:
$('#myModal').on('shown.bs.modal', function () {
if($("#siteList").children().length > 0)
return;
$.get("sites", function(data){
$.each(data, function( index, site ) {
$("#siteList").append('7.3. Загрузка сообщений Сайта
Когда пользователь выбирает веб – сайт из списка, нам нужно показать статьи этого сайта-опять же с некоторыми базовыми js:
function loadArticles(siteID,siteName){
$("#dropdownMenu1").html(siteName);
$.get("sites/articles?id="+siteID, function(data){
$("#articleList").html('');
$("#dropdownMenu2").html('Choose Article');
$.each(data, function( index, article ) {
$("#articleList").append(
'Это, конечно, подключается к простой операции на стороне сервера для загрузки статей сайта:
@RequestMapping(value = "/sites/articles") @ResponseBody public ListgetSiteArticles(@RequestParam("id") Long siteId) { return service.getArticlesFromSite(siteId); }
Наконец, мы получаем данные о статье, заполняем форму и планируем публикацию статьи в Reddit:
var title = "";
var link = "";
function chooseArticle(selectedTitle,selectedLink){
$("#dropdownMenu2").html(selectedTitle);
title=selectedTitle;
link = selectedLink;
}
function load(){
$("input[name='title']").val(title);
$("input[name='url']").val(link);
}8. Интеграционные тесты
Наконец – давайте протестируем наш SiteService на двух разных форматах ленты:
public class SiteIntegrationTest {
private ISiteService service;
@Before
public void init() {
service = new SiteService();
}
@Test
public void whenUsingServiceToReadWordpressFeed_thenCorrect() {
Site site = new Site("/feed/");
List articles = service.getArticlesFromSite(site);
assertNotNull(articles);
for (SiteArticle article : articles) {
assertNotNull(article.getTitle());
assertNotNull(article.getLink());
}
}
@Test
public void whenUsingRomeToReadBloggerFeed_thenCorrect() {
Site site = new Site("http://blogname.blogspot.com/feeds/posts/default");
List articles = service.getArticlesFromSite(site);
assertNotNull(articles);
for (SiteArticle article : articles) {
assertNotNull(article.getTitle());
assertNotNull(article.getLink());
}
}
} Здесь явно есть некоторое дублирование, но мы можем позаботиться об этом позже.
9. Заключение
В этом выпуске мы сосредоточились на новой небольшой функции – упрощении планирования публикации в Reddit.