@pisell/pisellos 2.2.122 → 2.2.123
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.
- package/dist/server/index.d.ts +57 -3
- package/dist/server/index.js +735 -190
- package/dist/server/modules/order/index.d.ts +25 -3
- package/dist/server/modules/order/index.js +282 -136
- package/lib/server/index.d.ts +57 -3
- package/lib/server/index.js +471 -33
- package/lib/server/modules/order/index.d.ts +25 -3
- package/lib/server/modules/order/index.js +105 -24
- package/package.json +1 -1
package/lib/server/index.d.ts
CHANGED
|
@@ -29,6 +29,10 @@ declare class Server {
|
|
|
29
29
|
private productQuerySubscribers;
|
|
30
30
|
private orderQuerySubscribers;
|
|
31
31
|
private bookingQuerySubscribers;
|
|
32
|
+
private bookingRemoteQuerySubscribers;
|
|
33
|
+
private bookingRemoteCache;
|
|
34
|
+
private readonly BOOKING_REMOTE_CACHE_TTL_MS;
|
|
35
|
+
private readonly BOOKING_REMOTE_CACHE_MAX_ENTRIES;
|
|
32
36
|
private floorPlanQuerySubscribers;
|
|
33
37
|
private moduleRegistry;
|
|
34
38
|
constructor(core: PisellCore);
|
|
@@ -176,11 +180,61 @@ declare class Server {
|
|
|
176
180
|
*/
|
|
177
181
|
private handleUnsubscribeOrderQuery;
|
|
178
182
|
/**
|
|
179
|
-
*
|
|
183
|
+
* 从查询值中提取 YYYY-MM-DD 日期串。
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* this.extractQueryDate('2026-04-15 09:58:00') // => '2026-04-15'
|
|
180
187
|
*/
|
|
181
|
-
private
|
|
188
|
+
private extractQueryDate;
|
|
182
189
|
/**
|
|
183
|
-
*
|
|
190
|
+
* 判断时间范围是否「开始和结束都不是今天」。
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* this.isRangeBothNotToday('2026-04-13', '2026-04-14') // => true
|
|
194
|
+
*/
|
|
195
|
+
private isRangeBothNotToday;
|
|
196
|
+
/**
|
|
197
|
+
* 订单查询是否走本地:
|
|
198
|
+
* - 当 start_time 与 end_time 都不是今天时,走远端;
|
|
199
|
+
* - 其余情况走本地。
|
|
200
|
+
*/
|
|
201
|
+
private shouldUseLocalOrderQuery;
|
|
202
|
+
/**
|
|
203
|
+
* 预约查询是否走本地:
|
|
204
|
+
* - 当 sales_time_between 的开始和结束都不是今天时,走远端;
|
|
205
|
+
* - 其余情况走本地。
|
|
206
|
+
*/
|
|
207
|
+
private shouldUseLocalBookingQuery;
|
|
208
|
+
/**
|
|
209
|
+
* 日历 SSE 精简 with:只拉日历渲染必需的关联字段
|
|
210
|
+
*/
|
|
211
|
+
private resolveBookingSalesWith;
|
|
212
|
+
/**
|
|
213
|
+
* 日历 SSE 精简 select:渲染 + filterBookingsFromOrders 所需的主表字段
|
|
214
|
+
* business_code / payment_status 不直接用于渲染,但 matchOrder 过滤依赖
|
|
215
|
+
*/
|
|
216
|
+
private resolveBookingSalesSelect;
|
|
217
|
+
private normalizeBookingRemoteQueryPayload;
|
|
218
|
+
/**
|
|
219
|
+
* 远端预约查询内存缓存开关:默认 false,仅显式 true 才启用。
|
|
220
|
+
*/
|
|
221
|
+
private isBookingRemoteMemoryCacheEnabled;
|
|
222
|
+
private stableSerialize;
|
|
223
|
+
private buildBookingRemoteCacheKey;
|
|
224
|
+
private parseBookingSalesRangeMeta;
|
|
225
|
+
private readBookingRemoteCache;
|
|
226
|
+
private writeBookingRemoteCache;
|
|
227
|
+
private toOrderIdKey;
|
|
228
|
+
private markBookingRemoteCacheAllStale;
|
|
229
|
+
private buildBookingResponse;
|
|
230
|
+
private notifyBookingRemoteSubscribersByCacheKey;
|
|
231
|
+
private syncBookingRemoteCacheFromOrderChanges;
|
|
232
|
+
/**
|
|
233
|
+
* 非本地窗口的订单查询:走真实 API。
|
|
234
|
+
*/
|
|
235
|
+
private fetchOrderListFromAPI;
|
|
236
|
+
/**
|
|
237
|
+
* 非今天的预约查询:通过 SSE 流式拉取订单,再做本地 booking 筛选展开
|
|
184
238
|
*/
|
|
185
239
|
private fetchBookingListFromAPI;
|
|
186
240
|
/**
|
package/lib/server/index.js
CHANGED
|
@@ -65,6 +65,10 @@ var Server = class {
|
|
|
65
65
|
// ---- 订单 / 预约列表查询订阅者 ----
|
|
66
66
|
this.orderQuerySubscribers = /* @__PURE__ */ new Map();
|
|
67
67
|
this.bookingQuerySubscribers = /* @__PURE__ */ new Map();
|
|
68
|
+
this.bookingRemoteQuerySubscribers = /* @__PURE__ */ new Map();
|
|
69
|
+
this.bookingRemoteCache = /* @__PURE__ */ new Map();
|
|
70
|
+
this.BOOKING_REMOTE_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
71
|
+
this.BOOKING_REMOTE_CACHE_MAX_ENTRIES = 80;
|
|
68
72
|
this.floorPlanQuerySubscribers = /* @__PURE__ */ new Map();
|
|
69
73
|
// 模块注册表 - 定义所有可用的模块配置
|
|
70
74
|
this.moduleRegistry = {
|
|
@@ -180,18 +184,32 @@ var Server = class {
|
|
|
180
184
|
console.log("[Server] handleOrderList:", url, method, data, config);
|
|
181
185
|
const queryPayload = data && typeof data === "object" ? { ...data } : {};
|
|
182
186
|
const { callback, subscriberId } = config || {};
|
|
183
|
-
this.
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
187
|
+
const useLocal = this.shouldUseLocalOrderQuery(queryPayload);
|
|
188
|
+
this.logInfo("handleOrderList: 开始处理订单列表请求", {
|
|
189
|
+
data: queryPayload,
|
|
190
|
+
useLocal
|
|
191
|
+
});
|
|
192
|
+
if (useLocal) {
|
|
193
|
+
if (subscriberId && typeof callback === "function") {
|
|
194
|
+
this.orderQuerySubscribers.set(subscriberId, {
|
|
195
|
+
callback,
|
|
196
|
+
context: queryPayload
|
|
197
|
+
});
|
|
198
|
+
this.logInfo("handleOrderList: 已注册订阅者", {
|
|
199
|
+
subscriberId,
|
|
200
|
+
totalSubscribers: this.orderQuerySubscribers.size
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return this.computeOrderQueryResult(queryPayload);
|
|
204
|
+
}
|
|
205
|
+
if (subscriberId) {
|
|
206
|
+
this.orderQuerySubscribers.delete(subscriberId);
|
|
207
|
+
this.logInfo("handleOrderList: 已清理订阅者(走远端)", {
|
|
190
208
|
subscriberId,
|
|
191
|
-
|
|
209
|
+
remaining: this.orderQuerySubscribers.size
|
|
192
210
|
});
|
|
193
211
|
}
|
|
194
|
-
return this.
|
|
212
|
+
return this.fetchOrderListFromAPI(queryPayload);
|
|
195
213
|
};
|
|
196
214
|
/**
|
|
197
215
|
* 取消订单列表查询订阅(HTTP 路由入口)
|
|
@@ -215,12 +233,15 @@ var Server = class {
|
|
|
215
233
|
console.log("[Server] handleBookingList:", url, method, data, config);
|
|
216
234
|
const queryPayload = data && typeof data === "object" ? { ...data } : {};
|
|
217
235
|
const { callback, subscriberId } = config || {};
|
|
218
|
-
const
|
|
236
|
+
const useLocal = this.shouldUseLocalBookingQuery(queryPayload);
|
|
219
237
|
this.logInfo("handleBookingList: 开始处理预约列表请求", {
|
|
220
238
|
data: queryPayload,
|
|
221
|
-
|
|
239
|
+
useLocal
|
|
222
240
|
});
|
|
223
|
-
if (
|
|
241
|
+
if (useLocal) {
|
|
242
|
+
if (subscriberId) {
|
|
243
|
+
this.bookingRemoteQuerySubscribers.delete(subscriberId);
|
|
244
|
+
}
|
|
224
245
|
if (subscriberId && typeof callback === "function") {
|
|
225
246
|
this.bookingQuerySubscribers.set(subscriberId, {
|
|
226
247
|
callback,
|
|
@@ -235,6 +256,26 @@ var Server = class {
|
|
|
235
256
|
} else {
|
|
236
257
|
if (subscriberId) {
|
|
237
258
|
this.bookingQuerySubscribers.delete(subscriberId);
|
|
259
|
+
}
|
|
260
|
+
if (subscriberId && typeof callback === "function") {
|
|
261
|
+
const withFields = this.resolveBookingSalesWith(queryPayload);
|
|
262
|
+
const requestPayload = this.normalizeBookingRemoteQueryPayload(
|
|
263
|
+
queryPayload,
|
|
264
|
+
withFields
|
|
265
|
+
);
|
|
266
|
+
const cacheKey = this.buildBookingRemoteCacheKey(requestPayload);
|
|
267
|
+
this.bookingRemoteQuerySubscribers.set(subscriberId, {
|
|
268
|
+
callback,
|
|
269
|
+
context: queryPayload,
|
|
270
|
+
cacheKey
|
|
271
|
+
});
|
|
272
|
+
this.logInfo("handleBookingList: 已注册订阅者(非今天)", {
|
|
273
|
+
subscriberId,
|
|
274
|
+
cacheKey,
|
|
275
|
+
totalSubscribers: this.bookingRemoteQuerySubscribers.size
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
if (subscriberId) {
|
|
238
279
|
this.logInfo("handleBookingList: 已清理订阅者(非今天)", {
|
|
239
280
|
subscriberId,
|
|
240
281
|
remaining: this.bookingQuerySubscribers.size
|
|
@@ -301,9 +342,11 @@ var Server = class {
|
|
|
301
342
|
const { subscriberId } = data || {};
|
|
302
343
|
if (subscriberId) {
|
|
303
344
|
this.bookingQuerySubscribers.delete(subscriberId);
|
|
345
|
+
this.bookingRemoteQuerySubscribers.delete(subscriberId);
|
|
304
346
|
this.logInfo("handleUnsubscribeBookingQuery: 已移除订阅者", {
|
|
305
347
|
subscriberId,
|
|
306
|
-
|
|
348
|
+
remainingLocal: this.bookingQuerySubscribers.size,
|
|
349
|
+
remainingRemote: this.bookingRemoteQuerySubscribers.size
|
|
307
350
|
});
|
|
308
351
|
}
|
|
309
352
|
return { code: 200, message: "ok", status: true };
|
|
@@ -634,6 +677,7 @@ var Server = class {
|
|
|
634
677
|
this.core.effects.on(import_types3.OrderHooks.onOrdersChanged, () => {
|
|
635
678
|
this.recomputeAndNotifyOrderQuery();
|
|
636
679
|
this.recomputeAndNotifyBookingQuery();
|
|
680
|
+
this.syncBookingRemoteCacheFromOrderChanges();
|
|
637
681
|
});
|
|
638
682
|
const duration = Date.now() - startTime;
|
|
639
683
|
this.logInfo("Server 初始化完成", {
|
|
@@ -927,23 +971,307 @@ var Server = class {
|
|
|
927
971
|
}
|
|
928
972
|
}
|
|
929
973
|
/**
|
|
930
|
-
*
|
|
974
|
+
* 从查询值中提取 YYYY-MM-DD 日期串。
|
|
975
|
+
*
|
|
976
|
+
* @example
|
|
977
|
+
* this.extractQueryDate('2026-04-15 09:58:00') // => '2026-04-15'
|
|
978
|
+
*/
|
|
979
|
+
extractQueryDate(raw) {
|
|
980
|
+
if (raw == null)
|
|
981
|
+
return null;
|
|
982
|
+
const date = (0, import_dayjs.default)(raw);
|
|
983
|
+
if (date.isValid()) {
|
|
984
|
+
return date.format("YYYY-MM-DD");
|
|
985
|
+
}
|
|
986
|
+
const text = String(raw).trim();
|
|
987
|
+
if (!text)
|
|
988
|
+
return null;
|
|
989
|
+
return text.split("T")[0].split(" ")[0] || null;
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
992
|
+
* 判断时间范围是否「开始和结束都不是今天」。
|
|
993
|
+
*
|
|
994
|
+
* @example
|
|
995
|
+
* this.isRangeBothNotToday('2026-04-13', '2026-04-14') // => true
|
|
996
|
+
*/
|
|
997
|
+
isRangeBothNotToday(startRaw, endRaw) {
|
|
998
|
+
const startDate = this.extractQueryDate(startRaw);
|
|
999
|
+
const endDate = this.extractQueryDate(endRaw);
|
|
1000
|
+
if (!startDate || !endDate)
|
|
1001
|
+
return false;
|
|
1002
|
+
const today = (0, import_dayjs.default)().format("YYYY-MM-DD");
|
|
1003
|
+
return startDate !== today && endDate !== today;
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* 订单查询是否走本地:
|
|
1007
|
+
* - 当 start_time 与 end_time 都不是今天时,走远端;
|
|
1008
|
+
* - 其余情况走本地。
|
|
1009
|
+
*/
|
|
1010
|
+
shouldUseLocalOrderQuery(data) {
|
|
1011
|
+
return !this.isRangeBothNotToday(data == null ? void 0 : data.start_time, data == null ? void 0 : data.end_time);
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* 预约查询是否走本地:
|
|
1015
|
+
* - 当 sales_time_between 的开始和结束都不是今天时,走远端;
|
|
1016
|
+
* - 其余情况走本地。
|
|
931
1017
|
*/
|
|
932
|
-
|
|
1018
|
+
shouldUseLocalBookingQuery(data) {
|
|
933
1019
|
const range = data == null ? void 0 : data.sales_time_between;
|
|
934
|
-
if (!Array.isArray(range) || range.length <
|
|
1020
|
+
if (!Array.isArray(range) || range.length < 2)
|
|
935
1021
|
return true;
|
|
936
|
-
|
|
937
|
-
const todayStr = (0, import_dayjs.default)().format("YYYY-MM-DD");
|
|
938
|
-
return startDateStr === todayStr;
|
|
1022
|
+
return !this.isRangeBothNotToday(range[0], range[1]);
|
|
939
1023
|
}
|
|
940
1024
|
/**
|
|
941
|
-
*
|
|
1025
|
+
* 日历 SSE 精简 with:只拉日历渲染必需的关联字段
|
|
942
1026
|
*/
|
|
943
|
-
|
|
1027
|
+
resolveBookingSalesWith(_queryPayload) {
|
|
1028
|
+
return [
|
|
1029
|
+
"bookings:booking_id,start_date,start_time,end_date,end_time,holder,metadata,parent_id,item_type",
|
|
1030
|
+
"customer:id,display_name,phone",
|
|
1031
|
+
"products"
|
|
1032
|
+
];
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* 日历 SSE 精简 select:渲染 + filterBookingsFromOrders 所需的主表字段
|
|
1036
|
+
* business_code / payment_status 不直接用于渲染,但 matchOrder 过滤依赖
|
|
1037
|
+
*/
|
|
1038
|
+
resolveBookingSalesSelect() {
|
|
1039
|
+
return "status,payment_status,business_code,phone,customer_name,schedule_date,created_at,metadata";
|
|
1040
|
+
}
|
|
1041
|
+
normalizeBookingRemoteQueryPayload(data, withFields) {
|
|
1042
|
+
return {
|
|
1043
|
+
...data,
|
|
1044
|
+
form_record_ids: void 0,
|
|
1045
|
+
enable_remote_memory_cache: void 0,
|
|
1046
|
+
with: withFields,
|
|
1047
|
+
select: this.resolveBookingSalesSelect(),
|
|
1048
|
+
chunk_size: 50
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* 远端预约查询内存缓存开关:默认 false,仅显式 true 才启用。
|
|
1053
|
+
*/
|
|
1054
|
+
isBookingRemoteMemoryCacheEnabled(queryPayload) {
|
|
1055
|
+
return (queryPayload == null ? void 0 : queryPayload.enable_remote_memory_cache) === true;
|
|
1056
|
+
}
|
|
1057
|
+
stableSerialize(value) {
|
|
1058
|
+
if (value === null || value === void 0)
|
|
1059
|
+
return "null";
|
|
1060
|
+
if (Array.isArray(value)) {
|
|
1061
|
+
return `[${value.map((item) => this.stableSerialize(item)).join(",")}]`;
|
|
1062
|
+
}
|
|
1063
|
+
if (typeof value === "object") {
|
|
1064
|
+
const obj = value;
|
|
1065
|
+
const keys = Object.keys(obj).filter((k) => obj[k] !== void 0).sort();
|
|
1066
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${this.stableSerialize(obj[k])}`).join(",")}}`;
|
|
1067
|
+
}
|
|
1068
|
+
return JSON.stringify(value);
|
|
1069
|
+
}
|
|
1070
|
+
buildBookingRemoteCacheKey(payload) {
|
|
1071
|
+
return this.stableSerialize(payload);
|
|
1072
|
+
}
|
|
1073
|
+
parseBookingSalesRangeMeta(payload) {
|
|
1074
|
+
const range = payload == null ? void 0 : payload.sales_time_between;
|
|
1075
|
+
if (!Array.isArray(range) || range.length < 2)
|
|
1076
|
+
return null;
|
|
1077
|
+
const startAtMs = (0, import_dayjs.default)(range[0]).valueOf();
|
|
1078
|
+
const endAtMs = (0, import_dayjs.default)(range[1]).valueOf();
|
|
1079
|
+
if (!Number.isFinite(startAtMs) || !Number.isFinite(endAtMs))
|
|
1080
|
+
return null;
|
|
1081
|
+
if (endAtMs <= startAtMs)
|
|
1082
|
+
return null;
|
|
1083
|
+
return { startAtMs, endAtMs };
|
|
1084
|
+
}
|
|
1085
|
+
readBookingRemoteCache(cacheKey) {
|
|
1086
|
+
const entry = this.bookingRemoteCache.get(cacheKey);
|
|
1087
|
+
if (!entry)
|
|
1088
|
+
return null;
|
|
1089
|
+
if (entry.stale) {
|
|
1090
|
+
this.bookingRemoteCache.delete(cacheKey);
|
|
1091
|
+
return null;
|
|
1092
|
+
}
|
|
1093
|
+
const now = Date.now();
|
|
1094
|
+
if (now - entry.updatedAt > this.BOOKING_REMOTE_CACHE_TTL_MS) {
|
|
1095
|
+
this.bookingRemoteCache.delete(cacheKey);
|
|
1096
|
+
return null;
|
|
1097
|
+
}
|
|
1098
|
+
return entry;
|
|
1099
|
+
}
|
|
1100
|
+
writeBookingRemoteCache(entry) {
|
|
1101
|
+
this.bookingRemoteCache.set(entry.key, entry);
|
|
1102
|
+
if (this.bookingRemoteCache.size <= this.BOOKING_REMOTE_CACHE_MAX_ENTRIES) {
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
let oldestKey = null;
|
|
1106
|
+
let oldestTime = Number.POSITIVE_INFINITY;
|
|
1107
|
+
for (const [key, cache] of this.bookingRemoteCache.entries()) {
|
|
1108
|
+
if (cache.updatedAt < oldestTime) {
|
|
1109
|
+
oldestTime = cache.updatedAt;
|
|
1110
|
+
oldestKey = key;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
if (oldestKey)
|
|
1114
|
+
this.bookingRemoteCache.delete(oldestKey);
|
|
1115
|
+
}
|
|
1116
|
+
toOrderIdKey(order) {
|
|
1117
|
+
if (!order || typeof order !== "object")
|
|
1118
|
+
return null;
|
|
1119
|
+
const raw = order.order_id;
|
|
1120
|
+
if (raw === void 0 || raw === null || raw === "")
|
|
1121
|
+
return null;
|
|
1122
|
+
return String(raw);
|
|
1123
|
+
}
|
|
1124
|
+
markBookingRemoteCacheAllStale(reason) {
|
|
1125
|
+
if (this.bookingRemoteCache.size === 0)
|
|
1126
|
+
return;
|
|
1127
|
+
for (const entry of this.bookingRemoteCache.values()) {
|
|
1128
|
+
entry.stale = true;
|
|
1129
|
+
}
|
|
1130
|
+
this.logWarning("bookingRemoteCache: 全量标记失效", {
|
|
1131
|
+
reason,
|
|
1132
|
+
cacheSize: this.bookingRemoteCache.size
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
buildBookingResponse(bookingResult, extra) {
|
|
1136
|
+
return {
|
|
1137
|
+
code: 200,
|
|
1138
|
+
data: {
|
|
1139
|
+
...extra ?? {},
|
|
1140
|
+
list: bookingResult.list || [],
|
|
1141
|
+
count: bookingResult.count || 0,
|
|
1142
|
+
size: bookingResult.size || 0,
|
|
1143
|
+
skip: bookingResult.skip || 0
|
|
1144
|
+
},
|
|
1145
|
+
message: "",
|
|
1146
|
+
status: true
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
notifyBookingRemoteSubscribersByCacheKey(cacheKey, entry, reason) {
|
|
1150
|
+
if (this.bookingRemoteQuerySubscribers.size === 0)
|
|
1151
|
+
return;
|
|
1152
|
+
for (const [subscriberId, subscriber] of this.bookingRemoteQuerySubscribers.entries()) {
|
|
1153
|
+
if (subscriber.cacheKey !== cacheKey)
|
|
1154
|
+
continue;
|
|
1155
|
+
try {
|
|
1156
|
+
const callbackStartAt = Date.now();
|
|
1157
|
+
subscriber.callback(
|
|
1158
|
+
this.buildBookingResponse(entry.bookingResult, {
|
|
1159
|
+
cache_hit: true,
|
|
1160
|
+
from_cache_sync: true,
|
|
1161
|
+
sync_reason: reason
|
|
1162
|
+
})
|
|
1163
|
+
);
|
|
1164
|
+
const callbackEndAt = Date.now();
|
|
1165
|
+
this.logInfo("notifyBookingRemoteSubscribersByCacheKey: 已推送", {
|
|
1166
|
+
subscriberId,
|
|
1167
|
+
cacheKey,
|
|
1168
|
+
reason,
|
|
1169
|
+
callbackDurationMs: callbackEndAt - callbackStartAt
|
|
1170
|
+
});
|
|
1171
|
+
} catch (error) {
|
|
1172
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
1173
|
+
this.logError("notifyBookingRemoteSubscribersByCacheKey: 推送失败", {
|
|
1174
|
+
subscriberId,
|
|
1175
|
+
cacheKey,
|
|
1176
|
+
reason,
|
|
1177
|
+
error: errMsg
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
syncBookingRemoteCacheFromOrderChanges() {
|
|
944
1183
|
var _a, _b;
|
|
1184
|
+
if (this.bookingRemoteCache.size === 0)
|
|
1185
|
+
return;
|
|
1186
|
+
const syncStartAt = Date.now();
|
|
1187
|
+
const latestOrders = ((_b = (_a = this.order) == null ? void 0 : _a.getOrders) == null ? void 0 : _b.call(_a)) ?? [];
|
|
1188
|
+
if (!Array.isArray(latestOrders) || latestOrders.length === 0) {
|
|
1189
|
+
this.markBookingRemoteCacheAllStale("orderStoreEmptyAfterChange");
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
let syncedEntryCount = 0;
|
|
1193
|
+
for (const [cacheKey, entry] of this.bookingRemoteCache.entries()) {
|
|
1194
|
+
if (entry.stale) {
|
|
1195
|
+
this.bookingRemoteCache.delete(cacheKey);
|
|
1196
|
+
continue;
|
|
1197
|
+
}
|
|
1198
|
+
try {
|
|
1199
|
+
const queryPayload = entry.queryPayload;
|
|
1200
|
+
const orderMap = /* @__PURE__ */ new Map();
|
|
1201
|
+
for (const order of entry.rawOrders) {
|
|
1202
|
+
const key = this.toOrderIdKey(order);
|
|
1203
|
+
if (key)
|
|
1204
|
+
orderMap.set(key, order);
|
|
1205
|
+
}
|
|
1206
|
+
let changed = false;
|
|
1207
|
+
for (const latest of latestOrders) {
|
|
1208
|
+
const key = this.toOrderIdKey(latest);
|
|
1209
|
+
if (!key)
|
|
1210
|
+
continue;
|
|
1211
|
+
if (orderMap.has(key)) {
|
|
1212
|
+
orderMap.set(key, latest);
|
|
1213
|
+
changed = true;
|
|
1214
|
+
continue;
|
|
1215
|
+
}
|
|
1216
|
+
orderMap.set(key, latest);
|
|
1217
|
+
changed = true;
|
|
1218
|
+
}
|
|
1219
|
+
if (!changed)
|
|
1220
|
+
continue;
|
|
1221
|
+
const mergedOrders = [...orderMap.values()];
|
|
1222
|
+
const filterStartAt = Date.now();
|
|
1223
|
+
const bookingResult = (0, import_filterBookings.sortBookings)(
|
|
1224
|
+
(0, import_filterBookings.filterBookingsFromOrders)(mergedOrders, queryPayload),
|
|
1225
|
+
queryPayload
|
|
1226
|
+
);
|
|
1227
|
+
const filterEndAt = Date.now();
|
|
1228
|
+
const nextEntry = {
|
|
1229
|
+
...entry,
|
|
1230
|
+
rawOrders: mergedOrders,
|
|
1231
|
+
bookingResult,
|
|
1232
|
+
updatedAt: Date.now(),
|
|
1233
|
+
stale: false
|
|
1234
|
+
};
|
|
1235
|
+
this.writeBookingRemoteCache(nextEntry);
|
|
1236
|
+
this.notifyBookingRemoteSubscribersByCacheKey(
|
|
1237
|
+
cacheKey,
|
|
1238
|
+
nextEntry,
|
|
1239
|
+
"orders_changed"
|
|
1240
|
+
);
|
|
1241
|
+
syncedEntryCount += 1;
|
|
1242
|
+
this.logInfo("bookingRemoteCache: 单条缓存同步完成", {
|
|
1243
|
+
cacheKey,
|
|
1244
|
+
mergedOrderCount: mergedOrders.length,
|
|
1245
|
+
filteredBookingCount: bookingResult.count,
|
|
1246
|
+
filterDurationMs: filterEndAt - filterStartAt
|
|
1247
|
+
});
|
|
1248
|
+
} catch (error) {
|
|
1249
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
1250
|
+
this.logWarning("bookingRemoteCache: 同步失败并标记失效", {
|
|
1251
|
+
cacheKey,
|
|
1252
|
+
error: errMsg
|
|
1253
|
+
});
|
|
1254
|
+
const current = this.bookingRemoteCache.get(cacheKey);
|
|
1255
|
+
if (current)
|
|
1256
|
+
current.stale = true;
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
const syncEndAt = Date.now();
|
|
1260
|
+
if (syncedEntryCount > 0) {
|
|
1261
|
+
this.logInfo("bookingRemoteCache: 已同步 Ably 变更", {
|
|
1262
|
+
syncedEntryCount,
|
|
1263
|
+
cacheSize: this.bookingRemoteCache.size,
|
|
1264
|
+
totalSyncDurationMs: syncEndAt - syncStartAt
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
/**
|
|
1269
|
+
* 非本地窗口的订单查询:走真实 API。
|
|
1270
|
+
*/
|
|
1271
|
+
async fetchOrderListFromAPI(data) {
|
|
1272
|
+
var _a;
|
|
945
1273
|
if (!((_a = this.app) == null ? void 0 : _a.request)) {
|
|
946
|
-
this.logError("
|
|
1274
|
+
this.logError("fetchOrderListFromAPI: app.request 不可用");
|
|
947
1275
|
return {
|
|
948
1276
|
code: 500,
|
|
949
1277
|
message: "app.request 不可用",
|
|
@@ -952,24 +1280,118 @@ var Server = class {
|
|
|
952
1280
|
};
|
|
953
1281
|
}
|
|
954
1282
|
try {
|
|
955
|
-
const response = await this.app.request.
|
|
1283
|
+
const response = await this.app.request.post("/shop/order/v2/list", data, {
|
|
956
1284
|
isShopApi: true
|
|
957
1285
|
});
|
|
958
|
-
const
|
|
959
|
-
const list = (0
|
|
960
|
-
|
|
961
|
-
rawCount: rawList.length,
|
|
962
|
-
flattenedCount: list.count
|
|
963
|
-
});
|
|
1286
|
+
const payload = (response == null ? void 0 : response.data) ?? response;
|
|
1287
|
+
const list = Array.isArray(payload == null ? void 0 : payload.list) ? payload.list : [];
|
|
1288
|
+
const count = typeof (payload == null ? void 0 : payload.count) === "number" ? payload.count : list.length;
|
|
964
1289
|
return {
|
|
965
1290
|
code: 200,
|
|
966
|
-
data: { ...
|
|
1291
|
+
data: { ...payload, list, count },
|
|
967
1292
|
message: "",
|
|
968
1293
|
status: true
|
|
969
1294
|
};
|
|
970
1295
|
} catch (error) {
|
|
971
1296
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
972
|
-
this.logError("
|
|
1297
|
+
this.logError("fetchOrderListFromAPI: 请求失败", { error: errorMessage });
|
|
1298
|
+
return {
|
|
1299
|
+
code: 500,
|
|
1300
|
+
message: errorMessage,
|
|
1301
|
+
data: { list: [], count: 0 },
|
|
1302
|
+
status: false
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
/**
|
|
1307
|
+
* 非今天的预约查询:通过 SSE 流式拉取订单,再做本地 booking 筛选展开
|
|
1308
|
+
*/
|
|
1309
|
+
async fetchBookingListFromAPI(data) {
|
|
1310
|
+
var _a;
|
|
1311
|
+
if (!this.order) {
|
|
1312
|
+
this.logError("fetchBookingListFromAPI: Order 模块不可用");
|
|
1313
|
+
return {
|
|
1314
|
+
code: 500,
|
|
1315
|
+
message: "Order 模块不可用",
|
|
1316
|
+
data: { list: [], count: 0 },
|
|
1317
|
+
status: false
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
try {
|
|
1321
|
+
const memoryCacheEnabled = this.isBookingRemoteMemoryCacheEnabled(data);
|
|
1322
|
+
const withFields = this.resolveBookingSalesWith(data);
|
|
1323
|
+
const requestPayload = this.normalizeBookingRemoteQueryPayload(
|
|
1324
|
+
data,
|
|
1325
|
+
withFields
|
|
1326
|
+
);
|
|
1327
|
+
const cacheKey = this.buildBookingRemoteCacheKey(requestPayload);
|
|
1328
|
+
if (memoryCacheEnabled) {
|
|
1329
|
+
const cached = this.readBookingRemoteCache(cacheKey);
|
|
1330
|
+
if (cached) {
|
|
1331
|
+
this.logInfo("fetchBookingListFromAPI: 命中内存缓存", {
|
|
1332
|
+
cacheKey,
|
|
1333
|
+
listCount: cached.bookingResult.count,
|
|
1334
|
+
withFields: cached.withFields
|
|
1335
|
+
});
|
|
1336
|
+
return this.buildBookingResponse(cached.bookingResult, {
|
|
1337
|
+
cache_hit: true
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
const rawList = await this.order.fetchOrdersBySSE(requestPayload);
|
|
1342
|
+
const bookingResult = (0, import_filterBookings.sortBookings)(
|
|
1343
|
+
(0, import_filterBookings.filterBookingsFromOrders)(rawList, data),
|
|
1344
|
+
data
|
|
1345
|
+
);
|
|
1346
|
+
if (typeof globalThis !== "undefined") {
|
|
1347
|
+
globalThis.__SSE_BOOKING_DEBUG__ = {
|
|
1348
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1349
|
+
sseRequestPayload: requestPayload,
|
|
1350
|
+
calendarFilters: data,
|
|
1351
|
+
rawOrderCount: rawList.length,
|
|
1352
|
+
rawOrderSample: rawList.slice(0, 2).map((o) => {
|
|
1353
|
+
var _a2, _b, _c;
|
|
1354
|
+
return {
|
|
1355
|
+
order_id: o == null ? void 0 : o.order_id,
|
|
1356
|
+
business_code: o == null ? void 0 : o.business_code,
|
|
1357
|
+
status: o == null ? void 0 : o.status,
|
|
1358
|
+
payment_status: o == null ? void 0 : o.payment_status,
|
|
1359
|
+
bookingsCount: (_a2 = o == null ? void 0 : o.bookings) == null ? void 0 : _a2.length,
|
|
1360
|
+
firstBooking: ((_b = o == null ? void 0 : o.bookings) == null ? void 0 : _b[0]) ? {
|
|
1361
|
+
start_date: o.bookings[0].start_date,
|
|
1362
|
+
start_time: o.bookings[0].start_time,
|
|
1363
|
+
parent_id: o.bookings[0].parent_id,
|
|
1364
|
+
item_type: o.bookings[0].item_type,
|
|
1365
|
+
resourcesCount: (_c = o.bookings[0].resources) == null ? void 0 : _c.length
|
|
1366
|
+
} : null
|
|
1367
|
+
};
|
|
1368
|
+
}),
|
|
1369
|
+
filteredBookingCount: bookingResult.count,
|
|
1370
|
+
filteredListLength: (_a = bookingResult.list) == null ? void 0 : _a.length
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
if (memoryCacheEnabled) {
|
|
1374
|
+
this.writeBookingRemoteCache({
|
|
1375
|
+
key: cacheKey,
|
|
1376
|
+
queryPayload: data,
|
|
1377
|
+
withFields,
|
|
1378
|
+
rawOrders: Array.isArray(rawList) ? rawList : [],
|
|
1379
|
+
bookingResult,
|
|
1380
|
+
updatedAt: Date.now(),
|
|
1381
|
+
stale: false,
|
|
1382
|
+
rangeMeta: this.parseBookingSalesRangeMeta(requestPayload)
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
this.logInfo("fetchBookingListFromAPI: SSE 返回并拆分完成", {
|
|
1386
|
+
rawCount: rawList.length,
|
|
1387
|
+
flattenedCount: bookingResult.count,
|
|
1388
|
+
cacheKey,
|
|
1389
|
+
withFields
|
|
1390
|
+
});
|
|
1391
|
+
return this.buildBookingResponse(bookingResult);
|
|
1392
|
+
} catch (error) {
|
|
1393
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1394
|
+
this.logError("fetchBookingListFromAPI: SSE 请求失败", { error: errorMessage });
|
|
973
1395
|
return {
|
|
974
1396
|
code: 500,
|
|
975
1397
|
message: errorMessage,
|
|
@@ -1266,14 +1688,25 @@ var Server = class {
|
|
|
1266
1688
|
async recomputeAndNotifyOrderQuery() {
|
|
1267
1689
|
if (this.orderQuerySubscribers.size === 0)
|
|
1268
1690
|
return;
|
|
1691
|
+
const notifyStartAt = Date.now();
|
|
1269
1692
|
this.logInfo("recomputeAndNotifyOrderQuery: 开始推送", {
|
|
1270
|
-
subscriberCount: this.orderQuerySubscribers.size
|
|
1693
|
+
subscriberCount: this.orderQuerySubscribers.size,
|
|
1694
|
+
notifyStartAt: new Date(notifyStartAt).toISOString()
|
|
1271
1695
|
});
|
|
1272
1696
|
for (const [subscriberId, subscriber] of this.orderQuerySubscribers.entries()) {
|
|
1273
1697
|
try {
|
|
1698
|
+
const computeStartAt = Date.now();
|
|
1274
1699
|
const result = await this.computeOrderQueryResult(subscriber.context);
|
|
1700
|
+
const computeEndAt = Date.now();
|
|
1701
|
+
const callbackStartAt = Date.now();
|
|
1275
1702
|
subscriber.callback(result);
|
|
1276
|
-
|
|
1703
|
+
const callbackEndAt = Date.now();
|
|
1704
|
+
this.logInfo("recomputeAndNotifyOrderQuery: 已推送", {
|
|
1705
|
+
subscriberId,
|
|
1706
|
+
computeDurationMs: computeEndAt - computeStartAt,
|
|
1707
|
+
callbackDurationMs: callbackEndAt - callbackStartAt,
|
|
1708
|
+
totalDurationMs: callbackEndAt - computeStartAt
|
|
1709
|
+
});
|
|
1277
1710
|
} catch (error) {
|
|
1278
1711
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1279
1712
|
this.logError("recomputeAndNotifyOrderQuery: 推送失败", {
|
|
@@ -1282,6 +1715,11 @@ var Server = class {
|
|
|
1282
1715
|
});
|
|
1283
1716
|
}
|
|
1284
1717
|
}
|
|
1718
|
+
const notifyEndAt = Date.now();
|
|
1719
|
+
this.logInfo("recomputeAndNotifyOrderQuery: 推送完成", {
|
|
1720
|
+
subscriberCount: this.orderQuerySubscribers.size,
|
|
1721
|
+
totalNotifyDurationMs: notifyEndAt - notifyStartAt
|
|
1722
|
+
});
|
|
1285
1723
|
}
|
|
1286
1724
|
/**
|
|
1287
1725
|
* 预约数据变更后,遍历订阅者重新计算并通过 callback 推送
|