一、系统需求分析 商品分类管理是生鲜电商系统的核心功能之一,主要需求包括: 1.多级分类体系:支持多级分类结构(如一级分类:蔬菜,二级分类:叶菜类,三级分类:菠菜) 2.分类属性管理:不同分类可配置不同属性(如生鲜保质期、产地等) 3.分类与商品关联:商品可归属到多个分类路径
一、系统需求分析
商品分类管理是生鲜电商系统的核心功能之一,主要需求包括:
1. 多级分类体系:支持多级分类结构(如一级分类:蔬菜,二级分类:叶菜类,三级分类:菠菜)
2. 分类属性管理:不同分类可配置不同属性(如生鲜保质期、产地等)
3. 分类与商品关联:商品可归属到多个分类路径
4. 分类可视化展示:前端展示分类树形结构
5. 分类搜索与筛选:支持按分类快速查找商品
二、数据库设计
核心表结构
```sql
-- 商品分类表
CREATE TABLE `category` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 分类ID,
`name` varchar(50) NOT NULL COMMENT 分类名称,
`parent_id` bigint(20) DEFAULT NULL COMMENT 父分类ID,
`level` tinyint(4) NOT NULL COMMENT 分类层级(1-5),
`sort_order` int(11) DEFAULT 0 COMMENT 排序权重,
`image_url` varchar(255) DEFAULT NULL COMMENT 分类图片,
`is_active` tinyint(1) DEFAULT 1 COMMENT 是否启用,
`created_at` datetime NOT NULL COMMENT 创建时间,
`updated_at` datetime NOT NULL COMMENT 更新时间,
PRIMARY KEY (`id`),
KEY `idx_parent_id` (`parent_id`),
KEY `idx_level` (`level`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=商品分类表;
-- 分类属性表
CREATE TABLE `category_attribute` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`category_id` bigint(20) NOT NULL COMMENT 分类ID,
`attr_name` varchar(50) NOT NULL COMMENT 属性名称,
`attr_type` varchar(20) NOT NULL COMMENT 属性类型(text,number,select),
`is_required` tinyint(1) DEFAULT 0 COMMENT 是否必填,
`sort_order` int(11) DEFAULT 0 COMMENT 排序权重,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_category_attr` (`category_id`,`attr_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=分类属性表;
-- 分类属性值表(用于select类型)
CREATE TABLE `category_attribute_value` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`attribute_id` bigint(20) NOT NULL COMMENT 属性ID,
`value` varchar(100) NOT NULL COMMENT 属性值,
`sort_order` int(11) DEFAULT 0 COMMENT 排序权重,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_attr_value` (`attribute_id`,`value`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=分类属性值表;
```
三、核心功能实现
1. 分类树形结构管理
后端实现(Java Spring Boot示例)
```java
@Service
public class CategoryService {
@Autowired
private CategoryRepository categoryRepository;
// 获取完整分类树
public List
getCategoryTree() {
List allCategories = categoryRepository.findAllByIsActive(true);
Map> childrenMap = new HashMap<>();
// 构建子分类映射
allCategories.forEach(category -> {
if (category.getParentId() != null) {
childrenMap.computeIfAbsent(category.getParentId(), k -> new ArrayList<>()).add(category);
}
});
// 构建树形结构
List tree = new ArrayList<>();
allCategories.stream()
.filter(c -> c.getParentId() == null)
.forEach(root -> tree.add(buildTree(root, childrenMap)));
return tree;
}
private CategoryTreeDTO buildTree(Category category, Map> childrenMap) {
CategoryTreeDTO dto = new CategoryTreeDTO();
BeanUtils.copyProperties(category, dto);
List children = childrenMap.getOrDefault(category.getId(), Collections.emptyList())
.stream()
.map(child -> buildTree(child, childrenMap))
.collect(Collectors.toList());
dto.setChildren(children);
return dto;
}
}
```
2. 分类属性管理
属性保存接口示例
```java
@PostMapping("/{categoryId}/attributes")
public ResponseEntity<?> saveCategoryAttributes(
@PathVariable Long categoryId,
@RequestBody List attributeDTOs) {
Category category = categoryRepository.findById(categoryId)
.orElseThrow(() -> new ResourceNotFoundException("Category not found"));
// 清空原有属性
categoryAttributeRepository.deleteByCategoryId(categoryId);
// 保存新属性
List attributes = attributeDTOs.stream().map(dto -> {
CategoryAttribute attribute = new CategoryAttribute();
attribute.setCategoryId(categoryId);
attribute.setAttrName(dto.getAttrName());
attribute.setAttrType(dto.getAttrType());
attribute.setIsRequired(dto.getIsRequired());
attribute.setSortOrder(dto.getSortOrder());
return attribute;
}).collect(Collectors.toList());
categoryAttributeRepository.saveAll(attributes);
return ResponseEntity.ok().build();
}
```
3. 前端分类展示组件(Vue示例)
```vue
:data="categoryTree"
:props="defaultProps"
node-key="id"
default-expand-all
highlight-current
@node-click="handleNodeClick">
{{ node.label }}
添加子分类
删除
<script>
export default {
data() {
return {
categoryTree: [],
defaultProps: {
children: children,
label: name
}
}
},
created() {
this.fetchCategoryTree();
},
methods: {
async fetchCategoryTree() {
const response = await this.$api.get(/categories/tree);
this.categoryTree = response.data;
},
handleNodeClick(data) {
// 处理分类点击事件
this.$emit(category-selected, data);
},
append(data) {
// 添加子分类逻辑
this.$prompt(请输入分类名称, 提示, {
confirmButtonText: 确定,
cancelButtonText: 取消
}).then(({ value }) => {
this.$api.post(`/categories/${data.id}/children`, { name: value })
.then(() => this.fetchCategoryTree());
});
},
remove(node, data) {
// 删除分类逻辑
this.$confirm(确认删除该分类吗?, 提示, {
confirmButtonText: 确定,
cancelButtonText: 取消,
type: warning
}).then(() => {
this.$api.delete(`/categories/${data.id}`)
.then(() => this.fetchCategoryTree());
});
}
}
}
```
四、关键业务逻辑实现
1. 分类移动与排序
```java
@Service
public class CategoryMoveService {
@Autowired
private CategoryRepository categoryRepository;
@Transactional
public void moveCategory(Long categoryId, Long newParentId, Integer newSortOrder) {
Category category = categoryRepository.findById(categoryId)
.orElseThrow(() -> new ResourceNotFoundException("Category not found"));
// 更新父分类
category.setParentId(newParentId);
// 更新排序
category.setSortOrder(newSortOrder != null ? newSortOrder : 0);
// 如果移动到新父分类下,需要重新计算同级排序
if (newParentId != null) {
adjustSiblingSortOrders(newParentId, categoryId, newSortOrder);
}
categoryRepository.save(category);
}
private void adjustSiblingSortOrders(Long parentId, Long excludeCategoryId, Integer newSortOrder) {
List siblings = categoryRepository.findByParentIdAndIdNot(parentId, excludeCategoryId);
// 如果指定了新排序位置,需要重新排列
if (newSortOrder != null) {
// 简单实现:将指定位置的后续节点排序+1
// 实际应用中可能需要更复杂的排序逻辑
siblings.stream()
.filter(s -> s.getSortOrder() >= newSortOrder)
.forEach(s -> s.setSortOrder(s.getSortOrder() + 1));
categoryRepository.saveAll(siblings);
}
}
}
```
2. 分类与商品关联
```java
@Entity
@Table(name = "product_category")
public class ProductCategory {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "product_id", nullable = false)
private Product product;
@ManyToOne
@JoinColumn(name = "category_id", nullable = false)
private Category category;
// 分类路径(用于快速查询,存储如 "1,23,45" 这样的路径字符串)
@Column(name = "category_path")
private String categoryPath;
// getters and setters
}
@Service
public class ProductCategoryService {
@Autowired
private ProductCategoryRepository productCategoryRepository;
@Autowired
private CategoryRepository categoryRepository;
@Transactional
public void updateProductCategories(Long productId, List categoryIds) {
// 先删除原有关联
productCategoryRepository.deleteByProductId(productId);
if (categoryIds == null || categoryIds.isEmpty()) {
return;
}
// 获取完整的分类路径信息
Map categoryPathMap = new HashMap<>();
categoryIds.forEach(categoryId -> {
String path = buildCategoryPath(categoryId, categoryPathMap);
categoryPathMap.put(categoryId, path);
});
// 创建新关联
List newRelations = categoryIds.stream().map(categoryId -> {
ProductCategory pc = new ProductCategory();
pc.setProduct(new Product(productId));
pc.setCategory(new Category(categoryId));
pc.setCategoryPath(categoryPathMap.get(categoryId));
return pc;
}).collect(Collectors.toList());
productCategoryRepository.saveAll(newRelations);
}
private String buildCategoryPath(Long categoryId, Map pathMap) {
if (pathMap.containsKey(categoryId)) {
return pathMap.get(categoryId);
}
Category category = categoryRepository.findById(categoryId)
.orElseThrow(() -> new ResourceNotFoundException("Category not found"));
String path = String.valueOf(categoryId);
if (category.getParentId() != null) {
String parentPath = buildCategoryPath(category.getParentId(), pathMap);
path = parentPath + "," + path;
}
pathMap.put(categoryId, path);
return path;
}
}
```
五、性能优化建议
1. 分类缓存:使用Redis缓存分类树结构,减少数据库查询
2. 批量操作:对于分类移动、排序等操作,采用批量更新方式
3. 索引优化:确保分类ID、父ID、路径等字段有适当索引
4. 异步处理:非实时性要求高的操作(如统计分类下商品数)可采用异步方式
5. 读写分离:对于读多写少的场景,配置数据库读写分离
六、扩展功能考虑
1. 分类标签:为分类添加标签系统,支持更灵活的筛选
2. 分类别名:支持分类别名,方便SEO优化
3. 分类权限:不同角色对分类的操作权限控制
4. 分类导入导出:支持Excel批量导入导出分类数据
5. 分类统计:统计各分类下商品数量、销量等数据
以上方案可根据美菜生鲜实际业务需求进行调整和扩展,核心是构建一个灵活、可扩展的分类管理体系,支持生鲜电商复杂的业务场景。