refactor: 商品分类重构

This commit is contained in:
hxr 2024-04-22 08:20:58 +08:00
parent a1e448411d
commit 7dd9b5fdaa
16 changed files with 297 additions and 171 deletions

View File

@ -1,27 +1,37 @@
package com.youlai.mall.pms.model.vo;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@Data
@Schema(description = "商品分类视图对象")
@Getter
@Setter
public class CategoryVO {
@Schema(description = "分类ID", example = "1")
private Long id;
@Schema(description = "分类名称", example = "电子产品")
private String name;
@Schema(description = "父分类ID", example = "0")
private Long parentId;
@Schema(description = "分类层级", example = "1")
private Integer level;
@Schema(description = "分类图标URL", example = "http://example.com/icon.jpg")
private String iconUrl;
@Schema(description = "分类排序", example = "100")
private Integer sort;
@Schema(description = "分类是否可见", allowableValues = {"0", "1"}, example = "1")
private Integer visible;
private List<CategoryVO> children = new ArrayList<>();
@Schema(description = "子分类列表")
private List<CategoryVO> children;
}

View File

@ -1,16 +1,16 @@
package com.youlai.mall.pms.controller.admin;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.youlai.common.result.Result;
import com.youlai.common.web.model.Option;
import com.youlai.mall.pms.model.entity.Category;
import com.youlai.mall.pms.model.form.CategoryForm;
import com.youlai.mall.pms.model.vo.CategoryVO;
import com.youlai.mall.pms.service.CategoryService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@ -18,8 +18,8 @@ import java.util.List;
/**
* Admin-商品分类控制器
*
* @author haoxr
* @since 2022/01/01
* @author Ray Hao
* @since 2024/4/20
*/
@Tag(name = "Admin-商品分类")
@RestController
@ -29,56 +29,45 @@ public class CategoryController {
private final CategoryService categoryService;
@Operation(summary = "商品分类列表")
@Operation(summary = "获取商品分类列表")
@GetMapping
public Result<List<CategoryVO>> getCategoryList() {
List<CategoryVO> list = categoryService.getCategoryList(null);
public Result<List<CategoryVO>> listCategories() {
List<CategoryVO> list = categoryService.listCategories(null);
return Result.success(list);
}
@Operation(summary = "商品分类级联列表")
@Operation(summary = "获取商品分类下拉列表")
@GetMapping("/options")
public Result getCategoryOptions() {
List<Option> list = categoryService.getCategoryOptions();
public Result<List<Option>> listCategoryOptions() {
List<Option> list = categoryService.listCategoryOptions();
return Result.success(list);
}
@Operation(summary = "商品分类详情")
@GetMapping("/{id}")
public Result detail(
@Operation(summary = "获取商品分类表单数据")
@GetMapping("/{id}/form")
public Result getCategoryForm(
@Parameter(name = "商品分类ID") @PathVariable Long id
) {
Category category = categoryService.getById(id);
return Result.success(category);
}
@Operation(summary = "新增商品分类")
@Operation(summary = "保存商品分类")
@PostMapping
public Result addCategory(@RequestBody Category category) {
Long id = categoryService.saveCategory(category);
return Result.success(id);
}
@Operation(summary = "修改商品分类")
@PutMapping(value = "/{id}")
public Result update(
@Parameter(name = "商品分类ID") @PathVariable Long id,
@RequestBody Category category
public Result saveCategory(
@Validated @RequestBody CategoryForm formData
) {
category.setId(id);
id = categoryService.saveCategory(category);
Long id = categoryService.saveCategory(formData);
return Result.success(id);
}
@Operation(summary = "选择性修改商品分类")
@PatchMapping(value = "/{id}")
@CacheEvict(value = "pms", key = "'categoryList'")
public Result patch(@PathVariable Long id, @RequestBody Category category) {
LambdaUpdateWrapper<Category> updateWrapper = new LambdaUpdateWrapper<Category>()
.eq(Category::getId, id);
updateWrapper.set(category.getVisible() != null, Category::getVisible, category.getVisible());
boolean result = categoryService.update(updateWrapper);
@Operation(summary = "删除商品分类")
@DeleteMapping("/{id}")
public Result deleteCategory(
@Parameter(name = "商品分类ID") @PathVariable Long id
) {
boolean result = categoryService.deleteCategory(id);
return Result.judge(result);
}
}

View File

@ -30,7 +30,7 @@ public class AppCategoryController {
@Operation(summary = "分类列表")
@GetMapping
public Result list(@Parameter(name = "上级分类ID") Long parentId) {
List<CategoryVO> list = categoryService.getCategoryList(parentId);
List<CategoryVO> list = categoryService.listCategories(parentId);
return Result.success(list);
}
}

View File

@ -1,7 +1,6 @@
package com.youlai.mall.pms.converter;
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.Mapper;
import org.mapstruct.*;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youlai.mall.pms.model.dto.AttributeDTO;
@ -13,6 +12,9 @@ import com.youlai.mall.pms.model.bo.AttributeBO;
@Mapper(componentModel = "spring")
public interface AttributeConverter{
@Mappings({
@Mapping(target = "inputTypeLabel", expression = "java(com.youlai.common.base.IBaseEnum.getLabelByValue(bo.getInputType(), com.youlai.mall.pms.enums.AttributeInputTypeEnum.class))")
})
AttributePageVO bo2PageVo(AttributeBO bo);
Page<AttributePageVO> bo2PageVo(Page<AttributeBO> bo);

View File

@ -0,0 +1,14 @@
package com.youlai.mall.pms.converter;
import com.youlai.mall.pms.model.entity.Category;
import com.youlai.mall.pms.model.form.CategoryForm;
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.Mapper;
@Mapper(componentModel = "spring")
public interface CategoryConverter {
CategoryForm entity2Form(Category entity);
@InheritInverseConfiguration(name = "entity2Form")
Category form2Entity(CategoryForm formData);
}

View File

@ -0,0 +1,29 @@
package com.youlai.mall.pms.enums;
import com.youlai.common.base.IBaseEnum;
import lombok.Getter;
/**
* 属性录入方式枚举
*
* @author Ray Hao
* @since 2024/4/19
*/
public enum AttributeInputTypeEnum implements IBaseEnum<Integer> {
MANUAL(1, "手动录入"),
SELECT(2, "可选列表");
AttributeInputTypeEnum(Integer value, String label) {
this.value = value;
this.label = label;
}
@Getter
// @EnumValue // Mybatis-Plus 提供注解表示插入数据库时插入该值
private Integer value;
@Getter
// @JsonValue // 表示对枚举序列化时返回此字段
private String label;
}

View File

@ -4,7 +4,19 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.youlai.mall.pms.model.entity.Category;
import org.apache.ibatis.annotations.Mapper;
/**
* 商品分类 Mapper 接口
*
* @author Ray Hao
* @since 2024/04/20
*/
@Mapper
public interface CategoryMapper extends BaseMapper<Category> {
/**
* 删除分类
* @param categoryId 分类ID
* @return 影响行数
*/
int deleteCategoryById(Long categoryId);
}

View File

@ -3,29 +3,64 @@ package com.youlai.mall.pms.model.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.youlai.common.base.BaseEntity;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
@Data
/**
* 商品分类实体类
*
* @author Ray Hao
* @since 2024/04/20
*/
@TableName("pms_category")
@Getter
@Setter
public class Category extends BaseEntity {
@TableId(type= IdType.AUTO)
private Long id;
/**
* 分类名称
*/
private String name;
/**
* 父分类ID
*/
private Long parentId;
/**
* 分类图标URL
*/
private String iconUrl;
@JsonInclude(value = JsonInclude.Include.NON_NULL)
/**
* 分类层级
*/
private Integer level;
@JsonInclude(value = JsonInclude.Include.NON_NULL)
/**
* 分类排序
*/
private Integer sort;
@JsonInclude(value = JsonInclude.Include.NON_NULL)
/**
* 分类是否可见
*/
private Integer visible;
/**
* 创建人ID
*/
private Long createBy;
/**
* 更新人ID
*/
private Long updateBy;
/**
* 逻辑删除标识(0-未删除1-已删除)
*/
private Integer isDeleted;
}

View File

@ -0,0 +1,40 @@
package com.youlai.mall.pms.model.form;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 商品分类表单对象
*
* @author Ray Hao
* @since 2024/04/20
*/
@Schema(description = "商品分类表单对象")
@Data
public class CategoryForm {
@Schema(description = "分类ID", example = "1")
private Long id;
@Schema(description = "分类名称", example = "电子产品")
@NotBlank(message = "分类名称不能为空")
private String name;
@Schema(description = "父分类ID", example = "0")
@NotNull(message = "父分类ID不能为空")
private Long parentId;
@Schema(description = "分类图标URL", example = "http://example.com/icon.jpg")
private String iconUrl;
@Schema(description = "分类层级", example = "1")
private Integer level;
@Schema(description = "分类排序", example = "100")
private Integer sort;
@Schema(description = "分类是否可见", allowableValues = {"0", "1"}, example = "1")
private Integer visible;
}

View File

@ -17,4 +17,7 @@ public class AttributePageQuery extends BasePageQuery {
@Schema(description="关键字")
private String keywords;
@Schema(description="属性分组ID")
private Long groupId;
}

View File

@ -1,14 +1,13 @@
package com.youlai.mall.pms.model.vo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 属性组 分页VO
*
@ -22,31 +21,20 @@ public class AttributeGroupPageVO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "属性组主键")
@Schema(description = "属性组ID")
private Long id;
@Schema(description = "属性组名称")
@Schema(description = "属性组名称")
private String name;
@Schema(description = "排序")
@Schema(description = "排序")
private Short sort;
@Schema(description = "备注")
@Schema(description = "备注")
private String remark;
@Schema(description = "创建时间")
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
private LocalDateTime createTime;
@Schema(description = "更新时间")
private LocalDateTime updateTime;
@Schema(description = "逻辑删除标识(0-未删除1-已删除)")
private Integer isDeleted;;
}

View File

@ -1,14 +1,13 @@
package com.youlai.mall.pms.model.vo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 属性 分页VO
*
@ -22,35 +21,22 @@ public class AttributePageVO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "属性主键")
@Schema(description = "属性主键")
private Long id;
@Schema(description = "属性组主键")
@Schema(description = "属性组主键")
private Long attributeGroupId;
@Schema(description = "属性名称")
@Schema(description = "属性名称")
private String name;
@Schema(description = "输入录入方式1-手动输入2-从列表选择")
private Integer inputType;
@Schema(description = "逗号分割的可选值列表仅当input_type是2使用")
@Schema(description = "输入录入方式标签")
private String inputTypeLabel;
@Schema(description = "逗号分割的可选值列表仅当input_type是2使用")
private String options;
@Schema(description = "创建时间")
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
private LocalDateTime createTime;
@Schema(description = "更新时间")
private LocalDateTime updateTime;
@Schema(description = "逻辑删除标识(0-未删除1-已删除)")
private Integer isDeleted;;
}
}

View File

@ -3,15 +3,17 @@ package com.youlai.mall.pms.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.youlai.common.web.model.Option;
import com.youlai.mall.pms.model.entity.Category;
import com.youlai.mall.pms.model.form.CategoryForm;
import com.youlai.mall.pms.model.vo.CategoryVO;
import java.util.List;
/**
* 商品分类
* 商品分类接口
*
* @author <a href="mailto:xianrui0365@163.com">haoxr</a>
* @author Ray Hao
* @since 2024/04/20
*/
public interface CategoryService extends IService<Category> {
@ -19,26 +21,31 @@ public interface CategoryService extends IService<Category> {
/**
* 分类列表树形
*
* @param parentId
* @param parentId 父分类ID
* @return
*/
List<CategoryVO> getCategoryList(Long parentId);
List<CategoryVO> listCategories(Long parentId);
/**
* 分类列表级联
* @return
*/
List<Option> getCategoryOptions();
List<Option> listCategoryOptions();
/**
*
* 保存新增/修改分类
*
*
* @param category
* @param formData 分类表单数据
* @return
*/
Long saveCategory(Category category);
Long saveCategory( CategoryForm formData);
/**
* 删除分类
*
* @param id 分类ID
* @return
*/
boolean deleteCategory(Long id);
}

View File

@ -48,10 +48,6 @@ public class AttributeServiceImpl extends ServiceImpl<AttributeMapper, Attribute
int pageNum = queryParams.getPageNum();
int pageSize = queryParams.getPageSize();
Page<AttributeBO> page = new Page<>(pageNum, pageSize);
// 格式化为数据库日期格式避免日期比较使用格式化函数导致索引失效
DateUtils.toDatabaseFormat(queryParams, "startTime", "endTime");
// 查询数据
Page<AttributeBO> boPage = this.baseMapper.listPagedAttributes(page, queryParams);

View File

@ -1,112 +1,124 @@
package com.youlai.mall.pms.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Assert;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.youlai.common.constant.GlobalConstants;
import com.youlai.common.web.model.Option;
import com.youlai.mall.pms.converter.CategoryConverter;
import com.youlai.mall.pms.mapper.CategoryMapper;
import com.youlai.mall.pms.model.entity.Category;
import com.youlai.mall.pms.model.form.CategoryForm;
import com.youlai.mall.pms.model.vo.CategoryVO;
import com.youlai.mall.pms.service.CategoryService;
import org.springframework.cache.annotation.CacheEvict;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 商品分类
* 商品分类服务实现类
*
* @author haoxr
* @author Ray Hao
* @since 2024/04/20
*/
@Service
@RequiredArgsConstructor
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
private final CategoryConverter categoryConverter;
/**
* 分类列表树形
*
* @param parentId
* @return
* @Cacheable value:缓存名称(分区)key缓存键
* @param parentId 父分类ID
* @return 分类列表
*/
// @Cacheable(value = "pms", key = "'categoryList'")
@Override
public List<CategoryVO> getCategoryList(Long parentId) {
public List<CategoryVO> listCategories(Long parentId) {
List<Category> categoryList = this.list(
new LambdaQueryWrapper<Category>()
.eq(Category::getVisible, GlobalConstants.STATUS_YES)
.orderByDesc(Category::getSort)
);
List<CategoryVO> list = recursionTree(parentId != null ? parentId : 0l, categoryList);
return list;
}
private static List<CategoryVO> recursionTree(Long parentId, List<Category> categoryList) {
List<CategoryVO> list = new ArrayList<>();
Optional.ofNullable(categoryList)
.ifPresent(categories ->
categories.stream().filter(category ->
category.getParentId().equals(parentId))
.forEach(category -> {
CategoryVO categoryVO = new CategoryVO();
BeanUtil.copyProperties(category, categoryVO);
List<CategoryVO> children = recursionTree(category.getId(), categoryList);
categoryVO.setChildren(children);
list.add(categoryVO);
}));
return list;
return buildTree(parentId != null ? parentId : 0L, categoryList,
category -> {
CategoryVO categoryVO = new CategoryVO();
BeanUtil.copyProperties(category, categoryVO);
return categoryVO;
},
CategoryVO::setChildren
);
}
/**
* 分类列表级联
*
* @return
* @return 分类列表选项
*/
@Override
public List<Option> getCategoryOptions() {
public List<Option> listCategoryOptions() {
List<Category> categoryList = this.list(
new LambdaQueryWrapper<Category>()
.eq(Category::getVisible, GlobalConstants.STATUS_YES)
.orderByAsc(Category::getSort)
);
List<Option> list = recursionCascade(0l, categoryList);
return list;
}
private List<Option> recursionCascade(Long parentId, List<Category> categoryList) {
List<Option> list = new ArrayList<>();
Optional.ofNullable(categoryList)
.ifPresent(categories ->
categories.stream().filter(category ->
category.getParentId().equals(parentId))
.forEach(category -> {
Option categoryVO = new Option<>(category.getId(), category.getName());
BeanUtil.copyProperties(category, categoryVO);
List<Option> children = recursionCascade(category.getId(), categoryList);
categoryVO.setChildren(children);
list.add(categoryVO);
})
);
return list;
return buildTree(0L, categoryList,
category -> new Option<>(category.getId(), category.getName()),
(option, children) -> ((Option<?>) option).setChildren(children)
);
}
/**
* 通用的递归树构建方法
*
* @param parentId 父分类ID
* @param categoryList 分类列表
* @param converter 实体转换器
* @param childSetter 子节点设置器用于将子节点列表设置到父节点上
* @return 构建好的树形结构列表
*/
private <T> List<T> buildTree(Long parentId, List<Category> categoryList, Function<Category, T> converter, BiConsumer<T, List<T>> childSetter) {
return categoryList.stream()
.filter(category -> category.getParentId().equals(parentId))
.map(category -> {
T node = converter.apply(category);
List<T> children = buildTree(category.getId(), categoryList, converter, childSetter);
childSetter.accept(node, children);
return node;
})
.collect(Collectors.toList());
}
/**
* 新增/修改分类
*
* @param category
* @param formData 分类表单数据
* @return 分类ID
* @CacheEvict 缓存失效
*/
@CacheEvict(value = "pms", key = "'categoryList'")
@Override
public Long saveCategory(Category category) {
this.saveOrUpdate(category);
public Long saveCategory(CategoryForm formData) {
Category category = categoryConverter.form2Entity(formData);
boolean result = this.saveOrUpdate(category);
Assert.isTrue(result, "保存商品分类失败");
return category.getId();
}
/**
* 删除分类
*
* @param id 分类ID
* @return 是否成功
*/
@Override
public boolean deleteCategory(Long id) {
int count= this.baseMapper.deleteCategoryById(id);
return count > 0;
}
}

View File

@ -19,7 +19,6 @@
id, attribute_group_id, name, input_type, options, create_time, update_time, is_deleted
</sql>
<!-- 属性分页列表 -->
<select id="listPagedAttributes" resultType="com.youlai.mall.pms.model.bo.AttributeBO">
SELECT
@ -33,8 +32,12 @@
name LIKE CONCAT('%',#{queryParams.keywords},'%')
)
</if>
<if test='queryParams.groupId!=null'>
AND attribute_group_id = #{queryParams.groupId}
</if>
</where>
ORDER BY sort ASC, create_time DESC
ORDER BY
create_time DESC
</select>
</mapper>