新闻门户网站综合项目——Maven、JavaEE三层架构(web,services,dao)、JSP、Servlet、Filter、JdbcTemplate、JavaBean

本项目已经完结,项目日期3月10日至3月17日,历时8天,在三实验楼207机房和作者寝室完成该项目。您可以访问以下链接体验该项目,发现有任何bug欢迎向我反馈。

AnonyEast's 新闻门户 http://news.anonyeast.top

项目源码地址 https://gitee.com/AnonyEast/news-portal

一、项目概述

本项目为一个简易的新闻门户网站,该门户网站需要实现新闻网站的基础功能。前端页面使用的是现成的,极其丑陋,还望见谅。

前台页面需要为读者展示新闻分类、展示新闻标题,其中新闻的排序要按照发布时间最新的新闻呈现在前。点击相应的分类可以显示该分类下的新闻,点击新闻标题可以进入该条新闻的阅读页面,在阅读页面中可以发表对该条新闻的评论。同时前台页面还要提供管理员登录入口。

后台页面为新闻管理员操作页面,需要管理员登录后才能进入,具有添加新闻、编辑新闻、添加新闻分类、编辑新新闻分类分、管理新闻评论这5个功能。

本项目要对以上的功能进行实现,并确保后台页面在管理员未登录的情况下不能进入。

二、技术实现

1.前端页面:使用JSP页面作为视图供用户访问,并结合HTML、CSS、JavaScript前端三大件展示出完整的用户页面。对于需要与后台进行交互的请求,使用form表单或超链接来实现跳转到后台的Servlet中,对于动态参数则使用EL表达式结合JSTL标签来进行获取。Servlet完成相关请求后将处理结果返回给前端JSP页面,前端页面通过前端三大件结合EL表达式将相关信息展示给用户。

2.后台:后台开发使用JavaEE三层架构,即web层、services层和dao层。

web层使用servlet技术,通过servlet接收用户从前端页面提交的信息,分析用户正在执行的操作,将这些信息封装为Java对象或者相关的参数,传递给services层对应的方法进行业务处理。

services层实现业务逻辑,即对新闻门户的各个功能模块需要完成的操作通过Java代码做具体的实现,如果某个功能需要进入数据库完成操作,则交给dao层进行处理。

dao层是对数据进行增删改查,这里要用到JDBC来实现对数据库的操作,然后将增删改查的结果返回给services层。

3.JDBC:JDBC是通过Java对MySQL数据库的连接,用于对数据库实现增删改查。这里我使用了阿里巴巴的Druid连接池和Spring框架下的JdbcTemplate技术,大幅减少了代码量,因为我们的目的仅仅是为了执行SQL语句,而不是使用大量的语句浪费在对数据库进行连接和资源释放。

4.domain层:对用户、新闻、评论、分类分别创建JavaBean,并结合BeanUtils工具类,在需要封装对象时实现快速封装,从而免去了手动调用setter方法,节省了代码量。

5.Junit单元测试:当我们实现一个功能却不知道我们写的代码正确与否,但是当时的代码并不足以让我们去实际环境进行测试时,可以使用Junit单元测试,检测我们代码的正确性。

6.项目构建:项目构建使用Maven,包括Tomcat运行环境同样从Maven插件中获取,最新版本为Tomcat7。所有依赖jar包也从Maven中央仓库获取,省去了自己导入jar包的过程,当项目转移到其他电脑时也可以快速构建项目。由于Maven中央仓库服务器在国外,为了加快下载速度,配置镜像服务器为阿里云服务器,这样可以提升依赖下载速度。

综上所述,本项目一共用到的技术有:

Maven、JavaSE基础、JSP、EL表达式、JSTL标签库、Servlet、JavaBean、BeanUtils快速封装JavaBean对象、JDBC、Druid连接池、JdbcTemplate。

所需要的全部依赖JAR包如下,通过配置Maven的pom.xml配置文件,这些依赖会自动进行下载。

三、项目结构

1.用户页面和管理员页面

包含了前端三大件(HTML、CSS、JavaScript),但这三大件我们几乎不对其进行修改,完成用户请求提交的页面均是JSP页面。

用户页面:主页、新闻阅读页面。

