Rendez-le facile à programmer pour Reddit
1. Vue d'ensemble
Dans cet article, nous allons passer àcontinue the case study etadd a new feature to the Reddit application, dans le but de simplifier considérablement la planification des articles.
Au lieu d’ajouter lentement chaque article à la main dans l’interface utilisateur de planification, l’utilisateur peut désormais n’avoir que quelques sites favoris pour publier des articles sur Reddit. Nous allons utiliser RSS pour ce faire.
2. L'entitéSite
Tout d'abord, créons une entité pour représenter le site:
@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;
}
Notez que le champurl représente l'URL dethe RSS feed of the site.
3. Le référentiel et le service
Suivant - Permet de créer le référentiel à utiliser avec la nouvelle entité de site:
public interface SiteRepository extends JpaRepository {
List findByUser(User user);
}
Et le service:
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. Charger des données à partir du flux
Voyons maintenant comment charger les détails des articles à partir du flux du site Web à l'aide de la bibliothèque de Rome.
Nous devons d'abord ajouter Rome dans nospom.xml:
com.rometools
rome
1.5.0
Et ensuite, utilisez-le pour analyser les flux des sites:
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;
}
Enfin, voici le DTO simple que nous allons utiliser dans la réponse:
public class SiteArticle {
private String title;
private String link;
private Date publishDate;
}
5. Gestion des exceptions
Remarquez comment, lors de l'analyse du flux, nous enveloppons l'ensemble de la logique d'analyse dans un bloctry-catch et, en cas d'exception (toute exception), nous l'enveloppons et le lançons.
La raison en est simple -we need to control the type of exception that gets thrown out of the parsing process - afin que nous puissions ensuite gérer cette exception et fournir une réponse appropriée au client de l'API:
@ExceptionHandler({ FeedServerException.class })
public ResponseEntity
6. La page des sites
6.1. Afficher les sites
Tout d'abord, nous verrons comment afficher la liste des sites appartenant à l'utilisateur connecté:
@RequestMapping(value = "/sites")
@ResponseBody
public List getSitesList() {
return service.getSitesByUser(getCurrentUser());
}
Et voici l’avant très simple:
Site Name Feed URL Actions
6.2. Ajouter un nouveau site
Voyons ensuite comment un utilisateur peut créer un nouveau site favori:
@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);
}
Et voici le - encore une fois très simple - côté client:
6.3. Valider un flux
La validation d'un nouveau flux est une opération assez coûteuse - nous devons récupérer le flux et l'analyser pour le valider complètement. Voici la méthode de service simple:
public boolean isValidFeedUrl(String feedUrl) {
try {
return getFeedEntries(feedUrl).size() > 0;
} catch (Exception e) {
return false;
}
}
6.3. Supprimer un site
Voyons maintenant comment l’utilisateur peutdelete a site from their list of favorite sites:
@RequestMapping(value = "/sites/{id}", method = RequestMethod.DELETE)
@ResponseStatus(HttpStatus.OK)
public void deleteSite(@PathVariable("id") Long id) {
service.deleteSiteById(id);
}
Et voici la méthode du niveau de service - encore une fois très simple -:
public void deleteSiteById(Long siteId) {
repo.delete(siteId);
}
7. Planifier une publication à partir d'un site
Maintenant, commençons à utiliser ces sites et mettons en œuvre un moyen de base permettant à un utilisateur de planifier un nouveau message pour qu'il soit envoyé sur Reddit non pas manuellement, mais en chargeant un article à partir d'un site existant.
7.1. Modifier le formulaire de planification
Commençons par le site client et modifions lesschedulePostForm.html existants - nous allons ajouter:
Notez que nous avons ajouté:
-
le bouton - «Load from my Sites» - pour démarrer le processus
-
la pop-up - montrant la liste des sites et leurs articles
7.2. Charger les sites
Charger les sites dans le popup est relativement facile avec un peu de 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. Charger les publications d'un site
Lorsque l'utilisateur sélectionne un site Web dans la liste, nous devons afficher les articles de ce site - à nouveau avec quelques js de base:
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);
});
}
Bien entendu, cela se rattache à une simple opération côté serveur pour charger les articles d'un site:
@RequestMapping(value = "/sites/articles")
@ResponseBody
public List getSiteArticles(@RequestParam("id") Long siteId) {
return service.getArticlesFromSite(siteId);
}
Enfin, nous obtenons les données de l'article, remplissons le formulaire et planifions l'article pour qu'il soit envoyé à 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. Tests d'intégration
Enfin, testons nosSiteService sur deux formats de flux différents:
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());
}
}
}
Il y a clairement un peu de duplication ici, mais nous pourrons nous en occuper plus tard.
9. Conclusion
Dans cet article, nous nous sommes concentrés sur une nouvelle petite fonctionnalité - simplifiant la planification du message sur Reddit.