不能再low的初级练手项目【学生信息管理系统】登录 + 注册 + 列表展示

题外话:
   此次文章的输出动力来源,是拉勾教育的大数据开发训练营课程(没有推销的意思,也没有广告费,纯粹是记录一下这个过程,陪伴我走过来的一些重要的人或事)
   虽然我的学习进度并不理想,但是好在他们提供的线上班级学习氛围很给力,除了给我一种推动力,在节奏跟不上想要give up的时候,也会有热情的同学、助教、班主任及时的督促和鼓励
   我会继续保持学习的状态,也会尽力跟上课程进度,提高学习效率
   另外要补充一点:该系统内容为我自己根据课程学习阶段、内容吸收情况以及自己的现有编程能力开发

涉及到的技术点:

【前端】:H5、CSS3、jQuery、Vue、laydate日期控件、Font Awesome图标
(未使用UI框架)
【交互】:Axios、Ajax、JSON
【后端】:普通JavaWeb项目、Tomcat、Servlet、三层架构模式、Druid连接池、MySQL
(未使用maven工程、ssm框架、无spring相关技术、微服务技术、无redis、消息队列技术等)
【开发工具】:VS Code、IDEA、SQLyog、浏览器


补充:

  1. 了解MVC与三层架构可以参考这篇文章 MVC与三层架构
  2. VSCode中无需手动刷新,代码自动生效需要安装Live Server扩展插件
  3. 界面布局未使用UI框架,采用Flex响应式布局和浮动布局(登录界面支持完全响应式,首页和添加学生页面未完全实现响应式布局)
  4. 此文主要用于练习,代码规范程度有限,如果有童鞋对代码有疑问,欢迎探讨

实现的目标:

  1. 支持学生登录
  2. 支持查看学生列表
  3. 支持添加学生

详细功能包括:

  1. 用户登录相关
    (1)用户注册入口
    (2)账号校验(账号是否符合规则、账号是否重复校验)
    (3)账号密码校验(密码是否符合规则)
  2. 学生列表界面/首页
    (1)数据即时更新
    (2)支持手动刷新
    (3)首页布局设计
    (4)header和footer展示关键信息和入口
  3. 添加学生相关
    (1)账号是否重复校验
    (2)账号格式校验
    (3)密码格式校验
    (4)评分数值校验
    (5)必填项校验
    (6)关闭添加窗口

完整代码下载:

查看建库及建表脚本
查看完整代码


正文目录

    • 一、界面开发(VS Code)
    • 二、后端结构搭建(IDEA)
    • 三、添加前端校验(VS Code)
    • 四、后端功能实现(IDEA)
    • 五、前后端联调(IDEA)
    • 六、相关参考资料(浏览器)

一、界面开发(VS Code)

  • 用户登录界面,
  • 用户列表页/系统首页,
  • 添加用户页/用户注册页
  • 草图原型如下
    不能再low的初级练手项目【学生信息管理系统】登录 + 注册 + 列表展示_第1张图片
  • 注意:
  1. 在绘制草图或界面过程中,或者开始画原型之前,需要考虑用户需要保存哪些信息,比如像界面中展示的:账号、姓名、昵称、出生日期、性别等,因为是学生信息管理系统,还可以补充:入学年份、所在学院、所选专业、综合评分等信息
  2. 另外,这里没有考虑修改密码的界面和实现,只是作为练习Demo,真正的企业系统肯定是需要的。
  • 开发结果如下:
    • 登录界面

    • 系统首页
      不能再low的初级练手项目【学生信息管理系统】登录 + 注册 + 列表展示_第2张图片

    • 添加学生界面
      不能再low的初级练手项目【学生信息管理系统】登录 + 注册 + 列表展示_第3张图片

