【摘要】商品服务API接口~
 
前言 商品服务-API-三级分类-查询-递归树形结构数据获取 所有的分类数据从数据库读取,对应数据库pms_category。 首先在数据库准备数据,然后再我们zheli-product中找到和分类有关的代码。 Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @RestController @RequestMapping ("product/category" )public  class  CategoryController   {    @Autowired      private  CategoryService categoryService; 	          @RequestMapping ("/list/tree" )     public  R list (@RequestParam Map<String, Object> params)  {         List<CategoryEntity> entities = categoryService.listWithTree();         return  R.ok().put("data" ,entities);     } 	 	 } 
 
Service
1 2 3 4 5 6 7 public  interface  CategoryService  extends  IService <CategoryEntity >  {    List<CategoryEntity> listWithTree ()  ; } 
 
ServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 @Service ("categoryService" )public  class  CategoryServiceImpl  extends  ServiceImpl <CategoryDao , CategoryEntity > implements  CategoryService   {    @Override      public  PageUtils queryPage (Map<String, Object> params)   {         IPage<CategoryEntity> page = this .page(                 new  Query<CategoryEntity>().getPage(params),                 new  QueryWrapper<CategoryEntity>()         );         return  new  PageUtils(page);     }     @Override      public  List<CategoryEntity> listWithTree ()   {                  List<CategoryEntity> entities = baseMapper.selectList(null );                           List<CategoryEntity> level1Menus = entities.stream().filter(categoryEntity ->                 categoryEntity.getParentCid() == 0          ).map((menu) -> {             menu.setChildren(getChildrens(menu, entities));             return  menu;         }).sorted((menu1, menu2) -> {             return  (menu1.getSort()==null ?0 :menu1.getSort()) - (menu2.getSort()==null ?0 :menu2.getSort());         }).collect(Collectors.toList());         return  level1Menus;     }          private  List<CategoryEntity> getChildrens (CategoryEntity root, List<CategoryEntity> all)   {         List<CategoryEntity> children = all.stream().filter(categoryEntity -> {             return  categoryEntity.getParentCid() == root.getCatId();         }).map(categoryEntity -> {                          categoryEntity.setChildren(getChildrens(categoryEntity, all));             return  categoryEntity;         }).sorted((menu1, menu2) -> {                          return  (menu1.getSort()==null ?0 :menu1.getSort()) - (menu2.getSort()==null ?0 :menu2.getSort());         }).collect(Collectors.toList());         return  children;     } } 
 
测试运行。
商品服务-API-三级分类-配置网关路由和路径重写 启动人人快速开发平台。在菜单管理新建商品系统目录,并在商品系统目录下新建分类维护菜单。 在前端项目中新建category.vue文件。 使用ElementUI的树形控件:https://element.eleme.cn/#/zh-CN/component/tree  之后在category.vue编写前端代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <template>   <el-tree :data="data"  :props="defaultProps"  @node-click="handleNodeClick" ></el-tree>  </ template><script> export  default  {  components: {},   data() {     return  {       data: [],       defaultProps: {         children: "children" ,         label: "label"        }     };   },   methods: {     handleNodeClick(data) {       console .log(data);     },     getMenus() {       this .$http({         url: this .$http.adornUrl("/product/category/list/tree" ),         method: "get"        }).then(data => {           console .log("成功获取到菜单数据..." ,data)       });     }   },   created() {       this .getMenus();   } }; </script>  <style lang='scss' scoped> </ style>
 
