关于RSS订阅功能的实现与初步认识
起初会有各种的规范,也就是要实现一个符合rss规范的xml文档,这个文档会在火狐、QQ浏览器上自动解析成一个文章列表,大概就是这个意思,好像还有个rss阅读器什么的,反正这个RSS订阅功能在2000年初的时候好像流行了一段时间,但是后来就不火了,所以网上可供参考的内容也不多吧。。。少说废话 ,直接code吧。
这是规范。。。。
好了,以上就是我当初拿到的规范和要实现的内容模板,直接看code。
我是用的web框架是sping+springmvc+hibernate,数据库的话是mysql.
package com.hjhz.app.web.action.admin.rss;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.hjhz.app.common.Global;
import com.hjhz.app.common.WebRootPath;
import com.hjhz.app.db.dao.JksbMessageDAO;
import com.hjhz.app.db.po.JksbMessage;
import com.sun.syndication.feed.rss.Channel;
import com.sun.syndication.feed.rss.Content;
import com.sun.syndication.feed.rss.Description;
import com.sun.syndication.feed.rss.Item;
import com.sun.syndication.feed.rss.Source;
import com.sun.syndication.io.FeedException;
import com.sun.syndication.io.WireFeedOutput;
/**
*
* @author YGA
*
*/
@Controller
@RequestMapping(value="/RSS")
public class SiteRSSAction extends ParameterizableViewController{
private static final String COULD_NOT_GENERATE_FEED_ERROR = "Could not generate feed";
@Inject
private JksbMessageDAO JksbMessageDAO;
@RequestMapping(value="/getRss")
public String createRSS(HttpServletRequest request,HttpServletResponse response) throws Exception{
String rootPath = WebRootPath.getPath();
String path = rootPath + Global.UPLOAD_FILE_RSS_PATH;//上传路径
try {
Channel channel = getChannel(request);
OutputStreamWriter o = new OutputStreamWriter(new FileOutputStream(path),"UTF-8");//文件编码格式为utf-8
// Writer w=new FileWriter(out);
WireFeedOutput outs = new WireFeedOutput();
outs.output(channel, o);//先上传至服务器做保存
o.close();
/*****************************************************************/
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setValidating(false);
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new FileInputStream(new File(path)));//再解析上传的RSS的xml文件为Document格式
Element element = doc.getDocumentElement();//获得根节点
NodeList nlist=element.getChildNodes();//获得根节点下的所有子节点
for(int i=0;i<nlist.getLength();i++){
Node node1 = nlist.item(i);
if("channel".equals(node1.getNodeName())){
NodeList nodeDetail = node1.getChildNodes();
for(int j=0;j<nodeDetail.getLength();j++){
Node node2=nodeDetail.item(j);
if("title".equals(node2.getNodeName())){//处理channel下的title节点
String rssContent=node2.getTextContent();
NodeList nodeDetails=node2.getChildNodes();
for(int f=0;f<nodeDetails.getLength();f++){
Node nodes=nodeDetails.item(f);
if("#text".equals(nodes.getNodeName())){//存在之前的乱码文本节点
nodes.setTextContent("");//将已经存在的文本内容置为空
}
}
CDATASection cdata = doc.createCDATASection(rssContent);//在title节点中追加<![CDATA[...]]>封装正文内容
node2.appendChild(cdata);//追加
}
if("link".equals(node2.getNodeName())){//处理channel下的link节点
String rssContent=node2.getTextContent();
NodeList nodeDetails=node2.getChildNodes();
for(int f=0;f<nodeDetails.getLength();f++){
Node nodes=nodeDetails.item(f);
if("#text".equals(nodes.getNodeName())){//存在之前的乱码文本节点
nodes.setTextContent("");//将已经存在的文本内容置为空
}
}
CDATASection cdata = doc.createCDATASection(rssContent);//在link节点中追加<![CDATA[...]]>封装正文内容
node2.appendChild(cdata);//追加
}
if("description".equals(node2.getNodeName())){//处理channel下的description节点
String rssContent=node2.getTextContent();
NodeList nodeDetails=node2.getChildNodes();
for(int f=0;f<nodeDetails.getLength();f++){
Node nodes=nodeDetails.item(f);
if("#text".equals(nodes.getNodeName())){//存在之前的乱码文本节点
nodes.setTextContent("");//将已经存在的文本内容置为空
}
}
CDATASection cdata = doc.createCDATASection(rssContent);//在description节点中追加<![CDATA[...]]>封装正文内容
node2.appendChild(cdata);//追加
}
if("item".equals(node2.getNodeName())){
NodeList nodeDetail2 = node2.getChildNodes();
for(int k=0;k<nodeDetail2.getLength();k++){
Node node3=nodeDetail2.item(k);
if("title".equals(node3.getNodeName())){//处理title节点
String rssContent=node3.getTextContent();
NodeList nodeDetail3=node3.getChildNodes();
for(int f=0;f<nodeDetail3.getLength();f++){
Node node4=nodeDetail3.item(f);
if("#text".equals(node4.getNodeName())){//存在之前的乱码文本节点
node4.setTextContent("");//将已经存在的文本内容置为空
}
}
CDATASection cdata = doc.createCDATASection(rssContent);//在item节点中追加<![CDATA[...]]>封装正文内容
node3.appendChild(cdata);//追加
}
if("link".equals(node3.getNodeName())){//处理link节点
String rssContent=node3.getTextContent();
NodeList nodeDetail3=node3.getChildNodes();
for(int f=0;f<nodeDetail3.getLength();f++){
Node node4=nodeDetail3.item(f);
if("#text".equals(node4.getNodeName())){//存在之前的乱码文本节点
node4.setTextContent("");//将已经存在的文本内容置为空
}
}
CDATASection cdata = doc.createCDATASection(rssContent);//在link节点中追加<![CDATA[...]]>封装正文内容
node3.appendChild(cdata);//追加
}
if("description".equals(node3.getNodeName())){//处理description节点
String rssContent=node3.getTextContent();
NodeList nodeDetail3=node3.getChildNodes();
for(int f=0;f<nodeDetail3.getLength();f++){
Node node4=nodeDetail3.item(f);
if("#text".equals(node4.getNodeName())){//存在之前的乱码文本节点
node4.setTextContent("");//将已经存在的文本内容置为空
}
}
CDATASection cdata = doc.createCDATASection(rssContent);//在description节点中追加<![CDATA[...]]>封装正文内容
node3.appendChild(cdata);//追加
}
if("content:encoded".equals(node3.getNodeName())){//处理content:encoded节点
String rssContent=node3.getTextContent();
rssContent=newString(rssContent.toString());//去掉所有的style="..."
NodeList nodeDetail3=node3.getChildNodes();
for(int f=0;f<nodeDetail3.getLength();f++){
Node node4=nodeDetail3.item(f);
if("#text".equals(node4.getNodeName())){//存在之前的乱码文本节点
node4.setTextContent("");//将已经存在的文本内容置为空
}
}
CDATASection cdata = doc.createCDATASection(rssContent);//在content:encoded节点中追加<![CDATA[...]]>封装正文内容
node3.appendChild(cdata);//追加
}
if("pubDate".equals(node3.getNodeName())){//处理pubDate节点
String rssContent=node3.getTextContent().replaceAll("GMT","+0800");
NodeList nodeDetail3=node3.getChildNodes();
for(int f=0;f<nodeDetail3.getLength();f++){
Node node4=nodeDetail3.item(f);
if("#text".equals(node4.getNodeName())){//存在之前的乱码文本节点
node4.setTextContent("");//将已经存在的文本内容置为空
}
}
CDATASection cdata = doc.createCDATASection(rssContent);//在pubDate节点中追加<![CDATA[...]]>封装正文内容
node3.appendChild(cdata);//追加
}
}
}
}
}
}
prettyPrint(doc,response);//解析完之后再返回给客户端
/*****************************************************************/
// String feedType = request.getParameter(FEED_TYPE);//null
// feedType = (feedType!=null) ? feedType : _defaultFeedType;
// channel.setFeedType(feedType);//rss_2.0
// response.setContentType(MIME_TYPE);
// WireFeedOutput out = new WireFeedOutput();
// out.output(channel,response.getWriter());//向发出请求的用户输出该RSS(xml格式)
}
catch (FeedException ex) {
String msg = COULD_NOT_GENERATE_FEED_ERROR;
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,msg);
}
return null;
}
/**
* 进行rss文件组装
* @param request
* @return
* @throws IOException
* @throws FeedException
*/
protected Channel getChannel(HttpServletRequest request) throws IOException,FeedException {
//feed就是channel
Channel channel = new Channel("rss_2.0");
channel.setTitle("aaaaaaaa");//网站标题
channel.setDescription("这是描述。。。。");//网站描述
channel.setLink("www.baidu.com");//网站主页链接
channel.setEncoding("UTF-8");//RSS文件编码
channel.setLanguage("zh_cn");//RSS使用的语言
// channel.setTtl(5);//time to live的简写,在刷新前当前RSS在缓存中可以保存多长时间(分钟)
List<Item> items = new ArrayList<Item>();//这个list对应rss中的item列表
Item item=null;
Iterator<JksbMessage> iterator = setIterator(request);
while(iterator.hasNext()){//遍历
JksbMessage jm=(JksbMessage)iterator.next();
item = new Item();//新建Item对象,对应rss中的<item></item>
item.setTitle(jm.getTitle());//对应<item>中的<title></title>
item.setLink("http://localhost:8080/aa/Rss/rssMessageDetial.jhtml?mid="+jm.getId()+"&s=cm");//新闻具体详情,走的是cc提供的模板
try {
item.setPubDate(dateFormat(jm.getCreateTime()));//这个<item>对应的发布时间
} catch (ParseException e) {
e.printStackTrace();
}
Source s=new Source();
s.setValue(jm.getInfoSource());//来源
item.setSource(s);
//新建一个Description,它是Item的描述部分
Description description = new Description();
description.setValue(clearIllegalCharacter(delLastImg(jm.getContent())));//<description>中的内容,过滤掉最后一张的广告和二维码图片
item.setDescription(description);//添加到item节点中
Content content=new Content();//正文
content.setType("text/html");
content.setValue(clearIllegalCharacter(delLastImg(jm.getContent())));//过滤掉最后一张的广告和二维码图片
item.setContent(content);
items.add(item);//代表一个段落<item></item>,
}
channel.setItems(items);
return channel;
}
//从数据库中获得的文章
private Iterator<JksbMessage> setIterator(HttpServletRequest request){
Long currentTime=System.currentTimeMillis();//当下时间毫秒数
Long biginTime=currentTime-(7*24*3600*1000);//获得提前一周的当下毫秒
return JksbMessageDAO.pageByTime(biginTime,currentTime).iterator();//所有的新闻数据
}
public static final void prettyPrint(Document xml,HttpServletResponse response) throws Exception {
Transformer tf = TransformerFactory.newInstance().newTransformer();
tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
tf.setOutputProperty(OutputKeys.INDENT, "yes");
Writer out = new StringWriter();
tf.transform(new DOMSource(xml), new StreamResult(out));
String rss = out.toString();//XML文本字符串
response.setCharacterEncoding("utf-8");
response.setContentType("text/xml;charset=utf-8");
response.setHeader("Cache-control", "no-cache");
PrintWriter o = response.getWriter();
o.println(rss);
out.close();
o.close();
}
public static Date dateFormat(Long t) throws ParseException{
Date d=new Date(t);
SimpleDateFormat sf=new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z",Locale.ENGLISH);
String dd=sf.format(d);
Calendar ca=Calendar.getInstance();
ca.setTime(sf.parse(dd));
ca.add(Calendar.HOUR_OF_DAY,8);
Date c=ca.getTime();
return c;
}
public static String newString(String originalString){
String content = originalString;
// 正则表达式
String regEx = "style=\"(.*?)\"";
Pattern p = Pattern.compile(regEx);
Matcher m = p.matcher(content);
String okContent = null;
if (m.find()) {
okContent = m.replaceAll("");
}else{
okContent=content;
}
return okContent;
}
public static String delLastImg(String originalString){
String content = originalString;
int lastedImgIndex=content.lastIndexOf("<img");
if(lastedImgIndex==-1){
return content;
}else{
String newContent=(String) content.subSequence(0, lastedImgIndex);
return newContent;
}
}
public static String clearIllegalCharacter (String str){
char [] xmlChar = str.toCharArray();
for (int i=0; i < xmlChar.length; ++i) {
if (xmlChar[i] > 0xFFFD)
{
//直接替换掉0xb
xmlChar[i] =' ';
}
else if (xmlChar[i] < 0x20 && xmlChar[i] != 't' & xmlChar[i] != 'n' & xmlChar[i] != 'r')
{
//直接替换掉0xb
xmlChar[i] =' ' ;
}
}
return String.valueOf(xmlChar);
}
}
把主要的思路给大家简单描述一下吧
第一步就是要封装成channel对象,这个channel就是符合rss规范的那个xml文档所必须的!规范内容代码中很详细也贴出了规范模板自己去看吧。。。
第二步我是先将一开始封装的xml上传到指定的服务器先做一下保存,也是为了后期可以知道用户是否使用了该功能。
第三步就是再讲刚上传的xml文档再读取回来,读取城document格式的,因为文档规范要求 ‘
文章正文,为HTML格式,需用<![CDATA[ ]]>封装并 |
包含所有图片的链接,但不要包含样式信息。 |
该元素定义在命名空间(Namespace)Content中, |
需要在rss元素中包含相应的声明。 |
第四步就是把解析完的xml再一次已模板要求的格式返回给浏览器客户端了。
哈哈哈,大致就是这么个流程吧,看了一天的文档,动手写了两天也总算是实现了功能了吧。希望看到这篇文章的人有所收获,也更希望看到这篇文章的大牛能够给出更为详细的指导和指正!