servlet+jsp+filter实现用户管理系统 —— 增删改查的艺术

本文阅读 21 分钟
首页 代码,Java 正文

目录

前言

1 数据库设计

2 查询所有用户

3 添加联系人功能

4 删除用户功能

5 修改联系人功能

5.1 回显部分

5.2 修改部分

6 删除选中的联系人功能

7 分页查询功能

8 复杂条件查询

9 登录功能

10 添加过滤器进行登录验证

11 过滤敏感词汇

下载地址:https://download.csdn.net/download/HNU_Csee_wjw/12493894

前言

本项目是基于java语言的用户管理系统,选用 Servlet + JSP + MySQL + JDBCTempleat + Duird + BeanUtils + tomcat 技术完成,旨在更深入了解JavaWeb工程的MVC开发模式,仅供交流学习使用,在文末将给出完整代码下载链接。具体效果如下:

 

img

将MySQL数据库中存储的信息展示在网页端,实现分页显示,支持添加联系人,删除单个/多个联系人,修改联系人信息,条件查询的功能。内置管理员登陆操作,可供后续继续开发使用。

 

本项目需要导入的jar包如下:

img

 

1 数据库设计

创建user表格,存有id,name,gender,address,qq,email,username,password属性,分别代表每一个用户的id,姓名,性别,年龄,地址,qq号,邮箱,账户名及密码,其中id为主键,如下:

create table user(   -- 创建表
        id int primary key auto_increment,
        name varchar(20) not null,
        gender varchar(5),
        age int,
        address varchar(32),
        qq varchar(20),
        email varchar(50),
        username varchar(50),
        password varchar(50)
);

数据库设计完毕之后,相应的把项目中对应的User类也创建出来,属性为以上一个,并提供所有属性的getter和setter方法,类的toString方法。

package domin;

/**
 * 对应数据库表的实体类
 */
public class User {

    private int id;
    private String name;
    private String gender;
    private int age;
    private String address;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    private String qq;
    private String email;
    private String username;
    private String password;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getQq() {
        return qq;
    }

    public void setQq(String qq) {
        this.qq = qq;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", age=" + age +
                ", address='" + address + '\'' +
                ", qq='" + qq + '\'' +
                ", email='" + email + '\'' +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

 

2 查询所有用户

查询所有用户并显示的设计逻辑如下图所示:

img

当点击主界面index.jsp中的查询按钮时,超链接跳转到后台 UserListServlet.java 程序,完成三个步骤:

  1. 调用service层的findAll(),返回保存有查询到的所有用户信息的List集合
  2. 将List集合存入request域中,令 key = "users"
  3. 转发值list.jsp进行页面展示

UserListServlet.java 对应核心代码为:

 

 

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // 调用UserService完成查询
    UserService service = new UserServiceImpl();
    List<User> users = service.findAll();
    // 将list存入request域
    request.setAttribute("users", users);
    // 转发到list.jsp
    request.getRequestDispatcher("/list.jsp").forward(request, response);
}

list.jsp页面采用 jstl + el 语法的 foreach 语句遍历request域中存储的集合,生成表格展示:

<c:forEach items="${users}" var="user" varStatus="s">
    <tr>
        <td>${s.count}</td>
        <td>${user.name}</td>
        <td>${user.gender}</td>
        <td>${user.age}</td>
        <td>${user.address}</td>
        <td>${user.qq}</td>
        <td>${user.email}</td>
        <td><a class="btn btn-default btn-sm" href="update.html">修改</a>&nbsp;<a class="btn btn-default btn-sm" href="">删除</a></td>
    </tr>
</c:forEach>

service层 UserServiceImpl.java 类定义findAll()方法以供UserListServlet.java 调用:

public class UserServiceImpl implements UserService{

    private UserDao dao = new UserDaoImpl();

    @Override
    public List<User> findAll(){
        // 调用Dao完成查询

        return dao.findAll();
    }
}

该方法调用了dao层 UserDaoImpl.java 中操作数据库查询的findAll()方法:

 

 

public class UserDaoImpl implements UserDao{

    private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());

    @Override
    public List<User> findAll(){
        // 使用JDBC操作数据库
        String sql = "select * from user";
        List<User> users = template.query(sql, new BeanPropertyRowMapper<User>(User.class));

        return users;
    }
}

操作数据库使用了Spring框架对JDBC的封装——JDBCTemplate对象template,来简化java操作数据库的开发,由于这里要执行的是查询操作,所以只需定义查询所有用户的sql语句并调用template的query方法即可。创建template时需要传入一个连接池类DataSource的对象,可以使用阿里巴巴druid数据库连接池技术来封装JDBC的工具类,该工具类封装了加载配置文件,初始化连接池对象,获取连接池对象以及获取连接Connection对象的功能,具体实现如下:

