cgb2010- 京淘我的项目 Day19
- 用户登录实现
==========
1.1 传统登录存在的问题
问题阐明: 依照如下的形式进行设计, 用户须要在不同的服务器中进行屡次登录操作. 用户体验较差.
1.2 登录操作优化
常识铺垫:
Session: 在一个 会话内 , 能够实现数据的共享 范畴大 公共的共享数据个别会保留到 Session 中.
Request: 在一个 申请内 , 实现数据的共享. 范畴小
上述的对象都是服务器端对象. 保留在服务器中. 如果服务器变动了, 或者敞开 / 宕机了 则对象全副生效.
Cookie: Cookie 是在客户端 ** 实现数据共享 ** 的一种机制, 同时能够保留服务器端传回来的数据(** 业务须要 **)
token 策略: 动静生成一个密钥.
1.3 SSO 单点登录设计
1.3.1 SSO 介绍
单点登录 (SingleSignOn,SSO),就是通过用户的一次性甄别登录。当用户在身份认证服务器上登录一次当前,即可取得拜访单点登录零碎中其余关联系统和应用软件的权限,同时这种实现是不须要管理员对用户的登录状态或其余信息进行批改的,这意味着在多个利用零碎中, 用户只需一次登录就能够拜访所有相互信任的利用零碎。这种形式缩小了由登录产生的工夫耗费,辅助了用户治理,是目前比拟风行的 [1]
1.3.2 单点登录业务流程
1.3.3 页面剖析
1.url 剖析
2. 参数阐明
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 框架实现.
1.4.2 页面剖析
1. 页面 URL 剖析
2. 页面 JS 剖析
1.4.3 编辑 JT-SSO Controller
`/**
* 业务需要:
* 依据用户 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 编辑 CartController
`/**
* 业务阐明: 实现用户购物车数据新增
* 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);
}
}