电商商品管理系统设计与实现

2024/1/1

# 电商商品管理系统设计与实现

# 系统概述

商品管理系统是电商平台的核心模块之一,负责商品信息的维护、分类管理、库存控制、价格策略等功能。一个完善的商品管理系统需要支持多维度的商品属性、灵活的分类体系、精准的库存管理和高效的搜索功能。

# 核心功能模块

# 1. 商品基础信息管理

# 1.1 商品实体设计

@Entity
@Table(name = "products")
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, length = 200)
    private String name;
    
    @Column(length = 100)
    private String brand;
    
    @Column(columnDefinition = "TEXT")
    private String description;
    
    @Column(precision = 10, scale = 2)
    private BigDecimal price;
    
    @Column(precision = 10, scale = 2)
    private BigDecimal originalPrice;
    
    @Column(nullable = false)
    private Integer stock;
    
    @Column(nullable = false)
    private Integer sales = 0;
    
    @Enumerated(EnumType.STRING)
    private ProductStatus status;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "category_id")
    private Category category;
    
    @OneToMany(mappedBy = "product", cascade = CascadeType.ALL)
    private List<ProductImage> images;
    
    @OneToMany(mappedBy = "product", cascade = CascadeType.ALL)
    private List<ProductAttribute> attributes;
    
    @CreationTimestamp
    private LocalDateTime createdAt;
    
    @UpdateTimestamp
    private LocalDateTime updatedAt;
}

public enum ProductStatus {
    DRAFT,      // 草稿
    ACTIVE,     // 上架
    INACTIVE,   // 下架
    DELETED     // 已删除
}

# 1.2 商品服务实现

@Service
@Transactional
public class ProductService {
    
    @Autowired
    private ProductRepository productRepository;
    
    @Autowired
    private CategoryService categoryService;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 创建商品
     */
    public Product createProduct(ProductCreateDTO dto) {
        // 验证分类是否存在
        Category category = categoryService.findById(dto.getCategoryId());
        if (category == null) {
            throw new BusinessException("商品分类不存在");
        }
        
        Product product = new Product();
        BeanUtils.copyProperties(dto, product);
        product.setCategory(category);
        product.setStatus(ProductStatus.DRAFT);
        
        // 保存商品基础信息
        product = productRepository.save(product);
        
        // 保存商品图片
        if (dto.getImages() != null && !dto.getImages().isEmpty()) {
            saveProductImages(product, dto.getImages());
        }
        
        // 保存商品属性
        if (dto.getAttributes() != null && !dto.getAttributes().isEmpty()) {
            saveProductAttributes(product, dto.getAttributes());
        }
        
        return product;
    }
    
    /**
     * 商品上架
     */
    public void publishProduct(Long productId) {
        Product product = findById(productId);
        if (product.getStatus() != ProductStatus.DRAFT) {
            throw new BusinessException("只有草稿状态的商品才能上架");
        }
        
        // 验证商品信息完整性
        validateProductForPublish(product);
        
        product.setStatus(ProductStatus.ACTIVE);
        productRepository.save(product);
        
        // 同步到搜索引擎
        syncToSearchEngine(product);
        
        // 清除缓存
        clearProductCache(productId);
    }
    
    /**
     * 更新库存
     */
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public boolean updateStock(Long productId, Integer quantity, StockOperation operation) {
        Product product = productRepository.findByIdForUpdate(productId);
        if (product == null) {
            return false;
        }
        
        int newStock;
        switch (operation) {
            case INCREASE:
                newStock = product.getStock() + quantity;
                break;
            case DECREASE:
                newStock = product.getStock() - quantity;
                if (newStock < 0) {
                    throw new BusinessException("库存不足");
                }
                break;
            default:
                throw new IllegalArgumentException("不支持的库存操作类型");
        }
        
        product.setStock(newStock);
        productRepository.save(product);
        
        // 更新缓存中的库存
        updateStockCache(productId, newStock);
        
        return true;
    }
}

# 2. 商品分类管理

# 2.1 分类实体设计

@Entity
@Table(name = "categories")
public class Category {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, length = 100)
    private String name;
    
    @Column(length = 500)
    private String description;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "parent_id")
    private Category parent;
    
    @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
    private List<Category> children;
    
    @Column(nullable = false)
    private Integer level;
    
    @Column(nullable = false)
    private Integer sortOrder = 0;
    
    @Column(nullable = false)
    private Boolean isActive = true;
    
    @CreationTimestamp
    private LocalDateTime createdAt;
    
    @UpdateTimestamp
    private LocalDateTime updatedAt;
}