package util;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import javax.xml.crypto.Data;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/**
 * JDBC工具类 使用Durid连接池
 */
public class JDBCUtils {

    private static DataSource ds ;

    static {

        try {
            //1.加载配置文件
            Properties pro = new Properties();
            //使用ClassLoader加载配置文件,获取字节输入流
            InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties");
            pro.load(is);

            //2.初始化连接池对象
            ds = DruidDataSourceFactory.createDataSource(pro);

        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取连接池对象
     */
    public static DataSource getDataSource(){
        return ds;
    }


    /**
     * 获取连接Connection对象
     */
    public static Connection getConnection() throws SQLException {
        return  ds.getConnection();
    }
}

相应的druid配置文件为:

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///day17
username=root
password=root
# 初始化连接数量
initialSize=5
# 最大连接数
maxActive=10
# 最大等待时间
maxWait=3000

3 添加联系人功能

添加联系人的设计逻辑如下图所示:

img

点击添加联系人的按钮之后,跳转至后台 AddUserServlet.java 程序,完成五个步骤:

  1. 设置编码为"utf-8"防止出现中文乱码
  2. 获取添加的新的联系人的所有数据
  3. 根据获取的数据封装联系人为User类的对象
  4. 调用service层的add()方法完成添加
  5. 跳转回userListServlet再次查询所有联系人并展示

基于以上步骤,AddUserServlet.java 的主要代码如下:

@WebServlet("/addUserServlet")
public class AddUserServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 设置编码
        request.setCharacterEncoding("utf-8");
        // 获取数据
        Map<String, String[]> map = request.getParameterMap();
        // 封装对象
        User user = new User();
        try {
            BeanUtils.populate(user, map);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        // 调用service保存
        UserService service = new UserServiceImpl();
        service.addUser(user);

        // 跳转到userListServlet
        response.sendRedirect(request.getContextPath() + "/userListServlet");
    }

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

其中使用了Spring的BeanUtils.populate()方法简化了User类的封装,只需传入初始化的空User类对象user和保存有该user对象的所有属性的map,map又可以由request.getParameterMap()方法快速获取。

 

添加联系人界面 add.jsp 需要为表单设置action属性,将其指向 addUserServlet

<form action="${pageContext.request.contextPath}/addUserServlet" method="post">

service层 UserServiceImpl.java 类定义add(User user)方法以供AddUserServlet.java 调用:

@Override
public void addUser(User user){
    dao.add(user);
}

该方法调用了dao层 UserDaoImpl.java 中添加数据库数据的add(User user)方法:

为了保证动态操作数据库,定义sql语句时将要写数据的地方用 ? 占位,使用template的update()方法时再动态传入数据

@Override
public void add(User user){
    String sql = "insert into user values(null,?,?,?,?,?,?,null,null)";
    template.update(sql, user.getName(), user.getGender(), user.getAge(), user.getAddress(), user.getQq(), user.getEmail());
}

 

4 删除用户功能

删除联系人的设计逻辑如下图所示:

img

点击删除按钮之后,跳转到后台 DelUserServlet.java 程序,完成三个步骤:

  1. 获取待删除用户的id
  2. 调用service层的deleteUser(String id)方法完成删除
  3. 跳转回userListServlet再次查询所有联系人并展示

基于以上步骤,DelUserServlet.java 的主要代码如下:

@WebServlet("/delUserServlet")
public class delUserServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 获取id
        String id = request.getParameter("id");
        // 调用service删除
        UserService service = new UserServiceImpl();
        service.deleteUser(id);
        // 跳转到查询所有servlet
        response.sendRedirect(request.getContextPath() + "/userListServlet");
    }

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

service层 UserServiceImpl.java 类定义deleteUser(String id)方法以供DelUserServlet.java 调用:

由于数据库存储的id是int型数据,所以在这里简单做一个数据类型转换,并利用java自动拆包特点

@Override
public void deleteUser(String id) {
    dao.delete(Integer.parseInt(id));
}

该方法调用了dao层 UserDaoImpl.java 中删除数据库数据的delete(int id)方法:

@Override
public void delete(int id) {
    String sql = "delete from user where id = ?";
    template.update(sql, id);
}

 

修改页面list.jsp,删除操作最好由js控制弹出一个确认提示框再进行相关操作,所以先对按钮增加一个js判断,由于user的id属性只能在foreach循环中获取(局部变量),所以这里js函数要传一个参数:

<a class="btn btn-default btn-sm" href="javascript:deleteUser(${user.id});">删除</a

 

相应的js代码为,跳转链接要把id也传过去:

function deleteUser(id) {
    // 用户确定操作提示
    if (confirm("您确定要删除吗")){
        location.href="${pageContext.request.contextPath}/delUserServlet?id="+id;
    }
}

 

5 修改联系人功能

修改联系人的设计逻辑如下图所示:

img

修改逻辑要分为两个部分进行:回显、修改

5.1 回显部分

点击修改按钮之后,首先进行回显部分的操作,将选中的用户信息事先展示到页面的各个输入框内,跳转到后台 FindUserServlet.java 程序,完成四个步骤:

