不怕库存不同步!用 Java + Spring Boot 每 5 分钟自动校准外卖平台库存!

为了解决这一痛点,我们基于 Java + Spring Boot 构建了一套可落地的 “库存双向同步系统”。 系统以 5 分钟为同步周期,自动检测外卖平台(美团/饿了么)与线下收银系统(商米、思迅或 Excel 导入)的库存差异,实现 “线下→线上” 与 “线上→线下” 的双向数据一致性。

对于中小商户而言,库存差异是外卖与线下经营中的“隐形杀手”。 线下卖完、线上还在售——导致“缺货订单”; 线上订单多、线下库存没更新——结果“超卖赔单”。

为了解决这一痛点,我们基于 Java + Spring Boot 构建了一套可落地的 “库存双向同步系统”。 系统以 5 分钟为同步周期,自动检测外卖平台(美团/饿了么)与线下收银系统(商米、思迅或 Excel 导入)的库存差异,实现 “线下→线上” 与 “线上→线下” 的双向数据一致性

系统整体架构(轻量三层结构)

项目目录结构

/usr/local/java/sync
├── src/main/java/com/icoderoad/inventory
│   ├── controller
│   ├── service
│   ├── mapper
│   └── entity
├── src/main/resources
│   ├── application.yml
│   └── mapper/*.xml
└── pom.xml

系统架构采用典型的三层模型:

┌─────────────────┐      ┌────────────────────────────────┐      ┌────────────────────────┐
│ 微信小程序       │◄────►│ Spring Boot 后端服务(MySQL、定时任务)│◄────►│ 外卖平台 / 收银系统 API │
│(配置入口)       │      │                                │      │ 美团 / 饿了么 / 商米等     │
└─────────────────┘      └────────────────────────────────┘      └────────────────────────┘

设计目标:

  • 轻量化部署:无复杂中间件依赖
  • 模块化职责清晰:映射管理、库存同步、日志追踪
  • 可平滑扩展:支持 Excel 导入、扫码匹配、实时推送

数据库设计

商品映射表(product_mapping

用于建立 “线下商品” 与 “线上商品” 的对应关系。

CREATE TABLE `product_mapping` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
  `shop_id` VARCHAR(50) NOT NULL COMMENT '店铺ID',
  `offline_product_id` VARCHAR(50) NOT NULL COMMENT '线下商品ID',
  `offline_product_name` VARCHAR(100) NOT NULL COMMENT '线下商品名称',
  `platform_type` TINYINT NOT NULL COMMENT '平台类型:1=美团,2=饿了么',
  `online_product_id` VARCHAR(50) NOT NULL COMMENT '线上商品ID',
  `online_product_name` VARCHAR(100) NOT NULL COMMENT '线上商品名称',
  `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_shop_offline_platform` (`shop_id`,`offline_product_id`,`platform_type`)
) COMMENT='商品映射表';

库存记录表(inventory_record

记录线下与线上库存数据的同步结果。

CREATE TABLE `inventory_record` (
  `id` BIGINT NOT NULL AUTO_INCREMENT,
  `shop_id` VARCHAR(50) NOT NULL COMMENT '店铺ID',
  `offline_product_id` VARCHAR(50) NOT NULL COMMENT '线下商品ID',
  `current_stock` INT NOT NULL DEFAULT 0 COMMENT '可售库存',
  `offline_actual_stock` INT NOT NULL DEFAULT 0 COMMENT '线下实际库存',
  `online_occupied_stock` INT NOT NULL DEFAULT 0 COMMENT '线上未发货订单占用',
  `last_sync_time` DATETIME DEFAULT NULL COMMENT '最后同步时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_shop_offline` (`shop_id`,`offline_product_id`)
) COMMENT='库存记录表';

同步日志表(sync_log

记录同步任务执行情况。

CREATE TABLE `sync_log` (
  `id` BIGINT NOT NULL AUTO_INCREMENT,
  `shop_id` VARCHAR(50) NOT NULL,
  `sync_type` TINYINT NOT NULL COMMENT '1=线上→线下, 2=线下→线上, 3=手动同步',
  `sync_result` TINYINT NOT NULL COMMENT '0=失败, 1=成功',
  `message` VARCHAR(500) DEFAULT NULL COMMENT '失败原因',
  `sync_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_shop_time` (`shop_id`,`sync_time`)
) COMMENT='同步日志表';

核心业务模块实现

商品映射管理模块(ProductMappingController

package com.icoderoad.inventory.controller;


@RestController
@RequestMapping("/api/product-mapping")
public class ProductMappingController {


    @Autowired
    private ProductMappingService mappingService;


    // 添加映射关系
    @PostMapping("/add")
    public Result<Void> addMapping(@RequestBody ProductMappingDTO dto) {
        mappingService.addMapping(dto);
        return Result.success();
    }


    // 查询映射列表
    @GetMapping("/list")
    public Result<List<ProductMappingVO>> getMappingList(@RequestParam String shopId) {
        return Result.success(mappingService.listByShopId(shopId));
    }
}

库存同步引擎(核心逻辑)

(1)拉取线上订单
package com.icoderoad.inventory.service;


@Service
public class OnlineOrderService {


    // 获取美团5分钟内新订单
    public List<MeituanOrder> pullMeituanOrders(String shopId, LocalDateTime start, LocalDateTime end) {
        String sign = generateSign(shopId, start, end);
        String url = "https://open-meituan.com/api/v1/order/list?shop_id=" + shopId
                + "&start_time=" + start + "&end_time=" + end + "&sign=" + sign;
        String response = HttpClientUtil.get(url);
        return JSON.parseArray(response, MeituanOrder.class);
    }


    // 拉取饿了么订单(同理实现)
}
(2)读取线下库存(API 或 Excel)
@Service
public class OfflineStockService {


    // 调用商米API获取库存
    public List<OfflineStock> getShangmiStock(String shopId) {
        String url = "https://shangmi-api.com/stock?shop_id=" + shopId;
        String response = HttpClientUtil.get(url);
        return JSON.parseArray(response, OfflineStock.class);
    }


    // 解析 Excel 文件
    public List<OfflineStock> parseExcelStock(MultipartFile file) throws IOException {
        List<OfflineStock> stocks = new ArrayList<>();
        try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
            Sheet sheet = wb.getSheetAt(0);
            for (int i = 1; i <= sheet.getLastRowNum(); i++) {
                Row row = sheet.getRow(i);
                stocks.add(new OfflineStock(
                    row.getCell(0).getStringCellValue(),
                    (int) row.getCell(1).getNumericCellValue()
                ));
            }
        }
        return stocks;
    }
}
(3)双向同步核心逻辑
@Service
@Transactional
public class InventorySyncService {


    @Autowired private OnlineOrderService onlineOrderService;
    @Autowired private OfflineStockService offlineStockService;
    @Autowired private InventoryRecordMapper inventoryMapper;
    @Autowired private ProductMappingService mappingService;


    // 线上→线下
    public void syncOnlineToOffline(String shopId) {
        LocalDateTime end = LocalDateTime.now();
        LocalDateTime start = end.minusMinutes(5);
        List<MeituanOrder> mtOrders = onlineOrderService.pullMeituanOrders(shopId, start, end);
        List<ElemeOrder> eleOrders = onlineOrderService.pullElemeOrders(shopId, start, end);
        Map<String, Integer> occupied = calculateOnlineOccupied(mtOrders, eleOrders, shopId);
        occupied.forEach((pid, num) -> inventoryMapper.updateOnlineOccupied(shopId, pid, num));
    }


    // 线下→线上
    public void syncOfflineToOnline(String shopId) {
        List<OfflineStock> stocks = offlineStockService.getLatestOfflineStock(shopId);
        for (OfflineStock s : stocks) {
            InventoryRecord record = inventoryMapper.getByShopAndProduct(shopId, s.getProductId());
            int available = record.getOfflineActualStock() - record.getOnlineOccupiedStock();
            syncToPlatform(shopId, s.getProductId(), available);
            inventoryMapper.updateCurrentStock(shopId, s.getProductId(), available);
        }
    }


    private void syncToPlatform(String shopId, String offlinePid, int stock) {
        List<ProductMapping> mappings = mappingService.getByOfflineProduct(shopId, offlinePid);
        for (ProductMapping m : mappings) {
            if (m.getPlatformType() == 1) meituanApi.updateStock(m.getOnlineProductId(), stock);
            if (m.getPlatformType() == 2) elemeApi.updateStock(m.getOnlineProductId(), stock);
        }
    }
}
定时任务 + 手动触发模块
@Service
public class SyncTask {


    @Autowired private InventorySyncService syncService;
    @Autowired private SyncLogService syncLogService;
    @Autowired private ShopService shopService;


    // 每5分钟执行
    @Scheduled(cron = "0 0/5 * * * ?")
    public void autoSync() {
        for (String shopId : shopService.getAllShopIds()) {
            try {
                syncService.syncOnlineToOffline(shopId);
                syncService.syncOfflineToOnline(shopId);
                syncLogService.saveLog(shopId, 3, 1, "定时同步成功");
            } catch (Exception e) {
                syncLogService.saveLog(shopId, 3, 0, "同步失败:" + e.getMessage());
            }
        }
    }


    // 手动同步(微信小程序入口)
    public void manualSync(String shopId) {
        try {
            syncService.syncOnlineToOffline(shopId);
            syncService.syncOfflineToOnline(shopId);
            syncLogService.saveLog(shopId, 3, 1, "手动同步成功");
        } catch (Exception e) {
            syncLogService.saveLog(shopId, 3, 0, "手动同步失败:" + e.getMessage());
            throw new BusinessException("同步失败,请稍后重试");
        }
    }
}
库存状态可视化后台界面

为了让运营人员能够直观了解库存同步情况,我们设计了一个基于 Thymeleaf + Bootstrap + Chart.js 的后台页面,实现如下功能:

  • 表格展示当前库存差异;
  • 动态条形图显示各商品库存状态;
  • 每 5 分钟自动刷新一次数据。
Controller 层
package com.icoderoad.stock.controller;


import com.icoderoad.stock.repository.ProductStockRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;


@Controller
@RequiredArgsConstructor
public class StockViewController {


    private final ProductStockRepository repository;


    /** 访问后台库存页面 */
    @GetMapping("/admin/stock")
    public String stockPage(Model model) {
        model.addAttribute("stocks", repository.findAll());
        return "stock-view";
    }


    /** 提供库存数据接口(Chart.js 拉取用) */
    @GetMapping("/admin/stock/data")
    @ResponseBody
    public Object stockData() {
        return repository.findAll();
    }
}

