栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 前沿技术 > 大数据 > 大数据系统

电商项目随手笔记(高级篇,未完)

电商项目随手笔记(高级篇,未完)

项目笔记

Elastic Search

安装与使用整合到Spring Boot 商城业务

商品的上架Thymeleaf配置Thymeleaf一级分类的渲染二级三级分类的渲染 缓存

Redis的配置将二三级分类的查询存入缓存缓存的穿透、雪崩、击穿分布式锁 Redisson

Elastic Search 安装与使用
    Elastic Search的安装与使用
整合到Spring Boot
    新建微服务wlmall-search导入ElasticSearch依赖
    
    	//因为Spring Boot会根据其版本来自动更换elasticsearch的依赖,所以在这指定版本
        7.4.2
    
//注意版本对应
   
            org.elasticsearch.client
            elasticsearch-rest-high-level-client
            7.4.2
   
    添加配置类
@SpringBootConfiguration
public class ElasticSearchConfig {
   private static final RequestOptions COMMON_OPTIONS;
    static {
        RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
        COMMON_OPTIONS = builder.build();
    }
    @Bean
    public RestHighLevelClient esRestClient(){
        RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("Ip",9200,"http")
                )
        );
        return client;
    }
}
    将该微服务加入到注册中心去。插入数据的使用
  IndexRequest indexRequest = new IndexRequest("索引名");
        indexRequest.id("指定插入数据的id,不指定会默认生成");
		//想要插入的数据,封装成对象,放到这里,转化成Json
        String 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);
    查询的使用
        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();
    }
Service
   public void up(Long spuId) {

        //根据spuId来获取SkuInfo的实体对象
        List skuInfoEntities = skuInfoService.getSkusBySpuId(spuId);
        //根据SpuId获取所有的规格属性
        List baseAttrs = productAttrValueService.baseAttrlistforspu(spuId);
        //收集所有的规格属性的attrId
        List attrIds = baseAttrs.stream().map(ProductAttrValueEntity::getAttrId).collect(Collectors.toList());
        //根据attrIds获取所有可以当作检索条件的attr
        List searchAttrIds = attrService.selectSearchAttrIds(attrIds);
        //将查到的Id都存入Set集合中去
        Set idSet = new HashSet<>(searchAttrIds);
        //先过滤掉不能被检索的属性,再将过滤后的一些属性赋值给要上架的对象实体并收集起来
        List 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());
        //收集所有的SkuId
        List skuIdList = skuInfoEntities.stream().map(SkuInfoEntity::getSkuId).collect(Collectors.toList());
        //远程调用来判断是否还有库存, 使用try,catch是为了使远程调用失败的时候将库存的值设为null
        Map stockMap = null;
        try {
            R skuHasStock = wareFeignService.getSkusHasStock(skuIdList);
            //将调用返回来的数据转化为List 类型,因为两个服务之间使用的是JSON来传输,
            TypeReference> typeReference = new TypeReference>() {
            };
            //简写方式,收集获得库存List集合转化成一个Map集合,其中skuId作为key,stock作为value
            stockMap = skuHasStock.getData(typeReference).stream()
                    .collect(Collectors.toMap(SkuHasStockVo::getSkuId, item -> item.getHasStock()));
        } catch (Exception e) {
            log.error("库存服务查询异常:原因{}", e);
        }
        Map finalStockMap = stockMap;
        //组合拼装上架的信息
        List 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 TODO
            esModel.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 skuIds) {
        List vos = wareSkuService.getSkusHasStock(skuIds);
        //将获取的数据带回去
        System.out.println(R.ok().setData(vos));
        return R.ok().setData(vos);
    }
    public List getSkusHasStock(List skuIds) {
        //使用skuId通过遍历来找出所有的sku库存是否存在
        List 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 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 {
    @Autowired
    RestHighLevelClient restHighLevelClient;
    @Override
    public boolean productStatusUp(List 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();
        }

        //如果批量错误,返回true
        boolean hasFailures = bulk.hasFailures();

        List collect = Arrays.stream(bulk.getItems()).map(BulkItemResponse::getId).collect(Collectors.toList());
        return hasFailures;
    }
}
Thymeleaf

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

