@pisell/pisellos 2.2.78 → 2.2.80

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.
@@ -23,11 +23,11 @@ __export(products_exports, {
23
23
  });
24
24
  module.exports = __toCommonJS(products_exports);
25
25
  var import_BaseModule = require("../../../modules/BaseModule");
26
- var import_lodash_es = require("lodash-es");
27
26
  var import_plugins = require("../../../plugins");
28
27
  var import_types = require("./types");
29
28
  var import_product = require("../../utils/product");
30
29
  var INDEXDB_STORE_NAME = "products";
30
+ var PRODUCT_SYNC_DEBOUNCE_MS = 1e4;
31
31
  var ProductsModule = class extends import_BaseModule.BaseModule {
32
32
  constructor(name, version) {
33
33
  super(name, version);
@@ -44,6 +44,7 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
44
44
  this.formatters = [];
45
45
  // 是否已注册内置价格格式化器
46
46
  this.isPriceFormatterRegistered = false;
47
+ this.pendingSyncMessages = [];
47
48
  }
48
49
  async initialize(core, options) {
49
50
  var _a;
@@ -71,6 +72,8 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
71
72
  }
72
73
  }
73
74
  this.registerBuiltinPriceFormatter();
75
+ this.initProductDataSource();
76
+ this.setupProductSync();
74
77
  this.logInfo("模块初始化完成", {
75
78
  hasDbManager: !!this.dbManager,
76
79
  hasLogger: !!this.logger,
@@ -186,29 +189,32 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
186
189
  * @returns 应用了价格的商品列表
187
190
  */
188
191
  async getProductsWithPrice(schedule_date, extraContext) {
192
+ const t0 = performance.now();
189
193
  const cacheKey = schedule_date;
190
194
  if (this.productsPriceCache.has(cacheKey)) {
191
- console.log(`[ProductsModule] 💰 商品价格缓存命中: ${cacheKey}`);
192
195
  const cachedProducts = this.productsPriceCache.get(cacheKey);
196
+ (0, import_product.perfMark)("getProductsWithPrice(cacheHit)", performance.now() - t0, {
197
+ cacheKey,
198
+ count: cachedProducts.length
199
+ });
193
200
  this.logInfo("商品价格缓存命中", {
194
201
  cacheKey,
195
202
  productCount: cachedProducts.length
196
203
  });
197
204
  return cachedProducts;
198
205
  }
199
- console.log(`[ProductsModule] 🌐 获取商品并应用价格: ${cacheKey}`);
200
206
  this.logInfo("商品价格缓存未命中,准备获取", { cacheKey });
201
- const startTime = Date.now();
202
207
  const result = await this.prepareProductsWithPrice(schedule_date, extraContext);
203
- const duration = Date.now() - startTime;
204
208
  this.productsPriceCache.set(cacheKey, result);
205
- console.log(`[ProductsModule] ✅ 商品价格已缓存: ${cacheKey}, 共 ${result.length} 个商品`);
206
209
  this.logInfo("商品价格已缓存", {
207
210
  cacheKey,
208
- productCount: result.length,
209
- duration: `${duration}ms`
211
+ productCount: result.length
210
212
  });
211
213
  this.cleanExpiredPriceCache();
214
+ (0, import_product.perfMark)("getProductsWithPrice(cacheMiss)", performance.now() - t0, {
215
+ cacheKey,
216
+ count: result.length
217
+ });
212
218
  return result;
213
219
  }
214
220
  /**
@@ -219,27 +225,35 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
219
225
  * @private
220
226
  */
221
227
  async prepareProductsWithPrice(schedule_date, extraContext) {
228
+ const tTotal = performance.now();
222
229
  this.logInfo("prepareProductsWithPrice 开始处理", { schedule_date });
223
230
  try {
224
- const allProducts = await this.getProducts();
231
+ const allProducts = this.getProductsRef();
225
232
  this.logInfo("获取到商品列表", { productCount: allProducts.length });
226
- console.log(`[ProductsModule] 🌐 开始获取商品报价单价格`);
233
+ const tIds = performance.now();
234
+ const ids = new Array(allProducts.length);
235
+ for (let i = 0; i < allProducts.length; i++) {
236
+ ids[i] = allProducts[i].id;
237
+ }
238
+ (0, import_product.perfMark)("prepareProducts.extractIds", performance.now() - tIds, { count: ids.length });
239
+ const tPrice = performance.now();
227
240
  const priceData = await this.loadProductsPrice({
228
- ids: allProducts.map((product) => product.id).sort((a, b) => a - b),
241
+ ids,
229
242
  schedule_date
230
243
  });
231
- console.log(`[ProductsModule] 🌐 获取商品报价单价格成功`, priceData);
244
+ (0, import_product.perfMark)("prepareProducts.loadPrice", performance.now() - tPrice, { count: ids.length });
232
245
  this.logInfo("获取商品报价单价格成功", { priceDataCount: (priceData == null ? void 0 : priceData.length) ?? 0 });
233
246
  const context = {
234
247
  schedule_date,
235
248
  priceData,
236
249
  ...extraContext
237
- // 合并 Server 层传入的额外数据(如 scheduleList)
238
250
  };
239
- console.log(`[ProductsModule] 🌐 通过格式化器流程处理商品(包括价格应用、字段扩展等)`);
240
- this.logInfo("开始通过格式化器流程处理商品", { formatterCount: this.formatters.length });
251
+ const tFormat = performance.now();
241
252
  const processedProducts = await this.applyFormatters(allProducts, context);
242
- console.log(`[ProductsModule] 🌐 通过格式化器流程处理商品(包括价格应用、字段扩展等)成功`, processedProducts);
253
+ (0, import_product.perfMark)("prepareProducts.applyFormatters", performance.now() - tFormat, {
254
+ count: allProducts.length,
255
+ formatterCount: this.formatters.length
256
+ });
243
257
  this.logInfo("prepareProductsWithPrice 处理完成", {
244
258
  originalProductCount: allProducts.length,
245
259
  processedProductCount: processedProducts.length,
@@ -249,6 +263,10 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
249
263
  import_types.ProductsHooks.onProductsPriceApplied,
250
264
  processedProducts
251
265
  );
266
+ (0, import_product.perfMark)("prepareProductsWithPrice", performance.now() - tTotal, {
267
+ productCount: allProducts.length,
268
+ formatterCount: this.formatters.length
269
+ });
252
270
  return processedProducts;
253
271
  } catch (err) {
254
272
  const errorMessage = err instanceof Error ? err.message : String(err);
@@ -281,8 +299,13 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
281
299
  for (let i = 0; i < this.formatters.length; i++) {
282
300
  const formatter = this.formatters[i];
283
301
  try {
284
- console.log(`[ProductsModule] 📝 应用格式化器 ${i + 1}/${this.formatters.length}`);
302
+ const tF = performance.now();
285
303
  result = await formatter(result, context);
304
+ (0, import_product.perfMark)(`applyFormatters[${i}]`, performance.now() - tF, {
305
+ index: i,
306
+ total: this.formatters.length,
307
+ productCount: result.length
308
+ });
286
309
  } catch (error) {
287
310
  const errorMessage = error instanceof Error ? error.message : String(error);
288
311
  console.error(`[ProductsModule] ❌ 格式化器 ${i + 1} 执行失败:`, error);
@@ -293,7 +316,6 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
293
316
  });
294
317
  }
295
318
  }
296
- console.log(`[ProductsModule] ✅ 所有格式化器已应用,共 ${this.formatters.length} 个`);
297
319
  this.logInfo("所有格式化器已应用", {
298
320
  formatterCount: this.formatters.length,
299
321
  resultProductCount: result.length
@@ -387,10 +409,45 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
387
409
  console.log("[ProductsModule] 🗑️ 商品价格缓存已清空");
388
410
  }
389
411
  /**
390
- * 加载完整商品列表通过接口(包含所有详细数据)
412
+ * 通过 ProductDataSource SSE 加载完整商品列表
413
+ */
414
+ async loadProductsByServer() {
415
+ if (!this.productDataSource) {
416
+ this.logWarning("loadProductsByServer: ProductDataSource 不可用");
417
+ return [];
418
+ }
419
+ this.logInfo("开始通过 DataSource SSE 加载商品列表");
420
+ const t0 = performance.now();
421
+ try {
422
+ const tSSE = performance.now();
423
+ const productList = await this.productDataSource.run({ sse: {} });
424
+ const list = productList || [];
425
+ (0, import_product.perfMark)("loadProductsByServer.SSE", performance.now() - tSSE, { count: list.length });
426
+ this.logInfo("通过 DataSource SSE 加载商品列表成功", {
427
+ productCount: list.length,
428
+ duration: `${Math.round(performance.now() - t0)}ms`
429
+ });
430
+ await this.saveProductsToIndexDB(list);
431
+ await this.core.effects.emit(import_types.ProductsHooks.onProductsLoaded, list);
432
+ (0, import_product.perfMark)("loadProductsByServer", performance.now() - t0, { count: list.length });
433
+ return list;
434
+ } catch (error) {
435
+ const duration = Math.round(performance.now() - t0);
436
+ const errorMessage = error instanceof Error ? error.message : String(error);
437
+ console.error("[Products] 加载商品数据失败:", error);
438
+ this.logError("通过 DataSource SSE 加载商品列表失败", {
439
+ duration: `${duration}ms`,
440
+ error: errorMessage
441
+ });
442
+ return [];
443
+ }
444
+ }
445
+ /**
446
+ * 纯请求方法:通过 HTTP 接口获取商品列表(无副作用,不触发事件、不写 IndexDB)
391
447
  * @param params 查询参数
448
+ * @returns 商品列表
392
449
  */
393
- async loadProductsByServer(params) {
450
+ async fetchProductsByHttp(params) {
394
451
  var _a, _b;
395
452
  const {
396
453
  category_ids = [],
@@ -399,7 +456,7 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
399
456
  customer_id,
400
457
  cacheId
401
458
  } = params || {};
402
- this.logInfo("开始从服务器加载商品列表", {
459
+ this.logInfo("fetchProductsByHttp: 开始请求", {
403
460
  categoryIdsCount: category_ids.length,
404
461
  productIdsCount: product_ids.length,
405
462
  collectionCount: Array.isArray(collection) ? collection.length : collection ? 1 : 0,
@@ -420,16 +477,12 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
420
477
  "event_item"
421
478
  ],
422
479
  force_ignore_cache_flag: 1,
423
- // 获取所有详细信息
424
480
  with: [
425
481
  "category",
426
482
  "collection",
427
483
  "resourceRelation",
428
- // 套餐
429
484
  "bundleGroup.bundleItem",
430
- // 单规格
431
485
  "optionGroup.optionItem",
432
- // 组合规格
433
486
  "variantGroup.variantItem"
434
487
  ],
435
488
  open_deposit: 1,
@@ -449,21 +502,16 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
449
502
  );
450
503
  const productList = ((_b = productsData == null ? void 0 : productsData.data) == null ? void 0 : _b.list) || [];
451
504
  const duration = Date.now() - startTime;
452
- this.logInfo("从服务器加载商品列表成功", {
505
+ this.logInfo("fetchProductsByHttp: 请求成功", {
453
506
  productCount: productList.length,
454
507
  duration: `${duration}ms`
455
508
  });
456
- await this.saveProductsToIndexDB(productList);
457
- await this.core.effects.emit(
458
- import_types.ProductsHooks.onProductsLoaded,
459
- productsData.data.list
460
- );
461
- return productsData.data.list;
509
+ return productList;
462
510
  } catch (error) {
463
511
  const duration = Date.now() - startTime;
464
512
  const errorMessage = error instanceof Error ? error.message : String(error);
465
- console.error("[Products] 加载商品数据失败:", error);
466
- this.logError("从服务器加载商品列表失败", {
513
+ console.error("[Products] fetchProductsByHttp 失败:", error);
514
+ this.logError("fetchProductsByHttp: 请求失败", {
467
515
  duration: `${duration}ms`,
468
516
  error: errorMessage
469
517
  });
@@ -471,10 +519,33 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
471
519
  }
472
520
  }
473
521
  /**
474
- * 获取商品列表(从缓存)
522
+ * 加载完整商品列表通过接口(包含所有详细数据)
523
+ * 包含副作用:保存到 IndexDB + 触发 onProductsLoaded 事件
524
+ * @param params 查询参数
525
+ */
526
+ async loadProductsByServerHttp(params) {
527
+ const productList = await this.fetchProductsByHttp(params);
528
+ if (productList.length > 0) {
529
+ await this.saveProductsToIndexDB(productList);
530
+ await this.core.effects.emit(import_types.ProductsHooks.onProductsLoaded, productList);
531
+ }
532
+ return productList;
533
+ }
534
+ /**
535
+ * 获取商品列表(深拷贝,供外部安全使用)
475
536
  */
476
537
  async getProducts() {
477
- return (0, import_lodash_es.cloneDeep)(this.store.list);
538
+ const t0 = performance.now();
539
+ const result = structuredClone(this.store.list);
540
+ (0, import_product.perfMark)("ProductsModule.getProducts(structuredClone)", performance.now() - t0, { count: result.length });
541
+ return result;
542
+ }
543
+ /**
544
+ * 内部获取商品列表的直接引用(无拷贝)
545
+ * 仅供内部 formatter 流程使用,因为 formatter 会创建新对象
546
+ */
547
+ getProductsRef() {
548
+ return this.store.list;
478
549
  }
479
550
  /**
480
551
  * 根据ID获取单个商品(从内存缓存)
@@ -482,7 +553,79 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
482
553
  */
483
554
  async getProductById(id) {
484
555
  const product = this.store.map.get(id);
485
- return product ? (0, import_lodash_es.cloneDeep)(product) : void 0;
556
+ return product ? structuredClone(product) : void 0;
557
+ }
558
+ /**
559
+ * 根据 ID 列表删除商品(用于 pubsub 同步删除场景)
560
+ * 同时更新 store.list、store.map、IndexDB 和价格缓存
561
+ */
562
+ async removeProductsByIds(ids) {
563
+ const idSet = new Set(ids);
564
+ this.logInfo("removeProductsByIds", { ids, count: ids.length });
565
+ this.store.list = this.store.list.filter((p) => !idSet.has(p.id));
566
+ for (const id of ids) {
567
+ this.store.map.delete(id);
568
+ }
569
+ if (this.dbManager) {
570
+ try {
571
+ for (const id of ids) {
572
+ await this.dbManager.delete(INDEXDB_STORE_NAME, id);
573
+ }
574
+ } catch (error) {
575
+ const errorMessage = error instanceof Error ? error.message : String(error);
576
+ this.logError("removeProductsByIds: IndexDB 删除失败", { ids, error: errorMessage });
577
+ }
578
+ }
579
+ for (const [dateKey, cachedProducts] of this.productsPriceCache.entries()) {
580
+ this.productsPriceCache.set(
581
+ dateKey,
582
+ cachedProducts.filter((p) => !idSet.has(p.id))
583
+ );
584
+ }
585
+ this.core.effects.emit(import_types.ProductsHooks.onProductsChanged, this.store.list);
586
+ this.logInfo("removeProductsByIds 完成", { remaining: this.store.list.length });
587
+ }
588
+ /**
589
+ * 重新从服务器加载全量商品列表并更新本地 store
590
+ * 用于 pubsub 同步 create / update / batch_update 场景
591
+ */
592
+ async refreshProducts() {
593
+ const tTotal = performance.now();
594
+ this.logInfo("refreshProducts 开始");
595
+ const products = await this.loadProductsByServer();
596
+ if (products && products.length > 0) {
597
+ this.store.list = products;
598
+ this.syncProductsMap();
599
+ this.core.effects.emit(import_types.ProductsHooks.onProductsChanged, this.store.list);
600
+ this.logInfo("refreshProducts 完成", { productCount: products.length });
601
+ } else {
602
+ this.logWarning("refreshProducts: 服务器未返回数据");
603
+ }
604
+ (0, import_product.perfMark)("refreshProducts", performance.now() - tTotal, { count: this.store.list.length });
605
+ return this.store.list;
606
+ }
607
+ /**
608
+ * 局部更新指定商品的报价单价格
609
+ * 遍历所有已缓存的日期,为目标商品重新获取价格并覆盖到缓存中
610
+ */
611
+ async updateProductPriceByIds(ids) {
612
+ this.logInfo("updateProductPriceByIds", { ids });
613
+ for (const [dateKey, cachedProducts] of this.productsPriceCache.entries()) {
614
+ try {
615
+ const priceData = await this.loadProductsPrice({
616
+ ids,
617
+ schedule_date: dateKey
618
+ });
619
+ if (priceData && priceData.length > 0) {
620
+ const updatedProducts = (0, import_product.applyPriceDataToProducts)(cachedProducts, priceData);
621
+ this.productsPriceCache.set(dateKey, updatedProducts);
622
+ this.logInfo("updateProductPriceByIds: 缓存已更新", { dateKey, priceDataCount: priceData.length });
623
+ }
624
+ } catch (error) {
625
+ const errorMessage = error instanceof Error ? error.message : String(error);
626
+ this.logError("updateProductPriceByIds: 失败", { dateKey, ids, error: errorMessage });
627
+ }
628
+ }
486
629
  }
487
630
  /**
488
631
  * 清空缓存
@@ -518,7 +661,9 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
518
661
  return [];
519
662
  }
520
663
  try {
664
+ const t0 = performance.now();
521
665
  const products = await this.dbManager.getAll(INDEXDB_STORE_NAME);
666
+ (0, import_product.perfMark)("loadProductsFromIndexDB", performance.now() - t0, { count: (products == null ? void 0 : products.length) ?? 0 });
522
667
  this.logInfo("从 IndexDB 加载商品数据", {
523
668
  productCount: (products == null ? void 0 : products.length) ?? 0
524
669
  });
@@ -541,11 +686,10 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
541
686
  }
542
687
  this.logInfo("开始保存商品数据到 IndexDB", { productCount: products.length });
543
688
  try {
689
+ const t0 = performance.now();
544
690
  await this.dbManager.clear(INDEXDB_STORE_NAME);
545
- const savePromises = products.map(
546
- (product) => this.dbManager.add(INDEXDB_STORE_NAME, product)
547
- );
548
- await Promise.all(savePromises);
691
+ await this.dbManager.bulkAdd(INDEXDB_STORE_NAME, products);
692
+ (0, import_product.perfMark)("saveProductsToIndexDB", performance.now() - t0, { count: products.length });
549
693
  console.log(
550
694
  `[Products] 已将 ${products.length} 个商品平铺保存到 IndexDB`
551
695
  );
@@ -565,11 +709,12 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
565
709
  * @private
566
710
  */
567
711
  syncProductsMap() {
712
+ const t0 = performance.now();
568
713
  this.store.map.clear();
569
714
  for (const product of this.store.list) {
570
715
  this.store.map.set(product.id, product);
571
716
  }
572
- console.log(`[Products] Map 缓存已同步,共 ${this.store.map.size} 个商品`);
717
+ (0, import_product.perfMark)("syncProductsMap", performance.now() - t0, { count: this.store.map.size });
573
718
  }
574
719
  /**
575
720
  * 预加载模块数据(统一接口)
@@ -577,22 +722,29 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
577
722
  */
578
723
  async preload() {
579
724
  console.log("[Products] 开始预加载数据...");
580
- const startTime = Date.now();
725
+ const tTotal = performance.now();
581
726
  this.logInfo("开始预加载数据");
582
727
  try {
728
+ const tIndexDB = performance.now();
583
729
  const cachedData = await this.loadProductsFromIndexDB();
730
+ (0, import_product.perfMark)("preload.loadFromIndexDB", performance.now() - tIndexDB, { count: (cachedData == null ? void 0 : cachedData.length) ?? 0 });
584
731
  if (cachedData && cachedData.length > 0) {
585
732
  console.log(`[Products] 从 IndexDB 加载了 ${cachedData.length} 个商品`);
586
- this.store.list = (0, import_lodash_es.cloneDeep)(cachedData);
733
+ this.store.list = cachedData;
734
+ const tSync = performance.now();
587
735
  this.syncProductsMap();
736
+ (0, import_product.perfMark)("preload.syncProductsMap", performance.now() - tSync, { count: cachedData.length });
588
737
  this.core.effects.emit(
589
738
  import_types.ProductsHooks.onProductsChanged,
590
739
  this.store.list
591
740
  );
592
- const duration = Date.now() - startTime;
741
+ (0, import_product.perfMark)("preload(IndexDB)", performance.now() - tTotal, {
742
+ count: cachedData.length,
743
+ source: "IndexDB"
744
+ });
593
745
  this.logInfo("预加载完成(从 IndexDB)", {
594
746
  productCount: cachedData.length,
595
- duration: `${duration}ms`,
747
+ duration: `${Math.round(performance.now() - tTotal)}ms`,
596
748
  source: "IndexDB"
597
749
  });
598
750
  return;
@@ -604,25 +756,417 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
604
756
  console.warn("[Products] 从 IndexDB 加载数据失败:", error);
605
757
  this.logWarning("从 IndexDB 加载数据失败,准备从服务器加载", { error: errorMessage });
606
758
  }
759
+ const tServer = performance.now();
607
760
  const products = await this.loadProductsByServer();
761
+ (0, import_product.perfMark)("preload.loadFromServer", performance.now() - tServer, { count: (products == null ? void 0 : products.length) ?? 0 });
608
762
  if (products && products.length > 0) {
609
- await this.saveProductsToIndexDB(products);
610
- this.store.list = (0, import_lodash_es.cloneDeep)(products);
763
+ this.store.list = products;
764
+ const tSync = performance.now();
611
765
  this.syncProductsMap();
766
+ (0, import_product.perfMark)("preload.syncProductsMap", performance.now() - tSync, { count: products.length });
612
767
  this.core.effects.emit(import_types.ProductsHooks.onProductsChanged, this.store.list);
613
- const duration = Date.now() - startTime;
768
+ (0, import_product.perfMark)("preload(Server)", performance.now() - tTotal, {
769
+ count: products.length,
770
+ source: "Server"
771
+ });
614
772
  this.logInfo("预加载完成(从服务器)", {
615
773
  productCount: products.length,
616
- duration: `${duration}ms`,
774
+ duration: `${Math.round(performance.now() - tTotal)}ms`,
617
775
  source: "Server"
618
776
  });
619
777
  } else {
620
- const duration = Date.now() - startTime;
778
+ (0, import_product.perfMark)("preload(empty)", performance.now() - tTotal, { source: "empty" });
621
779
  this.logWarning("预加载完成但未获取到数据", {
622
- duration: `${duration}ms`
780
+ duration: `${Math.round(performance.now() - tTotal)}ms`
623
781
  });
624
782
  }
625
783
  }
784
+ // =============================================
785
+ // 商品数据同步(pubsub)
786
+ // =============================================
787
+ /**
788
+ * 初始化 ProductDataSource 实例
789
+ * 与 pubsub 订阅和数据获取分开,仅负责创建实例
790
+ */
791
+ initProductDataSource() {
792
+ var _a, _b;
793
+ const ProductDataSourceClass = (_b = (_a = this.core.serverOptions) == null ? void 0 : _a.All_DATA_SOURCES) == null ? void 0 : _b.ProductDataSource;
794
+ if (!ProductDataSourceClass) {
795
+ this.logWarning("initProductDataSource: ProductDataSource 不可用");
796
+ return;
797
+ }
798
+ this.productDataSource = new ProductDataSourceClass();
799
+ this.logInfo("ProductDataSource 实例已创建");
800
+ }
801
+ /**
802
+ * 初始化 pubsub 订阅,监听管理后台商品变更
803
+ * 仅负责订阅 product / product_quotation 频道,消息通过防抖合并后批量处理
804
+ * 数据获取由 loadProductsByServer 单独负责
805
+ */
806
+ setupProductSync() {
807
+ if (!this.productDataSource) {
808
+ this.logWarning("setupProductSync: ProductDataSource 不可用,跳过同步初始化");
809
+ return;
810
+ }
811
+ const createHandler = (channelKey) => (message) => {
812
+ const data = (message == null ? void 0 : message.data) || message;
813
+ if (!data)
814
+ return;
815
+ console.log(`[ProductsModule] 收到同步消息 [${channelKey}]:`, data);
816
+ this.logInfo("收到同步消息", {
817
+ channelKey,
818
+ action: data.action,
819
+ id: data.id,
820
+ action_filed: data.action_filed
821
+ });
822
+ this.pendingSyncMessages.push({ ...data, _channelKey: channelKey });
823
+ if (this.syncTimer) {
824
+ clearTimeout(this.syncTimer);
825
+ }
826
+ this.syncTimer = setTimeout(() => {
827
+ this.processProductSyncMessages();
828
+ }, PRODUCT_SYNC_DEBOUNCE_MS);
829
+ };
830
+ this.productDataSource.run({
831
+ pubsub: {
832
+ callback: (res) => {
833
+ console.log("sse_products_callback", res);
834
+ this.logInfo("sse_products_callback: 收到同步消息", {
835
+ data: res == null ? void 0 : res.data
836
+ });
837
+ if (!(res == null ? void 0 : res.data))
838
+ return;
839
+ const data = res.data;
840
+ const channelKey = data.module || "product";
841
+ createHandler(channelKey)(data);
842
+ }
843
+ }
844
+ }).catch((err) => {
845
+ this.logError("setupProductSync: DataSource run 出错", {
846
+ error: err instanceof Error ? err.message : String(err)
847
+ });
848
+ });
849
+ this.logInfo("setupProductSync: pubsub 订阅已建立");
850
+ }
851
+ /**
852
+ * 处理防抖后的同步消息批次
853
+ *
854
+ * product 模块:
855
+ * - operation === 'delete' → 本地删除
856
+ * - 有 body(无 price change_types) → body 完整数据直接覆盖本地
857
+ * - change_types 包含 price → SSE 增量拉取 + 刷新报价单价格缓存
858
+ * - change_types 仅 stock → 跳过(暂不响应)
859
+ *
860
+ * product_collection / product_category / product_quotation:
861
+ * - 按 relation_product_ids SSE 拉取受影响商品
862
+ * - product_quotation 额外刷新报价单价格缓存
863
+ *
864
+ * 处理完成后 emit onProductsSyncCompleted 通知 Server 层
865
+ */
866
+ async processProductSyncMessages() {
867
+ var _a, _b, _c, _d, _e;
868
+ const messages = [...this.pendingSyncMessages];
869
+ this.pendingSyncMessages = [];
870
+ if (messages.length === 0)
871
+ return;
872
+ this.logInfo("processProductSyncMessages: 开始处理", { count: messages.length });
873
+ const deleteIds = [];
874
+ const bodyUpdates = /* @__PURE__ */ new Map();
875
+ const sseRefreshIds = [];
876
+ const priceRefreshIds = [];
877
+ for (const msg of messages) {
878
+ const channelKey = msg._channelKey || msg.module || "product";
879
+ if (channelKey === "product") {
880
+ if (msg.operation === "delete" || msg.action === "delete") {
881
+ if ((_a = msg.ids) == null ? void 0 : _a.length)
882
+ deleteIds.push(...msg.ids);
883
+ else if (msg.id)
884
+ deleteIds.push(msg.id);
885
+ continue;
886
+ }
887
+ if (((_b = msg.change_types) == null ? void 0 : _b.length) && msg.change_types.every((t) => t === "stock")) {
888
+ this.logInfo("跳过仅库存变更", { ids: msg.ids });
889
+ continue;
890
+ }
891
+ if ((_c = msg.change_types) == null ? void 0 : _c.includes("price")) {
892
+ const ids = msg.ids || (msg.id ? [msg.id] : []);
893
+ sseRefreshIds.push(...ids);
894
+ priceRefreshIds.push(...ids);
895
+ continue;
896
+ }
897
+ if (msg.body) {
898
+ const bodyId = msg.body.id || msg.id;
899
+ if (bodyId)
900
+ bodyUpdates.set(bodyId, msg.body);
901
+ continue;
902
+ }
903
+ if ((_d = msg.ids) == null ? void 0 : _d.length) {
904
+ sseRefreshIds.push(...msg.ids);
905
+ } else if (msg.id) {
906
+ sseRefreshIds.push(msg.id);
907
+ }
908
+ } else if (["product_collection", "product_category", "product_quotation"].includes(channelKey)) {
909
+ if ((_e = msg.relation_product_ids) == null ? void 0 : _e.length) {
910
+ sseRefreshIds.push(...msg.relation_product_ids);
911
+ if (channelKey === "product_quotation") {
912
+ this.clearPriceCache();
913
+ priceRefreshIds.push(...msg.relation_product_ids);
914
+ }
915
+ }
916
+ }
917
+ }
918
+ const uniqueDeleteIds = [...new Set(deleteIds)];
919
+ const uniqueSSEIds = [...new Set(sseRefreshIds)];
920
+ const uniquePriceIds = [...new Set(priceRefreshIds)];
921
+ if (uniqueDeleteIds.length > 0) {
922
+ await this.removeProductsByIds(uniqueDeleteIds);
923
+ }
924
+ if (bodyUpdates.size > 0) {
925
+ await this.applyBodyUpdatesToStore(bodyUpdates);
926
+ }
927
+ if (uniqueSSEIds.length > 0) {
928
+ const freshProducts = await this.fetchProductsBySSE(uniqueSSEIds);
929
+ if (freshProducts.length > 0) {
930
+ await this.mergeProductsToStore(freshProducts);
931
+ await this.updatePriceCacheForProducts(freshProducts);
932
+ }
933
+ this.logInfo("processProductSyncMessages: SSE 增量更新完成", {
934
+ requestedCount: uniqueSSEIds.length,
935
+ receivedCount: freshProducts.length
936
+ });
937
+ }
938
+ const sseHandledSet = new Set(uniqueSSEIds);
939
+ const remainingPriceIds = uniquePriceIds.filter((id) => !sseHandledSet.has(id));
940
+ if (remainingPriceIds.length > 0) {
941
+ await this.updateProductPriceByIds(remainingPriceIds);
942
+ }
943
+ this.logInfo("processProductSyncMessages: 处理完成", {
944
+ deleteCount: uniqueDeleteIds.length,
945
+ bodyUpdateCount: bodyUpdates.size,
946
+ sseRefreshCount: uniqueSSEIds.length,
947
+ priceRefreshCount: uniquePriceIds.length
948
+ });
949
+ await this.core.effects.emit(import_types.ProductsHooks.onProductsSyncCompleted, null);
950
+ }
951
+ /**
952
+ * 通过 SSE 按 ids 增量拉取商品数据
953
+ * 请求 GET /shop/core/stream?type=product&ids={ids}
954
+ */
955
+ async fetchProductsBySSE(ids) {
956
+ if (!this.productDataSource) {
957
+ this.logWarning("fetchProductsBySSE: ProductDataSource 不可用");
958
+ return [];
959
+ }
960
+ this.logInfo("fetchProductsBySSE: 开始", { ids, count: ids.length });
961
+ const t0 = performance.now();
962
+ try {
963
+ const productList = await this.productDataSource.run({
964
+ sse: { query: { type: "product", ids } }
965
+ });
966
+ const list = productList || [];
967
+ (0, import_product.perfMark)("fetchProductsBySSE", performance.now() - t0, { count: list.length });
968
+ this.logInfo("fetchProductsBySSE: 成功", {
969
+ requestedCount: ids.length,
970
+ receivedCount: list.length,
971
+ duration: `${Math.round(performance.now() - t0)}ms`
972
+ });
973
+ return list;
974
+ } catch (error) {
975
+ const errorMessage = error instanceof Error ? error.message : String(error);
976
+ this.logError("fetchProductsBySSE: 失败", { ids, error: errorMessage });
977
+ return [];
978
+ }
979
+ }
980
+ /**
981
+ * 将 body 完整数据直接覆盖到本地 store(不调用报价单接口)
982
+ * 已存在的 → 直接替换;不存在的 → 追加
983
+ * 同时更新 Map 缓存、IndexDB,清空价格缓存,触发 onProductsChanged
984
+ */
985
+ async applyBodyUpdatesToStore(bodyUpdates) {
986
+ this.logInfo("applyBodyUpdatesToStore: 开始", { count: bodyUpdates.size });
987
+ let updatedCount = 0;
988
+ let newCount = 0;
989
+ const appliedIds = /* @__PURE__ */ new Set();
990
+ this.store.list = this.store.list.map((p) => {
991
+ if (bodyUpdates.has(p.id)) {
992
+ updatedCount++;
993
+ appliedIds.add(p.id);
994
+ return bodyUpdates.get(p.id);
995
+ }
996
+ return p;
997
+ });
998
+ for (const [id, body] of bodyUpdates) {
999
+ if (!appliedIds.has(id)) {
1000
+ this.store.list.push(body);
1001
+ newCount++;
1002
+ }
1003
+ }
1004
+ this.syncProductsMap();
1005
+ await this.saveProductsToIndexDB(this.store.list);
1006
+ await this.updatePriceCacheForProducts([...bodyUpdates.values()]);
1007
+ this.core.effects.emit(import_types.ProductsHooks.onProductsChanged, this.store.list);
1008
+ this.logInfo("applyBodyUpdatesToStore: 完成", {
1009
+ updatedCount,
1010
+ newCount,
1011
+ totalCount: this.store.list.length
1012
+ });
1013
+ }
1014
+ /**
1015
+ * 将增量拉取的商品合并到 store
1016
+ * 已存在的 → 替换;新的 → 追加
1017
+ * 同时更新 store.map、IndexDB,触发 onProductsChanged
1018
+ */
1019
+ async mergeProductsToStore(freshProducts) {
1020
+ const freshMap = /* @__PURE__ */ new Map();
1021
+ for (const p of freshProducts) {
1022
+ freshMap.set(p.id, p);
1023
+ }
1024
+ const updatedList = this.store.list.map((p) => {
1025
+ if (freshMap.has(p.id)) {
1026
+ const fresh = freshMap.get(p.id);
1027
+ freshMap.delete(p.id);
1028
+ return fresh;
1029
+ }
1030
+ return p;
1031
+ });
1032
+ const newCount = freshMap.size;
1033
+ for (const p of freshMap.values()) {
1034
+ updatedList.push(p);
1035
+ }
1036
+ const updatedCount = freshProducts.length - newCount;
1037
+ this.store.list = updatedList;
1038
+ this.syncProductsMap();
1039
+ await this.saveProductsToIndexDB(this.store.list);
1040
+ this.logInfo("mergeProductsToStore: 合并完成", {
1041
+ updatedCount,
1042
+ newCount,
1043
+ totalCount: this.store.list.length
1044
+ });
1045
+ this.core.effects.emit(import_types.ProductsHooks.onProductsChanged, this.store.list);
1046
+ }
1047
+ /**
1048
+ * 增量更新价格缓存中变更的商品
1049
+ * 对每个已缓存的日期 key:替换/追加最新商品数据,重新拉取这些 ID 的价格并应用
1050
+ */
1051
+ async updatePriceCacheForProducts(freshProducts) {
1052
+ if (this.productsPriceCache.size === 0)
1053
+ return;
1054
+ const freshIds = freshProducts.map((p) => p.id);
1055
+ const freshMap = /* @__PURE__ */ new Map();
1056
+ for (const p of freshProducts) {
1057
+ freshMap.set(p.id, p);
1058
+ }
1059
+ this.logInfo("updatePriceCacheForProducts: 开始", {
1060
+ freshIds,
1061
+ cachedDateCount: this.productsPriceCache.size
1062
+ });
1063
+ for (const [dateKey, cachedProducts] of this.productsPriceCache.entries()) {
1064
+ try {
1065
+ const updatedList = cachedProducts.map((p) => {
1066
+ const fresh = freshMap.get(p.id);
1067
+ return fresh ? { ...fresh } : p;
1068
+ });
1069
+ const existingIds = new Set(cachedProducts.map((p) => p.id));
1070
+ for (const p of freshProducts) {
1071
+ if (!existingIds.has(p.id)) {
1072
+ updatedList.push({ ...p });
1073
+ }
1074
+ }
1075
+ const priceData = await this.loadProductsPrice({
1076
+ ids: freshIds,
1077
+ schedule_date: dateKey
1078
+ });
1079
+ const result = priceData && priceData.length > 0 ? (0, import_product.applyPriceDataToProducts)(updatedList, priceData) : updatedList;
1080
+ this.productsPriceCache.set(dateKey, result);
1081
+ this.logInfo("updatePriceCacheForProducts: 日期缓存已更新", {
1082
+ dateKey,
1083
+ updatedIds: freshIds
1084
+ });
1085
+ } catch (error) {
1086
+ const errorMessage = error instanceof Error ? error.message : String(error);
1087
+ this.logError("updatePriceCacheForProducts: 失败", { dateKey, error: errorMessage });
1088
+ }
1089
+ }
1090
+ }
1091
+ /**
1092
+ * 全量重新拉取报价单价格并重建价格缓存
1093
+ * 遍历当前已缓存的所有日期 key,对每个日期重新调用 loadProductsPrice
1094
+ */
1095
+ async refreshAllPriceCache() {
1096
+ const allProducts = this.getProductsRef();
1097
+ if (allProducts.length === 0)
1098
+ return;
1099
+ const ids = allProducts.map((p) => p.id);
1100
+ const dateKeys = Array.from(this.productsPriceCache.keys());
1101
+ this.clearPriceCache();
1102
+ if (dateKeys.length === 0) {
1103
+ this.logInfo("refreshAllPriceCache: 无已缓存日期,跳过");
1104
+ return;
1105
+ }
1106
+ this.logInfo("refreshAllPriceCache: 开始重新拉取", { dateKeys, productCount: ids.length });
1107
+ for (const dateKey of dateKeys) {
1108
+ try {
1109
+ const priceData = await this.loadProductsPrice({ ids, schedule_date: dateKey });
1110
+ if (priceData && priceData.length > 0) {
1111
+ const updatedProducts = (0, import_product.applyPriceDataToProducts)(allProducts, priceData);
1112
+ this.productsPriceCache.set(dateKey, updatedProducts);
1113
+ }
1114
+ this.logInfo("refreshAllPriceCache: 日期缓存已更新", { dateKey });
1115
+ } catch (error) {
1116
+ const errorMessage = error instanceof Error ? error.message : String(error);
1117
+ this.logError("refreshAllPriceCache: 失败", { dateKey, error: errorMessage });
1118
+ }
1119
+ }
1120
+ }
1121
+ /**
1122
+ * 静默全量刷新:后台重新拉取全量 SSE 数据并更新本地
1123
+ * 拿到完整数据后一次性替换 store,清除价格缓存,触发 onProductsSyncCompleted
1124
+ * @returns 刷新后的商品列表
1125
+ */
1126
+ async silentRefresh() {
1127
+ const t0 = performance.now();
1128
+ this.logInfo("silentRefresh 开始");
1129
+ try {
1130
+ const products = await this.loadProductsByServer();
1131
+ if (products && products.length > 0) {
1132
+ this.store.list = products;
1133
+ this.syncProductsMap();
1134
+ this.clearPriceCache();
1135
+ this.core.effects.emit(import_types.ProductsHooks.onProductsChanged, this.store.list);
1136
+ await this.core.effects.emit(import_types.ProductsHooks.onProductsSyncCompleted, null);
1137
+ this.logInfo("silentRefresh 完成", {
1138
+ productCount: products.length,
1139
+ duration: `${Math.round(performance.now() - t0)}ms`
1140
+ });
1141
+ } else {
1142
+ this.logWarning("silentRefresh: 服务器未返回数据");
1143
+ }
1144
+ (0, import_product.perfMark)("silentRefresh", performance.now() - t0, { count: this.store.list.length });
1145
+ return this.store.list;
1146
+ } catch (error) {
1147
+ const errorMessage = error instanceof Error ? error.message : String(error);
1148
+ this.logError("silentRefresh 失败", {
1149
+ duration: `${Math.round(performance.now() - t0)}ms`,
1150
+ error: errorMessage
1151
+ });
1152
+ return this.store.list;
1153
+ }
1154
+ }
1155
+ /**
1156
+ * 销毁同步资源(取消 pubsub 订阅、清除定时器)
1157
+ */
1158
+ destroyProductSync() {
1159
+ var _a;
1160
+ if (this.syncTimer) {
1161
+ clearTimeout(this.syncTimer);
1162
+ this.syncTimer = void 0;
1163
+ }
1164
+ if ((_a = this.productDataSource) == null ? void 0 : _a.destroy) {
1165
+ this.productDataSource.destroy();
1166
+ }
1167
+ this.pendingSyncMessages = [];
1168
+ this.logInfo("destroyProductSync: 同步资源已销毁");
1169
+ }
626
1170
  /**
627
1171
  * 获取模块的路由定义
628
1172
  * Products 模块暂不提供路由,由 Server 层统一处理