二、后端结构搭建(IDEA)

  • 创建Java web项目
    不能再low的初级练手项目【学生信息管理系统】登录 + 注册 + 列表展示_第4张图片

  • 根据三层架构思想创建包目录
    不能再low的初级练手项目【学生信息管理系统】登录 + 注册 + 列表展示_第5张图片

  • 编写视图层框架 ,处理前端请求

    • web下创建LoginServlet、RegisterServlet、CommonServlet

      @WebServlet("/loginServlet")
      public class LoginServlet extends HttpServlet {
          @Override
          protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
              String result = "false";
              String userId = req.getParameter("userid");
              String password = req.getParameter("password");
              if(userId.equals("admin") && password.equals("1")){
      			result = "true";
      		}
      		
              System.out.println("【登录服务】/loginServlet result = " + result);
              resp.getWriter().write(result);
          }
      
          @Override
          protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
              System.out.println("--------登录请求进入post方法--------");
              doGet(req,resp);
          }
      
      }
      
      
      @WebServlet("/registerServlet")
      public class RegisterServlet extends HttpServlet {
      
          @Override
          protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
              String result = "true";
      
              String userId = req.getParameter("userid");
              String password = req.getParameter("password");
              String userName = req.getParameter("username");
              String nickName = req.getParameter("nickname");
              String birth = req.getParameter("birth");
              String gender = req.getParameter("gender");
              String grade = req.getParameter("grade");
      
              System.out.println("userId = " + userId);
              System.out.println("password = " + password);
              System.out.println("userName = " + userName);
              System.out.println("nickName = " + nickName);
              System.out.println("birth = " + birth);
              System.out.println("gender = " + gender);
              System.out.println("grade = " + grade);
              
              System.out.println("【注册服务】/registerServlet result = " + result);
              resp.getWriter().write(result);
          }
      
          @Override
          protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
              System.out.println("--------注册请求进入post方法--------");
              doGet(req,resp);
          }
      }
      
      
      @WebServlet("/commonServlet")
      public class CommonServlet extends HttpServlet {
          @Override
          protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
              String result = "false";
              String userId = req.getParameter("userid");
              if(userId.equals("admin")){
      			result = "true";
      		}
              System.out.println("【公共服务】/commonServlet result = " + result);
              resp.getWriter().write(result);
          }
      
          @Override
          protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
              System.out.println("--------公用请求进入post方法--------");
              doGet(req,resp);
          }
      }
      
    • 将前端页面代码拷贝到项目的web目录下
      不能再low的初级练手项目【学生信息管理系统】登录 + 注册 + 列表展示_第6张图片

    • 配置WEB-INF下的web.xml文件,添加启动项配置

      
      <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
       version="4.0">
      	
          <welcome-file-list>
              <welcome-file>homework/login.htmlwelcome-file>
          welcome-file-list>
      web-app>
      

三、添加前端校验(VS Code)

为元素添加事件有俩种方式:事件绑定、事件派发

  • 事件绑定
<input type="button" value="点我试试" id="btn1" onclick="fn()">
function fn(){
alert("事件绑定!");
}
  • 事件派发
<head>
<script>
	$(function(){
		$("#btn2").click(function(){
			alert("事件派发");
		})
	})
script>
head>
	<input type="button" value="点我试试" id="btn2">

我主要使用的事件派发,也就意味着基本所有的校验都是在js文件中实现,很少改动之前写好的HTML代码

  • 登录页面校验 login.js(代码从完整代码中获取)

    1. 账号输入框失去焦点事件:发送ajax请求CommonServlet,判断账号是否存在
    2. 账号输入框获取焦点事件:隐藏账号警告信息
    3. 密码输入框获取焦点事件:隐藏密码警告信息
    4. 登录按钮点击事件:发送ajax请求,判断账号密码是否错误
    5. 注册链接点击事件:展示添加用户界面
  • 系统首页校验 welcome.js(代码从完整代码中获取)

    1. 退出按钮点击事件:跳转到登录页面
    2. 添加按钮点击事件:展示添加用户界面
    3. 刷新按钮点击事件:刷新列表页数据
  • 添加用户界面校验 add-stu.js(代码从完整代码中获取)

    1. 账号输入框失焦事件:判断是否为空;是否格式正确;账号是否重复
    2. 账号输入框聚焦事件:隐藏账号警告信息
    3. 密码输入框失焦事件:判断是否为空;是否格式正确
    4. 密码输入框聚焦事件:隐藏占位符;隐藏密码警告信息
    5. 姓名输入框失焦事件:判断是否为空
    6. 姓名输入框聚焦事件:隐藏姓名警告信息
    7. 出生日期失焦事件:判断是否为空
    8. 出生日期聚焦事件:隐藏日期警告信息
    9. 评分输入框失焦事件:判断评分是否格式正确
    10. 评分输入框聚焦事件:隐藏评分警告信息
    11. 提交按钮点击事件:ajax请求RegisterServlet,判断是否注册成功;刷新列表页
    12. 关闭按钮点击事件:隐藏添加用户界面

