基于JSP的RSS阅读器

阅读器访问地址:http://easyrss.tk/,欢迎体验!

目录

一、    概述 

二、    设计的基本概念和原理 

1.    RSS与RSS阅读器概念

2.    阅读器实现原理

三、    设计方案

1.    架构设计

2.    数据库设计

3.    界面设计

4.    功能设计

5.    网络安全补充

四、    主要源代码 

1.    视图部分

五、    阅读器使用说明

1.    注册与登录

2.    添加与管理RSS源

3.    阅读文章

概述

获得信息是在人类的生活中是必不可少的环节。如果现在的社会对获得信息不快捷,那么这个社会将不会像如今这般的发达和进步。在当今网络技术相当发达的今天,大量的信息充斥在网上。现在网络越来越发达,用户在网上既能工作也能娱乐。当用户在网上需浏览很多个网站才能获取自己多需的信息时,那就感觉很累。因为现在每个网站都有很多信息,找到自己所喜欢的并不是一件轻松的事。这时RSS阅读器的功用就体现出来了。

RSS 是一种简单的订阅方式,它能能够起到聚合网络信息的作用。就像订报纸、杂志一样,您可以订阅任何喜欢的内容。网络用户可以在客户端借助于支持RSS的新闻聚合软件在不打开网站内容页面的情况下,将网站提供的支持RSS输出的网站内容(新闻、Blog等)聚集到RSS阅读器中,用户可以不必登录各个提供信息的网站,而通过"RSS阅读器"来阅读这些内容。

本设计要设计一个RSS阅读器,本阅读器将要实现的功能如下:

  • 随时随地阅读:

通过注册与登录将用户信息保存到服务器,用户在任何时间、任何地方打开网页就可以阅读自己的RSS订阅。

  • RSS源的分组管理与结构化显示:

简洁直观的分类显示用户的RSS订阅列表,简化操作、提高用户的阅读体验。

  • RSS源的添加、修改与删除:

用户能够方便的添加、修改、删除RSS源及分组。

  • RSS源的的解析与显示:

阅读器可以通过用户提供的RSS源URL解析出该RSS源的标题、包含的文章列表、文章地址和文章更新时间等信息,并且能正确地以一定格式显示出来供用户阅读。

设计的基本概念和原理

 

RSS与RSS阅读器概念

  • RSS:

RSS(Really Simple Syndication)是一种描述和同步网站内容的格式,是使用最广泛的XML应用【1】。RSS搭建了信息迅速传播的一个技术平台,使得每个人都成为潜在的信息提供者。发布一个RSS文件后,这个RSS Feed中包含的信息就能直接被其他站点调用,而且由于这些数据都是标准的XML格式,所以也能在其他的终端和服务中使用,是一种描述和同步网站内容的格式。 RSS可以是以下三个解释的其中一个: Really Simple Syndication;RDF (Resource Description Framework) Site Summary; Rich Site Summary【2】。但其实这三个解释都是指同一种Syndication的技术。

RSS目前广泛用于网上新闻频道,blog和wiki,主要的版本有0.91, 1.0, 2.0。使用RSS订阅能更快地获取信息,网站提供RSS输出,有利于让用户获取网站内容的最新更新。网络用户可以在客户端借助于支持RSS的聚合工具软件,在不打开网站内容页面的情况下阅读支持RSS输出的网站内容。

  • RSS阅读器:

RSS阅读器基本可以分为三类【3】:

第一类大多数阅读器是运行在计算机桌面上的应用程序,通过所订阅网站的新闻供应,可自动、定时地更新新闻标题。在该类阅读器中,有Awasu、FeedDemon和RSSReader这三款流行的阅读器,都提供免费试用版和付费高级版。

第二类新闻阅读器通常是内嵌于已在计算机中运行的应用程序中。例如,NewsGator内嵌在微软的Outlook中,所订阅的新闻标题位于Outlook的收件箱文件夹中。另外,Pluck内嵌在Internet Explorer浏览器中!

第三类则是在线的WEB RSS阅读器,其优势在于不需要安装任何软件就可以获得RSS阅读的便利,并且可以保存阅读状态,推荐和收藏自己感兴趣的文章。提供此服务的有两类网站,一种是专门提供RSS阅读器的网站,例如国外的feedly,国内的有道、鲜果、抓虾;另一种是提供个性化首页的网站,例如国外的netvibes、pageflakes,国内的雅蛙、阔地。

本阅读器即是基于JSP的WEB RSS阅读器。

阅读器实现原理

本RSS阅读器使用主要使用JSP技术,根据用户请求的Url借助Rome和Jdom开源工具来解析XML文件获取相应内容,然后将获取到的内容以方便阅读的格式显示在网页上【4】。同时用数据库与JSP相应技术实现阅读器功能的扩展,如:用户阅读列表的管理,显示效果的优化等。

设计方案

架构设计

网站前台使用JSP技术实现页面与功能,后台数据库使用SQLServer作数据管理,阅读内容从RSS源获取,如下图:

数据库设计

数据库使用SQL Server 2008 R2,数据表设计如下:

为保护系统安全,数据表结构不直接给出

界面设计

登录与注册:

登录界面 login.jsp

注册界面signup.jsp:

主页

主页index.jsp(中间的分割线可以拖动)

主页采用框架设计,上部为网站信息和当前用户信息;左边为RSS源列表;右边是主框架,用于显示阅读内容。左右框架大小可以拖动随意调整。

RSS树形列表:

dtree

RSS源显示采用dtree树形列表【5】展示,能够直观展示分组与分组中的RRS源。

文章列表:

以表格形式列出选定RSS订阅中的文章列表

文章内容:

文章内容

文章内容是直接展示文章原网页,保持原文排版,并防止被屏蔽。

添加与管理:

添加与管理都采用弹窗的方式,具体如下:

添加源

