fix: 拒绝本地旧后端返回的过期 MKH 库存 #37
@ -18,6 +18,8 @@ npm run remote:dev
|
||||
|
||||
首次启动时填写配置请求地址和部署 Key,系统自动写入被 Git 忽略的本机配置并重启后端。保存一次性显示的管理员账号和密码后直接登录,不需要在本地创建数据库或继续执行设置向导。前端默认访问 http://localhost:5173,API 默认代理到本地后端。
|
||||
|
||||
拉取主仓库更新后必须停止旧的 `npm run remote:dev` 进程并重新启动,保证前后端使用同一版本。MKH 页的强制刷新带有版本协议校验;如果新前端仍代理到未重启的旧后端,页面会停止展示可能过期的库存并明确提示重启,不再将旧值冒充为“最新数据”。
|
||||
|
||||
## 验证与构建
|
||||
|
||||
```bash
|
||||
|
||||
@ -2271,6 +2271,10 @@ async function getMkhBomList(query = {}) {
|
||||
saleWindowDays: SALE_WINDOW_DAYS,
|
||||
dataMode: preferLiveData ? 'live' : 'snapshot',
|
||||
refreshedAt: new Date().toISOString(),
|
||||
// 前后端实时刷新协议。本地前端如果代理到未重启的旧后端,
|
||||
// 将拒绝展示其返回的旧库存,而不是把缺失协议字段当成刷新成功。
|
||||
freshDataContractVersion: 1,
|
||||
freshDataApplied: forceFresh && operationalRefreshedAt > 0,
|
||||
operationalRefreshedAt: operationalRefreshedAt
|
||||
? new Date(operationalRefreshedAt).toISOString()
|
||||
: null,
|
||||
|
||||
@ -18,6 +18,7 @@ import {
|
||||
syncMkhBom,
|
||||
} from '../../api/mkhBom';
|
||||
import { createRequestGate } from '../../utils/requestGate';
|
||||
import { requireMkhFreshContract } from '../../utils/mkhFreshContract';
|
||||
import { exportTableFile } from '../../utils/tableExport';
|
||||
import './style.css';
|
||||
|
||||
@ -382,7 +383,14 @@ const MkhBomData = () => {
|
||||
setError(result?.error || '加载 MKH-BOM 资料失败');
|
||||
return;
|
||||
}
|
||||
setData(result?.data || { rows: [], total: 0, page: 1, pageSize: 50 });
|
||||
const snapshot = result?.data || null;
|
||||
try {
|
||||
requireMkhFreshContract(snapshot, { requireApplied: forceFresh });
|
||||
} catch (contractError) {
|
||||
setData({ rows: [], total: 0, page: 1, pageSize: 50, bomReady: false });
|
||||
throw contractError;
|
||||
}
|
||||
setData(snapshot);
|
||||
if (forceFresh) lastListRefreshAtRef.current = Date.now();
|
||||
setError('');
|
||||
} catch (requestError) {
|
||||
@ -908,7 +916,7 @@ const MkhBomData = () => {
|
||||
if (!result || result.success === false || !result?.data?.snapshot) {
|
||||
throw new Error(result?.error || '同步接口未返回数据库快照');
|
||||
}
|
||||
setData(result.data.snapshot);
|
||||
setData(requireMkhFreshContract(result.data.snapshot));
|
||||
setError('');
|
||||
message.success('已实时读取最新订单、库存、BOM 与仓位数据');
|
||||
} catch (syncError) {
|
||||
|
||||
15
frontend/src/utils/mkhFreshContract.js
Normal file
15
frontend/src/utils/mkhFreshContract.js
Normal file
@ -0,0 +1,15 @@
|
||||
export const MKH_FRESH_DATA_CONTRACT_VERSION = 1;
|
||||
|
||||
const OUTDATED_BACKEND_MESSAGE = '本地后端版本过旧或更新后未重启,已停止展示可能过期的库存。'
|
||||
+ '请停止当前 npm run remote:dev,拉取最新 main 后重新启动。';
|
||||
|
||||
export function requireMkhFreshContract(snapshot, { requireApplied = false } = {}) {
|
||||
const contractVersion = Number(snapshot?.freshDataContractVersion) || 0;
|
||||
if (contractVersion < MKH_FRESH_DATA_CONTRACT_VERSION) {
|
||||
throw new Error(OUTDATED_BACKEND_MESSAGE);
|
||||
}
|
||||
if (requireApplied && snapshot?.freshDataApplied !== true) {
|
||||
throw new Error('后端未确认已重读最新运营数据,已停止展示可能过期的库存,请重试。');
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
23
frontend/test/mkhFreshContract.test.mjs
Normal file
23
frontend/test/mkhFreshContract.test.mjs
Normal file
@ -0,0 +1,23 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
import { requireMkhFreshContract } from '../src/utils/mkhFreshContract.js';
|
||||
|
||||
test('MKH fresh contract accepts a backend-confirmed live refresh', () => {
|
||||
const snapshot = { freshDataContractVersion: 1, freshDataApplied: true };
|
||||
assert.equal(requireMkhFreshContract(snapshot, { requireApplied: true }), snapshot);
|
||||
});
|
||||
|
||||
test('MKH fresh contract rejects an outdated local backend instead of showing stale stock', () => {
|
||||
assert.throws(
|
||||
() => requireMkhFreshContract({ rows: [{ stock: 4 }] }, { requireApplied: true }),
|
||||
/本地后端版本过旧/
|
||||
);
|
||||
});
|
||||
|
||||
test('MKH fresh contract requires explicit refresh completion for fresh requests', () => {
|
||||
assert.throws(
|
||||
() => requireMkhFreshContract({ freshDataContractVersion: 1 }, { requireApplied: true }),
|
||||
/未确认已重读/
|
||||
);
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user