后台管理页面:后台管理主页、显示新闻列表、新闻添加、新闻编辑、分类添加、分类修改功能。

2.数据库结构

数据库使用MySQL数据库,共4张表:评论表、新闻表、分类表、用户表,各个表所具有的字段如图所示。

评论表(comments):保存每一条新闻评论的详细信息,其中cnid属性作为外键约束关联news表的主键nid,一条新闻可以有多条评论。

新闻表(news):保存每一条新闻的详细信息,其中ntid属性作为外键约束关联topic表的主键tid,一个分类可以有多条新闻。

分类表(topic):保存每一个新闻分类,例如:国内新闻、国际新闻、娱乐新闻等。

用户表(news_users):保存用户名和密码。

3.Java代码结构

domain层:存放各个实体类的JavaBean。

util层:存放JDBC工具类,大大减少执行SQL语句的代码量。

servlet层:接收用户在前端JSP页面发出的请求,并将参数传递给services层执行相应方法。

services层:完成新闻门户各个模块的业务逻辑具体实现。

dao层:对数据进行增删改查,执行SQL语句。

filter层:拦截用户请求,判断请求是否非法,此处用于检测用户是否登录和敏感词汇过滤。

test层:需要进行单元测试的功能在该层进行测试。

四、功能实现

项目源代码已经发布在gitee,可以直接clone到本地,使用IDEA运行代码。以下文章不会大段大段的上传每一个Java文件的源代码,需要看源码请自行在仓库中浏览或者下载。

项目源码地址 https://gitee.com/AnonyEast/news-portal

项目体验地址 AnonyEast's 新闻门户 http://news.anonyeast.top

1.项目构建

配置pom.xml文件,添加tomcat7运行环境和本项目相关依赖。代码较长,请访问源码链接查看。

pom.xml源码

2.新闻门户首页的实现

首页效果如图所示,主要难点在于

  • 用户访问网站后如何直接访问显示主页的servlet
  • 中间部分能够显示所有的新闻分类,并且能够通过点击分类,切换不同类型的新闻。
  • 在不点击具体分类的情况下,按照时间顺序展示所有新闻。点击了分类后,按照时间顺序展示该分类下的新闻。
  • 新闻列表可以分页展示,每页20条。仅当分类下存在新闻时才显示页码和换页按钮。
  • 左侧分别显示指定分类的新闻。

具体实现代码:

1.用户访问后直接进入主页

写一个IndexServlet类,该类的功能就是实现自动跳转到newsServlet获取全部新闻。

package servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 实现首页跳转
 */
public class IndexServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.getRequestDispatcher("newsServlet?method=getIndexByTopic&pageNo=1&size=20&tid=0").forward(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
}

然后在WEB-INF/web.xml文件中配置当用户直接访问该项目时,进入IndexServlet,在indexServlet中又一次进行跳转,这样就实现了当用户访问后自动进入newsServlet。

<welcome-file-list>
  <welcome-file>indexServlet</welcome-file>
</welcome-file-list>

<servlet>
  <servlet-name>IndexServlet</servlet-name>
  <servlet-class>servlet.IndexServlet</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>IndexServlet</servlet-name>
  <url-pattern>/indexServlet</url-pattern>
</servlet-mapping>

2.实现分类分页显示新闻

后端servlet代码如下,位于NewsServlet中。在用户访问首页后,通过IndexServlet,直接跳转到了newsServlet?method=getIndexByTopic&pageNo=1&size=20&tid=0,这样就进入了以下方法。

if (method.equals("getIndexByTopic")) {//通过新闻分类显示新闻列表
    //获取所有topic
    List<Topic> topicList = topicServices.getAllTopic();
    session.setAttribute("allTopic", topicList);
    //获取页码、每页条数、获取具体新闻类型
    String pageNo = request.getParameter("pageNo");
    String size = request.getParameter("size");
    String tid = request.getParameter("tid");
    //获取该类型的新闻有多少条,计算需要多少页来展示
    int countNews = newsServices.countNews(Integer.parseInt(tid));
    int totalPages = (int) Math.ceil(countNews / Double.parseDouble(size));
    //将获取到的页码、每页条数、获取具体新闻类型存入request域
    request.setAttribute("pageNo", Integer.parseInt(pageNo));
    request.setAttribute("totalPages", totalPages);
    request.setAttribute("tid", Integer.parseInt(tid));
    //获取具体类型的新闻对象并存入request域
    List<News> newsList = newsServices.getNewsByNumber(Integer.parseInt(tid), Integer.parseInt(pageNo), Integer.parseInt(size));
    request.setAttribute("newsList", newsList);
    request.getRequestDispatcher("/index.jsp").forward(request, response);
}

