登录功能的后台逻辑代码;完善前台登录页的提交和校验功能,这儿的前台页面只是开发了登录的校验,具体登录后的跳转之类的,尚未开发(其实也没法开发,系统首页还没有开发,登录页没法跳转啊,对吧)。
登录功能的本质就是:根据前台输入的用户名将数据库中对应的用户提取出来,然后和前台输入的用户名密码进行比对;
目录
(1)首先:创建User类:承接和存储【sys_user表的查询结果】
(2)然后,定义数据查询所需的【Mapper XML】和编写【SQL语句】
(3)然后,根据MVC设计原则,编写Model(模型)中的DAO类
(4)然后,根据MVC设计原则,编写Model(模型)中的Service类
如何验证写的这个UserService类能不能用?(这部分是比较重要,保证开发质量和效率的策略!!!)
(5)然后,根据MVC设计原则,编写Controller(控制器):Servlet类
(6)然后,根据MVC设计原则,编写和完善View(视图):前端文件
(1)首先:创建User类:承接和存储【sys_user表的查询结果】
User实体类:
package com.imooc.oa.entity;
public class User {
private Long userId;
private String userName;
private String password;
private Long employeeId;
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
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;
}
public Long getEmployeeId() {
return employeeId;
}
public void setEmployeeId(Long employeeId) {
this.employeeId = employeeId;
}
}
说明:
(1)因为,登录的时候,需要根据前台输入的用户名去后台的【sys_user表】中查询对应的用户名和密码,然后再和前台输入的用户名和密码进行比对,,,,,所以,登录这个过程需要查询【sys_user表】,所以为了承接和存储 【sys_user表的查询结果】,自然需要创建一个对应的实体类也就是User类;
(2)注意,因为【sys_user表的user_id字段】和【sys_user表的employee_id字段】数据类型都是bigint;所以,在User类中【user_Id属性】和【employeeId属性】都设置成了Long;
(3)我们在写这个User实体类,就要让这个实体类对应从【sys_user表的查询结果】,所以User类的属性都是按照【sys_user表的字段】,根据驼峰命名法严格写的;但为了严谨起见(事实上目前最好也这么做),再加上我们已经在mybatis-config.xml中设置了驼峰命名转换,所以很OK~~
(2)然后,定义数据查询所需的【Mapper XML】和编写【SQL语句】
user.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="usermapper">
<select id="selectByUsername" parameterType="String" resultType="com.imooc.oa.entity.User">
select * from sys_user where username = #{value}
</select>
</mapper>
说明:
(1)这个真没什么好说的,很简单;
(2)创建的这个user.xml,记得要在mybatis-config.xml中配置一下哦:
(PS:MVC的部分可以参考:企业门户网站案例,项目开发知识点;其实不难,还好啦。)
(3)然后,根据MVC设计原则,编写Model(模型)中的DAO类
package com.imooc.oa.dao;
import com.imooc.oa.entity.User;
import com.imooc.oa.utils.MybatisUtils;
public class UserDao {
/**
* 根据用户名查询用户表
* @param username 用户名
* @return 返回值类型是User,如果返回了User对象,则包含对应的用户信息;如果返回了null,则代表对象不存在
*/
public User selectByUsername(String username){
User user = (User)MybatisUtils.executeQuery(sqlSession -> sqlSession.selectOne("usermapper.selectByUsername", username));
return user;
}
}
说明:
(1)方法的注释中,最好注释一下【方法的输入】和【方法的输出】;这是个尽量必须要遵守的开发好习惯;
(2)在OA系统四:前期准备二:在工程中集成Mybatis;开发MybatisUtils工具类;已经介绍了MyBatisUtils类的使用,这儿再啰嗦一下:
但是,处于约定俗成的编写习惯,和方便阅读代码的角度来说,最好还是写成sqlSession吧。
(4)然后,根据MVC设计原则,编写Model(模型)中的Service类
UserService类:
package com.imooc.oa.service;
import com.imooc.oa.dao.UserDao;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.exception.BussinessException;
public class UserService {
private UserDao userDao = new UserDao();
/**
* 根据前台输入进行登录校验,检查登录信息
* @param username:从前台获取的用户名;
* @param password:从前台获取的密码;
* @return:如果校验成功,就返回User对象;如果检验失败:就抛出BussinessException异常:L001-用户名不存在,L002-密码错误
*/
public User checkLogin(String username,String password){
User user = userDao.selectByUsername(username);
if (user == null) {
// 如果user==null,代表这个输入的用户是不存在的,需要抛出异常
throw new BussinessException("L001", "用户名不存在");
}
if (!password.equals(user.getPassword())) {
throw new BussinessException("L002", "密码错误");
}
return user;
}
}
说明:
(1)记得userDao一定要初始化:
否则,在调用UserDao的方法的时候,就会报空指针异常:
(2)自定义异常见Java异常十二:自定义异常概念,自定义异常流程,自定义异常的常见问题;对于自定义异常慢慢习惯就好了;
(3)自定义的异常类为BussinessException类:
BussinessException类:
package com.imooc.oa.service.exception; /** * BussinessException:业务逻辑异常,所有与程序校验或者业务逻辑相关的异常都可以放在这个类中; */ public class BussinessException extends RuntimeException{ private String code; //异常编码,异常的以为标识; private String message; //异常的具体文本消息 public BussinessException(String code,String msg){ super(code + ":" + msg); this.code = code; this.message = msg; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } @Override public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
BussinessException类的几点说明:
● 因为这个异常是在程序运行的时候,才可能抛出的异常,并不需要在程序编译时手动的捕获,所以BussinessException类继承RuntimeException就好了;
● code异常编码,有点像是异常的代号;以前做项目的时候遇到过,当时貌似把所有异常编码写在了单独的文件中;
● 这儿BussinessException类定义了code和message这两个属性;这儿还不是太清楚,有这个必要吗?待补充?????????????????????????(2021-12-8补:之所以添加code和message这两个属性,是为了和前端对应;后端返回给前端的信息需要包括code和message这两个属性。即使登录失败了,触发了这个BussinessException异常,后端返给前端的信息中需要包括为什么失败的异常信息。)
● 可以给code和message这两个属性添加get和set方法啦;
(4)code异常编码都是提前规定好的,比如以L开的都代表与登录或注销相关的异常;比如L001代表用户不存在,L002代表密码错误;
(5)Service类中方法的注释最好写的详细些,这样可以方便调用者浏览和分析代码,总之就是方便其他程序员的使用啦;
如何验证写的这个UserService类能不能用?(这部分是比较重要,保证开发质量和效率的策略!!!)
单元测试的部分可以参考单元测试与Junit4
基于这个类创建一个测试用例,对其进行测试:
对于这个测试包含3中不同结果:(1)用户名不存在;(2)密码错误;(3)用户名和密码都正确;
然后,对应着编写测试方法如下:
UserServiceTest类:
package com.imooc.oa.service;
import com.imooc.oa.entity.User;
import org.junit.Test;
import static org.junit.Assert.*;
public class UserServiceTest {
private UserService userService = new UserService();
/**
* 测试用户名不存在的情况
*/
@Test
public void checkLogin1() {
//这儿随便输入一个不存在的用户名
userService.checkLogin("hh","123");
}
/**
* 测试密码错误的情况
*/
@Test
public void checkLogin2() {
//这儿输入一个存在的用户名,但是输入错误的密码
userService.checkLogin("m8", "123");
}
/**
* 测试用户名和密码都正确的情况
*/
@Test
public void checkLogin3() {
userService.checkLogin("m8", "test");
}
}
说明:
(1)只补充一点:
(5)然后,根据MVC设计原则,编写Controller(控制器):Servlet类
在目前的架构中,Controller(控制器)采用Servlet来完成,主要职责是接收来自于前台的用户输入以及调用业务逻辑并且返回结果。
LoginServlet:
package com.imooc.oa.controller;
import com.alibaba.fastjson.JSON;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.UserService;
import com.imooc.oa.service.exception.BussinessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@WebServlet(name = "LoginServlet",urlPatterns = "/check_login")
public class LoginServlet extends HttpServlet {
Logger logger = LoggerFactory.getLogger(LoginServlet.class);
private UserService userService = new UserService();
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");// 这儿使用最原始的方式设置的编码方式,没有使用个过滤器来统一设置。
//1.第一部分:接受用户输入
String username = request.getParameter("username");
String password = request.getParameter("password");
Map<String, Object> result = new HashMap<>();
try{
//2.第二部分:调用业务逻辑
User user = userService.checkLogin(username, password);
result.put("code", "0");//如果登录成功
result.put("message", "success");//这个message仅仅是示意性的,表示服务器执行成功;
}catch (BussinessException ex){
logger.error(ex.getMessage(),ex);
//如果登录失败,报BussinessException异常;那么code和message就是BussinessException异常对象的code和message;
result.put("code", ex.getCode());
result.put("message", ex.getMessage());
}catch (Exception ex){
logger.error(ex.getMessage(),ex);
//遇到其他异常,可以变通一下。将异常类的名字作为编码;将内部的输出消息作为message;
result.put("code", ex.getClass().getSimpleName());
result.put("message", ex.getMessage());
}
//3.第三部分:返回对应结果。
String json = JSON.toJSONString(result);
response.getWriter().println(json);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
说明:
(1)在IDEA中创建的这个Servlet需要手动设置一下url;
(2)因为登录校验是包含了用户名和密码这样的敏感数据,所以需要用post方式提交请求;
(3)这儿请求和响应字符集我们还是采用Servlet与JSP进阶七:Post请求中文乱码;Get请求中文乱码;响应中文乱码;中的最朴素的设置方式。而没有采用过滤器四:案例-字符集过滤器;(设置请求与响应的编码,以过滤器的方式解决中文乱码)中的使用过滤器统一设置的方式;
(4)userService对象要及时实例化(和Service中的Dao对象一样,要及时实例化)
(5)异常结构
(6)logger.error();的用法,待补充……
(7)因为在登录校验的时候,采用Ajax的方式进行提交,即这个Servlet是为前台的Ajax请求服务的,所以此时这个Servlet并不需要【重定向或者请求转发】,这个Servlet只需要返回数据,默默的等到前端文件来主动请求访问这个Servlet就行了;可以参考Ajax二:一个ajax比较拟真的范例及周边的相关博客;
(8)因为后台的Servlet(JSON)要返回数据。那么因为在前后台数据交互的时候,JavaScript天然支持JSON,所以在前后台传递数据的时候,以JSON格式最好。故而需要引入一个JSON的工具包fastjson的依赖:
然后,每当在pom.xml中引入新的依赖后,记得随时将这个依赖加入到发布目录中:
(9)本次使用HashMap这种集合;将集合序列为JSON。具体内容可以快速参考 常用功能与过滤器、监听器、FreeMarker;中的相关文章;
(10)code和message
至此,LoginServlet的编码工作就完成了,接下来的任务就是前端页面了;
(6)然后,根据MVC设计原则,编写和完善View(视图):前端文件
前端文件指的是HTML,JSP,ftl这些。当然,我们这儿使用的是HTML;
在上篇博客(OA系统八:用户登录一:基于LayUI框架开发登录页;)中,login.html仅仅是规划了前端页面的布局,还不具备提交数据的功能。在上篇博客的基础上,继续完善login.html;
login.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>慕课网办公OA系统</title>
<link rel="stylesheet" href="resources/layui/css/layui.css">
<style>
body{
background-color: #F2F2F2;
}
.oa-container {
/*background-color: white;*/
position: absolute;
width: 400px;
height: 350px;
top: 50%;
left: 50%;
padding: 20px;
margin-left: -200px;
margin-top: -175px;
}
#usename,#password {
text-align: center;
font-size: 24px;
}
</style>
</head>
<body>
<div class="oa-container">
<h1 style="text-align: center;margin-bottom: 20px">慕课网办公OA系统</h1>
<form class="layui-form">
<div class="layui-form-item">
<input type="text" id="usename" lay-verify="required" name="username" placeholder="请输入用户名" autocomplete="off" class="layui-input">
</div>
<div class="layui-form-item">
<input type="password" id="password" lay-verify="required" name="password" placeholder="请输入密码" autocomplete="off" class="layui-input">
</div>
<div class="layui-form-item">
<button class="layui-btn layui-btn-fluid" lay-submit lay-filter="login">登录</button>
</div>
</form>
</div>
<script src="/resources/layui/layui.js"></script>
<script>
layui.form.on("submit(login)",function(formdata){ //formdata参数包含了当前要提交的表单中的数据
console.log(formdata);
layui.$.ajax({
url: "/check_login",
data: formdata.field,
type: "post",
dataType: "json",
success: function (json) {
console.log(json);
if (json.code == "0"){
layui.layer.msg("登录成功");
}else {
layui.layer.msg(json.message);
}
}
})
return false; // submit提交事件,如果返回的是true:表单就直接通过浏览器发送请求进行提交了;如果返回false:则阻止本次提交操作。
})
</script>
</body>
</html>
说明:
(1)引入layui的js文件。这儿需要使用JavaScript代码将前台输入的数据提交到后台,来完成用户校验的工作。这本质就是把这个静态页面转换成一个动态页面,动态页面自然是需要JavaScript的。而layui(除了提供了静态的css库之外)也提供了JavaScript的库,即在当前页面引入layui的js文件;
(2)代码整体的布局关系
(3)表单输入项的校验:如【lay-verify="required"】就是这一个输入项不能为空;【lay-verify="required email"】就是这个输入项不能为空而且输入格式需要是email格式的;
可以按照一下步骤,在layui的官网文档中,获取表单校验的相关规则:
(4)利用layui的对象去调用方法捕捉事件
(5)【非空校验效果】,以及【表单提交的数据分析】
(6)layui内置了jQuery。所以在layui中可以直接使用jQuery操作Ajax;
(7)最终的效果
(8)一个疑问的地方,待解决:这儿为什么要return false还是不明白????????????