  1. 获取待修改用户的id
  2. 调用service层的findUserById(String id)方法,查询用户信息
  3. 将User类对象存到request域内
  4. 转发至修改页面update.jsp并作展示

基于以上步骤,FindUserServlet.java 的主要代码如下:

@WebServlet("/findUserServlet")
public class FindUserServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 获取id
        String id = request.getParameter("id");
        // 调用service查询
        UserService service = new UserServiceImpl();
        User user = service.findUserById(id);
        // 将user存入request
        request.setAttribute("user", user);
        // 转发到update.jsp
        request.getRequestDispatcher("/update.jsp").forward(request, response);
    }

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

service层 UserServiceImpl.java 类定义findUserById(String id)方法以供FindUserServlet.java 调用:

@Override
public User findUserById(String id) {
    return dao.findById(Integer.parseInt(id));
}

该方法调用了dao层 UserDaoImpl.java 中查询数据库数据的findById(int id)方法:

由于要返回一个User类对象,所以要使用template.queryForObject()方法

@Override
public User findById(int id) {
    String sql = "select * from user where id = ?";
    return template.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), id);
}

 

获取到待修改的user后,将其各个原本的属性值回显至update.jsp页面的文本输入框内,每一个input输入框的value属性要用el语法设置为待修改用户的属性${user.属性名},如:

 

<input type="text" class="form-control" id="name" name="name" value="${user.name}" readonly="readonly" placeholder="请输入姓名" />

 

5.2 修改部分

对于修改信息界面,需要添加一个隐藏域,用于存储id,后续servlet操作需要根据id来进行

 

<input type="hidden" name="id" value="${user.id}">

 

点击提交按钮以后,跳转到后台 UpdateUserServlet.java 程序,完成四个步骤:

  1. 设置编码为 "utf-8",防止出现中文乱码问题
  2. 获取修改页面提交的表单数据集合map
  3. 使用获得的数据封装User类对象
  4. 调用service层的updateUser(User user)方法完成修改
  5. 跳转回userListServlet再次查询所有联系人并展示

基于以上步骤,UpdateUserServlet.java 的主要代码如下:

@WebServlet("/updateUserServlet")
public class UpdateUserServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        // 获取map
        Map<String, String[]> map = request.getParameterMap();
        // 封装对象
        User user = new User();
        try {
            BeanUtils.populate(user, map);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        // 调用service
        UserService service = new UserServiceImpl();
        service.updateUser(user);
        // 跳转到查询所有Servlet
        response.sendRedirect(request.getContextPath()+"/userListServlet");
    }

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

service层 UserServiceImpl.java 类定义updateUser(User user)方法以供UpdateUserServlet.java 调用:

@Override
public void updateUser(User user) {
    dao.update(user);
}

该方法调用了dao层 UserDaoImpl.java 中修改数据库数据的update(User user)方法:

修改数据库的sql逻辑和添加联系人的类似

@Override
public void update(User user) {
    String sql = "update user set name=?, gender=?, age=?, address=?, qq=?, email=? where id=?";
    template.update(sql, user.getName(), user.getGender(), user.getAge(), user.getAddress(), user.getQq(), user.getEmail(), user.getId());
}

 

6 删除选中的联系人功能

删除联系人的设计逻辑如下图所示:

img

点击删除选中按钮之后,跳转到后台 DelSelectServlet.java 程序,完成三个步骤:

  1. 获取所有选中的用户的id
  2. 调用service层的deleteUsers(String[] ids)方法,删除所有用户
  3. 跳转回userListServlet再次查询所有联系人并展示

基于以上步骤,DelSelectServlet.java 的主要代码如下:

@WebServlet("/delSelectedServlet")
public class DelSelectedServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 获取所有id
        String[] ids = request.getParameterValues("uid");
        // 调用service删除
        UserService service = new UserServiceImpl();
        service.delSelectedUser(ids);
        // 跳转查询所有的servlet
        response.sendRedirect(request.getContextPath() + "/userListServlet");
    }

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

service层 UserServiceImpl.java 类定义delSelectedUser(String[] ids)方法以供DelSelectServlet.java 调用:

@Override
public void delSelectedUser(String[] ids) {
    if (ids != null && ids.length > 0){
        for (String id : ids) {
            // 调用dao删除
            dao.delete(Integer.parseInt(id));
        }
    }
}

由于之前已经实现过dao层删除单个用户的操作,所以这里只需循环遍历id数组,将每一个id对应的单个用户都删除即可。

 

修改用户页面list.jsp,将用户展示的table用表单form包裹起来,这样就可以提交所有选中的行了。checkbox的value值要设置为用户的id。

