用户登录:
首先前面已经实现了跳转到登录页面
登录业务需求:
1)用户名和密码不能为空
2)不能登录:①用户名和密码错误,②用户已过期,③用户状态被锁定, ④ip受限(安全性问题)
(user表中有过期时间,用户状态,用户常用IP地址)
3)登录成功之后,所有业务页面显示当前用户的名称
4)十天记住密码
5)登录成功后,跳转到业务页面,如果错误输出提示信息
流程分析:
起始:客户端浏览器发请求
1)用户输入账号密码 ——> 用户点击登录按钮 / 按回车键
2)向后台发请求 ——> 后台写一个Controller接受请求并处理
到底创建几个Controller类,看响应信息是返回到哪个页面,如果页面占一个独立的资源目录,则写一个新的Controller。
响应信息还是返回原来的login.jsp页面,看是否成功再决定是否跳转
所以不用创建新的Controller,还是在UserController中添加方法。
遇到没学过的知识:
同步请求:全局刷新
异步请求:局部刷新
异步请求:根据返回条件判断,都有可能
所以这里用异步请求。
这里需要提供的参数:用户名密码
知识:
是否提交参数,提交哪些参数,看下一步Controller中需要什么
这里UserController中需要验证用户名,密码,判断是否需要记住密码,并决定是否写Cookie(Cookie也没学过)
参数:账号,密码,是否记住密码
loginAct, loginPwd, isRemPwd
现在流程推进到UserController:
复习Controller代码的三个职责:
1. 接收请求,获取参数,需要则封装参数(SpringMVC在方法上定义形参即可接收参数)
这里把参数封装到map中
2. 处理业务,即处理数据。一般调用Service层去具体处理业务,Controller是控制流程的,决定具体调用哪个Service类。
1)这里到数据库中查询账号密码的数据,并判断是否过期,是否锁定等
2)这里创建UserService类,Service类的创建与表相关(需要访问用户表),不看前台页面。
3)在UserService中编写方法来处理业务:queryUserByLoginActAndPwd(map),参数是UserController中封装好的map。
4)Service不能真正访问数据库,根据代码分层的原理,dao / mapper 层访问数据,本层也由表决定。
写个UserMapper类,编写方法selectUserByLoginActAndPwd(map),本项目通过mybatis来访问数据库,执行SQL语句。
5)查询的记录封装为实体类对象User来返回,这里因为账号不重复,所以只返回一个实体类对象user
6)Service再将user返回给Controller
3. 根据处理的结果返回响应信息
将响应信息返回给前台
又是没学的Ajax:
前面异步请求是Ajax发的,现在将响应信息返回给Ajax,Ajax能解析是是json数据
所以这里返回的是json字符串,字符串里包含什么数据由前台的需要决定
(联系前面的:前台发送请求所包含的数据由Controller的需要决定)
怎么知道前台需要的数据? 看需求文档
前台只需要知道成功或失败(,失败的原因):
成功则跳转
不成功则提示信息
所以将这两个数据放在json字符串中返回,返回给Ajax的回调函数,来解析json
表示成功或失败
code: 1 成功 0 失败
提示信息
message: xxxxx 失败原因
解析json信息并继续流程 ——> 渲染页面(很好又一个没学)
-------------------------------------------------------流程分析结束-------------------------------------------------------
mybatis逆向工程:
1. 简介:根据表生成mapper层三部分代码:①实体类,②mapper接口,③映射文件
2. 使用mybatis逆向工程:
1)创建工程:crm-mybatis-generator
2)添加插件:这里的jar包做成了插件。
一个小知识点:
依赖只能被别的程序调用,插件是可以独立运行的
去文档里复制
3)添加配置文件,告诉工程一些信息:
①数据库连接信息
②代码保存的目录
③表的信息
去文档里复制配置文件(P27注意配置文件的含义)
--------------------------------------------------------开始写代码---------------------------------------------------------
按照前面的分析,在UserMapper接口中写方法查询用户信息:
/**
* 根据账号和密码查询用户
* @param map
* @return
*/
User selectUserByLoginActAndPwd(Map map);
然后在UserMapper.xml中写SQL语句:
这里直接根据用户名和密码,查询表中所有字段信息。
注意在配置文件中开启扫描,这里不太懂开启什么扫描,好像并没有@MapperScan这个注解啊,也找不到basePackage在哪里。
现在mapper层做好了,开始写service层来调用mapper层。先写UserService接口,里面写方法调用mapper层方法的方法:
public interface UserService {
User queryUserByLoginActAndPwd(Map map);
}
然后实现类中写方法的具体实现:
//创建bean对象
@Service("userService")
public class UserServiceImpl implements UserService {
//这里创建mapper层的对象,用来调用mapper层的方法
//自动注入
@Autowired
private UserMapper userMapper;
@Override
public User queryUserByLoginActAndPwd(Map map) {
return userMapper.selectUserByLoginActAndPwd(map);
}
}
这个也要记得开启注解扫描:
然后开始写controller层,调用service层处理业务:
一些点:
1)响应结果应该返回json,这里将结果封装在一个Java实体类中返回
2)先封装参数:封装在map中
3)然后调用service层
4)然后处理响应信息:根据文档上的需求逐个判断
5)写一个Java实体类ReturnObject来封装相应信息,放在公共包中
这里有点疑问:
service应该是业务层,但是这里的service层的方法中只是调用了mapper层中的crud,响应信息的判断都在controller中,感觉controller层中的内容有点多。
或许因为是响应信息的处理,所以在controller层中。有弹幕说service只写crud的内容。
一些知识:
1. @ResponseBody注解,和json有关,没学过
2. HttpServletRequest request 请求信息可以直接在方法参数中注入
/**
* 这里应该返回json,
* 把数据封装在Java对象中返回
* 这里直接写成Object,无论哪种Java对象都能返回
* 最后会把实际返回的对象转为json,按子类转,不是按Object
*/
@RequestMapping("/settings/qx/user/login.do")
//响应信息返回给哪个页面,url就和哪个页面的资源路径保持一致,资源的名称和方法名一样
//因为要返回json,所以写一个@ResponseBody注解,这里没学过json不了解
public @ResponseBody Object login(String loginAct, String loginPwd, String isRemPwd, HttpServletRequest request) {
//封装参数
Map map = new HashMap<>();
map.put("loginAct", loginAct);
map.put("loginPwd", loginPwd);
//isRemPwd不需要封装进去,因为后面用不到这个参数
//调用service层方法来查询用户,得到user对象
User user = userService.queryUserByLoginActAndPwd(map);
//根据查询结果生成响应信息
ReturnObject returnObject = new ReturnObject();
if(user == null) {
//没查到用户名和密码
returnObject.setCode("0");
returnObject.setMessage("用户名或密码错误");
} else {
//查出来了,但是不一定成功
//拿到过期时间,和当前时间相比。这里都转换成字符串比较
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String nowStr = sdf.format(new Date());
if(nowStr.compareTo(user.getExpireTime()) > 0) {
//当前时间大,账号已过期,登录失败
returnObject.setCode("0");
returnObject.setMessage("账号已过期");
} else if("0".equals(user.getLockState())) {
//0 状态被锁定,登录失败
returnObject.setCode("0");
returnObject.setMessage("账号被锁定");
} else if( ! user.getAllowIps().contains(request.getRemoteAddr())) {
//请求里带有ip地址,得到后判断是否是用户常用ip地址
//request可以在方法参数里注入
//ip地址受限,登录失败
returnObject.setCode("0");
returnObject.setMessage("ip受限制");
} else {
//登录成功
returnObject.setCode("1");
}
}
//返回封装了信息的对象
return returnObject;
}
------------------------------------------------后台内容结束,开始前台页面处理响应信息------------------------------------------------
前台页面login.jsp收到响应后处理
登录按钮本来是submit类型,但是submit发送的是同步请求,会使整个页面刷新,所以改为button
然后添加jQuery的入口函数来给登录按钮添加单击事件(jQuery忘完了,仿佛没学过)
写一个入口函数:
昨天自己写的不行,点击登录毫无反应,用户名密码不能为空的提示也没有,应该是入口函数完全没用。找bug找了很久,没找到问题,然后发现复制老师的就可以,但是自己的跟老师一样。今天自己写的又行了。。。。。。
登录成功,中间部分目前还是404。
然后是一些代码优化:
1. 日期的格式化可以写一个工具类
2. 字符串代表登录成功,可以写成常量封装在一个类里
/**
* 对Date类型数据进行处理的工具类
*/
public class DateUtils {
/**
* 对指定的date对象进行格式化yyyy-MM-dd HH:mm:ss
* 静态方法方便调用
* @param date
* @return
*/
public static String formatDateTime(Date date) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateStr = sdf.format(date);
return dateStr;
}
/**
* 对指定的date对象进行格式化yyyy-MM-dd
* 静态方法方便调用
* @param date
* @return
*/
public static String formatDate(Date date) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String dateStr = sdf.format(date);
return dateStr;
}
/**
* 对指定的date对象进行格式化HH:mm:ss
* 静态方法方便调用
* @param date
* @return
*/
public static String formatTime(Date date) {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
String dateStr = sdf.format(date);
return dateStr;
}
}
public class Constant {
//保存ReturnObject类中的code值
public static final String RETURN_OBJECT_CODE_SUCCESS = "1";//成功
public static final String RETURN_OBJECT_CODE_FAIL = "0";//失败
}
优化后的UserController中的login方法:
一个小细节:
下载的数据库表中账号过期时间是2018年,所以无法登录,应该把数据库表中的过期时间改掉。但是发现数据库表改不了(遗留问题),所以把代码里改成在过期时间之后的可以登录了。
/**
* 这里应该返回json,
* 把数据封装在Java对象中返回
* 这里直接写成Object,无论哪种Java对象都能返回
* 最后会把实际返回的对象转为json,按子类转,不是按Object
*/
@RequestMapping("/settings/qx/user/login.do")
//响应信息返回给哪个页面,url就和哪个页面的资源路径保持一致,资源的名称和方法名一样
//因为要返回json,所以写一个@ResponseBody注解,这里没学过json不了解
public @ResponseBody Object login(String loginAct, String loginPwd, String isRemPwd, HttpServletRequest request) {
//封装参数
Map map = new HashMap<>();
map.put("loginAct", loginAct);
map.put("loginPwd", loginPwd);
//isRemPwd不需要封装进去,因为后面用不到这个参数
//调用service层方法来查询用户,得到user对象
User user = userService.queryUserByLoginActAndPwd(map);
//根据查询结果生成响应信息
ReturnObject returnObject = new ReturnObject();
if(user == null) {
//没查到用户名和密码
returnObject.setCode(Constant.RETURN_OBJECT_CODE_FAIL);
returnObject.setMessage("用户名或密码错误");
} else {
//查出来了,但是不一定成功
//拿到过期时间,和当前时间相比。这里都转换成字符串比较
// SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// String nowStr = sdf.format(new Date());
if(DateUtils.formatDateTime(new Date()).compareTo(user.getExpireTime()) < 0) {
//当前时间大,账号已过期,登录失败
returnObject.setCode(Constant.RETURN_OBJECT_CODE_FAIL);
returnObject.setMessage("账号已过期");
} else if("0".equals(user.getLockState())) {
//0 状态被锁定,登录失败
returnObject.setCode(Constant.RETURN_OBJECT_CODE_FAIL);
returnObject.setMessage("账号被锁定");
} else if( ! user.getAllowIps().contains(request.getRemoteAddr())) {
//请求里带有ip地址,得到后判断是否是用户常用ip地址
//request可以在方法参数里注入
//ip地址受限,登录失败
returnObject.setCode(Constant.RETURN_OBJECT_CODE_FAIL);
returnObject.setMessage("ip受限制");
} else {
//登录成功
returnObject.setCode(Constant.RETURN_OBJECT_CODE_SUCCESS);
}
}
//返回
return returnObject;
}
想起来应该去复习一下枚举类。
登录完成!