项目笔记

  • Elastic Search
    • 安装与使用
    • 整合到Spring Boot
  • 商城业务
    • 商品的上架
    • Thymeleaf
    • 配置Thymeleaf
    • 一级分类的渲染
    • 二级三级分类的渲染
  • 缓存
    • Redis的配置
    • 将二三级分类的查询存入缓存
    • 缓存的穿透、雪崩、击穿
    • 分布式锁 Redisson
    • SpringCaChe
  • 商城业务二
    • 搜索、查找的对象封装
    • 检索业务核心代码
    • 详情页展示核心代码
    • 短信认证注册用户与登录
    • 购物车功能
    • RabbitMQ
    • 订单服务

Elastic Search

安装与使用

  1. Elastic Search的安装与使用

整合到Spring Boot

  1. 新建微服务wlmall-search
  2. 导入ElasticSearch依赖
    <properties>//因为Spring Boot会根据其版本来自动更换elasticsearch的依赖,所以在这指定版本<elasticsearch.version>7.4.2</elasticsearch.version></properties>
//注意版本对应<dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId><version>7.4.2</version></dependency>
  1. 添加配置类
@SpringBootConfiguration
public class ElasticSearchConfig {private static final RequestOptions COMMON_OPTIONS;static {RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();COMMON_OPTIONS = builder.build();}@Beanpublic RestHighLevelClient esRestClient(){RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(new HttpHost("Ip",9200,"http")));return client;}
}
  1. 将该微服务加入到注册中心去。
  2. 插入数据的使用
  IndexRequest indexRequest = new IndexRequest("索引名");indexRequest.id("指定插入数据的id,不指定会默认生成");//想要插入的数据,封装成对象,放到这里,转化成JsonString s = JSONValue.toJSONString(对象);indexRequest.source(s, XContentType.JSON);IndexResponse index = null;try {//ElasticSearchConfig.COMMON_OPTIONS是上面的配置类定义的index = client.index(indexRequest, ElasticSearchConfig.COMMON_OPTIONS);} catch (IOException e) {e.printStackTrace();}//输出插入的结果System.out.println(index);
  1. 查询的使用
        SearchRequest searchRequest = new SearchRequest();searchRequest.indices("索引名");SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();//sourceBuilder.query(查询条件); 如下是查询所有address等于mill的sourceBuilder.query(QueryBuilders.matchQuery("address","mill")); searchRequest.source(sourceBuilder);SearchResponse searchResponse = null;try {searchResponse = client.search(searchRequest, ElasticSearchConfig.COMMON_OPTIONS);} catch (IOException e) {e.printStackTrace();}System.out.println(searchResponse.toString());

注:具体使用可参考官方文档

商城业务

商品的上架

大致代码和顺序