添加分组

管理分组

管理源

修改界面与添加界面相似,不再贴图。

提示信息(tips.jsp):

大部分提示信息由tips页面显示,举例如下:

注册提示

未登录提示

退出提示

……

提示页面会根据获取到的参数显示提示信息

功能设计

注册:

注册时,用户名、密码以及确认密码为必填项。由于密码不可见,设置确认密码可以防止输入错误。昵称为选填项,用以更加友好的显示用户名称,若为空显示时将以用户名代替。注册后将注册信息添加到数据库。同时应注意用户名不能和数据库中已有的用户名重复。

登录:

登录时首先获取用户名,连接数据库,查询该用户是否存在,若用户名不存在,跳转提示页面显示相应提示然后返回至登录页面。如果查找到,则再确认密码是否输入正确,密码正确则将用户uid存入session以备后面使用然后跳转至该用户主页。

主页:

主页采用框架布局,主要包括三个部分:

  • 上部:显示网站logo,当前用户信息等。用户信息根据session中的uid从数据库中查询该用户的详细信息。若该用户昵称列表不为空则显示昵称,若为空则显示用户名。
  • 左部:主要是RSS树形列表,详细见后文介绍;
  • 右部:主要显示区域,显示文章列表及文章具体内容,详细见后文介绍。

RSS树形列表:

RSS源显示采用开源的dtree项目进行二次开发。首先根据之前放入session中的uid获取当前用户的RSS分组数据,每获取到一个分组就根据该分组的gid获取属于该组内的的RSS源数据,然后将数据放入dtree中,依次循环直至获取到所有数据。最后由dtree根据获取到的数据按照树形样式显示出来。用户点击列表中的项目将会在右侧主框架中打开相应的文章列表。

主框架的文章列表:

主框架从左框架中发来的链接中提取feed参数中的url值,解析该url获取到相应文章列表。对url和xml文件的解析采用rss解析器(rome.jar和jdom.jar)进行解析获取相应数据。

同时,为了解决部分文章不支持在框架中打开,文章列表设计了在新窗口打开的选项。主要实现方法是:用户改变"在新窗口中打开"的选项的状态后,js立即将该选项状态写入Cookie并发送刷新请求。服务器根据Cookies值动态修改文章链接的target属性并向客户端发送新的页面。

文章内容显示:

文章内容未做任何处理直接显示原文,简单方便。但是有些文章不支持在框架中显示,这时需要勾选"在新窗口中打开"的选项,使文章在新开的浏览器窗口中显示。

RSS源的添加与管理:

添加分为添加源与添加分组,两个界面属于同一个弹窗,通过顶部tab切换,直观快捷。

添加源时需要提交:Feed地址、标题、分组。feed地址填写需要订阅的rss地址;标题可以从Feed地址中提取(具体实现方式为服务器获取到feed地址,根据feed地址解析出订阅标题,然后向客户端发送带有订阅标题的新页面);分组通过列表框选择用户已有的分组。客户端提交表单后,服务器获取到相应的信息并添加至数据库,然后返回成功信息;用户可再次添加新的源。

添加分组时只需要提交需要添加的分组名称即可。

管理界面属于新的弹窗,初始显示用户的所有分组,每个分组包含"展开"、"修改"和"删除"三个菜单。点击分组名或"展开"菜单将会跳转到该分组下的RSS源列表。RSS源列表与分组列表相识,每个分组包含"修改"与"删除"两个菜单。

修改时提交需要修改的项目,服务器根据获取到的gid、lid以及修改后的信息更新对应项目数据并返回相应提示。

删除时提交对应分组的gid或RSS源的lid,服务器根据获取到的id信息输出相应项目并返回提示。删除非空分组时将会删除该分组下所有RSS源(有提示)。

提示信息:

提示信息显示页面或根据获取到的参数显示相应的提示信息并在延迟特定时间后跳转到相应界面,参数为空时显示"未知错误"并跳转至主页。

网络安全补充

为了保护网站与用户数据安全,采取了一下辅助安全措施:

  • 注册输入限制:

用户名只能为字母与数字的组合

密码长度太短

用户名限制为字母与数字组合,防止用户使用SQL语言中的符号进行SQL注入。密码长度限制为8~20位。太短,密码不安全;太长,用户可以通过密码框使用SQL注入攻击

  • 使用过滤器防止SQL注入:

只通过表单的输入限制来防止SQL注入是远远不够的,用户依然可一使用URL参数的形式进行注入攻击。所以我在原项目中加入了一个过滤器来防护一些简单的SQL注入攻击。

该过滤器的原理是,截取用户的所有输入,检测是否是否包含特定关键词,有则重定向到一个错误信息页面error.jsp。没有则通过过滤器。

当然只是用过滤器是不能完全防护SQL注入攻击的,更有效的方式是项目中所有SQL语句都采用预编译语句(PreparedStatement)接口来实现【6】

面对日益复杂的网络安全环境,以上的安全措施只是简单的做了一些防护,对于一个实际项目是远远不够的。在实际的项目还用采取更加严谨,更加有效的措施。

具体方法参考本文:JSP使用过滤器防止SQL注入

主要源代码

视图部分

/RSSreader/WebContent/rsscontent.jsp:

<%@page contentType="text/html"%>

<%@page pageEncoding="UTF-8" import="java.text.SimpleDateFormat"%>

<html>

<script language=javascript>

function setCheck(){

    var newWindow=document.getElementById("newWindow").value;

    if(newWindow==0)

    {

        document.getElementById("check").checked=true;

    }

    else

    {

    document.getElementById("check").checked=false;

    }

}

function check(){

var check=document.getElementById("check").checked;

var feed=document.getElementById("feed").value;

var url="rsscontent.jsp?feed="+feed;

if(check)

    {

    document.cookie="newWindow=0";

    }

else

    {

    document.cookie="newWindow=1";

    }

self.location=url;

}

