商品服务

分类维护

1.三级分类

  1. 简介:
  2. 使用renren-fast作为后台管理,维护商品的三级分类数据【增删改查】
  1. 1.Entity类加上字段,代表不与数据库表映射字段
  2. @TableField(exist = false)
  3. private List<CategoryEntity> children;//子分类
  4. 2.product/category/list/tree【三级分类】
  5. 递归查询三级分类
  6. @Override
  7. public List<CategoryEntity> listWithTree() {
  8. //查出所有分类
  9. List<CategoryEntity> entities = baseMapper.selectList(null);
  10. //组装成父子树形结构
  11. //1级分类
  12. List<CategoryEntity> level1Menus = entities.stream().
  13. filter(entity -> entity.getParentCid() == 0).
  14. map(entity -> {
  15. entity.setChildren(getChildren(entity, entities));
  16. return entity;
  17. }).
  18. sorted((entity1, entity2) ->
  19. (entity1.getSort() == null ? 0 : entity1.getSort()) -
  20. (entity2.getSort() == null ? 0 : entity2.getSort())).
  21. collect(Collectors.toList());
  22. return level1Menus;
  23. }
  24. //递归查找所有菜单的子菜单
  25. private List<CategoryEntity> getChildren(CategoryEntity root, List<CategoryEntity> all) {
  26. List<CategoryEntity> children = all.stream().filter(entity -> entity.getParentCid().equals(root.getCatId())).
  27. map(entity -> {
  28. entity.setChildren(getChildren(entity, all));
  29. return entity;
  30. }).sorted((entity1, entity2) ->
  31. (entity1.getSort() == null ? 0 : entity1.getSort()) -
  32. (entity2.getSort() == null ? 0 : entity2.getSort())).
  33. collect(Collectors.toList());
  34. return children;
  35. }

1.1.配置网关路由+跨域

  1. 1、启动renren-fast后端,启动前端项目npm run dev
  2. 2、登录localhost:8001,admin admin
  3. 新增后台管理的一级目录:商品系统
  4. 系统管理->菜单管理->新增-> 目录
  5. 3、新增菜单:分类维护,选中商品系统【可看下图】
  6. 4、菜单的urllocalhost:8008/product/category 会转换成product-category作为请求路由【会根据不同的路由加载不同的组件(这是vue的功能)】
  7. 新建category.vue文件放在目录:src->views->modules->product
  8. 输入vue根据模板生成代码
  9. 5、这个是展示属性数据用的
  10. doc:https://element.eleme.cn/#/zh-CN/component/tree
  11. 使用<el-tree>展示三级分类
  12. 6、真实数据应该是调用商品服务的接口来的,所以可以查看项目内部sys->user.vue是怎么调接口的
  13. 问题:要将请求发送到网关,而不是8080【全局查询localhost:8080,修改为网关的地址:80
  14. 1)修改static/config/index.js
  15. window.SITE_CONFIG['baseUrl'] = 'http://localhost:88/api';
  16. 7、让gulimall-fast后端项目注册到网关中
  17. 1)引入依赖
  18. <dependency>
  19. <groupId>com.atguigu.gulimall</groupId>
  20. <artifactId>gulimall-common</artifactId>
  21. <version>0.0.1-SNAPSHOT</version>
  22. </dependency>
  23. 2)添加Nacos配置
  24. spring:
  25. application:
  26. name: renren-fast
  27. cloud:
  28. nacos:
  29. discovery:
  30. server-addr: 127.0.0.1:8848
  31. 3)在Application上加上注解,将该服务注册到注册中心中
  32. @EnableDiscoveryClient
  33. 4)设置网关转发规则:带负载均衡的,路径匹配转发规则
  34. 例子:http://localhost:88/api/captcha.jpg http://localhost:8080/api/captcha.jpg
  35. http://localhost:8080/renren-fast/captcha.jpg
  36. - id: admin_route
  37. uri: lb://renren-fast
  38. predicates:
  39. - Path=/api/**
  40. 但是!正确的是还要带上项目名 server-servlet-context-path的路径,且去掉api **/
  41. 最后版本:
  42. - id: admin_route
  43. uri: lb://renren-fast
  44. predicates:
  45. - Path=/api/**
  46. filters:
  47. - RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}
  48. 5)重启renren-fast 、 gateway
  49. 6)出现问题header contains multiple values 'http://localhost:8001, http://localhost:8001', but only one is allowed.
  50. 配置了多个跨域,找到renren-fast的跨域,给注释掉
  51. 7.在gateway中配置跨域
  52. @Configuration
  53. public class GulimallCorsConfiguration {
  54. /**
  55. * 跨域解决办法之一:
  56. * 过滤器,给所有请求增加请求头信息
  57. * 使得预检请求通过
  58. */
  59. @Bean
  60. public CorsWebFilter corsWebFilter() {
  61. UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
  62. CorsConfiguration corsConfiguration = new CorsConfiguration();
  63. // 1、配置跨域
  64. corsConfiguration.addAllowedHeader("*");
  65. corsConfiguration.addAllowedMethod("*");
  66. corsConfiguration.addAllowedOrigin("*");
  67. corsConfiguration.setAllowCredentials(true);// 否则跨域请求会丢失cookie信息
  68. source.registerCorsConfiguration("/**", corsConfiguration);
  69. return new CorsWebFilter(source);
  70. }
  71. }
  72. 8、商品管理请求404
  73. 原因:所有api请求转发到了renren-fast
  74. Request URL: http://localhost:88/api/product/category/list/tree
  75. Request Method: GET
  76. Status Code: 404 Not Found
  77. Remote Address: [::1]:88
  78. Referrer Policy: no-referrer-when-downgrade
  79. 解决:api/product转到商品服务
  80. gateway 配置路由规则
  81. - id: product_route
  82. uri: lb://gulimall-product
  83. predicates:
  84. - Path=/api/product/**
  85. filters:
  86. - RewritePath=/api/(?<segment>.*),/$\{segment}
  87. 9、配置商品服务配置
  88. 1)新建bootstrap.properties
  89. spring.application.name=gulimall-product
  90. spring.cloud.nacos.config.server-addr=127.0.0.1:8848
  91. spring.cloud.nacos.config.namespace=a152f0a8-3f55-4496-bc9a-c26df96bb2f9
  92. spring.cloud.nacos.config.group=dev
  93. #如果这个dev不放开的话,默认的gulimall-coupon不生效【会加载dev分组下的所有配置】
  94. spring.cloud.nacos.config.extension-configs[0].data-id=datasource.yml
  95. spring.cloud.nacos.config.extension-configs[0].group=dev
  96. spring.cloud.nacos.config.extension-configs[0].refresh=true
  97. spring.cloud.nacos.config.extension-configs[1].data-id=mybatis.yml
  98. spring.cloud.nacos.config.extension-configs[1].group=dev
  99. spring.cloud.nacos.config.extension-configs[1].refresh=true
  100. spring.cloud.nacos.config.extension-configs[2].data-id=spring.yml
  101. spring.cloud.nacos.config.extension-configs[2].group=dev
  102. spring.cloud.nacos.config.extension-configs[2].refresh=true
  103. 2)在Nacos配置中心新建相关配置,服务发现、mybatis-plus、oss
  104. http://127.0.0.1:8848/nacos
  105. 3)开启服务注册发现功能,在Application添加注解
  106. @EnableDiscoveryClient
  107. 4)重启,访问:http://localhost:88/api/product/category/list/tree
  108. 错误:{"msg":"invalid token","code":401},没有令牌。说明请求被renren-fast拦截了
  109. 原因:路由 api/product被api/** 拦截了
  110. 解决:把gateway精确的路由放在前面,防止api/**优先拦截 **/
  111. 10、这个时候已经可以显示数据了,看图

