2.商品服务
一、分类维护-通过Java8 Stream API 获取商品三级分类数据
数据库
实体类
@Data
@TableName("pms_category")
public class CategoryEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 分类id
*/
@TableId
private Long catId;
/**
* 分类名称
*/
private String name;
/**
* 父分类id
*/
private Long parentCid;
/**
* 层级
*/
private Integer catLevel;
/**
* 是否显示[0-不显示,1显示]
*/
@TableLogic(value = "1", delval = "0")
private Integer showStatus;
/**
* 排序
*/
private Integer sort;
/**
* 图标地址
*/
private String icon;
/**
* 计量单位
*/
private String productUnit;
/**
* 商品数量
*/
private Integer productCount;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@TableField(exist = false)
private List<CategoryEntity> children;
}
实现
要实现树形显示:
(1).查询出所有分类
(2).组装成父子的树形结构
@Autowired
private CategoryBrandRelationDao categoryBrandRelationDao;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RedissonClient redissonClient;
@Override
public List<CategoryEntity> listWithTree() {
//1、查询出所有分类
List<CategoryEntity> entities = super.baseMapper.selectList(null);
//2、组装成父子的树形结构
//2.1)、找到所有一级分类
List<CategoryEntity> levelMenus = entities.stream()
.filter(e -> e.getParentCid() == 0)
//2.2)、将子级设置到父级中
.peek((menu) -> menu.setChildren(getChildrens(menu, entities)))
//2.3)、将父菜单进行排序
.sorted((menu, menu2) -> {
return (menu.getSort() == null ? 0 : menu.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
})
//2.4)、最后组合
.collect(Collectors.toList());
return levelMenus;
}
//递归查找所有菜单的子菜单
private List<CategoryEntity> getChildrens(CategoryEntity root, List<CategoryEntity> all) {
return all.stream().filter(categoryEntity -> {
return categoryEntity.getParentCid().equals(root.getCatId());
}).peek(categoryEntity -> {
//1、找到子菜单(递归)
categoryEntity.setChildren(getChildrens(categoryEntity, all));
}).sorted((menu, menu2) -> {
//2、菜单的排序
return (menu.getSort() == null ? 0 : menu.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
}).collect(Collectors.toList());
}
二、分类维护-前端路由规范/跨域之三级分类
1.1路由设置
这一节演示了如何通过renren-fast-vue新建组件和导航目录菜单
1.1.1 商品系统新增侧边导航目录
1.1.2 商品系统新增分类维护菜单
之后,在分类维护中维护商品的三级分类。
1.1.3 前端脚手架路由规范
当点击侧边栏目录新增的分类维护时,会跳转到 /product-category
,对应新增菜单设置的路由 product/category
.
页面:对应前端项目 src->views->modules->product->category.vue
页面组件
2.1、API网关配置和跨域配置
这一节会解决一个前端向多个后端服务请求的问题(API网关),此外还有网关服务与前端前后分离的跨域问题。
2.1.1 前端工程配置API网关作为唯一接口
打开 static->config->index.js
配置统一请求地址
// api网关作为接口请求地址,由网关分发到不同服务
window.SITE_CONFIG['baseUrl'] = 'http://localhost:88/api';
2.2 将renren-fast接入网关服务配置
这里是因为配置第一步的网关地址后,renren-fast-vue 本身要请求到 renren-fast 的请求也会转到网关,所以这里要配置网关转发到renren-fast的接口。
2.2.1将renren-fast注册为nacos服务
- 引入 common依赖,
<dependency>
<groupId>com.zsy</groupId>
<artifactId>mall-common</artifactId>
</dependency>
- 配置nacos地址,
spring:
application:
name: renren-fast
cloud:
nacos:
discovery:
server-addr: 192.168.163.131:8848
- 主启动类增加开启注解。
@EnableDiscoveryClient
@SpringBootApplication
public class RenrenApplication {
public static void main(String[] args) {
SpringApplication.run(RenrenApplication.class, args);
}
}
2.2.2 网关增加路由断言转发不同服务
mall-gateway application.yaml
前端 /api/**
请求会被转发到renren-fast服务 /renren-fast/**
前端 /api/product/**
请求会被转发到mall-product服务 /product/**
spring:
application:
name: mall-gateway
cloud:
nacos:
discovery:
server-addr: 192.168.163.131:8848
gateway:
routes:
- id: product_route
uri: lb://mall-product
predicates:
- Path=/api/product/**
filters:
- RewritePath=/api/(?<segment>.*),/$\{segment}
- id: admin_route
uri: lb://renren-fast
predicates:
- Path=/api/**
filters:
- RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}
2.3 网关服务配置跨域
package com.zsy.gateway.config;
/**
* @author: zhangshuaiyin
* @date: 2021/3/7 12:07
*/
@Configuration
public class MallCorsConfiguration {
@Bean
public CorsWebFilter corsWebFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
// 配置跨域
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.setAllowCredentials(true);
source.registerCorsConfiguration("/**", corsConfiguration);
return new CorsWebFilter(source);
}
}
@梅子酒 如果springboot版本高于2.4的话 corsConfiguration.addAllowedOrigin("*");要替换成corsConfiguration.addAllowedOriginPattern("*");
2.3.1 renren-fast跨域配置删除
删除 src/main/java/io/renren/config/CorsConfig.java
中的配置内容
这里会导致请求头添加重复,导致跨域失败
Access to XMLHttpRequest at 'http://localhost:88/api/sys/login' from origin 'http://localhost:8001' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values 'http://localhost:8001, http://localhost:8001', but only one is allowed.
三、云存储-创建子模块作为第三方整合模块
创建子模块mall-third-party 整合阿里云OSS等第三方模块
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.zsy</groupId>
<artifactId>guli-mall</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>mall-third-party</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mall-third-party</name>
<description>第三方服务整合</description>
<dependencies>
<dependency>
<groupId>com.zsy</groupId>
<artifactId>mall-common</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yaml
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.163.131:8848
alicloud:
access-key: LTAI4FwvfjSycd1APnuG9bjj
secret-key: O6xaxyiWfSIitcOkSuK27ju4hXT5Hl
oss:
endpoint: oss-cn-beijing.aliyuncs.com
bucket: gulimall-hello
application:
name: mall-third-party
server:
port: 30000
bootstrap.yaml
spring:
application:
name: mall-third-party
cloud:
nacos:
config:
server-addr: 192.168.163.131:8848
namespace: 90a6e497-bfb6-4784-8dd6-244c08b0708c
file-extension: yaml
extension-configs:
- data-id: oss.yaml
group: DEFAULT_GROUP
refresh: true
服务端签名接口
OssController.java
package com.zsy.third.party.controller;
/**
* @author: zhangshuaiyin
* @date: 2021/3/7 19:29
*/
@RestController
public class OssController {
@Autowired
OSS ossClient;
@Value("${spring.cloud.alicloud.oss.endpoint}")
private String endpoint;
@Value("${spring.cloud.alicloud.oss.bucket}")
private String bucket;
@Value("${spring.cloud.alicloud.access-key}")
private String accessId;
/**
* Oss 获取服务端签名
* @return
*/
@RequestMapping("/oss/policy")
public R policy() {
// https://gulimall-hello.oss-cn-beijing.aliyuncs.com/hahaha.jpg host的格式为 bucketname.endpoint
String host = "https://" + bucket + "." + endpoint;
// callbackUrl为 上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实信息。
// String callbackUrl = "http://88.88.88.88:8888";
String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
// 用户上传文件时指定的前缀。
String dir = format + "/";
Map<String, String> respMap = null;
try {
long expireTime = 30;
long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
Date expiration = new Date(expireEndTime);
PolicyConditions policyConds = new PolicyConditions();
policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
byte[] binaryData = postPolicy.getBytes(StandardCharsets.UTF_8);
String encodedPolicy = BinaryUtil.toBase64String(binaryData);
String postSignature = ossClient.calculatePostSignature(postPolicy);
respMap = new LinkedHashMap<String, String>();
respMap.put("accessid", accessId);
respMap.put("policy", encodedPolicy);
respMap.put("signature", postSignature);
respMap.put("dir", dir);
respMap.put("host", host);
respMap.put("expire", String.valueOf(expireEndTime / 1000));
// respMap.put("expire", formatISO8601Date(expiration));
} catch (Exception e) {
// Assert.fail(e.getMessage());
System.out.println(e.getMessage());
}
return R.ok().put("data", respMap);
}
}
使用网关服务统一接入
mall-gateway 服务 application.yaml
注意配置规则断言要讲更精确的放在前面
spring:
application:
name: mall-gateway
cloud:
gateway:
routes:
- id: third_party_route
uri: lb://mall-third-party
predicates:
- Path=/api/third-party/**
filters:
- RewritePath=/api/third-party/(?<segment>.*),/$\{segment}
四、品牌管理-JSR303数据校验
后端在处理前端传过来的数据时,尽管前端表单已经加了校验逻辑,但是作为严谨考虑,在后端对接口传输的数据做校验也必不可少。
1.开启校验:
实体类上增加校验注解,接口参数前增加@Valid 开启校验
package com.zsy.product.entity;
import javax.validation.constraints.*;
import org.hibernate.validator.constraints.URL;
/**
* 品牌
*
* @author zsy
* @email 594983498@qq.com
* @date 2019-10-01 21:08:49
*/
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌id
*/
@NotNull(message = "修改必须指定品牌id", groups = {UpdateGroup.class})
@Null(message = "新增不能指定id", groups = {AddGroup.class})
@TableId
private Long brandId;
/**
* 品牌名
*/
@NotBlank(message = "品牌名必须提交", groups = {AddGroup.class, UpdateGroup.class})
private String name;
/**
* 品牌logo地址
*/
@NotBlank(groups = {AddGroup.class})
@URL(message = "logo必须是一个合法的url地址", groups = {AddGroup.class, UpdateGroup.class})
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 显示状态[0-不显示;1-显示]
*/
// @Pattern()
@NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
@ListValue(vals = {0, 1}, groups = {AddGroup.class, UpdateStatusGroup.class})
private Integer showStatus;
/**
* 检索首字母
*/
@NotEmpty(groups = {AddGroup.class})
@Pattern(regexp = "^[a-zA-Z]$", message = "检索首字母必须是一个字母", groups = {AddGroup.class, UpdateGroup.class})
private String firstLetter;
/**
* 排序
*/
@NotNull(groups = {AddGroup.class})
@Min(value = 0, message = "排序必须大于等于0", groups = {AddGroup.class, UpdateGroup.class})
private Integer sort;
}
@RequestMapping("/save")
//@RequiresPermissions("product:brand:save")
public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand/*,BindingResult result*/) {
brandService.save(brand);
return R.ok();
}
JSR303
1)、给Bean添加校验注解:javax.validation.constraints,并定义自己的message提示
2)、开启校验功能@Valid
效果:校验错误以后会有默认的响应;
3)、给校验的参数bean后紧跟一个BindingResult,就可以获取到校验的结果
4)、分组校验(多场景的复杂校验)
1)、 @NotBlank(message = "品牌名必须提交",groups = {AddGroup.class,UpdateGroup.class})
给校验注解标注什么情况需要进行校验
2)、@Validated({AddGroup.class})
3)、默认没有指定分组的校验注解@NotBlank,在分组校验情况@Validated({AddGroup.class})下不生效,只会在@Validated生效;
5)、自定义校验
1)、编写一个自定义的校验注解
2)、编写一个自定义的校验器 ConstraintValidator
3)、关联自定义的校验器和自定义的校验注解
2.自定义校验器和校验注解
2.1 自定义校验注解
/**
* 自定义校验注解 声明可以取那些值
* @author ZSY
*/
@Documented
@Constraint(validatedBy = {ListValueConstraintValidator.class})
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
public @interface ListValue {
String message() default "{com.zsy.common.valid.ListValue.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int[] values() default {};
}
2.2 自定义校验器
package com.zsy.common.valid;
/**
* @author ZSY
*/
public class ListValueConstraintValidator implements ConstraintValidator<ListValue, Integer> {
private final Set<Integer> set = new HashSet<>();
/**
* 初始化方法
* 参数:自定义注解的详细信息
*/
@Override
public void initialize(ListValue constraintAnnotation) {
int[] values = constraintAnnotation.values();
for (int val : values) {
set.add(val);
}
}
/**
* 判断是否校验成功
*
* @param value 需要校验的值
* @param context
* @return
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
2.3 创建校验信息提示配置文件
在resource文件下创建:ValidationMessages.properties
com.zsy.common.valid.ListValue.message=必须提交指定的值
五、品牌管理-统一异常处理
统一异常处理类
对于项目中出现的异常,我们通常采用ControllerAdvice的方式进行统一处理,简化代码。
1)、编写异常处理类,使用@ControllerAdvice。
2)、使用@ExceptionHandler标注方法可以处理的异常。
package com.zsy.product.exception;
/**
* 集中处理所有异常
* @author ZSY
*/
@Slf4j
//@ResponseBody
//@ControllerAdvice(basePackages = "com.zsy.product.controller")
@RestControllerAdvice(basePackages = "com.zsy.product.controller")
public class MallExceptionControllerAdvice {
@ExceptionHandler(value= MethodArgumentNotValidException.class)
public R handleValidException(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(BizCodeEnume.VAILD_EXCEPTION.getCode(),BizCodeEnume.VAILD_EXCEPTION.getMsg()).put("data",errorMap);
}
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable){
log.error("错误:",throwable);
return R.error(BizCodeEnume.UNKNOW_EXCEPTION.getCode(),BizCodeEnume.UNKNOW_EXCEPTION.getMsg());
}
}
异常代码枚举
package com.zsy.common.exception;
/***
* 错误码和错误信息定义类
* 1. 错误码定义规则为5为数字
* 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知异常
* 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式
* 错误码列表:
* 10: 通用
* 001:参数格式校验
* 11: 商品
* 12: 订单
* 13: 购物车
* 14: 物流
* @author ZSY
*/
public enum BizCodeEnum {
/**
* 系统未知异常
*/
UNKNOWN_EXCEPTION(10000, "系统未知异常"),
/**
* 参数校验错误
*/
VALID_EXCEPTION(10001, "参数格式校验失败");
private final int code;
private final String msg;
BizCodeEnum(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
六、基础概念-SPU 和 SKU(数据表关系)SPU
1、SPU
Standard Product Unit 标准化产品单元:是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。
SPU: Standard Product Unit
(标准产品单位)
- SPU 是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。
- 通俗点讲,属性值、特性相同的商品就可以称为一个 SPU。
- 例如:
iPhone 12
就是一个 SPU,与商家,与颜色、款式、套餐都无关。
2、SKU
Stock Keeping Unit 库存量单位:即库存进出计量的基本单元,可以是以件、盒等为单位。SKU这是对于大型连锁超市 DC (配送中心)物流管理的一个必要的方法。现在已经被引申为产品统一编号的简称,每种产品均对应有唯一的SKU号。
SKU: Stock Keeping Unit
(库存量单位)
- SKU 即库存进出计量的单位, 可以是以件、盒、托盘等为单位。
- SKU 是物理上不可分割的最小存货单元。在使用时要根据不同业态,不同管理模式来处理。
- 在服装、鞋类商品中使用最多最普遍。
- 例如:
iPhone 12
的颜色(深空灰等),存储容量(64GB 256GB)。
3、举例区分:
iPhoneX 是 SPU、MI8 是 SPU
iPhoneX 64G 黑曜石是 SKU
MI8 8+64G 黑色是 SKU
4、基础属性【规格参数】与销售属性
每个分类下的商品共享规格参数与销售属性。知识有些商品不一定要用这个分类下全部的属性。
- 属性是以三级分类组织起来的;
- 规格参数中有些是可以提供检索的;
- 规格参数也是基本属性,他们具有自己的分组;
- 属性的分组也是以三级分类组织起来的;
- 属性名是确定的,但是值是每一个商品不同决定的;
商品的基础属性是SPU的特性,商品的销售属性是SKU的特性。
七、获取分类属性分组
@Override
public PageUtils queryPage(Map<String, Object> params, Long catelogId) {
//1.获取key
String key = (String) params.get("key");
//2.构造QueryWrapper
QueryWrapper<AttrGroupEntity> wrapper = new QueryWrapper<AttrGroupEntity>();
//3.判断
if (!StringUtils.isEmpty(key)) {
//4. //select * from pms_attr_group where catelog_id=? and (attr_group_id=key or attr_group_name like %key%)
wrapper.and((obj) -> {
obj.eq("attr_group_id",key).or().like("attr_group_name",key);
});
}
//5.如果传过来的三级分类id为0,就查询所有数据
if (catelogId == 0) {
IPage<AttrGroupEntity> page = this.page(
new Query<AttrGroupEntity>().getPage(params),
wrapper
);
return new PageUtils(page);
} else {
wrapper.eq("catelog_id",catelogId);
IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params),wrapper);
return new PageUtils(page);
}
}
八、返回完整路径
//[2,29,20]
@Override
public Long[] findCatelogPath(Long catelogId) {
List<Long> paths = new ArrayList<>();
//递归查询是否还有父节点
List<Long> parentPath = findParentPath(catelogId, paths);
//进行一个逆序排列
Collections.reverse(parentPath);
return (Long[]) parentPath.toArray(new Long[parentPath.size()]);
}
private List<Long> findParentPath(Long catelogId, List<Long> paths) {
//1、收集当前节点id
paths.add(catelogId);
//根据当前分类id查询信息
CategoryEntity byId = this.getById(catelogId);
//如果当前不是父分类
if (byId.getParentCid() != 0) {
findParentPath(byId.getParentCid(), paths);
}
return paths;
}
九、品牌分类关联与级联更新
1、引入分页插件
@Configuration
@EnableTransactionManagement //开启使用
@MapperScan("com.xunqi.gulimall.product.dao")
public class MyBatisConfig {
//引入分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
paginationInterceptor.setOverflow(true);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
paginationInterceptor.setLimit(1000);
return paginationInterceptor;
}
}
2、模糊查询
@Override
public PageUtils queryPage(Map<String, Object> params) {
//1、获取key
String key = (String) params.get("key");
QueryWrapper<BrandEntity> queryWrapper = new QueryWrapper<>();
//如果传过来的数据不是空的,就进行多参数查询
if (!StringUtils.isEmpty(key)) {
queryWrapper.eq("brand_id",key).or().like("name",key);
}
IPage<BrandEntity> page = this.page(
new Query<BrandEntity>().getPage(params),
queryWrapper
);
return new PageUtils(page);
}
3、关联分类
@Override
public void saveDetail(CategoryBrandRelationEntity categoryBrandRelation) {
Long brandId = categoryBrandRelation.getBrandId();
Long catelogId = categoryBrandRelation.getCatelogId();
//1、查询品牌详细信息
BrandEntity brandEntity = brandDao.selectById(brandId);
//2、查询分类详细信息
CategoryEntity categoryEntity = categoryDao.selectById(catelogId);
//将信息保存到categoryBrandRelation中
categoryBrandRelation.setBrandName(brandEntity.getName());
categoryBrandRelation.setCatelogName(categoryEntity.getName());
// 保存到数据库中
this.baseMapper.insert(categoryBrandRelation);
}
4、级联更新
@Transactional(rollbackFor = Exception.class)
@Override
public void updateDetail(BrandEntity brand) {
//保证冗余字段的数据一致
baseMapper.updateById(brand);
if (!StringUtils.isEmpty(brand.getName())) {
//同步更新其他关联表中的数据
categoryBrandRelationService.updateBrand(brand.getBrandId(),brand.getName());
//TODO 更新其他关联
}
}
十、规格测试新增
@Transactional(rollbackFor = Exception.class)
@Override
public void saveAttr(AttrVo attr) {
AttrEntity attrEntity = new AttrEntity();
BeanUtils.copyProperties(attr,attrEntity);
//1、保存基本数据
this.save(attrEntity);
//2、保存关联关系
//判断类型,如果是基本属性就设置分组id
if (attr.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode() && attr.getAttrGroupId() != null) {
AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
relationEntity.setAttrGroupId(attr.getAttrGroupId());
relationEntity.setAttrId(attrEntity.getAttrId());
relationDao.insert(relationEntity);
}
}
十一、查询规格参数列表
@Override
public PageUtils queryBaseAttrPage(Map<String, Object> params, Long catelogId, String attrType) {
QueryWrapper<AttrEntity> queryWrapper = new QueryWrapper<AttrEntity>()
.eq("attr_type","base".equalsIgnoreCase(attrType) ?
ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode() : ProductConstant.AttrEnum.ATTR_TYPE_SALE.getCode());
//根据catelogId查询信息
if (catelogId != 0) {
queryWrapper.eq("catelog_id",catelogId);
}
String key = (String) params.get("key");
if (!StringUtils.isEmpty(key)) {
//attr_id attr_name
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> respVos = records.stream().map((attrEntity) -> {
AttrRespVo attrRespVo = new AttrRespVo();
BeanUtils.copyProperties(attrEntity, attrRespVo);
//设置分类和分组的名字
if ("base".equalsIgnoreCase(attrType)) {
AttrAttrgroupRelationEntity relationEntity =
relationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id",attrEntity.getAttrId()));
if (relationEntity != null && relationEntity.getAttrGroupId() != null) {
AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(relationEntity.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(respVos);
return pageUtils;
}
十二、回显规格
@Override
public AttrRespVo getAttrInfo(Long attrId) {
//查询详细信息
AttrEntity attrEntity = this.getById(attrId);
//查询分组信息
AttrRespVo respVo = new AttrRespVo();
BeanUtils.copyProperties(attrEntity,respVo);
//判断是否是基本类型
if (attrEntity.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()) {
//1、设置分组信息
AttrAttrgroupRelationEntity relationEntity = relationDao.selectOne
(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrId));
if (relationEntity != null) {
respVo.setAttrGroupId(relationEntity.getAttrGroupId());
//获取分组名称
AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(relationEntity.getAttrGroupId());
if (attrGroupEntity != null) {
respVo.setGroupName(attrGroupEntity.getAttrGroupName());
}
}
}
//2、设置分类信息
Long catelogId = attrEntity.getCatelogId();
Long[] catelogPath = categoryService.findCatelogPath(catelogId);
respVo.setCatelogPath(catelogPath);
CategoryEntity categoryEntity = categoryDao.selectById(catelogId);
if (categoryEntity != null) {
respVo.setCatelogName(categoryEntity.getName());
}
return respVo;
}
十三、修改规格
@Transactional(rollbackFor = Exception.class)
@Override
public void updateAttrById(AttrVo attr) {
AttrEntity attrEntity = new AttrEntity();
BeanUtils.copyProperties(attr,attrEntity);
this.updateById(attrEntity);
if (attrEntity.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()) {
//1、修改分组关联
AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
relationEntity.setAttrGroupId(attr.getAttrGroupId());
relationEntity.setAttrId(attr.getAttrId());
Integer count = relationDao.selectCount(new QueryWrapper<AttrAttrgroupRelationEntity>()
.eq("attr_id", attr.getAttrId()));
if (count > 0) {
relationDao.update(relationEntity,
new UpdateWrapper<AttrAttrgroupRelationEntity>().eq("attr_id",attr.getAttrId()));
} else {
relationDao.insert(relationEntity);
}
}
}
十四、查询、删除分组关联属性
1.查询
/**
* 根据分组id找到关联的所有属性
* @param attrgroupId
* @return
*/
@Override
public List<AttrEntity> getRelationAttr(Long attrgroupId) {
List<AttrAttrgroupRelationEntity> entities = relationDao.selectList
(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_group_id", attrgroupId));
List<Long> attrIds = entities.stream().map(AttrAttrgroupRelationEntity::getAttrId).collect(Collectors.toList());
//根据attrIds查找所有的属性信息
//Collection<AttrEntity> attrEntities = this.listByIds(attrIds);
//如果attrIds为空就直接返回一个null值出去
if (attrIds.size() == 0) {
return null;
}
return this.baseMapper.selectBatchIds(attrIds);
}
2.删除
@Override
public void deleteRelation(AttrGroupRelationVo[] vos) {
//relationDao.delete(new QueryWrapper<>().eq("attr_id",1L).eq("attr_group_id",1L));
List<AttrAttrgroupRelationEntity> entities = Arrays.stream(vos).map((item) -> {
AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
BeanUtils.copyProperties(item, relationEntity);
return relationEntity;
}).collect(Collectors.toList());
relationDao.deleteBatchRelation(entities);
}
3.deleteBatchRelation(entities);
/* void deleteBatchRelation(@Param("entities") List<AttrAttrgroupRelationEntity> entities);*/
<delete id="deleteBatchRelation">
DELETE FROM pms_attr_attrgroup_relation WHERE
<foreach collection="entities" item="item" separator=" OR ">
(attr_id = #{item.attrId} AND attr_group_id = #{item.attrGroupId})
</foreach>
</delete>
十五、获取当前分组没有被关联的所有属性
/**
* 获取当前分组没有被关联的所有属性
* @param params
* @param attrgroupId
* @return
*/
@Override
public PageUtils getNoRelationAttr(Map<String, Object> params, Long attrgroupId) {
//1、当前分组只能关联自己所属的分类里面的所有属性
AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrgroupId);
//获取当前分类的id
Long catelogId = attrGroupEntity.getCatelogId();
//2、当前分组只能关联别的分组没有引用的属性
//2.1)、当前分类下的其它分组
List<AttrGroupEntity> groupEntities = attrGroupDao.selectList(new QueryWrapper<AttrGroupEntity>()
.eq("catelog_id", catelogId));
//获取到所有的attrGroupId
List<Long> collect = groupEntities.stream().map((item) -> {
return item.getAttrGroupId();
}).collect(Collectors.toList());
//2.2)、这些分组关联的属性
List<AttrAttrgroupRelationEntity> groupId = relationDao.selectList
(new QueryWrapper<AttrAttrgroupRelationEntity>().in("attr_group_id", collect));
List<Long> attrIds = groupId.stream().map(AttrAttrgroupRelationEntity::getAttrId).collect(Collectors.toList());
//2.3)、从当前分类的所有属性移除这些属性
QueryWrapper<AttrEntity> queryWrapper = new QueryWrapper<AttrEntity>()
.eq("catelog_id", catelogId).eq("attr_type",ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode());
if (attrIds.size() > 0) {
queryWrapper.notIn("attr_id", attrIds);
}
//判断是否有参数进行模糊查询
String key = (String) params.get("key");
if (!StringUtils.isEmpty(key)) {
queryWrapper.and((w) -> {
w.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);
return pageUtils;
}
十六、批量添加属性与分组关联关系
/**
* 批量添加属性与分组关联关系
* @param vos
*/
@Override
public void saveBatch(List<AttrGroupRelationVo> vos) {
List<AttrAttrgroupRelationEntity> collect = vos.stream().map((item) -> {
AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
BeanUtils.copyProperties(item, relationEntity);
return relationEntity;
}).collect(Collectors.toList());
this.saveBatch(collect);
}
十七、获取分类关联的品牌
@Override
public List<BrandEntity> getBrandsByCatId(Long catId) {
List<CategoryBrandRelationEntity> catelogId = relationDao.selectList(new QueryWrapper<CategoryBrandRelationEntity>().eq("catelog_id", catId));
List<BrandEntity> collect = catelogId.stream().map(item -> {
Long brandId = item.getBrandId();
//查询品牌的详情
BrandEntity byId = brandService.getById(brandId);
return byId;
}).collect(Collectors.toList());
return collect;
}
十八、根据分类id查询出所有的分组以及这些组里面的属性
/**
* 根据分类id查询出所有的分组以及这些组里面的属性
* @param catelogId
* @return
*/
@Override
public List<AttrGroupWithAttrsVo> getAttrGroupWithAttrsByCatelogId(Long catelogId) {
//1、查询分组信息
List<AttrGroupEntity> attrGroupEntities = this.list(new QueryWrapper<AttrGroupEntity>().eq("catelog_id", catelogId));
//2、查询所有属性
List<AttrGroupWithAttrsVo> collect = attrGroupEntities.stream().map(group -> {
AttrGroupWithAttrsVo attrGroupWithAttrsVo = new AttrGroupWithAttrsVo();
BeanUtils.copyProperties(group,attrGroupWithAttrsVo);
List<AttrEntity> attrs = attrService.getRelationAttr(attrGroupWithAttrsVo.getAttrGroupId());
attrGroupWithAttrsVo.setAttrs(attrs);
return attrGroupWithAttrsVo;
}).collect(Collectors.toList());
return collect;
}
/**
* 根据分组id找到关联的所有属性
* @param attrgroupId
* @return
*/
@Override
public List<AttrEntity> getRelationAttr(Long attrgroupId) {
List<AttrAttrgroupRelationEntity> entities = relationDao.selectList
(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_group_id", attrgroupId));
List<Long> attrIds = entities.stream().map(AttrAttrgroupRelationEntity::getAttrId).collect(Collectors.toList());
//根据attrIds查找所有的属性信息
//Collection<AttrEntity> attrEntities = this.listByIds(attrIds);
//如果attrIds为空就直接返回一个null值出去
if (attrIds.size() == 0) {
return null;
}
return this.baseMapper.selectBatchIds(attrIds);
}
十九、新增商品
/**
* //TODO:高级部分完善后续
* @param vo 新增商品
*/
@GlobalTransactional(rollbackFor = Exception.class)
//@Transactional(rollbackFor = Exception.class)
@Override
public void savesupInfo(SpuSaveVo vo) {
//1、保存spu基本信息:pms_spu_info
SpuInfoEntity spuInfoEntity = new SpuInfoEntity();
BeanUtils.copyProperties(vo,spuInfoEntity);
spuInfoEntity.setCreateTime(new Date());
spuInfoEntity.setUpdateTime(new Date());
this.saveBaseSpuInfo(spuInfoEntity);
//2、保存spu的描述图片:pms_spu_info_desc
List<String> decript = vo.getDecript();
SpuInfoDescEntity spuInfoDescEntity = new SpuInfoDescEntity();
spuInfoDescEntity.setSpuId(spuInfoEntity.getId());
spuInfoDescEntity.setDecript(String.join(",",decript));
spuInfoDescService.saveSpuInfoDesc(spuInfoDescEntity);
//3、保存spu的图片集:pms_spu_images
List<String> images = vo.getImages();
spuImagesService.saveImages(spuInfoEntity.getId(),images);
//4、保存spu的规格参数:pms_product_attr_value
List<BaseAttrs> baseAttrs = vo.getBaseAttrs();
List<ProductAttrValueEntity> collect = baseAttrs.stream().map(attr -> {
ProductAttrValueEntity valueEntity = new ProductAttrValueEntity();
valueEntity.setAttrId(attr.getAttrId());
//查询attr属性名
AttrEntity byId = attrService.getById(attr.getAttrId());
valueEntity.setAttrName(byId.getAttrName());
valueEntity.setAttrValue(attr.getAttrValues());
valueEntity.setQuickShow(attr.getShowDesc());
valueEntity.setSpuId(spuInfoEntity.getId());
return valueEntity;
}).collect(Collectors.toList());
productAttrValueService.saveProductAttr(collect);
//5、保存spu的积分信息:gulimall_sms--->sms_spu_bounds
Bounds bounds = vo.getBounds();
SpuBoundTo spuBoundTo = new SpuBoundTo();
BeanUtils.copyProperties(bounds,spuBoundTo);
spuBoundTo.setSpuId(spuInfoEntity.getId());
R r = couponFeignService.saveSpuBounds(spuBoundTo);
if (r.getCode() != 0) {
log.error("远程保存spu积分信息失败");
}
//5、保存当前spu对应的所有sku信息:pms_sku_info
//5、1)、sku的基本信息:pms_sku_info
List<Skus> skus = vo.getSkus();
if(skus!=null && skus.size()>0){
skus.forEach(item->{
String defaultImg = "";
for (Images image : item.getImages()) {
if(image.getDefaultImg() == 1){
defaultImg = image.getImgUrl();
}
}
SkuInfoEntity skuInfoEntity = new SkuInfoEntity();
BeanUtils.copyProperties(item,skuInfoEntity);
skuInfoEntity.setBrandId(spuInfoEntity.getBrandId());
skuInfoEntity.setCatalogId(spuInfoEntity.getCatalogId());
skuInfoEntity.setSaleCount(0L);
skuInfoEntity.setSpuId(spuInfoEntity.getId());
skuInfoEntity.setSkuDefaultImg(defaultImg);
skuInfoService.saveSkuInfo(skuInfoEntity);
Long skuId = skuInfoEntity.getSkuId();
List<SkuImagesEntity> imagesEntities = item.getImages().stream().map(img -> {
SkuImagesEntity skuImagesEntity = new SkuImagesEntity();
skuImagesEntity.setSkuId(skuId);
skuImagesEntity.setImgUrl(img.getImgUrl());
skuImagesEntity.setDefaultImg(img.getDefaultImg());
return skuImagesEntity;
}).filter(entity -> {
//返回true就是需要,false就是剔除
return !StringUtils.isEmpty(entity.getImgUrl());
}).collect(Collectors.toList());
//5、2)、sku的图片信息:pms_sku_images
skuImagesService.saveBatch(imagesEntities);
//5、3)、sku的销售属性:pms_sku_sale_attr_value
List<Attr> attr = item.getAttr();
List<SkuSaleAttrValueEntity> skuSaleAttrValueEntities = attr.stream().map(a -> {
SkuSaleAttrValueEntity skuSaleAttrValueEntity = new SkuSaleAttrValueEntity();
BeanUtils.copyProperties(a, skuSaleAttrValueEntity);
skuSaleAttrValueEntity.setSkuId(skuId);
return skuSaleAttrValueEntity;
}).collect(Collectors.toList());
skuSaleAttrValueService.saveBatch(skuSaleAttrValueEntities);
//5、4)、sku的优惠、满减等信息:gulimall_sms--->sms_sku_ladder、sms_sku_full_reduction、sms_member_price
SkuReductionTo skuReductionTo = new SkuReductionTo();
BeanUtils.copyProperties(item,skuReductionTo);
skuReductionTo.setSkuId(skuId);
if (skuReductionTo.getFullCount() > 0 || skuReductionTo.getFullPrice().compareTo(BigDecimal.ZERO) > 0) {
R r1 = couponFeignService.saveSkuReduction(skuReductionTo);
if (r1.getCode() != 0) {
log.error("远程保存sku积分信息失败");
}
}
});
}
}
/*保存图片信息*/
@Override
public void saveImages(Long id, List<String> images) {
if (images == null || images.size() == 0) {
} else {
List<SpuImagesEntity> collect = images.stream().map(img -> {
SpuImagesEntity spuImagesEntity = new SpuImagesEntity();
spuImagesEntity.setSpuId(id);
spuImagesEntity.setImgUrl(img);
return spuImagesEntity;
}).collect(Collectors.toList());
this.saveBatch(collect);
}
}
二十、SPU、SKU检索
@Override
public PageUtils queryPageByCondtion(Map<String, Object> params) {
QueryWrapper<SpuInfoEntity> queryWrapper = new QueryWrapper<>();
String key = (String) params.get("key");
if (!StringUtils.isEmpty(key)) {
queryWrapper.and((wrapper) -> wrapper.eq("id",key).or().like("spu_name",key));
}
String status = (String) params.get("status");
if (!StringUtils.isEmpty(status)) {
queryWrapper.eq("publish_status",status);
}
String brandId = (String) params.get("brandId");
if (!StringUtils.isEmpty(brandId) && !"0".equalsIgnoreCase(brandId)) {
queryWrapper.eq("brand_id",brandId);
}
String catelogId = (String) params.get("catelogId");
if (!StringUtils.isEmpty(catelogId) && !"0".equalsIgnoreCase(catelogId)) {
queryWrapper.eq("catalog_id",catelogId);
}
IPage<SpuInfoEntity> page = this.page(new Query<SpuInfoEntity>().getPage(params), queryWrapper);
return new PageUtils(page);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?