</script>

<body >

<%

String pageTitle="";

String urlStr = request.getParameter("feed");

String target="";

Cookie cookies[]=request.getCookies(); //读出用户硬盘上的Cookie,并将所有的Cookie放到一个cookie对象数组里面

Cookie sCookie=null;

for(int i=0;i<cookies.length;i++){ //用一个循环语句遍历刚才建立的Cookie对象数组

sCookie=cookies[i]; //取出数组中的一个Cookie对象

if(sCookie!=null){

if(("newWindow").equals(sCookie.getName())){

     pageContext.setAttribute("newWindow",sCookie.getValue());

     System.out.println(pageContext.getAttribute("newWindow"));

     }

}

}

if(pageContext.getAttribute("newWindow")!=null)

{

if(pageContext.getAttribute("newWindow").equals("0")){

    target="_blank";

}

else{

    target="_self";

}

}

try{

/* java.util.Properties systemSettings = System.getProperties();

systemSettings.put("http.proxyHost", "mywebcache.com");

systemSettings.put("http.proxyPort", "8080");

System.setProperties(systemSettings); */

if (!urlStr.startsWith("http://"))

        urlStr = "http://"+request.getParameter("feed");

//String urlStr = "http://feed.cnblogs.com/blog/u/249598/rss";

System.out.println(urlStr);

java.net.URLConnection feedUrl = new java.net.URL(urlStr).openConnection();

feedUrl.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");

com.sun.syndication.io.SyndFeedInput input = new com.sun.syndication.io.SyndFeedInput();

com.sun.syndication.feed.synd.SyndFeed feed = input.build(new com.sun.syndication.io.XmlReader(feedUrl));

pageTitle=feed.getTitle();

%>

<div align="center">

<h1><%=pageTitle%></h1>

<input type="hidden" id="feed" value=<%=urlStr %>>

<input type="hidden" id="newWindow" value=<%=pageContext.getAttribute("newWindow")%>>

<input type=checkbox name="check" id="check" onclick="check()">在新窗口中打开(部分网页不支持在框架中显示,请尝试勾选此项)</input>

<table border=1 cellpadding=3 cellspacing="0">

<tr>

<th>序号</th>

<th>标题</th>

<th>发布时间</th>

</tr>

<%

String date="无";

java.util.List list = feed.getEntries();

for (int i=0; i< list.size(); i++) {

com.sun.syndication.feed.synd.SyndEntry entry = (com.sun.syndication.feed.synd.SyndEntry)list.get(i);

SimpleDateFormat sdf = new SimpleDateFormat();

sdf.applyPattern("yyyy年MM月dd日 HH:mm");

if(entry.getPublishedDate()!=null)

{

    date=sdf.format(entry.getPublishedDate());

}

%>

<tr>

<td><%=i+1%></td>

<td><a id="entry" href="<%=entry.getLink()%>" target=<%=target %>><%=entry.getTitle()%></a></td>

<td><%=date %></td>

</tr>

<%

}

} catch (Exception e) {

    // TODO Auto-generated catch block

    response.sendRedirect("tips.jsp?type=rssContentfail");

    e.printStackTrace();

}

%>

</table>

</div>

<br>

</body>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title><%=pageTitle%></title>

</head>

</html>

/RSSreader/WebContent/addGroup.jsp:

<%@ page language="java" contentType="text/html; charset=utf-8"

pageEncoding="utf-8" import="com.JDBConnection,java.sql.ResultSet,com.dataHelper,java.util.ArrayList"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

<title>添加分组</title>

<link href="css/tab.css" type="text/css" rel="stylesheet" />

<script type="text/javascript" src="js/jquery.js"></script>

</head>

<%

String lastAdd = request.getParameter("lastAdd");

String from = request.getParameter("from");

String tip="";

String backurl="";

String backname="";

if(lastAdd!=null){

     String name =new String(request.getParameter("name").getBytes("ISO8859_1"), "utf-8");

     if(lastAdd.equals("ture")){

            tip="成功添加分组:"+name;    

        }

     if(lastAdd.equals("false")){

            tip="添加分组失败";    

        }    

    }

%>

<body onunload="javascript:;window.opener.location.reload()">

<div id="wrapper">

<div align="center">

<div><a href="addRss.jsp">添加源</a> | 添加分组</div>

<form name="form_addGroup" method="post" action="dealAddGroup">

        <table border="0" align="center">

    <tr>

    <td height="30">组名:</td>

    <td height="30">

    <input type="text" size="40" name="gname">

    <input type="hidden" name="uid" value="<%=session.getAttribute("uid")%>">

    </td>

    </tr>

    <tr>

    <td> </td>

    <td><%=tip %></td>

    </tr>

<tr>

    <td height="30"> </td>

    <td height="30" align="right"><input type="submit" name="Submit" value="添加"></td>

    </tr>

    </table>

     </form>

</div>

</div>

</body>

</html>

/RSSreader/WebContent/addRss.jsp:

<%@ page language="java" contentType="text/html; charset=utf-8"

pageEncoding="utf-8" import="com.JDBConnection,java.sql.ResultSet,com.dataHelper,java.util.ArrayList"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

<title>添加RSS源</title>

<link href="css/tab.css" type="text/css" rel="stylesheet" />

<script type="text/javascript" src="js/jquery.js"></script>

</head>

<%

String lastAdd = request.getParameter("lastAdd");

String title ="";

String url ="";

String tip="";

String Sgid="";

if(request.getParameter("title")!=null)

{

     title=new String(request.getParameter("title").getBytes("ISO8859_1"), "utf-8");

}

if(request.getParameter("url")!=null)

{

     url=request.getParameter("url");

}

if(request.getParameter("group")!=null)

{

     Sgid=request.getParameter("group");

}