Thymeleaf 页面:src/main/resources/templates/stock-view.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>库存状态可视化后台</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body class="bg-light">


<div class="container mt-5">
    <h3 class="mb-4 text-center">📊 外卖平台库存同步监控后台</h3>


    <!-- 表格展示 -->
    <div class="card shadow-sm mb-4">
        <div class="card-body">
            <h5 class="card-title mb-3">库存详情</h5>
            <table class="table table-striped table-bordered">
                <thead class="table-dark">
                <tr>
                    <th>商品编码</th>
                    <th>商品名称</th>
                    <th>本地库存</th>
                    <th>平台库存</th>
                    <th>最后同步时间</th>
                </tr>
                </thead>
                <tbody>
                <tr th:each="s : ${stocks}">
                    <td th:text="${s.productCode}"></td>
                    <td th:text="${s.productName}"></td>
                    <td th:text="${s.localStock}"></td>
                    <td th:text="${s.remoteStock}"></td>
                    <td th:text="${s.updateTime}"></td>
                </tr>
                </tbody>
            </table>
        </div>
    </div>


    <!-- Chart.js 图表 -->
    <div class="card shadow-sm">
        <div class="card-body">
            <h5 class="card-title mb-3">库存对比图</h5>
            <canvas id="stockChart" height="100"></canvas>
        </div>
    </div>
