存模型)
07-貨道綁定-DeviceProduct與slotNo庫(kù)存模型系列URM Ultra 商品/設(shè)備/訂單/支付/視覺 業(yè)務(wù)實(shí)戰(zhàn)篇主角設(shè)備域的「貨道綁定」——把一臺(tái)售貨柜的每個(gè)格子跟具體商品、庫(kù)存、售價(jià)綁在一起。適合人群沒寫過自動(dòng)售貨柜后端、但會(huì)一點(diǎn) Java/SpringBoot 的新手。在無人零售系統(tǒng)里商品和設(shè)備是兩件獨(dú)立的事。商品表pms_product描述「可樂是什么、賣多少錢」設(shè)備表dms_device描述「這臺(tái)柜子長(zhǎng)什么樣、在哪」。但真正做生意時(shí)你要回答的是這臺(tái)柜子的 A01 格子里放的是可樂、還剩幾瓶、按什么價(jià)賣。把「格子」和「商品」關(guān)聯(lián)起來的那張表就是本篇的主角dms_device_product我們叫它貨道綁定表。讀懂它庫(kù)存、扣減、低庫(kù)存、補(bǔ)貨全鏈路就通了一半。一、先理清三個(gè)概念設(shè)備、貨道、商品概念對(duì)應(yīng)表一句話解釋設(shè)備 Devicedms_device一臺(tái)真實(shí)的售貨柜有編號(hào)deviceNo如DEV-0001貨道/格子 Slot無獨(dú)立表柜子里的一個(gè)格子用編號(hào)slotNo表示如A01、B02商品 Productpms_product商品主數(shù)據(jù)有productId、售價(jià)、條碼注意貨道不是一個(gè)單獨(dú)的數(shù)據(jù)庫(kù)表它只是dms_device_product里的一行數(shù)據(jù)。一行「設(shè)備 貨道編號(hào) 商品」就代表「這臺(tái)柜子的這個(gè)格子里放了這種商品」。二、核心實(shí)體 DeviceProductDeviceProduct掛在dms_device_product表上字段不多但每個(gè)都有講究// 項(xiàng)目源碼device/entity/DeviceProduct.javaEntityTable(namedms_device_product)publicclassDeviceProductextendsBaseEntity{privateLongdeviceId;// 設(shè)備 ID對(duì)應(yīng) dms_device.idprivateLongproductId;// 商品 ID對(duì)應(yīng) pms_product.idprivateStringslotNo;// 貨道/格子編號(hào)如 A01privateIntegercapacity;// 貨道容量最多能放幾件privateIntegerstock;// 當(dāng)前庫(kù)存還剩幾件privateBigDecimalprice;// 設(shè)備端售價(jià)可覆蓋標(biāo)準(zhǔn)售價(jià)privateIntegerenabled;// 是否啟用 1啟用 0停用}幾個(gè)新手容易踩的點(diǎn)繼承BaseEntity自動(dòng)帶id主鍵自增、createTime、updateTime不用自己寫。price是「設(shè)備端售價(jià)」商品表里也有一個(gè)標(biāo)準(zhǔn)價(jià)pms_product.price。這里允許「同一瓶可樂A 柜賣 3 元、B 柜賣 3.5 元」——地鐵口和寫字樓定價(jià)不同很常見。capacity與stock是兩回事capacity是容量上限stock是實(shí)時(shí)庫(kù)存。補(bǔ)貨就是把stock拉回capacity。enabled是軟開關(guān)不想賣這個(gè)格子了置 0 即可不用刪數(shù)據(jù)。三、關(guān)鍵方法bindProduct 的「覆蓋語義」綁定貨道的入口是DeviceService.bindProduct。它的行為不是「新增就新增」而是存在即覆蓋——這是本篇最容易誤解的地方。// 項(xiàng)目源碼device/service/DeviceService.javapublicDeviceProductbindProduct(LongdeviceId,StringslotNo,LongproductId,Integercapacity,BigDecimalprice,Integerenabled){get(deviceId);// 校驗(yàn)設(shè)備存在否則拋 404ProductproductproductRepository.findById(productId).orElseThrow(()-newBusinessException(404,商品不存在));// 重點(diǎn)先按 deviceId slotNo 查查不到才 new 一個(gè)新對(duì)象DeviceProductdpdeviceProductRepository.findByDeviceIdAndSlotNo(deviceId,slotNo).orElseGet(DeviceProduct::new);dp.setDeviceId(deviceId);dp.setSlotNo(slotNo);dp.setProductId(productId);dp.setCapacity(capacity);dp.setStock(capacity!null?capacity:0);// ① 庫(kù)存初始化為容量dp.setPrice(price!null?price:product.getPrice());// ② 價(jià)格缺省取商品價(jià)dp.setEnabled(enabled!null?enabled:1);// ③ 默認(rèn)啟用returndeviceProductRepository.save(dp);}逐條拆解它的「覆蓋語義」① 庫(kù)存初始化為容量dp.setStock(capacity!null?capacity:0);新綁定一個(gè)貨道時(shí)如果傳了capacity就把stock設(shè)成等于capacity相當(dāng)于「滿格上架」。如果沒傳capacitystock就是 0——意味著這個(gè)格子暫時(shí)是空的。為什么這樣設(shè)計(jì)因?yàn)椤傅谝淮紊霞堋雇ǔ>褪恰赴迅褡友b滿」所以默認(rèn)庫(kù)存容量省得前端再單獨(dú)調(diào)一次更新庫(kù)存接口。② 價(jià)格缺省取商品價(jià)dp.setPrice(price!null?price:product.getPrice());這是兜底策略管理員在綁定時(shí)如果懶得填售價(jià)系統(tǒng)自動(dòng)用商品主數(shù)據(jù)的標(biāo)準(zhǔn)價(jià)。但如果他想做差異化定價(jià)比如景區(qū)柜子貴一點(diǎn)傳一個(gè)price就能覆蓋。小提示這里用的是product.getPrice()所以綁定時(shí)商品價(jià)是多少就定多少。綁完之后如果改了商品標(biāo)準(zhǔn)價(jià)已綁定的貨道價(jià)不會(huì)自動(dòng)變——這是有意為之避免改主數(shù)據(jù)把歷史定價(jià)全沖掉。③ 默認(rèn)啟用enabled不傳就是 1啟用。停用某個(gè)格子時(shí)再單獨(dú)傳 0。覆蓋而不是報(bào)錯(cuò)findByDeviceIdAndSlotNo(...).orElseGet(DeviceProduct::new)這行是關(guān)鍵同一個(gè)deviceId slotNo再次調(diào)用bindProduct不會(huì)插入第二行而是查出舊行重新賦值。所以「改商品」「改價(jià)」「改容量」都復(fù)用同一個(gè)綁定接口前端不用區(qū)分「新增」還是「編輯」。這是一種很省心的接口設(shè)計(jì)。四、綁定接口長(zhǎng)什么樣管理端管理端控制器AdminDeviceController暴露的綁定接口// 項(xiàng)目源碼device/controller/AdminDeviceController.javaPostMapping(/{deviceId}/products)publicResultDeviceProductbindProduct(PathVariableLongdeviceId,RequestParamStringslotNo,RequestParamLongproductId,RequestParam(requiredfalse)Integercapacity,RequestParam(requiredfalse)BigDecimalprice,RequestParam(requiredfalse,defaultValue1)Integerenabled){returnResult.ok(deviceService.bindProduct(deviceId,slotNo,productId,capacity,price,enabled));}調(diào)用示例基于文檔約定端口 8080POST /admin-api/device/1/products?slotNoA01productId1capacity10含義給 1 號(hào)設(shè)備如DEV-0001的A01貨道綁定 1 號(hào)商品可樂容量 10。不傳price則售價(jià)自動(dòng)取可樂標(biāo)準(zhǔn)價(jià)。五、查詢時(shí)為什么要組裝 DeviceProductView綁定表dms_device_product只存了productId不存商品名和條碼。但管理端列表、小程序商品列表都需要把「可樂」「690xxx 條碼」直接展示出來。如果每行都去JOIN商品表SQL 寫法繁瑣本項(xiàng)目用了一個(gè)內(nèi)存組裝的視圖對(duì)象DeviceProductView// 項(xiàng)目源碼device/dto/DeviceProductView.javaDatapublicclassDeviceProductView{privateLongid;privateLongdeviceId;privateStringdeviceNo;// 來自 dms_deviceprivateStringslotNo;privateLongproductId;privateStringproductName;// 來自 pms_productprivateStringbarcode;// 來自 pms_productprivateIntegercapacity;privateIntegerstock;privateBigDecimalprice;privateIntegerenabled;}組裝邏輯在toView里先拿到Device取deviceNo再拿Product取productName、barcode拼成視圖。// 項(xiàng)目源碼device/service/DeviceService.javaprivateDeviceProductViewtoView(Devicedevice,DeviceProductdp){ProductproductproductRepository.findById(dp.getProductId()).orElse(null);returnnewDeviceProductView(dp.getId(),dp.getDeviceId(),device!null?device.getDeviceNo():null,dp.getSlotNo(),dp.getProductId(),product!null?product.getName():null,product!null?product.getBarcode():null,dp.getCapacity(),dp.getStock(),dp.getPrice(),dp.getEnabled());}而listDeviceProducts就是把某設(shè)備的全部綁定行逐行toView// 項(xiàng)目源碼device/service/DeviceService.javapublicListDeviceProductViewlistDeviceProducts(LongdeviceId){Devicedeviceget(deviceId);returndeviceProductRepository.findByDeviceId(deviceId).stream().map(dp-toView(device,dp)).toList();}新手視角理解綁定表是「關(guān)系」視圖對(duì)象是「給人看的成品」。底層保持干凈的多對(duì)多關(guān)系展示層才去拼名字——這就是領(lǐng)域模型與展示模型分離。六、演示數(shù)據(jù)里長(zhǎng)什么樣項(xiàng)目啟動(dòng)時(shí)DataInitializer會(huì)灌一批演示貨道節(jié)選deviceIdslotNo商品capacitystock說明1A01可樂88滿格1A03橙汁33滿格1B02巧克力22低庫(kù)存樣例1B01紙巾11低庫(kù)存樣例注意B02巧克力capacity2, stock2B01紙巾capacity1, stock1——它們?nèi)萘勘旧砭托∷允翘烊坏牡蛶?kù)存樣例后面第 08、09 篇會(huì)反復(fù)用到。七、本篇小結(jié)貨道不是表是dms_device_product的一行設(shè)備 貨道號(hào) 商品。bindProduct是覆蓋語義存在則更新不存在才新增stock默認(rèn)容量price缺省取商品價(jià)enabled默認(rèn) 1。capacity是上限stock是實(shí)時(shí)量補(bǔ)貨就是讓stock回到capacity。DeviceProductView是展示視圖把deviceNo、productName、barcode組裝進(jìn)來避免前端二次查表。下一篇08我們順著這條線看下單后庫(kù)存怎么扣扣的時(shí)候有哪些守衛(wèi)校驗(yàn)在保護(hù)數(shù)據(jù)。系列目錄03-商品設(shè)備訂單支付視覺實(shí)戰(zhàn)篇本篇 07 貨道綁定。