產(chǎn)品溯源系統(tǒng)設(shè)計(jì)與防偽溯源碼生成實(shí)戰(zhàn))
眼下做農(nóng)產(chǎn)品這塊最頭疼的一件事就是“自賣自夸”。你說你的大米是有機(jī)種植消費(fèi)者掃碼看到一個(gè)網(wǎng)頁寫著“綠色無公害”心里其實(shí)犯嘀咕這頁面誰做的數(shù)據(jù)哪來的是不是隨便找個(gè)人開發(fā)個(gè)網(wǎng)站就能編我在接過不少溯源項(xiàng)目后發(fā)現(xiàn)農(nóng)產(chǎn)品溯源系統(tǒng)的本質(zhì)不是“做一個(gè)網(wǎng)站”而是把農(nóng)產(chǎn)品從種子到餐桌的全鏈路數(shù)據(jù)變成一條消費(fèi)者愿意相信的證據(jù)鏈。這篇就來說說怎么用SpringBoot從零搭一套真正能落地、能應(yīng)對掃碼并發(fā)、能防數(shù)據(jù)造假的農(nóng)產(chǎn)品溯源系統(tǒng)包括我實(shí)際項(xiàng)目里的表設(shè)計(jì)、溯源碼生成策略和踩過的坑。這套系統(tǒng)適合誰準(zhǔn)備做農(nóng)產(chǎn)品品牌化的種植基地、做食品供應(yīng)鏈的技術(shù)團(tuán)隊(duì)、以及接農(nóng)產(chǎn)品類目外包項(xiàng)目的開發(fā)者??赐昴隳苣米咭惶讓?shí)打?qū)嵉谋斫Y(jié)構(gòu)和核心代碼思路而不是一個(gè)概念性的“大架構(gòu)圖”。1. 農(nóng)產(chǎn)品的“身份證”從哪里來溯源系統(tǒng)要解決的真實(shí)問題1.1 產(chǎn)地、批次、流通環(huán)節(jié)三者的數(shù)據(jù)關(guān)系先理清很多第一版溯源系統(tǒng)之所以做成“假大空”是因?yàn)橹蛔隽艘粋€(gè)靜態(tài)頁面加一個(gè)二維碼——掃碼進(jìn)去是一段不變的文字描述比如“產(chǎn)自山東某基地2023年10月采收”。這種系統(tǒng)本質(zhì)上是電子宣傳冊不是溯源。真正的溯源要求每個(gè)最小銷售單元一箱蘋果、一袋米、一盒茶葉都能對應(yīng)到具體的批次、具體的產(chǎn)地環(huán)境記錄、具體的檢測報(bào)告和流通軌跡。這就引出溯源系統(tǒng)里最核心的概念溯源單元Traceability Unit。它不是商品SPU同一個(gè)商品也不是庫存SKU某個(gè)規(guī)格而是“同一時(shí)間、同一產(chǎn)地、同一生產(chǎn)批次的一批貨”。比如同一個(gè)果園同一天采摘的同一品種蘋果做成一箱5kg的規(guī)格這500箱就是一個(gè)溯源批次。消費(fèi)者買到的是這500箱里的某一箱掃包裝上的碼看到的是這整整一個(gè)批次的完整數(shù)據(jù)。所以第一個(gè)要理清的架構(gòu)關(guān)系是商品Product比如“紅富士蘋果”只是基礎(chǔ)信息。批次Batch一次種植/采收/加工的過程記錄帶唯一批次號(hào)。溯源單元TraceUnit批次下的最小追溯單位通常一個(gè)溯源單元對應(yīng)一個(gè)唯一的溯源碼。流轉(zhuǎn)記錄TraceLog這個(gè)批次從種植、施肥、采收、檢測、倉儲(chǔ)、運(yùn)輸?shù)戒N售各環(huán)節(jié)的事件流。我在項(xiàng)目里用的簡化模型是Product 1 : N Batch 1 : N TraceUnit 1 : N TraceLog。業(yè)務(wù)上操作的是批次消費(fèi)者掃的是TraceUnit的碼掃碼后通過TraceUnit找到Batch再拉出整條TraceLog鏈條。這個(gè)模型的好處是消費(fèi)者感知到的是唯一碼我們存儲(chǔ)和管理的最小開口卻是批次數(shù)據(jù)量可控不至于每個(gè)蘋果都單獨(dú)建一份生長記錄的副本。1.2 一個(gè)合格的溯源碼掃碼后到底該展示什么消費(fèi)者掃碼的典型心理是“這是不是真的能不能證明”所以掃碼結(jié)果頁至少需要分三個(gè)層次來呈現(xiàn)產(chǎn)地證明層基地名稱、經(jīng)緯度、實(shí)景照片、種植戶信息可選展示。過程證明層農(nóng)事記錄施肥、澆水、施藥、采收時(shí)間、檢測報(bào)告農(nóng)殘、重金屬、加工/包裝記錄。流通證明層出庫時(shí)間、運(yùn)輸方式、到達(dá)銷售端的時(shí)間。我們當(dāng)時(shí)還加了一個(gè)很實(shí)用的細(xì)節(jié)在掃碼頁頂部顯示“本批次已通過XX項(xiàng)質(zhì)量檢測”用一個(gè)數(shù)字抓住消費(fèi)者注意力比放一堆報(bào)告圖片有效得多。因?yàn)檎嬲龝?huì)逐張點(diǎn)開PDF看報(bào)告的用戶極少但“已通過17項(xiàng)檢測”這個(gè)數(shù)字一眼就能建立信任。提示千萬別在掃碼頁堆長圖和視頻移動(dòng)端加載慢跳出率高。實(shí)測下來圖片體積控制在200KB以內(nèi)首屏接口響應(yīng)在500ms以內(nèi)用戶掃碼后愿意繼續(xù)往下看概率會(huì)大幅提升。2. 技術(shù)選型定盤子SpringBoot MyBatis Vue為主的架構(gòu)取舍2.1 為什么選SpringBoot而不是Spring MVC或者Spring Cloud這個(gè)項(xiàng)目我選的是SpringBoot 2.7.x而不是直接上Spring Cloud原因很現(xiàn)實(shí)農(nóng)產(chǎn)品溯源系統(tǒng)的瓶頸在數(shù)據(jù)采集端和碼的防偽能力不在微服務(wù)拆分。大部分基地的日常操作場景是農(nóng)戶通過小程序錄農(nóng)事記錄、倉庫管理員掃碼出庫、質(zhì)檢員上傳檢測報(bào)告。并發(fā)量集中在掃碼查詢但查詢鏈路極其簡單按碼查批次根本用不著微服務(wù)那套注冊中心、網(wǎng)關(guān)、配置中心。SpringBoot 對這個(gè)項(xiàng)目而言最大的價(jià)值是自動(dòng)配置帶來的開發(fā)效率。你要對接MySQL、Redis、MinIO對象存儲(chǔ)存圖片、RabbitMQ可選項(xiàng)用于異步生成溯源碼PDF的時(shí)候起步成本低到可以忽略。同時(shí)它內(nèi)置的Tomcat、優(yōu)雅停機(jī)、監(jiān)控端點(diǎn)actuator啥都有對中小團(tuán)隊(duì)十分友好。2.2 持久層用MyBatis還是JPA我的選擇和后端理由我兩個(gè)都用過農(nóng)產(chǎn)品溯源這種項(xiàng)目我選MyBatis-Plus。為什么因?yàn)樗菰聪到y(tǒng)里最頻繁的操作是多條件組合查詢。比如“查2024年5月之后采收的、產(chǎn)地是XX基地的、且檢測狀態(tài)為合格的所有批次”。MyBatis-Plus的WrapperQueryWrapper/LambdaQueryWrapper可以非常干凈地用代碼動(dòng)態(tài)拼接這種查詢不會(huì)把SQL字符串碎成一地。JPA雖然也能用Specification但實(shí)際項(xiàng)目里寫起來還是覺得不夠直給。另一個(gè)原因是MyBatis對復(fù)雜SQL的控制力。比如我要做“按省統(tǒng)計(jì)批次數(shù)量”的報(bào)表SQLJPA需要寫JPQL或者原生SQL混搭MyBatis里直接寫XML后期限定索引、EXPLAIN優(yōu)化都方便。本項(xiàng)目表的關(guān)聯(lián)不算深但報(bào)表查詢有門檻XML方式更適合漸進(jìn)式優(yōu)化。2.3 前端選Vue還是服務(wù)端渲染掃碼頁單獨(dú)處理的邏輯管理后臺(tái)給基地管理員、質(zhì)檢員用的我選的是Vue 3 Element Plus因?yàn)檫@類系統(tǒng)的核心是表格、表單、狀態(tài)流轉(zhuǎn)組件化很順手。但消費(fèi)者掃碼的H5頁面我的建議是不要和后臺(tái)共用一個(gè)SPA。原因很實(shí)際掃碼頁要快首屏要輕為了一頁展示引入整個(gè)Vue全家桶vue-router、pinia、axios、打包后的ElementUI沒必要。掃碼頁是面向消費(fèi)者的鏈路越短越好。所以掃碼頁我用了很輕量的一種方式后端直接用Thymeleaf渲染一個(gè)簡單模板因?yàn)镾pringBoot天然支持。生成的二維碼內(nèi)容指向后端接口/trace/scan/{code}后端查庫后把數(shù)據(jù)塞進(jìn)模板返回一個(gè)靜態(tài)化傾向的HTML頁面。這樣掃碼首屏只依賴一個(gè)接口比啟動(dòng)一個(gè)SPA應(yīng)用快得多還少一層跨域困擾。注意如果你團(tuán)隊(duì)里前端資源充足掃碼頁做成獨(dú)立小站點(diǎn)也完全可以。但技術(shù)決策上不要讓“掃碼頁”和“管理后臺(tái)”在同一個(gè)構(gòu)建產(chǎn)物里它們生命周期不同迭代頻率完全不同強(qiáng)行耦合在一起容易互相拖累。3. 核心鏈路拆解從播種批次到掃碼查詢數(shù)據(jù)如何一步步打通3.1 第一批數(shù)據(jù)從哪里來基礎(chǔ)檔案和批次初始化系統(tǒng)真正上線時(shí)第一步不是錄入一堆溯源記錄而是先把基礎(chǔ)檔案建好?;匦畔ase_info、產(chǎn)品庫product、種植戶/農(nóng)戶farmer、檢測機(jī)構(gòu)org、倉庫warehouse這五類主數(shù)據(jù)必須先有否則后續(xù)批次關(guān)聯(lián)時(shí)根本沒法落庫。然后是初始化批次比如“2024-春季-煙臺(tái)紅富士-001批次”。這個(gè)批次號(hào)不是自然的自增ID而是有業(yè)務(wù)含義的編碼我定的規(guī)則是area_code product_code year season seq例如YT-HFS-2024-S1-001。這樣在Excel導(dǎo)出、物流單打印、人工電話核對時(shí)光看號(hào)碼就能定位到產(chǎn)地和季別。批次初始化時(shí)就要把一批默認(rèn)的溯源數(shù)據(jù)掛上去比如“種苗來源”“種植面積”“定植日期”這些是后續(xù)TraceLog的起點(diǎn)。3.2 農(nóng)事記錄農(nóng)戶端小程序數(shù)據(jù)如何進(jìn)到主庫農(nóng)戶在大棚里干完活用小程序選“施肥”填肥料名稱、用量、施用地塊、現(xiàn)場拍照提交后通過后端接口寫入farm_record表。這里有個(gè)特別容易踩的坑農(nóng)戶上傳的圖片動(dòng)不動(dòng)是原圖3MB甚至5MB以上。如果你不做壓縮一兩周后MinIO或者OSS里就堆滿了垃圾數(shù)據(jù)而且掃碼頁加載原圖移動(dòng)網(wǎng)絡(luò)下能卡到崩潰。我的處理方式是后端接文件后先存原圖到臨時(shí)bucket異步用Java的Thumbnailator庫壓縮到最長邊1280px、質(zhì)量80%再轉(zhuǎn)存正式bucket然后刪除臨時(shí)文件。接口返回給前端的是壓縮后的URL。這個(gè)環(huán)節(jié)雖然不起眼但是真正影響體驗(yàn)的細(xì)節(jié)。農(nóng)事記錄落到庫里之后根據(jù)肥料類型/施藥類型自動(dòng)打標(biāo)到批次上如果本次是施藥得分錄是不是有“安全間隔期”字段消費(fèi)者端展示“距采收還有N天/已過安全期”這個(gè)細(xì)節(jié)比單純展示“打過藥”要好得多也算體現(xiàn)專業(yè)度。3.3 檢測報(bào)告與加工包裝狀態(tài)機(jī)設(shè)計(jì)比想象中重要溯源鏈條里的數(shù)據(jù)狀態(tài)不是隨手改改就行必須有一個(gè)狀態(tài)機(jī)約束。這是我做了幾單項(xiàng)目后才真正意識(shí)到的問題。比如一個(gè)批次的“檢測報(bào)告”和“包裝記錄”在業(yè)務(wù)上是有順序的先檢測合格才能開始包裝。如果設(shè)計(jì)上允許直接錄入包裝記錄很容易出現(xiàn)“檢測還沒出結(jié)果貨就包裝好了”的邏輯漏洞。我設(shè)計(jì)的核心狀態(tài)機(jī)是1 種植中只能添加農(nóng)事記錄、環(huán)境記錄。2 待檢測采收后進(jìn)入可上傳樣品信息和檢測報(bào)告。3 已檢測檢測通過可以打碼、包裝、生成溯源單元。4 在庫包裝完成入倉庫。5 流通過程出庫后每筆掃碼/收貨核銷記錄都被視為物流節(jié)點(diǎn)。6 已完成/異常終止售罄或者出現(xiàn)質(zhì)量追溯問題被強(qiáng)制終止。狀態(tài)變更統(tǒng)一走一個(gè)batch_status_log表記錄誰在什么時(shí)間把狀態(tài)從A改到B原因是什么。溯源系統(tǒng)本身就是為了“可信”所以審計(jì)日志不是可有可無而是基礎(chǔ)設(shè)施。消費(fèi)者雖然只看最終結(jié)果但你真正應(yīng)對職業(yè)打假人或者監(jiān)管抽檢時(shí)這套審計(jì)日志才是保命的。3.4 消費(fèi)者掃碼查詢一個(gè)TraceUnit查詢接口背后的SQL邏輯消費(fèi)者掃碼的接口我把路徑設(shè)計(jì)成/trace/scan/{traceCode}。traceCode就是印在包裝上的溯源碼。整個(gè)查詢邏輯我用三層對象返回TraceBaseVO產(chǎn)品名、批次號(hào)、產(chǎn)地、批次圖片、檢測狀態(tài)摘要。TraceLogVO種植記錄列表、檢測報(bào)告列表、包裝記錄列表按時(shí)間倒序。TraceFlowVO出庫、運(yùn)輸、到達(dá)門店的時(shí)間點(diǎn)流水。SQL層面我用了兩次查詢而不是一次性大JOIN第一次按trace_code查到trace_unit拿到batch_id第二次按batch_id批量查日志和圖片。原因很簡單掃碼請求是高頻讀鏈路要短日志查詢是低頻讀分開查更容易做緩存。Redis里我直接把掃碼結(jié)果的JSON緩存了2小時(shí)按批次維度緩存同類產(chǎn)品短時(shí)間被反復(fù)掃時(shí)基本不會(huì)打穿數(shù)據(jù)庫。注意緩存key要按batch_id而不是trace_code設(shè)計(jì)。因?yàn)橐粋€(gè)批次下有幾百上千個(gè)碼如果按trace_code緩存同一個(gè)批次大量碼被掃等于多次重復(fù)查庫和多次重復(fù)緩存。按批次緩存后第一個(gè)碼觸發(fā)查詢后面的碼直接命中同一份緩存性能壓力驟減。4. 溯源碼生成與防偽讓二維碼背后的數(shù)據(jù)不容易被造假4.1 碼的編碼規(guī)則用不透明ID替代自增主鍵溯源系統(tǒng)一定不能把數(shù)據(jù)庫自增ID直接拼成二維碼給別人掃。比如trace_unit表的自增ID是10086二維碼內(nèi)容是http://xxx/trace/scan/10086消費(fèi)者一眼就知道這個(gè)系統(tǒng)的碼是可以遍歷的把10087輸入照樣能掃出下一條數(shù)據(jù)。所以我給溯源單元設(shè)計(jì)的編碼是20位數(shù)字字母混合的防偽碼生成規(guī)則前4位產(chǎn)品類別碼如YTFS——煙臺(tái)富士后16位基于IdWorker雪花算法生成的ID做Base32編碼生成后寫入trace_code字段并加上唯一索引。雪花算法的好處是全局唯一、趨勢遞增而且在分布式環(huán)境下不用額外依賴中心發(fā)號(hào)器對溯源這種跨基地部署的場景很合適。4.2 防偽查詢次數(shù)提示這是溯源系統(tǒng)最容易被忽略的細(xì)節(jié)真正做過溯源系統(tǒng)的人都會(huì)告訴你不要只做“第一次掃碼顯示詳情”還要做防偽提示。我實(shí)際項(xiàng)目的邏輯是trace_unit表里有個(gè)scan_count字段。消費(fèi)者掃碼后后端在返回詳情前先做一次自增。判斷規(guī)則scan_count 1顯示“正品溯源信息首次查詢”。scan_count 1顯示“該溯源編碼已被查詢N次請確認(rèn)包裝完好”。這個(gè)機(jī)制雖然是簡單計(jì)數(shù)但在實(shí)際防偽場景里很有震懾力。消費(fèi)者不會(huì)去研究你的數(shù)據(jù)庫設(shè)計(jì)他看到“已被查詢3次”這個(gè)字眼再結(jié)合包裝實(shí)際情況自己就會(huì)判斷是否遇到二次包裝。我對這個(gè)功能還有個(gè)小忠告千萬別做得太極端比如碼被掃了兩次就報(bào)警因?yàn)橥粋€(gè)消費(fèi)者完全可能反復(fù)掃同一個(gè)碼。給一個(gè)溫和提醒的閾值即可。4.3 二維碼打印別在包裝環(huán)節(jié)掉鏈子后端的碼生成只是第一步真正封裝印刷時(shí)問題才多。最常遇到的就是包裝廠拿到的PDF二維碼和實(shí)際URL對不上或者碼被拉伸變形掃不出來。建議二維碼圖片統(tǒng)一用后端接口生成2400x2400px的PNG交付給包裝廠并打上“掃碼驗(yàn)證H5不依賴網(wǎng)絡(luò)環(huán)境”的說明其實(shí)需要網(wǎng)絡(luò)但至少不要被微信內(nèi)置瀏覽器攔截。另一個(gè)坑是二維碼的糾錯(cuò)級(jí)別我習(xí)慣用H最高糾錯(cuò)級(jí)別因?yàn)檗r(nóng)產(chǎn)品包裝袋/紙箱在運(yùn)輸過程中必定會(huì)有摩擦、污損糾錯(cuò)級(jí)別高的碼在部分遮擋的情況下依然可以掃出來。這個(gè)選擇犧牲了一點(diǎn)碼密度但換來的是終端的識(shí)別率。5. 數(shù)據(jù)庫設(shè)計(jì)實(shí)戰(zhàn)一張“流水主表”撐起整個(gè)溯源鏈條5.1 核心表的字段清單和關(guān)系說明下面直接給關(guān)鍵表結(jié)構(gòu)整理過的核心字段不是完整版base_info基地表id, name, address, lng, lat, cover_img, contact_name, contact_phone, status。product產(chǎn)品表id, name, category_id, spec, unit, origin_id, brand, desc。batch溯源批次主表id, batch_no, product_id, base_id, plan_qty, real_qty, harvest_date, status, status_reason。trace_unit溯源單元/溯源碼表id, batch_id, trace_code, qr_img_url, scan_count, first_scan_time, create_by, create_time。farm_record農(nóng)事記錄表id, batch_id, record_type, record_date, content, pesticide_name, dosage, safety_interval_day, operator, img_list。detect_report檢測報(bào)告表id, batch_id, report_no, org_name, report_date, result, pdf_url, items_json。trace_log流轉(zhuǎn)日志表id, batch_id, node_name, operator, operate_desc, img_list, location_name, create_time。batch_status_log批次狀態(tài)變更審計(jì)表id, batch_id, from_status, to_status, operator_id, reason, create_time。這張?jiān)O(shè)計(jì)里最關(guān)鍵的是溯源單元表的trace_code有唯一索引一切查詢都能在200ms內(nèi)完成批次表的狀態(tài)有索引列表頁的篩選不會(huì)因?yàn)闋顟B(tài)條件而全表掃描。5.2 為什么不用區(qū)塊鏈也敢說“數(shù)據(jù)難篡改”一說溯源很多人馬上想到區(qū)塊鏈。但實(shí)際做項(xiàng)目我會(huì)坦誠地說現(xiàn)階段中小型農(nóng)產(chǎn)品溯源區(qū)塊鏈更多是加分項(xiàng)而不是必需項(xiàng)。原因很簡單你基地內(nèi)部錄入數(shù)據(jù)的人是一樣的鏈上鏈下如果數(shù)據(jù)源在源頭就是人手工錄入的區(qū)塊鏈只能保證“上鏈后沒人改”不能保證“錄入時(shí)就是真的”。那區(qū)塊鏈的真實(shí)價(jià)值在哪在于多機(jī)構(gòu)間互換信任。如果未來消費(fèi)者掃碼時(shí)數(shù)據(jù)來源于“某政府平臺(tái)存證 基地自錄 物流公司軌跡”等多方節(jié)點(diǎn)區(qū)塊鏈的共識(shí)機(jī)制才有實(shí)際意義。在這個(gè)單體的SpringBoot項(xiàng)目里我采用了一個(gè)折中方案對關(guān)鍵數(shù)據(jù)檢測報(bào)告、批次狀態(tài)變更、首次掃碼時(shí)間計(jì)算MD5哈希存到獨(dú)立的hash字段里并定期把一批哈希匯總輸出成不可篡改的表單歸檔。這個(gè)方案成本很低但至少能應(yīng)對“你們系統(tǒng)數(shù)據(jù)是不是隨便改”的質(zhì)疑。5.3 如何設(shè)計(jì)索引避免掃碼查詢打爆數(shù)據(jù)庫開發(fā)者剛做完這類系統(tǒng)時(shí)最容易出現(xiàn)的問題是掃碼高峰期trace_unit全表掃碼導(dǎo)致數(shù)據(jù)庫CPU飆升。我的實(shí)際解決方案是trace_code建唯一索引必須。batch_idstatus建聯(lián)合索引。farm_record.detect_report里的batch_id建普通索引。列表頁SQL用LIMIT分頁禁止大偏移量OFFSET 100000這種方式。我在壓測時(shí)模擬一個(gè)批次2000個(gè)碼高頻掃碼QPS到200左右時(shí)數(shù)據(jù)庫無壓力瓶頸反而出在文件服務(wù)器上圖片加載。所以后來做了圖片CDN加速靜態(tài)資源全部走獨(dú)立域名避免和生產(chǎn)環(huán)境接口爭帶寬。6. 實(shí)操中最容易翻車的幾個(gè)點(diǎn)我的踩坑記錄和解決過程6.1 時(shí)間字段的“時(shí)區(qū)幽靈”項(xiàng)目上線第一天就出了個(gè)很低級(jí)的bug農(nóng)戶在山東下午5點(diǎn)錄的農(nóng)事記錄后臺(tái)看是第二天的凌晨1點(diǎn)。查了半天是前端傳的時(shí)間字符串沒帶時(shí)區(qū)后端用LocalDateTime.parse解析時(shí)默認(rèn)按服務(wù)器時(shí)區(qū)服務(wù)器是UTC就多了8小時(shí)偏差。修復(fù)方案全局約定所有時(shí)間以字符串格式帶時(shí)區(qū)傳輸統(tǒng)一ISO86012024-05-10T17:00:0008:00后端接受后轉(zhuǎn)LocalDateTime存儲(chǔ)。展示層一律按東八區(qū)格式化后再輸出。這個(gè)坑不復(fù)雜但在溯源場景里影響特別大因?yàn)橄M(fèi)者掃碼看到“施肥時(shí)間 2024-05-11 01:00”第一反應(yīng)就是這數(shù)據(jù)是瞎編的。6.2 批次狀態(tài)流轉(zhuǎn)時(shí)前端并發(fā)點(diǎn)擊導(dǎo)致狀態(tài)錯(cuò)亂入庫操作時(shí)倉庫管理員快速連點(diǎn)兩次“確認(rèn)入庫”后端并發(fā)執(zhí)行兩次都把狀態(tài)改成了“在庫”而且中間插入了兩條重復(fù)的入庫日志。原因是我當(dāng)時(shí)只用了一個(gè)狀態(tài)字段校驗(yàn)沒有事務(wù)鎖。修復(fù)方式是給批次表加了一個(gè)樂觀鎖版本號(hào)version字段每次更新時(shí)update batch set status3, versionversion1 where id? and versionoldVersion。影響行數(shù)為0時(shí)說明版本沖突重放查詢后提示用戶“狀態(tài)已更新請刷新重試”。同時(shí)在入庫日志插入前用batch_id node_name operate_date做了唯一索引約束雙重保險(xiǎn)。6.3 包裝廠拿不到碼溯源碼導(dǎo)出的權(quán)限和格式設(shè)計(jì)對接包裝廠時(shí)你肯定不希望業(yè)務(wù)人員直接從庫里導(dǎo)出裸數(shù)據(jù)。我們做的方案是后臺(tái)“碼管理”頁面按批次選擇要導(dǎo)出的碼數(shù)量系統(tǒng)會(huì)生成一個(gè)加密的PDF壓縮包里面按包裝規(guī)格排版好二維碼。壓縮包同時(shí)設(shè)置了打開密碼由管理員線下告知廠方。PDF排版用Java的iText庫生成每一頁有批次號(hào)、產(chǎn)品名、頁碼水印。這個(gè)模塊剛上線時(shí)有個(gè)設(shè)計(jì)失誤沒有限制單次導(dǎo)出數(shù)量結(jié)果有人一次性導(dǎo)了10萬個(gè)碼直接把PDF生成接口卡死了。后來加了個(gè)限制單次導(dǎo)出不超過5000個(gè)且生成任務(wù)走異步線程池完成后通過郵件推送下載鏈接。6.4 圖片上傳重復(fù)對象導(dǎo)致存儲(chǔ)膨脹前面提到過壓縮圖片還有個(gè)問題是同一個(gè)批次下農(nóng)戶和質(zhì)檢員可能上傳同一張現(xiàn)場照片微信傳到手機(jī)再上傳落庫后存了兩份。后來我在上傳接口里加了文件MD5去重同一個(gè)MD5在bucket中已存在時(shí)只記錄引用不重復(fù)存儲(chǔ)。這個(gè)優(yōu)化在運(yùn)營半年后查存儲(chǔ)量時(shí)效果非常明顯大約省了三分之一的空間。6.5 移動(dòng)端弱網(wǎng)場景下掃碼頁面的降級(jí)方案農(nóng)產(chǎn)品溯源的消費(fèi)者經(jīng)常是在菜市場、路邊攤掃碼網(wǎng)絡(luò)條件不比辦公室。如果后端接口超時(shí)前端如果直接白屏體驗(yàn)極差。我加的降級(jí)方案是后端接口設(shè)置快速失敗連接超時(shí)2秒、讀超時(shí)3秒。如果查詢失敗模板頁仍渲染基礎(chǔ)產(chǎn)品名和批次號(hào)并顯示“溯源信息加載失敗請稍后重試”。前端埋點(diǎn)監(jiān)聽錯(cuò)誤碼方便統(tǒng)計(jì)哪個(gè)批次掃碼頁失敗率高及時(shí)排查。7. 項(xiàng)目上線后要盯的數(shù)據(jù)這些指標(biāo)比“功能做完了”更重要系統(tǒng)交付以后維護(hù)階段比開發(fā)階段更見功力。我給自己定的三個(gè)核心觀察指標(biāo)掃碼轉(zhuǎn)化率掃碼次數(shù)/可掃碼總碼數(shù)低于30%說明消費(fèi)者對碼的興趣不大要么是碼的位置太隱蔽要么是掃碼頁面價(jià)值感不足。掃碼頁跳出率打開詳情后3秒內(nèi)關(guān)閉比例如果超過40%大概率詳情頁加載慢或者信息排版看著不專業(yè)。批次溯源完整率狀態(tài)走到“流通過程”批次的數(shù)量占比記錄不完整說明基地的操作流程沒有真正被系統(tǒng)約束住。我們有一次復(fù)盤發(fā)現(xiàn)某個(gè)茶葉基地的掃碼跳出率特別高點(diǎn)開一看是詳情頁放了4張超大尺寸的茶園實(shí)拍圖每張1MB以上移動(dòng)網(wǎng)絡(luò)下一張圖轉(zhuǎn)半天。后來前端把輪播圖改為“首圖加載 縮略圖點(diǎn)擊查看大圖”跳出率立刻降了十幾個(gè)點(diǎn)。溯源系統(tǒng)這種業(yè)務(wù)消費(fèi)者看的不是功能是信任感和體驗(yàn)這倆都得靠細(xì)節(jié)堆出來。再分享最后一個(gè)技巧也是我最常對合作伙伴說的溯源系統(tǒng)運(yùn)營半年后真正的價(jià)值不在二維碼頁面而在你手里積累的那張“批次-產(chǎn)地-檢測”數(shù)據(jù)表和消費(fèi)反饋數(shù)據(jù)。這是你做品牌故事、甚至和渠道談溢價(jià)的底氣。代碼寫得好保證的是下限數(shù)據(jù)運(yùn)營做得好才讓這套系統(tǒng)有了真正的生命。