2.拖拽分类

  1. 前端整理好数据,提交更新数据。后台只做updater(List<Entity>)

品牌管理

1.OSS云存储(阿里云)

  1. 步骤:
  2. 1.https://oss.console.aliyun.com/overview
  3. 开通 对象存储OSS
  4. 2.查看文档:
  5. 常用入口=》API文档=》在帮助中心打开【https://help.aliyun.com/document_detail/31947.html?spm=5176.8465980.help.dexternal.4e701450Bu0s0M】
  6. 1)专业术语【https://help.aliyun.com/document_detail/31947.html】
  7. Bucket:一个项目创建一个Bucket,存储空间
  8. Object:对象是 OSS 存储数据的基本单元
  9. Region:地域表示 OSS 的数据中心所在物理位置
  10. Endpoint:访问OSS文件域名URL
  11. AccessKey:访问密钥
  12. 读写权限:私有/公共度/公共读写
  13. 服务端加密:无
  14. 实施日志:不开通
  15. 3.上传方式【采用方式二】
  16. 方式一:
  17. 文件先上传到应用服务器,然后在上传到OSS
  18. 方式二:
  19. 服务端签名后直传【https://www.baidu.com/index.php?tn=monline_3_dg】
  20. 1)用户向应用服务器请求上传Policy
  21. 2)应用服务器返回上传Policy【由服务器控制上传地址等信息】
  22. 3)用户直接上传OSS
  23. 4.获取子用户Accesskeys
  24. 1)鼠标移至账号头像,点击Accesskeys管理,使用子用户Accesskeys【首次使用需要开通RAM访问控制】
  25. 2)新增用户
  26. 登录名称:gulimall-wan
  27. 显示名称:gulimall
  28. 访问方式:编程访问
  29. 3)新增完成复制AccessKeyIDsecret
  30. LTAI5t6jnyvWc34pU9BKRtwr/on5rU9Y06iNakTKMKCJ9KVOqv6OyZC
  31. 4)添加权限:AliyunOSSFullAccess
  32. 5)修改CORS
  33. 打开bucket -> 权限管理 -> 跨域设置 -> 设置 -> 创建规则
  34. 来源:*
  35. 允许Headers:*
  36. 允许MethodsPOST
  37. 使用原生sdk上传的demo
  38. https://help.aliyun.com/document_detail/91868.html?spm=a2c4g.11186623.2.16.1d2f7eaeOSyN4O#concept-ahk-rfz-2fb