配置Thymeleaf

添加依赖

   
            org.springframework.boot
            spring-boot-starter-thymeleaf
        
        //用来热部署,不需要每次都重启服务就可以看前端页面效果
        
            org.springframework.boot
            spring-boot-devtools
            true
        
一级分类的渲染
Controller
    @GetMapping({"/","/index.html"}) //访问首页
    public String indexPage(Model model){
        //查找所有的一级分类
        List categoryEntits = categoryService.getLevel1Categorys();
        model.addAttribute("categorys",categoryEntits);
        return "index"; //字符串类型返回的是页面,thymeleaf会拼接成页面名
    }
Service
  public List getLevel1Categorys() {
        return baseMapper.selectList(new QueryWrapper().eq("cat_level",1));
    }
该部分对应的前端代码
  • 家用电器
  • 二级三级分类的渲染

    创建实体类

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Catelog2Vo {
        private  String catalog1Id; // 一级分类的ID
        private List catalog3List;//三级分类的集合
        private String id;
        private String name;
    
        @Data
        @NoArgsConstructor
        @AllArgsConstructor
        public static class Catelog3Vo{
            private String catalog2Id; //二级分类的ID
            private String id;
            private String name;
        }
    }
    
    Controller
        @ResponseBody
        @GetMapping("/index/catalog.json") //前端请求二三级分类所发的路径
        public Map> getCatalogJson(){
            System.out.println("-------------------------");
            Map> map = categoryService.getCatalogJson();
            return map;
        }
    
    Service
    public Map> getCatalogJson() {
            //获取所有一级分类
            List level1Categorys = getLevel1Categorys();
            //将数据封装为MAP的形式
            Map> parent_cid = level1Categorys.stream().collect(Collectors.toMap(key->key.getCatId().toString(),value->{
                //查找所有的二级分类,因为一级分类的ID是二级分类的父ID
                List categoryEntities = baseMapper.selectList(new QueryWrapper().eq("parent_cid", value.getCatId()));
                List 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是三级分类的父ID
                        List level3Catalog = baseMapper.selectList(new QueryWrapper().eq("parent_cid",item.getCatId()));
                        //如果有三级分类
                        if(level3Catalog != null){
                            List 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的配置
    导入依赖
        
            org.springframework.boot
            spring-boot-starter-data-redis
        
     配置yaml
    spring: 
       redis:
    		host: redis所在的主机Ip
    		port: redis端口号
    
    将二三级分类的查询存入缓存
        public Map> getCatalogJson() {
            //先从缓存中获取
            String catelogJson = redisTemplate.opsForValue().get("catalogJson");
            //如果内存中没有,则从数据库中查询并放入内存
            //都以JSON字符串的格式进行存储,方便跨平台,跨语言
            if (StringUtils.isEmpty(catelogJson)){
            	//从数据库中查询,上面二三级分类改为getCatalogJsonDB方法
                Map> catalogJsonDB = getCatalogJsonDB();
                String s = JSON.toJSONString(catalogJsonDB);
                redisTemplate.opsForValue().set("catalogJson",s);
                return catalogJsonDB;
            }
            Map> stringCatelog2VoMap = JSON.parseObject(catelogJson, new TypeReference>>(){});
            return  stringCatelog2VoMap;
        }
    
    缓存的穿透、雪崩、击穿

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

    分布式锁 Redisson
      配置
      yaml
            
                org.redisson
                redisson
                3.12.0
            
    

    配置类

    @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;
        }
    }
    

      lock锁
      ①创建一个锁

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

    ②加锁

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

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

    转载请注明:文章转载自 www.mshxw.com
    本文地址:https://www.mshxw.com/it/775419.html
    我们一直用心在做
    关于我们 文章归档 网站地图 联系我们

    版权所有 (c)2021-2022 MSHXW.COM

    ICP备案号:晋ICP备2021003244-6号