分布式名词概念

微服务

微服务是一种架构,“微”代表由多个小的服务组成。

分布式

分布式也属于微服务中的一种,分布式系统由多个节点组成的一个系统,这个指的是服务器。不同的节点分布不同的地方。

集群

集群说的多个服务器组成,为同一个业务提供服务。

节点

节点指的是服务器。

远程调用

分布部署以后,两个不同的模块之间相互调用。

负载均衡

在一个集群内,模块需要调用那个集群中的某一个服务。假如集群中的其中一个服务资源沾满,此时就不能再向这个服务发起请求,这时负载均衡器需要向集群内的其他的可用服务器发起请求。常见的负载均衡算法有:轮询、随机、等。

服务注册、发现

不同的模块调用其他模块如何知道这个模块服务,这时将模块加入到注册中心如nacos、然后模块就知道有哪些服务。

配置中心

多个模块之间的配置大都相同,如果需要修改其中某一个模块的配置,然后还需要修改其他的模块之间的配置,然后再重启,而配置中心可以帮你解决这个问题,只需要让模块在配置中心获取配置。让配置中心来集中管理。

服务熔断、服务降级

在微服务中,之间通过网络通信,存在相互依赖,如果其中一台服务挂掉了,有可能造成雪崩效应,要避免这种清空,必须要使用容错机制来保护服务。

服务熔断:一个服务的失败到了某个阈值,就不在去请求这个服务,只返回失败或者默认数据。

服务降级:当到达系统高峰期,可以让非核心业务停止运行。只抛出异常或者返回本地mock数据

API网关

请求同一发送到网关层,在网关中做一些处理(限流、鉴权、熔断降级、路由,过滤,负载均衡),然后分发给其他服务。

环境

安装centos

自行安装。

安装docker

安装地址: https://docs.docker.com/engine/install/centos/

安装mysql

在docker中安装:

sudo mkdir -p /usr/local/mysql
sudo cd /usr/local/mysql/
sudo touch conf.d
sudo mkdir -p /usr/local/mysql/data
sudo mkdir -p /usr/local/mysql/log


mysql8.0的目录结构
/etc/mysql/conf.d
/var/lib/mysql
/var/log

安装镜像:
docker pull mysql:8.2.0

构建容器:
sudo docker run -d --name glsc-mysql \
-v /var/local/mysql/conf.d:/etc/mysql/conf.d \
-v /var/local/mysql/data:/var/lib/mysql \
-v /var/local/mysql/log:/var/log \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=root \
mysql:8.2.0

运行SQL脚本:

  • 创建五个数据库,然后导入对应的sql文件:
  1. gulimall_pms
  2. gulimall_ums
  3. gulimall_wms
  4. gulimall_oms
  5. gulimall_sms
安装redis
sudo mkdir -p /usr/local/redis/data
sudo mkdir -p /usr/local/redis/conf

sudo docker pull redis

sudo docker run -d  --name glsc-redis \
-v /user/local/redis/conf/redis.conf:/etc/redis/redis.conf \
-v /user/local/redis/data:/data \
-p 6379:6379 \
redis:7.0.12 redis-server \
/etc/redis/redis.conf --appendonly yes

创建项目

总项目名:gulimall

子模块:gulimall-服务名

包名:com.blog.qh.服务名

分为以下几个服务:

  • 商品服务(product)
  • 用户服务(member)
  • 仓储服务(ware)
  • 订单服务(order)
  • 优惠卷服务(coupon)
使用人人开源快速开发

后端:https://gitee.com/renrenio/renren-fast

前端:https://gitee.com/renrenio/renren-fast-vue

把API模块添加到gulimall下的子模块。

在创建一个数据库:gulimall_admin导入人人开源提供的mysql.sql文件。

运行前端:

  • npm i
  • npm run dev
url: jdbc:mysql://198.19.249.161:3306/gulimall_admin?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai

jdbc:mysql://198.19.249.161:3306/gulimall_admin?useSSL=false&serverTimezone=UTC

使用人人开源逆向生成各个模块的基础模块。

创建一个公共模块,存放相同的依赖的一些工具:gulimall-common

该模块下的依赖:

  • mybaitsplus
  • joda-time
  • org.apache.commons.lang
  • httpclient

配置各自服务的applicant.yaml数据库信息以及MybaitsPlus相关配置(mapper扫描路径、id自增类型等)。

使用的SpringCloudAlibaba的版本是2.1.0.RELEASE。

需要注意springCloud的版本和SpringBoot的版本对应关系:https://sca.aliyun.com/zh-cn/docs/2022.0.0.0/overview/version-explain;包括它的一些SpringCloud的分布式组件的版本对应。

Spring Cloud Alibaba VersionSentinelNacosRocketMQDubboSeata
2.1.0.RELEASE or 2.0.0.RELEASE or 1.5.0.RELEASE1.6.31.1.14.4.02.7.30.7.1

https://github.com/alibaba/spring-cloud-alibaba/blob/v2.1.1.RELEASE/README-zh.md

安装nacos

关于nacos的安装参照-《SpringCloudAlibaba》笔记下的分布式组件-nacos。

  1. 引入nacos服务发现依赖:
<dependency>
   <groupId>com.alibaba.cloud</groupId>
   <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
  1. 把各个服务注册到nacos中,编写application.yaml 配置文件:
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8890
  application:
    name: gulimall-coupon # 不同模块填写不同的name
  1. 启动类中启动添加服务发现注解:@EnableDiscoveryClient
引入SpringCloud Gateway

依赖:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <version>2.1.5.RELEASE</version>
        </dependency>

编写路由规则(Demo):

spring:
  cloud:
    gateway:
      routes:
#        - id: query_route
#          uri: https://www.baidu.com
#          predicates:
#            - Query:url,baidu
#        - id: path_route
#          uri: https://www.baidu.com
#          predicates:
#              - Path=/**
        - id: gulimall-order
          uri: lb://gulimall-order
          predicates:
            - Path=/test/**
      discovery:
        locator:
          enabled: true

使用MybaitsPlus配置逻辑删除配置。

商品服务

递归获取三级目录

根据商品服务的分类数据表。

    public List<CategoryEntity> builderTreeList() {
        List<CategoryEntity> list = this.list();
        List<CategoryEntity> tree = list.stream()
                .filter(categoryEntity -> categoryEntity.getParentCid().equals(0L)) 
                .map(categoryEntity -> {
                    categoryEntity.setChildren(BuilderChildren(list,categoryEntity));
                    return categoryEntity;
                })
                .collect(Collectors.toList());
        return tree;
    }


    /**
     * 构建children
     * @param source
     * @param me
     * @return
     */
    private List<CategoryEntity> BuilderChildren(List<CategoryEntity> source,CategoryEntity me) {
        return source.stream()
                .filter(categoryEntity -> categoryEntity.getParentCid().equals(me.getCatId()))
                .map(categoryEntity -> {
                    categoryEntity.setChildren(BuilderChildren(source, categoryEntity));
                    return categoryEntity;
                })
                .collect(Collectors.toList());
    }

把所有请求都转发到网关中。由网关统一分配;我的网关服务端口是9091。

把人人前端的BaseURL改为网关地址,规定前端请求都带/api。:'http://localhost:9091/api';

配置网关路由:

