1.单点登录的实现
1.1分布式项目中登录问题
问题说明:
当采用分布式的部署之后,jt-web服务来部署集群,如果用户信息保存到Session中时,由于Session不能共享数据,则可能会导致用户经过nginx负载均衡时每次访问不同的服务器,这时用户做敏感操作时如:支付、查询订单,必须要求用户登录,这样用户体验较差。
在一个业务系统中登录之后,访问其他系统时也是免密登录的,不应该重复登录,使用Session不能实现操作!
1.2登录操作优化
知识铺垫:
Session:在一个会话内,可以实现数据的共享,范围大,公共的共享数据一般会保存到Session中
Request:在一个请求内,实现数据共享,范围小
上述的对象都是服务器端对象,保存在服务器中,如果服务器变化了,或者宕机、关闭了,则对象全部消失。
cookie:cookie是在客户端实现数据共享的一种机制,同时可以保存服务器端传过来的数据(业务需要)
token策略:动态生成一个秘钥
1.3sso单点登录设计
1.3.1sso介绍
单点登录,就是通过用户的一次性鉴别登录,当用户在身份认证服务器上登录一次之后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这意味着在多个应用系统中,用户只需登录一次就可以访问所有相互信任的应用系统,这种方式登录减少由用户登录产生的时间消耗,辅助用户管理,是目前较流行的的
1.3.2单点登录业务流程
1.当用户通过浏览器登录时发送用户名密码进行校验
2.当jt-web服务器获取到用户信息时,由于 分布式的原因前台不能直接校验用户信息,所以访问sso单点登录业务系统
3.当jt-sso业务系统接收到前台jt-web传递过来的用户信息(用户名和密码),如果根据信息查询数据库没有获取数据库信息,则说明用户名和密码错误,如果后台查询失败则通知前台,前台通过友好提示告知用户用户名或密码错误,当查询到数据库时存在,说明用户输入的数据正确
4.如果数据正确则开始生成秘钥token、ticket,将用户信息转化为json一起保存到redis中7天有效
5.当上述操作完成之后,将token信息交给用户自己保存,将数据保存到cookie中7天有效
同时保证cookie在相互信任的系统中数据共享
1.3.3页面分析
1.url分析
1.参数说明:
3.页面js
1.3.4编辑UserController
/**
*
* 业务需求: 完成用户登录操作
* URL地址: http://www.jt.com/user/doLogin?r=0.6659570464851978
* 请求参数: 用户名和密码
* 返回值: SysResult对象
*
* 知识点讲解:
* Cookie: 在客户端保存服务器数据,在客户端实现数据共享.
* cookie.setMaxAge(); cookie生命周期
* cookie.setMaxAge(0); 立即删除cookie
* cookie.setMaxAge(100); 设定100秒有效期 100秒之后自动删除
* cookie.setMaxAge(-1); 关闭会话后删除
* 2.设定path cookie的权限设定
* cookie.setPath("/") 一般条件下设定为/ 通用
* 权限:根目录及其子目录有效
* cookie.setPath("/user")
* 权限:/user目录下有效
* 3.设定Cookie资源共享
* cookie特点: 自己的域名下,只能看到自己的Cookie. 默认条件下不能共享的
* cookie.setDomain("jt.com"); 只有在xxx.jt.com的域名中实现数据共享
*/
@RequestMapping("/doLogin")
@ResponseBody
public SysResult doLogin(User user, HttpServletResponse response){
String ticket = dubboUserService.doLogin(user);
if(!StringUtils.hasLength(ticket)){
return SysResult.fail();
}
Cookie cookie = new Cookie("JT_TICKET", ticket);
cookie.setMaxAge(7*24*60*60); //设定7天有效
cookie.setPath("/"); //请求在根目录中都可以获取cookie
cookie.setDomain("jt.com");
response.addCookie(cookie);
return SysResult.success();
}
1.3.5编辑userService实现类
/**
* 业务说明: 实现用户单点登录操作
* 1.根据用户名和密码查询数据库
* @param user
* @return
*/
@Override
public String doLogin(User user) {//username/password不为null
//密文加密
String md5Pass = DigestUtils.md5DigestAsHex(user.getPassword().getBytes());
user.setPassword(md5Pass);
//条件构造器 根据对象中不为null的属性充当where条件 查询的是user的全部信息
User userDB = userMapper.selectOne(new QueryWrapper(user));
String ticket = null;
if(userDB !=null){
//用户名和密码正确
ticket = UUID.randomUUID().toString().replace("-", "");
//数据安全性 没有办法得到保证 要对敏感数据进行脱敏处理
userDB.setPassword("123456你猜猜?");
String json = ObjectMapperUtil.toJSON(userDB);
jedisCluster.setex(ticket, 7*24*60*60, json);
}
return ticket;
}
1.3.6页面效果展示
1.4用户信息回显
1.4.1业务说明
如果用户登录之后,应该在系统首页中展现用户名称
实现思路:
1.跨域实现ajax请求 根据秘钥信息动态获取用户信息
2.httpClient方式实现,调用层级较多,不宜使用
3.利用Dubbo框架
现在选择最难的方式:跨域实现ajax请求
1.4.2页面分析
1.页面url分析
2.页面js分析
1.4.3编辑jt-ssoController
/**
* 业务需求:
* 根据用户ticket信息,查询用户信息
* 1.url地址:http://sso.jt.com/user/query/8d5fc189ccde43f7a6b6bf4aecd9eb0e?callback=jsonp1613793443098&_=1613793443147
* 2.请求参数: ticket信息
* 3.返回值结果:SysResult对象
* 注意: JSONP方式进行跨域请求. callback(JSON)
*/
@RequestMapping("/query/{ticket}")
public JSONPObject findUserByTicket(String callback,@PathVariable String ticket){
//利用ticket从redis中动态获取数据
String json = jedisCluster.get(ticket);
//User user = ObjectMapperUtil.toObj(json, User.class);
if(StringUtils.hasLength(json)){
return new JSONPObject(callback, SysResult.success(json));
}
return new JSONPObject(callback,SysResult.fail());
}
1.5封装CookieUtil API
package com.jt.util;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//工具API 主要负责 新增cookie 删除cookie 根据key获取cookie 获取cookie的值
public class CookieUtil {
public static void addCookie(HttpServletResponse response,String name, String value, int maxAge, String path, String domain){
Cookie cookie = new Cookie(name, value);
cookie.setMaxAge(maxAge);
cookie.setPath(path);
cookie.setDomain(domain);
response.addCookie(cookie);
}
public static void delCookie(HttpServletResponse response,String name,String path, String domain){
addCookie(response, name, "", 0, path, domain);
}
public static Cookie getCookie(HttpServletRequest request,String name){
Cookie[] cookies = request.getCookies();
if(cookies !=null && cookies.length >0){
for (Cookie cookie : cookies){
if(name.equals(cookie.getName())){
return cookie;
}
}
}
return null;
}
public static String getCookieValue(HttpServletRequest request,String name){
Cookie cookie = getCookie(request, name);
return cookie==null?null:cookie.getValue();
}
}
1.5用户退出操作
1.5.1业务说明
当用户点击退出时,应该重定向到系统首页,应该删除Cookie删除redis中的数据
1.5.2编辑UserController
/**
* 实现用户退出操作
* url地址: http://www.jt.com/user/logout.html
* 返回值: 重定向到系统首页
*/
@RequestMapping("/logout")
public String logout(HttpServletRequest request,HttpServletResponse response){
String ticket = CookieUtil.getCookieValue(request, "JT_TICKET");
if(StringUtils.hasLength(ticket)){
//删除redis
jedisCluster.del(ticket);
//删除cookie
CookieUtil.delCookie(response, "JT_TICKET", "/", "jt.com");
}
return "redirect:/"; //代表缺省值
}
2.购物车业务实现
2.1编辑pojo对象
@TableName("tb_cart")
@Data
@Accessors(chain = true)
public class Cart extends BasePojo{
@TableId(type = IdType.AUTO)
private Long id;
private Long userId;
private Long itemId;
private String itemTitle;
private String itemImage;
private Long itemPrice;
private Integer num;
}
2.2创建购物车项目
2.2.1添加继承、依赖、插件
<dependencies>
<dependency>
<groupId>com.jt</groupId>
<artifactId>jt-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<!--添加插件 有main方法时 需要添加插件-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
2.2.2业务结构
2.3展现购物车列表
2.3.1业务说明
说明:当用户点击购物车按钮时,应该跳转到购物车展现页面cart.jsp
2.3.2编辑CartController
package com.jt.controller;
import com.alibaba.dubbo.config.annotation.Reference;
import com.jt.pojo.Cart;
import com.jt.service.DubboCartService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
@Controller
@RequestMapping("/cart")
public class CartController {
@Reference(check = false)
private DubboCartService cartService;
/**
* 展现购物车列表信息 根据userId查询购物车记录
* url: http://www.jt.com/cart/show.html
* 参数: 根据userId查询购物车数据信息
* 返回值: 购物车展现页面
* 页面取值方式: ${cartList}
*/
@RequestMapping("/show")
public String show(Model model){
long userId = 7L; //暂时写死 后期维护
List<Cart> cartList = cartService.findCartListByUserId(userId);
//利用model对象将数据保存到request对象中
model.addAttribute("cartList",cartList);
return "cart";
}
}
2.3.2编辑CartService及其实现类
package com.jt.service;
import com.alibaba.dubbo.config.annotation.Service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jt.mapper.CartMapper;
import com.jt.pojo.Cart;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
@Service(timeout = 3000)
public class DubboCartServiceImpl implements DubboCartService{
@Autowired
private CartMapper cartMapper;
@Override
public List<Cart> findCartListByUserId(long userId) {
QueryWrapper queryWrapper = new QueryWrapper();
queryWrapper.eq("user_id", userId);
return cartMapper.selectList(queryWrapper);
}
}
2.4购物车新增操作
2.4.1页面分析
1。页面结构分析
2.加入购物车按钮
<a class="btn-append " id="InitCartUrl" onclick="addCart();" clstag="shangpin|keycount|product|initcarturl">加入购物车<b></b></a>
3.点击事件
//利用post传值
function addCart(){
var url = "http://www.jt.com/cart/add/${item.id}.html";
document.forms[0].action = url; //js设置提交链接
document.forms[0].submit(); //js表单提交
}
4.form表单分析
<form id="cartForm" method="post">
<input class="text" id="buy-num" name="num" value="1" onkeyup="setAmount.modify('#buy-num');"/>
<input type="hidden" class="text" name="itemTitle" value="${item.title }"/>
<input type="hidden" class="text" name="itemImage" value="${item.images[0]}"/>
<input type="hidden" class="text" name="itemPrice" value="${item.price}"/>
</form>
2.4.2编辑CarControllrt
/**
* 业务说明: 实现用户购物车数据新增
* url: http://www.jt.com/cart/add/1474391990.html
* 参数: 购物车表单提交 Cart对象
* 返回值: 重定向到跳转到购物车展现页面
*
* 扩展内容: 如果restFul的参数,与对象的属性名称一致,则可以直接赋值
*/
@RequestMapping("/add/{itemId}")
public String addCart(Cart cart){
long userId = 7;
cart.setUserId(userId);
cartService.addCart(cart);
return "redirect:/cart/show.html";
}
2.4.3编辑CartService及其实现类
/**
* 难点: 用户如果重复加购? 只做数量的更新
* 业务操作:
* 1.根据userId/itemId查询数据库检查是否加购.
* 有值: 已经加购 则只更新数量
* 没有值: 第一次加购 则直接入库即可.
* @param cart
*/
@Override
public void addCart(Cart cart) {
QueryWrapper<Cart> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_id", cart.getUserId());
queryWrapper.eq("item_id", cart.getItemId());
Cart cartDB = cartMapper.selectOne(queryWrapper);
if(cartDB == null){ //说明用户第一次加购
cartMapper.insert(cart);
}else{
//说明用户之间添加过该商品 数量的更新
int num = cart.getNum() + cartDB.getNum();
Cart temp = new Cart();
temp.setNum(num).setId(cartDB.getId());
//根据id 更新对象中不为null的数据...
cartMapper.updateById(temp);
}
}