Рубрики
Без рубрики

Сделайте его легким для планирования Reddit

Создание новой функции в приложении для тематических исследований-планирование публикации в Reddit из RSS – ленты предопределенного сайта.

Автор оригинала: baeldung.

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 List getArticlesFromSite(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 handleFeed(RuntimeException ex, WebRequest request) {
    logger.error("500 Status Code", ex);
    String bodyOfResponse = ex.getLocalizedMessage();
    return new ResponseEntity(bodyOfResponse, new HttpHeaders(), 
      HttpStatus.INTERNAL_SERVER_ERROR);
}

6. Страница Сайтов

6.1. Отображение Сайтов

Во-первых, мы увидим, как показать список сайтов, принадлежащих зарегистрированному пользователю:

@RequestMapping(value = "/sites")
@ResponseBody
public List getSitesList() {
    return service.getSitesByUser(getCurrentUser());
}

А вот очень простая передняя часть:

Site NameFeed URLActions

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('
    • '+site.name+'
    • ') }); }); });

      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(
                    '
    • '+article.title+' '+ new Date(article.publishDate).toUTCString()+'
    • ') }); }).fail(function(error){ alert(error.responseText); }); }

      Это, конечно, подключается к простой операции на стороне сервера для загрузки статей сайта:

      @RequestMapping(value = "/sites/articles")
      @ResponseBody
      public List getSiteArticles(@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.