前端代码如下,位于index.jsp中

<div class="content">
    <ul class="class_date">
        <!-- 新闻主题链接区域 -->
        <%--使用jstl处理集合数据--%>
        <c:forEach items="${allTopic}" var="topic" varStatus="i">
            <a href="newsServlet?method=getIndexByTopic&pageNo=1&size=20&tid=${topic.tid}"
               class="topic"><b>${topic.tname}</b></a>
            <c:if test="${i.count % 9 == 0}">
                <li id="class_month"></li>
            </c:if>
        </c:forEach>
    </ul>
    <ul class="classlist">
        <!-- 分页显示新闻区域 -->
        <%--判断是否有新闻数据--%>
        <c:choose>
            <c:when test="${empty newsList}">
                <li>暂无新闻数据</li>
            </c:when>
            <c:otherwise>
                <c:forEach items="${newsList}" var="news">
                    <li>
                        <a href="newsServlet?method=readNews&nid=${news.nid}">${news.ntitle}</a>
                        <span>${news.nmodifyDate}</span>
                    </li>
                </c:forEach>
            </c:otherwise>
        </c:choose>
        <c:if test="${not empty newsList}">
            <p align="center" style="font-size: 14px;">第${pageNo}页 共${totalPages}页</p>
        </c:if>
        <a href="newsServlet?method=getIndexByTopic&pageNo=1&size=20&tid=0">全部</a>
        <c:if test="${pageNo != 1}">
            <a href="newsServlet?method=getIndexByTopic&pageNo=1&size=20&tid=${tid}">首页</a>
        </c:if>
        <c:if test="${pageNo != 1}">
            <a href="newsServlet?method=getIndexByTopic&pageNo=${pageNo-1}&size=20&tid=${tid}">上一页</a>
        </c:if>
        <c:set var="totalPagesValue" value="${totalPages}"/>
        <c:if test="${pageNo != totalPagesValue && totalPagesValue != 0}">
            <a href="newsServlet?method=getIndexByTopic&pageNo=${pageNo+1}&size=20&tid=${tid}">下一页</a>
        </c:if>
        <c:if test="${pageNo != totalPagesValue && totalPagesValue != 0}">
            <a href="newsServlet?method=getIndexByTopic&pageNo=${totalPages}&size=20&tid=${tid}">末页</a>
        </c:if>
    </ul>
</div>

(1)显示所有分类:可以看到第3行调用了TopicServices层的getAllTopic方法获取所有的新闻分类并将每个分类封装成了Topic对象,存储在了一个List集合当中。然后我们将这个List集合存入了session中,并命名键名为allTopic,这样前端页面就能通过EL表达式获取到这个List集合中的所有Topic对象,再通过Topic对象的getter方法可以获取到分类名称,将所有的分类名称显示在页面上。

(2)新闻分页显示:跳转时指定了参数pageNo=1&size=20&tid=0,意思是当前页码第1页,每页显示20条,分类id为0。这三个参数可以被newsServlet接收,通过NewsServices层的countNews方法计算出总页数,然后将页码、总页数、新闻类型存入request域,这样主页就能通过EL表达式获取到当前页码和总页数并显示在页面上,新闻类型用于保存当前处于哪个分类,翻页时也只需要对页码进行加减,并通过EL表达式获取到分类名称,对newsServlet再次发送getIndexByTopic请求,获取下一页的新闻。

(3)分类显示新闻:由于每一个分类的Topic对象均已经存入session,这样每个分类都能通过EL表达式获取到自己的tid值,这样我们只需要将相应的tid参数修改为具体分类的tid值,传递到newsServlet层,就可以实现分类显示新闻。如果该分类下没有新闻,则显示"暂时没有新闻",并且不显示页码和翻页按钮,此需求通过EL表达式的判断是否为空来决定是否显示。

