若依不分离登录模块源码解析
首先将项目运行起来
我们发现 localhost有一个login路径就可以去idea找这个路径看看干了什么走起
打开项目右键 find in path
我们点进去这个GetMapping
我们把这个代码拿出来品尝一番,可以看到有个if判断如果是Ajax请求,返回Json字符串。
否则返回登录页
@GetMapping("/login")
public String login(HttpServletRequest request, HttpServletResponse response, ModelMap mmap)
{
// 如果是Ajax请求,返回Json字符串。
if (ServletUtils.isAjaxRequest(request))
{
return ServletUtils.renderString(response, "{\"code\":\"1\",\"msg\":\"未登录或登录超时。请重新登录\"}");
}
// 是否开启记住我
mmap.put("isRemembered", rememberMe);
// 是否开启用户注册
mmap.put("isAllowRegister", Convert.toBool(configService.getKey("sys.account.registerUser"), false));
return "login";
}
那么如何返回呢我们点进这个renderString方法
同样的把代码拿过来品尝一番
呦呵设置了数据类型为json,字符编码为utf-8,并且把json数据输出到浏览器,相当于把json对象返回给了Ajax
/**
* 将字符串渲染到客户端
*
* @param response 渲染对象
* @param string 待渲染的字符串
* @return null
*/
public static String renderString(HttpServletResponse response, String string)
{
try
{
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().print(string);
}
catch (IOException e)
{
e.printStackTrace();
}
return null;
}
注释说明
response.setContentType()的作用及参数
response.setContentType(MIME)的作用是使客户端浏览器,区分不同种类的数据,并根据不同的MIME调用浏览器内不同的程序嵌入模块来处理相应的数据。
上代码
/**
* 是设置响应连接过来的编码格式
* 比如我像一个url说:你好,那么传过来的话我就默认用UTF-8编码接受。
**/
response.setCharacterEncoding("UTF-8");
/**
* 设置页面以什么编码格式显示,这里设置为UTF-8
**/
response.setContentType("text/html;charset=UTF-8");
/**
* 是每个请求进来的话就发送给每个请求的客户端
**/
response.geWriter().print(msg);
为什么要加上这样一个判断呢?
是为了解决前后分离的项目,如果将来用手机登录,手机是独立的前端,视图解析器就派不上用场了。我自己登录的时候,写上/login显示未登录,前端会根据 code = 1自行跳转到登录页面。前端能怎么跳呢 location.href = ctx + ‘index’; 就跳了
// 如果是Ajax请求,返回Json字符串。
if (ServletUtils.isAjaxRequest(request))
{
return ServletUtils.renderString(response, "{\"code\":\"1\",\"msg\":\"未登录或登录超时。请重新登录\"}");
}
还想再看看验证码?
点击验证码就会刷新
http://localhost/captcha/captchaImage?type=math&s=0.15619298308869167
同样复制 captchaImage 去idea中 find in path
将这部分代码粘过来食用即可
/**
* 验证码生成
*/
@GetMapping(value = "/captchaImage")
public ModelAndView getKaptchaImage(HttpServletRequest request, HttpServletResponse response)
{
ServletOutputStream out = null;
try
{
HttpSession session = request.getSession();
response.setDateHeader("Expires", 0);
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
response.addHeader("Cache-Control", "post-check=0, pre-check=0");
response.setHeader("Pragma", "no-cache");
//响应告诉类型
response.setContentType("image/jpeg");
//从请求里获取参数类型(此处type对应的是math类型)
String type = request.getParameter("type");
String capStr = null;
String code = null;
BufferedImage bi = null;
if ("math".equals(type))
{
//创建了文本类型
String capText = captchaProducerMath.createText();
//生成的文本是"0+7=?@7"这一步截取@之前的部分
capStr = capText.substring(0, capText.lastIndexOf("@"));
//截取最后一个7
code = capText.substring(capText.lastIndexOf("@") + 1);
//根据文本创建图片
bi = captchaProducerMath.createImage(capStr);
}
else if ("char".equals(type))
{
capStr = code = captchaProducer.createText();
bi = captchaProducer.createImage(capStr);
}
session.setAttribute(Constants.KAPTCHA_SESSION_KEY, code);
out = response.getOutputStream();
//以流的形式将生成的图片输出到输出流
ImageIO.write(bi, "jpg", out);
out.flush();
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
try
{
if (out != null)
{
out.close();
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
return null;
}
继续回到登录页面
输入验证码查看
同样 复制 login去查找 这次是Post请求
找到代码开始食用即可
@PostMapping("/login")
@ResponseBody
// 将对象返回转换为json格式
public AjaxResult ajaxLogin(String username, String password, Boolean rememberMe)
{
//将前端获取到的信息,封装成一个Shiro中的用户名密码令牌对象
//这个令牌对象还实现了rememberMe记住我功能
UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe);
//Subject是Shiro的用户机制,用于安全验证等功能
//为了获取到Subject,我们通常使用SecurityUtils工具类
// package org.apache.shiro; 的工具类
// package org.apache.shiro.subject;
Subject subject = SecurityUtils.getSubject();
try
{
//用户验证,出错会抛出异常AuthenticationException
//其具体的异常在subject的实现类DelegatingSubject中具体制定
subject.login(token);
//调用父类BaseController的返回成功success方法
return success();
}
//验证码错误异常
catch (AuthenticationException e)
{
String msg = "用户或密码错误";
//判断是否有其他的错误
if (StringUtils.isNotEmpty(e.getMessage()))
{
//如果有,则替换成该错误
//比如验证码错误
msg = e.getMessage();
}
//将错误消息返回到前端
//@ResponseBody
//error的类型是AjaxResult,其本质是一个hashMap
return error(msg);
}
}
这里发现直接调用return success();我们点进去看一看
/**
* 返回成功
*/
public AjaxResult success()
{
return AjaxResult.success();
}
再点AjaxResult封装类,类中有枚举方法
/**
* 状态类型
*/
public enum Type
{
/** 成功 */
SUCCESS(0),
/** 警告 */
WARN(301),
/** 错误 */
ERROR(500);
private final int value;
Type(int value)
{
this.value = value;
}
public int value()
{
return this.value;
}
}
很显然 return success(); 没有用到视图解析器 是在前端判断跳转的
我们跟到login.html
简单看一看是怎么登录的 请求是怎么发送的
啊呦找不见,没关系我们进入login.js去找
老规矩粘出来食用即可
function login() {
$.modal.loading($("#btnSubmit").data("loading"));
//获取账号密码 消除空格
var username = $.common.trim($("input[name='username']").val());
var password = $.common.trim($("input[name='password']").val());
//获取验证码的值
var validateCode = $("input[name='validateCode']").val();
var rememberMe = $("input[name='rememberme']").is(':checked');
$.ajax({
type: "post",
//var ctx = [[@{/}]]; 在index.html中有定义的
url: ctx + "login",
//把这四个传到 控制层的login页面 扣出数据封装成对象给后端
data: {
"username": username,
"password": password,
"validateCode": validateCode,
"rememberMe": rememberMe
},
success: function(r) {
//判断状态码是否等于0
if (r.code == web_status.SUCCESS) {
//跳到/index页面
location.href = ctx + 'index';
} else {
//如果失败了就 关闭转圈圈
$.modal.closeLoading();
//触发更新验证码
$('.imgcode').click();
//验证码内容清空
$(".code").val("");
//弹窗登录失败
$.modal.msg(r.msg);
}
}
});
}
补充 js的特性 一个页面引了8个js就相当于8个js写在一个页面互相之间可以相互调用