目录
商城业务-购物车-环境搭建
商城业务-购物车-数据模型分析
商城业务-购物车-VO编写
商城业务-购物车-ThreadLocal用户身份鉴别
商城业务-购物车-页面环境搭建
商城业务-购物车-添加购物车
商城业务-购物车-添加购物车细节
商城业务-购物车-RedirectAttribute
商城业务-购物车-获取&合并购物车
商城业务-购物车-选中购物项
商城业务-购物车-改变购物项数量
商城业务-购物车-删除购物项
1.使用spring的初始化向导创建购物车服务
2.域名配置
3. 导入common服务,进行配置
开启服务注册并排除数据库自动配置
①上传静态资源
②复制html至templates,修改静态资源访问路径
③ 配置网关
④前端页面跳转
购物车功能
购物项详情对象
将购物车中的购物项存为list类型的话,修改起来太麻烦要从头到尾遍历。可以使用hash来存储购物车中的购物项
购物项的Vo编写
编写购车Vo
1.将购物车数据存储至Redis中,因此,需要导入Spring整合Redis的依赖以及Redis的配置。项目上线之后,应该有一个专门的Redis负责存储购物车的数据不应该使用缓存的Redis
org.springframework.boot
spring-boot-starter-data-redis
#配置redis的ip地址
spring.redis.host=192.168.56.22
2.编写服务层
3.判断用户是否登录则通过判断Session中是否有用户的数据,因此,导入SpringSession的依赖
org.springframework.session
spring-session-data-redis
配置Session
@EnableRedisHttpSession
@Configuration
public class GulimallSessionConfig {
/**
* 子域问题共享解决
*/
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
cookieSerializer.setDomainName("gulimall.com");
cookieSerializer.setCookieName("GULIMALLSESSION");
return cookieSerializer;
}
/**
* 使用json序列化方式来序列化对象数据到redis中
*/
@Bean
public RedisSerializer
4. cookie中的user-key说明
第一次访问京东,会给你的cookie中设置user-key标识你的身份,有效期为一个月,浏览器会保存你的user-key,以后访问都会带上
5.编写To与常量
拦截器逻辑:业务执行之前,判断是否登录,若登录则封装用户信息,将标识位设置为true,postHandler就不再设置作用域和有效时间,否则为其创建一个user-key
注意细节:整合SpringSession之后,Session获取数据都是从Redis中获取的
使用ThreadLocal,解决线程共享数据问题,方便同一线程共享UserInfoTo
编写拦截器实现类
过期时间为1一个月,编写一个月的时间常量
配置拦截器,否则拦截器不生效
Debug测试UserInfoTo中是否有数据
点击我的购物车跳转到商品列表页
跳转至购物车列表页 ,跳转商品详情页先写死保证能跳转即可
编写添加商品进入购物车的请求方法,需要知道商品的SkuId和数量
为加入购物车绑定单击事件,url改为#避免跳转并且设置id
为文本框设置id
为超链接自定义属性,用于存储skuId
编写单击事件 ,$(this)指当前实例,return false : 禁止默认行为
修改加入购物车的成功页面的显示
①默认图片的显示
②商品详情页跳转以及标题显示
③商品数量显示
购物车前缀
boundHashOps()方法:所有的增删改查操作只针对这个key
将其抽取成方法
选中->右击->Refactor->Extract Method
远程调用product查询sku详情
远程调用product服务查询销售属性
①编写product服务中的查询销售属性AsString的接口
@Configuration
public class MyThreadPoolConfig {
@Bean
public ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties threadPoolConfigProperties){
return new ThreadPoolExecutor(threadPoolConfigProperties.getCoreThreadSize(),
threadPoolConfigProperties.getMaxThreadSize(),
threadPoolConfigProperties.getKeepAliveTime(), TimeUnit.SECONDS,new LinkedBlockingQueue<>(100000),
Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
}
}
@ConfigurationProperties(prefix = "gulimall.thread")
@Component
@Data
public class ThreadPoolConfigProperties {
private Integer coreThreadSize;
private Integer maxThreadSize;
private Integer keepAliveTime;
}
#线程池配置
gulimall.thread.core-thread-size=20
gulimall.thread.max-thread-size=200
gulimall.thread.keep-alive-time=10
使用异步编排
①编写vo,属性从SkuInfoEntity中copy
②异步编排
解决方案:
将th:else改为th:if
上面的操作是针对添加新商品进购物车,若购物车里已存在此商品则是一个数量的叠加
@Slf4j
@Service
public class CartServiceImpl implements CartService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private ProductFeignService productFeignService;
@Autowired
private ThreadPoolExecutor executor;
private final String CART_PREFIX = "gulimall:cart:";
@Override
public CartItem addToCart(Long skuId, Integer num) throws ExecutionException, InterruptedException {
BoundHashOperations cartOpts = getCartOpts();
String item = (String) cartOpts.get(skuId.toString());
// redis中无此商品,需要往购物车中添加新商品
if (StringUtils.isEmpty(item)){
CartItem cartItem = new CartItem();
CompletableFuture getSkuInfoTask = CompletableFuture.runAsync(() -> {
// 1. 远程服务调用获取sku的信息
R r = productFeignService.info(skuId);
SkuInfoVo skuInfo = r.getData("skuInfo", new TypeReference() {
});
cartItem.setSkuId(skuId);
cartItem.setTitle(skuInfo.getSkuTitle());
cartItem.setPrice(skuInfo.getPrice());
cartItem.setDefaultImg(skuInfo.getSkuDefaultImg());
cartItem.setCheck(true);
cartItem.setCount(num);
}, executor);
CompletableFuture getSaleAttrsAsStringTask = CompletableFuture.runAsync(()->{
// 2. 获取销售属性
List saleAtrrs = productFeignService.getSaleAttrAsString(skuId);
cartItem.setSkuAttr(saleAtrrs);
},executor);
// 阻塞等待各个任务执行完成
CompletableFuture.allOf(getSkuInfoTask,getSaleAttrsAsStringTask).get();
// 3. 存储cartItem至Redis中
// 为了方便Redis序列化将pojo变成json字符串否则使用jdk的序列化器
String s = JSON.toJSONString(cartItem);
cartOpts.put(skuId.toString(),s);
return cartItem;
}else {
// 购物车中有此商品只需要数量的叠加
CartItem cartItem = JSON.parseObject(item, CartItem.class);
cartItem.setCount(cartItem.getCount()+num);
String s = JSON.toJSONString(cartItem);
cartOpts.put(skuId.toString(),s);
return cartItem;
}
}
private BoundHashOperations getCartOpts() {
UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
String cartKey = "";
// 判断是否登录
if (userInfoTo.getUserId()!=null){
cartKey = CART_PREFIX + userInfoTo.getUserId();
}else {
cartKey = CART_PREFIX + userInfoTo.getUserKey();
}
BoundHashOperations boundHashOperations = stringRedisTemplate.boundHashOps(cartKey);
return boundHashOperations;
}
}
为了避免用户一直刷新页面,重复提交数据,通过重定向的方式获取购物车内容
RedirectAttributes的addFlashAttribut()方法:将对象存储在Session中且只能使用一次,再次刷新就没有了
RedirectAttributes的addAttribut()方法:将对象拼接在url中
购物车列表展示逻辑:首先判断是否登录,没有登录则展示临时购物车,若登录则展示合并后的购物车,将临时购物车合并后并清空
1.编写获取购物车的方法
2. 编写删除购物车的方法
3. 合并购物车,合并完之后要删除临时购物车
前端页面编写
1.登录回显
2. 逻辑判断
3. 购物车中商品遍历
4. 是否选中
5.图片展示
6.标题展示
7. 销售属性展示
8.格式化商品单价展示
9.商品数量展示
10. 商品总价显示
11.购物车总价显示
效果如下图所示,对区域1和区域2进行优化
区域1优化:
区域2优化:
为input框设置class方便后续绑定单击事件修改选中状态,自定义属性保存skuId
单击事件编写 ,prop会返回true或false
编写Controller处理请求
为父标签自定义属性存储skuId,为加减操作设置相同的class,为数量设置class
编写加减的单击事件
为图中的删除按钮设置class,绑定单击事件临时保存skuId
编写Controller