课前资料中已经提供了Sql: 表结构: 需要注意的是:这里的密码是使用加密存储的。
POJO Mapper Service
查看页面,当我们填写用户名、邮箱或手机时,都会触发一次可用性检测: 查看开发者工具,发现其实是发起了一次请求(jsonp),检测数据有效性: 开发者工具搜索,发现发起Ajax请求的是一个叫jdValidata.js: 到静态资源文件中找到并查看: 注意,这里的路径后缀名应该改为 .html 与此类似的还有3处请求地址都需要修改
(1)问题分析 打开页面测试,发起接口依然访问失败: 查看控制台:表示说:不支持的媒体类型
org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation(2)406的原因 406状态码代表什么含义? 也就是说,浏览器所接收的数据格式与服务器响应的数据格式是不匹配的。 浏览器接收的数据格式一般可以是任意格式,但是我们在发起Ajax请求时就已经确定了要接收的是Json格式: 响应的数据类型定义在的content-type中: 这就很奇怪了,我们明明使用的返回类型是ResponseEntity,而ResponseEntity与@ResponseBody注解类似,都会尝试将数据解析为json格式,为什么这里的MediaType会变成:text/html呢?
去网上搜索spring406可能得到很多答案:
1)缺少Jackson依赖2)忘了开启注解驱动然而这些问题我们都没有出现。那到底是什么原因?
(3)跟踪SpringMVC源码,查看MediaType的判断依据 原因:主要和伪静态(.html)有关,之前的www.taotao.com虽然也做了伪静态,但因为都是从manage.taotao.com返回数据,请求 manage.taotao.com/…时没有.html后缀,所以并不会冲突
跟踪源码发现,SpringMVC判断是否支持返回值类型,是根据一个叫做:ContentNegotiationManager的类 其中已经注册了两个的ContentNegotiationStrategy 其中第一个就是:ServletPathExtensionContentNegotiationStrategy 这个类在判断MediaType时,默认会根据请求的路径进行判断:而我们的路径是以.html结尾,因此获得扩展名为.html 然后根据这个后缀来决定返回的mediaType:自然也是html 原因找到了: 默认情况下,SpringMVC会注册一个ServletPathExtensionContentNegotiationStrategy,是根据路径进行MediaType的判断,如果路径是.html,那么响应类型就一定是html 此时我们的返回值使用了ResponseBody或者ResponseEntity,需要把数据处理为JSON返回,自然后续会出现数据类型的不匹配,报406错误就可以理解了
解决方案:
方案1:修改SpringMVC的拦截方式,不再拦截.html (相当于做出了妥协)方案2:修改SpringMVC的拦截方式,除了拦截.html外,再添加路径匹配:(迂回策略。。) 以上两种方案都可以,只不过有点妥协的意思,碰到问题,最后的方案才是让步,首先应该先思考如何解决它。方案3:取消SpringMVC的默认根据路径判断的ServletPathExtensionContentNegotiationStrategy 通过刚才的源码跟踪,我们知道:ServletPathExtensionContentNegotiationStrategy其实是注册在一个叫做:ContentNegotiationManager类中的,这个类,默认是在注解驱动类mvc:annotation-driven中有默认注册的: 我们修改注解驱动,自己来创建ContentNegotiationManager即可:
<!-- 注解驱动 content-negotiation-manager:注册一个自定义的ContentNegotiationManager--> <mvc:annotation-driven content-negotiation-manager="negotiationManager"/> <!-- 自定义的ContentNegotiationManager --> <bean id="negotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean"> <!-- false:不根据请求路径后缀判断响应类型,会取消默认的ServletPathContentNegotiationStrategy --> <property name="favorPathExtension" value="false" /> </bean>测试: 数据库添加模拟数据: 填写用户名,离焦后自动校验: 查看开发者工具的请求:返回了200状态码 但是发现逻辑是相反的: 原来是页面JS的逻辑写反了:
查看开发者工具: 这里出现404,很明显这个地址是有问题的,我们需要找到对应的JS文件,并且修改。
在开发者工具中搜索这个地址:service/user/doRegister 是静态资源中的一个独立的JS文件:jdValidate.emReg.js,我们打开查看: 这个地址有问题,我们进行修改: 同时,我们应该注意到,返回的结果result中,应该有两个数据:status(业务状态码),msg(失败时的提示信息)
通过上面的JS分析,可以知道,以下几点:
请求路径:user/doRegister返回值:json数据,包含status和msg属性参数:User对象其实结合接口文档,我们发现,其实返回值中可能还会有一个data对象,不过这里没有用到。 除此以外,JS中与接口文档中基本是一致的。我们按照这个开发即可。
(1)封装通用页面结果对象
/** * 通用的页面结果对象 */ public class TaotaoResult { private Integer status;// 业务状态码,200代表成功 private String msg;// 返回页面的消息 private Object data;// 可能要携带的数据 public TaotaoResult() { super(); } // 构造函数 public TaotaoResult(Integer status) { super(); this.status = status; } public TaotaoResult(Integer status, String msg) { this(status); this.msg = msg; } public TaotaoResult(Integer status, String msg, Object data) { this(status, msg); this.data = data; } // 快捷获取200状态 public static TaotaoResult ok() { return new TaotaoResult(200); } // 快捷获取200状态 public static TaotaoResult ok(Object data) { return new TaotaoResult(200,null, data); } // 快捷获取PageResult对象 public static TaotaoResult build(Integer status, String msg) { return new TaotaoResult(status, msg); } // 快捷获取PageResult对象 public static TaotaoResult build(Integer status, String msg, Object data) { return new TaotaoResult(status, msg, data); } public Integer getStatus() { return status; } public void setStatus(Integer status) { this.status = status; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } }(2)编写Controller 编写Service 测试: 数据库: 测试密码加密是否正确: 测试密码加密是否正确:
刚才虽然实现了注册,但是服务端并没有进行数据校验,而前端的校验是很容易被有心人绕过的。所以我们必须在后台添加数据校验功能: 我们这里会使用Hibernate-Validator框架完成数据校验
(1)添加Hibernate Validator依赖
<!-- 数据校验 --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.1.3.Final</version> </dependency>(2)什么是Hibernate-Validator? 官网:http://hibernate.org/validator/
hibernate Validator 是 Bean Validation 的参考实现 。 Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint(约束) 的实现,除此之外还有一些附加的 constraint。 在日常开发中,Hibernate Validator经常用来验证bean的字段,基于注解,方便快捷高效。
(3)给Bean添加校验规则 Hibernate Validator 通过给JavaBean中的Field添加注解的方式来对字段完成校验,非常简单方便,支持以下常用注解:
Constraint详细信息@Valid被注释的元素是一个对象,需要检查此对象的所有字段值@Null被注释的元素必须为 null@NotNull被注释的元素必须不为 null@AssertTrue被注释的元素必须为 true@AssertFalse被注释的元素必须为 false@Min(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值@Max(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值@DecimalMin(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值@DecimalMax(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值@Size(max, min)被注释的元素的大小必须在指定的范围内@Digits (integer, fraction)被注释的元素必须是一个数字,其值必须在可接受的范围内@Past被注释的元素必须是一个过去的日期@Future被注释的元素必须是一个将来的日期@Pattern(value)被注释的元素必须符合指定的正则表达式@Email被注释的元素必须是电子邮箱地址@Length被注释的字符串的大小必须在指定的范围内@NotEmpty被注释的字符串的必须非空@Range被注释的元素必须在合适的范围内我们给User类添加校验规则: (4)对Controller中的参数进行校验 测试 反馈的结果: 需要注意的问题:当我们使用Hibernate – validator的时候,在参数列表中Bindingreult 对象必须紧跟着那个被校验的参数对象,否则会出问题
跳转到登录页:
尝试登录,发现返回404 在开发者工具搜索地址:service/user/doLogin,发现是在一个叫做login.js的文件中。 地址明显不正确,我们修改JS: 注意:在本例中,只支持用用户名登录,大家可以扩展用手机登录的方式。
经过上面的JS分析,可以确定接口中的以下内容: 1)请求路径:user/doLogin.html 2)请求参数:username, password 3)返回值:一个JSON格式的数据,包含status属性(业务状态码,200代表登录成功,其它代表用户名或密码错误)
这个结果与接口文档中的定义基本一致,只不过结果中只需要status属性,不需要其它
登录时,除了登录的逻辑,成功后还应该给用户生成一个token,然后把token写到cookie中: 在设置Cookie的时候,使用了自定义的工具类:CookieUtils,在课前资料中有: 我们把它引入到taotao-common中,然后install一下: 注意:这里需要在common中引入 slf4j和servlet的依赖
我们的cookie name使用了常量:
注意:这里redis的异常不能try,一定要抛。因为是逻辑的一部分
登录后成功跳转到首页: 但是查看token,发现并没有我们写入的token:
解决token写入问题 (1)问题分析 问题:我们代码中确实有写入token的逻辑,但是查看页面,发现并没有这个token,为什么?
我们debug跟踪一下: 在CookieUtils中,设置cookie的domain,是通过Request来获取的,先用Request获取URL地址,然后截取其中的域名 但是,我们发现获取的地址不是我们预期的sso.taotao.com,而是127.0.0.1:8083,继续执行,获取的domain自然不对: 我们知道cookie的domain决定了cookie所能作用的域名,现在这个cookie是0.0.1,自然不会在www.taotao.com的访问中携带。 为什么是127.0.0.1,而不是sso.taotao.com ? 虽然我们在浏览器访问的是sso.taotao.com,但是这个请求最终是被nginx处理,然后由Nginx 请求Tomcat的,而Nginx访问Tomcat时,请求的路径正是:127.0.0.1:8083,因此,Tomcat中通过request来获取URL,得到的就是127.0.0.1:8083了
解决方案 在Nginx配置中,修改请求头中的host信息,改为sso.taotao.com 不要忘了:重新加载nginx配置
再次尝试 页面工具:
在redis数据库中,我们存储的值是user对象的序列化对象,所以会有password 发现密码也被序列化到了Redis中。密码在缓存信息中并不需要,而且放在这里也不安全。因此我们需要去除password。
怎么办?我们给User的password字段添加一个注解,这样在序列化时,可以忽略密码字段: 再次测试:
登录成功后,会跳转到www.taotao.com页面, 在前台系统taotao-web中,有一个JS文件:t,会检测用户是否登录: 其原理就是拿着 cookie去后台查找 用户信息, 如果查询到,证明登录成功! 因此,我们需要在SSO中提供一个根据token查询用户信息的接口。
需要注意的是:在从缓存查询到用户信息后,一定要重置 缓存中数据的存活时间!代表用户一直处于活跃状态!