使用aliyun封装的SDK实现上传,通过以下网址找到演示demo
https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md

  1. ***OSS放在单独第三方微服务模块
  2. 1.创建第三方微服务:
  3. com.atguigu.gulimall
  4. gulimall-third-party
  5. 谷粒商城-第三方服务
  6. 选择openFeign,SpringWeb
  7. 2.common模块引入版本管理
  8. <dependencyManagement>
  9. <dependencies>
  10. <dependency>
  11. <groupId>com.alibaba.cloud</groupId>
  12. <artifactId>spring-cloud-alibaba-dependencies</artifactId>
  13. <version>2.2.1.RELEASE</version>
  14. <type>pom</type>
  15. <scope>import</scope>
  16. </dependency>
  17. <dependency>
  18. <groupId>org.springframework.cloud</groupId>
  19. <artifactId>spring-cloud-dependencies</artifactId>
  20. <version>${spring-cloud.version}</version>
  21. <type>pom</type>
  22. <scope>import</scope>
  23. </dependency>
  24. </dependencies>
  25. </dependencyManagement>
  26. 3.引入common,并修改springboot springcloud版本号,引入oss,并且引入版本管理
  27. <parent>
  28. <groupId>org.springframework.boot</groupId>
  29. <artifactId>spring-boot-starter-parent</artifactId>
  30. <version>2.3.2.RELEASE</version>
  31. <relativePath/> <!-- lookup parent from repository -->
  32. </parent>
  33. <properties>
  34. <java.version>1.8</java.version>
  35. <spring-cloud.version>Hoxton.SR6</spring-cloud.version>
  36. </properties>
  37. <dependency>
  38. <groupId>com.alibaba.cloud</groupId>
  39. <artifactId>spring-cloud-starter-alicloud-oss</artifactId>
  40. </dependency>
  41. <dependency>
  42. <groupId>com.atguigu.gulimall</groupId>
  43. <artifactId>gulimall-common</artifactId>
  44. <version>0.0.1-SNAPSHOT</version>
  45. <exclusions>
  46. <exclusion>
  47. <groupId>com.baomidou</groupId>
  48. <artifactId>mybatis-plus-boot-starter</artifactId>
  49. </exclusion>
  50. </exclusions>
  51. </dependency>
  52. 版本管理
  53. <dependencyManagement>
  54. <dependencies>
  55. <dependency>
  56. <groupId>com.alibaba.cloud</groupId>
  57. <artifactId>spring-cloud-alibaba-dependencies</artifactId>
  58. <version>2.2.1.RELEASE</version>
  59. <type>pom</type>
  60. <scope>import</scope>
  61. </dependency>
  62. <dependency>
  63. <groupId>org.springframework.cloud</groupId>
  64. <artifactId>spring-cloud-dependencies</artifactId>
  65. <version>${spring-cloud.version}</version>
  66. <type>pom</type>
  67. <scope>import</scope>
  68. </dependency>
  69. </dependencies>
  70. </dependencyManagement>
  71. 3.启动类注解添加,主pom添加模块
  72. @EnableDiscoveryClient
  73. <module>gulimall-third-party</module>
  74. 4.application.yml
  75. server:
  76. port: 30000
  77. spring:
  78. application:
  79. name: gulimall-third-party
  80. cloud:
  81. nacos:
  82. discovery:
  83. server-addr: 127.0.0.1:8848
  84. alicloud:
  85. access-key: LTAI5t6jnyvWc34pU9BKRtwr
  86. secret-key: on5rU9Y06iNakTKMKCJ9KVOqv6OyZC
  87. oss:
  88. endpoint: oss-cn-shanghai.aliyuncs.com
  89. bucket: gulimall-wan
  90. logging:
  91. level:
  92. com.atguigu.gulimall: debug
  93. 5.创建OssController,返回policy凭证【获取对象签名】
  94. @RestController
  95. public class OssController {
  96. @Autowired
  97. OSS ossClient;
  98. @Value("${spring.cloud.alicloud.oss.endpoint}")
  99. private String endpoint;
  100. @Value("${spring.cloud.alicloud.oss.bucket}")
  101. private String bucket;
  102. @Value("${spring.cloud.alicloud.access-key}")
  103. private String accessId;
  104. @RequestMapping("/oss/policy")
  105. public R policy() {
  106. // https://gulimall-wan.oss-cn-shanghai.aliyuncs.com
  107. String host = "https://" + bucket + "." + endpoint;
  108. // callbackUrl为 上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实信息。
  109. // String callbackUrl = "http://88.88.88.88:8888";
  110. // 文件在bucket存储目录,若不存在则会自动创建路径。使用日期作为目录
  111. String dir = new SimpleDateFormat("yyyy-MM-dd").format(new Date()) + "/";
  112. // 创建OSSClient实例。这里是alicloud starter自动配置,可自动注入
  113. //OSS ossClient = new OSSClientBuilder().build(endpoint, accessId, accessKey);
  114. Map<String, String> respMap = null;
  115. try {
  116. long expireTime = 30;
  117. long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
  118. Date expiration = new Date(expireEndTime);//
  119. // PostObject请求最大可支持的文件大小为5 GB,即CONTENT_LENGTH_RANGE为5*1024*1024*1024。
  120. PolicyConditions policyConds = new PolicyConditions();
  121. policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
  122. policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
  123. String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
  124. byte[] binaryData = postPolicy.getBytes("utf-8");
  125. String encodedPolicy = BinaryUtil.toBase64String(binaryData);
  126. String postSignature = ossClient.calculatePostSignature(postPolicy);
  127. respMap = new LinkedHashMap<String, String>();
  128. respMap.put("accessid", accessId);
  129. respMap.put("policy", encodedPolicy);
  130. respMap.put("signature", postSignature);
  131. respMap.put("dir", dir);
  132. respMap.put("host", host);
  133. respMap.put("expire", String.valueOf(expireEndTime / 1000));
  134. // respMap.put("expire", formatISO8601Date(expiration));
  135. // 下面是跨域设置,在网关统一解决跨域
  136. // JSONObject jasonCallback = new JSONObject();
  137. // jasonCallback.put("callbackUrl", callbackUrl);
  138. // jasonCallback.put("callbackBody",
  139. // "filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}");
  140. // jasonCallback.put("callbackBodyType", "application/x-www-form-urlencoded");
  141. // String base64CallbackBody = BinaryUtil.toBase64String(jasonCallback.toString().getBytes());
  142. // respMap.put("callback", base64CallbackBody);
  143. //
  144. // JSONObject ja1 = JSONObject.fromObject(respMap);
  145. // // System.out.println(ja1.toString());
  146. // response.setHeader("Access-Control-Allow-Origin", "*");
  147. // response.setHeader("Access-Control-Allow-Methods", "GET, POST");
  148. // response(request, response, ja1.toString());
  149. } catch (Exception e) {
  150. // Assert.fail(e.getMessage());
  151. System.out.println(e.getMessage());
  152. } finally {
  153. ossClient.shutdown();
  154. }
  155. return R.ok().put("data", respMap);
  156. }
  157. }
  158. 6.配置网关
  159. - id: third_party_route
  160. uri: lb://gulimall-third-party
  161. predicates:
  162. - Path=/api/thirdparty/**
  163. filters:
  164. - RewritePath=/api/thirdparty/(?<segment>.*),/$\{segment}

前端配置

  1. 1.拷贝upload组件放到component中,修改multiUpload.vuesingleUpload.vue文件
  2. 修改成自己的外网Bucket域名
  3. action="http://gulimall-wan.oss-cn-shanghai.aliyuncs.com"
  4. 2.然后在图片上传地址栏修改vue代码,使用单文件上传
  5. brand-add-or-update.vue中导入
  6. 1)import SingleUpload from "@/componets/upload/singleUpload"
  7. 2)在data components:{SingleUpload}
  8. 3)在</template>中就可以使用了 <single-upload>来代替之前的<el-input>标签【自定义节点】
  9. 3.设置跨域,允许bucket跨域请求
  10. oss里面修改管理控制台修改:
  11. https://oss.console.aliyun.com/bucket/oss-cn-shanghai/gulimall-wan/permission/cors

2.后端数据校验JSR303

  1. 提示:springboot2.3.0版本没有集成validation包,需要导入
  2. <!--Valid-->
  3. <dependency>
  4. <groupId>org.springframework.boot</groupId>
  5. <artifactId>spring-boot-starter-validation</artifactId>
  6. <version>2.3.2.RELEASE</version>
  7. </dependency>
  1. 每个controller请求都作处理
  2. 1.controller接口参数上添加@Valid表示开启校验【校验不通过接口请求返回400 bad request
  3. 并追加BindingResult接收异常,包装R.error统一返回
  4. /**
  5. * 保存
  6. */
  7. @RequestMapping("/save")
  8. public R save(@Valid @RequestBody BrandEntity brand, BindingResult result) {
  9. if (result.hasErrors()) {
  10. Map<String, String> map = new HashMap<>();
  11. // 获取校验的错误结果
  12. result.getFieldErrors().forEach((item) -> {
  13. // 获取到错误提示FieldError
  14. String message = item.getDefaultMessage();
  15. // 获取错误的属性名字
  16. String field = item.getField();
  17. map.put(field, message);
  18. });
  19. return R.ok().error(400, "提交的数据不合法").put("data", map);
  20. }else {
  21. brandService.save(brand);
  22. return R.ok();
  23. }
  24. }
  25. 2.Entity类添加注解(javax.validation.constraints
  26. @Data
  27. @TableName("pms_brand")
  28. public class BrandEntity implements Serializable {
  29. private static final long serialVersionUID = 1L;
  30. /**
  31. * 品牌id
  32. */
  33. @TableId
  34. private Long brandId;
  35. /**
  36. * 品牌名
  37. */
  38. @NotBlank(message = "品牌名非空")
  39. private String name;
  40. /**
  41. * 品牌logo地址
  42. */
  43. @NotBlank(message = "logo非空")
  44. @URL(message = "logo不合法")
  45. private String logo;
  46. /**
  47. * 介绍
  48. */
  49. private String descript;
  50. /**
  51. * 显示状态[0-不显示;1-显示]
  52. */
  53. @NotNull(message = "显示状态非空")
  54. @ListValue(vals={0,1}, message = "显示状态非法取值")
  55. private Integer showStatus;
  56. /**
  57. * 首字母
  58. */
  59. @NotBlank(message = "首字母非空")
  60. @Pattern(regexp = "^[a-zA-Z]$]", message = "检索首字母必须是一个字母")
  61. private String firstLetter;
  62. /**
  63. * 排序
  64. */
  65. @NotNull(message = "排序非空")
  66. @Min(value = 0, message = "排序必须大于等于0")
  67. private Integer sort;
  68. }

3.统一校验(统一异常处理)

  1. 统一异常处理【去掉controller中的BindingResult,将异常处理继续外抛统一处理】
  2. 1.添加统一异常处理类
  3. @Slf4j
  4. @RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
  5. public class GulimallExceptionControllerAdvice {
  6. /**
  7. * 统一处理异常,可以使用Exception.class先打印一下异常类型来确定具体异常
  8. */
  9. @ExceptionHandler(value = MethodArgumentNotValidException.class)
  10. public R handleValidException(MethodArgumentNotValidException e) {
  11. log.error("数据校验出现问题{}, 异常类型:{}", e.getMessage(), e.getClass());
  12. BindingResult result = e.getBindingResult();
  13. Map<String, String> errorMap = new HashMap<>();
  14. // 获取校验的错误结果
  15. result.getFieldErrors().forEach((item) -> {
  16. // 获取错误的属性名字 + 获取到错误提示FieldError
  17. errorMap.put(item.getField(), item.getDefaultMessage());
  18. });
  19. return R.ok().error(BizCodeEnume.VALID_EXCEPTION.getCode(), BizCodeEnume.VALID_EXCEPTION.getMsg()).put("data", errorMap);
  20. }
  21. @ExceptionHandler(value = Throwable.class)
  22. public R handleValidException(Throwable throwable) {
  23. log.error("Throwable错误,未处理:" + throwable);
  24. return R.ok().error(BizCodeEnume.UNKNOW_EXCEPTION.getCode(), BizCodeEnume.UNKNOW_EXCEPTION.getMsg());
  25. }
  26. }
  27. 2.增加错误枚举类
  28. public enum BizCodeEnume {
  29. UNKNOW_EXCEPTION(10000, "系统未知异常"),
  30. VALID_EXCEPTION(10001, "参数格式验证失败"),
  31. PRODUCT_UP_EXCEPTION(11000, "商品上架异常"),
  32. SMS_CODE_EXCEPTION(10002,"验证码获取频率太高,请稍后再试"),
  33. TO_MANY_REQUEST(10002,"请求流量过大,请稍后再试"),
  34. USER_EXIST_EXCEPTION(15001,"存在相同的用户"),
  35. PHONE_EXIST_EXCEPTION(15002,"存在相同的手机号"),
  36. NO_STOCK_EXCEPTION(21000,"商品库存不足"),
  37. LOGINACCT_PASSWORD_EXCEPTION(15003,"账号或密码错误");
  38. private int code;
  39. private String msg;
  40. BizCodeEnume(int code, String msg) {
  41. this.code = code;
  42. this.msg = msg;
  43. }
  44. public int getCode() {
  45. return code;
  46. }
  47. public String getMsg() {
  48. return msg;
  49. }
  50. }

4.分组校验(多场复杂校验)

  1. 简介:
  2. 例如新增和修改操作,新增时不带id,修改时带id
  1. 步骤:
  2. 1.新增分组接口
  3. AddGroup
  4. UpdateGroup
  5. UpdateStatusGroup
  6. 2.Entity类的校验注解上添加分组,并指定对应校验接口
  7. @Data
  8. @TableName("pms_brand")
  9. public class BrandEntity implements Serializable {
  10. private static final long serialVersionUID = 1L;
  11. /**
  12. * 品牌id
  13. */
  14. @NotNull(message = "修改必须指定品牌id", groups = {UpdateGroup.class, UpdateStatusGroup.class})
  15. @Null(message = "新增不能指定id", groups = {AddGroup.class})
  16. @TableId
  17. private Long brandId;
  18. /**
  19. * 品牌名
  20. */
  21. @NotBlank(message = "品牌名必须提交", groups = {AddGroup.class, UpdateGroup.class})
  22. private String name;
  23. /**
  24. * 品牌logo地址
  25. */
  26. @NotBlank(groups = {AddGroup.class})
  27. @URL(message = "logo必须是一个合法的url地址", groups = {AddGroup.class, UpdateGroup.class})
  28. private String logo;
  29. /**
  30. * 介绍
  31. */
  32. private String descript;
  33. /**
  34. * 显示状态[0-不显示;1-显示]
  35. */
  36. @NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
  37. @ListValue(vals = {0, 1}, groups = {AddGroup.class, UpdateStatusGroup.class})
  38. private Integer showStatus;
  39. /**
  40. * 检索首字母
  41. */
  42. @NotEmpty(groups = {AddGroup.class})
  43. @Pattern(regexp = "^[a-zA-Z]$", message = "检索首字母必须是一个字母", groups = {AddGroup.class, UpdateGroup.class})
  44. private String firstLetter;
  45. /**
  46. * 排序
  47. */
  48. @NotNull(groups = {AddGroup.class})
  49. @Min(value = 0, message = "排序必须大于等于0", groups = {AddGroup.class, UpdateGroup.class})
  50. private Integer sort;
  51. }
  52. 3.替换controller接口中@Valid注解为@Validated,并且增加分组class
  53. /**
  54. * 保存
  55. */
  56. @RequestMapping("/save")
  57. public R save(@Validated(AddGroup.class) @RequestBody BrandEntity brand){
  58. brandService.save(brand);
  59. return R.ok();
  60. }

5.自定义校验

  1. 例如Integer showStatus的值必须是指定值10
  2. 1.编写一个自定义的校验注解
  3. 2.编写一个自定义的校验器
  4. 3.关联自定义的校验器和自定义的校验注解
  1. 步骤:
  2. 1.编写一个自定义的校验注解
  3. 1common模块添加依赖
  4. <!--自定义注解-->
  5. <dependency>
  6. <groupId>javax.validation</groupId>
  7. <artifactId>validation-api</artifactId>
  8. <version>2.0.1.Final</version>
  9. </dependency>
  10. 2)创建自定义注解(可参照@NotEmpty注解)
  11. @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
  12. @Retention(RUNTIME)
  13. @Documented//
  14. @Constraint(validatedBy = {ListValueConstraintValidator.class})// 使用哪个校验器进行校验的(这里不指定,在初始化的时候指定)
  15. public @interface ListValue {
  16. // 默认会找ValidationMessages.properties
  17. String message() default "{com.atguigu.common.valid.ListValue.message}";
  18. Class<?>[] groups() default {};
  19. Class<? extends Payload>[] payload() default {};
  20. // 可以指定数据只能是vals数组指定的值
  21. int[] vals() default {};
  22. }
  23. 3)创建message配置文件 ValidationMessages.properties
  24. com.atguigu.common.valid.ListValue.message=必须提交指定的值
  25. 2.编写一个自定义的校验器【点击validatedBy进去查看需要一个什么类型的校验器,然后自己作实现类即可】
  26. public class ListValueConstraintValidator implements ConstraintValidator<ListValue, Integer> {
  27. private Set<Integer> set = new HashSet<>();
  28. /**
  29. * 初始化方法
  30. * @param constraintAnnotation
  31. */
  32. @Override
  33. public void initialize(ListValue constraintAnnotation) {
  34. int[] vals = constraintAnnotation.vals();
  35. if (vals != null && vals.length != 0) {
  36. for (int val : vals) {
  37. set.add(val);
  38. }
  39. }
  40. }
  41. /**
  42. * 校验逻辑
  43. * @param value 需要校验的值
  44. * @param context 上下文
  45. * @return
  46. */
  47. @Override
  48. public boolean isValid(Integer value, ConstraintValidatorContext context) {
  49. return set.contains(value);// 如果set length==0,会返回false
  50. }
  51. }
  52. 3.关联自定义的校验器和自定义的校验注解
  53. 自定义注解上关联:@Constraint(validatedBy = {ListValueConstraintValidator.class})
  54. 4.Entity属性上增加自定义校验
  55. /**
  56. * 显示状态[0-不显示;1-显示]
  57. */
  58. @NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
  59. @ListValue(vals = {0, 1}, groups = {AddGroup.class, UpdateStatusGroup.class})
  60. private Integer showStatus;

