本文目录导读:

我来为您设计一个完整的Java物流管理系统案例,这是一个基于Spring Boot + MyBatis + Vue.js的现代化物流管理系统。
系统架构设计
技术栈
- 后端: Spring Boot 2.7.x, MyBatis Plus, MySQL 8.0
- 前端: Vue 3 + Element Plus
- 工具: Maven, Redis, JWT
- 其他: RabbitMQ (消息通知), 阿里云OSS (文件存储)
项目结构
logistics-system/
├── src/main/java/com/logistics/
│ ├── config/ # 配置类
│ ├── controller/ # 控制器层
│ ├── service/ # 业务逻辑层
│ ├── mapper/ # 数据访问层
│ ├── entity/ # 实体类
│ ├── dto/ # 数据传输对象
│ ├── vo/ # 视图对象
│ ├── utils/ # 工具类
│ └── common/ # 通用类
├── src/main/resources/
│ ├── mapper/ # MyBatis映射文件
│ ├── static/ # 静态资源
│ └── application.yml # 配置文件
└── pom.xml
核心代码实现
1 实体类设计
// 物流订单实体类
@Data
@TableName("logistics_order")
public class LogisticsOrder {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private String orderNo; // 订单编号
private String senderName; // 发件人姓名
private String senderPhone; // 发件人电话
private String senderAddress; // 发件人地址
private String receiverName; // 收件人姓名
private String receiverPhone; // 收件人电话
private String receiverAddress; // 收件人地址
private Integer status; // 订单状态:0-待揽收 1-运输中 2-派送中 3-已签收 4-异常
private String trackingNo; // 运单号
private BigDecimal weight; // 货物重量(kg)
private BigDecimal freight; // 运费
@TableField(fill = FieldFill.INSERT)
private Date createTime; // 创建时间
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime; // 更新时间
private Long createUser; // 创建人
private Integer isDeleted; // 逻辑删除:0-未删除 1-已删除
}
// 运输轨迹实体
@Data
@TableName("logistics_track")
public class LogisticsTrack {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private String orderNo; // 订单编号
private String trackingNo; // 运单号
private String location; // 当前位置
private String description; // 状态描述
private String operator; // 操作人
private Date trackTime; // 轨迹时间
private Integer currentStatus; // 当前状态
}
// 车辆管理实体
@Data
@TableName("transport_vehicle")
public class TransportVehicle {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private String plateNumber; // 车牌号
private String model; // 车型
private Integer capacity; // 载重能力(千克)
private String driverName; // 司机姓名
private String driverPhone; // 司机电话
private Integer status; // 状态:0-空闲 1-运输中 2-维修中
private Date createTime;
private Date updateTime;
private Integer isDeleted;
}
2 控制器层实现
@RestController
@RequestMapping("/api/order")
@Slf4j
public class OrderController {
@Autowired
private OrderService orderService;
/**
* 创建物流订单
*/
@PostMapping("/create")
public ApiResult createOrder(@RequestBody @Valid OrderCreateDTO orderDTO) {
try {
Long orderId = orderService.createOrder(orderDTO);
return ApiResult.success(orderId);
} catch (BusinessException e) {
log.error("创建订单失败", e);
return ApiResult.error(e.getMessage());
}
}
/**
* 订单查询(分页)
*/
@GetMapping("/page")
public ApiResult queryOrderPage(@RequestParam(required = false) String orderNo,
@RequestParam(required = false) Integer status,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
Page<LogisticsOrder> page = orderService.queryOrderPage(orderNo, status, pageNum, pageSize);
return ApiResult.success(page);
}
/**
* 订单轨迹查询
*/
@GetMapping("/track/{orderNo}")
public ApiResult queryTrackList(@PathVariable String orderNo) {
List<LogisticsTrack> trackList = orderService.queryTrackList(orderNo);
return ApiResult.success(trackList);
}
/**
* 更新订单状态
*/
@PutMapping("/status")
public ApiResult updateOrderStatus(@RequestBody @Valid OrderStatusDTO statusDTO) {
orderService.updateOrderStatus(statusDTO.getOrderNo(), statusDTO.getStatus());
return ApiResult.success(true);
}
}
3 业务逻辑层实现
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Autowired
private LogisticsOrderMapper orderMapper;
@Autowired
private LogisticsTrackMapper trackMapper;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private RabbitTemplate rabbitTemplate;
@Override
@Transactional(rollbackFor = Exception.class)
public Long createOrder(OrderCreateDTO orderDTO) {
// 生成订单编号
String orderNo = generateOrderNo();
String trackingNo = generateTrackingNo();
LogisticsOrder order = new LogisticsOrder();
BeanUtils.copyProperties(orderDTO, order);
order.setOrderNo(orderNo);
order.setTrackingNo(trackingNo);
order.setStatus(0);
order.setCreateTime(new Date());
order.setUpdateTime(new Date());
// 计算运费
BigDecimal freight = calculateFreight(order.getWeight());
order.setFreight(freight);
// 保存订单
int insert = orderMapper.insert(order);
if (insert != 1) {
throw new BusinessException("订单创建失败");
}
// 记录初始化轨迹
insertTrack(orderNo, "订单已创建,等待揽收", 0);
// 发送消息通知
rabbitTemplate.convertAndSend("logistics.order", "创建订单:" + orderNo);
return order.getId();
}
@Override
public void updateOrderStatus(String orderNo, Integer status) {
// 使用Redis分布式锁防止并发
String lockKey = "lock:order:" + orderNo;
boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS);
if (!locked) {
throw new BusinessException("订单正在处理中,请稍后重试");
}
try {
LogisticsOrder order = orderMapper.selectOne(
new LambdaQueryWrapper<LogisticsOrder>()
.eq(LogisticsOrder::getOrderNo, orderNo)
.eq(LogisticsOrder::getIsDeleted, 0)
);
if (order == null) {
throw new BusinessException("订单不存在");
}
// 校验状态流转
validateStatusTransition(order.getStatus(), status);
order.setStatus(status);
orderMapper.updateById(order);
// 记录轨迹
String statusDesc = getStatusDescription(status);
insertTrack(orderNo, statusDesc, status);
// 短信通知收件人
if (status == 3) { // 已签收
sendSmsNotification(order);
}
} finally {
redisTemplate.delete(lockKey);
}
}
/**
* 生成订单编号
*/
private String generateOrderNo() {
return "ORD" + System.currentTimeMillis() + RandomUtil.randomNumbers(4);
}
/**
* 计算运费
*/
private BigDecimal calculateFreight(BigDecimal weight) {
// 基础运费10元,每公斤2元
BigDecimal base = new BigDecimal("10");
BigDecimal perKg = new BigDecimal("2");
return base.add(weight.multiply(perKg));
}
/**
* 插入轨迹信息
*/
private void insertTrack(String orderNo, String description, Integer status) {
LogisticsTrack track = new LogisticsTrack();
track.setOrderNo(orderNo);
track.setDescription(description);
track.setCurrentStatus(status);
track.setTrackTime(new Date());
track.setOperator("系统");
trackMapper.insert(track);
}
}
4 数据访问层
@Mapper
public interface LogisticsOrderMapper extends BaseMapper<LogisticsOrder> {
/**
* 统计每日订单数量
*/
@Select("SELECT DATE(create_time) AS date, COUNT(*) AS count " +
"FROM logistics_order " +
"WHERE is_deleted = 0 AND create_time BETWEEN #{startDate} AND #{endDate} " +
"GROUP BY DATE(create_time)")
List<OrderStatisticVO> selectOrderStatistic(@Param("startDate") Date startDate,
@Param("endDate") Date endDate);
/**
* 查询订单列表(含关联信息)
*/
IPage<OrderDetailVO> selectOrderDetailPage(IPage<OrderDetailVO> page,
@Param("query") OrderQueryDTO query);
}
5 配置类
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-");
executor.initialize();
return executor;
}
}
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 使用Jackson2JsonRedisSerializer替换默认序列化器
Jackson2JsonRedisSerializer<Object> jacksonSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(jacksonSerializer);
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(jacksonSerializer);
template.afterPropertiesSet();
return template;
}
}
6 工具类
@Component
public class SmsUtil {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 发送验证码
*/
public void sendVerificationCode(String phone) {
String code = String.valueOf((int)((Math.random() * 9 + 1) * 100000));
// 存储验证码到Redis,5分钟有效
redisTemplate.opsForValue().set("sms:code:" + phone, code, 5, TimeUnit.MINUTES);
// 实际接入短信服务商
// smsService.send(phone, "您的验证码是:" + code);
log.info("验证码发送成功,手机号:{},验证码:{}", phone, code);
}
}
@Aspect
@Component
@Slf4j
public class OperationLogAspect {
@Pointcut("@annotation(com.logistics.common.annotation.OperationLog)")
public void operationLogPointCut() {}
@Around("operationLogPointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
long executionTime = System.currentTimeMillis() - startTime;
// 记录操作日志
saveOperationLog(joinPoint, result, executionTime, true);
return result;
} catch (Exception e) {
long executionTime = System.currentTimeMillis() - startTime;
saveOperationLog(joinPoint, null, executionTime, false);
throw e;
}
}
}
7 前端Vue组件示例
<template>
<div class="order-page">
<!-- 搜索区域 -->
<el-card class="search-area">
<el-form inline>
<el-form-item label="订单号">
<el-input v-model="searchForm.orderNo" placeholder="请输入订单号" clearable />
</el-form-item>
<el-form-item label="订单状态">
<el-select v-model="searchForm.status" placeholder="请选择状态">
<el-option label="待揽收" :value="0" />
<el-option label="运输中" :value="1" />
<el-option label="派送中" :value="2" />
<el-option label="已签收" :value="3" />
<el-option label="异常" :value="4" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 表格区域 -->
<el-card>
<el-button type="primary" icon="Plus" @click="handleCreate">新建订单</el-button>
<el-table :data="orderList" stripe border>
<el-table-column prop="orderNo" label="订单编号" width="180" />
<el-table-column prop="senderName" label="发件人" width="120" />
<el-table-column prop="receiverName" label="收件人" width="120" />
<el-table-column label="货物重量" width="100">
<template #default="scope">
{{ scope.row.weight }}kg
</template>
</el-table-column>
<el-table-column label="运费" width="100">
<template #default="scope">
¥{{ scope.row.freight }}
</template>
</el-table-column>
<el-table-column label="状态" width="100">
<template #default="scope">
<el-tag :type="statusType(scope.row.status)">
{{ statusText(scope.row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="250" fixed="right">
<template #default="scope">
<el-button link type="primary" @click="handleDetail(scope.row)">详情</el-button>
<el-button link type="warning" @click="handleTrack(scope.row)">轨迹</el-button>
<el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
v-model:current-page="pageNum"
:page-size="pageSize"
:total="total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</el-card>
<!-- 新建订单对话框 -->
<el-dialog v-model="createDialogVisible" title="新建物流订单" width="600px">
<el-form :model="createForm" :rules="rules" ref="createFormRef">
<!-- 表单内容 -->
</el-form>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import request from '@/utils/request'
// 搜索表单
const searchForm = reactive({
orderNo: '',
status: null
})
// 表格数据
const orderList = ref([])
const pageNum = ref(1)
const pageSize = ref(10)
const total = ref(0)
// 获取订单列表
const fetchOrderList = async () => {
try {
const { data } = await request.get('/api/order/page', {
params: {
orderNo: searchForm.orderNo,
status: searchForm.status,
pageNum: pageNum.value,
pageSize: pageSize.value
}
})
orderList.value = data.records
total.value = data.total
} catch (error) {
ElMessage.error('获取订单列表失败')
}
}
// 状态类型映射
const statusType = (status) => {
const types = ['info', 'warning', 'primary', 'success', 'danger']
return types[status] || 'info'
}
const statusText = (status) => {
const texts = ['待揽收', '运输中', '派送中', '已签收', '异常']
return texts[status] || '未知'
}
// 搜索
const handleSearch = () => {
pageNum.value = 1
fetchOrderList()
}
// 其他处理函数...
onMounted(() => {
fetchOrderList()
})
</script>
数据库设计
-- 物流订单表 CREATE TABLE `logistics_order` ( `id` bigint(20) NOT NULL COMMENT '主键ID', `order_no` varchar(50) NOT NULL COMMENT '订单编号', `sender_name` varchar(50) NOT NULL COMMENT '发件人姓名', `sender_phone` varchar(20) NOT NULL COMMENT '发件人电话', `sender_address` varchar(200) NOT NULL COMMENT '发件人地址', `receiver_name` varchar(50) NOT NULL COMMENT '收件人姓名', `receiver_phone` varchar(20) NOT NULL COMMENT '收件人电话', `receiver_address` varchar(200) NOT NULL COMMENT '收件人地址', `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '订单状态', `tracking_no` varchar(50) DEFAULT NULL COMMENT '运单号', `weight` decimal(10,2) DEFAULT NULL COMMENT '货物重量(kg)', `freight` decimal(10,2) DEFAULT NULL COMMENT '运费', `create_time` datetime NOT NULL COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `create_user` bigint(20) DEFAULT NULL COMMENT '创建人', `is_deleted` tinyint(1) DEFAULT '0' COMMENT '删除标志', PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`), KEY `idx_tracking_no` (`tracking_no`), KEY `idx_create_time` (`create_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='物流订单表'; -- 运输轨迹表 CREATE TABLE `logistics_track` ( `id` bigint(20) NOT NULL COMMENT '主键ID', `order_no` varchar(50) NOT NULL COMMENT '订单编号', `tracking_no` varchar(50) DEFAULT NULL COMMENT '运单号', `location` varchar(100) DEFAULT NULL COMMENT '当前位置', `description` varchar(500) DEFAULT NULL COMMENT '状态描述', `operator` varchar(50) DEFAULT NULL COMMENT '操作人', `track_time` datetime NOT NULL COMMENT '轨迹时间', `current_status` tinyint(4) NOT NULL COMMENT '当前状态', PRIMARY KEY (`id`), KEY `idx_order_no` (`order_no`), KEY `idx_track_time` (`track_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='物流轨迹表'; -- 车辆信息表 CREATE TABLE `transport_vehicle` ( `id` bigint(20) NOT NULL COMMENT '主键ID', `plate_number` varchar(20) NOT NULL COMMENT '车牌号', `model` varchar(50) DEFAULT NULL COMMENT '车型', `capacity` decimal(10,2) DEFAULT NULL COMMENT '载重能力(kg)', `driver_name` varchar(50) DEFAULT NULL COMMENT '司机姓名', `driver_phone` varchar(20) DEFAULT NULL COMMENT '司机电话', `status` tinyint(4) DEFAULT '0' COMMENT '状态:0-空闲 1-运输中 2-维修中', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `is_deleted` tinyint(1) DEFAULT '0' COMMENT '删除标志', PRIMARY KEY (`id`), UNIQUE KEY `uk_plate_number` (`plate_number`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='运输车辆表';
系统特色功能
1 订单状态流转控制
public enum OrderStatus {
PENDING_PICKUP(0, "待揽收"),
IN_TRANSIT(1, "运输中"),
OUT_FOR_DELIVERY(2, "派送中"),
DELIVERED(3, "已签收"),
ABNORMAL(4, "异常");
private final int code;
private final String description;
// 允许的状态流转映射
private static final Map<Integer, List<Integer>> ALLOWED_TRANSITIONS =
new HashMap<>() {{
put(0, Arrays.asList(1, 4)); // 待揽收 -> 运输中/异常
put(1, Arrays.asList(2, 4)); // 运输中 -> 派送中/异常
put(2, Arrays.asList(3, 4)); // 派送中 -> 已签收/异常
put(4, Arrays.asList(0, 1, 2)); // 异常 -> 可恢复任意状态
}};
public static boolean canTransition(int from, int to) {
if (from == to) return true;
List<Integer> allowed = ALLOWED_TRANSITIONS.get(from);
return allowed != null && allowed.contains(to);
}
}
2 分布式锁处理高并发
@Service
public class OrderStatusUpdateService {
@Autowired
private StringRedisTemplate redisTemplate;
public boolean updateStatusWithLock(String orderNo, int newStatus) {
String lockKey = "lock:order:status:" + orderNo;
String requestId = UUID.randomUUID().toString();
// 尝试获取锁,5秒超时
boolean tryLock = redisTemplate.opsForValue()
.setIfAbsent(lockKey, requestId, 5, TimeUnit.SECONDS);
if (!tryLock) {
throw new BusinessException("系统繁忙,请稍后重试");
}
try {
// 执行业务逻辑
return performUpdate(orderNo, newStatus);
} finally {
// 释放锁
String lockVal = redisTemplate.opsForValue().get(lockKey);
if (requestId.equals(lockVal)) {
redisTemplate.delete(lockKey);
}
}
}
}
性能优化建议
-
数据库优化
- 合理使用索引
- 分表分库策略
- 读写分离
-
缓存优化
- Redis缓存热点数据
- 订单详情缓存
- 车辆位置实时缓存
-
异步处理
- 短信通知异步化
- 轨迹记录异步化
- 报表统计定时生成
部署方案
# Docker Compose部署
version: '3.8'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root1234
MYSQL_DATABASE: logistics
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
redis:
image: redis:7-alpine
ports:
- "6379:6379"
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
- redis
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/logistics
SPRING_REDIS_HOST: redis
frontend:
build: ./frontend
ports:
- "80:80"
depends_on:
- backend
volumes:
mysql-data:
这个物流系统案例包含了完整的订单管理、物流轨迹追踪、车辆管理等核心功能,并通过合理的架构设计和代码组织实现了系统的高性能和高可用性,您可以根据实际需求进行调整和扩展。