踩坑1:
添加用户界面是登录和首页的公共页面,所以单独拎出来一个add-stu.html,但是嵌套公共HTML的时候,加载公共html自己的js代码存在不生效问题。如下代码:

<body>

</body>
<script>
    window.onload = function(){
        console.log("----页面资源已经全部加载完毕----")
        $("#addStu").load("add-stu.html")
    }
</script>
<script src="js/add-stu.js"></script>

经过分析,是因为login.html或welcome.html中通过jquery的load函数加载公共html的方式属于动态加载,浏览器解析的时候会先加载静态资源,包括add-stu.js,然后才加载add-stu.html,也就导致出现了js代码中打印出来的公共页面的DOM元素不存在的情况
解决方式是:js文件也采用动态加载,在加载add-stu.html之后动态加载add-stu.js,代码如下:

<body>

</body>
<script>
    window.onload = function(){
        console.log("----页面资源已经全部加载完毕----")
        $("#addStu").load("add-stu.html")
        // jQuery.getScript()该函数用于动态加载JS文件,并在全局作用域下执行文件中的JS代码。
        // 该函数可以加载跨域的JS文件。请注意,该函数是通过异步方式加载数据的。
        // 该函数属于全局jQuery对象。
        $.getScript("js/login.js",function(){            
            console.log("公用页面js加载完毕")
        })
    }
</script>

踩坑2:
随着引入的外部js、css等外部资源变多,导致添加界面add-stu.html的性别单选框莫名其妙失效,排查和测试一段时间后,仍然未找到具体原因,原先代码如下:

<div class="option-div">
    <label>
        <input type="radio" name="r-gender" checked=true value="1">
        <div class="r-gender-div" >div>
    label>
    <label>
        <input type="radio" name="r-gender"  value="0">
        <div class="r-gender-div" >div>
    label>
div>

最后采用jquery监听事件解决,修改后代码如下:

<div class="option-div">
   <label>
        <input type="radio" name="r-gender" checked=true class="r-gender-input" value="1">
        <div class="r-gender-div" index="0">div>
    label>
    <label>
        <input type="radio" name="r-gender" class="r-gender-input" value="0">
        <div class="r-gender-div" index="1">div>
    label>
div>
	//20210123 解决性别单选框交互失效问题
    $(".r-gender-div").on("click",function(){
        // console.log($(this).prop("index"))   //prop获取自定义属性失败,提示undefined
        //console.log($(this).attr("index"))    
        //获取单选项的序号
        var index = $(this).attr("index")
        var gender_checked_dom = $(".r-gender-input")[index]  //获取的是DOM元素
        //console.log(gender_checked_dom)
        //$(gender_checked_dom).prop("checked",true);
        $(gender_checked_dom).attr("checked",true);
    })

四、后端功能实现(IDEA)

数据库准备工作(SQLyog)

  • 创建数据库webdb(从完整代码中的resources目录获取)
  • 创建用户表tuser(同上)

Java代码开发(IDEA)