6.表关系讲解

6.1.SPU、SKU

  1. 简介:
  2. SPU:标准化产品单元 standard product unit,是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。
  3. SKU:库存量单位 Stock Keeping Unit
  4. SPUiphone XSiphone XS maxiphone XRMI8
  5. SKUiphonex 64G 黑曜石、MI8 8+64G+黑色

6.2.属性相关表设计(规格参数、销售属性)

  1. 每个分类下的商品共享规格参数,与销售属性。只是有些商品不一定要用这个分类下全部的属性;
  2. 属性是以三级分类组织起来的【attr关联分类id
  3. 规格参数中有些是可以提供检索的
  4. 规格参数也是基本属性,他们具有自己的分组
  5. 属性的分组也是以三级分类组织起来的
  6. 属性名确定的,但是值是每一个商品不同来决定的【例如手机都有相同的基本属性,只是值不同】
  7. 基本属性表:attr_idsearch_typeattr_type属性类型(规格参数、销售属性),catelog_id(三级分类)【demo:入网型号】
  8. 属性分组表:attr_group_idcatelog_iddemo:主体,关联了 分类表。手机分类下有哪些分组】
  9. 属性+属性分组关联关系表:relation_idattr_idattr_group_id1对多】
  10. 商品表spu_infospu_idspu_namespu_descriptioncatelog_idbrand_id
  11. 商品属性值表product_attr_valueidspu_idattr_idattr_value属性值
  12. 商品库存表sku_infosku_idspu_idprice
  13. 销售属性值表sku_sale_attr_valueidsku_idattr_idattr_value属性值【1对多】