页面访问发现没有数据,打开浏览器控制台发现404。发现这里是对http://localhost:8080/renren-fast/product/category/list/tree进行请求获取数据。实际上,需要从http://localhost:8000/product/category/list/tree获取数据。 其实如果只是改变http://localhost:8080/renren-fast/这部分基准路径,那么后面分布式情况下需要给8001``8002``...发送请求就还会出现问题。所以找到前端基准路径设置成给网关发请求。 上面的设置成功之后,发现验证码请求不到。原因是因为验证码功能是renren-fast后台提供的,现在所有请求都会去网关服务找。要让网关可以发现这个验证码服务,就需要把renren-fast后台服务注册到nacos注册中心中。1.引入zheli-common。2.在application.yml中配置服务名字。 3.配置注册中心地址。4.开启服务的注册发现@EnableDiscoveryClient。5.重启renren-fast。 之后配置网关路由和路径重写。 重新登录遇到跨域问题。
商品服务-API-三级分类-网关统一配置跨域  这里选择第二种配置请求允许跨域。 配置完成后,重启网关服务和renren-fast服务。然后在页面重写登录。
商品服务-API-三级分类-查询-树形展示三级分类数据 进入分类维护页面的获取数据请求仍然是404。所以需要在网关服务中添加商品服务的网关路由和路径重写。 配置之后重启访问发现可以成功获取数据了。 修改前端代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <template>   <el-tree :data="menus"  :props="defaultProps"  @node-click="handleNodeClick" ></el-tree>  </ template><script> export  default  {  components: {},   data() {     return  {       menus: [],       defaultProps: {         children: "children" ,         label: "name"        }     };   },   methods: {     handleNodeClick(data) {       console .log(data);     },     getMenus() {       this .$http({         url: this .$http.adornUrl("/product/category/list/tree" ),         method: "get"        }).then(({data} )=> {           console .log("成功获取到菜单数据..." ,data.data)           this .menus = data.data;       });     }   },   created() {       this .getMenus();   } }; </script>  <style lang='scss' scoped> </ style>
 
效果展示:
商品服务-API-三级分类-删除-页面效果 使用 scoped slot。 没有子菜单可以删除,一级、二级菜单可以添加。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <template>   <el-tree :data="menus"  :props="defaultProps"  @node-click="handleNodeClick"  :expand-on-click-node="false"  show-checkbox node-key="catId" >     <span class  ="custom-tree-node"  slot-scope="{ node, data }" >       <span>{{ node.label }}</span>        <span>         <el-button v-if="node.level<=2" type="text" size="mini" @click="() => append(data)">Append</ el-button>        <el-button v-if ="node.childNodes.length==0"  type="text"  size="mini"  @click="() => remove(node, data)" >Delete</el-button>        </ span>    </span>    </ el-tree></template>  <script> export default {   components: {},   data() {     return {       menus: [],       defaultProps: {         children: "children",         label: "name"       }     };   },   methods: {     handleNodeClick(data) {       console.log(data);     },     getMenus() {       this.$http({         url: this.$http.adornUrl("/ product/category/list/tree"),         method: " get "      }).then(({ data }) => {         console .log("成功获取到菜单数据..." , data.data);         this .menus = data.data;       });     },     append(data) {       console .log("append" , data);     },     remove(node, data) {       console .log("remove" , node, data);     }   },   created() {     this .getMenus();   }, }; </script>  <style lang='scss' scoped> </ style>
 
商品服务-API-三级分类-删除-逻辑删除 下载安装Postman https://www.postman.com/downloads/  测试删除接口。
使用mybatis逻辑删除。
商品服务-API-三级分类-删除-逻辑删除 商品服务-API-三级分类-删除-删除效果细化 商品服务-API-三级分类-新增-新增效果完成 商品服务-API-三级分类-修改-基本修改效果完成 商品服务-API-三级分类-修改-拖拽效果 商品服务-API-三级分类-修改-拖拽数据收集 商品服务-API-三级分类-修改-拖拽功能完成 商品服务-API-三级分类-修改-批量拖拽效果 商品服务-API-三级分类-删除-批量删除&小结 商品服务-API-品牌管理-使用逆向工程的前后端代码 商品服务-API-品牌管理-效果优化与快速显示开关 商品服务-API-品牌管理-云存储开通与使用 SpringCloud-Alibaba-OSS 
简介 对象存储服务(Object Storage Service,OSS)是一种海量、安全、低成本、高可靠的云存储服务,适合存放任意类型的文件。容量和处理能力弹性扩展,多钟存储类型供选择,全面优化存储成本。
这个是对象存储的页面。 推荐一个项目创建一个Bucket。上传一张图片就可以外网访问。 普通上传方式 服务端签名后直传
商品服务-API-品牌管理-OSS整合测试 使用代码给阿里云OSS中的zheli-mall上传图片。 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com  创建RAM账号。 创建子AccessKey并设置权限。
首先我们创建一个工程,这个工程专门用来集成第三方应用的工程,我起名叫zhelimall-third-party。 1.在pom文件中加入依赖。
1 2 3 4 5 <dependency > 	<groupId > com.alibaba.cloud</groupId >  	<artifactId > spring-cloud-starter-alicloud-oss</artifactId >  </dependency > 
 