(4)按时间顺序显示新闻:此需求需要在对数据库查询时实现,通过SQL语句的sort by限制条件,可以指定排列方式。具体在NewsDao中的实现如下

public List<News> getNewsByNumber(int tid, int no, int size) {
    String sql = "SELECT * FROM news where ntid = ? ORDER BY nmodifyDate DESC LIMIT ?,?";
    //tid为0时代表查询所有新闻
    if (0 == tid) {
        sql = "SELECT * FROM news ORDER BY nmodifyDate DESC LIMIT ?,?";
        List<News> newsList = template.query(sql, new BeanPropertyRowMapper<News>(News.class), (no - 1) * size, size);
        return newsList;
    }
    List<News> newsList = template.query(sql, new BeanPropertyRowMapper<News>(News.class), tid, (no - 1) * size, size);
    return newsList;
}

3.新闻阅读页面的实现

后端servlet代码如下,位于NewsServlet中。当用户点击新闻标题时,链接地址是newsServlet?method=readNews&nid=${news.nid},其中这样就能进入NewsServlet中的readNews方法,指定新闻nid的值在数据库中查询新闻的详细信息,将查询结果封装为News对象返回。

同时,新闻页面还会显示评论,由于comments表的cnid关联了news表的nid,我们同时可以调用CommentsServices层的getCommentsByCnid方法获取这一条新闻的评论,并将这些评论封装为Comments对象,存储在List集合中。

最后将News对象和Comments对象存储到request域中,这样在前端页面,就能通过EL表达式获取到对象中的属性,并展示在页面上。

else if (method.equals("readNews")) {//新闻阅读页面
  //获取新闻的nid,通过nid查询新闻的全部信息,封装News对象
  String nid = request.getParameter("nid");
  News newsByNid = newsServices.getNewsDetailsByNid(Integer.parseInt(nid));
  //将查询到的新闻存入session
  request.setAttribute("news", newsByNid);
  //获取评论
  //1.进入服务层获取评论对象
  List<Comments> commentsByCnid = commentsServices.getCommentsByCnid(Integer.parseInt(nid));
  //2.将评论对象存入request域
  request.setAttribute("comments", commentsByCnid);
  //跳转到新闻阅读页面
  request.getRequestDispatcher("/newspages/news_read.jsp").forward(request, response);
}

前端代码为news_read.jsp,代码较长,此处不放代码,源码链接如下

news_read.jsp源码

4.添加评论的实现

添加评论的功能在新闻阅读页面就要实现,采用表单提交的方式,跳转到CommentsServlet中完成处理,前端代码就在上面的news_read.jsp源码中,后端的Servlet代码如下。servlet接收评论的各项参数,通过BeanUtils封装Comments对象,传递给CommentsServices层进行处理。

if (method.equals("addComment")) {//添加评论
    Map parameterMap = request.getParameterMap();
    //封装comment对象
    Comments comment = new Comments();
    BeanUtils.populate(comment, parameterMap);
    //添加时间
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    String date = sdf.format(new Date());
    comment.setCdate(date);
    //进入服务层添加评论到数据库
    int count = commentsServices.addComments(comment);
    System.out.println("有" + count + "条评论被添加:" + comment);
    response.sendRedirect(request.getContextPath() + "/newsServlet?method=readNews&nid=" + comment.getCnid());
}

5.登录到后台管理页面和退出登录的实现

此功能同样采用表单提交的方式,在主页和新闻阅读页面设置登录入口,以表单形式提交用户名和密码,将用户名和密码封装为User对象,交由UserServlet进行处理,在UserServices层中判断用户名和密码是否正确,如果正确返回该用户的User对象,并将这个对象保存到Session中,为后续做用户是否登录的判断做铺垫。登陆失败则将错误信息存储到request域,在前端使用EL表达式展示出错误信息。

退出登录,则直接在session中移除已经登录的User对象即可,为什么这么做在后面会写。