</div>


<script>
    const ctx = document.getElementById('stockChart');


    async function loadChartData() {
        const res = await fetch('/admin/stock/data');
        const data = await res.json();


        const labels = data.map(d => d.productName);
        const localStocks = data.map(d => d.localStock);
        const remoteStocks = data.map(d => d.remoteStock);


        new Chart(ctx, {
            type: 'bar',
            data: {
                labels: labels,
                datasets: [
                    {
                        label: '本地库存',
                        data: localStocks,
                        borderWidth: 1
                    },
                    {
                        label: '平台库存',
                        data: remoteStocks,
                        borderWidth: 1
                    }
                ]
            },
            options: {
                responsive: true,
                scales: {
                    y: { beginAtZero: true }
                }
            }
        });
    }


    loadChartData();


    // 每5分钟刷新一次数据
    setInterval(loadChartData, 300000);
</script>


</body>
</html>
微信小程序端设计

页面

功能说明

商品映射页

维护线下与线上商品对应关系

库存状态页

展示实时库存与“立即同步”操作

同步日志页

显示最近 10 条同步结果与错误原因

基于 uni-app 实现,一套代码支持微信与支付宝小程序。

关键技术要点与避坑建议

  1. 平台 API 调用签名
  • 美团与饿了么需申请 appKeysecret
  • 请求参数需按时间戳、签名算法严格生成;
  • 控制 API 调用频率,避免被限流。
  1. 库存计算准确性
  • 仅统计“已支付未发货”订单;
  • Excel 导入库存需记录时间戳,以最新数据为准。
  1. 异常与重试机制
  • 使用 @Retryable 实现 API 自动重试;
  • 库存为负时提示“库存不足”,并记录日志。
  1. 安全与配置隔离
  • 使用 token 认证小程序用户;
  • 敏感信息通过环境变量或 Spring Config 存储。