if(lastAdd!=null){

     String name =new String(request.getParameter("name").getBytes("ISO8859_1"), "utf-8");

     if(lastAdd.equals("ture")){

            tip="成功添加RSS源:"+name;    

        }

     if(lastAdd.equals("false")){

            tip="添加RSS源失败";    

        }    

    }

%>

<body  onunload="javascript:;window.opener.location.reload()">

<div id="wrapper">

<div align="center">

<div>添加源 | <a href="addGroup.jsp">添加分组</a></div>

<form name="form_addRss" method="post" action="dealAddRss">

        <input type="hidden" name="from" value="addRss">

        <table border="0" align="center">

    <tr>

    <td height="30">Feed地址:</td>

    <td height="30"><input type="text" size="40" name="url" value=<%=url %>></td>

    </tr>

    <tr>

    <td height="30">标题:</td>

    <td height="30">

        <input type="text" name="title" value=<%=title%>>    

        <input align="right" type=button name="getTitle" value=" 从feed中提取" onclick="form_addRss.action=‘getFeedTitle‘;form_addRss.submit()">

    </td>

    </tr>

    <tr>

            <td>分组:</td>

            <td>

            <select name="group">

            <%

                int gid;

                String gname;

                int uid;

                Object memo;

                int i;

            dataHelper dhp=new dataHelper();

            ArrayList<dataHelper.Group> groupList =dhp.getGroup((Integer)session.getAttribute("uid"));

            for(i=0;i<groupList.size();i++)

            {

                dataHelper.Group group=groupList.get(i);

            %>

            <option value="<%=group.getGid()%>"><%=group.getGname()%></option>

            <%

            }

            %>

            </select>

            </td>

    </tr>

    <tr>

    <td> </td>

    <td><%=tip %></td>

    </tr>

    <tr>

    <td height="30"> </td>

    <td height="30" align="right"><input type="submit" name="Submit" value="添加" onclick="form_addRss.action=‘dealAddRss‘"></td>

    </tr>

    </table>

     </form>

</div>

<div align="center">

    </div>

    </div>

</body>

</html>

/RSSreader/WebContent/delete.jsp:

<%@ page language="java" contentType="text/html; charset=utf-8"

pageEncoding="utf-8" import="com.JDBConnection,java.sql.ResultSet"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; utf-8">

<title>editGroup</title>

</head>

<body>

<div align="center">

<%

String name=(String)session.getAttribute("name");

String nickname="";

if(name==null)

{

     response.sendRedirect("index.jsp");

}

%>

<%

    String gid =request.getParameter("gid");

    String lid =request.getParameter("lid");

    String type =request.getParameter("type");

    String url="";

    //DELETE FROM 表名称 WHERE 列名称 = 值

    String sSql="";

    if(type!=null){

         if(type.equals("g")){

             sSql="delete from rssGroup where gid="+gid;

             url="manage.jsp";

            }

         if(type.equals("r")){

             sSql="delete from rssList where lid="+lid;

             url="rssListOfGroup.jsp?gid="+gid;

            }    

        }

    System.out.println("rssListOfGroup.Sql:"+sSql);

    JDBConnection JDBC=new JDBConnection();

    if(JDBC.executeUpdate(sSql)){

        out.print("删除成功");

    }

    else{

        out.print("删除失败");

    }

%>

</div>

<br>

<div align="center">

<a href=<%=url %>>返回</a>

</div>

</body>

</html>

JAVA部分

/RSSreader/src/com/JDBConnection.java

package com;

import java.sql.*;

