Design and Implementation of an Intelligent Unmanned Warehouse Management System with Spring Boot and Vue

Architecture Overview

The solution adopts a micro-service-oriented architecture that cleanly separates concerns between the warehouse-edge layer and the cloud-control layer. The edge layer runs on industrial PCs attached to each storage zone and is implemented with Spring Boot 2.7.x. The cloud layer is a lightweight Vue 3 SPA served through Nginx and communicates with the edge services through REST and WebSocket channels.

Key design goals:

  • Zero-downtime horizontal scaling of edge nodes
  • Real-time inventory accuracy via RFID + computer-vision fusion
  • Rule-based autonomous task assignment for AGVs and robotic arms
  • End-to-end traceability with immutable audit logs

Runtime Environment

Compnoent Version Purpose
OpenJDK 17 LTS JVM runtime
Spring Boot 2.7.12 Edge service framework
MySQL 8.0.33 Persistent store
Redis 7.0 Cache & message broker
Node.js 18.x Vue build toolchain
Nginx 1.24 Reverse proxy & static hosting

Domain Model

-- Core tables
CREATE TABLE warehouse_zone (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    code VARCHAR(16) UNIQUE NOT NULL,
    layout JSON NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE sku (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    barcode VARCHAR(64) UNIQUE NOT NULL,
    name VARCHAR(128) NOT NULL,
    length_mm INT NOT NULL,
    width_mm  INT NOT NULL,
    height_mm INT NOT NULL,
    weight_g  INT NOT NULL,
    INDEX idx_barcode (barcode)
);

CREATE TABLE bin (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    zone_id BIGINT NOT NULL,
    code VARCHAR(16) NOT NULL,
    x INT NOT NULL,
    y INT NOT NULL,
    z INT NOT NULL,
    max_weight_g INT NOT NULL,
    UNIQUE KEY uk_zone_code (zone_id, code),
    FOREIGN KEY (zone_id) REFERENCES warehouse_zone(id)
);

CREATE TABLE inventory_txn (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    sku_id BIGINT NOT NULL,
    bin_id BIGINT NOT NULL,
    delta INT NOT NULL,
    txn_type ENUM('INBOUND','OUTBOUND','MOVE','ADJUST') NOT NULL,
    operator VARCHAR(32) NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (sku_id) REFERENCES sku(id),
    FOREIGN KEY (bin_id) REFERENCES bin(id)
);

Edge Service Implementation

File Storage Endpoint

package io.warehouse.edge.api;

import io.warehouse.edge.config.StorageProps;
import io.warehouse.edge.model.FileRecord;
import io.warehouse.edge.service.FileManager;
import lombok.RequiredArgsConstructor;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.nio.file.Path;

@RestController
@RequestMapping("/api/v1/assets")
@RequiredArgsConstructor
public class AssetController {

    private final FileManager fileManager;
    private final StorageProps props;

    @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public FileRecord upload(@RequestPart("file") MultipartFile file,
                             @RequestParam(defaultValue = "false") boolean temp) throws IOException {
        Path target = fileManager.store(file, temp);
        return FileRecord.builder()
                .name(file.getOriginalFilename())
                .path(props.getUriPath(target))
                .size(file.getSize())
                .build();
    }

    @GetMapping("/{fileName}")
    public ResponseEntity<InputStreamResource> download(@PathVariable String fileName) throws IOException {
        Path file = fileManager.locate(fileName);
        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"")
                .contentLength(fileManager.size(file))
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .body(new InputStreamResource(fileManager.open(file)));
    }
}

Inventory Movement API

package io.warehouse.edge.api;

import io.warehouse.edge.command.MoveCommand;
import io.warehouse.edge.service.InventoryService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/v1/inventory")
@RequiredArgsConstructor
public class InventoryController {

    private final InventoryService inventoryService;

    @PostMapping("/move")
    public ResponseEntity<Void> move(@RequestBody @Validated MoveCommand cmd) {
        inventoryService.move(cmd);
        return ResponseEntity.accepted().build();
    }