# 2.2 分类服务实现

@Service
public class CategoryService {
    
    @Autowired
    private CategoryRepository categoryRepository;
    
    /**
     * 构建分类树
     */
    public List<CategoryTreeNode> buildCategoryTree() {
        List<Category> allCategories = categoryRepository.findAllByIsActiveOrderBySortOrder(true);
        Map<Long, CategoryTreeNode> nodeMap = new HashMap<>();
        
        // 转换为树节点
        for (Category category : allCategories) {
            CategoryTreeNode node = new CategoryTreeNode();
            BeanUtils.copyProperties(category, node);
            nodeMap.put(category.getId(), node);
        }
        
        // 构建树结构
        List<CategoryTreeNode> rootNodes = new ArrayList<>();
        for (CategoryTreeNode node : nodeMap.values()) {
            if (node.getParentId() == null) {
                rootNodes.add(node);
            } else {
                CategoryTreeNode parent = nodeMap.get(node.getParentId());
                if (parent != null) {
                    parent.getChildren().add(node);
                }
            }
        }
        
        return rootNodes;
    }
    
    /**
     * 获取分类路径
     */
    public List<Category> getCategoryPath(Long categoryId) {
        List<Category> path = new ArrayList<>();
        Category current = findById(categoryId);
        
        while (current != null) {
            path.add(0, current);
            current = current.getParent();
        }
        
        return path;
    }
}

# 3. 商品属性管理

# 3.1 属性实体设计

@Entity
@Table(name = "product_attributes")
public class ProductAttribute {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "product_id")
    private Product product;
    
    @Column(nullable = false, length = 100)
    private String attributeName;
    
    @Column(nullable = false, length = 500)
    private String attributeValue;
    
    @Enumerated(EnumType.STRING)
    private AttributeType type;
    
    @Column(nullable = false)
    private Integer sortOrder = 0;
}

public enum AttributeType {
    BASIC,      // 基础属性
    SPEC,       // 规格属性
    CUSTOM      // 自定义属性
}

# 4. 库存管理

# 4.1 库存服务实现

@Service
public class InventoryService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private ProductRepository productRepository;
    
    private static final String STOCK_KEY_PREFIX = "product:stock:";
    
    /**
     * 预减库存(Redis)
     */
    public boolean preReduceStock(Long productId, Integer quantity) {
        String key = STOCK_KEY_PREFIX + productId;
        
        // 使用Lua脚本保证原子性
        String luaScript = 
            "local stock = redis.call('get', KEYS[1]) " +
            "if stock == false then " +
            "  return -1 " +
            "end " +
            "if tonumber(stock) >= tonumber(ARGV[1]) then " +
            "  return redis.call('decrby', KEYS[1], ARGV[1]) " +
            "else " +
            "  return -2 " +
            "end";
        
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(luaScript);
        script.setResultType(Long.class);
        
        Long result = redisTemplate.execute(script, 
            Collections.singletonList(key), quantity);
        
        return result != null && result >= 0;
    }
    
    /**
     * 恢复库存
     */
    public void restoreStock(Long productId, Integer quantity) {
        String key = STOCK_KEY_PREFIX + productId;
        redisTemplate.opsForValue().increment(key, quantity);
    }
    
    /**
     * 同步库存到数据库
     */
    @Scheduled(fixedDelay = 60000) // 每分钟同步一次
    public void syncStockToDatabase() {
        Set<String> keys = redisTemplate.keys(STOCK_KEY_PREFIX + "*");
        
        for (String key : keys) {
            Long productId = Long.parseLong(key.replace(STOCK_KEY_PREFIX, ""));
            Integer redisStock = (Integer) redisTemplate.opsForValue().get(key);
            
            if (redisStock != null) {
                Product product = productRepository.findById(productId).orElse(null);
                if (product != null && !product.getStock().equals(redisStock)) {
                    product.setStock(redisStock);
                    productRepository.save(product);
                }
            }
        }
    }
}

# 5. 商品搜索

# 5.1 搜索服务实现

@Service
public class ProductSearchService {
    
    @Autowired
    private ElasticsearchRestTemplate elasticsearchTemplate;
    