public class JDBConnection {

private final String url = "jdbc:sqlserver://localhost:1433;DatabaseName=RSSreader";

private final String userName = "sa";

private final String password = "123456";

private Connection con = null;

//通过构造方法加载数据库驱动

static {

try {

Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");

} catch (Exception ex) {

System.out.println("数据库加载失败");

}

}

//创建数据库连接

public boolean creatConnection() {

try {

con = DriverManager.getConnection(url, userName, password);

con.setAutoCommit(true);

} catch (SQLException e) {

System.out.println(e.getMessage());

System.out.println("creatConnectionError!");

}

return true;

}

public void close(Connection con,Statement stmt,PreparedStatement pst,ResultSet rs){

if(rs!=null){

try {

rs.close();

} catch (SQLException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

if(stmt!=null){

try {

stmt.close();

} catch (SQLException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

if(pst!=null){

try {

pst.close();

} catch (SQLException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

if(con!=null){

try {

con.close();

} catch (SQLException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

public void closeConnection(){

if(con!=null){

try {

con.close();

} catch (SQLException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

//对数据库的增加、修改和删除的操作

public boolean executeUpdate(String sql) {

if (con == null) {

creatConnection();

}

try {

Statement stmt = con.createStatement();

int iCount = stmt.executeUpdate(sql);

System.out.println("操作成功,所影响的记录数为" + String.valueOf(iCount));

this.close(con, stmt,null, null);

return true;

} catch (SQLException e) {

System.out.println(e.getMessage());

         return false;

}

}

//对数据库的查询操作

public ResultSet executeQuery(String sql) {

ResultSet rs;

try {

if (con == null) {

creatConnection();

}

Statement stmt = con.createStatement();

try {

rs = stmt.executeQuery(sql);

} catch (SQLException e) {

System.out.println(e.getMessage());

return null;

}

} catch (SQLException e) {

System.out.println(e.getMessage());

System.out.println("executeQueryError!");

return null;

}

return rs;

}

}

/RSSreader/src/com/SqlFilter.java

package com;

import java.io.IOException;

import java.util.Enumeration;

import javax.servlet.Filter;

import javax.servlet.FilterChain;

import javax.servlet.FilterConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

//过滤sql关键字的Filter

public class SqlFilter implements Filter {

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

HttpServletRequest req = (HttpServletRequest) request;

HttpServletResponse res = (HttpServletResponse) response;

//获得所有请求参数名

Enumeration params = req.getParameterNames();

String sql = "";

while (params.hasMoreElements()) {

//得到参数名

String name = params.nextElement().toString();

//System.out.println("name===========================" + name + "--");

//得到参数对应值

String[] value = req.getParameterValues(name);

for (int i = 0; i < value.length; i++) {

sql = sql + value[i];

}

}

System.out.println("被匹配字符串:"+sql);

if (sqlValidate(sql)) {

res.sendRedirect("error.jsp");

} else {

chain.doFilter(req, res);

}

}

//效验

protected static boolean sqlValidate(String str) {

str = str.toLowerCase();//统一转为小写

//String badStr = "and|exec";

String badStr = "‘|and|exec|execute|insert|select|delete|update|count|drop|chr|mid|master|truncate|char|declare|sitename|net user|xp_cmdshell|or|like";

/*String badStr = "‘|and|exec|execute|insert|create|drop|table|from|grant|use|group_concat|column_name|" +

"information_schema.columns|table_schema|union|where|select|delete|update|order|by|count|*|" +

"chr|mid|master|truncate|char|declare|or|;|-|--|+|,|like|//|/|%|#";    */    //过滤掉的sql关键字,可以手动添加

String[] badStrs = badStr.split("\\|");

for (int i = 0; i < badStrs.length; i++) {

if (str.indexOf(badStrs[i]) !=-1) {

    System.out.println("匹配到:"+badStrs[i]);

    return true;

}

}

return false;

}

public void init(FilterConfig filterConfig) throws ServletException {

//throw new UnsupportedOperationException("Not supported yet.");

}

public void destroy() {

//throw new UnsupportedOperationException("Not supported yet.");

}

}

/RSSreader/src/dataCtrl/addGroup.java

package dataCtrl;

import com.JDBConnection;

public class addGroup {

    private String gname;

    private String uid;

    public String getGname() {

        return gname;

    }

    public void setGname(String gname) {

        this.gname = gname;

    }

    public String getUid() {

        return uid;

    }

    public void setUid(String uid) {

        this.uid = uid;

    }

    public boolean doAddGroup(){

        String    sSql    = "insert into rssGroup(gname,uid) values(‘"+gname+"‘,"+uid+")";

        System.out.println(sSql);

        JDBConnection JDBC=new JDBConnection();

        return JDBC.executeUpdate(sSql);    

    }

}

/RSSreader/src/dataCtrl/addRss.java

package dataCtrl;

import java.sql.*;

import com.JDBConnection;

import javafx.beans.property.adapter.JavaBeanProperty;

public class addRss {

    private String title;

    private String url;

    private String gid;

    public String getTitle() {

        return title;

    }

    public void setTitle(String title) {

        this.title = title;

    }

    public String getUrl() {

        return url;

    }

    public void setUrl(String url) {

        this.url = url;

    }

    public String getGid() {

        return gid;

    }

    public void setGid(String gid) {

        this.gid = gid;

    }

    public boolean doAddRss() throws SQLException{

        //INSERT INTO table_name (列1, 列2,...) VALUES (值1, 值2,....);

        String    sSql    = "insert into rssList(title,url,gid) values(‘"+title+"‘,‘"+url+"‘,"+gid+")";

        System.out.println(sSql);

        JDBConnection JDBC=new JDBConnection();

        return JDBC.executeUpdate(sSql);

    }

}

/RSSreader/src/dataCtrl/dealaddGroup.java

package dataCtrl;

import java.io.IOException;

import java.sql.SQLException;

import javax.servlet.ServletException;

import javax.servlet.annotation.WebServlet;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

/**

* Servlet implementation class dealaddGroup

*/

@WebServlet("/dealaddGroup")

public class dealaddGroup extends HttpServlet {

    private static final long serialVersionUID = 1L;

/**

* @see HttpServlet#HttpServlet()

*/

public dealaddGroup() {

super();

// TODO Auto-generated constructor stub

}

    /**

     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)

     */

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // TODO Auto-generated method stub

        response.getWriter().append("Served at: ").append(request.getContextPath());

    }

    /**

     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)

     */

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // TODO Auto-generated method stub

        String gname=request.getParameter("gname");

        String sgname =new String(gname.getBytes("ISO8859_1"), "utf-8");

        String uid=request.getParameter("uid");

        addGroup adgp=new addGroup();

        adgp.setGname(sgname);

        adgp.setUid(uid);

        if(adgp.doAddGroup()){

    request.getRequestDispatcher("/addGroup.jsp?name="+gname+"&lastAdd=ture").forward(request,response);

            //response.sendRedirect("index.jsp");

        }

        else{

    request.getRequestDispatcher("/addGroup.jsp?name="+gname+"&lastAdd=false").forward(request,response);

        }

        doGet(request, response);

    }

}

/RSSreader/src/dataCtrl/dealAddRss.java

package dataCtrl;

import java.io.IOException;

import java.sql.SQLException;

import javax.servlet.ServletException;

import javax.servlet.annotation.WebServlet;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

/**

* Servlet implementation class dealAddRss1

*/

@WebServlet("/dealAddRss1")

public class dealAddRss extends HttpServlet {

    private static final long serialVersionUID = 1L;

/**

* @see HttpServlet#HttpServlet()

*/

public dealAddRss() {

super();

// TODO Auto-generated constructor stub

}

    /**

     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)

     */

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // TODO Auto-generated method stub

        response.getWriter().append("Served at: ").append(request.getContextPath());

    }

    /**

     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)

     */

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // TODO Auto-generated method stub

        //response.setContentType("text/html;charset=utf-8");

        String title=request.getParameter("title");

        String stitle =new String(title.getBytes("ISO8859_1"), "utf-8");

        String url =request.getParameter("url");

        String gid=request.getParameter("group");

        addRss adrs=new addRss();

        adrs.setTitle(stitle);

        adrs.setUrl(url);

        adrs.setGid(gid);

        try {

            if(adrs.doAddRss()){

                request.getRequestDispatcher("/addRss.jsp?name="+title+"&lastAdd=ture").forward(request,response);

                //response.sendRedirect("index.jsp");

            }

            else{

                request.getRequestDispatcher("/addRss.jsp?name="+title+"&lastAdd=false").forward(request,response);

            }

        } catch (SQLException e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        }

        doGet(request, response);

    }

}

/RSSreader/src/dataCtrl/getFeedTitle.java

package dataCtrl;

import java.io.IOException;

import javax.servlet.ServletException;

import javax.servlet.annotation.WebServlet;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import com.sun.syndication.io.FeedException;

/**

* Servlet implementation class getFeedTitle

*/

@WebServlet("/getFeedTitle")

public class getFeedTitle extends HttpServlet {

    private static final long serialVersionUID = 1L;

/**

* @see HttpServlet#HttpServlet()

*/

public getFeedTitle() {

super();

// TODO Auto-generated constructor stub

}

    /**

     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)

     */

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // TODO Auto-generated method stub

        response.getWriter().append("Served at: ").append(request.getContextPath());

    }

    /**

     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)

     */

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // TODO Auto-generated method stub

        String urlStr =request.getParameter("url");

        String title="";

        String gid=request.getParameter("group");

        String lid=request.getParameter("lid");

        String Lastgid=request.getParameter("Lastgid");

        String from=request.getParameter("from");

        if (!urlStr.startsWith("http://"))

                urlStr = "http://"+request.getParameter("feed");

        //String urlStr = "http://feed.cnblogs.com/blog/u/249598/rss";

        System.out.print(urlStr);

        java.net.URLConnection feedUrl = new java.net.URL(urlStr).openConnection();

        feedUrl.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");

        com.sun.syndication.io.SyndFeedInput input = new com.sun.syndication.io.SyndFeedInput();

        com.sun.syndication.feed.synd.SyndFeed feed;

        try {

            feed = input.build(new com.sun.syndication.io.XmlReader(feedUrl));

            title=feed.getTitle();

        } catch (IllegalArgumentException | FeedException e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        }

        if(from.equals("addRss")){

        response.sendRedirect("addRss.jsp?title="+title+"&url="+urlStr+"&group="+gid+"&Lastgid="+Lastgid+"&lid="+lid);

        }

        if(from.equals("editRss")){

            response.sendRedirect("editRss.jsp?title="+title+"&url="+urlStr+"&group="+gid+"&Lastgid="+Lastgid+"&lid="+lid);

            }

        doGet(request, response);

    }

}

/RSSreader/src/loginCtrl/checkUser.java

package loginCtrl;

import java.sql.ResultSet;

import java.sql.SQLException;

import com.JDBConnection;

public class checkUser {

    private int uid=0;

    private String name=" ";

    private String pwd=" ";

    public int getUid() {

        return uid;

    }

    public void setUid(int uid) {

        this.uid = uid;

    }

    public String getName() {

        return name;

    }

    public void setName(String name) {

        this.name = name;

    }

    public String getPwd() {

        return pwd;

    }

    public void setPwd(String pwd) {

        this.pwd = pwd;

    }

    public boolean check() {

        String sPwd="";

        String    sSql    = "select * from users where uname=‘"+name+"‘";

        System.out.println(sSql);

        try {

            JDBConnection JDBC=new JDBConnection();

            ResultSet rs    =JDBC.executeQuery(sSql);

            System.out.println(rs.isBeforeFirst());

            if(rs.next())

            {

                System.out.println("?");

                sPwd=rs.getString("upwd");

                uid=rs.getInt("uid");

                //System.out.println(sPwd);

                JDBC.closeConnection();

            }

            } catch (SQLException e) {

                // TODO Auto-generated catch block

                System.out.println(e.getMessage());

                System.out.println("ConnectError!");

            }

        System.out.println(name+"-"+pwd+"-"+sPwd);

        if(pwd.equals(sPwd))

        {

            return true;

        }

        else

        {

            return false;

        }

    }

}

/RSSreader/src/loginCtrl/dealsignup.java

package loginCtrl;

import java.io.IOException;

import java.sql.Connection;

import javax.servlet.ServletException;

import javax.servlet.annotation.WebServlet;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpSession;

/**

* Servlet implementation class dealsignup

*/

@WebServlet("/dealsignup")

public class dealsignup extends HttpServlet {

    private static final long serialVersionUID = 1L;

/**

* @see HttpServlet#HttpServlet()

*/

public dealsignup() {

super();

// TODO Auto-generated constructor stub

}

    /**

     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)

     */

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // TODO Auto-generated method stub

        response.getWriter().append("Served at: ").append(request.getContextPath());

    }

    /**

     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)

     */

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // TODO Auto-generated method stub

        response.setContentType("text/html;charset=utf-8");

        String name =request.getParameter("account");

        String url =request.getParameter("url");

        String nickname =request.getParameter("nickname");

        if (nickname==null||nickname=="")

            nickname=null;

        else

            nickname=new String(request.getParameter("nickname").getBytes("ISO8859_1"), "utf-8");

        String pwd= request.getParameter("password");

        String enpwd= request.getParameter("ensurepassword");

        signup snp=new loginCtrl.signup();

        System.out.println("dealsingnup:"+name+"-"+nickname+"-"+pwd+"-"+enpwd);

        if(!pwd.equals(enpwd))

        {

            response.sendRedirect("tips.jsp?type=signupwithwrongpwd");

            System.out.println("两次密码不一致");

        }

        else

        {

            snp.setName(name);

            snp.setNickname(nickname);

            snp.setPwd(pwd);

            Connection conn=snp.connect();

            if(snp.signupcheck(conn))

            {

                if(snp.dosignup(conn))

                {

                    response.sendRedirect("tips.jsp?type=signupsuccess&str="+name);

                    System.out.println("注册成功");

                }

                else

                {

                    response.sendRedirect("tips.jsp?type=sinupfail");

                    System.out.println("未知错误");

                }

            }

            else

            {

                response.sendRedirect("tips.jsp?type=usernamevalid");

                System.out.println("用户名已被注册");

            }

            snp.closeConnection(conn);

        }

        doGet(request, response);

    }

}

/RSSreader/src/loginCtrl/loginCheck.java

package loginCtrl;

import java.io.IOException;

import java.sql.ResultSet;

import javax.servlet.ServletException;

import javax.servlet.annotation.WebServlet;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpSession;

import com.JDBConnection;

/**

* Servlet implementation class loginCheck

*/

@WebServlet("/loginCheck")

public class loginCheck extends HttpServlet {

    private static final long serialVersionUID = 1L;

/**

* @see HttpServlet#HttpServlet()

*/

public loginCheck() {

super();

// TODO Auto-generated constructor stub

}

    /**

     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)

     */

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // TODO Auto-generated method stub

        response.getWriter().append("Served at: ").append(request.getContextPath());

    }

    /**

     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)

     */

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // TODO Auto-generated method stub

        String name =request.getParameter("account");

        String pwd= request.getParameter("password");

        checkUser uc=new loginCtrl.checkUser();

        System.out.println(name+"-"+pwd);

        uc.setName(name);

        uc.setPwd(pwd);

        if(uc.check())

        {

            HttpSession session=request.getSession();

            session.setAttribute("name", name);

            session.setAttribute("uid", uc.getUid());

            response.sendRedirect("index.jsp");

        }

        else

        {

            response.sendRedirect("tips.jsp?type=logfail");

        }

        doGet(request, response);

    }

}

/RSSreader/src/loginCtrl/signup.java

package loginCtrl;

import java.sql.*;

public class signup {