if (method.equals("login")) {//用户登录
    //获取用户发送的数据
    Map<String, String[]> parameterMap = request.getParameterMap();
    //使用BeanUtils封装正在登录的用户
    User loginUser = new User();
    BeanUtils.populate(loginUser, parameterMap);
    //进入服务层判断用户密码是否正确
    User user = userServices.login(loginUser);
    if (user == null) {
        //登录失败,写入错误信息,跳转回首页
        request.setAttribute("loginError_msg", "很高兴,你的用户名或密码错误");
        request.getRequestDispatcher("/indexServlet").forward(request, response);
    } else {
        //登录成功
        //获取所有topic
        List<Topic> allTopic = topicServices.getAllTopic();
        //将User对象和topic列表分别存入session和request域
        session.setAttribute("user", user);
        request.setAttribute("topicList", allTopic);
        request.getRequestDispatcher("/newspages/admin.jsp").forward(request, response);
    }
} else if (method.equals("loginOut")){//退出登录
    //在session中移除user对象
    session.removeAttribute("user");
    //存储提示信息
    request.setAttribute("loginError_msg", "您已退出登录");
    //跳转到主页
    request.getRequestDispatcher("/indexServlet").forward(request, response);
}

前端页面代码:

<form action="userServlet/news" method="post" onsubmit="return check()">
    <input type="hidden" name="method" value="login"/>
    <label> 登录名 </label>
    <input type="text" name="uname" value="" id="uname" class="login_input"/>
    <label> 密码 </label>
    <input type="password" name="upwd" value="" id="upwd" class="login_input"/>
    <input type="submit" class="login_sub" value="登录"/>
    <label id="error" style="color: red">${loginError_msg}</label>
    <a href="indexServlet" class="login_link">返回首页</a>
    <img src="images/friend_logo.gif" alt="Google" id="friend_logo"/>
</form>

6.后台管理页面:管理新闻分类的实现

先说一下其中一个功能:显示所有分类页面的展示,首先要显示出所有分类,这个通过servlet即可实现,在servlet中调用TopicServices层的方法,代码如下

else if (method.equals("refreshTopic")) {//刷新分类页面
  //获取所有topic
  List<Topic> topicList = topicServices.getAllTopic();
  //将topic列表存入request域
  request.setAttribute("topicList", topicList);
  request.getRequestDispatcher("/newspages/admin.jsp").forward(request, response);
}

前端页面则通过EL表达式结合JSTL的forEach循环,将所有的分类展示在页面上

<c:forEach items="${topicList}" var="topic">
    <li> &#160;&#160;&#160;&#160; ${topic.tname} &#160;&#160;&#160;&#160;
        <a href='../newspages/topic_modify.jsp?method=modifyTopic&tid=${topic.tid}&tname=${topic.tname}'
           class="tpsMdfLink" id='${topic.tid}:${topic.tname}'>修改</a>
        <a href='../adminServlet/news?method=deleteTopic&tid=${topic.tid}&tname=${topic.tname}' class="tpsDelLink"
           id='${topic.tid}'>删除</a>
    </li>
</c:forEach>

删除分类、修改分类、添加分类这三个功能,实现原理是一样的,均是通过特定的servlet链接或者表单,先进入Servlet获取相关参数,然后去Services层调用相关的方法,再去Dao层对数据库进行操作,操作完成后逐层返回,根据返回值结果判断是否操作成功,将操作成功的消息存入request域,在前端页面使用EL表达式展示消息。这里就不做演示了。

7.后台管理页面:管理新闻的实现

修改新闻,我们要先获取到这一条新闻的News对象,获取到News对象后,将News对象存入request域,然后在新闻编辑页面通过EL表达式获取News对象的各个属性,就能显示出新闻的各项信息和具体内容显示在JSP页面了。