因为第二部分已经搭好了架子,这部分相对容易一些

  • domain包
    • 创建用户/学生的bean对象User
    • 创建一个响应统一返回的Java对象ResultData
    • 其中涉及到code字段,创建一个枚举类CodeEnum
    • 其中涉及到的status字段,创建一个枚举类StatusEnum
  • util包
    • 创建Druid连接池工具类DruidUtils,方便dao层进行操作数据库
    • 在项目根目录下创建一个resources目录,右键该目录,设置Mark Directory As 属性为Resources Root
    • 在resources目录下创建一个数据库连接信息配置文件druid.properties(名字及后缀可以自定义,DruidUtils工具类中匹配上就行,建议用druid.properties命名,见名知意)
  • dao包
    • 创建用户信息持久化层接口UserDao,定义4个方法
      • 根据账号查询用户数量 findUserCountByUserId
      • 根据账号和密码查询用户 findUserByUserIdAndPwd
      • 添加用户 insertUserByUser
      • 查询所有用户 findAllUsers
    • 创建一个子包impl
    • impl包下创建UserDao接口的实现类UserDaoImpl,调用DruidUtils操作数据库,实现上述4个方法
  • service包
    • 用户服务层接口UserService,定义4个方法
      • 查询该账号是否存在 isExistStudentOfUserId
      • 验证账号密码是否正确 isCorrectOfUserIdAndPwd
      • 添加用户 addUser
      • 查询所有用户 findAllUsers
    • 创建一个子包impl
    • impl包下创建UserService实现类UserServiceImpl,重写UserService接口的4个方法
      • isExistStudentOfUserId,调用dao层的实现类UserDaoImpl方法 findUserCountByUserId
      • isCorrectOfUserIdAndPwd,调用dao层的实现类UserDaoImpl方法findUserByUserIdAndPwd
      • addUser,调用dao层的实现类UserDaoImpl方法insertUserByUser
      • findAllUsers,调用dao层的实现类UserDaoImpl方法findAllUsers
  • web包
    • 创建查询账号处理器CommonServlet,调用UserService的方法 isExistStudentOfUserId
    • 创建用户登录请求处理器LoginServlet,调用UserService的方法 isCorrectOfUserIdAndPwd
    • 创建添加用户处理器RegisterServlet,调用UserService的方法 addUser
    • 创建查询用户列表处理器UserListServlet,调用UserService的方法 findAllUsers
    • 创建一个子包filter
    • filter包下创建编码过滤器 EncodeFilter,重写doFilter方法,统一设置请求和响应的编码格式
    • 配置WEB-INF的web.xml,添加过滤器拦截路径

五、前后端联调(IDEA)

  • 调整前端代码中的ajax请求,axios请求的回调函数中对响应数据的获取和判断
  • 后端Java代码中根据联调情况,添加相应的日志打印或者服务器采用debug方式运行,打断点调试后端代码

六、相关参考资料(浏览器)

  • box-sizing介绍

  • 关于登录界面,80%的代码都是参考该文章进行实现的
    html+css实现漂亮的透明登录页面,HTML实现炫酷登录页面

  • 关于响应式布局的学习,参考此文章
    HTML+CSS十分钟实现响应式布局页面,响应式布局实战教程

  • 修改placeholder样式 - css篇

  • translate(x,y) x,y的值都是百分比的话,这个百分比是参照什么来计算的?

  • 谈谈CSS3的长度单位(vh、vw、rem)

  • 前端布局神器display:flex

  • css em父元素没设置字体大小是不是要再上一层找字体大小

  • 使用layui的laydate组件点击后日期组件闪退,一闪而过的解决方法

  • 嵌套HTML后内部HTML的js失效问题参考(坑)
    使用jquery的load方法加载公共html页面,但是引入的公共html的js却不生效

  • Ajax的回调函数中使用return返回内容,但是外部函数接收到的确实undefined(坑)
    Ajax回调函数中return不生效问题

  • jar依赖包在idea中添加依赖后,启动tomcat报错问题(注意必须要放到tomcat的lib目录下)
    java.lang.ClassNotFoundException: com.alibaba.druid.support.http.WebStatFilter异常解决

  • com.alibaba.druid.pool.DruidDataSource.error {dataSource-1} init error
    java.sql.SQLException: com.mysql.cj.jdbc.Driver 问题
    • 我这里是因为没有添加mysql 8.0连接驱动的jar包到tomcat目录下,添加上即可。
    • 如果之前可以连接和访问,之后出现报错的话,可以参考下方文章
      (已解决)解决问题:dataSource init error java.sql.SQLException: com.mysql.cj.jdbc.Driver和一些mysql连接jar包的问题

  • idea中在dao层写sql语句时。表名和字段名报红,但是不影响程序运行,可以正常访问

不能再low的初级练手项目【学生信息管理系统】登录 + 注册 + 列表展示_第7张图片

  • 如果看着不舒服,想解决idea报红问题,可以在idea中打开database选项(打开方法咨询度娘),连接到本地的数据库,这样idea就可以识别表和字段名是否正确,只有在不正确的情况下才会报红
    不能再low的初级练手项目【学生信息管理系统】登录 + 注册 + 列表展示_第8张图片

  • jdbc插入mysql的date类型字段时会报错

Data truncation: Incorrect datetime value: ‘Fri Jan 22 17:53:31 CST 2021’ for column ‘create_time’ at row 1
参考解决方案:java插入数据到mysql的datetime类型字段


  • ajax通过post方式访问servlet时,发现浏览器控制台总会多一个get请求,地址栏也会变化