    /**
     * 商品搜索
     */
    public PageResult<ProductSearchResult> searchProducts(ProductSearchQuery query) {
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        
        // 关键词搜索
        if (StringUtils.hasText(query.getKeyword())) {
            MultiMatchQueryBuilder multiMatchQuery = QueryBuilders
                .multiMatchQuery(query.getKeyword(), "name", "description", "brand")
                .type(MultiMatchQueryBuilder.Type.BEST_FIELDS)
                .fuzziness(Fuzziness.AUTO);
            boolQuery.must(multiMatchQuery);
        }
        
        // 分类筛选
        if (query.getCategoryId() != null) {
            boolQuery.filter(QueryBuilders.termQuery("categoryId", query.getCategoryId()));
        }
        
        // 价格区间筛选
        if (query.getMinPrice() != null || query.getMaxPrice() != null) {
            RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("price");
            if (query.getMinPrice() != null) {
                rangeQuery.gte(query.getMinPrice());
            }
            if (query.getMaxPrice() != null) {
                rangeQuery.lte(query.getMaxPrice());
            }
            boolQuery.filter(rangeQuery);
        }
        
        // 品牌筛选
        if (query.getBrands() != null && !query.getBrands().isEmpty()) {
            boolQuery.filter(QueryBuilders.termsQuery("brand", query.getBrands()));
        }
        
        // 状态筛选
        boolQuery.filter(QueryBuilders.termQuery("status", "ACTIVE"));
        
        // 排序
        List<SortBuilder<?>> sorts = buildSorts(query.getSortType());
        
        // 构建搜索请求
        NativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder()
            .withQuery(boolQuery)
            .withPageable(PageRequest.of(query.getPage(), query.getSize()))
            .withSorts(sorts);
        
        // 执行搜索
        SearchHits<ProductSearchDocument> searchHits = 
            elasticsearchTemplate.search(searchQueryBuilder.build(), ProductSearchDocument.class);
        
        // 转换结果
        List<ProductSearchResult> results = searchHits.getSearchHits().stream()
            .map(hit -> convertToSearchResult(hit.getContent()))
            .collect(Collectors.toList());
        
        return new PageResult<>(results, searchHits.getTotalHits(), query.getPage(), query.getSize());
    }
}

# 性能优化策略

# 1. 缓存策略

@Component
public class ProductCacheManager {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final String PRODUCT_CACHE_PREFIX = "product:";
    private static final int CACHE_EXPIRE_SECONDS = 3600; // 1小时
    
    /**
     * 缓存商品信息
     */
    public void cacheProduct(Product product) {
        String key = PRODUCT_CACHE_PREFIX + product.getId();
        redisTemplate.opsForValue().set(key, product, CACHE_EXPIRE_SECONDS, TimeUnit.SECONDS);
    }
    
    /**
     * 获取缓存的商品信息
     */
    public Product getCachedProduct(Long productId) {
        String key = PRODUCT_CACHE_PREFIX + productId;
        return (Product) redisTemplate.opsForValue().get(key);
    }
    
    /**
     * 清除商品缓存
     */
    public void evictProductCache(Long productId) {
        String key = PRODUCT_CACHE_PREFIX + productId;
        redisTemplate.delete(key);
    }
}

# 2. 数据库优化

-- 商品表索引优化
CREATE INDEX idx_product_category_status ON products(category_id, status);
CREATE INDEX idx_product_brand_status ON products(brand, status);
CREATE INDEX idx_product_price_status ON products(price, status);
CREATE INDEX idx_product_created_at ON products(created_at);
CREATE INDEX idx_product_sales ON products(sales DESC);

-- 分类表索引
CREATE INDEX idx_category_parent_active ON categories(parent_id, is_active);
CREATE INDEX idx_category_level_sort ON categories(level, sort_order);

# 总结

商品管理系统是电商平台的基础模块,需要考虑以下关键点:

  1. 数据模型设计:合理的实体关系设计,支持灵活的商品属性扩展
  2. 库存管理:使用Redis预减库存,定时同步到数据库,保证高并发下的库存准确性
  3. 搜索功能:集成Elasticsearch,支持多维度搜索和筛选
  4. 缓存策略:多层缓存设计,提升系统性能
  5. 数据一致性:通过事务和分布式锁保证数据一致性
  6. 扩展性:支持商品属性的动态扩展和分类体系的灵活调整

通过以上设计和实现,可以构建一个高性能、高可用的商品管理系统,为电商平台提供稳定可靠的商品服务。