<td><input type="checkbox" name="uid" value="${user.id}"></td>

 

这样提交时才能把选中的用户的id都提交过去

<form action="${pageContext.request.contextPath}/delSelectedServlet">
    <table border="1" class="table table-bordered table-hover">
        <tr class="success">
            <th><input type="checkbox" id="firstCb"></th>
            <th>编号</th>
            <th>姓名</th>
            <th>性别</th>
            <th>年龄</th>
            <th>籍贯</th>
            <th>QQ</th>
            <th>邮箱</th>
            <th>操作</th>
        </tr>
    
        <c:forEach items="${users}" var="user" varStatus="s">
            <tr>
                <td><input type="checkbox" name="uid" value="${user.id}"></td>
                <td>${s.count}</td>
                <td>${user.name}</td>
                <td>${user.gender}</td>
                <td>${user.age}</td>
                <td>${user.address}</td>
                <td>${user.qq}</td>
                <td>${user.email}</td>
                <td><a class="btn btn-default btn-sm" href="${pageContext.request.contextPath}/findUserServlet?id=${user.id}">修改</a>&nbsp;
                    <a class="btn btn-default btn-sm" href="javascript:deleteUser(${user.id});">删除</a></td>
            </tr>
        </c:forEach>
    
    </table>
</form>

添加script代码,实现手动确认功能

window.onload = function () {
    // 给删除选中按钮添加单击事件
    document.getElementById("delSelected").onclick = function () {
        // 表单提交
        if (confirm("您确定要删除选中条目吗")){
            // 判断是否有选中的条目
            var flag = false;
            var cbs = document.getElementsByName("uid");
            for (var i = 0; i < cbs.length; i ++){
                if(cbs[i].checked){
                    flag = true;
                    break;
                }
            }
            if (flag){
                document.getElementById("form").submit();
            }
        }
    }
}

接下来设置第一行的checkbox可以实现全选全不选,逻辑上只用保证所有的所有checkbox的checked属性与第一行相同即可

 

window.onload = function () {
    // 第一行checkbox实现全选
    document.getElementById("firstCb").onclick = function () {
        var cbs = document.getElementsByName("uid");
        for (var i = 0; i < cbs.length; i ++){
            // 设置所有checkbox的状态与第一行相同
            cbs[i].checked = this.checked;
        }
    }
}

 

7 分页查询功能

由于要实现分页查询,需要总记录数、总页码数、每一页的数据集合、当前的页码以及每一页显示的条目数等数据,所以最好用一个类的对象来保存这些数据,如下图所示

 

img

于是设计PageBean类来保存这些数据,相应的生成getter、setter及toString方法

package domin;

import java.util.List;

/**
 * 分页对象
 */
public class PageBean<T> {
    private int totalCount; // 总记录数
    private int totalPage;  // 总页码
    private List<T> list;   // 每页数据
    private int currentPage;// 当前页码
    private int rows;       // 每页显示记录数

    public int getTotalCount() {
        return totalCount;
    }

    public void setTotalCount(int totalCount) {
        this.totalCount = totalCount;
    }

    public int getTotalPage() {
        return totalPage;
    }

    public void setTotalPage(int totalPage) {
        this.totalPage = totalPage;
    }

    public List<T> getList() {
        return list;
    }

    public void setList(List<T> list) {
        this.list = list;
    }

    public int getCurrentPage() {
        return currentPage;
    }

    public void setCurrentPage(int currentPage) {
        this.currentPage = currentPage;
    }

    public int getRows() {
        return rows;
    }

    public void setRows(int rows) {
        this.rows = rows;
    }

    @Override
    public String toString() {
        return "PageBean{" +
                "totalCount=" + totalCount +
                ", totalPage=" + totalPage +
                ", list=" + list +
                ", currentPage=" + currentPage +
                ", rows=" + rows +
                '}';
    }
}

实际上,服务器端只需接收到页面传过来的当前页码及每页显示的条目数即可得到所有的PageBean类的属性,具体如上图所示

 

实现分页查询并显示的设计如下图所示:

img

查询用户信息时,会自动调用后台 FindUserByPageServlet.java 程序,完成以下四个步骤:

  1. 接受请求参数 currentPage, rows
  2. 调用service层的 findUserByPage(currentPage, rows) 方法获取分页查询结果(PageBean类的对象)
  3. 将该对象存入request域中
  4. 转发至页面list.jsp展示

基于以上步骤,FindUserByPageServlet.java 的主要代码如下:

