商品管理模块:分类、上下架、图片、规格管理
商品管理模块分类、上下架、图片、规格管理商品管理是无人售货柜的粮草官没有商品后面的订单、支付、库存全部是空谈。今天我们就来把商品管理这个基础模块拆开揉碎一文讲透。一、商品在无人售货柜中的角色在无人售货柜系统中商品是核数据源。用户扫码开门 → 看到商品 → 拿走商品 → 结算扣库存 → 生成订单整条链路的数据起点就是商品。没有商品数据设备就是个空壳子小程序也是白屏状态。商品管理模块需要解决的问题运营人员如何添加、编辑、下架商品商品如何分类饮料/零食/日用品每个柜子摆哪些商品摆在第几排第几列商品图片怎么存规格怎么定义二、商品表结构设计一张好的表结构是模块稳定的地基。以下是我们项目的商品主表设计CREATETABLEproduct(idbigintNOTNULLCOMMENT商品ID雪花算法生成,namevarchar(100)NOTNULLCOMMENT商品名称,category_idbigintNOTNULLCOMMENT分类ID,pricedecimal(10,2)NOTNULLCOMMENT售价(元),cost_pricedecimal(10,2)DEFAULTNULLCOMMENT成本价(元),main_imagevarchar(500)DEFAULTNULLCOMMENT主图URL,imagesjsonDEFAULTNULLCOMMENT多图URL数组,descriptiontextCOMMENT商品描述,specvarchar(200)DEFAULTNULLCOMMENT规格如500ml/瓶,barcodevarchar(50)DEFAULTNULLCOMMENT条码扫码识别用,statustinyintNOTNULLDEFAULT0COMMENT0-下架 1-上架,sort_orderintDEFAULT0COMMENT排序权重,shelf_timedatetimeDEFAULTNULLCOMMENT上架时间,create_timedatetimeNOTNULLDEFAULTCURRENT_TIMESTAMP,update_timedatetimeNOTNULLDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMP,deletedtinyintNOTNULLDEFAULT0COMMENT逻辑删除,PRIMARYKEY(id),KEYidx_category_id(category_id),KEYidx_barcode(barcode),KEYidx_status(status))ENGINEInnoDBDEFAULTCHARSETutf8mb4COMMENT商品表;几个设计要点说明主键用雪花算法分布式环境下避免自增ID冲突同时保持时间有序。images 字段用 JSON 类型商品可能有多张展示图JSON 比多表关联更直接。barcode 加索引扫码是高频操作必须走索引。deleted 逻辑删除商品关联订单历史数据不能物理删除。三、商品分类管理分类采用树形结构一张表搞定CREATETABLEcategory(idbigintNOTNULL,namevarchar(50)NOTNULLCOMMENT分类名称,parent_idbigintDEFAULT0COMMENT父分类ID0顶级,leveltinyintDEFAULT1COMMENT层级1/2/3,sort_orderintDEFAULT0,PRIMARYKEY(id))ENGINEInnoDBDEFAULTCHARSETutf8mb4COMMENT商品分类表;典型的分类树饮料(1) ├── 碳酸饮料(2) ├── 果汁(3) └── 茶饮(4) 零食(5) ├── 膨化食品(6) └── 坚果(7)分类 CRUD 的实现和常规项目差不多但有两个业务小细节删除分类前必须检查如果该分类及其子分类下还有商品不允许删除。分类下商品数量统计用 SQL 的COUNT GROUP BY或维护product_count冗余字段后者在经常展示的场景下性能更好。// 查询分类及其商品数量Select(SELECT c.*, COUNT(p.id) AS product_count FROM category c LEFT JOIN product p ON c.id p.category_id AND p.deleted 0 WHERE c.deleted 0 GROUP BY c.id)ListCategoryVOlistWithProductCount();四、商品上下架上下架逻辑不复杂重点是批量操作和缓存同步ServicepublicclassProductService{AutowiredprivateRedisTemplateString,ObjectredisTemplate;/** * 商品上架 */TransactionalpublicvoidonShelf(LongproductId){ProductproductproductMapper.selectById(productId);if(productnull)thrownewBusinessException(商品不存在);if(product.getStatus()1)thrownewBusinessException(商品已上架);product.setStatus(1);product.setShelfTime(LocalDateTime.now());productMapper.updateById(product);// 上架后清除商品缓存下次查询重建redisTemplate.delete(product:detail:productId);redisTemplate.delete(product:list);}/** * 批量下架 */TransactionalpublicvoidbatchOffShelf(ListLongproductIds){LambdaUpdateWrapperProductwrappernewLambdaUpdateWrapper();wrapper.in(Product::getId,productIds).set(Product::getStatus,0);productMapper.update(null,wrapper);// 批量删除缓存ListStringkeysproductIds.stream().map(id-product:detail:id).collect(Collectors.toList());redisTemplate.delete(keys);redisTemplate.delete(product:list);}}缓存清除的时机任何商品信息变更上下架、改价、换图都必须在事务提交后清除对应缓存否则用户看到的就是僵尸商品。五、商品图片管理图片管理的关键是OSS 存储 缩略图上传流程用户选图 → 前端上传至OSS → 回调后端保存URL → 返回给前端PostMapping(/upload/image)publicResultStringuploadImage(RequestParam(file)MultipartFilefile){// 1. 上传到阿里云OSSStringoriginalNamefile.getOriginalFilename();StringextoriginalName.substring(originalName.lastIndexOf(.));StringobjectNameproduct/DateUtil.format(LocalDate.now(),yyyy/MM/dd)/IdUtil.snowflake()ext;ossClient.putObject(bucketName,objectName,file.getInputStream());// 2. 生成缩略图使用时拼接 ?x-oss-processimage/resize,m_fill,h_200,w_200StringurlossConfig.getDomain()/objectName;returnResult.ok(url);}使用阿里云 OSS 的图片处理参数生成缩略图无需单独存储访问时带上?x-oss-processimage/resize,w_200即可实时缩放省钱又省事。主图设置逻辑main_image存一张imagesJSON 数组存全部。前端展示时优先展示主图。六、商品规格管理与条码关联规格字段spec存字符串如500ml/瓶、200g/袋。条码barcode是重要关联字段扫码 → 查询barcode → 确定商品ID → 后续结算/库存操作如果同一商品有不同规格如可乐 330ml vs 500ml应该是两个独立的 Product 记录各自有独立的 barcode而不是在同一商品下做规格子表。好处是库存、价格、销量统计全部独立逻辑清晰。七、商品与设备绑定每台售货柜需要知道它卖哪些商品以及每个商品的陈列位置CREATETABLEdevice_product(idbigintNOTNULL,device_idbigintNOTNULLCOMMENT设备ID,product_idbigintNOTNULLCOMMENT商品ID,row_numintDEFAULTNULLCOMMENT所在排,col_numintDEFAULTNULLCOMMENT所在列,stock_countintDEFAULT0COMMENT当前库存量,max_stockintDEFAULT10COMMENT最大库存量,PRIMARYKEY(id),UNIQUEKEYuk_device_product(device_id,product_id))ENGINEInnoDBDEFAULTCHARSETutf8mb4COMMENT设备商品关联表;row_num/col_num 用于在小程序端绘制柜子可视化货架让用户直观看到每排每列放了什么。八、Feign 远程调用商品服务需要对外暴露接口给订单、库存等服务调用FeignClient(nameproduct-service,path/product)publicinterfaceProductFeignClient{GetMapping(/{id})ResultProductVOgetById(PathVariable(id)Longid);GetMapping(/batch)ResultListProductVOgetByIds(RequestParam(ids)ListLongids);GetMapping(/by-barcode)ResultProductVOgetByBarcode(RequestParam(barcode)Stringbarcode);}九、Redis 缓存策略// 读取商品时先查缓存publicProductVOgetById(Longid){StringcacheKeyproduct:detail:id;ProductVOcached(ProductVO)redisTemplate.opsForValue().get(cacheKey);if(cached!null)returncached;ProductproductproductMapper.selectById(id);if(productnull)thrownewBusinessException(商品不存在);ProductVOvoBeanUtil.copyProperties(product,ProductVO.class);redisTemplate.opsForValue().set(cacheKey,vo,30,TimeUnit.MINUTES);returnvo;}十、常见问题缓存不一致是最典型的问题。商品信息变更后如果先更新 DB 成功、清除缓存失败用户读到的就是旧数据。解决方案先更新 DB再删缓存不是更新缓存是删除加双重过期时间缓存 30 分钟但 25 分钟时异步刷新使用 Canal 监听 binlogDB 变更后自动删缓存更高级方案商品管理说简单也简单说复杂也复杂关键在于表结构设计的合理性和缓存一致性的把控。下一关我们进入库存管理的深水区。