@ray-js/lamp-schedule-core 1.0.5-beta.6 → 1.0.5-beta.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -69,12 +69,11 @@ export class WakeUpSigmesh {
69
69
  var _data$nodes;
70
70
  scheduleLogger.debug('Sigmesh dpParser ===> WakeUpSigmesh formatter: data', data);
71
71
  // dataMode 可能是字符串 ('01'/'02'/'03') 或数字 (1/2/3)
72
- const dataModeNum = typeof data.dataMode === 'string' ? parseInt(data.dataMode, 10) : data.dataMode;
73
- // 使用 nodes.length 而非 data.length,因为调用方可能未更新 length 字段
72
+ typeof data.dataMode === 'string' ? parseInt(data.dataMode, 10) : data.dataMode; // 使用 nodes.length 而非 data.length,因为调用方可能未更新 length 字段
74
73
  const nodeCount = (_data$nodes = data.nodes) === null || _data$nodes === void 0 ? void 0 : _data$nodes.length;
75
74
  // 版本号固定为 1, 与文档不一致,文档有问题
76
75
 
77
- const result = [numToHexString(1), numToHexString(dataModeNum), numToHexString(nodeCount)];
76
+ const result = [numToHexString(1), numToHexString(1), numToHexString(nodeCount)];
78
77
  data.nodes.forEach(node => {
79
78
  var _node$loops;
80
79
  result.push(numToHexString(node.onOff ? 1 : 0), numToHexString(parseInt(node === null || node === void 0 || (_node$loops = node.loops) === null || _node$loops === void 0 || (_node$loops = _node$loops.split('')) === null || _node$loops === void 0 || (_node$loops = _node$loops.reverse()) === null || _node$loops === void 0 ? void 0 : _node$loops.join(''), 2)), numToHexString(node.step), numToHexString(node.hour), numToHexString(node.minute), numToHexString(node.brightness), numToHexString(node.temperature), numToHexString(node.duration));
@@ -1,5 +1,5 @@
1
1
  import { act, renderHook } from '@testing-library/react-hooks';
2
- import { useBaseLightDp, getDpDataByMesh, fetchDpData, useFetchDpDataByMesh, useFetchDpData } from '../useBaseLightDp';
2
+ import { useBaseLightDp, getDpDataByMesh, fetchDpData, useFetchDpDataByMesh, useFetchDpData, __clearFetchDpDataThrottleCacheForTest } from '../useBaseLightDp';
3
3
  import { DP_CHANGE_EVENT_KEY } from '../../constant';
4
4
  import { devIdOrGroupIdCache } from '../../utils/ScheduleCache';
5
5
  import { getDpIdByDpCode } from '../../utils/ScheduleUtils';
@@ -184,6 +184,7 @@ describe('getDpDataByMesh', () => {
184
184
  });
185
185
  describe('fetchDpData', () => {
186
186
  beforeEach(() => {
187
+ __clearFetchDpDataThrottleCacheForTest();
187
188
  jest.clearAllMocks();
188
189
  devIdOrGroupIdCache.get.mockReturnValue({
189
190
  devId: 'device-1',
@@ -210,9 +211,15 @@ describe('fetchDpData', () => {
210
211
  dpIds: ['2']
211
212
  }));
212
213
  });
214
+ it('skips duplicate fetch for same dpCodes within throttle window', () => {
215
+ fetchDpData(['bright_value']);
216
+ fetchDpData(['bright_value']);
217
+ expect(queryDps).toHaveBeenCalledTimes(1);
218
+ });
213
219
  });
214
220
  describe('useFetch hooks', () => {
215
221
  beforeEach(() => {
222
+ __clearFetchDpDataThrottleCacheForTest();
216
223
  jest.clearAllMocks();
217
224
  getDpIdByDpCode.mockReturnValue(3);
218
225
  devIdOrGroupIdCache.get.mockReturnValue({
@@ -232,7 +239,8 @@ describe('useFetch hooks', () => {
232
239
  act(() => {
233
240
  result.current.refresh();
234
241
  });
235
- expect(queryDps).toHaveBeenCalledTimes(2);
242
+ // 同一组 dpCodes 在 3s 内被节流,refresh 不会触发第二次请求
243
+ expect(queryDps).toHaveBeenCalledTimes(1);
236
244
  });
237
245
  it('useFetchDpDataByMesh skips when not sigmesh', () => {
238
246
  useSupport.mockReturnValue({
@@ -10,6 +10,8 @@ type TDpRes<T> = {
10
10
  };
11
11
  export declare function useBaseLightDp<T>(dpCode: string, defaultDpValue?: T): TDpRes<T>;
12
12
  export declare const getDpDataByMesh: (dpCodes?: string[]) => void;
13
+ /** 仅用于测试:清空 fetchDpData 节流缓存,避免用例间相互影响 */
14
+ export declare const __clearFetchDpDataThrottleCacheForTest: () => void;
13
15
  export declare const fetchDpData: (dpCodes?: string[]) => Promise<void>;
14
16
  /**
15
17
  * 主动拉取 dp 数据, 仅支持 sigmesh 设备
@@ -21,7 +23,10 @@ export declare const useFetchDpDataByMesh: (dpCodes: string[]) => {
21
23
  };
22
24
  /**
23
25
  * 主动拉取 dp 数据, 不限制协议
24
- * @param dpCodes dpCode 数组
26
+ * 同一组 dpCodes 3s 内只会实际拉取一次(模块级节流)。
27
+ * @param dpCodes dpCode 数组,建议使用 useMemo 稳定引用,避免因引用变化导致 effect 重复执行,例如:
28
+ * const dpCodes = useMemo(() => [scheduleDpCodes.SLEEP_MODE, scheduleDpCodes.WAKE_UP_MODE], []);
29
+ * useFetchDpData(dpCodes, { initFetch: true, offsetTime: 0 });
25
30
  * @param options 拉取配置
26
31
  * @param options.initFetch 是否初始化拉取
27
32
  * @param options.offsetTime 延迟时间 ms
@@ -1,5 +1,6 @@
1
1
  import "core-js/modules/esnext.iterator.constructor.js";
2
2
  import "core-js/modules/esnext.iterator.filter.js";
3
+ import "core-js/modules/esnext.iterator.for-each.js";
3
4
  import "core-js/modules/esnext.iterator.map.js";
4
5
  /* eslint-disable no-console */
5
6
  import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
@@ -140,8 +141,20 @@ export const getDpDataByMesh = function () {
140
141
  }
141
142
  };
142
143
 
144
+ /** 模块级缓存:同一组 dpCodes 最近一次拉取时间,用于 3s 内去重 */
145
+ const lastFetchTimeByKey = {};
146
+
147
+ /** 相同 dpCodesKey 节流时间(毫秒) */
148
+ const FETCH_DP_THROTTLE_MS = 3000;
149
+
150
+ /** 仅用于测试:清空 fetchDpData 节流缓存,避免用例间相互影响 */
151
+ export const __clearFetchDpDataThrottleCacheForTest = () => {
152
+ Object.keys(lastFetchTimeByKey).forEach(k => delete lastFetchTimeByKey[k]);
153
+ };
154
+
143
155
  // 查询设备的 DP
144
156
  export const fetchDpData = function () {
157
+ var _lastFetchTimeByKey$k;
145
158
  let dpCodes = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
146
159
  if (!Array.isArray(dpCodes)) {
147
160
  scheduleLogger.error('fetchDpData: 参数dpCodes必须是数组');
@@ -162,6 +175,16 @@ export const fetchDpData = function () {
162
175
  scheduleLogger.warn('fetchDpData', dpCodes, '未匹配到 dp');
163
176
  return;
164
177
  }
178
+
179
+ // 模块级节流:同一组 dpCodes 在 3s 内只拉取一次
180
+ const key = dpCodes.slice().sort().join(',');
181
+ const now = Date.now();
182
+ const lastFetch = (_lastFetchTimeByKey$k = lastFetchTimeByKey[key]) !== null && _lastFetchTimeByKey$k !== void 0 ? _lastFetchTimeByKey$k : 0;
183
+ if (now - lastFetch < FETCH_DP_THROTTLE_MS) {
184
+ scheduleLogger.debug('fetchDpData: 相同 dpCodes 在 3s 内已拉取过,已跳过', dpCodes);
185
+ return;
186
+ }
187
+ lastFetchTimeByKey[key] = now;
165
188
  scheduleLogger.debug('fetchDpData 查询DP转换后参数 dpIds:', dpIds);
166
189
  try {
167
190
  queryDps({
@@ -210,7 +233,10 @@ export const useFetchDpDataByMesh = dpCodes => {
210
233
 
211
234
  /**
212
235
  * 主动拉取 dp 数据, 不限制协议
213
- * @param dpCodes dpCode 数组
236
+ * 同一组 dpCodes 3s 内只会实际拉取一次(模块级节流)。
237
+ * @param dpCodes dpCode 数组,建议使用 useMemo 稳定引用,避免因引用变化导致 effect 重复执行,例如:
238
+ * const dpCodes = useMemo(() => [scheduleDpCodes.SLEEP_MODE, scheduleDpCodes.WAKE_UP_MODE], []);
239
+ * useFetchDpData(dpCodes, { initFetch: true, offsetTime: 0 });
214
240
  * @param options 拉取配置
215
241
  * @param options.initFetch 是否初始化拉取
216
242
  * @param options.offsetTime 延迟时间 ms
@@ -221,34 +247,25 @@ export const useFetchDpData = function (dpCodes) {
221
247
  initFetch: true,
222
248
  offsetTime: 0
223
249
  };
224
- // 使用 useMemo 稳定 dpCodes 的字符串表示,避免因数组引用变化导致重复执行
225
- // 注意:这里依赖 dpCodes 数组本身,但由于调用方已使用 useMemo 稳定引用,所以是安全的
226
250
  const dpCodesKey = useMemo(() => dpCodes.join(','), [dpCodes]);
227
251
  const getDpDataFn = useCallback(() => {
228
- scheduleLogger.debug('useFetchDpData getDpDataFn get1:', dpCodes);
252
+ scheduleLogger.debug('useFetchDpData getDpDataFn get:', dpCodes);
229
253
  fetchDpData(dpCodes);
230
254
  }, [dpCodes]);
231
-
232
- // 使用 useRef 跟踪上一次的 dpCodesKey,避免重复执行
233
- const prevDpCodesKeyRef = useRef(null);
255
+ const getDpDataFnRef = useRef(getDpDataFn);
256
+ getDpDataFnRef.current = getDpDataFn;
234
257
  useEffect(() => {
235
258
  if (!options.initFetch) {
236
259
  return () => null;
237
260
  }
238
- // 只有当 dpCodesKey 真正变化时才执行
239
- if (prevDpCodesKeyRef.current === dpCodesKey) {
240
- scheduleLogger.debug('useFetchDpData useEffect: dpCodesKey unchanged', dpCodesKey);
241
- return () => null;
242
- }
243
- prevDpCodesKeyRef.current = dpCodesKey;
244
261
  const timer = setTimeout(() => {
245
- scheduleLogger.debug('useFetchDpData useEffect get:', dpCodes);
246
- getDpDataFn();
262
+ scheduleLogger.debug('useFetchDpData useEffect get:', dpCodesKey);
263
+ getDpDataFnRef.current();
247
264
  }, options.offsetTime || 0);
248
265
  return () => {
249
266
  clearTimeout(timer);
250
267
  };
251
- }, [dpCodesKey, getDpDataFn, dpCodes, options.initFetch, options.offsetTime]);
268
+ }, [dpCodesKey, options.initFetch, options.offsetTime]);
252
269
  return {
253
270
  refresh: getDpDataFn
254
271
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ray-js/lamp-schedule-core",
3
- "version": "1.0.5-beta.6",
3
+ "version": "1.0.5-beta.8",
4
4
  "description": "照明计划模块核心能力",
5
5
  "main": "./lib/index.js",
6
6
  "files": [