@WebServlet("/findUserByPageServlet")
public class FindUserByPageServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 获取参数
        String currentPage = request.getParameter("currentPage");
        String rows = request.getParameter("rows");

        if (currentPage == null || "".equals(currentPage)){
            currentPage = "1";
        }
        if (rows == null || "".equals(rows)){
            rows = "5";
        }

        // 调用service查询
        UserService service = new UserServiceImpl();
        PageBean<User> pb= service.findUserByPage(currentPage, rows);
        // 将PageBean存入request
        request.setAttribute("pb", pb);
        // 转发到list.jsp
        request.getRequestDispatcher("/list.jsp").forward(request, response);
    }

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

 

service层 UserServiceImpl.java 类定义findUserByPage(String _currentPage, String _rows)方法以供FindUserByPageServlet.java 调用,在这里要根据传入的当前页面和每页显示条目数来计算出其他所需要的值,具体分为以下七个步骤:

  1. 创建PageBean对象pb
  2. 设置pb的currentPage和rows属性
  3. 调用dao层的finTotalCount()方法查询总记录数totalCount
  4. 计算sql查询的起始数据位置 start = (currentPage - 1) * rows
  5. 调用dao层的findByPage(int start, int rows)方法查询用户集合
  6. 计算总页码,totalCount % rows 并向上取整
  7. 返回pb

基于以上步骤,UserServiceImpl.java 中添加方法代码如下:

@Override
public PageBean<User> findUserByPage(String _currentPage, String _rows) {
    int currentPage = Integer.parseInt(_currentPage);
    int rows = Integer.parseInt(_rows);
    // 创建PageBean对象
    PageBean<User> pb = new PageBean<User>();
    // 查询总记录数
    int totalCount = dao.finTotalCount();
    pb.setTotalCount(totalCount);
    // 计算总页码
    int totalPage = (totalCount % rows) == 0 ? (totalCount/rows) : (totalCount/rows) + 1;
    pb.setTotalPage(totalPage);
    // 设置参数
    if (currentPage > totalPage)
        currentPage = totalPage;
    if (currentPage <= 0)
        currentPage = 1;
    
    pb.setCurrentPage(currentPage);
    pb.setRows(rows);
    // 调用dao查询List集合
    int start = (currentPage - 1) * rows;
    List<User> list = dao.findByPage(start, rows);
    pb.setList(list);
    
    return pb;
}

该方法调用了dao层 UserDaoImpl.java 中查询数据库总数据数的finTotalCount()方法:

@Override
public int finTotalCount() {
    String sql = "select count(*) from user";
    return template.queryForObject(sql, Integer.class);
}

还调用了dao层按照页面可展示用户数查询的findByPage(int start, int rows)方法:

@Override
public List<User> findByPage(int start, int rows) {
    String sql = "select * from user limit ? , ?";

    return template.query(sql, new BeanPropertyRowMapper<>(User.class), start, rows);
}

改造显示页面list.jsp

首先将循环取出每一条用户数据按行展示的jstl语句中foreach的items参数修改

 

items="${pb.list}"

动态显示总记录数

<span style="font-size: 25px; margin-left: 5px;">
    共${pb.totalCount}条记录,共${pb.totalPage}页
</span>

动态修改导航栏个数,高亮当前页面,并绑定servlet

<c:forEach begin="1" end="${pb.totalPage}" var="i">
    <c:if test="${pb.currentPage == i}">
        <li class="active"><a href="${pageContext.request.contextPath}/findUserByPageServlet?currentPage=${i}&rows=5">${i}</a></li>
    </c:if>

    <c:if test="${pb.currentPage != i}">
        <li><a href="${pageContext.request.contextPath}/findUserByPageServlet?currentPage=${i}&rows=5">${i}</a></li>
    </c:if>

</c:forEach>

添加导航栏向左符号《 和 向右符号 》功能

<c:if test="${pb.currentPage == 1}">
    <li class="disabled">
</c:if>

<c:if test="${pb.currentPage != 1}">
    <li>
</c:if>
    <a href="${pageContext.request.contextPath}/findUserByPageServlet?currentPage=${pb.currentPage - 1}&rows=5" aria-label="Previous">
        <span aria-hidden="true">&laquo;</span>
    </a>
</li>

 

<c:if test="${pb.currentPage == pb.totalPage}">
    <li class="disabled">
</c:if>

<c:if test="${pb.currentPage != pb.totalPage}">
    <li>
</c:if>
    <a href="${pageContext.request.contextPath}/findUserByPageServlet?currentPage=${pb.currentPage + 1}&rows=5" aria-label="Next">
        <span aria-hidden="true">&raquo;</span>
    </a>
</li>

完成分页查询后将 UpdateUserServlet.java 和 AddUserServlet.java 和 DelSelectedServlet.java 和delUserServlet.java 的重定向目标换成分页查询的servlet

 

8 复杂条件查询

由于根据条件查询所得的结果也要分页展示,所以仍需借助PageBean对象,如下图所示:

img

当用户点击查询按钮时,会传过来一个表单,这里表单一共有姓名、地址、email三个属性,但有可能用户只输入其中的几个,所以sql语句要动态的生成,如上图所示,如果用户输入了姓名和地址,那么查询总的条目数的模糊查询sql语句变为:

