题外话:
此次文章的输出动力来源,是拉勾教育的大数据开发训练营课程(没有推销的意思,也没有广告费,纯粹是记录一下这个过程,陪伴我走过来的一些重要的人或事)
虽然我的学习进度并不理想,但是好在他们提供的线上班级学习氛围很给力,除了给我一种推动力,在节奏跟不上想要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、浏览器
补充:
- 了解MVC与三层架构可以参考这篇文章 MVC与三层架构
- VSCode中无需手动刷新,代码自动生效需要安装Live Server扩展插件
- 界面布局未使用UI框架,采用Flex响应式布局和浮动布局(登录界面支持完全响应式,首页和添加学生页面未完全实现响应式布局)
- 此文主要用于练习,代码规范程度有限,如果有童鞋对代码有疑问,欢迎探讨
实现的目标:
- 支持学生登录
- 支持查看学生列表
- 支持添加学生
详细功能包括:
- 用户登录相关
(1)用户注册入口
(2)账号校验(账号是否符合规则、账号是否重复校验)
(3)账号密码校验(密码是否符合规则)- 学生列表界面/首页
(1)数据即时更新
(2)支持手动刷新
(3)首页布局设计
(4)header和footer展示关键信息和入口- 添加学生相关
(1)账号是否重复校验
(2)账号格式校验
(3)密码格式校验
(4)评分数值校验
(5)必填项校验
(6)关闭添加窗口
完整代码下载:
查看建库及建表脚本
查看完整代码
- 用户登录界面,
- 用户列表页/系统首页,
- 添加用户页/用户注册页
- 注意:
- 在绘制草图或界面过程中,或者开始画原型之前,需要考虑用户需要保存哪些信息,比如像界面中展示的:账号、姓名、昵称、出生日期、性别等,因为是学生信息管理系统,还可以补充:入学年份、所在学院、所选专业、综合评分等信息
- 另外,这里没有考虑修改密码的界面和实现,只是作为练习Demo,真正的企业系统肯定是需要的。
编写视图层框架 ,处理前端请求
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-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>
为元素添加事件有俩种方式:事件绑定、事件派发
<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(代码从完整代码中获取)
- 账号输入框失去焦点事件:发送ajax请求CommonServlet,判断账号是否存在
- 账号输入框获取焦点事件:隐藏账号警告信息
- 密码输入框获取焦点事件:隐藏密码警告信息
- 登录按钮点击事件:发送ajax请求,判断账号密码是否错误
- 注册链接点击事件:展示添加用户界面
系统首页校验 welcome.js(代码从完整代码中获取)
- 退出按钮点击事件:跳转到登录页面
- 添加按钮点击事件:展示添加用户界面
- 刷新按钮点击事件:刷新列表页数据
添加用户界面校验 add-stu.js(代码从完整代码中获取)
- 账号输入框失焦事件:判断是否为空;是否格式正确;账号是否重复
- 账号输入框聚焦事件:隐藏账号警告信息
- 密码输入框失焦事件:判断是否为空;是否格式正确
- 密码输入框聚焦事件:隐藏占位符;隐藏密码警告信息
- 姓名输入框失焦事件:判断是否为空
- 姓名输入框聚焦事件:隐藏姓名警告信息
- 出生日期失焦事件:判断是否为空
- 出生日期聚焦事件:隐藏日期警告信息
- 评分输入框失焦事件:判断评分是否格式正确
- 评分输入框聚焦事件:隐藏评分警告信息
- 提交按钮点击事件:ajax请求RegisterServlet,判断是否注册成功;刷新列表页
- 关闭按钮点击事件:隐藏添加用户界面
踩坑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);
})
数据库准备工作(SQLyog)
Java代码开发(IDEA)
因为第二部分已经搭好了架子,这部分相对容易一些
Data truncation: Incorrect datetime value: ‘Fri Jan 22 17:53:31 CST 2021’ for column ‘create_time’ at row 1
参考解决方案:java插入数据到mysql的datetime类型字段
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中删除tomcat资源的输出目录(我这里是out目录),然后重启tomcat,便会全部加载,查看本地out目录路径方法如下图:
解决方法
- 在俩个平级的div外面加一层div,新的div作为挂载点(缺点是:添加div后,可能要重新调整界面样式)
- 只用其中一个div作为挂载点,然后在vue的的某个方法内,使用jquery或js给另一个div的元素传参或赋值(我采用)
- 取消使用vue,全部使用jquey或js实现(缺点:渲染表格比较麻烦,还得拼接标签)
- -20210126 更新代码
添加登录过滤器,session过期或丢失后,再点击添加/刷新按钮,会自动进入登录状态拦截器,检查登录状态,发现没有session后,自动跳转到登录页面
涉及到的技术:
问题总结
访问/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
}
}
)