2.配置key,endpoint相关信息
1 2 3 4 5 6 7 8 9 10 11 12 13 spring:   cloud:      nacos:        discovery:          server-addr:  127.0 .0 .1 :8848      alicloud:        access-key:  LTAI4GAGUKKkE4agXXXXXXxX        secret-key:  ggruUCv4r9juD8QkmBXXXXXxxXXXX        oss:          endpoint:  oss-cn-shanghai.aliyuncs.com          bucket:  zheli-mall    application:      name:  zheli-third-party  
 
3.使用OSSClient 进行相关操作
商品服务-API-品牌管理-OSS获取服务端签名 
编写代码:OssController.java 配置网关:http://localhost:30000/oss/policy http://localhost:88/api/thirdparty/oss/policy 
商品服务-API-品牌管理-OSS前后联调测试上传 Element-UI中的
商品服务-API-品牌管理-表单校验&自定义校验器 前端校验方法
商品服务-API-品牌管理-JSR303数据校验 后端校验方法 BrandEntity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 @Data @TableName ("pms_brand" )public  class  BrandEntity  implements  Serializable   {	private  static  final  long  serialVersionUID = 1L ; 	 	@NotNull (message = "修改必须指定品牌id" ) 	@Null (message = "新增不能指定id" ) 	@TableId  	private  Long brandId; 	 	@NotBlank (message = "品牌名必须提交" ) 	private  String name; 	 	@NotBlank  	@URL (message = "logo必须是一个合法的url地址" ) 	private  String logo; 	 	private  String descript; 	 	@NotNull  	 	private  Integer showStatus; 	 	@NotEmpty  	@Pattern (regexp = "/^[a-zA-z]$/" ,message = "检索首字母必须是一个字母" ) 	private  String firstLetter; 	 	@NotNull  	@Min (value = 0 ,message = "排序必须大于等于0" ) 	private  Integer sort; } 
 
BrandController.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @RequestMapping ("/save" )public  R save (@Valid @RequestBody BrandEntity brand, BindingResult result)   {	if  (result.hasErrors()) { 		Map<String, String> map = new  HashMap<>(); 		 		result.getFieldErrors().forEach((item) -> { 			 			String defaultMessage = item.getDefaultMessage(); 			 			String field = item.getField(); 			map.put("field" , defaultMessage); 		}); 		return  R.error(400 , "提交的数据不合法" ).put("data" , map); 	} else  { 		brandService.save(brand); 		return  R.ok(); 	} } 
 
使用PostMan测试。
商品服务-API-品牌管理-统一异常处理 
@ResponseBody + @ControllerAdvice 系统错误码 错误码和错误信息定义类 1.错误码定义规则为5位数 2.前两位表示业务场景,最后三位表示错误码。例如:10001。10:通用。 001:系统未知异常。 3.维护错误码后需要维护错误描述,将他们定义为枚举形式。 错误码列表:     10:通用         001:参数格式校验     11:商品     12:订单     13:购物车     14:物流
 
错误码BizCodeEnum.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public  enum  BizCodeEnum {    UNKNOW_EXCEPTION(10000 ,"系统未知异常" ),     VAILD_EXCEPTION(10001 ,"参数格式校验失败" );     private  int  code;     private  String msg;     BizCodeEnum(int  code, String msg) {         this .code = code;         this .msg = msg;     }     public  int  getCode ()   {         return  code;     }     public  String getMsg ()   {         return  msg;     } } 
 
集中处理所有异常ZhelimallExceptionControllerAdvice.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Slf 4j@RestControllerAdvice (basePackages = "com.aiz.zhelimall.product.controller" )public  class  ZhelimallExceptionControllerAdvice   {    @ResponseBody      @ExceptionHandler (value = MethodArgumentNotValidException.class )      public  R  handleVaildException (MethodArgumentNotValidException  e ) {        log.error("数据校验出现问题:{},异常类型:{}" ,e.getMessage(),e.getClass());         BindingResult bindingResult = e.getBindingResult();         Map<String,String> errorMap = new  HashMap<>();         bindingResult.getFieldErrors().forEach((fieldError -> {             errorMap.put(fieldError.getField(),fieldError.getDefaultMessage());         }));         return  R.error(BizCodeEnum.VAILD_EXCEPTION.getCode(),BizCodeEnum.VAILD_EXCEPTION.getMsg()).put("data" ,errorMap);     }     @ExceptionHandler (value = Throwable.class )      public  R  handleException () {        return  R.error();     } } 
 