select count(*) from user where name like ? and address like ?;

同理,分页查询的sql语句为:

select * from user where name like ? and address like ? limit ?, ?;

改造list.jsp

给查询部分的表单添加action和method,action传入分页的servlet

 

<form class="form-inline" action="${pageContext.request.contextPath}/findUserByPageServlet" method="post">

注意表单中的input标签必须加上name属性,否则数据到不了服务器。

 

为了查询条件的回显,将查询输入框的value进行设置

<input type="text" name="name" value="${condition.name[0]}" class="form-control" id="exampleInputName2">
<input type="text" name="address" value="${condition.address[0]}" class="form-control" id="exampleInputHome2">

 

<input type="text" name="email" value="${condition.email[0]}" class="form-control" id="exampleInputEmail2">

 

分页导航在点击左右按钮或数字按钮是,要把可能存在的条件查询的条件name=${condition.name[0]}&amp;address=${condition.address[0]}&email=${condition.eamil[0]} 也加上

 

<li><a href="${pageContext.request.contextPath}/findUserByPageServlet?currentPage=${i}&rows=5&name=${condition.name[0]}&address=${condition.address[0]}&email=${condition.eamil[0]}">${i}</a></li>

 

改造FindUserByPageServlet.java

 

添加获取条件查询的参数condition,condition如果为空,则表示默认查询,没有设置查询条件

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // 获取参数
    String currentPage = request.getParameter("currentPage");
    String rows = request.getParameter("rows");

    if (currentPage == null || "".equals(currentPage)){
        currentPage = "1";
    }
    if (rows == null || "".equals(rows)){
        rows = "5";
    }

    // 获取条件查询的参数
    Map<String, String[]> condition = request.getParameterMap();

    // 调用service查询
    UserService service = new UserServiceImpl();
    PageBean<User> pb= service.findUserByPage(currentPage, rows, condition);
    // 将PageBean存入request
    request.setAttribute("pb", pb);
    request.setAttribute("condition", condition);   // 储存查询条件,用于回显
    // 转发到list.jsp
    request.getRequestDispatcher("/list.jsp").forward(request, response);
}

改造UserServiceImpl.java

 

其中的findUserByPage方法添加一个参数map,表示条件查询的条件,在方法里面调用UserDaoImpl类的操作数据库方法时,也多传入一个map参数

@Override
public PageBean<User> findUserByPage(String _currentPage, String _rows, Map<String, String[]> condition) {
    int currentPage = Integer.parseInt(_currentPage);
    int rows = Integer.parseInt(_rows);
    // 创建PageBean对象
    PageBean<User> pb = new PageBean<User>();
    // 查询总记录数
    int totalCount = dao.finTotalCount(condition);
    pb.setTotalCount(totalCount);
    // 计算总页码
    int totalPage = (totalCount % rows) == 0 ? (totalCount/rows) : (totalCount/rows) + 1;
    pb.setTotalPage(totalPage);
    // 设置参数
    if (currentPage <= 0)
        currentPage = 1;
    if (currentPage > totalPage)
        currentPage = totalPage;

    pb.setCurrentPage(currentPage);
    pb.setRows(rows);
    // 调用dao查询List集合
    int start = (currentPage - 1) * rows;
    List<User> list = dao.findByPage(start, rows, condition);
    pb.setList(list);

    return pb;
}

改造UserDaoImpl.java

动态生成sql语句,注意要排除分页条件参数 "currentPage" 和 "rows"

 

@Override
public int finTotalCount(Map<String, String[]> condition) {
    // 定义模板sql
    String sql = "select count(*) from user where 1=1";
    StringBuilder sb = new StringBuilder(sql);
    // 遍历map
    Set<String> keySet = condition.keySet();
    // 参数的集合
    List<Object> params = new ArrayList<>();

    for (String key : keySet) {

        // 排除分页条件参数
        if ("currentPage".equals(key) || "rows".equals(key))
            continue;

        String value = condition.get(key)[0];
        if (value != null && !"".equals(value)){
            // 有值
            sb.append(" and "+ key +" like ? ");
            params.add("%" + value + "%");  // 存储sql语句中 ? 的值
        }
    }
    System.out.println(sb.toString());
    System.out.println(params);
    return template.queryForObject(sb.toString(), Integer.class,params.toArray());
}

findByPage(int start, int rows, Map condition)方法的实现类似于上面的实现,在这里没有将重复代码提取出一个单独的方法,大家可以自己完成一下