spring:
  cloud:
    gateway:
      routes:
        - id: renren-fast
          uri: lb://renren-fast
          predicates:
            - Path=/api/**
          filters:
            - RewritePath=/api/?(?<segment>.*), /renren-fast/$\{segment}

上面使用重写路由,把/api/替换为/renren-fast/**。

网关配置跨域

添加CorsWebFilter类配置跨域。

注意:这些类的在reactive包下。

@Configuration
public class GulimallCorsConfig {

    @Bean
    public CorsWebFilter corsConfig() {

        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        config.setAllowCredentials(true);
        // 必须是reactive包下的UrlBasedCorsConfigurationSource
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        source.registerCorsConfiguration("/**", config);
        return new CorsWebFilter(source);
    }
}

配置商品服务的路由转发规则:

        - id: product_route
          uri: lb://gulimall-product
          predicates:
            - Path=/api/product/**
          filters:
            - RewritePath=/api/?(?<segment>.*), /$\{segment} 

商品分类的添加和删除—— 前端实现 :

添加:三级菜单后不允许添加新分类
删除:只有当要删除的菜单没有子菜单才可以删除。
  • [ ] 商品分类-拖拽功能:TODO:待实现

    • 不可以拖入到三级分类下。

品牌管理

使用人人开源生成基本的增删改查页面。

品牌图片上传(使用阿里云OSS)存储图片。

统一异常处理:在《Spring开发中需要封装的操作》的文章中说明。

使用JSR303做数据校验。

JSR303

在Controller方法中加入@valida或@validated。然后在实体对象属性中添加相关校验注解@NotBlank或@NotNull等。

分组校验:

分组校验必须使用@validated注解。在该注解中指定(group)例如:

@Validated(value = {UpdateGroup.class})。然后再在需要校验的方法上指定分组:

@NotNull(message = "showStatus不能为空",groups = {AddGroup.class, UpdateGroup.class}):只有在添加和修改时才会校验。AddGroup和UpdateGroup由我们自己定义只要为一个标识,不需要有有实际内容。

自定义校验器:

  1. 定义接口
@Documented
@Constraint(validatedBy = CustomerScope.class )
@Target({METHOD, FIELD})
@Retention(RUNTIME)
public @interface CustomerScopeValidation {
    String message() default "取值范围只能是1和0之间";
    String start() default "_";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
    @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
    @Retention(RUNTIME)
    @Documented
    @interface List {
        CustomerScopeValidation[] value();
    }
}
  1. 定义处理类,需要实现 ConstraintValidator接口

    public class CustomerScope implements ConstraintValidator<CustomerScopeValidation,Integer> {
        @Override
        public boolean isValid(Integer integer, ConstraintValidatorContext constraintValidatorContext) {
            log.info("integer是传过来的值:{}",integer);
            if(integer.equals(1) || integer.equals(0)) {
                return true;
            }
            return false;
        }
    
        @Override
        public void initialize(CustomerScopeValidation constraintAnnotation) {
            String start = constraintAnnotation.start();
        }
    }
  2. 使用

        @CustomerScopeValidation(groups = {AddGroup.class, DeleteGroup.class, UpdateGroup.class})
        private Integer showStatus;

概念:SKU、SPU

SPU、SKU

SPU:Standard Product Unit(标准化产品单元)

SKU:Stock Keeping Unit(库存量单位)

SPU是SKU的爸爸,例如一部手机iPhone 15 就是一个SPU。

你要选择iPhone 15 黑色、128G,这些参数就是SKU。

平台属性-属性分组

获取三级分类的分组

  • 点击对应的三级分类传递该分类id,查询该分类所有的分组数据,默认传入0查询全部分组。
  • 条件查询输入条件可以精确查询分组id和模糊查询组名。

新增分组

  • 前端:新增分组需要选择要新增在那个三级分类下。提供一个选择框。选择三级分类。
  • 注意:后端供选择的三级分类,第三级分类的children属性为空的情况不应该返回该字段。可以在分类实体类中添加一个注解:@JsonInclude(value = JsonInclude.Include.NON_EMPTY)

分组关联属性

  • 只能查询当前分组属于的分类下的所有属性。

1.先查询分组表,得到当前分组属于的分类id。然后保存这个分类id,(得到当前分组属于的id)

2.使用传过来的分组id,去属性属性分组表中查询该分组下的所有属性,得到该分组下的所有属性id,保存起来。(目的是排除当前分组已有的属性)

3.最后去属性表中查询所有属性不含包上面属于当前分组的属性id,使用not in,并且条件是分类id等于上面保存的分类id。

  • 上面三步对应的sql语句:

    select * from pms_attr_group where attr_group_id = 6;

    
    - ```sql
    # 得到要排除的属性id,假如是7,8
    select * from pms_attr_attrgroup_relation where attr_group_id = 6  
  • 最后判断是否有传入key来进行搜索:

    在最后加入Wrapper是判断加入就可以了。

  • 下面是完整代码:

    • public PageResultVo attrGroupNotRelationAttr(Map<String,Object> map,Long groupId) {
        
          AttrGroupEntity attrGroup = attrGroupService.getById(groupId);
          Long catelogId = attrGroup.getCatelogId();
          List<AttrAttrgroupRelationEntity> attrAttrGroupRelation = attrAttrgroupRelationService.list(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_group_id", groupId));
          List<Long> existAttrId = new ArrayList<>();
          attrAttrGroupRelation.forEach(item -> {
              existAttrId.add(item.getAttrId());
          });
          long page = Long.parseLong((String) map.get("page"));
          long limit = Long.parseLong((String) map.get("limit"));
          QueryWrapper<AttrEntity> queryWrapper = new QueryWrapper<AttrEntity>().eq("catelog_id", catelogId).and(i -> {
              i.notIn("attr_id", existAttrId);
          });
        
          String key = (String) map.get("key");
          if(!StringUtils.isEmpty(key)) {
              queryWrapper.and(i -> {
                  i.eq("attr_id",key).or().like("attr_name",key);
              });
          }
        
          Page<AttrEntity> result = attrService.page(new Page<>(page, limit),queryWrapper);
          return PageResultVo.builder().currPage(result.getCurrent()).pageSize(result.getSize()).totalPage(result.getTotal()).totalCount(result.getTotal()).list(result.getRecords()).build();
    
    - PageResultVo是自己写的Vo,没有使用提供的工具类返回page数据。

添加分组和属性的关联

  • 获取传过来的分组id和属性id,是一个集合存这两个字段。取出来编辑

品牌管理-关联分类

  • 添加品牌关联的分类:

    • 选择关联的三级分类后,点击确定,发送请求到后台,携带品牌id和分类id,在品牌分类关联表中添加,注意该表中有两个冗余字段品牌名和分类名,还需要根据拿到的品牌id和分类id去获取到它们的名字然后再添加到品牌分类关联实体对象中,再一起更新。
  • 因为上面两个冗余字段,当在更新品牌名或者分类名时还需要同时去更新品牌分类关联表中对应的字段保证一致性。

注意,在删除属性时,还需要删除去删除属性和属性分组关联表中的数据需要修改人人生成生成的删除逻辑。

商品维护

新增商品

  1. 商品基本信息

选择商品的分类。选择该分类下的品牌。例如手机这个三级分类,然后手机这个分类的品牌,假如有华为、小米、iPhone等品牌。

  1. 规格参数

显示当前分类的分组信息和分组属性。例如手机分类下的分组有主体、基本信息等分组。以及对应分组的属性。

  1. 销售属性

例如手机的销售属性有颜色、内存、版本等。

  1. SKU信息

保存商品的完整步骤:

  • 商品基本信息:pms_spu_info
  • 商品基本信息的商品介绍图片:pms_spu_info_desc
  • 商品图集存:pms_spu_images
  • 规格参数:pms_product_attr_value
  • SPU对应的所有SKU信息:

    • SKU基本信息:pms_sku_info
    • SKU销售属性:pms_sku_sale_attr_value
    • SKU的图片信息:pms_sku_images
    • SKU的折扣、满减信息、积分。

      • 需要操作gulimall_sms数据库下的表
      • sms_sku_ladder 折扣信息
      • sms_sku_full_reduction 满减信息
      • sms_spu_bounds 积分
  • 保存成功

代码:

  @Autowired
    private SpuInfoService spuInfoService;

    @Autowired
    private SpuInfoDescService spuInfoDescService;

    @Autowired
    private SpuImagesService spuImagesService;

    @Autowired
    private AttrService attrService;

    @Autowired
    private SkuInfoService skuInfoService;

    @Autowired
    private ProductAttrValueService productAttrValueService;

    @Autowired
    private SkuImagesService skuImagesService;

    @Autowired
    private SkuSaleAttrValueService skuSaleAttrValueService;

    @Autowired
    private CouponFeignService couponFeignService;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<SpuInfoEntity> page = this.page(
                new Query<SpuInfoEntity>().getPage(params),
                new QueryWrapper<SpuInfoEntity>()
        );
        return new PageUtils(page);
    }

    @Override
    @Transactional
    public void saveProduct(SpuSaveVo spuSaveVo) {

        log.info("保存商品信息spuSaveVo :{}",spuSaveVo);

//       1.保存spu基本信息-》pms_spu_info
        SpuInfoEntity spuInfoEntity = new SpuInfoEntity();
        BeanUtils.copyProperties(spuSaveVo,spuInfoEntity);
        spuInfoService.save(spuInfoEntity);

//       2.保存spu商品介绍图片-》pms_spu_info_desc
        List<String> decriptImg = spuSaveVo.getDecript();
        if(decriptImg.size() > 0) {
            SpuInfoDescEntity spuInfoDescEntity = new SpuInfoDescEntity();
            spuInfoDescEntity.setSpuId(spuInfoEntity.getId());
            spuInfoDescEntity.setDecript(String.join(",",decriptImg));
            log.info("spu商品介绍图片分割后的:{}",String.join(",",decriptImg));
            spuInfoDescService.save(spuInfoDescEntity);
        }
//       3.保存spu图集-》pms_spu_images
        List<String> images = spuSaveVo.getImages();
        if(images.size() > 0) {
            List<SpuImagesEntity> spuImg = images.stream().map(img -> {
                log.info("商品图集列表:{}", img);
                SpuImagesEntity spuImagesEntity = new SpuImagesEntity();
                spuImagesEntity.setSpuId(spuInfoEntity.getId());
                spuImagesEntity.setImgUrl(img);
                return spuImagesEntity;
            }).collect(Collectors.toList());
            spuImagesService.saveBatch(spuImg);
        }
//       4.保存spu规格参数-》pms_product_attr_value
        List<BaseAttr> baseAttrs = spuSaveVo.getBaseAttrs();
        List<ProductAttrValueEntity> productAttr = baseAttrs.stream().map(baseAttr -> {
            ProductAttrValueEntity productAttrValueEntity = new ProductAttrValueEntity();
            Long attrId = baseAttr.getAttrId();
            AttrEntity attr = attrService.getById(attrId);
            productAttrValueEntity.setSpuId(spuInfoEntity.getId());
            productAttrValueEntity.setAttrId(attrId);
            productAttrValueEntity.setAttrName(attr.getAttrName());
            productAttrValueEntity.setAttrValue(baseAttr.getAttrValues());
            productAttrValueEntity.setQuickShow(baseAttr.getShowDesc());
            return productAttrValueEntity;
        }).collect(Collectors.toList());
        productAttrValueService.saveBatch(productAttr);

//       5.保存SKU信息:
        log.info("保存sku before");
        List<Skus> skus = spuSaveVo.getSkus();


//           - 保存积分
            Bounds bounds = spuSaveVo.getBounds();
            if (bounds != null) {
                SpuBoundsTo spuBoundsTo = new SpuBoundsTo();
                spuBoundsTo.setSpuId(spuInfoEntity.getId());
                spuBoundsTo.setBuyBounds(bounds.getBuyBounds());
                spuBoundsTo.setGrowBounds(bounds.getGrowBounds());
                log.info("保存商品的购买积分:{}", spuBoundsTo);
                couponFeignService.save(spuBoundsTo);
            }


        skus.stream().forEach(sku -> {

            log.info("保存sku!!!!");
//        - 5.1保存sku基本信息 -》pms_sku_info
            SkuInfoEntity skuInfoEntity = new SkuInfoEntity();
            BeanUtils.copyProperties(sku, skuInfoEntity);
            AtomicReference<String> skuDefaultImg = new AtomicReference<>("");
            List<ImagesVo> skuImg = sku.getImages();
            for (ImagesVo i : skuImg) {
                if (i.getDefaultImg() == 1) {
                    log.info("sku id :{} -  默认图片 : {}", skuInfoEntity.getSkuId(), skuDefaultImg.get());
                    skuInfoEntity.setSkuDefaultImg(skuDefaultImg.get());
                }
            }
            skuInfoService.save(skuInfoEntity);

//        - 5.2保存sku图片信息 -》pms_sku_images
            List<SkuImagesEntity> skuImages = skuImg.stream().map(img -> {
                if (img.getDefaultImg() == 1) {
                    skuDefaultImg.set(img.getImgUrl());
                }
                SkuImagesEntity skuImagesEntity = new SkuImagesEntity();
                skuImagesEntity.setSkuId(skuInfoEntity.getSkuId());
                skuImagesEntity.setImgUrl(img.getImgUrl());
                skuImagesEntity.setDefaultImg(img.getDefaultImg());
                return skuImagesEntity;
            }).collect(Collectors.toList());
            skuImagesService.saveBatch(skuImages);

//        - 5.3保存sku的销售属性-》pms_sku_sale_attr_value
            List<BaseAttr> attr = sku.getAttr();
            List<SkuSaleAttrValueEntity> skuSaleAttrValueEntities = attr.stream().map(saleAttr -> {
                SkuSaleAttrValueEntity skuSaleAttrValueEntity = new SkuSaleAttrValueEntity();
                BeanUtils.copyProperties(saleAttr, skuSaleAttrValueEntity);
                skuSaleAttrValueEntity.setSkuId(skuInfoEntity.getSkuId());
                return skuSaleAttrValueEntity;
            }).collect(Collectors.toList());
            skuSaleAttrValueService.saveBatch(skuSaleAttrValueEntities);

//       在优惠卷数据库中添加商品的折扣、满减、积分等操作(远程调用优惠卷服务)
//       - 5.4 保存商品的折扣信息-》sms_sku_ladder
//       - 5.5 保存商品的满减信息-》sms_sku_full_reduction

            int fullCount = sku.getFullCount();
//            如果fullCount或者discount为0,表示满0件打0折,就不添加到数据中。
            if ((sku.getFullCount() > 0) || (sku.getDiscount().compareTo(new BigDecimal(0)) > 0)) {
                SkuLadderTo skuLadderTo = new SkuLadderTo();
                BeanUtils.copyProperties(sku, skuLadderTo);
//             TODO 折扣价在付款时再计算
                skuLadderTo.setPrice(null);
                skuLadderTo.setSkuId(skuInfoEntity.getSkuId());
                log.info("保存商品的折扣信息:{}", skuLadderTo);
                couponFeignService.save(skuLadderTo);
            }

            if (sku.getFullPrice().compareTo(new BigDecimal(0)) > 0 && sku.getReducePrice().compareTo(new BigDecimal(0)) > 0) {
                SkuFullReductionTo skuFullReductionTo = new SkuFullReductionTo();
                BeanUtils.copyProperties(sku, skuFullReductionTo);
                skuFullReductionTo.setSkuId(skuInfoEntity.getSkuId());
                log.info("保存商品的满减信息:{}", skuFullReductionTo);
                couponFeignService.save(skuFullReductionTo);
            }

//          保存sku会员价格:
            List<MemberPrice> memberPrice = sku.getMemberPrice();
            if (memberPrice.size() > 0) {
                memberPrice.stream().peek(item -> {
                    MemberPriceTo memberPriceTo = new MemberPriceTo();
                    memberPriceTo.setSkuId(skuInfoEntity.getSkuId());
                    memberPriceTo.setMemberPrice(item.getPrice());
                    memberPriceTo.setId(item.getId());
                    memberPriceTo.setMemberLevelName(item.getName());
                    log.info("保存商品的会员价格:{}", memberPriceTo);
                    couponFeignService.save(memberPriceTo);
                });
            }

        });
}

spu检索

根据

  • key: '华为',//检索关键字
  • catelogId: 6,//三级分类id
  • brandId: 1,//品牌id
  • status: 0,//商品状态
  • 还有分页参数

仓储服务

仓库表、比如北京有一个仓库、上海有一个仓库:

表字段:id、仓库名字、仓库地址、。

仓库业务:

采购需求(wms_purchase_detail)、采购单(wms_purchase)、多个采购需求合并成一个采购单。

创建一个采购需求,例如需要采购10部小米10手机。

然后需要把这个采购需求分配到采购单中,就是更新采购需求表,添加采购单id。

如果合并采购单没有选择一个采购单就会新建一个采购单,把合并的采购需求关联到这个采购单中。

采购需求合并整单:需要查出采购单状态为新建、没有被别人领取的采购单。(合并采购单是状态必须是0或1)

采购单:新建采购单、可以分配该采购单的采购人员。

领取采购单:

在postman中模拟领取采购单和完成采购单:

领取采购单:/ware/purchase/received

传入领取的采购单id,是一个数组,采购人员可以领取多个采购单。

员工只能领取自己没有领取的采购单,领取采购单后需要改变当前采购单的所有状态。还需要修改采购单里面所有采购详情的状态。

完成采购单:/ware/purchase/done

传入参数:{ id: 123,//采购单id items: [{itemId:1,status:4,reason:""}]//完成/失败的需求详情 }

如果有采购失败的订单项,需要说明采购失败的原因,需要在采购详情表中新增一个字段reason。

Elasticsearch

docker 镜像版本:

  • elasticsearch:7.4.2
  • kibana:7.4.2

在《SpringCloud》博客中写了相关文章。

压力测试

压力测试考察的是当前软硬件环境下系统所能承受的最大负荷,并帮忙找出系统瓶颈所在。

内存泄漏、并发和同步。

性能指标

  • 响应时间

    • 响应时间指的是客户端发起一个请求开始,到服务端接受到返回客户端的整个过程所耗费的时间。
  • HPS

    • 每秒点击数、单位是次/秒
  • TPS

    • 系统每秒处理交易数、单位是笔/秒
  • QPS

    • 系统每秒处理查询数、单位是次/秒。
    • 对于互联网业务中,如果某些业务有且仅有一个请求连接,那么 TPS=QPS=HPS,一

    般情况下用 TPS 来衡量整个业务流程,用 QPS 来衡量接口查询次数,用 HPS 来表

    示对服务器单击请求。

无论 TPS、QPS、HPS,此指标是衡量系统处理能力非常重要的指标,越大越好。

  • 最大响应时间(Max Response Time) 指用户发出请求或者指令到系统做出反应(响应)的最大时间
  • 最少响应时间(Mininum ResponseTime) 指用户发出请求或者指令到系统做出反应(响应)的最少时间
  • 90%响应时间(90% Response Time) 是指所有用户的响应时间进行排序,第 90%的响应时间
  • 从外部看,性能测试主要关注如下三个指标:

    • 吞吐量:每秒钟系统能够处理的请求数、任务数
    • 响应时间:服务处理一个请求或一个任务的耗时
    • 错误率:一批请求中结果出错的请求所占比例

使用Apache JMeter进行压测。

优化:配置Nginx动静分离。

代办:

把一些常量改为枚举。在common里每个服务新建一个类,该类里有很多枚举类。