京淘 单点登录系统 SSO
1 现状分析
当前在京淘前台页面中,点击“登录”和“免费注册”这两个按钮,跳转的网页地址分别为:
http://www.jt.com/user/login.html
http://www.jt.com/user/register.html
显然这是个伪静态的用法,实际上要访问的资源为:
要想展现对应的页面,客户端势必要发送两次ajax请求。
那么对应的controller中难道也一定得写2个方法去接收吗?
不需要,用restful风格,一个方法搞定
1.1 编辑UserController
@Controller //涉及到页面的跳转功能(登录,注册)
@RequestMapping("/user")
public class UserController {
/**
* 通用页面的跳转的实现
* 1.http://www.jt.com/user/login.html login.jsp页面
* 2.http://www.jt.com/user/register.html register.jsp页面
*/
@RequestMapping("/{moduleName}")
public String module(@PathVariable String moduleName){
return moduleName; //返回的是login.jsp register.jsp等这种形式
}
}
1.2 页面效果展现
2 创建京淘SSO项目----JT-SSO
2.1 JT-SSO项目说明
作用:
主要为jt-web的服务器提供用户的数据的支持,但凡涉及到user的CRUD操作都应该由该系统完成.
所以势必会用到跨域访问(jt-web跨域访问jt-sso)
由于jt-sso不需要展现什么页面给客户,所以打包时,打jar包。
继承jt父级,依赖jt-common,添加插件。
端口号:8093
2.2 创建项目
2.3 编辑pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>jt-sso</artifactId>
<!--继承父级-->
<parent>
<artifactId>jt</artifactId>
<groupId>com.jt</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<!--添加jt-common依赖-->
<dependencies>
<dependency>
<groupId>com.jt</groupId>
<artifactId>jt-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--引入数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--springboot连接数据库-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!--跳过测试类打包 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.4 编辑User的POJO对象
这个User文件要写在jt-common中
由这个表可知,User的POJO对象有这么几个属性。其中创建时间和修改时间从BasePojo那继承即可。
@TableName("tb_user")
@Data
@Accessors(chain = true)
public class User extends BasePojo {
@TableId(type = IdType.AUTO) //主键自增
private Long id;
//用户名
private String username;
//密码
private String password;
//电话
private String phone;
//邮箱 暂时用电话号代替
private String email;
}
2.5 手动补全JT-SSO的项目结构
2.6 编辑nginx配置文件
修改nginx之后记得重启服务器.
# 配置前台服务器
server {
listen 80;
server_name sso.jt.com;
location / {
proxy_pass http://localhost:8093;
}
}
2.7 编辑Hosts文件
3 用户数据校验
用户在登录时,在注册时,都需要输入很多信息。而服务器需要校验这些数据,来控制用户是否能成功登录,成功注册。
3.1 客户端的Ajax请求的url分析
3.2 查看JS
1.在IDEA中按CTRL+H 检索一下这个请求对应的JS代码在哪个文件中
2.查看JSONP跨域请求是怎么写的
在jdValidate.js的第600行左右
$.ajax({
url : "http://sso.jt.com/user/check/"+escape(pin)+"/1?r=" + Math.random(),
dataType : "jsonp",
success : function(data) { //返回状态信息:200(服务器干活成功) 201(服务器干活失败)
checkpin = data.data?"1":"0";
if(data.status == 200){
if (!data.data) {
validateSettings.succeed.run(option);
namestate = true;
}else {
validateSettings.error.run(option, "该用户名已占用!");
namestate = false;
}
}else{ //表示 后台服务器运行异常了,需要先通知一下客户
validateSettings.error.run(option, "服务器正忙,请稍后重试!");
namestate = false;
}
}
});
3.3 接口文档说明
一般工作中与前端工作交接时都需要接口文档,这个是很重要的一个文档!!!记录着工作量,与工作记录。
一般用PPT或WORD的方式写接口文档。
一个例子:
3.4 编辑jt-sso中的UserController
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 返回用户表tb_user中用户的记录
* 返回值类型:json串
*/
@RequestMapping("/findAll")
public List<User> findAll(){
List<User> userList = userService.findAll();
return userList;
}
/**
* 需求分析:校验用户在注册框里输入的数据们 是否可以被注册
* url:http://sso.jt.com/user/check/{param}/{type}
* 参数:param 和 type
* 参数的格式如下:
* xiaoming/1
* 其中chenchen是校验的数据
* Type为类型,可选参数1 username、2 phone、3 email
* 返回值结果:SysResult对象
*
*/
@RequestMapping("/check/{param}/{type}")
public JSONPObject checkUser(@PathVariable String param,@PathVariable Integer type,String callback){
//根据客户端传来的param和type,去查询数据库,获取响应的结果
Boolean flag = userService.checkUser(param,type);
//int a = 1/0; //由于不能保证服务器端会100%将业务处理成功,所以要完善全局异常处理类
return new JSONPObject(callback,SysResult.success(flag));
}
}
3.5 编辑jt-sso的UserService
public interface UserService {
List<User> findAll();
Boolean checkUser(String param, Integer type);
}
3.6 编辑jt-sso的UserServiceImpl
@Service
public class UserServiceImpl implements UserService{
//将type: 1 username 2 phone 3 email 用Map的形式封装起来
private static Map<Integer,String> paramMap = new HashMap<>();
static{
paramMap.put(1,"username");
paramMap.put(2,"phone");
paramMap.put(3,"email");
}
@Autowired
private UserMapper userMapper;
@Override
public List<User> findAll() {
List<User> userList = userMapper.selectList(null);
return userList;
}
/**
* 校验数据
* @param param 需要校验的数据
* @param type 校验的类型 1 username 2 phone 3 email
* @return
*/
@Override
public Boolean checkUser(String param, Integer type) {
//通过Integer类型的type 获取它对应的名字(username phone email)
String column = paramMap.get(type);
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq(column,param); //注意 这里比对的是从map中得到的column和用户传来的param是否相同
//如果根据传来的参数,从数据库中查出来的记录数>0,说明数据库中已经有这个用户的信息了,这个用户名就不能再注册了
boolean flag = userMapper.selectCount(queryWrapper)>0?true:false;
return flag;
}
}
3.7 页面效果展现
如果用户名可以注册,右边会显示绿色的对号。
3.8 修改全局异常处理类
之前客户端发的请求都是普通的ajax请求,请求的数据类型都是json。
这次的请求数据类型是JSONP,所以在全局异常处理类中要加入判断:
如果客户端是发的jsonp请求,那么返回的json结果要进行特殊的封装(用回调函数名包裹)
这个全局异常处理类在jt-common中
@RestControllerAdvice //作用:标识我是一个通知方法,并且只拦截Controller层的异常,(Service层和Mapper层的异常抛给Controller层就行了),并且返回JSON
public class SysResultException {
//需要定义一个全局的方法,万一服务器干活fail了,返回指定的报错信息。
//ExceptionHandler 配置异常的类型,这个例子中规定的是:遇到了运行时异常RuntimeException,就执行这个exception方法
//JSONP请求的异常处理,返回的结果应该是这个样子 : callback({status:201,msg:"",data:""})
//利用Request对象动态获取callback对应的回调函数名称,然后动态封装返回值
@ExceptionHandler(RuntimeException.class) //为了得到用户传递的callback对应的回调函数名具体是什么,要引入HttpServletRequest这个对象
public Object exception(Exception e, HttpServletRequest request) {
//在服务器控制台数据报错的原因,让程序员看的。
e.printStackTrace();
//从request的身上可以获得用户传过来的callback这个变量具体对应着什么样的回调函数名称
String callback = request.getParameter("callback");
if(!StringUtils.isEmpty(callback)){ //如果用户传过来的callback变量 不为空,说明它对应着方法名呢,也就说明此时用户发的是JSONP请求
return new JSONPObject(callback,SysResult.fail());
}
return SysResult.fail();//SysResult.fail()会显示201,调用服务器失败。即告诉用户,我服务器这边出错了。
//但在页面上显示的信息是:新增商品失败! 是因为在item-add.jsp中第115行规定的。
}
}