@Override
public List<User> findByPage(int start, int rows, Map<String, String[]> condition) {
    String sql = "select * from user where 1 = 1 ";

    StringBuilder sb = new StringBuilder(sql);
    // 遍历map
    Set<String> keySet = condition.keySet();
    // 参数的集合
    List<Object> params = new ArrayList<>();

    for (String key : keySet) {

        // 排除分页条件参数
        if ("currentPage".equals(key) || "rows".equals(key))
            continue;

        String value = condition.get(key)[0];
        if (value != null && !"".equals(value)){
            // 有值
            sb.append(" and "+ key +" like ? ");
            params.add("%" + value + "%");  // 存储sql语句中 ? 的值
        }
    }

    // 添加分页查询
    sb.append(" limit ?,? ");
    // 添加分页查询参数值
    params.add(start);
    params.add(rows);
    sql = sb.toString();
    System.out.println(sql);
    System.out.println(params);
    return template.query(sql, new BeanPropertyRowMapper<>(User.class), params.toArray());
}

 

9 登录功能

img

login.jsp页面

将表单的action设置为对应servlet程序的目录

 

<form action="${pageContext.request.contextPath}/loginServlet" method="post">

验证码图片的src设置为对应servlet程序的目录

<a href="javascript:refreshCode()">
    <img src="${pageContext.request.contextPath}/checkCodeServlet" title="看不清点击刷新" id="vcode"/>
</a>

在javascript中定义刷新验证码的函数

function refreshCode() {
    // 获取验证码图片对象
    var vcode = document.getElementById("vcode");
    // 设置src属性 加上时间戳
    vcode.src = "${pageContext.request.contextPath}/checkCodeServlet?time=" + new Date().getTime();
}

验证码自动生成程序 CheckCodeServlet.java,网上有很多实例代码,这里只将其贴在这里

package web.servlet;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 验证码
 */
@WebServlet("/checkCodeServlet")
public class CheckCodeServlet extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
        
        //服务器通知浏览器不要缓存
        response.setHeader("pragma","no-cache");
        response.setHeader("cache-control","no-cache");
        response.setHeader("expires","0");
        
        //在内存中创建一个长80,宽30的图片,默认黑色背景
        //参数一:长
        //参数二:宽
        //参数三:颜色
        int width = 80;
        int height = 30;
        BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
        
        //获取画笔
        Graphics g = image.getGraphics();
        //设置画笔颜色为灰色
        g.setColor(Color.GRAY);
        //填充图片
        g.fillRect(0,0, width,height);
        
        //产生4个随机验证码,12Ey
        String checkCode = getCheckCode();
        //将验证码放入HttpSession中
        request.getSession().setAttribute("CHECKCODE_SERVER",checkCode);
        
        //设置画笔颜色为黄色
        g.setColor(Color.YELLOW);
        //设置字体的小大
        g.setFont(new Font("黑体",Font.BOLD,24));
        //向图片上写入验证码
        g.drawString(checkCode,15,25);
        
        //将内存中的图片输出到浏览器
        //参数一:图片对象
        //参数二:图片的格式,如PNG,JPG,GIF
        //参数三:图片输出到哪里去
        ImageIO.write(image,"PNG",response.getOutputStream());
    }
    /**
     * 产生4位随机字符串 
     */
    private String getCheckCode() {
        String base = "0123456789ABCDEFGabcdefg";
        int size = base.length();
        Random r = new Random();
        StringBuffer sb = new StringBuffer();
        for(int i=1;i<=4;i++){
            //产生0到size-1的随机值
            int index = r.nextInt(size);
            //在base字符串中获取下标为index的字符
            char c = base.charAt(index);
            //将c放入到StringBuffer中去
            sb.append(c);
        }
        return sb.toString();
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request,response);
    }
}

 

当用户点击登陆按钮时,自动跳转到后台 LoginServlet.java 程序, 完成以下几个步骤:

  1. 设置编码为"utf-8",防止中文乱码问题
  2. 获取用户输入的登陆数据map及 输入的验证码
  3. 验证码校验
  4. 若校验成功则根据 map 封装用户,并调用service层的login(User user)方法查询是否登陆成功
  5. 若登陆成功则返回主页 index.jsp,失败返回登录页面 login.jsp

基于以上步骤,LoginServlet.java 程序的主要代码如下:

@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 设置编码
        request.setCharacterEncoding("utf-8");
        // 获取数据
        String verifycode = request.getParameter("verifycode");
        Map<String, String[]> map = request.getParameterMap();

        // 封装User对象
        User user = new User();
        try {
            BeanUtils.populate(user, map);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        // 验证码校验
        HttpSession session = request.getSession();
        String checkcode_server = (String) session.getAttribute("CHECKCODE_SERVER");
        session.removeAttribute("CHECKCODE_SERVER");

        if (! checkcode_server.equalsIgnoreCase(verifycode)){
            request.setAttribute("login_msg", "验证码错误");
            request.getRequestDispatcher("/login.jsp").forward(request, response);
            return;
        }
        // 调用service查询
        UserService service = new UserServiceImpl();
        User loginUser = service.login(user);
        // 判断是否成功
        if (loginUser != null){
            // 登陆成功
            session.setAttribute("user", loginUser);
            response.sendRedirect(request.getContextPath() + "/index.jsp");
        }else {
            request.setAttribute("login_msg", "用户名或密码错误");
            request.getRequestDispatcher("/login.jsp").forward(request, response);
        }
    }

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

