使用MVC结构来创建一个包含用户注册登录、图书增删改查的图书管理系统(一)

这是笔者第一次写博客,由于数量较大,项目分为两篇文章,笔者力求各位看官和学子能够学会学懂,会在每段代码下面说明需要注意的地方和为什么用哪种方式写。如有文章层次不明晰的地方请多多指出,整个项目完整代码会放到第二章文末。

系列文章第二篇链接地址:
https://blog.csdn.net/alodonoa/article/details/107283410

一、先说说MVC和SpringMVC的异同

首先,MVC结构与Spring MVC架构不同,MVC不依赖于Spring,只是有与其类似的结构;Spring MVC必须依赖于Spring,SpringMVC是基于Spring上添加的开发框架。二者都是Model、View、Controller结构。其次,这是笔者第一篇博客,如果有文章层次不明晰的地方请多多指出。

一、本项目所使用的技术和软件以及环境

技术上,用的是MVC框架模式,JDBC连接,Session技术,以及过滤器Filter技术,还有最捞的JSP技术。
软件上,用的是IDEA2019版本,Navicat Premium。
环境上,笔者用的数据库是MySQL8.0.16,JDK是11LTS。后续我们会陈述与大家用的JDK8.0和MySQL5.x的不同和如何在代码里修改。

三、项目结构

先阐明项目的分布结构。下面没有截到的,都是JSP文件与IDEA的默认文件了。
在这里插入图片描述

四、正文

4.1登录和注册部分页面

话不多说,上图,图片为证。在这里插入图片描述
这是注册页面。
在这里插入图片描述这是登录页面。

4.2、登录和注册部分代码的讲解

4.2.1用户实体类

