Day17(购物车)

所有代码发布在 [https://github.com/hades0525/leyou]

判断登录状态

  1. 在ly-auth-service中定义用户的校验接口,通过cookie获取token,然后校验通过返回用户信息。
    • 每当用户在页面进行新的操作,都应该刷新token的过期时间,否则30分钟后用户的登录信息就无效了。而刷新其实就是重新生成一份token,然后写入cookie即可。
/**
*校验用户登录状态
*/
@GetMapping("verify")
publicResponseEntity<UserInfo>verify(
@CookieValue("LY_TOKEN")Stringtoken,
HttpServletResponseresponse,HttpServletRequestrequest
){
try{
//解析token
UserInfouserInfo=JwtUtils.getInfoFromToken(token,prop.getPublicKey());
 
//刷新token,重新生成token
StringnewToken=JwtUtils.generateToken(userInfo,prop.getPrivateKey(),prop.getExpire());
 
//写入cookie
CookieUtils.newBuilder(response).httpOnly().request(request)
.build("LY_TOKEN",newToken);
returnResponseEntity.ok(userInfo);
}catch(Exceptione){
//token已过期,或者token篡改了
thrownewLyException(ExceptionEnum.UNAUTHRIZED);
}
}

登录拦截器

  1. 在Zuul编写拦截器,对用户的token进行校验,如果发现未登录,则进行拦截。
    • pom
<dependency>
<groupId>com.leyou.service</groupId>
<artifactId>ly-auth-common</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.leyou.common</groupId>
<artifactId>ly-common</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>

• application.yml

ly:
jwt:
pubKeyPath:D:/MY_IDEA/rsa/rsa.pub#公钥地址
cookieName:LY_TOKEN#cookie的名称

• JwtProperties

@Data
@ConfigurationProperties(prefix="ly.jwt")
publicclassJwtProperties{
 
privateStringpubKeyPath;//公钥
 
privateStringcookieName;
 
privatePublicKeypublicKey;//公钥
 
@PostConstruct
publicvoidinit(){
try{
//获取公钥
this.publicKey=RsaUtils.getPublicKey(pubKeyPath);
}catch(Exceptione){
thrownewRuntimeException();
}
}
 
}

编写filter extends zuulfilter
• 获取cookie中的token
• 通过JWT对token进行校验
• 通过:则放行;不通过:则重定向到登录页

@Override
publicObjectrun()throwsZuulException{
//获取上下文
RequestContextctx=RequestContext.getCurrentContext();
//获取request
HttpServletRequestrequest=ctx.getRequest();
//获取token
Stringtoken=CookieUtils.getCookieValue(request,jwtProp.getCookieName());
try{
//解析token
UserInfouserInfo=JwtUtils.getInfoFromToken(token,jwtProp.getPublicKey());
//TODO校验权限
}catch(Exceptione){
//解析token失败,未登录,拦截
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(403);
}
returnnull;
}
  1. 发现拦截器拦截了所有的请求,但我们并不是所有的路径我们都需要拦截。
    所以,我们需要在拦截时,配置一个白名单,如果在名单内,则不进行拦截。
    • application.yml中添加白名单
ly:
  filter:
    allowPaths:
      - /api/auth
      - /api/search
      - /api/user/register
      - /api/user/check
      - /api/user/code
      - /api/item

• filterproperties

@Data
@ConfigurationProperties("ly.filter")
publicclassFilterProperties{
 
privateList<String>allowPaths;
}
•	在过滤器中的shouldFilter方法中添加判断逻辑:
//是否过滤
@Override
publicbooleanshouldFilter(){
//获取上下文
RequestContextctx=RequestContext.getCurrentContext();
//获取request
HttpServletRequestrequest=ctx.getRequest();
//获取请求的url路径
Stringpath=request.getRequestURI();
//判断是否放行,放行返回false
return!isAllowPath(path);
}
 
privatebooleanisAllowPath(Stringpath){
//遍历白名单
for(StringallowPath:filterProp.getAllowPaths()){
//判断是否允许
if(path.startsWith(allowPath)){
returntrue;
}
}
returnfalse;
}

购物车微服务

  1. 搭建购物车服务
    • pom
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.leyou.service</groupId>
<artifactId>ly-auth-common</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.leyou.common</groupId>
<artifactId>ly-common</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
</dependencies>

• application.yml

server:
  port: 8088
spring:
  application:
    name: cart-service
  redis:
    host: 192.168.163.128
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
    registry-fetch-interval-seconds: 10
  instance:
    prefer-ip-address: true
    ip-address: 127.0.0.1
    instance-id: ${eureka.instance.ip-address}.${server.port}
    lease-renewal-interval-in-seconds: 5
    lease-expiration-duration-in-seconds: 15
ly:
  jwt:
    pubKeyPath: D:/MY_IDEA/rsa/rsa.pub # 公钥地址
    cookieName: LY_TOKEN # cookie的名称

• 启动类

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
publicclassLyCartApplication{

publicstaticvoidmain(String[]args){
SpringApplication.run(LyCartApplication.class,args);
}
}
  1. 购物车功能分析
    2.1.需求描述:
    • 用户可以在登录状态下将商品添加到购物车
    • 放入数据库
    • 放入redis(采用)
    • 用户可以在未登录状态下将商品添加到购物车
    • 放入localstorage
    • 用户可以使用购物车一起结算下单
    • 用户可以查询自己的购物车
    • 用户可以在购物车中可以修改购买商品的数量。
    • 用户可以在购物车中删除商品。
    • 在购物车中展示商品优惠信息
    • 提示购物车商品价格变化
    2.2.流程图
    在这里插入图片描述
  2. 未登录购物车
    • 每一个购物车信息,都是一个对象,包含:
{
    skuId:2131241,
    title:"小米6",
    image:"",
    price:190000,
    num:1,
    ownSpec:"{"机身颜色":"陶瓷黑尊享版","内存":"6GB","机身存储":"128GB"}"
}

购物车中不止一条数据,因此最终会是对象的数组。

[
    {...},{...},{...}
]

• 保存购物车数据,可以使用Localstorage来实现。
在我们的common.js中,已经对localStorage进行了简单的封装。
item.html中

.catch(()=>{
//获取以前的购物车
constcarts=ly.store.get("carts")||[];
//获取与当前商品id一致的购物车数据
constcart=carts.find(c=>c.skuId===this.sku.id);
if(cart){
//存在,修改数量
cart.num+=this.num;
}else{
//不存在,新增
carts.push({
skuId:this.sku.id,
title:this.sku.title,
image:this.images[0],
price:this.sku.price,
num:this.num,
ownSpec:JSON.stringify(this.ownSpec)
})
}
//未登录
ly.store.set("carts",carts);
//跳转到购物车列表页
window.location.href="http://www.leyou.com/cart.html";
})
  1. 已登录购物车
    4.1.登录校验
    • pom 配置公钥
ly:
  jwt:
    pubKeyPath: D:/MY_IDEA/rsa/rsa.pub # 公钥地址
    cookieName: LY_TOKEN # cookie的名称

• 加载公钥

@Data
@ConfigurationProperties(prefix="ly.jwt")
publicclassJwtProperties{
 
privateStringpubKeyPath;//公钥路径
 
privateStringcookieName;
 
privatePublicKeypublicKey;//公钥
 
@PostConstruct
publicvoidinit(){
try{
//获取公钥
this.publicKey=RsaUtils.getPublicKey(pubKeyPath);
}catch(Exceptione){
thrownewRuntimeException();
}
}
 
}

• 编写过滤器
因为很多接口都需要进行登录,我们直接编写SpringMVC拦截器,进行统一登录校验。同时,我们还要把解析得到的用户信息保存起来,以便后续的接口可以使用。

@Slf4j
publicclassUserInterceptorimplementsHandlerInterceptor{
 
privateJwtPropertiesprop;
 
privatestaticfinalThreadLocal<UserInfo>tl=newThreadLocal<>();
 
publicUserInterceptor(JwtPropertiesprop){
this.prop=prop;
}
 
@Override
publicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{
//获取cookie中的token
Stringtoken=CookieUtils.getCookieValue(request,prop.getCookieName());
try{
//解析token
UserInfouserInfo=JwtUtils.getInfoFromToken(token,prop.getPublicKey());
 
//传递user
tl.set(userInfo);
 
//放行
returntrue;
}catch(Exceptione){
log.error("[购物车服务]解析用户身份失败。",e);
returnfalse;
}
}
 
@Override
publicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,Exceptionex)throwsException{
//最后用完数据,一定要清空
tl.remove();
}
 
publicstaticUserInfogetUser(){
returntl.get();
}
}