    private String name=" ";

    private String nickname="";

    private String pwd=" ";

    private Connection conn;

    public String getName() {

        return name;

    }

    public void setName(String name) {

        this.name = name;

    }

    public String getNickname() {

        return nickname;

    }

    public void setNickname(String nickname) {

        this.nickname = nickname;

    }

    public String getPwd() {

        return pwd;

    }

    public void setPwd(String pwd) {

        this.pwd = pwd;

    }

    public Connection connect() {

        try

        {

            String JDriver="com.microsoft.sqlserver.jdbc.SQLServerDriver";//SQL数据库引擎

            String connectDB= "jdbc:sqlserver://localhost:1433;DatabaseName=RSSreader";//数据源

            try

            {

                Class.forName(JDriver);//加载数据库引擎,返回给定字符串名的类

            }catch(ClassNotFoundException e)

            {

         //e.printStackTrace();

                System.out.println("加载数据库引擎失败:"+e);

                System.exit(0);

            }

            System.out.println("数据库驱动成功");

            String user="sa";

            String password="123456";

            conn=DriverManager.getConnection(connectDB,user,password);//连接数据库对象

            System.out.println("连接数据库成功");

        }

        catch(Exception e)

        {

            System.out.println("链接数据库失败:"+e);

        }

        return conn;

    }

