游泳用品这个品类,在很多开发者眼里不过是又一个"增删改查"的进销存项目。真正做过之后才发现,从泳镜的度数规格到泳衣的偏小码设计,再到线上线下共用库存的并发扣减,每一个环节都有得有失。这篇文章我把整个springboot游泳用品专卖店系统的设计与实现过程拆开来讲,从数据库建模到订单库存一致性,从权限控制到部署扩展,讲的都是实际开发中遇到的真实问题和我的取舍思路。内容比较长,但跟着走一遍,做同类课时设计或真实门店管理系统时,能少走不少弯路。
我记得当时接到这个需求的第一反应是:这不就是个标准的管理系统吗?商品管理加订单管理加会员管理,网上模板一抓一大把。但深入了解游泳用品门店的实际运营后,我发现事情没那么简单。游泳用品SKU的属性组合和普通服装鞋帽有很大差异,比如泳镜按度数分规格,泳衣尺码体系混乱,不同品牌同码数的实际版型能差出两个码。这种领域特殊性,往往会直接决定数据库怎么设计、商品发布流程怎么走。
1. 面向游泳用品场景的项目定位与整体方案权衡
1.1 这个系统实际要解决的三个核心问题
游泳用品专卖店的日常运营,拆解下来主要就三块:商品进销存、门店收银、线上订单。很多小门店早期用Excel加手工记账,商品一多就乱;后来上了通用电商系统,又发现不支持门店维度,批发和零售价格混在一起,库存对不上。
这套系统的定位就非常明确了:给单品单店的游泳用品门店提供一个集商品、库存、订单、会员于一身的管理后台,前期先保证核心链路顺畅。这里有一个取舍——我没有一开始就上微服务,也没有做小程序端,而是先采用Spring Boot单体应用加Thymeleaf服务端渲染的方案。
选择Spring Boot单体应用的原因很简单:游泳用品专卖店的业务体量在初期根本没有到需要分布式架构的程度。一个后台管理系统,用户量几十人,并发量集中在每天傍晚和周末的收银高峰,单体能搞定的事情,拆分只会增加维护成本。数据库用MySQL,缓存用Redis,ORM用MyBatis-Plus,权限认证用SA-Token,这套组合在保证开发速度的同时,后期真要拆服务也不至于重写底层。
1.2 从单店到连锁预留的扩展空间
虽然当前做的是单店版本,但我在设计时还是给连锁化留了路。商品表、库存表、订单表都预留了门店字段,后续如果要做多门店版,只需要把门店维度从普通字段提升为分库分表键,而不是推翻重来。
这里要说一句实在话:很多课程设计或毕业设计喜欢一上来就画微服务架构图,动不动就是网关加注册中心加分布式事务。但真实开店场景里,老板不会关心你用了什么架构,他关心的是操作顺不顺手,库存准不准,月底对账快不快。系统设计的第一原则是匹配业务复杂度,而不是技术复杂度。
2. 泳具SKU的特殊维度:从泳镜度数到服装尺码的数据库设计
2.1 为什么通用电商系统的商品模型不完全适用
通用电商系统处理SPU和SKU的方式通常比较简单:规格值写死,颜色尺码一套走天下。但游泳用品不一样,光是泳镜这一个类目,就有平光和近视之分,近视泳镜又分-1.5、-2.0、-2.5、-3.0、-4.0、-5.0、-6.0、-8.0等多个度数档位,再加上镜片颜色、是否带防雾涂层,一个SPU下面能组合出几十个SKU。
泳衣泳裤的问题在尺码。国内游泳用品的尺码体系比传统服装更混乱,有的品牌用S/M/L,有的用160/165/170,还有的用均码。同一个品牌,男款泳裤和女款泳衣的尺码对照表完全不一样。这就意味着,尺码必须作为自定义规格项存在,不能在代码里写枚举写死。
我把规格设计成了一张独立的规格模板表,包含类目ID、规格名称和候选值列表。发布商品时,运营人员选择类目,系统根据类目关联的规格模板动态生成规格项,运营只需填写每个规格项的候选值,系统自动笛卡尔积组合出SKU集合。
2.2 九张核心表的结构设计与关键字段解析
最终落地的核心表一共九张,这九张表撑起了整个系统的数据骨架:
| 表名 | 核心职责 |
|---|---|
| 门店表 | 存储门店基本信息,后续连锁化的基础 |
| 用户表 | 后台账号,关联门店ID,一个账号只能属于一个门店 |
| 商品表 | 存储SPU级信息,名称、类目、品牌、描述 |
| 规格模板表 | 类目与规格项的映射关系,决定SKU生成规则 |
| SKU表 | 规格值组合的具体商品,价格、库存、条码都在这一层 |
| 库存流水表 | 每次入库、出库、盘点、调整的记录,可追溯 |
| 订单表 | 订单主表,收银订单和线上订单共用 |
| 订单明细表 | 订单下的商品快照,锁定成交时的价格和规格信息 |
| 会员表 | 储值、折扣、积分信息 |
商品表和SKU表之间通过SPU_ID关联,一个商品下挂多个SKU。订单明细表里的商品信息不直接关联SKU表的实时数据,而是做快照存储。这个设计很重要,因为商品价格和名称后续可能调整,但历史订单必须保留成交那一刻的真实信息。我之前见过一个项目直接在订单明细里外键关联商品表,商家改个促销价,几个月前的订单对账全乱了。
库存没放在SKU表里,而是单独拆出了一张库存表,按SKU加门店维度记录。之所以不直接在SKU表里存一个库存字段,是因为游泳用品门店经常需要区分仓库库存和货架陈列库存,某些紧俏款还有预售库存的诉求。分表设计后,各门店各仓库的库存互不影响,查询和扣减都清晰。
金额字段全部用了DECIMAL(10,2),没有用FLOAT或DOUBLE,这个属于老生常谈,但每次都要强调:二进制浮点数在金额计算上会有精度丢失,哪怕只差一分钱,月底对不上账都是大事。
3. 权限模块:从后台用户到门店导购的细粒度控制
3.1 为什么没直接套Spring Security
权限模块我纠结过一阵子。Spring Security功能全,社区资料多,但配置重,注解表达式复杂,对一个小型后台管理系统来说有点杀鸡用牛刀。我最终选了SA-Token,原因是它在Token鉴权这个场景下足够轻量,API直观,集成Spring Boot只需要三步配置。
系统内角色分四类:系统管理员、店长、导购、仓库管理员。这四类角色对同一份数据的可见范围完全不同。系统管理员能看全部门店的数据,店长只能看自己门店全部订单和导购业绩,导购只能看自己开的单和自己的客户会员信息,仓库管理员只能操作出入库和库存查询,没有查看销售业绩的权限。
如果只用角色权限控制"能不能访问某个接口",粒度太粗。比如店长和导购都能访问订单列表接口,但店长能看到整店订单,导购只看到本人订单。这就需要数据权限层的支持。SA-Token在登录时把角色和门店ID写入会话,在服务层校验时读取当前用户的门店ID做数据过滤,一张门店维度就完成了数据隔离。
3.2 登录态刷新与异地登录的处理细节
游泳用品门店有个实际场景:收银电脑白天挂机,导购轮班,经常出现一个账号在后台被顶下线,但收银机器的旧会话还没失效的问题。SA-Token默认的Token机制下,同账号重复登录默认不互踢,这容易造成操作日志归属混乱。
我开启了单点登录模式,同一账号新登录后旧Token直接失效。导购换班必须重新登录,每一次卖货操作都能追踪到具体责任人。同时登录时用is-concurrent参数做了并发控制,每位导购最多允许一个设备登录,后台操作记录里会记录登录IP和登录时间,方便店长回溯异常操作。
在会话超时上我做了一个比较猥琐的配置:后台管理端的Token有效期为8小时,但没有任何操作时30分钟自动过期。这样既保证午休回来还能继续操作,又避免导购下班忘关电脑导致隔天Token仍有效。自动续期这功能没加,宁可让用户重新登录一次,也不要让未授权会话长期挂在收银机上。
4. 商品资料与上架流程:用事务保证多图片加规格加SKU一次成功
4.1 商品发布页背后的事务边界
游泳用品的商品发布流程是这样的:填写基本信息(名称、类目、品牌)、按规格模板添加规格项和候选值、系统生成SKU列表后逐个填写价格库存、上传商品轮播图和详情图、最后上架。这个流程在界面上分成三步,但在后端是放在同一个事务里的。
事务边界从创建商品主记录开始,到全部SKU生成完毕结束。中间任何一步失败,商品主记录和已生成的SKU数据全部回滚。我在这一步用了一个比较谨慎的设计:商品创建和SKU生成走同一条事务链路,不在Controller层做多段提交,由Service层统一控制事务边界。
有不少初学者习惯在Controller里做一串操作,觉得每调一次Mapper就自动提交一次没问题。但Spring Boot的自动提交只在无事务时生效,一旦外层方法标注了@Transactional,内层的更新操作全部挂起,直到外层方法走完才一并提交或回滚。实际开发中我遇到过这样一个问题:商品主记录创建成功,SKU生成时因为某个规格值重复导致异常,但异常被某层代码给吞掉了,结果数据库里出现了一款没有SKU的空壳商品,前端怎么查都查不到。
这就是典型的示例代码和真实需求差距。商品发布必须保证原子性,我的建议很简单:Controller只接收参数,业务处理全放在带@Transactional的Service方法里,不允许在任何地方裸调Mapper,所有SQL操作统一走Service层。
4.2 图片上传本地上传还是OSS的取舍
图片上传这个问题,在一开始就纠结了半天。用OSS省心,但涉及到配置成本、流量费用和内外网传输延迟,如果是校内项目或者小门店私有化部署,没必要。所以我先做了本地上传,图片存服务器指定目录,数据库里只存相对路径,通过Nginx映射静态资源访问。
选择本地上传的另一个原因是商品图片往往需要本地快速预览,部署环境是内网服务器时,上传和访问都在局域网内,直接走本机磁盘速度最快。如果以后系统要对外开放或图片量变大,再切换到OSS也不费劲,只需要替换存储策略实现,数据库路径字段不需要改动。
商品详情图这块要注意的是图片压缩。泳衣泳镜的商品图原图动辄几MB,收银台加载商品列表时如果一次性加载几十张原图,页面直接卡死。我在上传时用Java自带的ImageIO做了等比压缩,默认把宽度压到800像素内,JPEG质量系数调到0.8,实测商品列表页的图片体积平均缩小了75%以上,加载速度肉眼可见提升。
4.3 上架状态和缓存同步的联动设计
商品上架和下架不是简单地改一个状态字段。商品列表页和收银台商品检索会优先查Redis缓存,商品上下架时如果只更新数据库不更新缓存,客户在收银台还能看到已下架的商品,下单时会报"商品已失效"。这个问题的根因在于状态变更和缓存更新不在同一个时序里。
我的处理方式是:上下架操作统一走一个状态变更接口,接口内先更新数据库状态,再主动删除相关的商品缓存Key,由下一次查询触发重建。这样虽然多了一次缓存穿透,但保证了数据一致性,换来的是逻辑上的绝对简单。不采用先删缓存再更新数据库的顺序,因为这两个操作不是原子的,中途一旦发生查询,查到的就是旧数据。
这里还有一个容易忽略的细节:SKU级别的上下架。泳镜的某个度数缺货但其他度数正常时,只需要下架对应SKU,而不是整个商品下架。所以SKU表里单独加了状态字段,商品列表默认展示启用状态下的SKU,收银台搜索时也会过滤掉停用SKU,避免下单选到一个当前不可售的规格。
5. 订单与库存的并发处理:先锁库存再扣库存的方案演进
5.1 直接改库存字段的问题在哪
最原始的扣库存写法是这样:查询SKU库存,判断库存大于下单数量,然后执行UPDATE语句把库存减掉。这个逻辑在并发量低的时候没问题,但收银高峰期两个客户同时买同一个热销款泳镜,两个请求同时读到库存是5,都判断5大于要买的2,然后都执行减2,库存最终变成3而不是1。这就是经典的超卖问题。
问题的本质在于,判断库存和扣减库存之间不是原子操作。两个请求的读取和更新之间存在间隙,在这个间隙里数据被并发修改了。解决思路就是把判断和扣减合并成一个原子操作,或者用锁把这段临界区保护起来。
MySQL的单行UPDATE其实是一个行级锁操作,如果绕开应用层的判断逻辑,直接用UPDATE sku SET stock = stock - #{count} WHERE id = #{skuId} AND stock >= #{count},这条SQL本身就包含了库存充足性校验和原子扣减,不需要先SELECT再UPDATE。但这种方式有一个隐患:每次下单都直接修改数据库,高频操作下数据库压力大,热卖SKU的行锁竞争会很激烈。
5.2 Redis预扣加数据库兜底的两段式方案
为了抗住收银高峰期的并发压力,我最终采用了Redis预扣加数据库兜底的两段式方案。下单时先在大库存池中锁定库存,确认锁定时才进入订单创建流程,同时用Lua脚本保证扣减动作的原子性,避免并发预扣导致的超卖:
if (redis.call('exists', KEYS[1]) == 1) then local stock = tonumber(redis.call('get', KEYS[1])) if (stock >= tonumber(ARGV[1])) then redis.call('decrby', KEYS[1], ARGV[1]) return 1 end return -1 end return -1这段Lua脚本做的事情很直接:判断缓存中该SKU的剩余可售库存是否够,够就扣减并返回1,不够返回-1。Lua脚本在Redis服务端是原子执行的,不会存在并发环境下两个请求同时读到同一个库存值的情况。这里没用更复杂的hash结构,因为单值存储对这个场景已经足够了。
订单创建成功后,异步任务把Redis中扣掉的库存同步到MySQL数据库,同时记录库存流水。如果异步任务失败,会有定时对账任务扫描Redis和MySQL的库存差异,以Redis实际扣减数为准做补偿更新。这个对账任务我设置了每五分钟跑一次,实际运行中确实抓到过几次Redis与数据库不一致的情况,基本全是网络抖动导致异步写库失败,补偿机制都能正确修复。
5.3 超时未支付订单的自动关闭机制
线上订单和收银订单不一样,收银当场付款当场拿货,线上订单会存在用户下单后不付款的情况。游泳用品门店有个特点,端午前后泳具销量暴增,热销款库存本来就紧,如果被无效订单锁住库存,真正想买的用户反而买不到。
超时关单我用的是延迟关闭方案。订单创建时在订单表写入过期时间,每三十秒扫一次,把超过三十分钟未支付的订单状态改为已取消,同步释放Redis中预扣的库存。为了释放库存时不扣多,释放操作走的是Lua脚本的INCRBY命令,和下单扣减对称。
这个方案的缺点是定时任务存在扫描延迟,最坏情况下会有30秒的库存锁定空窗。但对游泳用品门店的订单量来说完全够用,而且实现和维护成本极低。如果真要做到秒级关单,可以用RabbitMQ延迟队列或Redisson的延迟队列实现,但复杂度会明显增加,短期内性价比不高。
5.4 超卖问题的兜底软件设计
即便做了Redis预扣,也不能完全信任缓存数据。缓存和服务端的数据一致性在异常场景下可能被破坏,比如Redis持久化配置不当重启丢数据,或者对账任务还没跑而缓存已经错乱。
我在创建订单前加了一道最终的数据库校验:订单提交事务里,除了扣减Redis预扣数,还要对MySQL库存做一次乐观锁更新,用版本号字段控制,更新失败即判定库存不足,终止下单。这套兜底逻辑看起来冗余,但正是这层双保险让我在后续压测时心里有底。压测数据800并发同时抢购同一个SKU,最终未出现一个超卖订单。
6. 仓库端和收银端的关键操作链路
6.1 采购入库、盘点与库存调整的操作流设计
游泳用品门店的仓库操作有自己的规律。夏初大批量进泳衣泳镜泳帽,淡季则零星补货。仓库管理员的日常操作主要有三块:采购入库、库存盘点、库存调整。每块都写进了库存流水表,让每一步操作可追溯。
采购入库时,货到后仓管先扫商品条码,系统自动识别SKU并显示规格信息。仓管填实际到货数量,和采购单数量对不上时可以选择原因:破损、短缺或多余,系统按实际数据入库,差异数单独记录。这个动作为后续供应商结算提供了准确的入库依据。
库存盘点按月进行。盘点的流程是先做库存冻结,锁定盘点周期内的出入库操作,然后仓管对照实物清点,录入系统,系统计算盘盈盘亏数。这里有个实际问题,库存冻结期间如果刚好赶上线上订单或者门店调拨,会造成业务中断。我最后的处理方案是不做全量冻结,改为对单个SKU做盘点锁——正在盘点的SKU暂停出入库,其他SKU不受影响,业务影响范围最小化。
库存调整这个功能权限控制很严。正常调拨走调拨单流程,只有盘点差异处理或商品破损报废才允许直接做库存调整。每一次调整必须填写原因和关联单据编号,调整操作需要店长审批才能生效。
6.2 收银台的快速开单设计
收银台是门店每天使用频率最高的界面,操作体验直接决定导购的工作效率。我把收银台设计成了极简布局:左侧商品栏,支持扫码枪扫码和关键词搜索;右侧购物车,显示当前商品列表和合计金额;底部结算按钮,一次点击进入收款页。
扫码这块很多开发容易忽略,扫码枪本质是键盘输入设备,扫一次条码就相当于在输入框快速打一串字符再敲回车。不能把监听焦点放在底层全局键盘事件上,否则浏览器页面上其他输入框都会收到误输入。我的处理是在收银台页面挂一个全局Keydown事件,只在商品扫码框获得焦点时把扫码内容送到购物车,扫码框失焦时扫码功能自动禁用,避免导购在别的输入框里打字时被扫码枪事件干扰。
结算环节支持现金、微信、支付宝和会员储值四种支付方式。现金收款需要记录实收和找零,微信支付宝则对接了支付接口,但前期为了快速上线,支付接口走的是手动确认到账模式——顾客扫码付款后,导购在收银台手动确认。等流水稳定后再接入支付回调自动确认,这个思路能避免开发早期被支付联调拖住进度。
6.3 会员储值和折扣如何在一单里计算
游泳用品店的老客户粘性很强,办卡储值很常见。会员模块我设计了两个维度:储值余额和折扣等级。折扣等级分金牌、银牌、普通三档,金牌85折,银牌95折,普通无折扣。结算时先按商品原价计算总金额,再按会员折扣算出优惠金额,最后从储值余额扣款。
这里有一个顺序问题需要注意:是先打折再减储值,还是用完储值再打折?我的实现是先计算折扣后的应付金额,再用储值余额抵扣,余额不足时差额走现金或扫码。如果反过来先扣储值再打折,会员等于双重享受,门店要亏。
同时,购买特定类目的商品是否能享受折扣也是有差异的。比如特价清仓的泳镜不打折,新款正价的游泳衣才参与折扣。这个规则我在SKU上加了"参与折扣"标记和"特价标记",收银结算时先剔除特价商品,再有折扣的商品参与折扣计算。这种业务规则如果写死在代码里,后续调整定价策略非常痛苦,维护成本高,所以我所有折扣规则都放在了数据库配置里,由店长在后台直接维护。
7. 开发中踩过的坑:金额精度、ID精度、事务失效、缓存穿透
7.1 Long类型ID传到前端丢失精度问题
Spring Boot默认使用Jackson做JSON序列化,后端实体类主键是Long类型时,前端JavaScript解析JSON时会丢失精度。游泳用品门店的商品和订单ID正常情况下都不会超过JS的安全整数范围,但订单号是用雪花算法生成的19位整数,前端拿到后会变成末尾几位全是0的状态。
这问题排查了很久。前端点击订单详情跳转时,URL里带的订单号已经变成了错误的数字,后端按错误ID查询自然查不到,页面就白屏。最后在Jackson的全局配置里,把所有Long类型统一序列化为String,前端拿到的订单号就是字符串,跳转时原样传递,后端再转成Long查询。
7.2 金额计算必须用BigDecimal的直接教训
游泳用品有泳镜整件和配件之类的零散商品,订单里经常出现小数金额。最早我图省事,部分金额计算用了Double,结果对账时发现几毛钱的差异,查了半天发现是浮点运算的精度丢失。
具体场景是:某副泳镜99元,会员银牌95折后是94.05元,再用储值余额全额抵扣。看起来简单的三次运算,如果用Double算,94.049999999这种值就出现了。我最终的规则是前端展示金额一律用字符串,后端金额运算全部以分为单位用整数运算,或者用BigDecimal的精度构造。下单时金额计算统一在服务端完成,前端传入的金额只作展示,不参与计算。
7.3 同一个类内部调用事务方法失效
这个坑是当初给商品上下架接口加事务时踩的。自己写的Service里有一个商品下架方法,内部调用同类的另一个更新库存方法,方法上标了@Transactional。结果实际运行时,整个更新流程没有走事务,库存明明更新失败却返回了成功。
原因是Spring的事务是AOP代理实现的,只有外部调用才会经过代理对象,类内部方法之间的this调用绕过了代理,事务注解自然不生效。解决办法是把需要事务保证的方法拆到另一个Service里,从外部注入后调用,或者通过AopContext.currentProxy()取得当前代理对象再调用。这个问题在SSM阶段和Spring Boot阶段都是高频雷区,但凡多个数据库写操作在一个方法内串行执行,最好认真确认事务有没有真正生效。
7.4 热点SKU的缓存穿透和雪崩
游泳用品的热销款很集中,夏天防晒泳镜、儿童泳圈这类爆款在周末会突然被大量浏览。缓存Key过期瞬间,如果大批请求同时落到数据库,数据库连接池很容易被打满。我第一次压测时没考虑这一点,缓存过期的瞬间数据库连接池满,所有请求全部超时。
针对热点数据,我的方案是三管齐下。第一,热销SKU的缓存Key过期时间设置为固定值加一个随机偏移,避免大量Key同时过期;第二,数据库查询为空时也缓存一个空值,防止有人高频请求不存在的SKUID造成穿透;第三,在Service入口加一个简易的限流器,同一SKU每一秒超过100次查询时直接返回默认库存信息,不再下探数据库。这三个措施加上后,再压测时数据库连接池压力明显下降。
8. 部署方案与后续扩展建议
8.1 单体应用部署的实际载体验证
这个系统我最终部署在了一台2核4G的云服务器上,MySQL、Redis、Spring Boot应用全部在这台机器上运行。很多人觉得数据库、缓存、应用混装不专业,但实际起步阶段这是最务实的选择。我做了压测,50个虚拟用户同时在线操作,包含浏览商品、下单、支付确认,CPU峰值在60%左右,内存稳定在3G以内,数据库连接池维持在20个连接左右,系统运行稳定。
部署时用Docker Compose统一编排三个容器比较省心,单独写一个docker-compose.yml把MySQL、Redis和应用服务都定义好,一条命令就能拉起整套环境。MySQL的数据目录用宿主机目录挂载,避免容器重建后数据丢失。不过要注意容器内部的时区和内存限制问题,MySQL容器要加TZ=Asia/Shanghai环境变量,JVM堆大小根据服务器物理内存调整,4G内存的机器给JVM分1G堆大小比较合适,留足剩余内存给MySQL和Redis。
8.2 数据库备份的落地做法
数据是门店的核心资产,游泳用品的会员储值余额、库存账、销售记录,任何丢失都是灾难。我用crontab配合mysqldump做了全量备份,每天凌晨两点执行,保留最近14天的备份文件。恢复演练也做了两次,确认备份文件能正常导入才敢放心部署到生产。
增量备份短期没有做,原因是这个业务体量下全量备份已经能在一分钟内完成,增量备份的价值有限。如果未来数据库大小超过5G,再考虑用binlog日志做增量备份。备份文件我单独上传了一份到一个异地服务器,防止服务器磁盘损坏导致备份和数据同时丢失。
8.3 从单店走向多门店需要调整什么
这套系统目前是单店版,但之前设计时预留的门店字段让多门店扩展相对轻松。真正要多门店化,首先要调整的是数据权限,门店ID要从用户表的一个字段升级为一个完整的门店与员工关联关系;其次是库存模型,各门店库存需要独立管理,总店可以有中央仓做调拨;最后是订单归属和业绩统计,每个订单必须明确归属到产生订单的门店和导购。
批发和零售双价格体系是游泳用品专卖店很常见的新需求,同一款泳衣批发价和零售价差异很大。当前SKU表里只有一个售价字段,扩展时把它拆成零售价、批发价、会员价三列,订单表增加订单类型字段,区分销售订单和批发订单即可。小程序端的在线商城也可以列入下一步规划,后端接口不需要大改,只需要把现有订单创建接口支撑起来,加一层用户端的登录注册逻辑。
最后再分享一点个人体会。这类管理系统的实现,真正的护城河不是用了多新的技术框架,而是对业务场景的理解深度。泳镜度数规格、泳衣尺码偏差、夏日爆款并发争抢、储值折扣计算顺序,这些细节不深入门店一线,很难在设计时考虑周全。我在整个项目开发中,每次和业务方对完需求都会重新审视一次数据库表结构,宁可前期多调整几次,也别让错误的模型在后面越走越偏。
如果有同行正在做类似的系统,我建议先花时间把订单和库存的并发一致性理清楚,这部分是出问题最多、排查最痛苦的地方。先把这两块的时序流程图画明白,再动手写Controller和页面,节奏会顺很多。希望这篇分享能给正在做Spring Boot管理系统的朋友一些参考。