public class UserInfo {
    private String userid;
    private String username;
    private String password;
    private String contact;

这是用户的属性,这部分比较简单,且篇幅有限,就不多放了。需要的可以在第二篇文章文末自取。用户实体类里定义属性,这里我们全部设置为了String,对应数据库里的varchar,如果定义成int也可以,对应数据库里的tinyint。这里我也不设置主属性id的自增,当时做项目时候我想着用学号或或者手机号什么的来代替账号。定义好属性后需要有用户属性的全参构造,以及getter和setter方法,为了以防万一,再加上toString方法。

4.2.2注册部分Servlet层代码

public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws IOException,ServletException{
        request.setCharacterEncoding("UTF-8");
        String userid = request.getParameter("useridtext");
        String username = request.getParameter("usernametext");
        String password = request.getParameter("passwordtext");
        String contact = request.getParameter("contacttext");
        UserInfo userInfo = new UserInfo(userid,username,password,contact);
        UserService userService = new UserServiceImpl();
        System.out.println(userInfo);
        //check if user information is existed in data table
        int n ;
        if(userService.ifExists(userid)){
            try {
                n = userService.doRegister(userInfo);
                response.sendRedirect(request.getContextPath() + "/login.jsp");
                return;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }else {
            request.setAttribute("errorMsg","注册失败,用户已经存在");
            request.getRequestDispatcher("/welcome.jsp");
        }
    }

先说注册部分的后端,我们用doPost方法,因为前段用了post方法,所以这里必须保持一致。
为什么不用doGet?前段get方法会在URL地址上暴露输入的信息,比如用户名和账号,而post不会,post相对更加安全。

<table style="margin-left:40%">
            <marquee width="200" scrolldelay="250">欢迎使用</marquee>
            <tr>
                <td>账号(请输入学号)</td>
                <td><input name="useridtext" type="text" size="20" placeholder="StudentID"></td>
            </tr>
            <tr>
                <td>登录名:</td>
                <td><input name="usernametext" type="text" size="20" placeholder="your nickname"></td>
            </tr>
            <tr>
                <td>密码:</td>
                <td><input name="passwordtext" type="password" size="20" placeholder="number and character"></td>
            </tr>
            <tr>
                <td>联系方式:</td>
                <td><input name="contacttext" type="text" placeholder="phone number or email"></td>
            </tr>
            <tr>
                <td>确认密码:</td>
                <td><input name="passwordConfirm" type="checkbox"></td>
            </tr>
        </table>

(1)这两段代码块中,需要注意的是,doPost中getParameter方法所获取的字符串(“ueridtext”)与前端input标签中的name必须一致,比如后端是useridtext,那么name也必须useridtext,不能是userid或者其他的字符串,笔者深受其害,为此找了一晚上bug。
(2)其次说一下sendRedirect和getRequestDispatcher的区别在于,前者重定向时候不会发送数据,后者转发时候会携带数据。

4.2.3登录部分Servlet层代码

//check the username
        if(userid==null || "".equals(userid)){
            request.setAttribute("iderror", "用户名不能为空(JSP)!");
            request.getRequestDispatcher("/login.jsp").forward(request, response);
            return;
        }
        //check the password
        if(password==null || "".equals(password)){
            request.setAttribute("passwordnull", "密码不能为空(JSP)!");
            request.getRequestDispatcher("/login.jsp").forward(request, response);
            return;
        }
        //3.跳转:成功:success.jsp  失败: login.jsp
        if(user!=null) {
            try {
                String cid = URLEncoder.encode(userid, StandardCharsets.UTF_8);
                Cookie cookie1 = new Cookie("cid", cid);
                Cookie cookie2 = new Cookie("upwd", password);
                //set cookie
                if ("yes".equals(remember)) {
                    cookie1.setMaxAge(60 * 60 * 10);
                    cookie2.setMaxAge(60 * 60 * 10);
                } else {
                    cookie1.setMaxAge(0);
                    cookie2.setMaxAge(0);
                }
                response.addCookie(cookie1);
                response.addCookie(cookie2);
                HttpSession session1 = request.getSession();
                session1.setAttribute("useridtext", userid);
                response.sendRedirect(request.getContextPath() + "/operation.jsp");
                return;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }else{
            request.setAttribute("error", "用户名或密码错误");
            response.sendRedirect(request.getContextPath() + "/login_failure.jsp");
        }

这是登录部分后端代码,同样用doPost,原因同上。

<table style="margin-left:40%">
            <marquee width="200" scrolldelay="250">用户登录</marquee>

            <tr>
                <td>账号:</td>
                <td><input type="text" size="21" name="useridtext" placeholder="userID" value="<%=userid%>"/></td>

            </tr>
            <tr>
                <td>密码:</td>
                <td><input type="password" name="passwordtext" size="21" value="<%=password%>"/></td>
            </tr>
            <tr>
                <td>七天内免登录</td>
                <td><input type="checkbox" name="memory" value="yes" <%=check%>></td>
            </tr>
        </table>

这是前端页面代码,如果登陆成功则会转发到操作页面,登陆失败则重定向到失败页面。需要注意的地方,也是name等同上面注册部分。

4.2.4封装数据库连接类DBHelper

我们把连接数据库的类DBHelper单独封装起来到工具包util包,这是为了减少代码量,而且在修改代码时候,不会大面积修改,不会牵扯到太多。这里的工具类可以万用,只需要修改端口号和 数据库URL地址。

public class DBHelper {
    private static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";
    private static final String url = "jdbc:mysql://localhost:数据库端口号/数据库名称?useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&autoReconnect=true&serverTimezone=UTC";
    private static final String username = "数据库用户名";
    private static final String password = "数据库密码";
    public static Connection getConnection() {
        Connection connection =null;
        try {
            Class.forName(JDBC_DRIVER);
            if(connection==null)
            connection = DriverManager.getConnection(url, username, password);
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }
        return connection;
    }

    public static void closeAll(ResultSet rs, PreparedStatement ps, Connection connection){
        try{
            if(connection!=null){
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try{
            if(rs!=null){
                rs.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try{
            if(ps!=null){
                ps.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

这里需要注意的是,MySQL8.0.16和MySQL5.x的区别,8.0.16的url地址需要输入时间区域和字符编码集,8.0.16的数据库连接驱动是com.mysql.cj.jdbc.Driver,而5.x的url地址不需要时间区,连接驱动是com.mysql.jdbc.Driver。

4.2.5Dao层注册部分代码

try{
            connection = DBHelper.getConnection();
            String sql = "insert into userinfo (userid,username,password,contact) values(?,?,?,?)";
            ps = connection.prepareStatement(sql);
            ps.setString(1,user.getUserid());
            ps.setString(2,user.getUsername());
            ps.setString(3,user.getPassword());
            ps.setString(4,user.getContact());
            n= ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

这是Dao层注册功能的主要代码,我们只说思路以及需要注意的地方,我们已经封装类DBHelper类,因此直接调用该类连接数据库,然后写SQL语句,然后用connection.prepareStatement(sql);来对语句进行封装;ps.setString(1,user.getUserid());后面的这句代码,对数据库进行操作,这里需要注意的是,对数据库操作的若干句代码,其顺序一定要与上面SQL语句的问号顺序对应,就是说,第一个问号是id的,那么setString就先操作id,如果顺序不对应会导致操作数据库失败。

4.2.6Dao层登录部分代码

try{
           connection = DBHelper.getConnection();
           String sql = "select * from userinfo where userid = ? and password = ? ";
           ps = connection.prepareStatement(sql);
           ps.setString(1,userid);
           ps.setString(2,password);
           rs = ps.executeQuery();
           while (rs.next()){
               userid = rs.getString("userid");
               String username = rs.getString("username");
               password = rs.getString("password");
               String contact = rs.getString("contact");
               userInfo = new UserInfo(userid,username,password,contact);
           }

这是Dao层登录部分代码,需要注意的地方与Dao层注册部分相同,唯一不同的是Result的对象rs,查询时候需要用Result对查询到的数据进行封装处理,因此我们调用了这个方法rs = ps.executeQuery();这句代码从数据库里进行查找,如果有需要数据,则继续。当rs不为空时候,我们进行下面的匹配操作,并对数据进行封装处理。

4.2.7Service层注册部分代码

   UserDao userDao = new UserDaoImpl();

    @Override
    public int doRegister(UserInfo user) {
/*        UserInfo userInfo = null;
        try{
            userInfo = userDao.doRegister(user);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return userInfo;*/
        return userDao.doRegister(user);
    }

这是service层的注释功能代码,这里被注释掉的代码,其功能与return语句的代码功能完全相同,笔者在做项目的时候为了自己能够对更多知识点进行巩固,或者说是对return语句的一个更详细的拆解,故意这样做,各位看官和学子大可不必再去写被注释掉的代码。

4.2.8Service层登录部分代码

@Override
    public UserInfo doLogin(String userid, String password) {
        return userDao.doLogin(userid,password);
    }

这是Service层的登录部分代码,逻辑比较简单,因此直接调用dao层的方法,上面的注册部分也是如此。

4.2.9设置登录过滤器LoginFilter类

到这里其实我们已经可以结束了,但是为了让项目更加贴近实际,我们设置登录过滤器。这里还需要对Web目录下,WEB-INF目录里的web.xml文件进行配置,我们稍后做解释。话不多说,上图!
在这里插入图片描述
我们注意这是登录界面,我们还没有进行登录操作,URL地址还是login.jsp。
我们输入operation.jsp或者随便输入其他项目里的URL,如果没登录,都会强行跳转到这个登录页面来提醒用户登录。下面放代码

@Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        HttpSession session = request.getSession();

        //configure the paths that would not filtering
        String noLoginPaths = config.getInitParameter("noLoginPaths");

        //check if the character is legal
        String charset = config.getInitParameter("charset");
        if(charset==null){
            charset = "UTF-8";
        }
        request.setCharacterEncoding(charset);

        //search for resources from noLoginPaths
        if(noLoginPaths!=null){
            String[] strArray = noLoginPaths.split(";");
            for (int i = 0; i < strArray.length; i++) {
                if(strArray[i]==null || "".equals(strArray[i]))continue;
                if(request.getRequestURI().contains(strArray[i])){
                    filterChain.doFilter(servletRequest, servletResponse);
                    return;
                }
            }
        }
        //receive userid from web page and check if userid is right
        if(session.getAttribute("useridtext")!=null){
            filterChain.doFilter(servletRequest, servletResponse);
        }else {
            response.sendRedirect("login.jsp");
        }
    }

这个是主要代码部分,其中noLoginPaths是webxml里我们自定义的不需要进行登录操作的页面,如注册页面register.jsp等等。因为request和response类型不同,我们还需要分别进行强制转换,然后对web.xml文件里设置的不过滤的 文件进行循环遍历,如果有该文件,在不过滤,如果没有改文件,则进行过滤操作。这里需要注意的是,useridtext也要与login.jsp的useridtext对应,否则是获取不到数据的。

<filter>
        <filter-name>LoginFilter</filter-name>
        <filter-class>filter.impl.LoginFilterImpl</filter-class>
        <init-param>
            <param-name>noLoginPaths</param-name>
            <param-value>login.jsp;login_failure.jsp;LoginServlet;welcome.jsp;error.jsp;register.jsp</param-value>
        </init-param>
        <init-param>
            <param-name>charset</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>LoginFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

这个是web.xml文件,与上面所述对应,param-name我们可以自定义,filter-name是我们定义的过滤器类LoginFilter。下面的映射mapping,filter-name同理;url-parrten,考虑到写的页面以后会有许多许多,因此我们设置为全部过滤,并且在上面noLoginPaths里设置特定的不过滤的页面。这就好比,除了小明,其他人都要去参与某个活动这个意思。

五、总结

在这里总结一下遇到的问题,希望能够帮到需要的人:
(1)MySQL版本问题,MySQL8.0.16与MySQL5.x的不同之处在于,8.0.16的驱动是com.mysql.cj.jdbc.Driver,需要加上cj,8.0.16的URL地址是jdbc:mysql://localhost:数据库端口号/数据库名称?useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&autoReconnect=true&serverTimezone=UTC
数据库端口号是我们MySQL的端口号如3307和3306,数据库名称是我们自定义建立的数据库的名称,比如我的是bookdbms。这个链接可以万用,只要是8.0.x版本的MySQL数据库,修改一下端口号和数据库名称就可以用,笔者为此烦扰了很久,这也是比较大众化的一个问题。希望看到这篇文章的朋友一定要注意这个,也希望我能够帮到大家!
(2)Servlet层getParameter的字符串一定与前端页面的字符串相同。包括比如我们写了个超链接<a href = "../xxxxx/xxxx?userid=xxx>"这里的userid也要与getParameter里的字符串相同,否则在转发时候会获取不到数据,成为空值。
(3)JDK的问题,JDK8与DJK11LTS并没有太大的不同,我们平时用还是可以的。其次,由于Oracle公司每半年更新一次,我们就只用最新版本的上一版本就好了 ,比如笔者之前下载时候有了JDK13,但是为了出错能够得到帮助和解决,就用了比较旧的JDK11LTS,11LTS是长期维护 的版本,比较稳定。笔者这里建议大家不要用最新版本当小白鼠了哈O(∩_∩)O~

注释:
(1)这篇文章所写的内容,完全可以独立于整个项目之外,可以当做独立的登录和注册来使用。
(2)这是笔者第一次写博客,希望能够帮到大家,同时也希望各位社区的朋友们,能够尊重知识主权,如果需要转载,请说明出处。
(3)各位如果觉得写的好的话,可以打赏一下哈,不嫌多不嫌少,多多捂脸⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄
(4)如果有看官学子们需要下载的话,在系列文章第二篇文末,笔者会附上下载链接。
在这里插入图片描述

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值