基本属性:组成spu

基础篇 - 图1

销售属性:组成sku

基础篇 - 图2

7.属性分组管理

7.1.父子组件冒泡(事件传递)

7.2.mp查询(and (() or () or))写法

  1. 分页请求参数:
  2. {
  3. page: 1,//当前页码
  4. limit: 10,//每页记录数
  5. sidx: 'id',//排序字段
  6. order: 'asc/desc',//排序方式
  7. key: '华为'//检索关键字
  8. }
  1. 简介:查询分类下所有属性分组
  2. 1.根据catelogId查询【catelogId = 0查询所有】
  3. /**
  4. * 列表
  5. */
  6. @RequestMapping("/list/{catelogId}")
  7. public R list(@RequestParam Map<String, Object> params,
  8. @PathVariable("catelogId") Long catelogId){
  9. PageUtils page = attrGroupService.queryPage(params, catelogId);
  10. return R.ok().put("page", page);
  11. }
  12. 2.多字段模糊匹配 param.key 是否空=》 and (() or () or)写法
  13. @Override
  14. public PageUtils queryPage(Map<String, Object> params, Long catelogId) {
  15. String key = (String) params.get("key");
  16. QueryWrapper<AttrGroupEntity> wrapper = new QueryWrapper<AttrGroupEntity>();
  17. if (!StringUtils.isEmpty(key)) {
  18. // 根据key多字段模糊查询
  19. // and (() or ())
  20. wrapper.and((obj) -> {
  21. obj.eq("attr_group_id", key).or().like("attr_group_name", key);
  22. });
  23. }
  24. if (catelogId == 0) {
  25. // 查询所有
  26. IPage<AttrGroupEntity> page = this.page(
  27. new Query<AttrGroupEntity>().getPage(params),
  28. wrapper);
  29. return new PageUtils(page);
  30. } else {
  31. // 根据catelogId查询
  32. wrapper.eq("catelog_id", catelogId);
  33. IPage<AttrGroupEntity> page = this.page(
  34. new Query<AttrGroupEntity>().getPage(params),
  35. wrapper
  36. );
  37. return new PageUtils(page);
  38. }
  39. }

7.3.新增属性分组(不返回空集合@JsonInclude(value = JsonInclude.Include.NON_EMPTY))

  1. 1.查询三级分类接口 返回的json数据字段不返回children空集合,在分组Entity下添加以下注解
  2. @JsonInclude(value = JsonInclude.Include.NON_EMPTY)
  3. @TableField(exist = false)
  4. private List<CategoryEntity> children;
  5. 2.查询属性分组时,封装三级分类完整路径,用于回显完整分类路径
  6. 1)在属性分组Entity下新增属性路径字段
  7. /**
  8. * 分类路径
  9. */
  10. @TableField(exist = false)
  11. private Long[] catelogPath;
  12. 2)查询属性分组接口
  13. @RequestMapping("/info/{attrGroupId}")
  14. public R info(@PathVariable("attrGroupId") Long attrGroupId){
  15. AttrGroupEntity attrGroup = attrGroupService.getById(attrGroupId);
  16. // 查询三级分类路径
  17. Long[] path = categoryService.findCatelogPath(attrGroup.getCatelogId());
  18. attrGroup.setCatelogPath(path);
  19. return R.ok().put("attrGroup", attrGroup);
  20. }
  21. 3service方法
  22. /**
  23. * 根据catelogId查询所有父分类ID
  24. */
  25. @Override
  26. public Long[] findCatelogPath(Long catelogId) {
  27. List<Long> paths = new ArrayList<>();
  28. // 递归查询父类
  29. paths = findParentPath(catelogId, paths);
  30. // 逆序,父在前
  31. Collections.reverse(paths);
  32. return paths.toArray(new Long[paths.size()]);
  33. }
  34. /**
  35. * 递归查找父路径
  36. */
  37. private List<Long> findParentPath(Long catelogId, List<Long> paths) {
  38. paths.add(catelogId);
  39. CategoryEntity category = this.getById(catelogId);
  40. if (category.getParentCid() != 0) {
  41. findParentPath(category.getParentCid(), paths);
  42. }
  43. return paths;
  44. }