service层 UserServiceImpl.java 类定义login(User user)方法以供 LoginServlet.java 调用:

@Override
public User login(User user){
    return dao.findUserByUsernameAndPassword(user.getUsername(), user.getPassword());
}

该方法调用了dao层的findUserByUsernameAndPassword(String username, String password)判断能否根据用户名和密码找到该用户,如果无法找到即返回null

public User findUserByUsernameAndPassword(String username, String password){
    try{
        String sql = "select * from user where username = ? and password = ?";
        User user = template.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), username, password);
        return user;
    }catch (Exception e){
        e.printStackTrace();
        return null;
    }
}

 

10 添加过滤器进行登录验证

为了保证只有在登录的条件下才可以访问各个资源,需要添加 Filter 来进行验证,验证主要分为以下两步骤:

  1. 访问的是否是与登录相关的资源
  • 是,则直接放行
  • 否,则判断其是否登录

    2. 判断是否已经登陆(Session中是否有User)

  • 是,则放行
  • 否,则跳转至登录页面

 

基于以上步骤,编写Filter代码如下:

package web.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * 登录验证的过滤器
 */
@WebFilter("/*")
public class LoginFilter implements Filter {
    public void destroy() {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        // 强制转换
        HttpServletRequest request = (HttpServletRequest) req;
        // 获取资源请求路径
        String uri = request.getRequestURI();
        // 判断是否包含登录相关资源路径
        // 要注意放行掉 css/js/图片/验证码等
        if (uri.contains("/login.jsp") || uri.contains("/loginServlet") || uri.contains("/css/") || uri.contains("/js/") || uri.contains("/fonts/") || uri.contains("/checkCodeServlet")){
            chain.doFilter(req, resp);
        }else {
            // 验证用户是否登陆
            Object user = request.getSession().getAttribute("user");
            if (user != null){
                // 登陆了,放行
                chain.doFilter(req, resp);
            }else {
                request.setAttribute("login_msg", "您尚未登陆,请登录");
                request.getRequestDispatcher("login.jsp").forward(request, resp);
            }
        }
    }

    public void init(FilterConfig config) throws ServletException {
    }
}

注意与登陆有关的资源不只是相应的.jsp页面和servlet,还有相关的css,js,字体,验证码等资源。

 

11 过滤敏感词汇

有些时候,用户可能会输入一些敏感词汇,我们需要将其和谐掉,然而request没有setAttribute的方法,所以可以使用代理的方式,创建一个新的request代理,增强网页中getParameter()方法,如果做了敏感词汇的河蟹,则放行新的request代理,否则,放行原先的request。

package web.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;

/**
 * 敏感词汇过滤器
 */
@WebFilter("/*")
public class SensitiveWordsFilter implements Filter {
    public void destroy() {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        //1.创建代理对象,增强getParameter方法
        ServletRequest proxy_req = (ServletRequest)Proxy.newProxyInstance(req.getClass().getClassLoader(), req.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //增强getParameter方法
                //判断是否是getParameter方法
                if (method.getName().equals("getParameter")){
                    //增强返回值
                    //获取返回值
                    String value = (String) method.invoke(req, args);
                    if (value != null){
                        for (String str : list) {
                            if (value.contains(str)){
                                value = value.replaceAll(str, "***");
                            }
                        }
                    }
                    return value;
                }
                return method.invoke(req, args);
            }
        });

        // 注意要改成代理后的request
        chain.doFilter(proxy_req, resp);
    }

    private List<String> list = new ArrayList<>();  // 敏感词汇集合
    public void init(FilterConfig config) throws ServletException {
        try {
            // 获取文件真实路径
            ServletContext servletContext = config.getServletContext();
            String realPath = servletContext.getRealPath("/WEB-INF/classes/敏感词汇.txt");
            // 读取文件
            BufferedReader br = new BufferedReader(new FileReader(realPath));
            // 将文件的每一行数据添加到list中
            String line = null;
            while ((line = br.readLine()) != null){
                list.add(line);
            }
            br.close();
            System.out.println(list);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

 

 

 

 

本文为互联网自动采集或经作者授权后发布,本文观点不代表立场,若侵权下架请联系我们删帖处理!文章出自:https://wangjiawei.blog.csdn.net/article/details/106456670
-- 展开阅读全文 --
安全面试之XSS(跨站脚本攻击)
« 上一篇 07-24

发表评论

成为第一个评论的人

热门文章

标签TAG

最近回复