注意:
• 这里我们使用了ThreadLocal来存储查询到的用户信息,线程内共享,因此请求到达Controller后可以共享User
• 并且对外提供了静态的方法:getLoginUser()来获取User信息

• 配置过滤器

@Configuration
@EnableConfigurationProperties(JwtProperties.class)
publicclassMvcConfigimplementsWebMvcConfigurer{
@Autowired
privateJwtPropertiesprop;

@Override
publicvoidaddInterceptors(InterceptorRegistryregistry){
registry.addInterceptor(newUserInterceptor(prop)).addPathPatterns("/**");
}
}

4.2.后台购物车设计
• 当用户登录时,我们需要把购物车数据保存到后台,可以选择保存在数据库。但是购物车是一个读写频率很高的数据。因此我们这里选择读写效率比较高的Redis作为购物车存储。
• 购物车结构是一个双层Map:Map<String,Map<String,String>>
• 第一层Map,Key是用户id
• 第二层Map,Key是购物车中商品id,值是购物车数据
• 实体类

@Data
publicclassCart{
privateLongskuId;//商品id
privateStringtitle;//标题
privateStringimage;//图片
privateLongprice;//加入购物车时的价格
privateIntegernum;//购买数量
privateStringownSpec;//商品规格参数
}
4.3.添加商品到购物车
•	controller
/**
*新增购物车
*@paramcart
*@return
*/
@PostMapping
publicResponseEntity<Void>addCart(@RequestBodyCartcart){
cartService.addCart(cart);
returnResponseEntity.status(HttpStatus.CREATED).build();
}
•	service
@Service
publicclassCartService{
 
@Autowired
privateStringRedisTemplateredisTemplate;
 
privatestaticfinalStringKEY_PREFIX="cart:uid:";
 
/**
*添加购物车
*@paramcart
*/
publicvoidaddCart(Cartcart){
//获取登录的用户
UserInfouser=UserInterceptor.getUser();
//key
Stringkey=KEY_PREFIX+user.getId();
//hashKey
StringhashKey=cart.getSkuId().toString();
//记录num
Integernum=cart.getNum();
BoundHashOperations<String,Object,Object>operation=redisTemplate.boundHashOps(key);
//判断当前购物车商品是否存在
if(operation.hasKey(hashKey)){
//存在,修改数量
Stringjson=operation.get(hashKey).toString();
cart=JsonUtils.toBean(json,Cart.class);
cart.setNum(cart.getNum()+num);
}
//不存在,直接写回redis
operation.put(hashKey,JsonUtils.toString(cart));
}
4.4.查询购物车
•	controller
/**
*查询购物车
*@return
*/
@GetMapping("list")
publicResponseEntity<List<Cart>>queryCartList(){
returnResponseEntity.ok(cartService.queryCartList());
}
•	service
/**
*查询购物车
*@return
*/
publicList<Cart>queryCartList(){
//获取登录的用户
UserInfouser=UserInterceptor.getUser();
//key
Stringkey=KEY_PREFIX+user.getId();
 
if(!redisTemplate.hasKey(key)){
//key不存在,返回404
thrownewLyException(ExceptionEnum.CART_NOT_FOUND);
}
//获取登录用户的所有购物车
BoundHashOperations<String,Object,Object>operation=redisTemplate.boundHashOps(key);
List<Cart>cartList=operation.values().stream().map(o->JsonUtils.toBean(o.toString(),Cart.class))
.collect(Collectors.toList());
returncartList;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值