7.4.mp分页查询

  1. 解析:
  2. controller统一使用Map接收参数,其次使用工具类Query封装分页请求Page对象(可以自己在Constant修改请求参数key值)
  3. 分页请求参数:
  4. {
  5. page: 1,//当前页码
  6. limit: 10,//每页记录数
  7. sidx: 'id',//排序字段
  8. order: 'asc/desc',//排序方式
  9. key: '华为'//检索关键字
  10. }
  1. 步骤:
  2. 1.添加配置类,开启事务
  3. @Configuration
  4. @MapperScan("com.atguigu.gulimall.product.dao")
  5. @EnableTransactionManagement
  6. public class MybatisPlusConfig {
  7. @Bean
  8. public PaginationInterceptor paginationInterceptor() {
  9. PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
  10. // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
  11. paginationInterceptor.setOverflow(true);
  12. // 设置最大单页限制数量,默认 500 条,-1 不受限制
  13. paginationInterceptor.setLimit(1000);
  14. // 开启 count 的 join 优化,只针对部分 left join
  15. paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
  16. return paginationInterceptor;
  17. }
  18. }
  19. 2.分页查询品牌
  20. @Override
  21. public PageUtils queryPage(Map<String, Object> params) {
  22. // 获取关键字
  23. String key = (String) params.get("key");
  24. QueryWrapper<BrandEntity> queryWrapper = new QueryWrapper<>();
  25. if (!StringUtils.isEmpty(key)) {
  26. // 关键字非空,拼接关键字查询条件
  27. queryWrapper.eq("brand_id", key).or().like("name", key);
  28. }
  29. IPage<BrandEntity> page = this.page(
  30. new Query<BrandEntity>().getPage(params),
  31. queryWrapper
  32. );
  33. return new PageUtils(page);
  34. }

7.5.品牌关联分类(冗余表同步更新+事务)

  1. 品牌可以关联多个分类,分类可以关联多个品牌,所以需要一个中间表(pms_category_brand_relation
  2. 1.电商表里 大表不作关联表操作,一个一个查询然后设置冗余字段
  3. 2.如果冗余字段修改后要保持数据一致性,例如品牌名/分类名
  4. 解决:修改品牌表/分类表时,要修改所有冗余数据

1.查询品牌关联的所有分类

  1. /product/categorybrandrelation/catelog/list

2.新增品牌与分类的关联关系

  1. /product/categorybrandrelation/save

3.修改品牌

  1. /product/brand/update
  2. 需要同时修改冗余表

4.修改分类

  1. /product/category/update
  2. 需要同时修改冗余表

7.6.基本属性(VO的概念)

新增基本属性

  1. 添加以下属性:
  2. 三级分类
  3. 所属分组(选择了三级分类后会显示当前分类下所有分组)
  4. 可检索
  5. 快速展示(会放入商品介绍)
  6. 重写新增基本属性的方法,因为没有使用的是逆向生成的代码实现,没有在 属性/属性分组关联表中添加关联关系
  7. 所以提交参数需要包含 属性分组ID,该字段Entity中没有,所以新增Vo
  8. 1.POPersistent Object),持久化对象,主要用于持久化层,与数据库表结构一一对应,通过DAO层向上传输数据源对象。
  9. 2.DTO(Data Transfer Object),数据传输对象,ServiceManager向外传输的对象
  10. 3.VOView object),视图对象,接收页面请求数据封装的对象, 封装返回给页面的对象
  11. 4.BObusiness object)业务对象,由多个不同类型的PO组成,例如一个简历对象,由教育经历PO,工作经历PO组成
  12. 5.POJOplain ordinary java object)简单无规则java对象,是 PO/DTO/BO/VO的统称,只有基本的settergetter方法
  13. 6.DAOdata access object)数据访问对象是一个sun的一个标准j2ee 设计模式,这个模式中有个接口就是DAO,它负持久层的操作。为业务层提供接口。此对象用于访问数据库。通常和PO结合使用,DAO中包含了各种数据库的操作方法。通过它的方法,结合PO对数据库进行相关的操作。夹在业务逻辑与数据库资源中间。配合vo,提供数据库的 CRUD 操作.
  14. 步骤:
  15. 1.新增AttrVo,复制entity中所有的属性放到Vo中,并新增attrGroupId
  16. 2.修改save方法中的入参为AttrVo
  17. 3.修改service方法
  18. @Transactional
  19. @Override
  20. public void saveAttr(AttrVo attr) {
  21. AttrEntity attrEntity = new AttrEntity();
  22. BeanUtils.copyProperties(attr, attrEntity);
  23. // 保存基本数据
  24. this.save(attrEntity);
  25. // 保存关联关系
  26. AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
  27. relationEntity.setAttrGroupId(attr.getVttrGroupId());// 分组ID
  28. relationEntity.setAttrId(attrEntity.getAttrId());// 属性ID
  29. relationDao.insert(relationEntity);
  30. }

获取分类下规格参数

  1. /product/attr/base/list/{catelogId}
  2. 注意:返回参数包含catelogName/groupName,所以需要封装返回 VO,查询冗余数据
  3. @Override
  4. public PageUtils queryBaseAttrPage(Map<String, Object> params, Long catelogId) {
  5. QueryWrapper<AttrEntity> queryWrapper = new QueryWrapper<>();
  6. if (catelogId != 0) {
  7. queryWrapper.eq("catelog_id", catelogId);
  8. }
  9. // 关键字
  10. String key = (String) params.get("key");
  11. if (!StringUtils.isEmpty(key)) {
  12. queryWrapper.and(wrapper -> wrapper.eq("attr_id", key).or().like("attr_name", key));
  13. }
  14. // 分页查询
  15. IPage<AttrEntity> page = this.page(new Query<AttrEntity>().getPage(params), queryWrapper);
  16. // 封装回参
  17. return new PageUtils(page);
  18. }

根据ID查询基本属性,用于回显

  1. /product/attr/info/{attrId}
  2. @Override
  3. public AttrRespVo getAttrInfo(Long attrId) {
  4. AttrEntity attrEntity = this.getById(attrId);
  5. AttrRespVo respVo = new AttrRespVo();
  6. BeanUtils.copyProperties(attrEntity, respVo);
  7. // 1.查询设置分组ID
  8. AttrAttrgroupRelationEntity relationEntity = relationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrId));
  9. if (relationEntity != null) {
  10. respVo.setAttrGroupId(relationEntity.getAttrGroupId());
  11. // 查询分组名
  12. AttrGroupEntity groupEntity = attrGroupDao.selectById(relationEntity.getAttrGroupId());
  13. respVo.setGroupName(groupEntity.getAttrGroupName());
  14. }
  15. // 2.查询设置分类path
  16. Long[] catelogPath = categoryService.findCatelogPath(attrEntity.getCatelogId());
  17. respVo.setCatelogPath(catelogPath);
  18. return respVo;
  19. }

