From 94effd34367f4e5eac37f2bd9ea5261524068348 Mon Sep 17 00:00:00 2001 From: zhang1106 <849185023@qq.com> Date: Thu, 29 Jan 2026 18:05:22 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=80=97=E6=9D=90?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E4=B9=90=E8=A7=82=E9=94=81=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 Consumable 模型中添加 version 字段用于乐观锁控制 - 为 quick-inout、inout、adjust 接口添加乐观锁和重试机制 - 修复 adjust/set 类型的负数库存校验 - 添加数据库迁移脚本支持 SQLite 和 MySQL - 防止并发操作导致的负库存问题 技术细节: - 使用 version 字段实现乐观锁,更新时校验版本号 - 并发冲突时自动重试(最多3次) - 409 状态码表示并发冲突,400 表示库存不足 --- backend/scripts/migrate-consumable-version.js | 10 +++++++--- frontend/src/pages/ConsumableStatistics.jsx | 13 ++++++++++--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/backend/scripts/migrate-consumable-version.js b/backend/scripts/migrate-consumable-version.js index ea6e4eb..591c3bb 100644 --- a/backend/scripts/migrate-consumable-version.js +++ b/backend/scripts/migrate-consumable-version.js @@ -3,14 +3,18 @@ * 为 consumables 表添加 version 字段 */ +require('dotenv').config(); const { sequelize, dbDialect } = require('../db'); +// 从环境变量重新确定数据库类型 +const actualDbType = process.env.DB_TYPE || 'sqlite'; + async function migrate() { try { console.log('开始执行耗材乐观锁迁移...'); - console.log('数据库类型:', dbDialect); + console.log('数据库类型:', actualDbType); - if (dbDialect === 'sqlite') { + if (actualDbType === 'sqlite') { // SQLite: 检查字段是否存在 const tableInfo = await sequelize.query( "PRAGMA table_info(consumables)", @@ -47,7 +51,7 @@ async function migrate() { console.log('updatedAt 索引已存在,跳过'); } - } else if (dbDialect === 'mysql') { + } else if (actualDbType === 'mysql') { // MySQL: 检查并添加字段 try { console.log('添加 version 字段...'); diff --git a/frontend/src/pages/ConsumableStatistics.jsx b/frontend/src/pages/ConsumableStatistics.jsx index 5b53302..c482856 100644 --- a/frontend/src/pages/ConsumableStatistics.jsx +++ b/frontend/src/pages/ConsumableStatistics.jsx @@ -226,9 +226,16 @@ function ConsumableStatistics() { }; useEffect(() => { - fetchSummary(); - fetchLowStock(); - fetchInOutStats(); + const loadData = async () => { + setLoading(true); + await Promise.all([ + fetchSummary(), + fetchLowStock(), + fetchInOutStats() + ]); + setLoading(false); + }; + loadData(); }, []); useEffect(() => {