通过Javaweb实现一个简易留言板

150 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第9天,点击查看活动详情

学到过滤后,最简单的检测项目就是留言板了,通过对内容的过滤,考察大家对过滤,文件读写,乱码处理,用户优先级的选择如何实现提供思路。

  • 首页为所有留言页面。未登录用户可以查看留言及回复,但不能留言。
  • 登录用户可以留言,可以删除自己的留言。
  • 管理员可以回复留言,可以删除任意留言

过滤需求:

  • 留言内容中不能出现中文乱码,且空格和换行需要保留。
  • 提交留言时,留言内容中如有禁用词,需用*进行替换禁用词。
  • 禁用词以 txt 文本文件形式保存,方便随时修改。过滤器中使用初始化参数配置该文件路径。
  • 未登录用户和非管理员用户不能看到回复页面。

数据库设计

image.png

image.png

sql代码如下:

CREATE TABLE `guestbook_table` ( 
`id` int NOT NULL AUTO_INCREMENT,
`userId` int DEFAULT NULL COMMENT '留言人 id',
`subject` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '留言主题', 
`content` text CHARACTER SET utf8 COLLATE utf8_bin COMMENT '留言内容', 
`addMsgTime` bigint DEFAULT NULL COMMENT '留言时间',
`isReplied` tinyint DEFAULT '1' COMMENT '是否已回复,默认 1 未回复;2 已回复',
`reUserId` int DEFAULT NULL COMMENT '回复人 id', 
`reply` text CHARACTER SET utf8 COLLATE utf8_bin COMMENT '回复内容', 
`reTime` bigint DEFAULT NULL COMMENT '回复时间', 
PRIMARY KEY (`id`) ) 
ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
CREATE TABLE `user_table` ( 
`id` int NOT NULL AUTO_INCREMENT, 
`name` varchar(20) COLLATE utf8_bin DEFAULT NULL COMMENT '用户名', 
`password` varchar(20) COLLATE utf8_bin DEFAULT NULL COMMENT '密码', 
`isAdmin` tinyint DEFAULT '0' COMMENT '是否是管理员,默认 0 否,1 是', 
PRIMARY KEY (`id`) ) 
ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

具体实现

新建项目,目录如下

image.png

对应相关model这里不进行讲解

不过msg里面需要添加一个 boolean类型方法,isReplied,作为判断是否回复

相关dao包下:

接口:对用户本实验只需要增加就行,但是考虑更加全面一点,我们可以把查询全部用户,根据id查询用户,分页查询用户,增删改用户添加进去。

image.png

而实现类,实现接口。重写方法。

对增删改模板如下:

  1. 先根据返回值定义相关对象,或者属性
  2. 书写sql语句:比如增加INSERT INTO user(name,password) VALUES(?,?)文号是占位符,这里可以采用预编译,即PreparedStatement 然后呢根据传入参数的方法,进行问号赋值,再进行executeUpdate()方法
  3. 返回定义属性 实例:
       @Override
public int insert(User user) {
        int rows = 0;
        String sql = "INSERT INTO user(name,password) VALUES(?,?)";
        try {
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, user.getUsername());
            pstmt.setString(2, user.getPassword());
            rows = pstmt.executeUpdate();
            System.out.println("DAO 添加用户: " + rows + ", " + user);
            } catch (SQLException e) {
            System.out.println("DAO 添加用户失败");
           e.printStackTrace();
            }
       return rows;
        }

对查询模板如下:

  1. 定义对象
  2. 书写sql
  3. 对象赋值
  4. 返回对象 参考如下:
        @Override
public User get(String name, String password) {
        User user = null;
        String sql = "SELECT * FROM user WHERE name=? AND password=?";

        try {
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, name);
            pstmt.setString(2, password);
            rs = pstmt.executeQuery();
            while (rs.next()) {
                user = new User();
                user.setId(rs.getInt("id"));
                user.setUsername(rs.getString("name"));
                user.setPassword(rs.getString("password"));
                user.setIsAdmin(rs.getInt("isAdmin"));
                }
            } catch (SQLException e) {
            System.out.println("DAO 获取登录用户失败:name=" + name + ", password=" + password);
            e.printStackTrace();
            }

        return user;
        }

service包下

为了解耦合,推荐面向接口编程,新建接口,接口书写和dao类似,不过把int类型的增删改返回值变成boolean类型。

image.png 实现: 本次实现非常简单,先定义dao对象,采用多态,声明接口实现是类,然后调用其方法

        @Override
public boolean add(User user) {
        if (user == null) {
            return false;
            }
        return userDao.insert(user) == 1 ? true : false;
        }

如何进行分页? 我们知道传入了两个参数,我们int类型可以直接调用方法,但是string类型参数需要进行转换才能使用。

首先定义分页的两个参数,用国语传入sql里面的?。然后通过Integer.parseInt()进行类型转换。最后调用方法


UserDao userDao = new UserDaoImpl();

       @Override
public List<User> findByPage(String sPage, String sPageSize) {
         int page = 1;
         int pageSize = 10;
         if (sPage != null || !sPage.equals("")) {
            page = Integer.parseInt(sPage);
            if (page < 1) {
                page = 1;
                 }
            }
         if (sPageSize != null || !sPageSize.equals("")) {
            pageSize = Integer.parseInt(sPageSize);
             }

         return findByPage(page, pageSize);
         }

controller

list页面:就是全查或者分页查询界面。

我们通过注解@WebServlet("/list") 声明这个是控制类,然后让这个类继承HttServlet,重写里面的doget,dopost,或者service方法。然后多态实现service实例。在里面声明对象通过service调用方法进行赋值,然后封装到session或者request里面,方便前提页面获取数据。然后进行转发或者重定向,把页面转到list前端页面

image.png 关于转发和重定向

  • 转发 req.getRequestDispatcher("URL地址").forward(req,resp);
  • 重定向 resp.sendRedirect(req.getContextPath()+ "/xxx.jsp"); 重定向是浏览器向服务器发送一个请求并收到响应后再次向一个新地址发出请求,转发是服务器收到请求后为了完成响应跳转到一个新的地址;重定向至少请求两次,转发请求一次;

重定向两次请求不共享数据,转发一次请求共享数据(在request级别使用信息共享,使用重定向必然出错);

重定向地址栏会发生变化,转发地址栏不会发生变化;

转发 image.png image.png

重定向 image.png

如何过滤

  1. 判断违禁词 新建文件xx,里面添加违禁词。 新建一个MyMsgReqWrapper类,通过@WebFilter声明是filter类,并另外新建类继承HttpServletRequestWrapper重写getParameter方法。为什么不直接在filter类写。每一次getParameter都需要进行违规判断,如果直接filter写,代码很冗余,而且不好控制。

重写方法后,每次调用都会进行判断。

违禁词,空格,换行(也是新建一个文件进行处理,具体过滤代码在后面) image.png

乱码处理:

image.png

用户优先级

image.png 过滤内容: 实现filter接口,重写init方法。通过fileconfig获取文件地址 。然后通过把内容赋值到一个新的file里面,通过判断这个文件是不是存在,用read读取,一行一行读取

String realPath = filterConfig.getServletContext().getRealPath(path);
File file = new File(realPath);
if (file.exists()) {
   FileReader fileReader = new FileReader(file);
   BufferedReader bufferedReader = new BufferedReader(fileReader);
   String line = bufferedReader.readLine();
   while (line != null) {
        if (line.length() > 0) {
              stopwords.add(line.trim());
              }
          line = bufferedReader.readLine();
          }
      bufferedReader.close();
      fileReader.close();
    

实现乱码:

image.png