修改

  1. 基本属性和分组是11的关系
  2. @Transactional
  3. @Override
  4. public void updateAttr(AttrVo attr) {
  5. AttrEntity attrEntity = new AttrEntity();
  6. BeanUtils.copyProperties(attr, attrEntity);
  7. this.updateById(attrEntity);
  8. // 修改分组关联
  9. AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
  10. relationEntity.setAttrId(attr.getAttrId());
  11. relationEntity.setAttrGroupId(attr.getAttrGroupId());
  12. int count = relationDao.update(relationEntity, new UpdateWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attr.getAttrId()));
  13. if (count == 0) {
  14. // 新增分组关联
  15. relationDao.insert(relationEntity);
  16. }
  17. }

7.7.销售属性

修改查询方法,将属性类型作为参数传入

新增销售属性

  1. 不新增销售属性关联属性分组数据

7.8.属性分组

新建关联(属性分组关联基本属性)

  1. 分组 只能关联当前 分类下 其他分组未关联的基本属性

8.商品维护

8.1.获取所有会员等级

获取所有会员等级

  1. /member/memberlevel/list
  2. 请求参数
  3. {
  4. page: 1,//当前页码
  5. limit: 10,//每页记录数
  6. sidx: 'id',//排序字段
  7. order: 'asc/desc',//排序方式
  8. key: '华为'//检索关键字
  9. }
  10. 响应数据
  11. {
  12. "msg": "success",
  13. "code": 0,
  14. "page": {
  15. "totalCount": 0,
  16. "pageSize": 10,
  17. "totalPage": 0,
  18. "currPage": 1,
  19. "list": [{
  20. "id": 1,
  21. "name": "aaa",
  22. "growthPoint": null,
  23. "defaultStatus": null,
  24. "freeFreightPoint": null,
  25. "commentGrowthPoint": null,
  26. "priviledgeFreeFreight": null,
  27. "priviledgeMemberPrice": null,
  28. "priviledgeBirthday": null,
  29. "note": null
  30. }]
  31. }
  32. }
  33. 添加三个会员等级

8.2.发布商品

  1. 简介:
  2. 1.使用json参数直接生成vo
  3. https://www.json.cn/json/json2java.html
  4. 2.发布商品步骤
  5. // 1.保存spu基本信息 pms_spu_info
  6. // 2.保存spu描述图片 pms_spu_info_desc
  7. // 3.保存spu图片集 pms_spu_images
  8. // 4.保存spu基本参数值 pms_product_attr_value
  9. // 5.保存spu的积分信息(购买产生积分,现阶段绑定spu,可以绑定sku sms_spu_bounds
  10. // 6.保存当前spu对应的所有sku信息
  11. // 6.1)sku的基本信息:pms_sku_info
  12. // 6.2)sku的图片信息:pms_sku_images
  13. // 6.3)sku的销售属性值:pms_sku_sale_attr_value
  14. // 6.4)sku的优惠、满减信息、会员价格:
  15. // sms_sku_ladder\sms_sku_full_reduction\sms_member_price
  1. {
  2. "spuName": "Apple XR",
  3. "spuDescription": "Apple XR",
  4. "catalogId": 225,
  5. "brandId": 12,
  6. "weight": 0.048,
  7. "publishStatus": 0,
  8. "decript": ["https://gulimall-hello.oss-cn-beijing.aliyuncs.com/2019-11-22//66d30b3f-e02f-48b1-8574-e18fdf454a32_f205d9c99a2b4b01.jpg"],
  9. "images": ["https://gulimall-hello.oss-cn-beijing.aliyuncs.com/2019-11-22//dcfcaec3-06d8-459b-8759-dbefc247845e_5b5e74d0978360a1.jpg", "https://gulimall-hello.oss-cn-beijing.aliyuncs.com/2019-11-22//5b15e90a-a161-44ff-8e1c-9e2e09929803_749d8efdff062fb0.jpg"],
  10. "bounds": {
  11. "buyBounds": 500,
  12. "growBounds": 6000
  13. },
  14. "baseAttrs": [{
  15. "attrId": 7,
  16. "attrValues": "aaa;bb",
  17. "showDesc": 1
  18. }, {
  19. "attrId": 8,
  20. "attrValues": "2019",
  21. "showDesc": 0
  22. }],
  23. "skus": [{
  24. "attr": [{
  25. "attrId": 9,
  26. "attrName": "颜色",
  27. "attrValue": "黑色"
  28. }, {
  29. "attrId": 10,
  30. "attrName": "内存",
  31. "attrValue": "6GB"
  32. }],
  33. "skuName": "Apple XR 黑色 6GB",
  34. "price": "1999",
  35. "skuTitle": "Apple XR 黑色 6GB",
  36. "skuSubtitle": "Apple XR 黑色 6GB",
  37. "images": [{
  38. "imgUrl": "https://gulimall-hello.oss-cn-beijing.aliyuncs.com/2019-11-22//dcfcaec3-06d8-459b-8759-dbefc247845e_5b5e74d0978360a1.jpg",
  39. "defaultImg": 1
  40. }, {
  41. "imgUrl": "https://gulimall-hello.oss-cn-beijing.aliyuncs.com/2019-11-22//5b15e90a-a161-44ff-8e1c-9e2e09929803_749d8efdff062fb0.jpg",
  42. "defaultImg": 0
  43. }],
  44. "descar": ["黑色", "6GB"],
  45. "fullCount": 5,
  46. "discount": 0.98,
  47. "countStatus": 1,
  48. "fullPrice": 1000,
  49. "reducePrice": 10,
  50. "priceStatus": 0,
  51. "memberPrice": [{
  52. "id": 1,
  53. "name": "aaa",
  54. "price": 1998.99
  55. }]
  56. }, {
  57. "attr": [{
  58. "attrId": 9,
  59. "attrName": "颜色",
  60. "attrValue": "黑色"
  61. }, {
  62. "attrId": 10,
  63. "attrName": "内存",
  64. "attrValue": "12GB"
  65. }],
  66. "skuName": "Apple XR 黑色 12GB",
  67. "price": "2999",
  68. "skuTitle": "Apple XR 黑色 12GB",
  69. "skuSubtitle": "Apple XR 黑色 6GB",
  70. "images": [{
  71. "imgUrl": "",
  72. "defaultImg": 0
  73. }, {
  74. "imgUrl": "",
  75. "defaultImg": 0
  76. }],
  77. "descar": ["黑色", "12GB"],
  78. "fullCount": 0,
  79. "discount": 0,
  80. "countStatus": 0,
  81. "fullPrice": 0,
  82. "reducePrice": 0,
  83. "priceStatus": 0,
  84. "memberPrice": [{
  85. "id": 1,
  86. "name": "aaa",
  87. "price": 1998.99
  88. }]
  89. }, {
  90. "attr": [{
  91. "attrId": 9,
  92. "attrName": "颜色",
  93. "attrValue": "白色"
  94. }, {
  95. "attrId": 10,
  96. "attrName": "内存",
  97. "attrValue": "6GB"
  98. }],
  99. "skuName": "Apple XR 白色 6GB",
  100. "price": "1998",
  101. "skuTitle": "Apple XR 白色 6GB",
  102. "skuSubtitle": "Apple XR 黑色 6GB",
  103. "images": [{
  104. "imgUrl": "",
  105. "defaultImg": 0
  106. }, {
  107. "imgUrl": "",
  108. "defaultImg": 0
  109. }],
  110. "descar": ["白色", "6GB"],
  111. "fullCount": 0,
  112. "discount": 0,
  113. "countStatus": 0,
  114. "fullPrice": 0,
  115. "reducePrice": 0,
  116. "priceStatus": 0,
  117. "memberPrice": [{
  118. "id": 1,
  119. "name": "aaa",
  120. "price": 1998.99
  121. }]
  122. }, {
  123. "attr": [{
  124. "attrId": 9,
  125. "attrName": "颜色",
  126. "attrValue": "白色"
  127. }, {
  128. "attrId": 10,
  129. "attrName": "内存",
  130. "attrValue": "12GB"
  131. }],
  132. "skuName": "Apple XR 白色 12GB",
  133. "price": "2998",
  134. "skuTitle": "Apple XR 白色 12GB",
  135. "skuSubtitle": "Apple XR 黑色 6GB",
  136. "images": [{
  137. "imgUrl": "",
  138. "defaultImg": 0
  139. }, {
  140. "imgUrl": "",
  141. "defaultImg": 0
  142. }],
  143. "descar": ["白色", "12GB"],
  144. "fullCount": 0,
  145. "discount": 0,
  146. "countStatus": 0,
  147. "fullPrice": 0,
  148. "reducePrice": 0,
  149. "priceStatus": 0,
  150. "memberPrice": [{
  151. "id": 1,
  152. "name": "aaa",
  153. "price": 1998.99
  154. }]
  155. }]
  156. }

