@pisell/pisellos
Version:
一个可扩展的前端模块化SDK框架,支持插件系统
1,187 lines (1,185 loc) • 43.9 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/server/modules/products/index.ts
var products_exports = {};
__export(products_exports, {
ProductsModule: () => ProductsModule
});
module.exports = __toCommonJS(products_exports);
var import_BaseModule = require("../../../modules/BaseModule");
var import_plugins = require("../../../plugins");
var import_types = require("./types");
var import_product = require("../../utils/product");
var INDEXDB_STORE_NAME = "products";
var PRODUCT_SYNC_DEBOUNCE_MS = 1e4;
var ProductsModule = class extends import_BaseModule.BaseModule {
constructor(name, version) {
super(name, version);
this.defaultName = "products";
this.defaultVersion = "1.0.0";
// LoggerManager 实例
this.otherParams = {};
// 商品价格缓存:Map<日期, 应用了价格的商品列表>
this.productsPriceCache = /* @__PURE__ */ new Map();
// 缓存配置
this.CACHE_MAX_DAYS = 7;
// 最多缓存7天
// 商品格式化器列表
this.formatters = [];
// 是否已注册内置价格格式化器
this.isPriceFormatterRegistered = false;
this.pendingSyncMessages = [];
}
async initialize(core, options) {
var _a;
this.core = core;
this.store = options.store;
this.otherParams = options.otherParams || {};
if (Array.isArray((_a = options.initialState) == null ? void 0 : _a.list)) {
this.store.list = options.initialState.list;
this.syncProductsMap();
this.core.effects.emit(import_types.ProductsHooks.onProductsChanged, this.store.list);
} else {
this.store.list = [];
this.store.map = /* @__PURE__ */ new Map();
}
this.request = core.getPlugin("request");
const appPlugin = core.getPlugin("app");
if (appPlugin) {
const app = appPlugin.getApp();
this.dbManager = app.dbManager;
this.logger = app.logger;
if (this.dbManager) {
console.log("[Products] IndexDB Manager 已初始化");
} else {
console.warn("[Products] IndexDB Manager 未找到");
}
}
this.registerBuiltinPriceFormatter();
this.initProductDataSource();
this.setupProductSync();
this.logInfo("模块初始化完成", {
hasDbManager: !!this.dbManager,
hasLogger: !!this.logger,
initialProductCount: this.store.list.length
});
}
/**
* 记录信息日志
* @param title 日志标题
* @param metadata 日志元数据
*/
logInfo(title, metadata) {
try {
if (this.logger) {
this.logger.addLog({
type: "info",
title: `[ProductsModule] ${title}`,
metadata: metadata || {}
});
}
} catch {
}
}
/**
* 记录警告日志
* @param title 日志标题
* @param metadata 日志元数据
*/
logWarning(title, metadata) {
try {
if (this.logger) {
this.logger.addLog({
type: "warning",
title: `[ProductsModule] ${title}`,
metadata: metadata || {}
});
}
} catch {
}
}
/**
* 记录错误日志
* @param title 日志标题
* @param metadata 日志元数据
*/
logError(title, metadata) {
try {
if (this.logger) {
this.logger.addLog({
type: "error",
title: `[ProductsModule] ${title}`,
metadata: metadata || {}
});
}
} catch {
}
}
/**
* 加载商品价格(原始方法,不带缓存)
* @private
*/
async loadProductsPrice({
ids = [],
customer_id,
schedule_date
}) {
var _a;
this.logInfo("开始加载商品价格", {
productCount: ids.length,
schedule_date,
customer_id
});
const startTime = Date.now();
try {
const productsData = await this.request.post(
`/product/query/price`,
{
ids,
customer_id,
schedule_date
},
{
cache: {
mode: import_plugins.RequestModeENUM.REMOTE_LOCAL,
type: "memory"
}
}
);
const duration = Date.now() - startTime;
this.logInfo("加载商品价格成功", {
productCount: ids.length,
priceDataCount: ((_a = productsData.data) == null ? void 0 : _a.length) ?? 0,
duration: `${duration}ms`
});
return productsData.data;
} catch (error) {
const duration = Date.now() - startTime;
const errorMessage = error instanceof Error ? error.message : String(error);
this.logError("加载商品价格失败", {
productCount: ids.length,
duration: `${duration}ms`,
error: errorMessage
});
return [];
}
}
/**
* 获取应用了价格的商品列表(带缓存)
* 使用日期作为缓存 key,同一天的商品价格会被缓存
* 缓存的是已经应用了价格的完整商品列表,避免重复转换
* @param schedule_date 日期
* @param extraContext 额外的上下文数据(可选,由 Server 层传入)
* @param options 可选参数
* @param options.changedIds 变更的商品 IDs,非空时仅对这些商品增量执行 prepare 并更新缓存
* @returns 应用了价格的商品列表
*/
async getProductsWithPrice(schedule_date, extraContext, options) {
const t0 = performance.now();
const cacheKey = schedule_date;
const changedIds = options == null ? void 0 : options.changedIds;
if (this.productsPriceCache.has(cacheKey)) {
const cachedProducts = this.productsPriceCache.get(cacheKey);
if (changedIds && changedIds.length > 0) {
this.logInfo("商品价格缓存命中,增量更新变更商品", {
cacheKey,
changedIds,
cachedProductCount: cachedProducts.length
});
try {
const updatedProducts = await this.prepareProductsWithPrice(
schedule_date,
extraContext,
{ productIds: changedIds }
);
if (updatedProducts.length > 0) {
const updatedMap = new Map(updatedProducts.map((p) => [p.id, p]));
const mergedCache = [];
for (const p of cachedProducts) {
if (updatedMap.has(p.id)) {
mergedCache.push(updatedMap.get(p.id));
updatedMap.delete(p.id);
} else {
mergedCache.push(p);
}
}
for (const p of updatedMap.values()) {
mergedCache.push(p);
}
this.productsPriceCache.set(cacheKey, mergedCache);
this.logInfo("增量更新完成", {
cacheKey,
changedCount: updatedProducts.length,
totalCount: mergedCache.length
});
(0, import_product.perfMark)("getProductsWithPrice(incrementalUpdate)", performance.now() - t0, {
cacheKey,
changedCount: changedIds.length,
count: mergedCache.length
});
return mergedCache;
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.logError("增量更新失败,返回现有缓存", {
cacheKey,
changedIds,
error: errorMessage
});
}
(0, import_product.perfMark)("getProductsWithPrice(incrementalUpdate)", performance.now() - t0, {
cacheKey,
changedCount: changedIds.length,
count: cachedProducts.length
});
return cachedProducts;
}
(0, import_product.perfMark)("getProductsWithPrice(cacheHit)", performance.now() - t0, {
cacheKey,
count: cachedProducts.length
});
this.logInfo("商品价格缓存命中", {
cacheKey,
productCount: cachedProducts.length
});
return cachedProducts;
}
this.logInfo("商品价格缓存未命中,准备获取", { cacheKey });
const result = await this.prepareProductsWithPrice(schedule_date, extraContext);
this.productsPriceCache.set(cacheKey, result);
this.logInfo("商品价格已缓存", {
cacheKey,
productCount: result.length
});
this.cleanExpiredPriceCache();
(0, import_product.perfMark)("getProductsWithPrice(cacheMiss)", performance.now() - t0, {
cacheKey,
count: result.length
});
return result;
}
/**
* 准备带价格的商品数据(通过格式化器流程处理)
* @param schedule_date 日期
* @param extraContext 额外的上下文数据(可选)
* @param options 可选参数
* @param options.productIds 指定商品 IDs,仅处理这些商品;不传则处理全量
* @returns 处理后的商品列表
* @private
*/
async prepareProductsWithPrice(schedule_date, extraContext, options) {
var _a, _b;
const tTotal = performance.now();
const targetIds = options == null ? void 0 : options.productIds;
const isIncremental = targetIds && targetIds.length > 0;
this.logInfo("prepareProductsWithPrice 开始处理", {
schedule_date,
mode: isIncremental ? "incremental" : "full",
targetIdsCount: targetIds == null ? void 0 : targetIds.length
});
try {
let products;
let ids;
if (isIncremental) {
const idSet = new Set(targetIds);
products = this.getProductsRef().filter((p) => idSet.has(p.id));
ids = products.map((p) => p.id);
} else {
products = this.getProductsRef();
const tIds = performance.now();
ids = new Array(products.length);
for (let i = 0; i < products.length; i++) {
ids[i] = products[i].id;
}
(0, import_product.perfMark)("prepareProducts.extractIds", performance.now() - tIds, { count: ids.length });
}
this.logInfo("获取到商品列表", { productCount: products.length });
const tPrice = performance.now();
const priceData = await this.loadProductsPrice({
ids,
schedule_date
});
(0, import_product.perfMark)("prepareProducts.loadPrice", performance.now() - tPrice, { count: ids.length });
this.logInfo("获取商品报价单价格成功", { priceDataCount: (priceData == null ? void 0 : priceData.length) ?? 0 });
const context = {
schedule_date,
priceData,
locale: (_b = (_a = this.core) == null ? void 0 : _a.context) == null ? void 0 : _b.locale,
...extraContext
};
const tFormat = performance.now();
const processedProducts = await this.applyFormatters(products, context);
(0, import_product.perfMark)("prepareProducts.applyFormatters", performance.now() - tFormat, {
count: products.length,
formatterCount: this.formatters.length
});
this.logInfo("prepareProductsWithPrice 处理完成", {
mode: isIncremental ? "incremental" : "full",
originalProductCount: products.length,
processedProductCount: processedProducts.length,
formatterCount: this.formatters.length
});
if (!isIncremental) {
await this.core.effects.emit(
import_types.ProductsHooks.onProductsPriceApplied,
processedProducts
);
}
(0, import_product.perfMark)("prepareProductsWithPrice", performance.now() - tTotal, {
productCount: products.length,
formatterCount: this.formatters.length,
mode: isIncremental ? "incremental" : "full"
});
return processedProducts;
} catch (err) {
const errorMessage = err instanceof Error ? err.message : String(err);
console.log(`[ProductsModule] 🌐 ERROR`, err);
this.logError("prepareProductsWithPrice 处理失败", {
schedule_date,
error: errorMessage
});
}
return [];
}
/**
* 应用所有已注册的格式化器
* @param products 商品列表
* @param context 上下文信息
* @returns 格式化后的商品列表
* @private
*/
async applyFormatters(products, context) {
let result = products;
if (this.formatters.length === 0) {
console.warn("[ProductsModule] ⚠️ 没有注册任何格式化器");
this.logWarning("没有注册任何格式化器", { productCount: products.length });
return result;
}
this.logInfo("开始应用格式化器", {
productCount: products.length,
formatterCount: this.formatters.length
});
for (let i = 0; i < this.formatters.length; i++) {
const formatter = this.formatters[i];
try {
const tF = performance.now();
result = await formatter(result, context);
(0, import_product.perfMark)(`applyFormatters[${i}]`, performance.now() - tF, {
index: i,
total: this.formatters.length,
productCount: result.length
});
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error(`[ProductsModule] ❌ 格式化器 ${i + 1} 执行失败:`, error);
this.logError(`格式化器 ${i + 1} 执行失败`, {
formatterIndex: i + 1,
totalFormatters: this.formatters.length,
error: errorMessage
});
}
}
this.logInfo("所有格式化器已应用", {
formatterCount: this.formatters.length,
resultProductCount: result.length
});
return result;
}
/**
* 注册内置的价格格式化器
* 这个格式化器负责将价格数据应用到商品上
* @private
*/
registerBuiltinPriceFormatter() {
if (this.isPriceFormatterRegistered) {
console.warn("[ProductsModule] 内置价格格式化器已注册,跳过");
return;
}
const priceFormatter = (products, context) => {
if (!context.priceData || context.priceData.length === 0) {
console.log("[ProductsModule] 💰 没有价格数据,跳过价格应用");
return products;
}
console.log(`[ProductsModule] 💰 应用价格数据到 ${products.length} 个商品`);
return (0, import_product.applyPriceDataToProducts)(products, context.priceData);
};
const i18nFormatter = (products, context) => {
return (0, import_product.applyI18nToProducts)(products, context.locale);
};
const detailValueFormatter = (products, context) => {
return (0, import_product.applyDetailValueToProducts)(products, context);
};
this.formatters.unshift(priceFormatter);
this.formatters.push(i18nFormatter);
this.formatters.push(detailValueFormatter);
this.isPriceFormatterRegistered = true;
console.log("[ProductsModule] ✅ 内置价格格式化器已注册(第 1 个)");
}
/**
* 注册商品格式化器
* 格式化器会按注册顺序依次执行(内置价格格式化器始终是第一个)
* @param formatter 格式化函数
* @param prepend 是否插入到队列开头(默认 false,追加到末尾)
* @example
* ```ts
* productsModule.registerFormatter((products, context) => {
* return products.map(p => ({
* ...p,
* custom_field: 'custom_value'
* }));
* });
* ```
*/
registerFormatter(formatter, prepend = false) {
if (prepend) {
const insertIndex = this.isPriceFormatterRegistered ? 1 : 0;
this.formatters.splice(insertIndex, 0, formatter);
console.log(`[ProductsModule] 📌 已在位置 ${insertIndex + 1} 插入格式化器,当前共 ${this.formatters.length} 个`);
} else {
this.formatters.push(formatter);
console.log(`[ProductsModule] 📌 已注册格式化器,当前共 ${this.formatters.length} 个`);
}
}
/**
* 清空所有格式化器(包括内置价格格式化器)
* @param keepBuiltin 是否保留内置价格格式化器(默认 true)
*/
clearFormatters(keepBuiltin = true) {
if (keepBuiltin && this.isPriceFormatterRegistered) {
this.formatters = [this.formatters[0]];
console.log("[ProductsModule] 🧹 已清空自定义格式化器,保留内置价格格式化器");
} else {
this.formatters = [];
this.isPriceFormatterRegistered = false;
console.log("[ProductsModule] 🧹 已清空所有格式化器");
}
}
/**
* 清理过期的价格缓存
* 当缓存数量超过限制时,删除最老的缓存
* @private
*/
cleanExpiredPriceCache() {
if (this.productsPriceCache.size > this.CACHE_MAX_DAYS) {
const keys = Array.from(this.productsPriceCache.keys());
const oldestKey = keys[0];
this.productsPriceCache.delete(oldestKey);
console.log(`[ProductsModule] 🧹 清理过期商品价格缓存: ${oldestKey}`);
}
}
/**
* 清空商品价格缓存
* 可用于手动刷新价格数据
*/
clearPriceCache() {
this.productsPriceCache.clear();
console.log("[ProductsModule] 🗑️ 商品价格缓存已清空");
}
/**
* 通过 ProductDataSource SSE 加载完整商品列表
*/
async loadProductsByServer() {
if (!this.productDataSource) {
this.logWarning("loadProductsByServer: ProductDataSource 不可用");
return [];
}
this.logInfo("开始通过 DataSource SSE 加载商品列表");
const t0 = performance.now();
try {
const tSSE = performance.now();
const productList = await this.productDataSource.run({ sse: {} });
const list = productList || [];
(0, import_product.perfMark)("loadProductsByServer.SSE", performance.now() - tSSE, { count: list.length });
this.logInfo("通过 DataSource SSE 加载商品列表成功", {
productCount: list.length,
duration: `${Math.round(performance.now() - t0)}ms`
});
await this.saveProductsToIndexDB(list);
await this.core.effects.emit(import_types.ProductsHooks.onProductsLoaded, list);
(0, import_product.perfMark)("loadProductsByServer", performance.now() - t0, { count: list.length });
return list;
} catch (error) {
const duration = Math.round(performance.now() - t0);
const errorMessage = error instanceof Error ? error.message : String(error);
console.error("[Products] 加载商品数据失败:", error);
this.logError("通过 DataSource SSE 加载商品列表失败", {
duration: `${duration}ms`,
error: errorMessage
});
return [];
}
}
/**
* 纯请求方法:通过 HTTP 接口获取商品列表(无副作用,不触发事件、不写 IndexDB)
* @param params 查询参数
* @returns 商品列表
*/
async fetchProductsByHttp(params) {
var _a, _b;
const {
category_ids = [],
product_ids = [],
collection = [],
customer_id,
cacheId
} = params || {};
this.logInfo("fetchProductsByHttp: 开始请求", {
categoryIdsCount: category_ids.length,
productIdsCount: product_ids.length,
collectionCount: Array.isArray(collection) ? collection.length : collection ? 1 : 0,
customer_id
});
const startTime = Date.now();
try {
const productsData = await this.request.post(
`/product/query`,
{
open_bundle: 1,
exclude_extension_type: [
"product_party",
"product_event",
"product_series_event",
"product_package_ticket",
"ticket",
"event_item"
],
force_ignore_cache_flag: 1,
with: [
"category",
"collection",
"resourceRelation",
"bundleGroup.bundleItem",
"optionGroup.optionItem",
"variantGroup.variantItem"
],
open_deposit: 1,
is_append_product_description: 1,
with_count: ["bundleGroup", "optionGroup"],
with_category_tree: 1,
status: "published",
num: 500,
skip: 1,
category_ids,
ids: product_ids,
collection,
front_end_cache_id: cacheId,
application_code: (_a = this.otherParams) == null ? void 0 : _a.channel
},
{ cache: void 0 }
);
const productList = ((_b = productsData == null ? void 0 : productsData.data) == null ? void 0 : _b.list) || [];
const duration = Date.now() - startTime;
this.logInfo("fetchProductsByHttp: 请求成功", {
productCount: productList.length,
duration: `${duration}ms`
});
return productList;
} catch (error) {
const duration = Date.now() - startTime;
const errorMessage = error instanceof Error ? error.message : String(error);
console.error("[Products] fetchProductsByHttp 失败:", error);
this.logError("fetchProductsByHttp: 请求失败", {
duration: `${duration}ms`,
error: errorMessage
});
return [];
}
}
/**
* 加载完整商品列表通过接口(包含所有详细数据)
* 包含副作用:保存到 IndexDB + 触发 onProductsLoaded 事件
* @param params 查询参数
*/
async loadProductsByServerHttp(params) {
const productList = await this.fetchProductsByHttp(params);
if (productList.length > 0) {
await this.saveProductsToIndexDB(productList);
await this.core.effects.emit(import_types.ProductsHooks.onProductsLoaded, productList);
}
return productList;
}
/**
* 获取商品列表(深拷贝,供外部安全使用)
*/
async getProducts() {
const t0 = performance.now();
const result = structuredClone(this.store.list);
(0, import_product.perfMark)("ProductsModule.getProducts(structuredClone)", performance.now() - t0, { count: result.length });
return result;
}
/**
* 内部获取商品列表的直接引用(无拷贝)
* 仅供内部 formatter 流程使用,因为 formatter 会创建新对象
*/
getProductsRef() {
return this.store.list;
}
/**
* 根据ID获取单个商品(从内存缓存)
* 使用 Map 快速查询,时间复杂度 O(1)
*/
async getProductById(id) {
const product = this.store.map.get(id);
return product ? structuredClone(product) : void 0;
}
/**
* 根据 ID 列表删除商品(用于 pubsub 同步删除场景)
* 同时更新 store.list、store.map、IndexDB 和价格缓存
*/
async removeProductsByIds(ids) {
const idSet = new Set(ids);
this.logInfo("removeProductsByIds", { ids, count: ids.length });
this.store.list = this.store.list.filter((p) => !idSet.has(p.id));
for (const id of ids) {
this.store.map.delete(id);
}
if (this.dbManager) {
try {
for (const id of ids) {
await this.dbManager.delete(INDEXDB_STORE_NAME, id);
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.logError("removeProductsByIds: IndexDB 删除失败", { ids, error: errorMessage });
}
}
for (const [dateKey, cachedProducts] of this.productsPriceCache.entries()) {
this.productsPriceCache.set(
dateKey,
cachedProducts.filter((p) => !idSet.has(p.id))
);
}
this.core.effects.emit(import_types.ProductsHooks.onProductsChanged, this.store.list);
this.logInfo("removeProductsByIds 完成", { remaining: this.store.list.length });
}
/**
* 重新从服务器加载全量商品列表并更新本地 store
* 用于 pubsub 同步 create / update / batch_update 场景
*/
async refreshProducts() {
const tTotal = performance.now();
this.logInfo("refreshProducts 开始");
const products = await this.loadProductsByServer();
if (products && products.length > 0) {
this.store.list = products;
this.syncProductsMap();
this.core.effects.emit(import_types.ProductsHooks.onProductsChanged, this.store.list);
this.logInfo("refreshProducts 完成", { productCount: products.length });
} else {
this.logWarning("refreshProducts: 服务器未返回数据");
}
(0, import_product.perfMark)("refreshProducts", performance.now() - tTotal, { count: this.store.list.length });
return this.store.list;
}
/**
* 清空缓存
*/
async clear() {
this.logInfo("开始清空缓存", {
currentProductCount: this.store.list.length,
priceCacheCount: this.productsPriceCache.size
});
this.store.list = [];
this.store.map.clear();
if (this.dbManager) {
try {
await this.dbManager.clear(INDEXDB_STORE_NAME);
console.log("[Products] IndexDB 缓存已清空");
this.logInfo("IndexDB 缓存已清空");
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error("[Products] 清空 IndexDB 缓存失败:", error);
this.logError("清空 IndexDB 缓存失败", { error: errorMessage });
}
}
console.log("[Products] 缓存已清空");
this.logInfo("缓存清空完成");
}
/**
* 从 IndexDB 加载商品数据
* @private
*/
async loadProductsFromIndexDB() {
if (!this.dbManager) {
this.logWarning("loadProductsFromIndexDB: dbManager 不可用");
return [];
}
try {
const t0 = performance.now();
const products = await this.dbManager.getAll(INDEXDB_STORE_NAME);
(0, import_product.perfMark)("loadProductsFromIndexDB", performance.now() - t0, { count: (products == null ? void 0 : products.length) ?? 0 });
this.logInfo("从 IndexDB 加载商品数据", {
productCount: (products == null ? void 0 : products.length) ?? 0
});
return products || [];
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error("[Products] 从 IndexDB 读取数据失败:", error);
this.logError("从 IndexDB 读取数据失败", { error: errorMessage });
return [];
}
}
/**
* 保存商品数据到 IndexDB(平铺存储)
* @private
*/
async saveProductsToIndexDB(products) {
if (!this.dbManager) {
this.logWarning("saveProductsToIndexDB: dbManager 不可用");
return;
}
this.logInfo("开始保存商品数据到 IndexDB", { productCount: products.length });
try {
const t0 = performance.now();
await this.dbManager.clear(INDEXDB_STORE_NAME);
await this.dbManager.bulkUpdate(INDEXDB_STORE_NAME, products);
(0, import_product.perfMark)("saveProductsToIndexDB", performance.now() - t0, { count: products.length });
console.log(
`[Products] 已将 ${products.length} 个商品平铺保存到 IndexDB`
);
this.logInfo("商品数据已保存到 IndexDB", { productCount: products.length });
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error("[Products] 保存数据到 IndexDB 失败:", error);
this.logError("保存数据到 IndexDB 失败", {
productCount: products.length,
error: errorMessage
});
}
}
/**
* 同步更新商品 Map 缓存
* 将 list 中的商品同步到 map,以 id 为 key
* @private
*/
syncProductsMap() {
const t0 = performance.now();
this.store.map.clear();
for (const product of this.store.list) {
this.store.map.set(product.id, product);
}
(0, import_product.perfMark)("syncProductsMap", performance.now() - t0, { count: this.store.map.size });
}
/**
* 预加载模块数据(统一接口)
* 在模块注册后自动调用
*/
async preload() {
console.log("[Products] 开始预加载数据...");
const tTotal = performance.now();
this.logInfo("开始预加载数据");
try {
const tIndexDB = performance.now();
const cachedData = await this.loadProductsFromIndexDB();
(0, import_product.perfMark)("preload.loadFromIndexDB", performance.now() - tIndexDB, { count: (cachedData == null ? void 0 : cachedData.length) ?? 0 });
if (cachedData && cachedData.length > 0) {
console.log(`[Products] 从 IndexDB 加载了 ${cachedData.length} 个商品`);
this.store.list = cachedData;
const tSync = performance.now();
this.syncProductsMap();
(0, import_product.perfMark)("preload.syncProductsMap", performance.now() - tSync, { count: cachedData.length });
this.core.effects.emit(
import_types.ProductsHooks.onProductsChanged,
this.store.list
);
(0, import_product.perfMark)("preload(IndexDB)", performance.now() - tTotal, {
count: cachedData.length,
source: "IndexDB"
});
this.logInfo("预加载完成(从 IndexDB)", {
productCount: cachedData.length,
duration: `${Math.round(performance.now() - tTotal)}ms`,
source: "IndexDB"
});
return;
}
console.log("[Products] IndexDB 中没有缓存数据,从服务器加载...");
this.logInfo("IndexDB 中没有缓存数据,准备从服务器加载");
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn("[Products] 从 IndexDB 加载数据失败:", error);
this.logWarning("从 IndexDB 加载数据失败,准备从服务器加载", { error: errorMessage });
}
const tServer = performance.now();
const products = await this.loadProductsByServer();
(0, import_product.perfMark)("preload.loadFromServer", performance.now() - tServer, { count: (products == null ? void 0 : products.length) ?? 0 });
if (products && products.length > 0) {
this.store.list = products;
const tSync = performance.now();
this.syncProductsMap();
(0, import_product.perfMark)("preload.syncProductsMap", performance.now() - tSync, { count: products.length });
this.core.effects.emit(import_types.ProductsHooks.onProductsChanged, this.store.list);
(0, import_product.perfMark)("preload(Server)", performance.now() - tTotal, {
count: products.length,
source: "Server"
});
this.logInfo("预加载完成(从服务器)", {
productCount: products.length,
duration: `${Math.round(performance.now() - tTotal)}ms`,
source: "Server"
});
} else {
(0, import_product.perfMark)("preload(empty)", performance.now() - tTotal, { source: "empty" });
this.logWarning("预加载完成但未获取到数据", {
duration: `${Math.round(performance.now() - tTotal)}ms`
});
}
}
// =============================================
// 商品数据同步(pubsub)
// =============================================
/**
* 初始化 ProductDataSource 实例
* 与 pubsub 订阅和数据获取分开,仅负责创建实例
*/
initProductDataSource() {
var _a, _b;
const ProductDataSourceClass = (_b = (_a = this.core.serverOptions) == null ? void 0 : _a.All_DATA_SOURCES) == null ? void 0 : _b.ProductDataSource;
if (!ProductDataSourceClass) {
this.logWarning("initProductDataSource: ProductDataSource 不可用");
return;
}
this.productDataSource = new ProductDataSourceClass();
this.logInfo("ProductDataSource 实例已创建");
}
/**
* 初始化 pubsub 订阅,监听管理后台商品变更
* 仅负责订阅 product / product_quotation 频道,消息通过防抖合并后批量处理
* 数据获取由 loadProductsByServer 单独负责
*/
setupProductSync() {
if (!this.productDataSource) {
this.logWarning("setupProductSync: ProductDataSource 不可用,跳过同步初始化");
return;
}
const createHandler = (channelKey) => (message) => {
const data = (message == null ? void 0 : message.data) || message;
if (!data)
return;
console.log(`[ProductsModule] 收到同步消息 [${channelKey}]:`, data);
this.logInfo("收到同步消息", {
channelKey,
action: data.action,
id: data.id,
action_filed: data.action_filed
});
this.pendingSyncMessages.push({ ...data, _channelKey: channelKey });
if (this.syncTimer) {
clearTimeout(this.syncTimer);
}
this.syncTimer = setTimeout(() => {
this.processProductSyncMessages();
}, PRODUCT_SYNC_DEBOUNCE_MS);
};
this.productDataSource.run({
pubsub: {
callback: (res) => {
console.log("sse_products_callback", res);
this.logInfo("sse_products_callback: 收到同步消息", {
data: res == null ? void 0 : res.data
});
if (!(res == null ? void 0 : res.data))
return;
const data = res.data;
const channelKey = data.module || "product";
createHandler(channelKey)(data);
}
}
}).catch((err) => {
this.logError("setupProductSync: DataSource run 出错", {
error: err instanceof Error ? err.message : String(err)
});
});
this.logInfo("setupProductSync: pubsub 订阅已建立");
}
/**
* 处理防抖后的同步消息批次
*
* product 模块:
* - operation === 'delete' → 本地删除
* - change_types 包含 price → 仅收集变更 IDs(不拉商品数据)
* - 有 body → body 完整数据直接覆盖本地
* - 其他 → SSE 增量拉取
*
* product_collection / product_category:
* - 按 relation_product_ids SSE 拉取受影响商品
*
* product_quotation:
* - 报价单变更影响范围大,直接清除价格缓存走全量重建
*
* 处理完成后 emit onProductsSyncCompleted(携带 changedIds),
* Server 层监听该事件后对变更商品增量执行 prepareProductsWithPrice 并更新缓存
*/
async processProductSyncMessages() {
var _a, _b, _c, _d, _e, _f;
const messages = [...this.pendingSyncMessages];
this.pendingSyncMessages = [];
if (messages.length === 0)
return;
this.logInfo("processProductSyncMessages: 开始处理", { count: messages.length });
const deleteIds = [];
const bodyUpdates = /* @__PURE__ */ new Map();
const sseRefreshIds = [];
const priceRefreshIds = [];
let shouldClearPriceCache = false;
for (const msg of messages) {
const channelKey = msg._channelKey || msg.module || "product";
if (channelKey === "product") {
if ((_a = msg.relation_product_ids) == null ? void 0 : _a.length) {
sseRefreshIds.push(...msg.relation_product_ids);
}
if (msg.operation === "delete" || msg.action === "delete") {
if ((_b = msg.ids) == null ? void 0 : _b.length)
deleteIds.push(...msg.ids);
else if (msg.id)
deleteIds.push(msg.id);
continue;
}
if ((_c = msg.change_types) == null ? void 0 : _c.includes("price")) {
const ids = msg.ids || (msg.id ? [msg.id] : []);
priceRefreshIds.push(...ids);
if (msg.body) {
const bodyId = msg.body.id;
if (bodyId)
bodyUpdates.set(bodyId, msg.body);
}
continue;
}
if (msg.body) {
const bodyId = msg.body.id || msg.id;
if (bodyId)
bodyUpdates.set(bodyId, msg.body);
continue;
}
if ((_d = msg.ids) == null ? void 0 : _d.length) {
sseRefreshIds.push(...msg.ids);
} else if (msg.id) {
sseRefreshIds.push(msg.id);
}
} else if (channelKey === "product_quotation") {
shouldClearPriceCache = true;
if ((_e = msg.relation_product_ids) == null ? void 0 : _e.length) {
sseRefreshIds.push(...msg.relation_product_ids);
}
} else if (["product_collection", "product_category"].includes(channelKey)) {
if ((_f = msg.relation_product_ids) == null ? void 0 : _f.length) {
sseRefreshIds.push(...msg.relation_product_ids);
}
}
}
const uniqueDeleteIds = [...new Set(deleteIds)];
const uniqueSSEIds = [...new Set(sseRefreshIds)];
const uniquePriceIds = [...new Set(priceRefreshIds)];
if (uniqueDeleteIds.length > 0) {
await this.removeProductsByIds(uniqueDeleteIds);
}
if (bodyUpdates.size > 0) {
await this.applyBodyUpdatesToStore(bodyUpdates);
}
if (uniqueSSEIds.length > 0) {
const freshProducts = await this.fetchProductsBySSE(uniqueSSEIds);
if (freshProducts.length > 0) {
await this.mergeProductsToStore(freshProducts);
}
this.logInfo("processProductSyncMessages: SSE 增量更新完成123", {
requestedCount: uniqueSSEIds.length,
receivedCount: freshProducts.length
});
}
const allChangedIds = [.../* @__PURE__ */ new Set([
...Array.from(bodyUpdates.keys()),
...uniqueSSEIds,
...uniquePriceIds
])];
this.logInfo("processProductSyncMessages: 处理完成123", {
deleteCount: uniqueDeleteIds.length,
bodyUpdateCount: bodyUpdates.size,
sseRefreshCount: uniqueSSEIds.length,
priceRefreshCount: uniquePriceIds.length,
allChangedIdsCount: allChangedIds.length,
shouldClearPriceCache
});
const hasChanges = uniqueDeleteIds.length > 0 || allChangedIds.length > 0 || shouldClearPriceCache;
if (!hasChanges) {
this.logInfo("processProductSyncMessages: 没有变更,不触发 onProductsSyncCompleted");
return;
}
if (shouldClearPriceCache) {
this.clearPriceCache();
}
await this.core.effects.emit(import_types.ProductsHooks.onProductsSyncCompleted, {
changedIds: allChangedIds
});
}
/**
* 通过 SSE 按 ids 增量拉取商品数据
* 请求 GET /shop/core/stream?type=product&ids={ids}
*/
async fetchProductsBySSE(ids) {
if (!this.productDataSource) {
this.logWarning("fetchProductsBySSE: ProductDataSource 不可用");
return [];
}
this.logInfo("fetchProductsBySSE: 开始", { ids, count: ids.length });
const t0 = performance.now();
try {
const productList = await this.productDataSource.run({
sse: { query: { type: "product", ids } }
});
const list = productList || [];
(0, import_product.perfMark)("fetchProductsBySSE", performance.now() - t0, { count: list.length });
this.logInfo("fetchProductsBySSE: 成功", {
requestedCount: ids.length,
receivedCount: list.length,
duration: `${Math.round(performance.now() - t0)}ms`
});
return list;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.logError("fetchProductsBySSE: 失败", { ids, error: errorMessage });
return [];
}
}
/**
* 将 body 完整数据直接覆盖到本地 store(不调用报价单接口)
* 已存在的 → 直接替换;不存在的 → 追加
* 同时更新 Map 缓存、IndexDB,触发 onProductsChanged
* 价格缓存由 processProductSyncMessages 末尾统一清除
*/
async applyBodyUpdatesToStore(bodyUpdates) {
this.logInfo("applyBodyUpdatesToStore: 开始", { count: bodyUpdates.size });
let updatedCount = 0;
let newCount = 0;
const appliedIds = /* @__PURE__ */ new Set();
this.store.list = this.store.list.map((p) => {
if (bodyUpdates.has(p.id)) {
updatedCount++;
appliedIds.add(p.id);
return bodyUpdates.get(p.id);
}
return p;
});
for (const [id, body] of bodyUpdates) {
if (!appliedIds.has(id)) {
this.store.list.push(body);
newCount++;
}
}
this.syncProductsMap();
await this.saveProductsToIndexDB(this.store.list);
this.core.effects.emit(import_types.ProductsHooks.onProductsChanged, this.store.list);
this.logInfo("applyBodyUpdatesToStore: 完成", {
updatedCount,
newCount,
totalCount: this.store.list.length
});
}
/**
* 将增量拉取的商品合并到 store
* 已存在的 → 替换;新的 → 追加
* 同时更新 store.map、IndexDB,触发 onProductsChanged
*/
async mergeProductsToStore(freshProducts) {
const freshMap = /* @__PURE__ */ new Map();
for (const p of freshProducts) {
freshMap.set(p.id, p);
}
const updatedList = this.store.list.map((p) => {
if (freshMap.has(p.id)) {
const fresh = freshMap.get(p.id);
freshMap.delete(p.id);
return fresh;
}
return p;
});
const newCount = freshMap.size;
for (const p of freshMap.values()) {
updatedList.push(p);
}
const updatedCount = freshProducts.length - newCount;
this.store.list = updatedList;
this.syncProductsMap();
await this.saveProductsToIndexDB(this.store.list);
this.logInfo("mergeProductsToStore: 合并完成", {
updatedCount,
newCount,
totalCount: this.store.list.length
});
this.core.effects.emit(import_types.ProductsHooks.onProductsChanged, this.store.list);
}
/**
* 静默全量刷新:后台重新拉取全量 SSE 数据并更新本地
* 拿到完整数据后一次性替换 store,清除价格缓存,触发 onProductsSyncCompleted
* @returns 刷新后的商品列表
*/
async silentRefresh() {
const t0 = performance.now();
this.logInfo("silentRefresh 开始");
try {
const products = await this.loadProductsByServer();
if (products && products.length > 0) {
this.store.list = products;
this.syncProductsMap();
this.clearPriceCache();
this.core.effects.emit(import_types.ProductsHooks.onProductsChanged, this.store.list);
await this.core.effects.emit(import_types.ProductsHooks.onProductsSyncCompleted, null);
this.logInfo("silentRefresh 完成", {
productCount: products.length,
duration: `${Math.round(performance.now() - t0)}ms`
});
} else {
this.logWarning("silentRefresh: 服务器未返回数据");
}
(0, import_product.perfMark)("silentRefresh", performance.now() - t0, { count: this.store.list.length });
return this.store.list;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.logError("silentRefresh 失败", {
duration: `${Math.round(performance.now() - t0)}ms`,
error: errorMessage
});
return this.store.list;
}
}
/**
* 销毁同步资源(取消 pubsub 订阅、清除定时器)
*/
destroyProductSync() {
var _a;
if (this.syncTimer) {
clearTimeout(this.syncTimer);
this.syncTimer = void 0;
}
if ((_a = this.productDataSource) == null ? void 0 : _a.destroy) {
this.productDataSource.destroy();
}
this.pendingSyncMessages = [];
this.logInfo("destroyProductSync: 同步资源已销毁");
}
/**
* 获取模块的路由定义
* Products 模块暂不提供路由,由 Server 层统一处理
*/
getRoutes() {
return [];
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
ProductsModule
});