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
url如下: http://www.jt.com/cart/show.html
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);
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。