# springboot-stock **Repository Path**: seek412/springboot-stock ## Basic Information - **Project Name**: springboot-stock - **Description**: 商品库存扣库通用解决方案;支持单个商品以及多个商品同时扣库 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 1 - **Created**: 2022-06-25 - **Last Updated**: 2023-08-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 批量扣库 ## 需求 > 假设有一个赠品活动,需要同时给用户发放A、B、C三个商品, 默认情况下,A商品赠送1个,B商品赠送2个,C商品赠送3个; > 在赠送时可能出现某个或者几个商品库存不足的情况 > 赠送给用户的商品,需要提示那几个商品成功,数量是多少 针对不同的业务,可能存在以下三种场景: 1. 如果某个商品库存不足, 那么A\B\C三个商品,一个都不赠送 2. 如果某个商品库存不足, 那么剩余多少库存就赠送多少。(假设C原本的设定是赠送3个,现在只有2个库存了,那就只送2个) 3. 如果某个商品库存不足,跳过该商品,不影响其他商品的赠送(假设C原本的设定是赠送3个,现在只有2个库存了,那就只送A和B商品,C一个也不送) 在以上3种场景下,如果使用mysql直接扣库,可以使用乐观锁防止库存超卖,但是一个个循环去更新的话效率较低。而如果使用批量更新,那么无法获取哪些扣库成功哪些失败 ## 解决 扣库时使用redis lua脚本做多个商品的批量扣库; 先将商品库存信息保存到redis,扣库时从redis扣库,如果成功,在执行mysql的扣库。如果mysql异常,可以通过事务回滚,redis的数据已统一封装方法回滚 执行的结果统一封装BatchReduceStockVo类中,其中包含哪些商品扣库成功,哪些失败,以及扣库成功的数量均有返回 ## 测试用例 注:单元测试用例覆盖率100% ### 单个商品扣库 场景1: - 只扣除A, 数量为1 - 扣库方式:库存不足则失败 - 假设mysql扣库成功 - 预期结果:扣库数量为1,结果返回成功 - 详见用例:ReduceStockTest.test1 场景2: - 只扣除A, 数量为1 - 扣库方式:库存不足则失败 - 假设mysql扣库失败 - 预期结果:扣库数量为0,结果返回失败。 redis库存数量保持原来的不变 - 详见用例:ReduceStockTest.test1_1 场景3: - 只扣除A, 数量为2 - 扣库方式:库存不足则失败 - 假设mysql扣库成功 - 预期结果:扣库数量为0, 结果返回失败。 redis库存数量保持原来的不变 - 详见用例:ReduceStockTest.test2 场景4: - 只扣除A, 数量为2 - 扣库方式:库存不足则失败 - 假设mysql扣库失败 - 预期结果:扣库数量为0, 结果返回失败。 redis库存数量保持原来的不变 - 详见用例:ReduceStockTest.test2_1 场景5: - 只扣除A, 数量为2 - 扣库方式:库存不足有多少扣多少 - 假设mysql扣库成功 - 预期结果:扣库数量为1, 结果返回成功 - 详见用例:ReduceStockTest.test3 场景6: - 只扣除A, 数量为2 - 扣库方式:库存不足有多少扣多少 - 假设mysql扣库失败 - 预期结果:扣库数量为0, 结果返回失败。redis库存数量保持原来的不变 - 详见用例:ReduceStockTest.test3_1 ### 多个商品扣库 场景7: - 扣除A\B\C三个商品, A的数量为1, B的数量为1,C的数量为1 - 扣库方式:库存不足则失败 - 假设mysql扣库成功 - 预期结果:扣库成功,结果返回成功。剩余A\B\C的库存为0、1、2 - 详见用例:ReduceStockTest.test4 场景8: - 扣除A\B\C三个商品, A的数量为1, B的数量为1,C的数量为1 - 扣库方式:库存不足则失败 - 假设mysql扣库失败 - 预期结果:扣库失败,结果返回失败。剩余A\B\C的库存为1、2、3 - 详见用例:ReduceStockTest.test4_1 场景9: - 扣除A\B\C三个商品, A的数量为1, B的数量为3,C的数量为2 - 扣库方式:库存不足则失败 - 假设mysql扣库成功 - 预期结果:扣库失败,结果返回失败。剩余A\B\C的库存为1、2、3 - 详见用例:ReduceStockTest.test5 场景10: - 扣除A\B\C三个商品, A的数量为1, B的数量为3,C的数量为2 - 扣库方式:库存不足则失败 - 假设mysql扣库失败 - 预期结果:扣库失败,结果返回失败。剩余A\B\C的库存为1、2、3 - 详见用例:ReduceStockTest.test5_1 场景11: - 扣除A\B\C三个商品, A的数量为1, B的数量为3,C的数量为2 - 扣库方式:有多少扣多少 - 假设mysql扣库成功 - 预期结果:扣库成功, 扣库结果A数量减1, B数量减2, C数量减2 - 详情用例:ReduceStockTest.test6 场景12: - 扣除A\B\C三个商品, A的数量为1, B的数量为3,C的数量为2 - 扣库方式:有多少扣多少 - 假设mysql扣库失败 - 预期结果:扣库失败, A\B\C库存不变 - 详情用例:ReduceStockTest.test6 场景13: - 扣除A\B\C三个商品, A的数量为1, B的数量为3,C的数量为2 - 扣库方式:库存不足跳过该商品,不影响其他商品扣库 - 假设mysql扣库成功 - 预期结果:扣库成功, 扣库结果A=1, B=0, C=2 - 详情用例:ReduceStockTest.test7 场景14: - 扣除A\B\C三个商品, A的数量为1, B的数量为3,C的数量为2 - 扣库方式:库存不足跳过该商品,不影响其他商品扣库 - 假设mysql扣库失败 - 预期结果:扣库失败, A\B\C库存不变 - 详情用例:ReduceStockTest.test7_1 场景15: - 扣库不存在的商品:ReduceStockTest.test8 - 参数校验,扣库数量为负数的场景:ReduceStockTest.test10 - 参数校验,测试参数为空的场景:ReduceStockTest.test11 场景16: - 只扣redis库存,不扣mysql库存:ReduceStockTest.test12