Springboot智能物流拼單組合系統(tǒng)設計與實現(xiàn)
一,項目簡介
如今互聯(lián)網與電子商務的飛速發(fā)展,物流服務行業(yè)也日益重要,如何提升物流服務的效率,降低物流服務的成本成為當下人們所關注的內容。大多數的傳統(tǒng)物流運輸業(yè)花費大量的金錢和人力在運輸、倉儲、配送中,因此這些傳統(tǒng)物流企業(yè)大多成本高利潤低。為了保證物流市場的健康發(fā)展,需要有效地支持物流配送,提高物流質量服務。而物流配送活動,作為連接商家和客戶的關鍵環(huán)節(jié)最應當被重視和優(yōu)化,從而避免出現(xiàn)物流配送效率低、資源浪費嚴重和配送成本高等問題,以此來提升我國物流行業(yè)的整體發(fā)展。
物流配送優(yōu)化主要可以從2個方面進行優(yōu)化。一、提高車輛的裝載問題:即車輛裝載問題(VehicleFillingProblem.VFP)。二、減少車輛運輸路徑:即車輛裝載問題(VehicleRoutingProblem.VRP)。本文將使用IDEA作為開發(fā)工具,java作為開發(fā)語言,建立貨車裝載和配送貨物的模型,通過算法對VFP和VRP同時進行優(yōu)化,得到如何進行貨物配送才能最優(yōu)的解決方案。并使用html語言,在web頁面上進行動畫展示。
目前已經研發(fā)完畢,在Web界面上演示效果佳,人機交互效果好,反應較快。同時與未采用該算法的傳統(tǒng)配送方案相比,該程序得到的運輸方案,貨車裝載率有了明顯提高和運輸路途的總里程有顯著降低。通過該程序成功實現(xiàn)了物流拼單組合系統(tǒng),有效提升物流服務的效率
二,環(huán)境介紹
語言環(huán)境:Java:? jdk1.8
數據庫:Mysql: mysql5.7
應用服務器:Tomcat:? tomcat8.5.31
開發(fā)工具:IDEA或eclipse
后臺開發(fā)技術:Springboot+Mybatis
前臺開發(fā)技術:html5 + javascript+thymeleaf
三,系統(tǒng)展示
3.1 系統(tǒng)的功能點描述
? 本系統(tǒng)為物流拼單組合系統(tǒng),主要功能有查看送貨地點,分配貨車,查看所有的路徑規(guī)劃,查看單個路徑動畫,查看所有路徑動畫。
3.2 系統(tǒng)功能的具體測試
3.2.1 查看送貨地點
(1)測試用例:
使用隨機數隨機生成200個起點一致,終點和貨物種類、數量不一致的當日訂單,生成隨機訂單程序核心代碼截圖如下圖5-1所示,庫中部分訂單數據如下圖5-2所示。

圖5?1 生成200個隨機訂單的核心代碼

圖5?2 數據庫200個訂單部分截圖
(2)測試結果:

圖5?3 查看送貨地點測試結果
(3)結果描述:
選定時間后,點擊查看送貨地點,前端向后端發(fā)送請求,后端將當日所有訂單結果返回,如下圖5-4所示:

圖5?4 前端發(fā)送請求后端返回結果圖。
? 前端解析json后,在地圖上進行標注,顯示中文名稱和需要配送的體積。
3.2.2 分配貨車
(1)測試用例:
有5種貨車,分別為體積為600,1000,1400,2200,2500,這五種貨車,和5.2.1生成的200個訂單。
測試結果:

圖5?5 查看送貨地點測試結果
結果描述:
點擊分配貨車后,前端向后端發(fā)送請求,后端返回如何進行貨車分配,前端解析后,顯示派出哪種類型的車和派出數量,并計算出滿載率并顯示在頁面上。

圖5?6 前端發(fā)送請求后端返回結果圖。
3.2.3 查看所有的路徑規(guī)劃
(1)測試用例:
?3.2.1生成的200個訂單和5.2.2得到的貨車分配
測試結果:

圖5?7 查看所有的路徑規(guī)劃部分結果
結果描述:
?? 點擊查看所有路徑規(guī)劃,向后端發(fā)送請求,如下圖5-8所示后端返回所有路徑返回,解析后顯示成文字路徑,包含了派出哪種類型的車,使用了多少的體積,起點、途經點、終點的名字和向這些地點配送了多少的體積,并將部分參數放在url中,生成超鏈接,可以通過單擊超鏈接播放動畫

圖5?8 前端發(fā)送請求后端返回結果圖
3.2.4 查看單個路徑動畫
(1)測試用例:
3.2.3得到的路徑
測試結果:

圖5?9 查看單個路徑動畫演示部分結果圖
結果描述:
? 動態(tài)的展示單次貨車配送的起點、途經點、終點名字和配送體積,三角形為模擬貨車,綠色為貨車行經路線。
3.2.5 查看所有路徑動畫
(1)測試用例:
3.2.3得到的所有路徑
測試結果:

圖5?10 查看所有路徑動畫演示部分結果圖
(3)結果描述:
動態(tài)的展示當日所有貨車配送的起點、途經點、終點名字和配送體積,三角
形為模擬貨車,綠色為貨車行經路線。
3.3 本章小結
? 本章主要講述了系統(tǒng)整體的功能點和對整體功能點的具體測試,包含測試用例,測試結果,結果分析,保證了系統(tǒng)的可用性
四,核心代碼展示
業(yè)務層具體實現(xiàn)
業(yè)務層采用springboot框架。springboot繼承了spring的優(yōu)秀特性:控制反轉,面向切面編程等,同時能幫助我們簡化開發(fā),減少XML的配置。
業(yè)務層主要通過調用持久層的接口獲取存在數據庫中的某個日期的訂單集合。查詢訂單集合。根據訂單上的起始地點id和終點id關聯(lián)地點表,獲得起始地點和終點的經緯度。根據貨物id關聯(lián)貨物表可以得到貨物的單位體積,與訂單上的貨物數量相乘就可以得到該訂單需要配送的體積。從貨車表知得知有哪些類型的貨車可以使用,得到了以上數據,就能解決我們的第一個問題:貨物裝載問題。
使用Map作為存儲的數據結構,key值為當前的體積,value值為構成這個體積的最少用車集合,實現(xiàn)動態(tài)規(guī)劃求解貨物裝載問題的具體過程為先初始化一個map,key為0,value為””,循環(huán)遍歷這個map的key,如果存在值,用這個值加貨車的體積,就是新key,判斷新key是否已經有value,如果沒有value為舊key的value+貨車的體積,中間用”,”分隔,如果有,將這個新value和老value進行比較,看哪一種“,”少,因為,少說明用車少,成本少,加到key值超過我們需要的貨物總體積后遍從換到下一個貨車,直到把貨車遍歷完,拿到最小值,取他的value就是我們的裝配方案,關鍵代碼如下圖3-4,3-5所示:

圖3-4 解決裝配問題的核心算法

圖3-5 解決裝配問題的核心算法
?? ?拿到貨車的具體安排后,根據具體安排和從數據庫拿到的各種數據,即可使用區(qū)域劃分解決本系統(tǒng)的路徑規(guī)劃問題,具體解決方案如下:
?? 使用Map作為存儲地點區(qū)域的集合,key值為角度值,例如”0,10”表示是與起點夾角(與x軸正向所成的角度),value值為地點區(qū)域的集合,一個鍵值對就表示與起點夾角為幾度到幾度的區(qū)域內的所有配送點。將從數據庫拿到的所有地點放入到Map中后,通過遍歷map的key,得到key所對應的value,得到當前區(qū)域的該配送的體積,如果大于起送體積,就通過計算各個點之間的距離,將各個點進行排序,根據順序開始配送,如果小于就換成下一個key,當key遍歷完成后,查看是否有剩余貨物沒被配送,若沒有則結束,若有則將角度加大10度,重復上述操作,直到貨物全部配送完全為止。關鍵代碼,如下圖所示:

圖3-6 解決區(qū)域劃分問題的核心算法

