我想,大部分人都应该有着购物的经历,当商品下单时就会出现一个物流单号,接下来几天内的物流信息会根据这个单号更新。
然后接下来的几天可能会到达不同地点,进行更新,你可能会好奇这样一个功能是如何实现,本案例就通过SpringBoot+MongoDB实现一个简易版本的物流订单系统。当然具体实现商用肯定要考虑很多细节也很复杂,本案例更侧重于功能实现和MongoDB使用。
一个订单数据是如何产生和更新的呢?首先一个订单数据由下单时产生,然后该订单经历各个物流点更新物流信息和订单状态,最后在用户取件之后订单状态更新后数据基本就不再更新了。
下单模块:我想大部分人看过寄快递下单流程或者自己下过单,核心就是一个表单页面填写寄件人姓名、地址、手机等信息和收件人姓名、地址、手机等信息。所以在这里具体实现也是填写寄件人和收件人信息储存。
物流模块 :一个订单下单后可能经历若干物流地点,最终才能到达目的地被签收。而就各个物流点来看,各个物流点的管理人员对该物流订单添加一些物流信息,例如到达地址、订单目前状态、联系方式等等。而本案例在添加物流信息的实现上也通过一个表单添加该订单的物流信息,通过物流订单的id进行联立。
实现这种数据应该如何存储?如果使用关系型数据库,就单订单物流信息存储可能至少需要使用两张表来实现,一张订单(order)信息表存储订单一些固定栏位信息,一张物流(Logistics)信息表储存动态的物流变化,通过订单id实现两张表的关联。
按照E-R图设计数据库,按照我们简洁的设计方式,其数据其中一部分的数据是这样的:
物流表中的order_id外键引用order表中的id字段进行关联。在查询订单数据的时候需要关联查询。物流订单系统确实可以使用关系数据库去实现,但是数据量过大可能会有性能瓶颈需要优化,如果采用MongoDB不仅可以提高效率,还可以使得流程变得更加简单。
订单的特点是随着递送过程,订单数据需要随时更新路径。数据结构上需要可以灵活应对,这点非常符合MongoDB的document文档模型,并且MongoDB支持GIS功能,非常适用于MongoDB来支撑物流业务(这里简易版本就不使用该功能了)。而物流行业里订单比较独立,跨订单的操作很少,创建、更新(追加)的操作会较多,物流业务模型上与MongoDB非常的匹配。本课程就是使用MongoDB实现一个物流订单系统的小例子。
SpringBoot
相信你对SpringBoot很熟悉,由于Spring的发展、微服务的发展使得SpringBoot越来越流行,已经成为JavaWeb开发的主流框架。
SpringBoot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,SpringBoot在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。
简而言之,SpringBoot是当前web开发主流,其简化了Spring的配置让开发者能够更容易上手Web项目的开发。且MongdoDB能够快速与SpringBoot整合,在项目中能够快速便捷操作MongoDB;
MongoDB
MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为web应用提供可扩展的高性能数据存储解决方案。MongoDB是一个介于关系型数据库和非关系型数据库之间的产品,是非关系型数据库当中功能最丰富,最像关系型数据库的。它支持的数据结构非常松散,是类似JSON的BSON格式,因此可以存储比较复杂的数据类型。MongoDB最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。
本案例就是基于SpringBoot和MongoDB实现一个物流订单系统的小案例,实际的物流场景需要考虑的问题肯定很多也比较复杂,这是实现一个简易版本的物流订单系统主要为了MongoDB的使用和学习。
分析完案例以及了解案例设计的知识点后,就可以一步一步开始动手实现本案例,本案例要实现的就是订单创建、订单信息更新、查询、删除的一个小型完整的物流订单管理系统。而在具体实现上按照以下步骤:
整个案例实现火热运行的环境如下:
打开Studio 3T数据库管理工具,连接本地MongoDB数据库之后,创建名为test的数据库,在test数据库中创建名为order的集合:
首先,打开IDEA创建项目,选择创建SpringBoot项目:
然后在选择Gruop和Aritifact的时候分别填写com和mongodemo,Java Version选择8版本。
在勾选模块时候,这里勾选Spring web、MongoDB依赖模块,选择合适位置创建项目,项目就可以成功创建:
创建项目之后,需要做一些前置工作预备。
创建完项目,我们需要做一些预备工作用来完成缓存。我们首先要在项目中的application.properties中添加配置连接到数据库,配置规则为:spring.data.mongodb.uri=mongodb://地址:端口/数据库名
,本案例使用本地的MongoDB数据库,默认端口为27017,而使用的MongoDB具体数据库名称为test,那么就可以按照以下进行配置:
spring.data.mongodb.uri=mongodb://localhost:27017/test
这样在项目中就可以连接到本地的MongoDB的test数据库并访问。
其次在项目中com.mongodb目录下分别创建controller,service,pojo文件夹,在controller文件夹下创建orderController
类,为负责url和逻辑的控制器:
package com.mongodemo.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class orderController {
private static Logger logger= LoggerFactory.getLogger(orderController.class);
}
其中:
orderController创建完毕后,在service 文件夹下创建orderService.java
类,里面先编写以下内容:
package com.mongodemo.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Service;
@Service
public class orderService {
private static Logger logger= LoggerFactory.getLogger(orderService.class);
@Autowired
MongoTemplate mongoTemplate;
}
其中:
service创建完成,我们需要在pojo中创建logistics类和order类,分别表示具体物流信息和订单信息。其中logistics类如下,各个字段的含义请看注释:
package com.mongodemo.pojo;
import java.util.Date;
public class logistics {
private int orderId;//订单id
private String operation;//操作
private String operator;//操作员
@JsonFormat(pattern = "yyyy-MM-dd HH:mm",timezone = "GMT+8")
private Date operationTime;//操作时间
private String address;//地址
private String details;//备注细节
public logistics(){}
public logistics(int orderId,String operation, String operator, Date operationTime, String address, String details) {
this.orderId = orderId;
this.operation=operation;
this.operator = operator;
this.operationTime = operationTime;
this.address = address;
this.details = details;
}
public int getOrderId() {
return orderId;
}
public void setOrderId(int orderId) {
this.orderId = orderId;
}
public String getOperator() {
return operator;
}
public void setOperator(String operator) {
this.operator = operator;
}
public Date getOperationTime() {
return operationTime;
}
public void setOperationTime(Date operationTime) {
this.operationTime = operationTime;
}
public String getAdress() {
return address;
}
public void setAdress(String address) {
this.address = address;
}
public String getDetails() {
return details;
}
public void setDetails(String details) {
this.details = details;
}
public String getOperation() {
return operation;
}
public void setOperation(String operation) {
this.operation = operation;
}
}
order类的内容如下:
package com.mongodemo.pojo;
import java.util.Date;
import java.util.List;
public class order {
private int id;//订单id
private String status;//状态
@JsonFormat(pattern = "yyyy-MM-dd HH:mm",timezone = "GMT+8")
private Date orderTime;//下单时间
private String shipper;//发货人
private String shippingAdress;//发货地址
private long shipperPhone;//发货人手机
@JsonFormat(pattern = "yyyy-MM-dd HH:mm",timezone = "GMT+8")
private Date shipTime;//发货时间
private String recevier;//接收人
private String recevierAddress;//接收地址
private long receviePhone;//接收人号码
private List<logistics>logistics;//物流信息
public order(int id, String status, Date orderTime, String shipper, String shippingAdress, long shipperPhone, Date shipTime, String recevier, String recevierAddress, long receviePhone, List<com.mongodemo.pojo.logistics> logistics) {
this.id = id;
this.status = status;
this.orderTime = orderTime;
this.shipper = shipper;
this.shippingAdress = shippingAdress;
this.shipperPhone = shipperPhone;
this.shipTime = shipTime;
this.recevier = recevier;
this.recevierAddress = recevierAddress;
this.receviePhone = receviePhone;
this.logistics = logistics;
}
//省略get set方法,自己补全
}
其中 @JsonFormat(pattern = “yyyy-MM-dd HH:mm”,timezone = “GMT+8”)为时间类的json输出格式,供前端使用。
在static文件夹下创建index.html
,addlogistics.html
,addorder.html
.ordermanage.html
.
进入layui官网下载layui的js和css文件。解压后核心文件放到static下。到JQuery官网下载jquery-3.5.1.min.js文件,在static下创建js文件夹并把JQuery的js文件放进去,最终前端的页面会是这样的:
其中index.html文件为后台管理的主要ui页面,每个小功能的页面只需要编写对应页面即可。在index.html中编写以下内容:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>订单管理系统</title>
<link rel="stylesheet" href="layui/css/layui.css">
</head>
<body class="layui-layout-body">
<div class="layui-layout layui-layout-admin">
<div class="layui-header">
<div class="layui-logo">ordermanage</div>
<!-- 头部区域(可配合layui已有的水平导航) -->
<ul class="layui-nav layui-layout-right">
<li class="layui-nav-item">
<a href="javascript:;">
bigsai
</a>
</li>
</ul>
</div>
<div class="layui-side layui-bg-black">
<div class="layui-side-scroll">
<!-- 左侧导航区域(可配合layui已有的垂直导航) -->
<ul class="layui-nav layui-nav-tree" lay-filter="test">
<li class="layui-nav-item layui-nav-itemed">
<a class="" href="javascript:;">订单管理</a>
<dl class="layui-nav-child">
<dd><a href="ordermanage.html" target="container">订单管理</a></dd>
<dd><a href="addorder.html" target="container">订单添加</a></dd>
<dd><a href="addlogistics.html" target="container">物流添加</a></dd>
</dl>
</li>
</ul>
</div>
</div>
<div class="layui-body">
<!-- 内容主体区域 -->
<iframe src="addorder.html" name="container" width="100%" height="100%"></iframe>
</div>
<div class="layui-footer">
<!-- 底部固定区域 -->
bigsai带你学
</div>
</div>
<script src="layui/layui.js"></script>
<script src="layui/modules/jquery.js"></script>
<!--<script src="layui/main.js"></script>-->
<script>
// JavaScript代码区域
layui.use('element', function(){
var $ = layui.jquery
,element = layui.element; //Tab的切换功能,切换事件监听等,需要依赖element模块
//触发事件
var active = {
tabAdd: function(){
//新增一个Tab项
element.tabAdd('demo', {
title: '新选项'+ (Math.random()*1000|0) //用于演示
,content: '内容'+ (Math.random()*1000|0)
,id: new Date().getTime() //实际使用一般是规定好的id,这里以时间戳模拟下
})
}
,tabDelete: function(othis){
//删除指定Tab项
element.tabDelete('demo', '44'); //删除:“商品管理”
othis.addClass('layui-btn-disabled');
}
,tabChange: function(){
//切换到指定Tab项
element.tabChange('demo', '22'); //切换到:用户管理
}
};
});
</script>
</body>
</html>
打开页面后可以看到后台管理的初步页面:
左侧三个菜单分别对应创建的ordermanage.html,addorder.html,addlogistics.html三个页面。至此预备工作已经完成了,下面只需要完成具体的操作。本课程会着重讲解后端和MongoDB的部分,前端知识会简单介绍,需要深入理解还要自己多多研究。
下单我想谁都会,每次等待物流信息的时候是不是有一种满满的期待和喜悦感呢!
咱们今天带你动手体验这份小喜悦,完成案例后想下多少单下多少单。
首先,在orderService编写addorder函数,用来向MongoDB中添加订单。具体代码如下:
//创建订单,传来order对象
public void addorder(order order)
{
mongoTemplate.insert(order,"order");
}
上面的代码中:
mongoTemplate.insert(order,"order")
,第一个参数为插入的文档记录,第二个参数"order"
为连接的MongoDB对应数据库下的集合(Collections)。在orderController中编写addorder()接口,用来处理前端的请求添加订单。具体代码为:
@Autowired
orderService orderService;
@PostMapping("addorder")
public String addorder(order order)
{
order.setStatus("发货中");
order.setOrderTime(new Date());
order.setShipTime(new Date());
orderService.addorder(order);
return "添加成功";
}
上面代码中:
有了后端部分的支持,前端我们在addorder.html中编写以下内容,主要是一个表单向服务端发送数据和请求:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="layui/css/layui.css">
</head>
<body>
<section class="layui-larry-box">
<div class="larry-personal">
<blockquote class="layui-elem-quote layui-text">
<span>增加订单</span>
</blockquote>
<form class="layui-form col-lg-5 " action="addorder" method="post">
<div class="layui-form-item">
<label class="layui-form-label">订单id</label>
<div class="layui-input-block">
<input type="text" name="id" autocomplete="off" class="layui-input" value="" >
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">发货人姓名</label>
<div class="layui-input-block">
<input type="text" name="shipper" autocomplete="off" class="layui-input" value="">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">发货人地址</label>
<div class="layui-input-block">
<input type="text" name="shippingAdress" autocomplete="off" class="layui-input" value="">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">发货人电话</label>
<div class="layui-input-block">
<input type="text" name="shipperPhone" autocomplete="off" class="layui-input" value="">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">收件人姓名</label>
<div class="layui-input-block">
<input type="text" name="recevier" autocomplete="off" class="layui-input" value="">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">收件人地址</label>
<div class="layui-input-block">
<input type="text" name="recevierAddress" autocomplete="off" class="layui-input" value="">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">收件人手机</label>
<div class="layui-input-block">
<input type="text" name="receviePhone" autocomplete="off" class="layui-input" value="">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="formDemo">添加</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>
</form>
</div>
</section>
</body>
<script type="text/javascript" src="layui/layui.js"></script>
写完后启动程序访问localhost:8080
点击订单添加,然后在表单中填写对应内容
当然为了测试你可以再写一单,添加之后你会发现MongoDB中成功添加了订单数据,这样下单这一步就大功告成啦!
创建完订单之后,无数配送公司和人员便开始配送物流,而我们在查询的时候,物流状态信息也能够时不时的刷新,具体物流信息也能得到追加。
在后端的处理上,我们先写service,再写controller,在orderService中编写addLogisticsAndUpdateStatus()函数,主要实现更新订单的状态和订单的物流情况。具体代码为:
//更新物流
public void addLogisticsAndUpdateStatus(logistics logistics)
{
String status=logistics.getOperation();
Query query = new Query(Criteria.where("_id").is(logistics.getOrderId()));
Update update = new Update();
update.set("status", status);//更新状态
update.push("logistics",logistics);
mongoTemplate.upsert(query, update, order.class);
}
其中:
编写完orderService,在orderController中编写一个名为updateorder的接口,用来处理更新的请求和参数并执行更新的操作,具体代码为:
@PostMapping("updateorder")
public String updateorder(logistics logistics)
{
logistics.setOperationTime(new Date());
orderService.addLogisticsAndUpdateStatus(logistics);
return "添加成功";
}
同样接口类型为post类型,接收部分参数然后将物流操作时间设置为当前时间,调用orderService的addLogisticsAndUpdateStatus()方法执行更新的操作。这样后端部分就完成了。
有了后端部分的支持,前端我们在addlogistics.html中编写以下内容,主要是一个表单向服务端发送数据和更新请求:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="layui/css/layui.css">
</head>
<body>
<section class="layui-larry-box">
<div class="larry-personal">
<blockquote class="layui-elem-quote layui-text">
<span>增加物流信息</span>
</blockquote>
<form class="layui-form col-lg-5 " action="updateorder" method="post">
<div class="layui-form-item">
<label class="layui-form-label">订单id</label>
<div class="layui-input-block">
<input type="text" name="orderId" autocomplete="off" class="layui-input" value="" >
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">操作名称</label>
<div class="layui-input-block">
<input type="text" name="operation" autocomplete="off" class="layui-input" value="">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">操作员</label>
<div class="layui-input-block">
<input type="text" name="operator" autocomplete="off" class="layui-input" value="">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">操作地址</label>
<div class="layui-input-block">
<input type="text" name="adress" autocomplete="off" class="layui-input" value="">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">备注</label>
<div class="layui-input-block">
<input type="text" name="details" autocomplete="off" class="layui-input" value="">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="formDemo">添加</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>
</form>
</div>
</section>
</body>
<script type="text/javascript" src="layui/layui.js"></script>
这样,前端部分编写完成,执行程序访问localhost:8080
,点击添加物流,根据1001的订单号添加物流信息。
添加之后查看MongoDB中订单状态得到更新且物流数据得到更新(追加):
订单的添加和修改都完成了,非常重要的查询当然不能少,不仅不能少,还要特色的展示,这里查询通过一个订单号查询对应订单的状态和物流。
首先在orderservice中编写getOrderById()函数,用来根据订单id查询该条订单记录。具体代码为:
//通过id查询物流
public order getOrderById(int id)
{
Query query = new Query(Criteria.where("_id").is(id));
order order=mongoTemplate.findOne(query, order.class);
return order;
}
其中:
写完service然后在orderController中编写getorderbyid接口用来处理用户请求和参数并调用orderService的getOrderById()方法给前端返回该order对象序列化成的json字符串。具体代码为:
@GetMapping("getorderbyid")
public order getOrderById(int id)
{
order order=orderService.getOrderById(id);
return order;
}
这样,后端部分就完成,仅需要前端渲染数据即可。
后端设计完成之后,需要前端来实现,在这里使用Ajax来实现交互,前端页面点击按钮JavaScript携带参数发送请求,后端查询MongoDB后返回结果给前端渲染, 而在渲染方面为了更像物流订单系统,我们使用layui的 时间轴组件,将各个物流订单数据直观性展示。
前端在ordermanage.html中编写以下内容:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="layui/css/layui.css">
</head>
<body>
<blockquote class="layui-elem-quote layui-text">
订单管理
</blockquote>
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">订单号</label>
<div class="layui-input-inline">
<input type="tel" name="orderid" id="orderid" autocomplete="off" class="layui-input">
</div>
</div>
<button class="layui-btn" id="seach" onclick="search()">搜索</button><br>
<div style="padding: 20px; background-color: #F2F2F2;">
<div class="layui-row layui-col-space15">
<div class="layui-col-md6">
<div class="layui-card">
<div class="layui-card-header" id="order"></div>
<div class="layui-card-body" id="orderbody">
</div>
</div>
</div>
</div>
</div>
<ul class="layui-timeline" id="timezhou">
</ul>
</div>
</body>
<script type="text/javascript" src="layui/layui.js"></script>
<script type="text/javascript" src="js/jquery-3.5.1.min..js"></script>
<script type="text/javascript">
function search() {//根据
var orderid = $("#orderid").val();
$("#orderbody").html('');
$("#timezhou").html('');
$.ajax( {
url:"getorderbyid",
data:{
'id':orderid
},
method:'GET',
success:function (order) {
$("#order").html('订单号:'+orderid+'('+order['status']+')');
$("#orderbody").append('发件人:'+order['shipper']+' 发件人手机:'+order['shipperPhone']+' 发件人地址:'+order['shippingAdress']+' 下单时间:'+order['shipTime']);
$("#orderbody").append('<br>收件人:'+order['recevier']+' 收获人手机:'+order['receviePhone']+' 收获人地址:'+order['recevierAddress']);
var logistics=order['logistics'];
console.log(logistics);
for(var i=logistics.length-1;i>=0;i--)
{
console.log(logistics[i]);
$("#timezhou").append(' <li class="layui-timeline-item">\n' +
' <i class="layui-icon layui-timeline-axis"></i>\n' +
' <div class="layui-timeline-content layui-text">\n' +
' <h3 class="layui-timeline-title">'+'('+logistics[i].operation+')'+logistics[i].operationTime+
' </h3><p>'+logistics[i].operator+' '+logistics[i].details+'<br>'+logistics[i].adress);
if(logistics[i].phone!=0)
{
$("#timezhou").append('<br>'+logistics[i].phone);
}
$("#timezhou").append(' </p>\n' +
' </div>\n' +
' </li>');
}
},
error:function (order) {
layer.msg(order)
}
})
}
</script>
其中Ajax将返回的值通过组装渲染,将带填充数据区域先设置id属性,然后用JavaScript把数据渲染到该部分,核心的思路在 search() 函数中。
启动程序,访问localhost:8080
,点击订单管理,查询订单号为1001的物流情况。
利用上述添加物流信息,多添加该订单的物流信息,模拟多一些流程。查询的结果为:
作为管理人员,可能偶尔会遇到特殊情况需要删除订单,而这种操作需求也是很有必要的,在这里实现根据id删除订单。我们在删除订单时候,一般先查询订单的一些数据和结果,然后根据查询的id删除对应的记录。
首先在orderService中编写deleteOrderById()函数,用来根据id删除,编写getAllorder()函数,用来查询所有订单。
本文系作者在时代Java发表,未经许可,不得转载。
如有侵权,请联系nowjava@qq.com删除。