http://localhost:8080/homework/welcome.html?r-userid=test7&r-password=777777&r-username=%E6%B5%8B%E8%AF%957&r-nickname=&r-birth=2021-01-26&r-gender=1&r-grade=

  • 因为form默认的提交方式是get,需要禁止默认提交操作
    $("form").on("click",function(){ event.preventDefault(); })

  • IDEA中写js函数时,提示错误,版本过低,解决方法如下
    method definition shorthands are not supported by current JavaScript version

  • tomcat的web目录下新添加的js文件在浏览器一直没有生效,不管怎么清缓存刷新都不行

IDEA中删除tomcat资源的输出目录(我这里是out目录),然后重启tomcat,便会全部加载,查看本地out目录路径方法如下图:
不能再low的初级练手项目【学生信息管理系统】登录 + 注册 + 列表展示_第9张图片


  • 登录之后在首页获取到登录成功后存入cookie的值,报错$.cookie is not a function
    参考解决方法

  • 在一个页面中,body下面是2个平级的div,那么如何在俩个div中都挂载上vue对象(因为body不允许作为vue的挂载点)

解决方法

  1. 在俩个平级的div外面加一层div,新的div作为挂载点(缺点是:添加div后,可能要重新调整界面样式)
  2. 只用其中一个div作为挂载点,然后在vue的的某个方法内,使用jquery或js给另一个div的元素传参或赋值(我采用)
  3. 取消使用vue,全部使用jquey或js实现(缺点:渲染表格比较麻烦,还得拼接标签)




- -20210126 更新代码

添加登录过滤器,session过期或丢失后,再点击添加/刷新按钮,会自动进入登录状态拦截器,检查登录状态,发现没有session后,自动跳转到登录页面

涉及到的技术:

  • 后端:Filter、Servlet、Session
  • 前端:ajax回调函数、axios回调函数、Cookie

问题总结

  • 访问/action/loginServlet后登录,该servlet向Cookie中保存了登陆用户的姓名userName,但是当页面自动跳转到首页后,地址栏一闪而过立马就重定向到登录页面了

    【问题分析】:经过排查,发现是cookie的path不同导致的,登录后存储到cookie的path没有设置默认是/action,但是访问首页后,加载的userListServlet的路径没有/action,导致首页中cookie不可用,此> 时会判断cookie不存在默认跳转到登录页面

    【问题解决】:在登录后存储cookie之前,主动设置cookie的path为 “/”,而不是默认的"/action"。关于cookie的path问题介绍可以参考:Cookie的路径设置(很重要)

    cookie.setPath("/")  //这样可以解决问题,但不是最好的方式
    

  • ajax发出请求,如果被过滤器拦截后,会进入ajax的error回调函数中,而不是success回调函数

    另外success回调函数和error回调函数的参数顺序,以及从参数中获取请求头的方式之前不熟悉,记录如下:

    success函数:

    success:function(response,status,xhr){
    	...
    }
    

    error函数:

    error:function(xhr,status,error){
                    var REDIRECT = xhr.getResponseHeader("REDIRECT");
                    if (REDIRECT == "REDIRECT"){
                        location.href = xhr.getResponseHeader("CONTENTPATH")
                    }else{
                        alert("检查登录状态接口异常!")
                    }
                }
    

  • axios发送的请求被过滤器拦截后,也会进入error回调函数,取出响应头的方式记录如下:

    axios.get("/action/userListServlet", {params: {}}).then(
                        function (response) {
                            t.userList = response.data.data
                        }, function (error) {
                            //20210126 add
                            //axios的请求被拦截后,会进入error方法                      
                            //直接通过.headers.contentpath的方式获取到headers的重定向路径,没有提供专门获取响应头的函数
                            console.log("error.response.headers.contentpath = " + error.response.headers.contentpath)
                            if(error.response.headers.redirect == "REDIRECT"){
                                location.href = error.response.headers.contentpath
                            }
                        }
                    )
    

  • axios的拦截器,以及ajax回调函数执行完必然执行的的complete函数,也尝试使用,发现可能是因为系统设计的不规范,使用起来会出现很多新问题,最终没有采用

你可能感兴趣的:(Java进阶历程,jQuery,三层架构,H5+CSS,Servlet,JavaWeb)