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 { ListgetSitesByUser(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 ListgetSitesByUser(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 ResponseEntity
6. Страница Сайтов
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/"); Listarticles = 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.