Controller@PostMapping("/{spuId}/up")public R spuUp(@PathVariable("spuId") Long spuId){spuInfoService.up(spuId);return R.ok();}
Servicepublic void up(Long spuId) {//根据spuId来获取SkuInfo的实体对象List<SkuInfoEntity> skuInfoEntities = skuInfoService.getSkusBySpuId(spuId);//根据SpuId获取所有的规格属性List<ProductAttrValueEntity> baseAttrs = productAttrValueService.baseAttrlistforspu(spuId);//收集所有的规格属性的attrIdList<Long> attrIds = baseAttrs.stream().map(ProductAttrValueEntity::getAttrId).collect(Collectors.toList());//根据attrIds获取所有可以当作检索条件的attrList<Long> searchAttrIds = attrService.selectSearchAttrIds(attrIds);//将查到的Id都存入Set集合中去Set<Long> idSet = new HashSet<>(searchAttrIds);//先过滤掉不能被检索的属性,再将过滤后的一些属性赋值给要上架的对象实体并收集起来List<SkuEsModel.Attrs> attrsList = baseAttrs.stream().filter(item -> idSet.contains(item.getAttrId())).map(item -> {SkuEsModel.Attrs attrs = new SkuEsModel.Attrs();BeanUtils.copyProperties(item, attrs);return attrs;}).collect(Collectors.toList());//收集所有的SkuIdList<Long> skuIdList = skuInfoEntities.stream().map(SkuInfoEntity::getSkuId).collect(Collectors.toList());//远程调用来判断是否还有库存, 使用try,catch是为了使远程调用失败的时候将库存的值设为nullMap<Long, Boolean> stockMap = null;try {R skuHasStock = wareFeignService.getSkusHasStock(skuIdList);//将调用返回来的数据转化为List<SkuHasStockVo> 类型,因为两个服务之间使用的是JSON来传输,TypeReference<List<SkuHasStockVo>> typeReference = new TypeReference<List<SkuHasStockVo>>() {};//简写方式,收集获得库存List集合转化成一个Map集合,其中skuId作为key,stock作为valuestockMap = skuHasStock.getData(typeReference).stream().collect(Collectors.toMap(SkuHasStockVo::getSkuId, item -> item.getHasStock()));} catch (Exception e) {log.error("库存服务查询异常:原因{}", e);}Map<Long, Boolean> finalStockMap = stockMap;//组合拼装上架的信息List<SkuEsModel> collect = skuInfoEntities.stream().map(sku -> {//组装需要的数据SkuEsModel esModel = new SkuEsModel();esModel.setSkuPrice(sku.getPrice());esModel.setSkuImg(sku.getSkuDefaultImg());// 设置库存信息,如果Map为空也表示有库存,不为空则根据该skuId来判断是否有库存if (finalStockMap == null) {esModel.setHasStock(true);} else {esModel.setHasStock(finalStockMap.get(sku.getSkuId()));}//先将物品的热度评分设置为0 TODOesModel.setHotScore(0L);//根据品牌Id 和分类Id 来查询 品牌和分类,并赋值给要上架的对象BrandEntity brandEntity = brandService.getById(sku.getBrandId());esModel.setBrandName(brandEntity.getName());esModel.setBrandId(brandEntity.getBrandId());esModel.setBrandImg(brandEntity.getLogo());CategoryEntity categoryEntity = categoryService.getById(sku.getCatalogId());esModel.setCatalogId(categoryEntity.getCatId());esModel.setCatalogName(categoryEntity.getName());// 设置检索属性esModel.setAttrs(attrsList);//将剩下对应的数据直接赋值BeanUtils.copyProperties(sku, esModel);return esModel;}).collect(Collectors.toList());// 远程调用,将数据发给es进行保存R r = searchFeignService.productStatusUp(collect);if (r.getCode() == 0) {// 远程调用成功后,修改当前spu的状态this.baseMapper.updateSpuStatus(spuId, WareConstant.StatusEnum.SPU_UP.getCode());} else {// 远程调用失败// TODO 以后再来}}

远程调用 ware

Cotroller// 远程调用查询是否还有库存@PostMapping(value = "/hasStock")public R getSkuHasStock(@RequestBody List<Long> skuIds) {List<SkuHasStockVo> vos = wareSkuService.getSkusHasStock(skuIds);//将获取的数据带回去System.out.println(R.ok().setData(vos));return R.ok().setData(vos);}
    public List<SkuHasStockVo> getSkusHasStock(List<Long> skuIds) {//使用skuId通过遍历来找出所有的sku库存是否存在List<SkuHasStockVo> collect = skuIds.stream().map(skuId -> {SkuHasStockVo vo = new SkuHasStockVo();Long count = baseMapper.getSkuStock(skuId);vo.setSkuId(skuId);//如果有库存就返回true即可。vo.setHasStock(count ==null?false:count > 0);return vo;}).collect(Collectors.toList());return collect;}

远程调用 Search

Controller@PostMapping("/product")//插入到es中public R  productStatusUp(@RequestBody List<SkuEsModel> skuEsModels){boolean status = false;status = elasticSaveService.productStatusUp(skuEsModels);if (status) {return R.error(BizCodeEnum.PRODUCT_UP_EXCEPTION.getCode(), BizCodeEnum.PRODUCT_UP_EXCEPTION.getMsg());} else {return R.ok();}}
Service
@Service
public class ElasticSaveServiceImpl implements ElasticSaveService {@AutowiredRestHighLevelClient restHighLevelClient;@Overridepublic boolean productStatusUp(List<SkuEsModel> skuEsModels) {// 批量操作,建立es的映射//在ES中保存这些数据BulkRequest bulkRequest = new BulkRequest();for (SkuEsModel skuEsModel : skuEsModels) {//构造保存请求IndexRequest indexRequest = new IndexRequest(EsConstant.PRODUCT_INDEX);//插入时指定Id不指定会给默认值indexRequest.id(skuEsModel.getSkuId().toString());//将对象转化为JSON后插入String jsonString = JSON.toJSONString(skuEsModel);indexRequest.source(jsonString, XContentType.JSON);bulkRequest.add(indexRequest);}//执行批量操作BulkResponse bulk = null;try {bulk = restHighLevelClient.bulk(bulkRequest, ElasticSearchConfig.COMMON_OPTIONS);} catch (IOException e) {e.printStackTrace();}//如果批量错误,返回trueboolean hasFailures = bulk.hasFailures();List<String> collect = Arrays.stream(bulk.getItems()).map(BulkItemResponse::getId).collect(Collectors.toList());return hasFailures;}
}

Thymeleaf

商城的前端页面使用Thymeleaf来写.且.前端页面省略…

配置Thymeleaf

添加依赖

   <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency>//用来热部署,不需要每次都重启服务就可以看前端页面效果<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><optional>true</optional></dependency>

一级分类的渲染

Controller@GetMapping({"/","/index.html"}) //访问首页public String indexPage(Model model){//查找所有的一级分类List<CategoryEntity> categoryEntits = categoryService.getLevel1Categorys();model.addAttribute("categorys",categoryEntits);return "index"; //字符串类型返回的是页面,thymeleaf会拼接成页面名}
Servicepublic List<CategoryEntity> getLevel1Categorys() {return baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("cat_level",1));}
该部分对应的前端代码
<li th:each="category : ${categorys}"><a href="#" class="header_main_left_a" th:attr="ctg-data=${category.catId}"><b th:text="${category.name}">家用电器</b></a>
</li>

二级三级分类的渲染

创建实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Catelog2Vo {private  String catalog1Id; // 一级分类的IDprivate List<Catelog3Vo> catalog3List;//三级分类的集合private String id;private String name;@Data@NoArgsConstructor@AllArgsConstructorpublic static class Catelog3Vo{private String catalog2Id; //二级分类的IDprivate String id;private String name;}
}
Controller@ResponseBody@GetMapping("/index/catalog.json") //前端请求二三级分类所发的路径public Map<String,List<Catelog2Vo>> getCatalogJson(){System.out.println("-------------------------");Map<String,List<Catelog2Vo>> map = categoryService.getCatalogJson();return map;}
Service
public Map<String, List<Catelog2Vo>> getCatalogJson() {//获取所有一级分类List<CategoryEntity> level1Categorys = getLevel1Categorys();//将数据封装为MAP的形式Map<String,List<Catelog2Vo>> parent_cid = level1Categorys.stream().collect(Collectors.toMap(key->key.getCatId().toString(),value->{//查找所有的二级分类,因为一级分类的ID是二级分类的父IDList<CategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", value.getCatId()));List<Catelog2Vo> catelog2Vos = null;//如果有二级分类if(categoryEntities != null){catelog2Vos = categoryEntities.stream().map(item ->{//有参构造来New一个二级分类对象Catelog2Vo catelog2Vo = new Catelog2Vo(value.getCatId().toString(),null,item.getCatId().toString(),item.getName());//查找所有的三级分类,因为二级分类的ID是三级分类的父IDList<CategoryEntity> level3Catalog = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid",item.getCatId()));//如果有三级分类if(level3Catalog != null){List<Catelog2Vo.Catelog3Vo> collect = level3Catalog.stream().map(item3->{//有参构造来New一个三级分类对象Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(item.getCatId().toString(),item3.getCatId().toString(),item3.getName());return  catelog3Vo;}).collect(Collectors.toList());catelog2Vo.setCatalog3List(collect);}return catelog2Vo;}).collect(Collectors.toList());}return catelog2Vos;}));return parent_cid;}

缓存

Redis的配置

导入依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>配置yaml
spring: redis:host: redis所在的主机Ipport: redis端口号

将二三级分类的查询存入缓存

    public Map<String, List<Catelog2Vo>> getCatalogJson() {//先从缓存中获取String catelogJson = redisTemplate.opsForValue().get("catalogJson");//如果内存中没有,则从数据库中查询并放入内存//都以JSON字符串的格式进行存储,方便跨平台,跨语言if (StringUtils.isEmpty(catelogJson)){//从数据库中查询,上面二三级分类改为getCatalogJsonDB方法Map<String, List<Catelog2Vo>> catalogJsonDB = getCatalogJsonDB();String s = JSON.toJSONString(catalogJsonDB);redisTemplate.opsForValue().set("catalogJson",s);return catalogJsonDB;}Map<String, List<Catelog2Vo>> stringCatelog2VoMap = JSON.parseObject(catelogJson, new TypeReference<Map<String, List<Catelog2Vo>>>(){});return  stringCatelog2VoMap;}

缓存的穿透、雪崩、击穿

通俗理解:
①缓存穿透就是当缓存不存在的时候,需要去数据库中查,这个时候,如果几百万个请求(总之就是很多请求),同时访问缓存,因为缓存不存在,所以都会去访问数据库,数据库压力增大,失去了缓存的意义。
②缓存雪崩就是当很多的缓存同时失效,并且很多用户同时访问这些失效的缓存,这些请求都转到了数据库导致数据库压力过重。
③缓存击穿就是当许多用户来进行请求的时候,该缓存刚好失效,以至于这么多请求都会到数据库,就叫做缓存击穿。

分布式锁 Redisson

  1. 配置
    yaml
        <dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.12.0</version></dependency>

配置类

@Configuration
public class MyRedissonConfig {@Bean(destroyMethod = "shutdown")public RedissonClient redisson() throws IOException {Config config = new Config();config.useSingleServer().setAddress("redis://47.97.18.245:6379");RedissonClient redissonClient = Redisson.create(config);return redissonClient;}
}
  1. lock锁
    ①创建一个锁

    RLock lock = redissonClient.getLock("锁名");
    

②加锁

lock.lock();
阻塞式的等待,默认加的锁都是30S的时间
锁的自动续期:如果业务超长,运行期间会自动给锁续上新的30S,不需要担心业务时间长,锁会自动过期被删掉
加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30S以后自动删除
lock.lock(时间,单位);
lock.lock(10,TimeUnit.SECONDS)//相当于10秒自动解锁,自动解锁时间一定要大于业务的执行时间.
这样锁的话,在锁的时间到了后不会自动续期
如果规定了锁的超时时间,就发送给redis执行脚本,进行占锁,默认超时时间就是指定的时间
如果没指定锁的超时时间,就使用 30 * 1000 即30S,看门狗的默认时间(LockWatchdogTimeout)
只要占锁成功就会启动一个定时任务,重新设置锁的过期时间,新的过期时间就是看门狗的默认时间,每隔十秒都会再次续期,续为30S

一般在设置锁的时候,都会传递超时时间,省掉了整个续期操作,手动解锁

SpringCaChe

  1. 依赖
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency>
  1. yaml
spring:cache:type: redis  # 缓存种类redis:time-to-live: 360000  #缓存时间key-prefix: CACHE_  #缓存前缀名use-key-prefix: true # 是否使用缓存前缀名cache-null-values: true   #缓存为null的时候是否设为null
  1. 在主启动类添加开启缓存的注解
    @EnableCaching
  2. 在想要添加缓存的地方加上注解
    @Cacheable(value = {“前缀名”},key = “key,后缀”)
    如下:前缀为category(域)且后缀为方法名命名的缓存,缓存内容为返回值
    @Cacheable(value = {“category”},key = “#root.method.name”)
    当该缓存对应的数据库发生改变的时候,删除该缓存
    @CacheEvict(value = “category”, key = “‘getCatalogJson’”)
    一次性指定多个缓存操作
    @Caching(evict = {
    @CacheEvict(value = “category”, key = “‘getLevel1Categorys’”),
    @CacheEvict(value = “category”, key = “‘getCatalogJson’”)
    })
  3. 因为默认的配置类在redis中格式有问题,所以需要自定义配置
@EnableConfigurationProperties(CacheProperties.class)
@Configuration
@EnableCaching
public class MyCacheConfig {@BeanRedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties){RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();configuration = configuration.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));//读取配置文件的内容CacheProperties.Redis redis = cacheProperties.getRedis();if(redis.getTimeToLive() != null){configuration = configuration.entryTtl(redis.getTimeToLive());}if(redis.getKeyPrefix() != null){configuration = configuration.prefixKeysWith(redis.getKeyPrefix());configuration = configuration.prefixCacheNameWith(redis.getKeyPrefix());}if(!redis.isCacheNullValues()){configuration = configuration.disableCachingNullValues();}if(!redis.isUseKeyPrefix()){configuration = configuration.disableKeyPrefix();}return  configuration;}
}

商城业务二

搜索、查找的对象封装

  1. 创建搜索的参数Vo和返回的结果Vo
搜索的参数Vo
public class SearchParam {private Long catalog3Id; //三级分类idprivate String keyword; //页面传递过来的全文匹配关键字private List<Long> brandId; //品牌id,可以多选private String sort; //排序条件:sort=price/salecount/hotscore_desc/ascprivate Integer hasStock; //是否显示有货private String skuPrice;  //价格区间查询private List<String> attrs; //按照属性进行筛选private Integer pageNum = 1;//页码private String _queryString; //原生的所有查询条件}
返回结果的参数Vo
@Data
public class SearchResult {private List<SkuEsModel> product; //查询到的所有商品信息private Integer pageNum; //当前页码private Long total; //总记录数//总页码private Integer totalPages;private List<Integer> pageNavs;private List<BrandVo> brands; //当前查询到的结果,所有涉及到的品牌private List<AttrVo> attrs; //当前查询到的结果,所有涉及到的所有属性private List<CatalogVo> catalogs; //当前查询到的结果,所有涉及到的所有分类//===========================以上是返回给页面的所有信息============================///* 面包屑导航数据 */private List<NavVo> navs;@Datapublic static class NavVo {private String navName;private String navValue;private String link;}@Datapublic static class BrandVo {private Long brandId;private String brandName;private String brandImg;}@Datapublic static class AttrVo {private Long attrId;private String attrName;private List<String> attrValue;}@Datapublic static class CatalogVo {private Long catalogId;private String catalogName;}

检索业务核心代码

前端,略
封装页面响应Vo

package com.sky.wlmall.vo;import lombok.Data;@Data
public class AttrResponseVo {/*** 属性id*/private Long attrId;/*** 属性名*/private String attrName;/*** 是否需要检索[0-不需要,1-需要]*/private Integer searchType;/*** 属性图标*/private String icon;/*** 可选值列表[用逗号分隔]*/private String valueSelect;/*** 属性类型[0-销售属性,1-基本属性,2-既是销售属性又是基本属性]*/private Integer attrType;/*** 启用状态[0 - 禁用,1 - 启用]*/private Long enable;/*** 所属分类*/private Long catelogId;/*** 快速展示【是否展示在介绍上;0-否 1-是】,在sku中仍然可以调整*/private Integer showDesc;private Long attrGroupId;private String catelogName;private String groupName;private Long[] catelogPath;}
Controller@GetMapping("/index.html")public String listPage(SearchParam searchParam, Model model, HttpServletRequest request){searchParam.set_queryString(request.getQueryString());SearchResult result = mallSearchService.search(searchParam);model.addAttribute("result",result);return "list";}
@Slf4j
@Service
public class MallSearchServiceImpl implements MallSearchService {@Autowiredprivate RestHighLevelClient esRestClient;@Resourceprivate ProductFeignService productFeignService;@Overridepublic SearchResult search(SearchParam param) {// 动态构建出查询需要的DSL语句SearchResult result = null;//1、准备检索请求SearchRequest searchRequest = buildSearchRequest(param);try {//2、执行检索请求SearchResponse response = esRestClient.search(searchRequest, ElasticSearchConfig.COMMON_OPTIONS);//3、分析响应数据,封装成我们需要的格式result = buildSearchResult(response, param);} catch (IOException e) {e.printStackTrace();}return result;}/*** 构建结果数据* 模糊匹配,过滤(按照属性、分类、品牌,价格区间,库存),完成排序、分页、高亮,聚合分析功能** @param response* @return*/private SearchResult buildSearchResult(SearchResponse response, SearchParam param) {SearchResult result = new SearchResult();//1、返回的所有查询到的商品SearchHits hits = response.getHits();List<SkuEsModel> esModels = new ArrayList<>();//遍历所有商品信息if (hits.getHits() != null && hits.getHits().length > 0) {for (SearchHit hit : hits.getHits()) {String sourceAsString = hit.getSourceAsString();SkuEsModel esModel = JSON.parseObject(sourceAsString, SkuEsModel.class);//判断是否按关键字检索,若是就显示高亮,否则不显示if (!StringUtils.isEmpty(param.getKeyword())) {//拿到高亮信息显示标题HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");String skuTitleValue = skuTitle.getFragments()[0].string();esModel.setSkuTitle(skuTitleValue);}esModels.add(esModel);}}result.setProduct(esModels);//2、当前商品涉及到的所有属性信息List<SearchResult.AttrVo> attrVos = new ArrayList<>();//获取属性信息的聚合ParsedNested attrsAgg = response.getAggregations().get("attr_agg");ParsedLongTerms attrIdAgg = attrsAgg.getAggregations().get("attr_id_agg");for (Terms.Bucket bucket : attrIdAgg.getBuckets()) {SearchResult.AttrVo attrVo = new SearchResult.AttrVo();//1、得到属性的idlong attrId = bucket.getKeyAsNumber().longValue();attrVo.setAttrId(attrId);//2、得到属性的名字ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attr_name_agg");String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();attrVo.setAttrName(attrName);//3、得到属性的所有值ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attr_value_agg");List<String> attrValues = attrValueAgg.getBuckets().stream().map(MultiBucketsAggregation.Bucket::getKeyAsString).collect(Collectors.toList());attrVo.setAttrValue(attrValues);attrVos.add(attrVo);}result.setAttrs(attrVos);//3、当前商品涉及到的所有品牌信息List<SearchResult.BrandVo> brandVos = new ArrayList<>();//获取到品牌的聚合ParsedLongTerms brandAgg = response.getAggregations().get("brand_agg");for (Terms.Bucket bucket : brandAgg.getBuckets()) {SearchResult.BrandVo brandVo = new SearchResult.BrandVo();//1、得到品牌的idlong brandId = bucket.getKeyAsNumber().longValue();brandVo.setBrandId(brandId);//2、得到品牌的名字ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brand_name_agg");String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();brandVo.setBrandName(brandName);//3、得到品牌的图片ParsedStringTerms brandImgAgg = bucket.getAggregations().get("brand_img_agg");String brandImg = brandImgAgg.getBuckets().get(0).getKeyAsString();brandVo.setBrandImg(brandImg);brandVos.add(brandVo);}result.setBrands(brandVos);//4、当前商品涉及到的所有分类信息//获取到分类的聚合List<SearchResult.CatalogVo> catalogVos = new ArrayList<>();ParsedLongTerms catalogAgg = response.getAggregations().get("catalog_agg");for (Terms.Bucket bucket : catalogAgg.getBuckets()) {SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo();//得到分类idString keyAsString = bucket.getKeyAsString();catalogVo.setCatalogId(Long.parseLong(keyAsString));//得到分类名ParsedStringTerms catalogNameAgg = bucket.getAggregations().get("catalog_name_agg");String catalogName = catalogNameAgg.getBuckets().get(0).getKeyAsString();catalogVo.setCatalogName(catalogName);catalogVos.add(catalogVo);}result.setCatalogs(catalogVos);//===============以上可以从聚合信息中获取====================////5、分页信息-页码result.setPageNum(param.getPageNum());//5、1分页信息、总记录数long total = hits.getTotalHits().value;result.setTotal(total);//5、2分页信息-总页码-计算int totalPages = (int) total % EsConstant.PRODUCT_PAGESIZE == 0 ?(int) total / EsConstant.PRODUCT_PAGESIZE : ((int) total / EsConstant.PRODUCT_PAGESIZE + 1);result.setTotalPages(totalPages);List<Integer> pageNavs = new ArrayList<>();for (int i = 1; i <= totalPages; i++) {pageNavs.add(i);}result.setPageNavs(pageNavs);//6、构建面包屑导航if (param.getAttrs() != null && param.getAttrs().size() > 0) {List<SearchResult.NavVo> collect = param.getAttrs().stream().map(attr -> {//1、分析每一个attrs传过来的参数值SearchResult.NavVo navVo = new SearchResult.NavVo();String[] s = attr.split("_");navVo.setNavValue(s[1]);R r = productFeignService.attrInfo(Long.parseLong(s[0]));if (r.getCode() == 0) {AttrResponseVo data = r.getData("attr", new TypeReference<AttrResponseVo>() {});navVo.setNavName(data.getAttrName());} else {navVo.setNavName(s[0]);}//2、取消了这个面包屑以后,我们要跳转到哪个地方,将请求的地址url里面的当前置空//拿到所有的查询条件,去掉当前String encode = null;try {encode = URLEncoder.encode(attr, "UTF-8");encode.replace("+", "%20");  //浏览器对空格的编码和Java不一样,差异化处理} catch (UnsupportedEncodingException e) {e.printStackTrace();}String replace = param.get_queryString().replace("&attrs=" + attr, "");navVo.setLink("http://127.0.0.1:12000/index.html?" + replace);return navVo;}).collect(Collectors.toList());result.setNavs(collect);}return result;}/*** 准备检索请求* 模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存),排序,分页,高亮,聚合分析** @return*/private SearchRequest buildSearchRequest(SearchParam param) {// 检索请求构建SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();/*** 查询:模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存)*///1. 构建 bool-queryBoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();//1.1 bool-must 模糊匹配if (!StringUtils.isEmpty(param.getKeyword())) {boolQueryBuilder.must(QueryBuilders.matchQuery("skuTitle", param.getKeyword()));}//1.2.1 bool-filter catalogId 按照三级分类id查询if (null != param.getCatalog3Id()) {boolQueryBuilder.filter(QueryBuilders.termQuery("catalogId", param.getCatalog3Id()));}//1.2.2 bool-filter brandId 按照品牌id查询if (null != param.getBrandId() && param.getBrandId().size() > 0) {boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId", param.getBrandId()));}//1.2.3 bool-filter attrs 按照指定的属性查询if (param.getAttrs() != null && param.getAttrs().size() > 0) {param.getAttrs().forEach(item -> {//attrs=1_5寸:8寸&2_16G:8GBoolQueryBuilder boolQuery = QueryBuilders.boolQuery();//attrs=1_5寸:8寸String[] s = item.split("_");String attrId = s[0]; // 检索的属性idString[] attrValues = s[1].split(":");//这个属性检索用的值boolQuery.must(QueryBuilders.termQuery("attrs.attrId", attrId));boolQuery.must(QueryBuilders.termsQuery("attrs.attrValue", attrValues));// 每一个属性都要生成一个 nested 查询NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery("attrs", boolQuery, ScoreMode.None);boolQueryBuilder.filter(nestedQueryBuilder);});}//1.2.4 bool-filter hasStock 按照是否有库存查询if (null != param.getHasStock()) {boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock", param.getHasStock() == 1));}//1.2.5 skuPrice bool-filter 按照价格区间查询if (!StringUtils.isEmpty(param.getSkuPrice())) {//skuPrice形式为:1_500或_500或500_RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("skuPrice");String[] price = param.getSkuPrice().split("_");if (price.length == 2) {rangeQueryBuilder.gte(price[0]).lte(price[1]);} else if (price.length == 1) {if (param.getSkuPrice().startsWith("_")) {rangeQueryBuilder.lte(price[1]);}if (param.getSkuPrice().endsWith("_")) {rangeQueryBuilder.gte(price[0]);}}boolQueryBuilder.filter(rangeQueryBuilder);}// 封装所有的查询条件searchSourceBuilder.query(boolQueryBuilder);/*** 排序,分页,高亮*/// 2.1 排序  形式为sort=hotScore_asc/descif (!StringUtils.isEmpty(param.getSort())) {String sort = param.getSort();// sort=hotScore_asc/descString[] sortFields = sort.split("_");SortOrder sortOrder = "asc".equalsIgnoreCase(sortFields[1]) ? SortOrder.ASC : SortOrder.DESC;searchSourceBuilder.sort(sortFields[0], sortOrder);}// 2.2 分页 from = (pageNum - 1) * pageSizesearchSourceBuilder.from((param.getPageNum() - 1) * EsConstant.PRODUCT_PAGESIZE);searchSourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);// 2.3 高亮if (!StringUtils.isEmpty(param.getKeyword())) {HighlightBuilder highlightBuilder = new HighlightBuilder();highlightBuilder.field("skuTitle");highlightBuilder.preTags("<b style='color:red'>");highlightBuilder.postTags("</b>");searchSourceBuilder.highlighter(highlightBuilder);}System.out.println("构建的DSL语句" + searchSourceBuilder.toString());/*** 聚合分析*///1. 按照品牌进行聚合TermsAggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg");brand_agg.field("brandId").size(50);//1.1 品牌的子聚合-品牌名聚合brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(1));//1.2 品牌的子聚合-品牌图片聚合brand_agg.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg").size(1));searchSourceBuilder.aggregation(brand_agg);//2. 按照分类信息进行聚合TermsAggregationBuilder catalog_agg = AggregationBuilders.terms("catalog_agg");catalog_agg.field("catalogId").size(20);catalog_agg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1));searchSourceBuilder.aggregation(catalog_agg);// 3. 按照属性信息进行聚合NestedAggregationBuilder attr_agg = AggregationBuilders.nested("attr_agg", "attrs");//3.1 按照属性ID进行聚合TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId");attr_agg.subAggregation(attr_id_agg);//3.1.1 在每个属性ID下,按照属性名进行聚合attr_id_agg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1));//3.1.2 在每个属性ID下,按照属性值进行聚合attr_id_agg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(50));searchSourceBuilder.aggregation(attr_agg);log.debug("构建的DSL语句 {}", searchSourceBuilder.toString());SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, searchSourceBuilder);return searchRequest;}
}

详情页展示核心代码

Controller@GetMapping("/{skuId}.html")public String skuItem(@PathVariable("skuId") Long skuId, Model model){SkuItemVo skuItemVo = skuInfoService.item(skuId);model.addAttribute("item",skuItemVo);System.out.println(skuItemVo);return "item";}
Servicepublic SkuItemVo item(Long skuId) {SkuItemVo skuItemVo = new SkuItemVo();CompletableFuture<SkuInfoEntity> infoFuture = CompletableFuture.supplyAsync(()->{SkuInfoEntity info = getById(skuId);skuItemVo.setInfo(info);return info;},executor);CompletableFuture<Void> saleAttrFuture = infoFuture.thenAcceptAsync((res) -> {List<SkuItemSaleAttrVo> saleAttrVos = skuSaleAttrValueService.getSaleAttrsBySpuId(res.getSpuId());skuItemVo.setSaleAttr(saleAttrVos);}, executor);CompletableFuture<Void> descFuture = infoFuture.thenAcceptAsync(res -> {SpuInfoDescEntity spuInfoDescEntity = spuInfoDescService.getById(res.getSpuId());skuItemVo.setDesc(spuInfoDescEntity);}, executor);CompletableFuture<Void> baseAttrFuture = infoFuture.thenAcceptAsync(res -> {List<SpuItemAttrGroupVo> attrGroupVos = attrGroupService.getAttrGroupWithAttrsBySpuId(res.getSpuId(), res.getCatalogId());skuItemVo.setGroupAttrs(attrGroupVos);}, executor);CompletableFuture<Void> imageFuture = CompletableFuture.runAsync(() -> {List<SkuImagesEntity> imagesEntities = skuImagesService.getImagesBySkuId(skuId);skuItemVo.setImages(imagesEntities);}, executor);try {CompletableFuture.allOf(saleAttrFuture, descFuture, baseAttrFuture, imageFuture).get();} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}return skuItemVo;}

短信认证注册用户与登录

  1. 写的时候忘了记了…现在不想记了

购物车功能

  1. 创建购物车微服务\前端页面…略
  2. 创建购物车的VO
购物车项
public class CartVo {/*** 购物车子项信息*/List<CartItemVo> items;/*** 商品数量*/private Integer countNum;/*** 商品类型数量*/private Integer countType;/*** 商品总价*/private BigDecimal totalAmount;/*** 减免价格*/private BigDecimal reduce = new BigDecimal("0.00");;public List<CartItemVo> getItems() {return items;}public void setItems(List<CartItemVo> items) {this.items = items;}public Integer getCountNum() {int count = 0;if (items != null && items.size() > 0) {for (CartItemVo item : items) {count += item.getCount();}}return count;}public Integer getCountType() {int count = 0;if (items != null && items.size() > 0) {for (CartItemVo item : items) {count += 1;}}return count;}public BigDecimal getTotalAmount() {BigDecimal amount = new BigDecimal("0");// 计算购物项总价if (!CollectionUtils.isEmpty(items)) {for (CartItemVo cartItem : items) {if (cartItem.getCheck()) {amount = amount.add(cartItem.getTotalPrice());}}}// 计算优惠后的价格return amount.subtract(getReduce());}public BigDecimal getReduce() {return reduce;}public void setReduce(BigDecimal reduce) {this.reduce = reduce;}
}
购物车子项
public class CartItemVo {private Long skuId;private Boolean check = true;private String title;private String image;/*** 商品套餐属性*/private List<String> skuAttrValues;private BigDecimal price;private Integer count;private BigDecimal totalPrice;public Long getSkuId() {return skuId;}public void setSkuId(Long skuId) {this.skuId = skuId;}public Boolean getCheck() {return check;}public void setCheck(Boolean check) {this.check = check;}public String getTitle() {return title;}public void setTitle(String title) {this.title = title;}public String getImage() {return image;}public void setImage(String image) {this.image = image;}public List<String> getSkuAttrValues() {return skuAttrValues;}public void setSkuAttrValues(List<String> skuAttrValues) {this.skuAttrValues = skuAttrValues;}public BigDecimal getPrice() {return price;}public void setPrice(BigDecimal price) {this.price = price;}public Integer getCount() {return count;}public void setCount(Integer count) {this.count = count;}/*** 计算当前购物项总价** @return*/public BigDecimal getTotalPrice() {return this.price.multiply(new BigDecimal("" + this.count));}public void setTotalPrice(BigDecimal totalPrice) {this.totalPrice = totalPrice;}}
  1. 配置
    ①配置redis缓存(将购物车的东西存到缓存中)
    ②配置SpingSession来共享登录时的Session

            <dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId></dependency>
    
    @Configuration
    public class WlmallSessionConfig {@Beanpublic CookieSerializer cookieSerializer() {DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();//放大作用域cookieSerializer.setDomainName("127.0.0.1");cookieSerializer.setCookieName("WLSESSION");return cookieSerializer;}@Beanpublic RedisSerializer<Object> springSessionDefaultRedisSerializer() {return new GenericJackson2JsonRedisSerializer();}}
    主启动类中开启@EnableRedisHttpSession
    
  2. 创建用户的To

public class UserInfoTo {private Long userId;private String userKey;/*** 是否临时用户*/private Boolean tempUser = false;}
  1. 设置拦截器、ThreadLocal、全局Session和线程池
public class CartInterceptor implements HandlerInterceptor {//线程共享数据public static ThreadLocal<UserInfoTo> toThreadLocal = new ThreadLocal<>();/**** 目标方法执行之前* @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {UserInfoTo userInfoTo = new UserInfoTo();HttpSession session = request.getSession();//获得当前登录用户的信息MemberResponseVo memberResponseVo = (MemberResponseVo) session.getAttribute(AuthServerConstant.LOGIN_USER);if (memberResponseVo != null) {//用户登录了userInfoTo.setUserId(memberResponseVo.getId());}//如果用户没有登录也会给一个Cookie来标识购物车Cookie[] cookies = request.getCookies();if (cookies != null && cookies.length > 0) {for (Cookie cookie : cookies) {//user-keyString name = cookie.getName();if (name.equals(CartConstant.TEMP_USER_COOKIE_NAME)) {userInfoTo.setUserKey(cookie.getValue());//标记为已是临时用户userInfoTo.setTempUser(true);}}}//如果没有临时用户一定分配一个临时用户if (StringUtils.isEmpty(userInfoTo.getUserKey())) {String uuid = UUID.randomUUID().toString();userInfoTo.setUserKey(uuid);}//目标方法执行之前toThreadLocal.set(userInfoTo);return true;}/*** 业务执行之后,分配临时用户来浏览器保存** @param request* @param response* @param handler* @param modelAndView* @throws Exception*/@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {//获取当前用户的值UserInfoTo userInfoTo = toThreadLocal.get();//如果没有临时用户一定保存一个临时用户if (!userInfoTo.getTempUser()) {//创建一个cookieCookie cookie = new Cookie(CartConstant.TEMP_USER_COOKIE_NAME, userInfoTo.getUserKey());//扩大作用域cookie.setDomain("127.0.0.1");//设置过期时间cookie.setMaxAge(CartConstant.TEMP_USER_COOKIE_TIMEOUT);response.addCookie(cookie);}}
}
@Configuration
public class WlmallWebConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new CartInterceptor())//注册拦截器.addPathPatterns("/**");}
}
@Configuration
public class WlmallSessionConfig {@Beanpublic CookieSerializer cookieSerializer() {DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();//放大作用域cookieSerializer.setDomainName("127.0.0.1");cookieSerializer.setCookieName("WLSESSION");return cookieSerializer;}@Beanpublic RedisSerializer<Object> springSessionDefaultRedisSerializer() {return new GenericJackson2JsonRedisSerializer();}}
@Configuration
public class MyThreadConfig {@Beanpublic ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties pool){return new ThreadPoolExecutor(pool.getCoreSize(),pool.getMaxSize(),pool.getKeepAliveTime(), TimeUnit.SECONDS,new LinkedBlockingDeque<>(100000),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());}
}
@ConfigurationProperties(prefix = "wlmall.thread")
@Component
@Data
public class ThreadPoolConfigProperties {private Integer coreSize;private Integer maxSize;private Integer keepAliveTime;
}

部分

Controller
@Controller
public class CartController {@Autowiredprivate CartService cartService;/**进入购物车* * @param model* @return* @throws ExecutionException* @throws InterruptedException*/@GetMapping( "/cart.html")public String cartListPage(Model model) throws ExecutionException, InterruptedException {//快速得到用户信息:id,user-keyUserInfoTo userInfoTo = CartInterceptor.toThreadLocal.get();CartVo cartVo = cartService.getCart();model.addAttribute("cart", cartVo);return "cartList";}/*** 添加商品到购物车* attributes.addFlashAttribute():将数据放在session中,可以在页面中取出,但是只能取一次* attributes.addAttribute():将数据放在url后面** @return*/@GetMapping(value = "/addToCart")public String addCartItem(@RequestParam("skuId") Long skuId,@RequestParam("num") Integer num,RedirectAttributes attributes) throws ExecutionException, InterruptedException {cartService.addToCart(skuId, num);attributes.addAttribute("skuId", skuId);return "redirect:http://127.0.0.1:21000/addToCartSuccessPage.html";}/*** 跳转到添加购物车成功页面** @param skuId* @param model* @return*/@GetMapping(value = "/addToCartSuccessPage.html")public String addToCartSuccessPage(@RequestParam("skuId") Long skuId,Model model) {//重定向到成功页面。再次查询购物车数据即可CartItemVo cartItemVo = cartService.getCartItem(skuId);model.addAttribute("cartItem", cartItemVo);return "success";}/**通过复选框勾选选项* * @param skuId* @param checked* @return*/@GetMapping(value = "/checkItem")public String checkItem(@RequestParam(value = "skuId") Long skuId,@RequestParam(value = "checked") Integer checked) {cartService.checkItem(skuId, checked);return "redirect:http://127.0.0.1:21000/cart.html";}/*** 改变商品数量** @param skuId* @param num* @return*/@GetMapping(value = "/countItem")public String countItem(@RequestParam(value = "skuId") Long skuId,@RequestParam(value = "num") Integer num) {cartService.changeItemCount(skuId, num);return "redirect:http://127.0.0.1:21000/cart.html";}/*** 删除商品信息** @param skuId* @return*/@GetMapping(value = "/deleteItem")public String deleteItem(@RequestParam("skuId") Integer skuId) {cartService.deleteIdCartInfo(skuId);return "redirect:http://127.0.0.1:21000/cart.html";}
}
Service
@Service
public class CartServiceImpl implements CartService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Autowiredprivate ProductFeignService productFeignService;@AutowiredThreadPoolExecutor executor;private final  String CART_PREFIX = "wlmall:cart:";@Overridepublic CartVo getCart(){CartVo cartVo = new CartVo();UserInfoTo userInfoTo = CartInterceptor.toThreadLocal.get();if (userInfoTo.getUserId() != null) {//1、登录String cartKey = CART_PREFIX + userInfoTo.getUserId();//临时购物车的键String temptCartKey = CART_PREFIX + userInfoTo.getUserKey();//2、如果临时购物车的数据还未进行合并List<CartItemVo> tempCartItems = getCartItems(temptCartKey);if (tempCartItems != null) {//临时购物车有数据需要进行合并操作for (CartItemVo item : tempCartItems) {addToCart(item.getSkuId(), item.getCount());}//清除临时购物车的数据clearCartInfo(temptCartKey);}//3、获取登录后的购物车数据【包含合并过来的临时购物车的数据和登录后购物车的数据】List<CartItemVo> cartItems = getCartItems(cartKey);cartVo.setItems(cartItems);} else {//没登录String cartKey = CART_PREFIX + userInfoTo.getUserKey();//获取临时购物车里面的所有购物项List<CartItemVo> cartItems = getCartItems(cartKey);cartVo.setItems(cartItems);}return cartVo;}@Overridepublic CartItemVo addToCart(Long skuId, Integer num)  {//拿到要操作的购物车信息BoundHashOperations<String, Object, Object> cartOps = getCartOps();//判断Redis是否有该商品的信息String productRedisValue = (String) cartOps.get(skuId.toString());//如果没有就添加数据if (StringUtils.isEmpty(productRedisValue)) {//添加新的商品到购物车CartItemVo cartItemVo = new CartItemVo();//开启第一个异步任务CompletableFuture<Void> getSkuInfoFuture = CompletableFuture.runAsync(() -> {//远程查询当前要添加商品的信息R productSkuInfo = productFeignService.getInfo(skuId);SkuInfoVo skuInfo = productSkuInfo.getData("skuInfo", new TypeReference<SkuInfoVo>() {});//数据赋值操作cartItemVo.setSkuId(skuInfo.getSkuId());cartItemVo.setTitle(skuInfo.getSkuTitle());cartItemVo.setImage(skuInfo.getSkuDefaultImg());cartItemVo.setPrice(skuInfo.getPrice());cartItemVo.setCount(num);}, executor);//开启第二个异步任务CompletableFuture<Void> getSkuAttrValuesFuture = CompletableFuture.runAsync(() -> {//2、远程查询skuAttrValues组合信息List<String> skuSaleAttrValues = productFeignService.getSkuSaleAttrValues(skuId);cartItemVo.setSkuAttrValues(skuSaleAttrValues);}, executor);//等待所有的异步任务全部完成try {CompletableFuture.allOf(getSkuInfoFuture, getSkuAttrValuesFuture).get();} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}String cartItemJson = JSON.toJSONString(cartItemVo);cartOps.put(skuId.toString(), cartItemJson);return cartItemVo;} else {//购物车有此商品,修改数量即可CartItemVo cartItemVo = JSON.parseObject(productRedisValue, CartItemVo.class);cartItemVo.setCount(cartItemVo.getCount() + num);//修改redis的数据String cartItemJson = JSON.toJSONString(cartItemVo);cartOps.put(skuId.toString(), cartItemJson);return cartItemVo;}}@Overridepublic CartItemVo getCartItem(Long skuId) {//拿到要操作的购物车信息BoundHashOperations<String, Object, Object> cartOps = getCartOps();String redisValue = (String) cartOps.get(skuId.toString());return JSON.parseObject(redisValue, CartItemVo.class);}@Overridepublic void checkItem(Long skuId, Integer checked) {//查询购物车里面的商品CartItemVo cartItem = getCartItem(skuId);//修改商品状态cartItem.setCheck(checked == 1);//序列化存入redis中String redisValue = JSON.toJSONString(cartItem);BoundHashOperations<String, Object, Object> cartOps = getCartOps();cartOps.put(skuId.toString(), redisValue);}@Overridepublic void changeItemCount(Long skuId, Integer num) {//查询购物车里面的商品CartItemVo cartItem = getCartItem(skuId);cartItem.setCount(num);BoundHashOperations<String, Object, Object> cartOps = getCartOps();//序列化存入redis中String redisValue = JSON.toJSONString(cartItem);cartOps.put(skuId.toString(), redisValue);}@Overridepublic void deleteIdCartInfo(Integer skuId) {BoundHashOperations<String, Object, Object> cartOps = getCartOps();cartOps.delete(skuId.toString());}private BoundHashOperations<String, Object, Object> getCartOps() {//先得到当前用户信息UserInfoTo userInfoTo = CartInterceptor.toThreadLocal.get();String cartKey = "";if (userInfoTo.getUserId() != null) {cartKey = CART_PREFIX + userInfoTo.getUserId();} else {cartKey = CART_PREFIX + userInfoTo.getUserKey();}//绑定指定的key操作Redisreturn stringRedisTemplate.boundHashOps(cartKey);}/*** 获取购物车里面的数据** @param cartKey* @return*/private List<CartItemVo> getCartItems(String cartKey) {//获取购物车里面的所有商品BoundHashOperations<String, Object, Object> operations = stringRedisTemplate.boundHashOps(cartKey);List<Object> values = operations.values();if (values != null && values.size() > 0) {return values.stream().map((obj) -> {String str = (String) obj;return JSON.parseObject(str, CartItemVo.class);}).collect(Collectors.toList());}return null;}public void clearCartInfo(String cartKey) {stringRedisTemplate.delete(cartKey);}
}

RabbitMQ

  1. 安装
docker run -d --name rabbitmq -p 5671:5671 -p 5672:5672 -p 4369:4369 -p 25672:25672 -p 15671:15671 -p 15672:15672 rabbitmq:management
Unable to find image 'rabbitmq:management' locally
  1. 整合到SpringBoot
        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency>
spring.rabbitmq.host=Ip
spring.rabbitmq.port=5672
spring.rabbitmq.virtual-host=/
spring.rabbitmq.publisher-confirm-type=correlated
# 开启发送端消息抵达队列的确认
spring.rabbitmq.publisher-returns=true
#只要抵达队列,以异步发送优先回调我们这个return
spring.rabbitmq.template.mandatory=true
#手动ack
spring.rabbitmq.listener.simple.acknowledge-mode=manualspring.main.allow-circular-references = true

主启动类开启消息队列
@EnableRabbit
测试使用

    @AutowiredAmqpAdmin amqpAdmin;@Testvoid contextLoads() {//创建exchangeDirectExchange directExchange = new DirectExchange("hello-java-exchange",true,false);amqpAdmin.declareExchange(directExchange);//创建队列Queue queue = new Queue("hello-java-queue",true,false,false);amqpAdmin.declareQueue(queue);//绑定两者Binding binding = new Binding("hello-java-queue",Binding.DestinationType.QUEUE,"hello-java-exchange","hello.java",null);amqpAdmin.declareBinding(binding);//发送信息String msg = "hello word!";rabbitTemplate.convertAndSend("hello-java-exchange","hello.java",msg);}

如果传一个对象信息,可以将该对象序列号后传入,或者传json格式,需要如下配置


@Configuration
public class MyRabbitConfig {@Beanpublic MessageConverter messageConverter(){return new Jackson2JsonMessageConverter();}
}

可靠传输设置

spring.rabbitmq.publisher-confirm-type=correlated
# 开启发送端消息抵达队列的确认
spring.rabbitmq.publisher-returns=true
#只要抵达队列,以异步发送优先回调我们这个return
spring.rabbitmq.template.mandatory=true
#手动ack
spring.rabbitmq.listener.simple.acknowledge-mode=manual

@Configuration
public class MyRabbitConfig {@AutowiredRabbitTemplate rabbitTemplate;@Beanpublic MessageConverter messageConverter(){return new Jackson2JsonMessageConverter();}@PostConstruct //对象创建完成后,执行该方法public void initRabbitTemplate(){//设置确认回调rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {@Overridepublic void confirm(CorrelationData correlationData, boolean b, String s) {}});rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {@Overridepublic void returnedMessage(Message message, int i, String s, String s1, String s2) {}});}
}

订单服务

电商项目随手笔记(高级篇)相关推荐

  1. 课堂笔记 - 电商项目开发笔记-02

    易购商城 第二天 目  录 1 课程计划 3 1.1 目标 3 1.2 功能分析 3 1.2.1 相关数据表 3 1.2.2 实现的思路 3 2 第一部分:实现商品类目选择功能 4 2.1 需求分析 ...

  2. Django REST framework+Vue 打造生鲜电商项目(笔记八)

    (form:http://www.cnblogs.com/derek1184405959/p/8862569.html) 十一.pycharm 远程代码调试 第三方登录和支付,都需要有服务器才行(回调 ...

  3. python全栈生鲜电商_Django REST framework+Vue 打造生鲜电商项目(笔记一)

    首先,这系列随笔是我个人在学习Bobby老师的Django实战项目中,记录的觉得对自己来说比较重要的知识点,不是完完整整的项目步骤过程....如果有小伙伴想找完整的教程,可以看看这个(https:// ...

  4. python全栈生鲜电商_Django REST framework+Vue 打造生鲜电商项目(笔记八)

    (form:http://www.cnblogs.com/derek1184405959/p/8862569.html) 十一.pycharm 远程代码调试 第三方登录和支付,都需要有服务器才行(回调 ...

  5. mmall电商项目学习笔记之 idea,maven工程整合ssm框架

    项目目录结构 1.pom文件导入jar包 1.1 <properties><!--设置编码格式--><project.build.sourceEncoding>UT ...

  6. python全栈生鲜电商_Django REST framework+Vue 打造生鲜电商项目(笔记十)

    (from:https://www.cnblogs.com/derek1184405959/p/8877643.html  有修改) 十三.首页.商品数量.缓存和限速功能开发 首先把pycharm环境 ...

  7. mmall电商项目学习笔记之mybatis三剑客

    一.Mybatis plugin IDEA 2017.3版本下Mybatis plugin 3.53安装使用 插件下载地址 http://www.awei.org/download/iMybatis- ...

  8. 谷粒商城电商项目 分布式高级篇

    更多视频,JAVA收徒 QQ:987115885谷粒商城电商项目 分布式高级篇102.全文检索-ElasticSearch-简介.mp4103.全文检索-ElasticSearch-Docker安装E ...

  9. 老表笔记之电商项目实战测试流程

    寰球优品电商项目-购物车的功能需求分析 01 寰球优品电商项目的核心业务流程 注册登录>浏览商品>添加购物车>提交订单>订单支付>查看订单 02 软件测试点分析基本原则- ...

最新文章

  1. shiro系列二、身份验证和授权
  2. Bear and Strings
  3. Bash shell脚本打印出正在执行的命令
  4. 15-3 并发调度器
  5. 待起飞的の集训8.5
  6. Pivot与Center的区别
  7. React Native之七牛
  8. 微商引流脚本,微商怎样选择正确的引流脚本?
  9. 有源滤波器: 基于UAF42的50Hz陷波器设计
  10. 61php飞信发送类(phpfetion)v1.5,资源索引 L_PC6下载
  11. 据说这是最难学的十大编程语言 Java排第三
  12. Veeam BR 11 Windows Agent备份
  13. PHP加密如何保护php源码不被破解不被轻易去授权
  14. MySQL---数据库基础入门
  15. 【校招Verilog快速入门】基础语法篇:VL1、四选一多路器
  16. VsCode中运行HTML页面时出现乱码
  17. 2023 华为 Datacom-HCIE 真题题库 12/12(完结)--含解析
  18. ZZNUOJ_C语言1046:奇数的乘积(完整代码)
  19. CentOS 8构建桌面办公环境
  20. erp服务器和文件服务器,erp是用本地服务器还是云

热门文章

  1. 计算机办公软件应用操作试题,计算机应用和Office办公软件考试试题题目
  2. Java学习——已知有六个数字1,2,3,4,5,6 在要求将这六个数字中所有的组合存放到数组中 (1)必须是6位数 (2)每个位置上的数不能重复
  3. Dinornis – Rendering your Model in Mudbox by RenderMan Directly !
  4. 论文阅读《SuperGlue: Learning Feature Matching with Graph Neural Networks》
  5. unity3d Runtime Transform Gizmos 插件使用
  6. 对话ZEGO即构科技许明龙:聊聊元宇宙与实时互动RTI
  7. css calc复合运算
  8. 工业废水在线监测系统
  9. 大额订单分部门统计报表的制作
  10. 经纬恒润Adaptive AUTOSAR解决方案 INTEWORK-EAS-AP