    public boolean signupcheck(Connection conn)

    {

        try{

        Statement stmt=conn.createStatement();

        ResultSet rs=stmt.executeQuery("select * from users where uname=‘"+name+"‘");

        if(!rs.next())

            return true;

        }

        catch(Exception e)

        {

            System.out.println("查询失败"+e);

            return false;

        }

        return false;

    }

    public boolean dosignup(Connection conn) {

        try

        {    

                PreparedStatement ps = conn.prepareStatement("insert into users (uname,nickname,upwd) values(?,?,?)");

                ps.setString(1, name);

                ps.setString(2, nickname);

                ps.setString(3, pwd);

                int num =ps.executeUpdate();

                System.out.println(num);

                if (num==0)

                    return false;

                ps.close();

        }

        catch(Exception e)

        {

            System.out.println("添加失败"+e);

            return false;

        }

        return true;    

    }

    public boolean closeConnection(Connection conn)

    {

        try

        {

            System.out.println("关闭链接成功");

        conn.close();

        }

        catch(Exception e)

        {

            System.out.println("关闭链接失败"+e);

            return false;    

        }

        return true;

    }

}

  阅读器使用说明

注册与登录

使用阅读器之前需要注册用户并登录,服务器会保存用户的RSS列表数据。未登录之前阅读器会推荐阅读阅读列表,并指导用户寻找RSS源,如下图:

注册时输入为空或输入错误会有相应提示

注册成功后会提示用户记住用户名,然后跳转登录页面:

登录后来到主页,同样阅读器会引导新用户寻找RSS源,点击"寻找RSS源"会跳转到下面这样一个神奇的页面:

然后就是添加自己需要订阅的RSS源了。

添加与管理RSS源

注册登录后,就可以添加自己喜爱的RSS源了。点击添加,弹出添加对话框,这是我们需要先添加分组再添加RSS源。我们先添加两个测试分组,如下图:

添加成功会有提示:

添加好分组后再来添加RSS源,我们测试添加一个"知乎每日精选"的订阅:www.zhihu.com/rss 直接在浏览器中打开是这样的:

这个当然看不懂,我们将url粘贴到阅读器中,标题可以不用自己填,点击"从Feed中获取",阅读器会根据url从RSS源中解析出RSS源的标题。选择"测试分组1",保存。

第一个源已经添加成功了,接下来再添加几个源用作测试。

添加完成后,关闭对话框,左边列表会自动刷新,点击展开显示已添加的RSS源:

这样我们就完成了RSS源的添加。如果有些源名字、分组有错或者源失效了、自己不想再订阅了,我们可以对其进行编辑或者直接删除。点击"管理",弹出管理对话框:

此时我们可以修改分组名或者将其删除:

注意:删除分组会删除该组内的所有RSS源。修改之后:

点击展开,可以显示该分组下的所有RSS源:

同样我们可以修改RSS源或者将其删除,方法与对组的操作类似,修改后:

修改完成后,关闭对话框,同样左边列表会自动刷新:

阅读文章

点击其中一个RSS源,会在右边显示出该订阅的文章列表:

然后点击文章的标题就会显示文章的具体内容:

如果勾选了"在新窗口中打开",文章将会在新的浏览器窗口中打开。

好了基于JSP的RSS阅读器的介绍就到这里,阅读器访问地址:http://easyrss.tk/

时间: 2024-10-18 16:26:46

基于JSP的RSS阅读器的相关文章

对下载到RSS阅读器里订阅内容

网络推广方法有很多,RSS推广就是其中的一种,RSS订阅能够为网站增加访问量,这是众人皆知的事实.不过,如何推广RSS,让更多人知道并促使更多人订阅RSS,却是一个很大的问题.下面就有我给大家讲解一下什么事RSS推广,如何利用RSS进行网络推广. 首先来说说什么是RSS? RSS是在线共享内容的一种简单方式(也叫聚合内容,Really Simple Syndication).通常在时效性比较强的内容上使用RSS订阅能更快速获取信息.网站提供RSS输出,有利于让用户获取网站内容的最新信息.网络用户

C#版简易RSS阅读器

C#版简易RSS阅读器.由VB版修改完成,感谢aowind的技术支持! 源代码: using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.Xml; using System.IO; using System.Threading; namespace Yu

Reeder 4 for Mac(RSS阅读器客户端)

Reeder 4 for Mac是macOS 系统上最优秀的 RSS 阅读器客户端之一,Reeder 4 Mac拥有精致的界面和舒适的交互,支持 iPhone.iPad 和 Mac.在您的日常应用程序中添加RSS阅读器可以帮助您与自己喜欢的兴趣保持联系,而Reeder 4 Mac是一种非常有用且优雅的方法,可以访问大量多样的RSS feed.欢迎前来下载. reeder 4 mac软件功能 Reeder 4增加了新功能...... 新的仿生阅读模式 与iCloud同步的应用内读取服务 文章列表中

android rss阅读器开发一点小技巧

这几天一直在学习开发Rss阅读器,遇到一个很坑的问题,InputSource这里总是出错.弄了好久,终于让我找到一个解决方法----看代码: new Thread(){ @Override public void run() { try { URL url = new URL(RSS_URL); SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser parser = factory.newSAXParser();

RSS阅读器&amp;BT sync

①RSS阅读器? 答:RSS阅读器是一种软件或是说一个程序,这种软件可以自由读取RSS和Atom两种规范格式的文档,且这种读取RSS和Atom文档的软件有多个版本,由不同的人或公司开发,有着不同的名字. Really Simple Syndication “真正简单的聚合”就是RSS的英文原意.把新闻标题.摘要(Feed).内容按照用户的要求,“送”到用户的桌面就是RSS的目的. ②BT sync BT sync 是一个文件同步工具,让你在几台不同的设备之间,同步文件.

那些值得收藏的神奇的网站,使用RSS阅读器订阅喜欢的网站 --授人以鱼不如授人以渔

知识越是分享越是精通.姿势越学习越标准越熟练..咳咳..好邪恶...在分享自己的神器之际把自己淘宝的地方和大家共享,我觉得还是十分有必要的 一些神器的网站 包含放放面面,随机排序: 1.iPc.me    异次元软件世界  师出同门,分享有意思的东西,好软件,IT业界新闻,限免推荐,正版国内代购,推广各种优秀实用软件.网络资源. 2.On HAX 软钥  On HAX是我最喜爱的网站之一,软件xx工具和Serial聚集地.笔者在这上面淘到东西还真不少,小红伞Pro.Revo Uninstalle

SAX实现的简易RSS阅读器

RSS RSS是简易信息聚合,用户可以订阅多个RSS源,从而在不打开网站页面的情况下阅读RSS输出的网站内容. 一个RSS文件就是一段规范的XML数据,如:http://sse.tongji.edu.cn/SSEMainRSS.aspx SAX与DOM SAX(Simple API for XML)是一个事件驱动的顺序访问XML解析API.不同于DOM(Document Object Model)将整个XML文档作为一个整体,SAX解析器按顺序解析XML文档的每个部分. DOM解析器在任何处理开

RSS阅读器

RSS阅读器基本可以分为三类. 第一类大多数阅读器是运行在计算机桌面上的应用程序,通过所订阅网站的新闻供应,可自动.定时地更新新闻标题.在该类阅读器中,有Awasu.FeedDemon和RSSReader这三款流行的阅读器,都提供免费试用版和付费高级版 第二类新闻阅读器通常是内嵌于已在计算机中运行的应用程序中.例如,NewsGator内嵌在微软的Outlook中,所订阅的新闻标题位于Outlook的收件箱文件夹中.另外,Pluck内嵌在Internet Explorer浏览器中! 第三类则是在线

网易新闻RSS阅读器

首先需要分析网易RSS订阅中心的网页布局情况. 网易RSS订阅中心:http://www.163.com/rss/ 你会发现RSS文件由一个<channel>元素及其子元素组成,除了频道本身内容之外,<channel>还以项的形式包含表示频道元数据的元素. 其中频道下面主要的三个元素就是: 1.title:频道或提要的名称. 2.link:与该频道关联的WEB站点或者站点区域的URL. 3.description:简要介绍该频道是做什么的. 当然还有其他子元素是可选的,比如常用的有