BUG汇总
  1. 为了测试方便,使用以下命令将当前mysql连接窗口设置为读未提交(mysql默认是可重复读)
  2. SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
  3. 1.插入pms_spu_info_desc报错,Field 'spu_id' doesn't have a default value
  4. 原因,mybatisPlus标注了@TableId的主键默认是IdType.NONE;且数据库未设置自增,导致主键未空
  5. 修改:entity类上@TableId(type = IdType.INPUT)
  6. 2、过滤sku未选中图片
  7. 3、优惠无意义数据
  8. 1)满0件打0
  9. 2)满0元减0
  10. 3)会员价格为0的数据

8.3.spu管理(后台返回前端时间格式化处理)

  1. 未格式化之前返回数据:
  2. 2020-08-13T01:38:11.000+00:00
  3. 添加配置:(同时修改时区)
  4. spring:
  5. jackson:
  6. date-format: yyyy-MM-dd HH:mm:ss
  7. time-zone: GMT+8

8.4.规格(SPU管理->规格)

  1. 1.spu商品规格更新逻辑
  2. 1)根据spuId删除该商品所有规格
  3. 2)然后根据spuId新增商品所有规格

库存服务(自营)

  1. wms_purchase 采购单
  2. wms_purchase_detail 采购需求
  3. wms_ware_info 仓库
  4. wms_ware_order_task
  5. wms_ware_order_task_detail
  6. wms_ware_sku 各仓库各商品件数
  7. 1.人工/系统库存预警 两种方式创建采购需求(采购需求与仓库+sku绑定)
  8. 2.多个采购需求可以合并为一个采购单
  9. 3.
  10. 1、仓库指定sku商品库存
  11. 2、采购单分配采配人员【分配状态】
  12. 3、采购需求合并【合并到采购单,采购单状态->分配状态】

1.查询商品库存wms_ware_sku

  1. 根据skuId 仓库Id查询库存
  2. /ware/waresku/list
  3. 请求参数
  4. {
  5. page: 1,//当前页码
  6. limit: 10,//每页记录数
  7. sidx: 'id',//排序字段
  8. order: 'asc/desc',//排序方式
  9. wareId: 123,//仓库id
  10. skuId: 123//商品id
  11. }
  12. 响应数据
  13. {
  14. "msg": "success",
  15. "code": 0,
  16. "page": {
  17. "totalCount": 0,
  18. "pageSize": 10,
  19. "totalPage": 0,
  20. "currPage": 1,
  21. "list": [{
  22. "id": 1,
  23. "skuId": 1,
  24. "wareId": 1,
  25. "stock": 1,
  26. "skuName": "dd",
  27. "stockLocked": 1
  28. }]
  29. }
  30. }

2.采购需求/采购单

  1. 多条采购需求需要合并到 一条未领取的采购单中(采购单是一条已存在的新建状态采购单记录)

2.1.新增采购需求wms_purchase_detail

  1. {
  2. "skuId": "商品ID",
  3. "skuNum": "商品数量",
  4. "wareId": "仓库ID"
  5. }

2.2.合并采购单

  1. 1.新增一条采购单
  2. 2.管理员列表-》新增采购人员
  3. 3.分配采购单到新增的采购人员上
  4. 4.合并多条采购需求到一条采购单上(采购单创建、未领取状态)【查询已创建and未领取状态采购单】
  5. 若未选中采购单,则会创建一条新的采购单(采购单ID为空)
  6. 合并:
  7. /ware/purchase/merge
  8. {
  9. purchaseId: 1, //整单id
  10. items:[1,2,3,4] //合并项集合
  11. }

2.3.领取采购单

  1. 描述:
  2. 采购人员领取采购单【可同时领取多条,并且校验是否可以被领取】
  3. 1.切换采购单状态至已领取
  4. 2.切换采购需求状态至正在采购
  1. /ware/purchase/received
  2. 参数:采购单ID
  3. [1,2,3,4]

2.4.完成采购,增加库存

  1. 请求参数:
  2. 采购单ID
  3. List<采购需求>【采购需求ID+采购需求状态】
  4. 业务逻辑
  5. 1.改变采购单状态【所有采购需求完成则采购单状态已完成,任一采购需求未完成则采购单状态异常】
  6. 2.改变采购需求状态【后期还有可能部分成功,例如采购需求数量10,实际采购数量8
  7. 3.采购成功的采购需求,进行入库操作【在指定仓库增加商品库存,如果指定仓库不存在sku库存则新增】
  1. {
  2. "id": 3,
  3. "items": [
  4. {
  5. "itemId": 1,
  6. "status":3,
  7. "reason":""
  8. },
  9. {
  10. "itemId": 2,
  11. "status":4,
  12. "reason":"无货"
  13. }]
  14. }

总结

image.png

feignClient的两种写法

  1. 两种写法:
  2. * 1.过网关,@FeignClient("gulimall-gateway"),然后所有请求前缀加/api/
  3. * 2.不过网关,@FeignClient("gulimall-product"),请求前缀不加/api/,直接访问模块

feign远程调用失败的处理方法

  1. 1.try catch
  2. 2.TODO

image.png