else if (method.equals("showNewsDetailsByNid")) {//获取新闻的详细信息展示在编辑新闻页面上
  //获取需要修改的新闻nid
  String nid = request.getParameter("nid");
  //获取所有topic,用于所有显示新闻类型
  List<Topic> allTopic = topicServices.getAllTopic();
  //topic列表存入request域
  request.setAttribute("topicList", allTopic);
  //根据nid进入服务层获取News对象
  News news = newsServices.getNewsDetailsByNid(Integer.parseInt(nid));
  //将News对象存入request域
  request.setAttribute("news", news);
  //获取评论
  //1.进入服务层获取评论对象
  List<Comments> commentsByCnid = commentsServices.getCommentsByCnid(Integer.parseInt(nid));
  //2.将评论对象存入request域
  request.setAttribute("comments", commentsByCnid);
  //跳转到新闻编辑页面
  request.getRequestDispatcher("/newspages/news_modify.jsp").forward(request, response);
}

前端页面则是点击需要修改的新闻后,访问../adminServlet/news?method=showNewsDetailsByNid&nid=${news.nid},进入新闻编辑页面。

分页显示所有新闻的功能,与前面前端用户页面实现分页显示的实现原理完全相同。

添加新闻,通过填写表单-提交到Servlet-Servlet封装新闻对象-Services层处理-Dao层添加新的新闻到数据库的方式。

删除新闻则更简单,只需要获取到新闻的nid值,通过SQL语句删除即可。

8.当用户直接通过链接访问后台时,自动检查用户是否已经登录,如果未登录则跳转回首页

此功能通过Filter过滤器实现,拦截的链接有以下链接(在web.xml文件填写),因为这些链接是涉及到后台页面的。

<filter>
  <filter-name>LoginFilter</filter-name>
  <filter-class>filter.LoginFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>LoginFilter</filter-name>
  <url-pattern>/adminServlet/news</url-pattern>
  <url-pattern>/newspages/admin.jsp</url-pattern>
  <url-pattern>/newspages/news_add.jsp</url-pattern>
  <url-pattern>/newspages/news_modify.jsp</url-pattern>
  <url-pattern>/newspages/topic_modify.jsp</url-pattern>
  <url-pattern>/newspages/topic_add.jsp</url-pattern>
  <url-pattern>/newspages/showNews.jsp</url-pattern>
  <url-pattern>/newspages/showTopics.jsp</url-pattern>
</filter-mapping>

实现思路为,当用户登录成功后,会将登录的User对象存入Session,在过滤器中判断Session中是否存在User对象即可,如果存在则放行,不存在则跳转到首页,并显示“请先登录”。用户退出登录后,会清除Session中的User对象。

代码实现如下:

public class LoginFilter implements Filter {
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        //强制转换为HttpServletRequest
        HttpServletRequest request = (HttpServletRequest) req;
        //1.获取资源的请求路径(谁请求这个资源)
        String uri = request.getRequestURI();
        //2.判断是否包含与登录相关的资源路径,并排除css/jd/图片/验证码等资源
        if(uri.contains("userServlet")){
            //如果包含,说明用户就是想去登录,直接放行
            chain.doFilter(req, resp);
        }else{
            //如果不包含,说明用户并不是在执行登录操作,需要判断用户是否已经登录
            //3.从Session中获取User对象
            Object user = request.getSession().getAttribute("user");
            if (user != null){
                //用户已登录,放行
                chain.doFilter(req,resp);
            }else {
                //用户未登录,跳转回主页
                request.setAttribute("loginError_msg","请先登录");
                request.getRequestDispatcher("/indexServlet").forward(req,resp);
            }
        }
    }
}

9.过滤评论中的敏感词汇

使用一个文件来保存敏感词汇,读取这个文件,当用户发表的评论出现了文件中的敏感词汇时,自动将敏感词汇替换为"***"显示在页面上。

实现思路为使用Filter拦截访问CommentsServlet的请求,这些请求都是发表评论的请求。拦截这些请求后,做动态代理,在动态代理中对getParametergetParameterMap方法进行增强。如果获取到的参数值有敏感词汇,则将敏感词汇替换为"***"后,再将参数值返回,这样存入数据库的评论中敏感词汇也会被***代替。

此处有一个坑,对getParameterMap方法增强时,如果出现敏感词汇,是不能直接通过Map集合的put方法直接试图覆盖原来的参数的值的,具体原因看以下文章。

动态代理踩坑:java.lang.IllegalStateException: No modifications are allowed to a locked ParameterMap

public class SensitiveWordsFilter implements Filter {
    private List<String> wordsList = new ArrayList<String>();//敏感词汇列表

