Java物流系统案例

wen java案例 3

本文目录导读:

Java物流系统案例

  1. 系统架构设计
  2. 项目结构
  3. 核心代码实现
  4. 数据库设计
  5. 系统特色功能
  6. 性能优化建议
  7. 部署方案

我来为您设计一个完整的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);
            }
        }
    }
}

性能优化建议

  1. 数据库优化

    • 合理使用索引
    • 分表分库策略
    • 读写分离
  2. 缓存优化

    • Redis缓存热点数据
    • 订单详情缓存
    • 车辆位置实时缓存
  3. 异步处理

    • 短信通知异步化
    • 轨迹记录异步化
    • 报表统计定时生成

部署方案

# 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:

这个物流系统案例包含了完整的订单管理、物流轨迹追踪、车辆管理等核心功能,并通过合理的架构设计和代码组织实现了系统的高性能和高可用性,您可以根据实际需求进行调整和扩展。

抱歉,评论功能暂时关闭!