|
|
@@ -187,6 +187,13 @@ Page({
|
|
|
this.checkAndHandleScan(options);
|
|
|
|
|
|
plugin.init(config, wx.getStorageSync('invite'))
|
|
|
+
|
|
|
+ try {
|
|
|
+ getApp().checkTrafficDeductPending = this.checkTrafficDeductPending.bind(this);
|
|
|
+ console.log('🚦 [traffic] 已注册全局复判方法 checkTrafficDeductPending');
|
|
|
+ } catch (e) {
|
|
|
+ console.warn('🚦 [traffic] 注册全局复判方法失败:', e?.message || e);
|
|
|
+ }
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
@@ -200,6 +207,14 @@ Page({
|
|
|
*/
|
|
|
onShow: async function () {
|
|
|
await this.review()
|
|
|
+ try {
|
|
|
+ if (typeof this.checkTrafficDeductPending === 'function') {
|
|
|
+ console.log('🚦 [traffic] onShow 触发暂缓扣减复判');
|
|
|
+ await this.checkTrafficDeductPending();
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.warn('🚦 [traffic] onShow 复判触发失败:', e?.message || e);
|
|
|
+ }
|
|
|
},
|
|
|
|
|
|
async review(force){
|
|
|
@@ -312,6 +327,7 @@ Page({
|
|
|
|
|
|
// 检查是否有待记录的扫码信息
|
|
|
await this.checkAndRecordPendingScan();
|
|
|
+ await this.checkTrafficDeductPending();
|
|
|
|
|
|
// 如果用户没有来源信息,且当前有扫码参数,记录来源
|
|
|
await this.checkAndRecordUserSourceOnLogin();
|
|
|
@@ -328,12 +344,14 @@ Page({
|
|
|
|
|
|
// 用户已登录,检查是否有待记录的扫码信息
|
|
|
await this.checkAndRecordPendingScan();
|
|
|
+ await this.checkTrafficDeductPending();
|
|
|
|
|
|
// 如果用户没有来源信息,且当前有扫码参数,记录来源
|
|
|
await this.checkAndRecordUserSourceOnLogin();
|
|
|
}
|
|
|
getApp().Parse = Parse
|
|
|
getApp().checkAuth = checkAuth
|
|
|
+ getApp().checkTrafficDeductPending = this.checkTrafficDeductPending
|
|
|
if (!await this.getCompanyServerExpire(url)) {
|
|
|
return
|
|
|
}
|
|
|
@@ -530,6 +548,98 @@ Page({
|
|
|
}
|
|
|
},
|
|
|
|
|
|
+ /**
|
|
|
+ * 检查并处理暂缓扣减任务(在获取到手机号后复判白名单并决定是否扣减)
|
|
|
+ */
|
|
|
+ async checkTrafficDeductPending() {
|
|
|
+ try {
|
|
|
+ const pending = wx.getStorageSync('traffic_deduct_pending');
|
|
|
+ if (!pending) return;
|
|
|
+ const currentUser = Parse.User.current();
|
|
|
+ if (!currentUser || !currentUser.id) return;
|
|
|
+ const beforeNumbers = collectUserMobiles(currentUser);
|
|
|
+ console.log('🚦 [traffic] 检测到暂缓扣减任务:', {
|
|
|
+ storeId: pending && pending.storeId,
|
|
|
+ userId: currentUser.id,
|
|
|
+ userNumbers: beforeNumbers
|
|
|
+ });
|
|
|
+ let userNumbers = beforeNumbers;
|
|
|
+ try {
|
|
|
+ const sessionToken = typeof currentUser.getSessionToken === 'function' ? currentUser.getSessionToken() : null;
|
|
|
+ const q = new Parse.Query('_User');
|
|
|
+ q.select('mobile', 'phone');
|
|
|
+ const freshUser = await q.get(currentUser.id, sessionToken ? { sessionToken } : undefined);
|
|
|
+ const afterNumbers = collectUserMobiles(freshUser);
|
|
|
+ console.log('🚦 [traffic] 刷新用户后号码:', { before: beforeNumbers, after: afterNumbers });
|
|
|
+ if (Array.isArray(afterNumbers) && afterNumbers.length) {
|
|
|
+ userNumbers = afterNumbers;
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.warn('🚦 [traffic] 刷新用户手机号失败:', e?.message || e);
|
|
|
+ }
|
|
|
+ if (!userNumbers || !userNumbers.length) {
|
|
|
+ const storageMobile = wx.getStorageSync('user_mobile') || '';
|
|
|
+ console.log('🚦 [traffic] 仍未获取到手机号,继续暂缓,并安排 800ms 后重试', {
|
|
|
+ storageMobile
|
|
|
+ });
|
|
|
+ try {
|
|
|
+ const retryInfo = wx.getStorageSync('traffic_deduct_retry') || { count: 0 };
|
|
|
+ if ((retryInfo.count || 0) < 3) {
|
|
|
+ wx.setStorageSync('traffic_deduct_retry', { count: (retryInfo.count || 0) + 1 });
|
|
|
+ setTimeout(() => {
|
|
|
+ try {
|
|
|
+ if (typeof getApp().checkTrafficDeductPending === 'function') {
|
|
|
+ console.log('🚦 [traffic] 重试触发暂缓扣减复判');
|
|
|
+ getApp().checkTrafficDeductPending();
|
|
|
+ }
|
|
|
+ } catch (e) {}
|
|
|
+ }, 800);
|
|
|
+ }
|
|
|
+ } catch (e) {}
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const storeId = pending && pending.storeId ? pending.storeId : null;
|
|
|
+ if (!storeId) {
|
|
|
+ console.warn('🚦 [traffic] 暂缓扣减任务缺少 storeId,清除任务');
|
|
|
+ wx.removeStorageSync('traffic_deduct_pending');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const isExempt = await isTrafficExemptForStore(Parse, storeId, currentUser);
|
|
|
+ if (isExempt) {
|
|
|
+ console.log('🚦 [traffic] 暂缓扣减复判:命中白名单,取消扣减并清除暂缓任务');
|
|
|
+ try {
|
|
|
+ currentUser.set('trafficExempt', true);
|
|
|
+ currentUser.set('trafficExemptAt', new Date());
|
|
|
+ currentUser.set('trafficExemptStoreId', storeId);
|
|
|
+ await currentUser.save();
|
|
|
+ } catch (e) {
|
|
|
+ console.warn('🚦 [traffic] 写入 trafficExempt 失败(不影响继续访问):', e?.message || e);
|
|
|
+ }
|
|
|
+ wx.removeStorageSync('traffic_deduct_pending');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ console.log('🚦 [traffic] 暂缓扣减复判:未命中白名单,执行扣减');
|
|
|
+ try {
|
|
|
+ await decrementStoreTrafficImpl(Parse, storeId);
|
|
|
+ const deductedStores = Array.isArray(currentUser.get('trafficDeductedStores')) ? currentUser.get('trafficDeductedStores') : [];
|
|
|
+ const updatedStores = Array.isArray(deductedStores) ? deductedStores.slice() : [];
|
|
|
+ if (!updatedStores.includes(storeId)) updatedStores.push(storeId);
|
|
|
+ currentUser.set('trafficDeductedStores', updatedStores);
|
|
|
+ currentUser.set('trafficDeductedStoreId', storeId);
|
|
|
+ const atMap = currentUser.get('trafficDeductedAtMap') || {};
|
|
|
+ atMap[storeId] = new Date();
|
|
|
+ currentUser.set('trafficDeductedAtMap', atMap);
|
|
|
+ await currentUser.save();
|
|
|
+ console.log('🚦✅ [traffic] 暂缓扣减复判:已为店铺扣减 1 个流量,storeId:', storeId);
|
|
|
+ } catch (decErr) {
|
|
|
+ console.error('🚦❌ [traffic] 暂缓扣减复判:扣减失败:', decErr?.message || decErr);
|
|
|
+ }
|
|
|
+ wx.removeStorageSync('traffic_deduct_pending');
|
|
|
+ } catch (error) {
|
|
|
+ console.error('🚦❌ [traffic] 处理暂缓扣减任务失败:', error);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
async ensureTrackingUser() {
|
|
|
try {
|
|
|
let currentUser = Parse.User.current();
|
|
|
@@ -716,27 +826,57 @@ Page({
|
|
|
console.log('ℹ️ 用户已有来源信息,跳过覆盖:', existingSource);
|
|
|
}
|
|
|
|
|
|
- const trafficDeducted =
|
|
|
- currentUser.get('trafficDeducted') === true ||
|
|
|
- !!currentUser.get('trafficDeductedAt') ||
|
|
|
- !!currentUser.get('trafficDeductedStoreId');
|
|
|
+ const deductedStores = Array.isArray(currentUser.get('trafficDeductedStores')) ? currentUser.get('trafficDeductedStores') : [];
|
|
|
+ const hasDeductedForThisStore =
|
|
|
+ (Array.isArray(deductedStores) && deductedStores.includes(storeId)) ||
|
|
|
+ (currentUser.get('trafficDeductedStoreId') && currentUser.get('trafficDeductedStoreId') === storeId);
|
|
|
|
|
|
- if (storeId && !trafficDeducted) {
|
|
|
- console.log('🧮 [扣减流量] 检测到新用户首次扣减,准备扣减门店流量');
|
|
|
- try {
|
|
|
- await decrementStoreTrafficImpl(Parse, storeId);
|
|
|
- currentUser.set('trafficDeducted', true);
|
|
|
- currentUser.set('trafficDeductedStoreId', storeId);
|
|
|
- currentUser.set('trafficDeductedAt', new Date());
|
|
|
- await currentUser.save();
|
|
|
- console.log('✅ [扣减成功] 已为店铺扣减 1 个流量,storeId:', storeId);
|
|
|
- } catch (decErr) {
|
|
|
- console.error('❌ [扣减失败] 扣减门店流量失败:', decErr?.message || decErr);
|
|
|
+ if (storeId && !hasDeductedForThisStore) {
|
|
|
+ const internal = await isInternalVisit(Parse, storeId, Parse.User.current(), { ownerId, employeeId, partnerId });
|
|
|
+ if (internal) {
|
|
|
+ console.log('ℹ️ [扣减跳过] 内部角色访问(老板/异业/员工),不扣减流量');
|
|
|
+ } else {
|
|
|
+ const nums = collectUserMobiles(currentUser);
|
|
|
+ if (!nums || !nums.length) {
|
|
|
+ console.log('ℹ️ [扣减暂停] 用户未提供手机号,暂缓扣减', { storeId, userId: currentUser.id });
|
|
|
+ try {
|
|
|
+ wx.setStorageSync('traffic_deduct_pending', { storeId, userId: currentUser.id, time: Date.now() });
|
|
|
+ } catch (e) {}
|
|
|
+ } else {
|
|
|
+ const isExempt = await isTrafficExemptForStore(Parse, storeId, currentUser);
|
|
|
+ if (isExempt) {
|
|
|
+ console.log('ℹ️ [扣减跳过] 命中白名单手机号,跳过扣减流量');
|
|
|
+ try {
|
|
|
+ currentUser.set('trafficExempt', true);
|
|
|
+ currentUser.set('trafficExemptAt', new Date());
|
|
|
+ currentUser.set('trafficExemptStoreId', storeId);
|
|
|
+ await currentUser.save();
|
|
|
+ } catch (e) {
|
|
|
+ console.warn('⚠️ [扣减跳过] 写入 trafficExempt 失败(不影响继续访问):', e?.message || e);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ console.log('🧮 [扣减流量] 检测到新用户首次扣减,准备扣减门店流量');
|
|
|
+ try {
|
|
|
+ await decrementStoreTrafficImpl(Parse, storeId);
|
|
|
+ const updatedStores = Array.isArray(deductedStores) ? deductedStores.slice() : [];
|
|
|
+ if (!updatedStores.includes(storeId)) updatedStores.push(storeId);
|
|
|
+ currentUser.set('trafficDeductedStores', updatedStores);
|
|
|
+ currentUser.set('trafficDeductedStoreId', storeId);
|
|
|
+ const atMap = currentUser.get('trafficDeductedAtMap') || {};
|
|
|
+ atMap[storeId] = new Date();
|
|
|
+ currentUser.set('trafficDeductedAtMap', atMap);
|
|
|
+ await currentUser.save();
|
|
|
+ console.log('✅ [扣减成功] 已为店铺扣减 1 个流量,storeId:', storeId);
|
|
|
+ } catch (decErr) {
|
|
|
+ console.error('❌ [扣减失败] 扣减门店流量失败:', decErr?.message || decErr);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
} else if (!storeId) {
|
|
|
console.warn('⚠️ [扣减跳过] storeId 为空,无法扣减 ShopStore.traffic');
|
|
|
- } else if (trafficDeducted) {
|
|
|
- console.log('ℹ️ [扣减跳过] 已扣减过流量,不重复扣减');
|
|
|
+ } else if (hasDeductedForThisStore) {
|
|
|
+ console.log('ℹ️ [扣减跳过] 该用户已在该门店扣减过流量,不重复扣减');
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
@@ -1729,6 +1869,271 @@ Page({
|
|
|
}
|
|
|
});
|
|
|
|
|
|
+function normalizeCnMobile(value) {
|
|
|
+ if (value === null || value === undefined) return '';
|
|
|
+ const digits = String(value).replace(/\D/g, '');
|
|
|
+ if (!digits) return '';
|
|
|
+ const last11 = digits.length >= 11 ? digits.slice(-11) : digits;
|
|
|
+ if (/^1\d{10}$/.test(last11)) return last11;
|
|
|
+ if (/^1\d{10}$/.test(digits)) return digits;
|
|
|
+ return '';
|
|
|
+}
|
|
|
+
|
|
|
+function addCnMobileToSet(set, value) {
|
|
|
+ const m = normalizeCnMobile(value);
|
|
|
+ if (m) set.add(m);
|
|
|
+}
|
|
|
+
|
|
|
+function addCnMobilesFromValue(set, value) {
|
|
|
+ if (!value) return;
|
|
|
+ if (Array.isArray(value)) {
|
|
|
+ value.forEach((v) => addCnMobileToSet(set, v));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ addCnMobileToSet(set, value);
|
|
|
+}
|
|
|
+
|
|
|
+function addCnMobilesFromParseObject(set, parseObj, keys) {
|
|
|
+ if (!parseObj || typeof parseObj.get !== 'function') return;
|
|
|
+ keys.forEach((k) => {
|
|
|
+ try {
|
|
|
+ const v = parseObj.get(k);
|
|
|
+ addCnMobilesFromValue(set, v);
|
|
|
+ } catch (e) {}
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+async function addCnMobileFromUserPointer(set, Parse, pointer) {
|
|
|
+ if (!pointer) return;
|
|
|
+
|
|
|
+ if (typeof pointer.get === 'function') {
|
|
|
+ addCnMobileToSet(set, pointer.get('mobile'));
|
|
|
+ addCnMobileToSet(set, pointer.get('phone'));
|
|
|
+ }
|
|
|
+
|
|
|
+ const pointerId = pointer && pointer.id ? pointer.id : null;
|
|
|
+ if (!pointerId) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const q = new Parse.Query('_User');
|
|
|
+ q.select('mobile');
|
|
|
+ const u = await q.get(pointerId);
|
|
|
+ if (u) addCnMobileToSet(set, u.get('mobile'));
|
|
|
+ } catch (e) {}
|
|
|
+}
|
|
|
+
|
|
|
+function collectUserMobiles(currentUser) {
|
|
|
+ const set = new Set();
|
|
|
+ addCnMobileToSet(set, currentUser && typeof currentUser.get === 'function' ? currentUser.get('mobile') : null);
|
|
|
+ addCnMobileToSet(set, currentUser && typeof currentUser.get === 'function' ? currentUser.get('phone') : null);
|
|
|
+ try {
|
|
|
+ const storageMobile = wx.getStorageSync('user_mobile');
|
|
|
+ addCnMobileToSet(set, storageMobile);
|
|
|
+ } catch (e) {}
|
|
|
+ return Array.from(set);
|
|
|
+}
|
|
|
+
|
|
|
+function setSample(set, limit) {
|
|
|
+ const arr = [];
|
|
|
+ let i = 0;
|
|
|
+ for (const v of set) {
|
|
|
+ arr.push(v);
|
|
|
+ i++;
|
|
|
+ if (i >= (limit || 5)) break;
|
|
|
+ }
|
|
|
+ return arr;
|
|
|
+}
|
|
|
+
|
|
|
+function intersectArrSet(arr, set) {
|
|
|
+ return arr.filter((v) => set.has(v));
|
|
|
+}
|
|
|
+
|
|
|
+async function collectPartnerPhonesForStore(Parse, storeId) {
|
|
|
+ const phones = new Set();
|
|
|
+ const mobiles = new Set();
|
|
|
+ try {
|
|
|
+ if (!storeId) return { phones, mobiles };
|
|
|
+ const currentUser = Parse.User.current();
|
|
|
+ const sessionToken = currentUser && typeof currentUser.getSessionToken === 'function'
|
|
|
+ ? currentUser.getSessionToken()
|
|
|
+ : null;
|
|
|
+ const requestOptions = sessionToken ? { sessionToken } : undefined;
|
|
|
+ const storePointer = new Parse.Object('ShopStore'); storePointer.id = storeId;
|
|
|
+ const q = new Parse.Query('Partner');
|
|
|
+ q.equalTo('store', storePointer);
|
|
|
+ q.limit(200);
|
|
|
+ q.select('phone', 'mobile');
|
|
|
+ const list = await q.find(requestOptions);
|
|
|
+ if (list && list.length) {
|
|
|
+ list.forEach((p) => {
|
|
|
+ addCnMobileToSet(phones, p.get('phone'));
|
|
|
+ addCnMobileToSet(mobiles, p.get('mobile'));
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.warn('⚠️ [traffic] Partner 查询失败:', e?.code, e?.message || e);
|
|
|
+ }
|
|
|
+ return { phones, mobiles };
|
|
|
+}
|
|
|
+
|
|
|
+async function collectTrafficWhitelistMobiles(Parse, storeId) {
|
|
|
+ const set = new Set();
|
|
|
+
|
|
|
+ const mobileKeys = [
|
|
|
+ 'mobile',
|
|
|
+ 'phone'
|
|
|
+ ];
|
|
|
+
|
|
|
+ const userPointerKeys = [
|
|
|
+ 'owner',
|
|
|
+ 'boss',
|
|
|
+ 'admin',
|
|
|
+ 'manager',
|
|
|
+ 'user',
|
|
|
+ 'ownerUser',
|
|
|
+ 'bossUser',
|
|
|
+ 'adminUser',
|
|
|
+ 'managerUser',
|
|
|
+ 'createdBy'
|
|
|
+ ];
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ try {
|
|
|
+ if (storeId) {
|
|
|
+ const q = new Parse.Query('ShopStore');
|
|
|
+ q.equalTo('objectId', storeId);
|
|
|
+ q.select(...mobileKeys, ...userPointerKeys);
|
|
|
+ userPointerKeys.forEach((k) => q.include(k));
|
|
|
+ const storeObj = await q.first();
|
|
|
+ if (storeObj) {
|
|
|
+ addCnMobilesFromParseObject(set, storeObj, mobileKeys);
|
|
|
+ for (const k of userPointerKeys) {
|
|
|
+ await addCnMobileFromUserPointer(set, Parse, storeObj.get(k));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (e) {}
|
|
|
+
|
|
|
+ const userStaffPhoneKeys = ['mobile', 'phone'];
|
|
|
+ try {
|
|
|
+ if (storeId) {
|
|
|
+ const flags = getApp().globalData || {};
|
|
|
+ const disabled = !!flags.disableUserStaffQuery || wx.getStorageSync('disableUserStaffQuery') === true;
|
|
|
+ if (disabled) {
|
|
|
+ console.log('⚠️ [traffic] userStaff 查询已禁用(检测到类不存在/无权限)');
|
|
|
+ } else {
|
|
|
+ const currentUser = Parse.User.current();
|
|
|
+ const sessionToken = currentUser && typeof currentUser.getSessionToken === 'function'
|
|
|
+ ? currentUser.getSessionToken()
|
|
|
+ : null;
|
|
|
+ const requestOptions = sessionToken ? { sessionToken } : undefined;
|
|
|
+ const companyId = getApp().globalData.company;
|
|
|
+ const companyPointer = new Parse.Object('Company'); companyPointer.id = companyId;
|
|
|
+ const q = new Parse.Query('UserStaff');
|
|
|
+ q.equalTo('company', companyPointer);
|
|
|
+ q.limit(200);
|
|
|
+ q.select(...userStaffPhoneKeys);
|
|
|
+ const list = await q.find(requestOptions);
|
|
|
+ if (list && list.length) {
|
|
|
+ list.forEach((staff) => addCnMobilesFromParseObject(set, staff, userStaffPhoneKeys));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.warn('⚠️ [traffic] userStaff 查询失败:', e?.code, e?.message || e);
|
|
|
+ try {
|
|
|
+ const msg = (e?.message || '').toLowerCase();
|
|
|
+ if (
|
|
|
+ e?.code === 119 ||
|
|
|
+ msg.includes('non-existent class') ||
|
|
|
+ msg.includes('class: userstaff') ||
|
|
|
+ msg.includes('class: userstaff') ||
|
|
|
+ msg.includes('class: userstaff')
|
|
|
+ ) {
|
|
|
+ getApp().globalData.disableUserStaffQuery = true;
|
|
|
+ wx.setStorageSync('disableUserStaffQuery', true);
|
|
|
+ console.warn('⚠️ [traffic] 已禁用后续 userStaff 查询(类不存在或无权限)');
|
|
|
+ }
|
|
|
+ } catch (_) {}
|
|
|
+ }
|
|
|
+
|
|
|
+ const partnerPhoneKeys = ['mobile', 'phone'];
|
|
|
+ try {
|
|
|
+ if (storeId) {
|
|
|
+ const currentUser = Parse.User.current();
|
|
|
+ const sessionToken = currentUser && typeof currentUser.getSessionToken === 'function'
|
|
|
+ ? currentUser.getSessionToken()
|
|
|
+ : null;
|
|
|
+ const requestOptions = sessionToken ? { sessionToken } : undefined;
|
|
|
+ const storePointer = new Parse.Object('ShopStore'); storePointer.id = storeId;
|
|
|
+ const q = new Parse.Query('Partner');
|
|
|
+ q.equalTo('store', storePointer);
|
|
|
+ q.limit(200);
|
|
|
+ q.select(...partnerPhoneKeys);
|
|
|
+ const list = await q.find(requestOptions);
|
|
|
+ if (list && list.length) {
|
|
|
+ list.forEach((p) => addCnMobilesFromParseObject(set, p, partnerPhoneKeys));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.warn('⚠️ [traffic] Partner 查询失败:', e?.code, e?.message || e);
|
|
|
+ }
|
|
|
+
|
|
|
+ return set;
|
|
|
+}
|
|
|
+
|
|
|
+async function isTrafficExemptForStore(Parse, storeId, currentUser) {
|
|
|
+ try {
|
|
|
+ if (!currentUser) return false;
|
|
|
+ if (currentUser.get('trafficExempt') === true) return true;
|
|
|
+ const userNumbers = collectUserMobiles(currentUser);
|
|
|
+ const whitelist = await collectTrafficWhitelistMobiles(Parse, storeId);
|
|
|
+ const partnerSets = await collectPartnerPhonesForStore(Parse, storeId);
|
|
|
+ console.log('🧾 [traffic] 白名单判定开始:', {
|
|
|
+ storeId,
|
|
|
+ userId: currentUser.id,
|
|
|
+ userNumbers
|
|
|
+ });
|
|
|
+ console.log('🧾 [traffic] Partner 集合:', {
|
|
|
+ phoneCount: partnerSets.phones.size,
|
|
|
+ phoneSample: setSample(partnerSets.phones, 5),
|
|
|
+ mobileCount: partnerSets.mobiles.size,
|
|
|
+ mobileSample: setSample(partnerSets.mobiles, 5)
|
|
|
+ });
|
|
|
+ const partnerPhoneMatches = intersectArrSet(userNumbers, partnerSets.phones);
|
|
|
+ if (partnerPhoneMatches.length) {
|
|
|
+ console.log('🧾 [traffic] 命中 Partner.phone:', {
|
|
|
+ matches: partnerPhoneMatches
|
|
|
+ });
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ const partnerMobileMatches = intersectArrSet(userNumbers, partnerSets.mobiles);
|
|
|
+ if (partnerMobileMatches.length) {
|
|
|
+ console.log('🧾 [traffic] 命中 Partner.mobile:', {
|
|
|
+ matches: partnerMobileMatches
|
|
|
+ });
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ console.log('🧾 [traffic] 聚合白名单:', {
|
|
|
+ size: whitelist.size,
|
|
|
+ sample: setSample(whitelist, 10)
|
|
|
+ });
|
|
|
+ const whitelistMatches = intersectArrSet(userNumbers, whitelist);
|
|
|
+ if (whitelistMatches.length) {
|
|
|
+ console.log('🧾 [traffic] 命中聚合白名单:', {
|
|
|
+ matches: whitelistMatches
|
|
|
+ });
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ console.log('🧾 [traffic] 白名单未命中');
|
|
|
+ return false;
|
|
|
+ } catch (e) {
|
|
|
+ console.warn('⚠️ [traffic] 白名单判定失败,按非白名单处理:', e?.message || e);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
async function decrementStoreTrafficImpl(Parse, storeId) {
|
|
|
const currentUser = Parse.User.current();
|
|
|
const sessionToken = currentUser && typeof currentUser.getSessionToken === 'function'
|
|
|
@@ -1761,3 +2166,29 @@ async function decrementStoreTrafficImpl(Parse, storeId) {
|
|
|
}
|
|
|
console.log('🧾 [traffic] 结束扣减 ShopStore.traffic');
|
|
|
}
|
|
|
+
|
|
|
+async function isInternalVisit(Parse, storeId, currentUser, ids) {
|
|
|
+ try {
|
|
|
+ if (!currentUser) return false;
|
|
|
+ const { ownerId, employeeId, partnerId } = ids || {};
|
|
|
+ const roles = Array.isArray(currentUser.get('roles')) ? currentUser.get('roles') : [];
|
|
|
+ const roleSet = new Set(roles.map(r => String(r).toLowerCase()));
|
|
|
+ const isOwnerRole = roleSet.has('owner') || roleSet.has('boss');
|
|
|
+ const isEmployeeRole = roleSet.has('employee') || roleSet.has('staff') || roleSet.has('sales');
|
|
|
+ const isAdminStaffRole = roleSet.has('userstaff') || roleSet.has('admin_staff') || roleSet.has('manager_staff');
|
|
|
+ const isPartnerRole = roleSet.has('partner') || roleSet.has('partner_admin') || roleSet.has('partner_staff');
|
|
|
+ if (partnerId && isPartnerRole) return true;
|
|
|
+ if (employeeId && isEmployeeRole) return true;
|
|
|
+ if (isAdminStaffRole) return true;
|
|
|
+ const storeQuery = new Parse.Query('ShopStore');
|
|
|
+ const store = await storeQuery.get(storeId);
|
|
|
+ const storeOwner = store ? store.get('user') : null;
|
|
|
+ if (storeOwner && storeOwner.id === currentUser.id) return true;
|
|
|
+ if (ownerId && isOwnerRole) return true;
|
|
|
+ if (employeeId && currentUser.id === employeeId) return true;
|
|
|
+ if (ownerId && currentUser.id === ownerId) return true;
|
|
|
+ return false;
|
|
|
+ } catch (e) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+}
|