    public void init(FilterConfig config) throws ServletException {
        try {
            //加载敏感词汇
            //1.获取文件路径
            URL resource = SensitiveWordsFilter.class.getResource("words.txt");
            String path = resource.getPath();
            //2.读取文件
            InputStreamReader isr = new InputStreamReader(new FileInputStream(path), "utf-8");
            BufferedReader br = new BufferedReader(isr);
            //3.将文件的每一行数据添加到list中
            String line = null;
            while (null != (line = br.readLine())) {
                wordsList.add(line);
            }
            br.close();
            System.out.println(wordsList);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void doFilter(final ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        //1.创建代理对象,增强获取参数值的方法(该方法用于获取表单填写的信息)
        /**
         * 动态代理增强Request对象
         * 三个固定参数:
         *      1.类加载器:真实对象.getClass().getClassLoader()
         *      2.接口数组:真实对象.getClass().getInterfaces()
         *      3.处理器:new InvocationHandler()
         */
        ServletRequest proxy_req = (ServletRequest) Proxy.newProxyInstance(req.getClass().getClassLoader(), req.getClass().getInterfaces(), new InvocationHandler() {
            /**
             * 代理逻辑编写的方法:代理对象调用的所有方法都会触发该方法执行
             * @param proxy 代理对象
             * @param method 代理对象调用的方法,被封装为一个对象
             * @param args 代理对象调用方法时,传递的所有实参,被封装为一个数组
             * @return
             * @throws Throwable
             */
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //判断是否是getParameter方法
                if (method.getName().equals("getParameter")) {
                    /*
                     * String getParameter(String name)
                     */
                    String value = (String) method.invoke(req, args);//返回从表单获取到的属性值
                    //增强返回值
                    if (null != value) {
                        //有返回值,遍历敏感字符
                        for (String word : wordsList) {
                            //返回值中包含敏感字符,替换为***
                            if (value.contains(word)) {
                                value = value.replaceAll(word, "***");
                            }
                        }
                    }
                    return value;//返回被修改后的返回值
                    //判断是否是getParameter方法
                } else if (method.getName().equals("getParameterMap")) {
                    /*
                     * Map<String,String[]> getParameterMap()
                     */
                    Map<String,String[]> map = new HashMap<String,String[]>((Map) method.invoke(req, args));//返回从表单获取到的由属性名和属性值组成的map
                    if (!map.isEmpty()) {
                        //遍历map
                        Set<String> keySet = map.keySet();
                        for (String key : keySet) {
                            //获取每一个属性名对应的属性值
                            String[] valueArray = map.get(key);
                            //遍历敏感词汇
                            for (String word : wordsList) {
                                String value = String.valueOf(valueArray[0]);
                                if (value.contains(word)){
                                    //如果属性值包含敏感词,将敏感词替换为"***"
                                    valueArray[0] = value.replaceAll(word, "***");
                                    map.put(key,valueArray);
                                }
                            }
                        }
                    }
                    return map;
                } else {
                    Object obj = method.invoke(req, args);
                    return obj;
                }
            }
        });
        //放行时要放行代理对象
        chain.doFilter(proxy_req, resp);
    }
}

五、总结

本项目使用了Maven、JavaSE基础、JSP、EL表达式、JSTL标签库、Servlet、JavaBean、BeanUtils快速封装JavaBean对象、JDBC、Druid连接池、JdbcTemplate技术,是一个综合性项目,对于我来说是一个非常好的练手项目,巩固了JavaWeb的学习,加深了对Servlet的理解,初步实践了JavaEE三层架构的思想,并学会了使用Maven构建一个项目,这些对我将来的Java框架学习之路打下了一个基础。

本项目已经上传到服务器,可以直接访问,欢迎体验:AnonyEast's 新闻门户 http://news.anonyeast.top

AnonyEast

一个爱折腾的技术萌新

3 Comments

  • 棒~

    • @可可爱爱没脑袋的小仙女 嘻嘻

  • 杨东,做的很好,进行努力。。。达内李胤东

留下你的评论

*评论支持代码高亮<pre class="prettyprint linenums">代码</pre>

相关推荐