圖3-7 解決區(qū)域劃分問題的核心算法
package com.dwy.logistics.controller;import com.dwy.logistics.model.dto.front.CarFrontDTO;import com.dwy.logistics.model.dto.front.PlaceFrontDTO;import com.dwy.logistics.model.dto.front.RouteFrontDTO;import com.dwy.logistics.model.dto.front.TransportFrontDTO;import com.dwy.logistics.service.IFrontService;import com.dwy.logistics.service.IOrdersService;import lombok.extern.slf4j.Slf4j;import org.springframework.format.annotation.DateTimeFormat;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;import java.util.Date;import java.util.List;/**
* @Author: znz
* @Date: 2022/8/12 15:42
*/public class FrontController { ? ?
? ?IFrontService frontService; ? ?
? ?public List<PlaceFrontDTO> getPlaceFrontDTO({ ? ? ? ? Date date)return frontService.getPlaceFrontDTO(date);
? ?} ? ?
? ?public ?List<CarFrontDTO> getCarFrontDTO({ ? ? ? ? Date date)return frontService.getCarFrontDTO(date);
? ?} ? ?
? ?public ?List<TransportFrontDTO> getTransportFrontDTO({ ? ? ? ? Date date)return frontService.getTransportFrontDTO(date);
? ?} ? ?
? ?public ?List<RouteFrontDTO> getRouteFrontDTO({ ? ? ? ? Date date)return frontService.getRouteFrontDTO(date);
? ?}
}
package com.dwy.logistics.service.impl;import com.dwy.logistics.consts.CONST;import com.dwy.logistics.model.dto.distance.report.CarRouteReportDTO;import com.dwy.logistics.model.dto.distance.report.TruckRouteReportDTO;import com.dwy.logistics.model.dto.distance.PathDTO;import com.dwy.logistics.model.dto.front.PlaceFrontDTO;import com.dwy.logistics.model.dto.place.PlaceDTO;import com.dwy.logistics.service.IPlaceDTOService;import com.dwy.logistics.service.IRouteService;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Service;import javax.annotation.Resource;import java.util.List;/**
* @Author: znz
* @Date: 2022/8/4 16:52
*/public class RouteServiceImpl extends AbstractServiceImpl implements IRouteService { ? ?
? ?IPlaceDTOService placeService; ? ?
? ?public double getTrunkMinDistance(PlaceDTO startPlace, PlaceDTO endPlace, int size) { ? ? ? ?//https://restapi.amap.com/v4/direction/truck?parameters
? ? ? ?TruckRouteReportDTO truckRouteReportDTO =
? ? ? ? ? ? ? ?sendGet("https://restapi.amap.com/v4/direction/truck?key="+ CONST.GAODE_MAP_KEY+"&origin="+startPlace.getLocation()+"&originid="+startPlace.getId()+"&destination="+endPlace.getLocation()+"&destinationid="+endPlace.getId()+"&size="+size)
? ? ? ? ? ? ? ? ? ? ? ?.toJavaObject(TruckRouteReportDTO.class);
? ? ? ?List<PathDTO> paths = truckRouteReportDTO.getData().getRoute().getPaths(); ? ? ? ?return getMinDistance(paths);
? ?} ? ?
? ?public double getCarMinDistance(PlaceDTO startPlace, PlaceDTO endPlace) { ? ? ? ?CarRouteReportDTO carRouteReportDTO =
? ? ? ? ? ? ? ?sendGet("https://restapi.amap.com/v3/direction/driving?key="+ CONST.GAODE_MAP_KEY+"&origin="+startPlace.getLocation()+"&originid="+startPlace.getId()+"&destination="+endPlace.getLocation()+"&destinationid="+endPlace.getId()+"&strategy="+"10")
? ? ? ? ? ? ? ? ? ? ? ?.toJavaObject(CarRouteReportDTO.class);
? ? ? ?List<PathDTO> paths = carRouteReportDTO.getRoute().getPaths(); ? ? ? ?return getMinDistance(paths);
? ?} ? ?
? ?public double getCarMinDistance(String startPlaceID, String endPlaceID) { ? ? ? ?PlaceDTO startPlace = placeService.getPlaceDTOByID(startPlaceID); ? ? ? ?PlaceDTO endPlace = placeService.getPlaceDTOByID(endPlaceID); ? ? ? ?return getCarMinDistance(startPlace,endPlace);
? ?} ? ?
? ?public double getCarMinDistance(PlaceFrontDTO startPlace, PlaceFrontDTO endPlace) { ? ? ? ?String url = "https://restapi.amap.com/v3/direction/driving?key="+ CONST.GAODE_MAP_KEY+"&origin="+startPlace.getLng()+","+startPlace.getLat()+"&destination="+endPlace.getLng()+","+endPlace.getLat()+"&strategy="+"10";
? ? ? ?log.info("url:"+url); ? ? ? ?CarRouteReportDTO carRouteReportDTO =
? ? ? ? ? ? ? ?sendGet(url)
? ? ? ? ? ? ? ? ? ? ? ?.toJavaObject(CarRouteReportDTO.class);
? ? ? ?List<PathDTO> paths = carRouteReportDTO.getRoute().getPaths(); ? ? ? ?return getMinDistance(paths);
? ?} ? ?private double getMinDistance(List<PathDTO> paths){ ? ? ? ?double minDistance = paths.get(0).getDistance(); ? ? ? ?for (int i = 1 ; i <paths.size() ; i++){ ? ? ? ? ? ?if (minDistance > paths.get(i).getDistance()) {
? ? ? ? ? ? ? ?minDistance = paths.get(i).getDistance();
? ? ? ? ? ?}
? ? ? ?} ? ? ? ?return minDistance;
? ?}
}
package com.dwy.logistics.service.impl;import com.dwy.logistics.mapper.PlaceMapper;import com.dwy.logistics.model.dto.place.PlaceDTO;import com.dwy.logistics.model.entities.Place;import com.dwy.logistics.model.entities.PlaceKey;import com.dwy.logistics.service.IPlaceDTOService;import com.dwy.logistics.service.IPlaceService;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Service;import javax.annotation.Resource;/**
* @Author: znz
* @Date: 2022/8/5 17:52
*/public class PlaceServiceImpl implements IPlaceService { ? ?
? ?PlaceMapper placeMapper; ? ?
? ?IPlaceDTOService placeDTOService; ? ?
? ?public Place selectPlaceByID(String id) { ? ? ? ?PlaceKey placeKey = new PlaceKey();
? ? ? ?placeKey.setUid(id); ? ? ? ?return placeMapper.selectByPrimaryKey(placeKey);
? ?} ? ?
? ?public int insertPlace(Place place) {
? ? ? ?log.debug("insertPlace:"+place.toString()); ? ? ? ?return placeMapper.insert(place);
? ?} ? ?
? ?public int insertPlaceByName(String keywords, String cityName) {
? ? ? ?log.debug("insertPlaceByName,keywords:"+keywords+",cityName:"+cityName); ? ? ? ?PlaceDTO placeDTO = placeDTOService.getFirstPlaceDTO(keywords, cityName); ? ? ? ?Place place = PlaceDTOToPlace(placeDTO);
? ? ? ?log.debug("insertPlaceByName:"+place.toString()); ? ? ? ?return placeMapper.insert(place);
? ?} ? ?
? ?public int deletePlace(String id) { ? ? ? ?PlaceKey placeKey = new PlaceKey();
? ? ? ?placeKey.setUid(id); ? ? ? ?return placeMapper.deleteByPrimaryKey(placeKey);
? ?} ? ?private Place PlaceDTOToPlace(PlaceDTO placeDTO){ ? ? ? ?Place place = new Place();
? ? ? ?String s[] = placeDTO.getLocation().split(",");
? ? ? ?place.setUid(placeDTO.getId());
? ? ? ?place.setLat(Double.parseDouble(s[1]));
? ? ? ?place.setLng(Double.parseDouble(s[0]));
? ? ? ?place.setName(placeDTO.getName()); ? ? ? ?return place;
? ?}
}