    @GetMapping("/bin/{binCode}")
    public BinSnapshot getBin(@PathVariable String binCode) {
        return inventoryService.snapshot(binCode);
    }

    public record BinSnapshot(String binCode, List<SkuQty> items) {}
    public record SkuQty(String barcode, int qty) {}
}

Front-End Highlights

Vue 3 Composition API with Pinia state management:

// stores/zone.ts
import { defineStore } from 'pinia';
import { ref } from 'vue';
import { api } from '@/lib/axios';

export interface Zone {
  id: number;
  code: string;
  layout: any;
}

export const useZoneStore = defineStore('zone', () => {
  const zones = ref<Zone[]>([]);

  async function load() {
    const { data } = await api.get<Zone[]>('/zones');
    zones.value = data;
  }

  return { zones, load };
});

// components/BinHeatMap.vue
<template>
  <svg :viewBox="viewBox">
    <rect
      v-for="bin in heatData"
      :key="bin.code"
      :x="bin.x * cellSize"
      :y="bin.y * cellSize"
      :width="cellSize"
      :height="cellSize"
      :fill="colorScale(bin.utilization)"
      @click="selectBin(bin)"
    />
  </svg>
</template>

<script setup lang="ts">
import { computed } from 'vue';
import { useZoneStore } from '@/stores/zone';

const props = defineProps<{ zoneId: number }>();
const zoneStore = useZoneStore();

const heatData = computed(() => zoneStore.zones.find(z => z.id === props.zoneId)?.layout.bins);
const colorScale = (u: number) => `hsl(${120 - u * 120}, 80%, 50%)`;
</script>

Autonomous Task Assignmant

The rule engine is implemented using Spring’s @Component and RuleEngine from Easy Rules:

package io.warehouse.edge.rules;

import org.jeasy.rules.annotation.*;

@Rule(name = "High-Priority Outbound", description = "Fast lane for urgent outbound")
public class PriorityOutboundRule {

    @Condition
    public boolean isUrgent(@Fact("task") Task task) {
        return task.getType() == TaskType.OUTBOUND && task.getPriority() > 80;
    }

    @Action
    public void assignAGV(@Fact("ctx") AssignmentContext ctx) {
        ctx.allocateNearestAGV();
    }
}

Testing Strategy

  • Unit: JUnit 5 + AssertJ for domain logic
  • Integration: Testcontainers for MySQL and Redis
  • Contract: OpenAPI-driven tests for REST endpoints
  • End-to-End: Cypress flows covering operator workflows

Sample integration test:

@Testcontainers
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class InventoryIntegrationTest {

    @Container
    static MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0.33");

    @DynamicPropertySource
    static void props(DynamicPropertyRegistry r) {
        r.add("spring.datasource.url", mysql::getJdbcUrl);
        r.add("spring.datasource.username", mysql::getUsername);
        r.add("spring.datasource.password", mysql::getPassword);
    }

    @Test
    void shouldRecordInboundTransaction() {
        var txn = InboundTxn.builder()
                .sku("SKU-123")
                .bin("A-01-03")
                .qty(50)
                .build();

        web.post()
           .uri("/api/v1/inventory/inbound")
           .bodyValue(txn)
           .exchange()
           .expectStatus().isAccepted();

        assertThat(repo.count()).isEqualTo(1);
    }
}

Deployment Pipeline

  1. Maven build produces layered Docker image (paketobuildpacks)
  2. Helm chart deploys edge service as Deployment + Service
  3. Canary analysis via Argo Rollouts based on error-rate SLO
  4. Front-end assets pushed to S3 + CloudFront invalidation
# helm/values.yaml
edge:
  image:
    repository: warehouse/edge
    tag: "{{ .Values.tag }}"
  resources:
    requests:
      cpu: 200m
      memory: 512Mi
  autoscaling:
    enabled: true
    minReplicas: 2
    maxReplicas: 10
    targetCPUUtilization: 60

Tags: spring-boot vue warehouse automation inventory

Posted on Tue, 12 May 2026 15:24:07 +0000 by artech