部署方案与成本控制

项目

方案

年成本

服务器

阿里云轻量应用服务器(2核4G)

¥1000

数据库

MySQL 8.0(同机部署)

¥0

小程序认证

微信官方认证

¥300

开发成本

个人开发 2–3 周

约 ¥5000

💡 初期总成本约 ¥1300 / 年,适合小微商户快速上线。

验证与商业化路径

冷启动验证 选择 3–5 家便利店免费试用,收集反馈。

迭代优化

  • 扫码匹配商品条码
  • 缩短同步间隔(3 分钟)
  • 接入“订单推送 API”实现实时同步

盈利模式 按店铺收取月费 ¥50,30 家店即可覆盖全部成本。

结论:让库存同步真正“自动化、可信赖”

这套基于 Spring Boot + 定时任务 + 平台 API 对接 的方案,用极低的开发与运维成本,实现了稳定、精准、可扩展的库存双向同步。 它不仅节省了人工更新成本,更避免了库存误差导致的差评和损失。

先让 1 家店同步跑通,再放大规模——这是中小商户数字化转型最务实的路径。 用熟悉的 Java 技术栈,你就能轻松打造一个“自动校准库存的隐形员工”。

AI大模型学习福利

作为一名热心肠的互联网老兵,我决定把宝贵的AI知识分享给大家。 至于能学习到多少就看你的学习毅力和能力了 。我已将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

一、全套AGI大模型学习路线

AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!

因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获取

二、640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

三、AI大模型经典PDF籍

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。


因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

四、AI大模型商业化落地方案

因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

作为普通人,入局大模型时代需要持续学习和实践,不断提高自己的技能和认知水平,同时也需要有责任感和伦理意识,为人工智能的健康发展贡献力量

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值