商品服务-API-品牌管理-JSR303分组校验 
分组校验(多场景的复杂校验) 1.@NotBlank(message = “品牌名必须提交”,groups = {AddGroup.class,UpdateGroup.class}) 给校验注解标注什么情况进行校验 2.@Validated(AddGroup.class) 3.默认没有指定分组的校验注解@Notblank,在分组校验情况@Validated(AddGroup.class)下不生效,只会在@Validated生效 BrandEntity.java
 
1 2 3 4 5 6 @NotEmpty (groups={AddGroup.class }) @Pattern (regexp  ="^[a-zA-Z]$" ,message = "检索首字母必须是一个字母" ,groups={AddGroup.class ,UpdateGroup .class }) private  String  firstLetter  ;
 
1 2 3 4 5 6 7 8 9 @RequestMapping ("/update" )public  R update (@Validated(UpdateGroup.class) @RequestBody BrandEntity brand)  {	brandService.updateById(brand); 	return  R.ok(); } 
 
商品服务-API-品牌管理-JSR303自定义校验注解 导入javax依赖
1 2 3 4 5 <dependency > 	<groupId > javax.validation</groupId >  	<artifactId > validation-api</artifactId >  	<version > 2.0.1.Final</version >  </dependency > 
 
自定义校验 (一).编写一个自定义的校验注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Documented @Constraint (        validatedBy = {ListValueConstraintValidator.class }//可以指定多个不同的校验器,适配不同类型的校验  ) @Target ( {ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})@Retention (RetentionPolicy.RUNTIME)public  @interface  ListValue {    String message ()  default  " {com.aiz.common.valid.ListValue.message}";      Class<?>[] groups() default {};     Class<? extends Payload>[] payload() default {};     int[] vals() default {}; } 
 
配置文件ValidationMessages.properties
1 com.aiz.common.valid.ListValue.message  = 必须提交指定的值 
 
(二).编写一个自定义的校验器 ConstraintValidator
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public  class  ListValueConstraintValidator  implements  ConstraintValidator <ListValue ,Integer >  {    private  Set<Integer> set = new  HashSet<>();          @Override      public  void  initialize (ListValue constraintAnnotation)   {         int [] vals = constraintAnnotation.vals();         for  (int  val : vals){             set.add(val);         }     }               @Override      public  boolean  isValid (Integer value, ConstraintValidatorContext Context)   {         return  set.contains(value);     } } 
 
(三).关联自定义的校验器和自定义的校验注解
1 2 3 @Constraint(         validatedBy = {ListValueConstraintValidator.class}//可以指定多个不同的校验器,适配不同类型的校验 ) 
 
使用 BrandEntity.java
1 2 3 4 5 6 @NotNull (groups = {AddGroup.class , UpdateStatusGroup .class }) @ListValue (vals  ={0 ,1 },groups = {AddGroup.class , UpdateStatusGroup .class }) private  Integer  showStatus  ;
 
BrandController.java
1 2 3 4 5 6 7 8 9 @RequestMapping ("/update/status" )public  R updateStatus (@Validated(UpdateStatusGroup.class) @RequestBody BrandEntity brand)  {	brandService.updateById(brand); 	 	return  R.ok(); } 
 
检查前端接口调用。
商品服务-概念-SPU&SKU&规格参数&销售属性 SPU和SKU SPU:Standard Product Unit(标准化产品单元) 是商品信息聚合的最小单元,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。 SKU:Stock Keeping Unit(库存量单元) 即库存进出计量的基本单元,可以是以件、盒、托盘等为单位。SKU这对大型连锁超市DC(配送中心)物流管理的一个必要的方法。现在已经被引申为产品统一编号的简称,每种产品均对应有唯一的SKU号。 ex:iphone12是SPU、MI10的SPU     iphone12 64G 黑曜石 是SKU     MI10+64G+黑色 是SKU
基本属性【规格参数】与销售属性 每个分类下的商品共享规格参数,与销售属性。只是有些商品不一定要用这个分类下全部属性。
属性是以三级分类组织起来的 
规格参数中有些是可以提供检索的 
规格参数也是基本属性,他们具有自己的价值 
属性的分组也是以三级分类组织起来的 
属性名确定的,但是值是每一个商品不同来决定的 
 
商品服务-API-属性分组-前端组件抽取&父子组件交互 
父子组件传递数据 子组件给父组件传递数据,事件机制。子组件给父组件发送一个事件,携带上数据。 this.$emit(“事件名称”, 需要携带的数据…);
 
抽取子组件../common/category.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <template > 	<el-tree  :data ="menus"  :props ="defaultProps"  node-key ="catId"  ref ="menuTree"  @node-click ="nodeclick" > </el-tree >  </template > <script > export  default  {  data() {     return  {       menus: [],       expandedKey: [],       defaultProps: {         children: "children" ,         label: "name"        }     };   },   methods: {          nodeclick(data, node, component) {       console .log("子组件category的节点被点击" , data, node, component);              this .$emit("tree-node-click" , data, node, component);     }   } }; </script > <style  scoped > </style > 
 
父组件/product/attrgroup.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <template >   <div >      <category  @tree-node-click ="treenodeclick" > </category >    </div >  </template > <script > import  Category from  "../common/category" ;export  default  {	methods: { 		 		treenodeclick(data, node, component) { 		  console .log("attrgroup感知到category的节点被点击" ,data, node, component); 		  console .log("刚才被点击的菜单id:" ,data.catId); 		  if (node.level == 3) { 			this .catId = data.catId; 			this .getDataList();  		  } 		}, 	} </script > }<style  scoped > </style > 
 
商品服务-API-属性分组-获取分类属性分组 接口文档:https://easydoc.xyz/s/78237135/ZUqEdvA4/hKJTcbfd 
AttrGroupController.java
1 2 3 4 5 6 7 @RequestMapping ("/list/{catelogId}" )public  R list (@RequestParam Map<String, Object> params, 			  @PathVariable("catelogId" )  Long catelogId) {	PageUtils page = attrGroupService.queryPage(params,catelogId); 	return  R.ok().put("page" , page); } 
 
AttrGroupServiceImpl.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Override public  PageUtils queryPage (Map<String, Object> params, Long catelogId)   {	if  (catelogId == 0 ) { 		IPage<AttrGroupEntity> page = this .page( 				new  Query<AttrGroupEntity>().getPage(params), 				new  QueryWrapper<AttrGroupEntity>() 		); 		return  new  PageUtils(page); 	} else  { 		String key = (String) params.get("key" ); 		 		QueryWrapper<AttrGroupEntity> wrapper = new  QueryWrapper<AttrGroupEntity>().eq("catelog_id" , catelogId); 		if  (!StringUtils.isEmpty(key)) { 			wrapper.and((obj) -> { 				obj.eq("attr_group_id" , key).or().like("attr_group_name" , key); 			}); 		} 		IPage<AttrGroupEntity> page = this .page( 				new  Query<AttrGroupEntity>().getPage(params), 				wrapper 		); 		return  new  PageUtils(page); 	} } 
 
商品服务-API-属性分组-分组新增&级联选择器 商品服务-API-属性分组-分组修改&级联选择器回显 Element-UI中Cascader级联选择器的使用
商品服务-API-品牌管理-品牌分类关联与级联更新 MyBatis Plus分页插件使用 https://mp.baomidou.com/guide/page.html  com.aiz.zhelimall.product.config.MyBatisConfig.java
一个品牌会有多个分类,一个分类也会包含多个品牌。分类&品牌是多对多的关系。
查询接口:product/categorybrandrelation/catelog/list?brandId=4 添加接口:product/categorybrandrelation/save 在电商系统中对于大表尽量避免关联查询,所以这边存放brand_name和catelog_name冗余字段。 因为有冗余字段设计,所以在业务代码中修改品牌名称还需要同时修改关联表中的字段。
使用QueryWrapper方式: BrandController.java
1 2 3 4 5 6 7 @RequestMapping ("/update" )public  R update (@Validated(UpdateGroup.class) @RequestBody BrandEntity brand)  {	 	 	brandService.updateDetail(brand); 	return  R.ok(); } 
 
BrandServiceImpl.java
1 2 3 4 5 6 7 8 9 10 11 @Transactional @Override public  void  updateDetail (BrandEntity brand)   {	 	this .updateById(brand); 	if (!StringUtils.isEmpty(brand.getName())){ 		 		categoryBrandRelationService.updateBrand(brand.getBrandId(),brand.getName()); 		 	} } 
 
CategoryBrandRelationServiceImpl.java
1 2 3 4 5 6 public  void  updateBrand (Long brandId, String name)   {	CategoryBrandRelationEntity relationEntity = new  CategoryBrandRelationEntity(); 	relationEntity.setBrandId(brandId); 	relationEntity.setBrandName(name); 	this .update(relationEntity,new  UpdateWrapper<CategoryBrandRelationEntity>().eq("brand_id" ,brandId)); } 
 
使用自定义SQL语句方式: CategoryController.java
1 2 3 4 5 @RequestMapping ("/update" )public  R update (@RequestBody CategoryEntity category)  {	categoryService.updateCascade(category); 	return  R.ok(); } 
 
CategoryServiceImpl.java 这里包含事务控制。
1 2 3 4 5 6 7 8 9 10 @Transactional @Override public  void  updateCascade (CategoryEntity category)   {	this .updateById(category); 	categoryBrandRelationService.updateCategory(category.getCatId(),category.getName()); } 
 
CategoryBrandRelationDao.java
1 void  updateCategory (@Param("catId" )  Long catId, @Param ("name" )  String name) ;
 
CategoryBrandRelationDao.xml
1 2 3 <update  id ="updateCategory" > 	UPDATE `pms_category_brand_relation` SET catelog_name=#{name} WHERE catelog_id=#{catId} </update > 
 
商品服务-API-平台属性-规格参数新增与VO Object划分 
PO(Persistant Object)持久对象 PO就是对应数据库中某个表中的一条记录,多个记录可以用PO的集合。PO中应该不包含任何对数据库的操作。 
DO(Domain Object)领域对象 就是从现实世界中抽象出来的有形或无形的业务实体。 
TO(Transfer Object)数据传输对象 不同应用程序之间传输的对象。 
DTO(Data Transfer Object)数据传输对象 这个概念来源于J2EE的设计模式,原来的目的是为了EJB的分布式应用提供粗颗粒度的数据实体,以减少分布式调用的次数,从而提高分布式调用的性能和降低网络负载,但在这里,泛指用于展示层与服务层之间的数据传输对象。 
VO(Value Object)值对象 通常用于业务层之间的数据传递,和PO一样也是仅仅包含数据而已。但应是抽象出的业务对象,可以和表对应,也可以不。这根据业务的需要。用new关键字创建,由GC回收的。 View Object:视图对象。接收页面传递来的数据,封装对象。将业务处理完的对象封装成页面要用的数据。 
BO(Business Object)业务对象 从业务模型的角度看,见UML元件领域模型中的领域对象。封装业务逻辑的Java对象,通过调用DAO方法,结合PO/VO进行业务操作。business object:业务对象 主要作用是把业务逻辑封装为一个对象。这个对象可以包括一个或多个其他的对象。比如一个简历,有教育经历、工作经历、社会关系等等。我们可以把教育经历对应一个PO,工作经历对应一个PO,社会关系对应一个PO。建立一个对应简历的BO对象处理简历,每个BO包含这些PO。这样处理业务逻辑时,我们就可以针对BO去处理。 
POJO(plain ordinary java object)简单无规则Java对象 传统意义的java对象。就是说在一些Object/Relation Mapping工具中,能够做到维护数据库表记录的persisent object完全是一个符合Java Bean规范的纯Java对象,没有增加别的属性和方法。我的理解就是最基本的Java Bean,只有属性字段以及setter和getter方法。 POJO是DO/DTO/BO/VO的统称。 
DAO(Data Accsee Object)数据访问对象 是一个sun的一个标准j2ee设计模式,这个模式中有个接口就是DAO,它负责持久层的操作。为业务层通过接口。此对象用于访问数据库。通过和PO结合使用,DAO中包含了各种数据库的操作方法。通过它的方法,结合PO对数据库进行相关的操作。夹在业务逻辑与数据库资源终究,配合VO,提供数据库的CRUD操作。 
 
规格参数新增 因为在接受页面传来的属性数据的时候,会把属性的分组信息也带过来。所以新建AttrVo.java用于存放属性信息和属性分组attrGroupId信息。 AttrVo.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public  class  AttrVo   {         private  Long attrId;          private  String attrName;          private  Integer searchType;          private  String icon;          private  String valueSelect;          private  Integer attrType;          private  Long enable;          private  Long catelogId;          private  Integer showDesc;          private  Long attrGroupId; } 
 
页面提交信息时,会涉及到属性信息和属性分组信息两张数据库表。 AttrController.java
1 2 3 4 5 @RequestMapping ("/save" )public  R save (@RequestBody AttrVo attr)  {	attrService.saveAttr(attr); 	return  R.ok(); } 
 
AttrServiceImpl.java在给attrEntity设置属性值的时候,可以(1).一个一个set。(2).使用spring的BeanUtils.copyProperties(),需要注意的是这个浅拷贝,也就是说如果存在子对象且子对象还需要改变就一定不能使用这个方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Transactional @Override public  void  saveAttr (AttrVo attr)   {	AttrEntity attrEntity = new  AttrEntity(); 	BeanUtils.copyProperties(attr,attrEntity); 	 	this .save(attrEntity); 	 	AttrAttrgroupRelationEntity relationEntity = new  AttrAttrgroupRelationEntity(); 	relationEntity.setAttrGroupId(attr.getAttrGroupId()); 	relationEntity.setAttrId(attrEntity.getAttrId()); 	relationDao.insert(relationEntity); } 
 
效果。 属性表 属性分组关系表
商品服务-API-平台属性-规格参数列表  这个页面展示的是属性相关信息,数据来源于【属性表pms_attr】。但是【属性表pms_attr】中并没有【所属分类】和【所属分组】的信息。(1).【属性表pms_attr】中有【分类ID:catelog_id】,就是拿到分类ID再去分类表找到分类名称。(2).需要从【属性&属性分组表pms_attr_attrgroup_relation】中获取【分组ID】【分组名称attr_group_name】。
1 2 3 4 5 select  attr.*,category.`name`  as  category_name,attr_group.attr_group_name as  group_namefrom  pms_attr attr inner  join  pms_category category  on  attr.catelog_id=category.cat_idleft  join  pms_attr_attrgroup_relation relation on  relation.attr_id=attr.attr_idleft  join  pms_attr_group attr_group on  relation.attr_group_id = attr_group.attr_group_id;
 
新建响应数据AttrRespVo.java
1 2 3 4 5 6 7 8 9 10 11 public  class  AttrRespVo  extends  AttrVo  {         private  String catelogName;          private  String groupName; } 
 
AttrController.java
1 2 3 4 5 6 @GetMapping ("/base/list/{catelogId}" )public  R baseAttrList (@RequestParam Map<String, Object> params, 					  @PathVariable("catelogId" )  Long catelogId) {	PageUtils page = attrService.queryBaseAttrPage(params,catelogId); 	return  R.ok().put("page" , page); } 
 
AttrServiceImpl.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 @Override public  PageUtils queryBaseAttrPage (Map<String, Object> params, Long catelogId)   {	QueryWrapper<AttrEntity> queryWrapper = new  QueryWrapper<>(); 	if (catelogId!=0 ){ 		queryWrapper.eq("catelog_id" ,catelogId); 	} 	String key = (String) params.get("key" ); 	if (!StringUtils.isEmpty(key)){ 		 		queryWrapper.and((wrapper)->{ 			wrapper.eq("attr_id" ,key).or().like("attr_name" ,key); 		}); 	} 	IPage<AttrEntity> page = this .page( 			new  Query<AttrEntity>().getPage(params), 			queryWrapper 	); 	PageUtils pageUtils = new  PageUtils(page); 	List<AttrEntity> records = page.getRecords(); 	List<AttrRespVo> attrRespVos = records.stream().map(attrEntity -> { 		AttrRespVo attrRespVo = new  AttrRespVo(); 		BeanUtils.copyProperties(attrEntity, attrRespVo); 		 		AttrAttrgroupRelationEntity attrId = relationDao.selectOne(new  QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id" , attrEntity.getAttrId())); 		if  (attrId != null ) { 			AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrId.getAttrGroupId()); 			attrRespVo.setGroupName(attrGroupEntity.getAttrGroupName()); 		} 		CategoryEntity categoryEntity = categoryDao.selectById(attrEntity.getCatelogId()); 		if  (categoryEntity != null ) { 			attrRespVo.setCatelogName(categoryEntity.getName()); 		} 		return  attrRespVo; 	}).collect(Collectors.toList()); 	pageUtils.setList(attrRespVos); 	return  pageUtils; } 
 
商品服务-API-平台属性-规格修改  在回显数据的时候三级分类路径缺少。所以在AttrRespVo中添加属性catelogPath。
1 2 3 4 public  class  AttrRespVo  extends  AttrVo  {	     private  Long[] catelogPath; } 
 
AttrController.java
1 2 3 4 5 @RequestMapping ("/info/{attrId}" )public  R info (@PathVariable("attrId" )  Long attrId) {	AttrVo attrVo = attrService.getAttrInfo(attrId); 	return  R.ok().put("attr" , attrVo); } 
 
AttrServiceImpl.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Override public  AttrVo getAttrInfo (Long attrId)   {	AttrRespVo attrRespVo = new  AttrRespVo(); 	AttrEntity attrEntity = this .getById(attrId); 	BeanUtils.copyProperties(attrEntity,attrRespVo); 	if (attrEntity.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()){ 		 		AttrAttrgroupRelationEntity attrgroupRelation = relationDao.selectOne(new  QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id" , attrEntity.getAttrId())); 		if (attrgroupRelation!=null ){ 			attrRespVo.setAttrGroupId(attrgroupRelation.getAttrGroupId()); 			AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrgroupRelation.getAttrGroupId()); 			if (attrGroupEntity!=null ){ 				attrRespVo.setGroupName(attrGroupEntity.getAttrGroupName()); 			} 		} 	} 	 	Long catelogId = attrEntity.getCatelogId(); 	Long[] catelogPath = categoryService.findCatelogPath(catelogId); 	attrRespVo.setCatelogPath(catelogPath); 	CategoryEntity categoryEntity = categoryDao.selectById(catelogId); 	if (categoryEntity!=null ){ 		attrRespVo.setCatelogName(categoryEntity.getName()); 	} 	return  attrRespVo; } 
 
商品服务-API-平台属性-销售属性维护 AttrController.java
1 2 3 4 5 6 7 @GetMapping ("/{attrType}/list/{catelogId}" )public  R baseAttrList (@RequestParam Map<String, Object> params, 					  @PathVariable("catelogId" )  Long catelogId,					  @PathVariable ("attrType" )  String type) {	PageUtils page = attrService.queryBaseAttrPage(params,catelogId,type); 	return  R.ok().put("page" , page); } 
 
商品服务-API-平台属性-查询分组关联属性&删除关联 商品服务-API-平台属性-查询分组未关联的属性 商品服务-API-新增商品-调试会员等级相关接口 
商品服务-API-新增商品-调试会员等级相关接口 
商品服务-API-新增商品-获取分类关联的品牌 商品服务-API-新增商品-获取分类下所有分组以及属性 商品服务-API-新增商品-商品新增vo抽取 对于前端返回的json,可以使用bejson.com转化为JavaBean。下载代码拷贝到项目对应位置。
商品服务-API-新增商品-商品新增业务流程分析 SpuInfoServiceImpl.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Transactional @Override public  void  saveSpuInfo (SpuSaveVo vo)   {	 	 	 	 	 	 	 	 	 	 } 
 
商品服务-API-新增商品-保存SPU基本信息 商品服务-API-新增商品-保存SKU基本信息 商品服务-API-新增商品-调用远程服务保存优惠等信息 在远程调用的时候,A服务想要给B服务发送数据,Spring Cloud会把数据封装成json发送过去。所以引申出新的领域模型TO。 还因为这个TO对象A服务和B服务都要使用,所以建议把这个TO对象放在common模块中。
注:Mybatis-Plus会自动插入一个ID到实体类,导致设置的id失效。所以需要在@TableId指定type的类型。可参考链接 
 
1 SET  SESSION  TRANSACTION  ISOLATION  LEVEL  READ  UNCOMMITTED;
 
SpuInfoDescEntity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Data @TableName ("pms_spu_info_desc" )public  class  SpuInfoDescEntity  implements  Serializable   {	private  static  final  long  serialVersionUID = 1L ; 	 	@TableId (type = IdType.INPUT) 	private  Long spuId; 	 	private  String decript; } 
 
介绍一下Mybatis-Plus几种生成ID类型枚举类的区别:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @Getter public  enum  IdType {         AUTO(0 ),          NONE(1 ),          INPUT(2 ),               ID_WORKER(3 ),          UUID(4 ),          ID_WORKER_STR(5 );     private  final  int  key;     IdType(int  key) {         this .key = key;     } } 
 
商品服务-API-新增商品-商品保存其他问题处理 商品服务-API-商品管理-SPU检索 
Spring配置时间格式
 
1 2 3 spring:     jackson:        date-format:  yyyy-MM-dd  HH:mm:ss  
 
商品服务-API-商品管理-SKU检索 结束语