完成业务:
- 首页功能
- 用户登录
- 安全退出
- 登录验证
最终效果:
首页功能
需求分析
用户访问项目首页,首先进入登录页面。
时序&流程
** 前端要先访问首页index.jsp,再跳转到登录页面login.jsp,但页面资源都WEB-INF中不能直接访问,只能通过代码访问,通过Controller层请求页面,所以要在后端要做两件事,先根据用户输入跳转到首页,再根据首页请求跳转到登录页面。
Tomcat的web.xml配置在
标签中规定了启动要显示的页面为首页
Controller层
IndexController
@Controller
public class IndexController {
/*
理论上,给Controller方法分配请求url:http://127.0.0.1:8080/crm/
为了简便,协议://ip:port/应用名称必须省去,用/代表应用根目录下的/
*/
@RequestMapping("/")
public String index(){
//请求转发
return "index";
}
}
UserController
接收到settings/qx/user/toLogin.do请求,跳转到login.jsp
@Controller
public class UserController {
/**
* url要和controller方法处理完请求之后,响应信息返回的页面的资源目录保持一致
*/
@RequestMapping("/settings/qx/user/toLogin.do")
public String toLogin(){
//请求转发到登录页面
return "settings/qx/user/login";
}
}
前端页面
index.jsp
在浏览器中输入settings/qx/user/toLogin.do以通过UserController访问login.jsp
用户登录
需求分析
用户在登录页面,输入用户名和密码,点击”登录”按钮或者回车,完成用户登录的功能.
用户名和密码不能为空
用户名或者密码错误,用户已过期,用户状态被锁定,ip受限 都不能登录成功
登录成功之后,所有业务页面显示当前用户的名称
实现10天记住密码
登录成功之后,跳转到业务主页面
登录失败,页面不跳转,提示信息
时序&流程
Mapper层
UserMapper.java
创建查询封装好的用户名和密码的方法
User selectUserByLoginActAndPwd(Map<String,Object> map);
UserMapper.xml
编写tbl_user表查询语句,查询与用户名和密码对应的用户的所有信息
<select id="selectUserByLoginActAndPwd" parameterType="map" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from tbl_user
where login_act=#{loginAct} and login_pwd=#{loginPwd}
</select>
Service层
UserService.java
创建查询用户的服务
User queryUserByLoginActAndPwd(Map<String, Object> map);
UserServiceImpl.java
调用userMapper中的selectUserByLoginActAndPwd方法实现UserService接口中的queryUserByLoginActAndPwd服务
@Autowired
private UserMapper userMapper;
@Override
public User queryUserByLoginActAndPwd(Map<String, Object> map) {
return userMapper.selectUserByLoginActAndPwd(map);
}
Controller层
UserController.java
@RequestMapping("/settings/qx/user/login.do")
public @ResponseBody Object login(String loginAct, String loginPwd, String isRemPwd, HttpServletRequest request, HttpServletResponse response, HttpSession session) {
//封装参数
Map<String,Object> map = new HashMap<>();
map.put("loginAct",loginAct);
map.put("loginPwd",loginPwd);
//调用service层方法,查询用户
User user = userService.queryUserByLoginActAndPwd(map);
//根据查询结果,生成响应信息
ReturnObject returnObject = new ReturnObject();
if (user == null) {
//登录失败,用户名或者密码错误
returnObject.setCode(Contants.RETURN_OBJECT_CODE_FAIL);
returnObject.setMessage("用户名或者密码错误");
}else {//进一步判断账号是否合法
//user.getExpireTime() //2019-10-20
// new Date() //2020-09-10
if (DateUtils.formateDateTime(new Date()).compareTo(user.getExpireTime()) > 0) {
//登录失败,账号已过期
returnObject.setCode(Contants.RETURN_OBJECT_CODE_FAIL);
returnObject.setMessage("账号已过期");
} else if ("0".equals(user.getLockState())) {
//登录失败,状态被锁定
returnObject.setCode(Contants.RETURN_OBJECT_CODE_FAIL);
returnObject.setMessage("状态被锁定");
} else if (!user.getAllowIps().contains(request.getRemoteAddr())) {
//登录失败,ip受限
returnObject.setCode(Contants.RETURN_OBJECT_CODE_FAIL);
returnObject.setMessage("ip受限");
} else {
//登录成功
returnObject.setCode(Contants.RETURN_OBJECT_CODE_SUCCESS);
//把user加入session
session.setAttribute(Contants.SESSION_USER, user);
Cookie c1=new Cookie("loginAct",user.getLoginAct());
Cookie c2=new Cookie("loginPwd",user.getLoginPwd());
if("true".equals(isRemPwd)){
//如果需要记住密码,则往外写cookied
c1.setMaxAge(10*24*60*60);
c2.setMaxAge(10*24*60*60);
}else{
//把没有过期cookie删除
c1.setMaxAge(0);
c2.setMaxAge(0);
}
response.addCookie(c1);
response.addCookie(c2);
}
}
return returnObject;
}
WorkbenchIndexController.java
@RequestMapping("/workbench/index.do")
public String index(){
//跳转到业务主页面
return "workbench/index";
}
前端页面
login.jsp
在入口函数添加事件
安全退出
需求分析
用户在任意的业务页面,点击”退出”按钮,弹出确认退出的模态窗口;用户在确认退出的模态窗口,点击”确定”按钮,完成安全退出的功能
- 安全退出,清空cookie,销毁session
- 退出完成,跳转到首页
时序&流程
Controller层
UserController
@RequestMapping("/settings/qx/user/logout.do")
public String logout(HttpServletResponse response, HttpSession session){
//清空cookie
Cookie c1=new Cookie("loginAct","1");
c1.setMaxAge(0);
response.addCookie(c1);
Cookie c2=new Cookie("loginPwd","1");
c2.setMaxAge(0);
response.addCookie(c2);
//销毁session
session.invalidate();
//跳转到首页 重定向
return "redirect:/";
}
前端页面
index.jsp
给确认退出按钮加id选择器
在入口函数给“确定”按钮添加单击事件
//给“确定”按钮添加单击事件 $("#logoutBtn").click(function () { //发送同步请求 window.location.href="settings/qx/user/logout.do"; });
登录验证
需求分析
用户访问任何业务资源,都需要进行登录验证.
- 只有登录成功的用户才能访问业务资源
- 没有登录成功的用户访问业务资源,跳转到登录页面
配置拦截器
登录验证拦截器类LoginInterceptor.java
@Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { //如果用户没有登录成功,则跳转到登录页面 HttpSession session=httpServletRequest.getSession(); User user=(User) session.getAttribute(Contants.SESSION_USER); if(user==null){ httpServletResponse.sendRedirect(httpServletRequest.getContextPath());//重定向时,url必须加项目的名称 return false; } return true; }
在mvc配置文件applicationContext-mvc.xml中配置拦截器
<mvc:interceptors> <mvc:interceptor> <!--拦截所有以settings和workbench开头的请求--> <mvc:mapping path="/settings/**"/> <mvc:mapping path="/workbench/**"/> <!--排除登录本身请求的拦截(优先级高)--> <mvc:exclude-mapping path="/settings/qx/user/toLogin.do"/> <mvc:exclude-mapping path="/settings/qx/user/login.do"/> <!--拦截器类--> <bean class="com.tu.crm.settings.web.interceptor.LoginInterceptor"/> </mvc:interceptor> </mvc:interceptors>
OK
复习知识点
1)同步请求和异步请求的区别
- 同步请求:浏览器窗口发出的请求,响应信息返回到浏览器窗口,所以会进行全局刷新。
- 异步请求:ajax发出的请求,响应信息返回到ajax的回调函数,既可以进行全局刷新,也可以进行局部刷新。
小结
- 如果需要进行全局刷新,推荐使用同步请求,当然也可以使用异步请求
- 如果需要进行局部刷新,只能使用异步请求
- 如果既可能进行全局刷新,也可能进行局部刷新,也是只能使用异步请求
2)使用jquery获取指定元素的指定属性的值
- 选择器.attr(“属性名”);
- 来获取那些值不是true/false的属性的值
- 选择器.prop(“属性名”);
- 用来获取值是true/false的属性的值.例如:checked,selected,readonly,disabled等。
3)把控制层(controller)代码中处理好的数据传递到视图层(jsp),使用作用域传递
- pageContext:用来在同一个页面的不同标签之间传递数
- request:在同一个请求过程中间传递数据。
- session: 同一个浏览器窗口的不同请求之间传递数据。
- application:所有用户共享的数据,并且长久频繁使用的数据。
4)jquery事件函数的用法
5)记住密码
访问:login.jsp
- 如果上次记住密码,自动填上账号和密码;否则,不填。
- 如何判断上次是否记住密码?
- 上次登录成功,判断是否需要记住密码:如果需要记住密码,则往浏览器写cookie;否则,删除cookie。而且cookie的值必须是该用户的loginAct和loginPwd
- 下次登录时,判断该用户有没有cookie:如果有,则自动填写账号和密码;否则,不写。而且填写的是cookie的值
浏览器显示
获取cookie:
使用java代码获取cookie:
Cookie[] cs=request.getCookies(); for(Cookie c:cs){ if(c.getName().equals("loginAct")){ String loginAct=c.getValue(); }else if(c.getName().equals("loginPwd")){ String loginPwd=c.getValue(); } }
使用EL表达式获取cookie
- ${cookie.loginAct.value}
- ${cookie.loginPwd.value}
6)过滤器和拦截器
过滤器
过滤器类:
implements Filter{ --init //初始化过滤器,Filter生命周期中只被调用一次 --doFilter //每一次请求都会调用,FilterChain 用来调用下一个过滤器Filter --destroy //销毁或关闭资源,Filter生命周期中只被调用一次 }
配置过滤器:web.xml
拦截器
拦截器类:
implements HandlerInterceptor{ --preHandle //请求处理之前进行调用,返回值为false ,将视为当前请求结束,自身拦截器失效,其他的拦截器也不再执行 --postHandle //preHandle()方法返回值为true时执行;Controller中的方法调用之后,DispatcherServlet返回渲染视图之前被调用 --afterCompletion//preHandle()方法返回值为true时执行;整个请求结束之后,DispatcherServlet 渲染了对应的视图之后执行 }
先声明的拦截器
preHandle()
方法先执行,postHandle()
方法后执行。(看源码)配置拦截器:springmvc.xml
链式调用,一个应用中可以同时存在多个拦截器
Interceptor
, 一个请求也可以触发多个拦截器 ,而每个拦截器的调用会依据它的声明顺序依次执行
功能上拦截器是高配版的过滤器,但他们的底层实现不同,具体请看–》