知識庫 / Spring RSS 訂閱

輕鬆安排內容至Reddit

Spring
HongKong
5
02:53 PM · Dec 06 ,2025

1. 概述

本文將繼續案例研究,並向 Reddit 應用添加一項新功能——簡化文章排期。 目標是讓用户能夠更輕鬆地安排文章發佈。

不再需要逐個手動添加文章到排期 UI 中,用户現在可以從自己喜歡的網站向 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 字段代表 網站的 RSS 訂閲源 URL

3. 倉庫與服務

接下來,讓我們創建一個用於處理新 Site 實體的倉庫:

public interface SiteRepository extends JpaRepository<Site, Long> {
    List<Site> findByUser(User user);
}

以及服務:

public interface ISiteService {

    List<Site> 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<Site> 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 庫從網站源集中加載文章詳情。

首先,我們需要將 Rome 添加到我們的 <em >pom.xml</em> 中:

<dependency>
    <groupId>com.rometools</groupId>
    <artifactId>rome</artifactId>
    <version>1.5.0</version>
</dependency>

然後使用它來解析這些網站的 RSS 源:

public List<SiteArticle> getArticlesFromSite(Long siteId) {
    Site site = repo.findOne(siteId);
    return getArticlesFromSite(site);
}

List<SiteArticle> getArticlesFromSite(Site site) {
    List<SyndEntry> entries;
    try {
        entries = getFeedEntries(site.getUrl());
    } catch (Exception e) {
        throw new FeedServerException("Error Occurred while parsing feed", e);
    }
    return parseFeed(entries);
}

private List<SyndEntry> 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<SiteArticle> parseFeed(List<SyndEntry> entries) {
    List<SiteArticle> articles = new ArrayList<SiteArticle>();
    for (SyndEntry entry : entries) {
        articles.add(new SiteArticle(
          entry.getTitle(), entry.getLink(), entry.getPublishedDate()));
    }
    return articles;
}
<p>最後 – 這是我們將用於響應的簡單 DTO:</p>
public class SiteArticle {
    private String title;
    private String link;
    private Date publishDate;
}

5. 異常處理

請注意,在解析數據流時,我們將整個解析邏輯封裝在一個 <em >try-catch</em> 塊中,並在發生異常(任何異常)時,將其封裝並拋出。

這樣做的原因很簡單——我們需要控制解析過程中拋出的異常類型——以便我們可以處理該異常並向 API 的客户端提供適當的響應:

@ExceptionHandler({ FeedServerException.class })
public ResponseEntity<Object> handleFeed(RuntimeException ex, WebRequest request) {
    logger.error("500 Status Code", ex);
    String bodyOfResponse = ex.getLocalizedMessage();
    return new ResponseEntity<Object>(bodyOfResponse, new HttpHeaders(), 
      HttpStatus.INTERNAL_SERVER_ERROR);
}

6. The Sites Page

6.1. 顯示站點

首先,我們將查看如何顯示已登錄用户所屬的站點列表:

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

以下是極簡的前端示例:

<table>
<thead>
<tr><th>Site Name</th><th>Feed URL</th><th>Actions</th></tr>
</thead>
</table>
<script>
$(function(){
	$.get("sites", function(data){
        $.each(data, function( index, site ) {
            $('.table').append('<tr><td>'+site.name+'</td><td>'+site.url+
              '</td><td><a href="#" onclick="deleteSite('+site.id+') ">Delete</a> </td></tr>');
        });
    });
});

function deleteSite(id){
    $.ajax({ url: 'sites/'+id, type: 'DELETE', success: function(result) {
            window.location.href="mysites"
        }
    });
}
</script>

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);
}

以下是客户端,同樣非常簡單:

<form>
    <input name="name" />
    <input id="url" name="url" />
    <button type="submit" onclick="addSite()">Add Site</button>
</form>

<script>
function addSite(){
    $.post("sites",$('form').serialize(), function(data){
        window.location.href="mysites";
    }).fail(function(error){
        alert(error.responseText);
    });
}
</script>

6.3. 驗證 Feed

驗證一個新的 Feed 是一項相對耗費資源的 操作 – 我們需要實際檢索 Feed 並解析它以完全驗證。以下是簡單的服務方法:

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 – 我們將添加:

<button data-target="#myModal">Load from My Sites</button>
<div id="myModal">
    <button id="dropdownMenu1">Choose Site</button><ul id="siteList"></ul>
    <button id="dropdownMenu2">Choose Article</button><ul id="articleList"></ul>
    <button onclick="load()">Load</button>
</div>

請注意,我們已添加了:

  • 按鈕“從我的站點加載”以啓動該過程
  • 彈出窗口,顯示站點及其文章的列表

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('<li><a href="#" onclick="loadArticles('+
              site.id+',\''+site.name+'\')">'+site.name+'</a></li>')
	});
    });
});

7.3. 加載站點文章

當用户從列表中選擇一個網站時,我們需要顯示該網站的文章——同樣使用一些基本的 JavaScript 代碼:

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(
              '<li><a href="#" onclick="chooseArticle(\''+article.title+
              '\',\''+article.link+'\')"><b>'+article.title+'</b> <small>'+
              new Date(article.publishDate).toUTCString()+'</small></li>')
    });
    }).fail(function(error){ 
        alert(error.responseText); 
    });
}

這當然會與一個簡單的服務器端操作聯動,以加載網站的文章:

@RequestMapping(value = "/sites/articles")
@ResponseBody
public List<SiteArticle> 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<SiteArticle> 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<SiteArticle> articles = service.getArticlesFromSite(site);

        assertNotNull(articles);
        for (SiteArticle article : articles) {
            assertNotNull(article.getTitle());
            assertNotNull(article.getLink());
        }
    }
}

這裏顯然存在一些重複,但我們可以稍後再處理。

9. 結論

在本節中,我們重點關注了一個新的、小型功能——將帖子安排發佈到Reddit的簡化。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.