import cloud from '@lafjs/cloud' import { createHash } from 'crypto'; import * as JungleConfigModule from "@/jungleConfig"; import { WUCAI_TYPE_AUTO_MIGRATED, WUCAI_TYPE_NONE, WUCAI_TYPE_PENDING_CHOICE, buildWucaiPreview, fetchWucaiUser, isPristineWucaiTarget, isValidWucaiType, normalizeWucaiUser, } from "./wucaiMigration"; const JungleConfig: any = JungleConfigModule as any; const db = cloud.database(); import crypto from 'crypto'; const mchid = process.env.WX_MCH_ID;//商户id const serialNo = process.env.WX_MCH_CERT_SERIAL_NO;//证书序列号 const ThinkingData = require('thinkingdata-node'); import Ip2region from 'ip2region'; const query = new Ip2region(); const canIos = false; const version = "9.9.99"; const AB_CONFIG_COLLECTION = "ab_config"; const MANAGED_AB_LAYERS = ["1", "2", "3"]; const DEFAULT_AB_GROUP = "Default"; export default async function (ctx: FunctionContext) { const body = ctx.body || {}; let code = body.code; let distinctId = body.distinctId; let isDebug = body.isDebug; const from = body.from; const supportsWucaiMigration = Number(body.wucai_capability) >= 1; let dbname = "users"; // const gameName = body.gameName; // if (gameName == "iaa") { // dbname = "usersAd"; // } if (from === "wucai" && dbname !== "users") { return wucaiLoginFail("WUCAI_UNSUPPORTED_GAME", "五彩账号迁移仅支持主用户库"); } if (from === "wucai" && !supportsWucaiMigration) { return wucaiLoginFail("WUCAI_CLIENT_UPGRADE_REQUIRED", "当前客户端不支持五彩账号迁移,请更新游戏"); } let idname = process.env.IDCOUNT_NAME; // if (gameName == "iaa") { // idname = "69fabaacb4d89b170da4bf57"; // } let info = await getWXOpenId(code, dbname); if (!info) { return; } let openid = info[0]; let session_key = info[1]; let unionid = info[2]; let userData = await getUserData(dbname, openid); const isNewUser = !userData; if (userData?.freeze) { return { code: 0, data: "账号封禁", msg: "账号封禁" }; } let token = await stringToHash(Date.now() + openid); let abTests: any = null; let shouldPersistABTests = false; let wucaiPreview = null; if (!userData) { if (dbname === "users") { abTests = await getActiveABAssignments(openid, null, true); } let migratedUser = null; if (from === "wucai") { const source = await fetchWucaiUser(unionid); if (!source) { return wucaiLoginFail("WUCAI_SOURCE_UNAVAILABLE", "五彩数据获取失败,请重新进入游戏后重试"); } migratedUser = normalizeWucaiUser(source); } let ref = await db.collection("idcount").field({ count: 1 }).where({ _id: idname }).getOne(); await db.collection("idcount").where({ _id: idname }).update({ count: ref.data.count + 1 }); await db.collection(dbname).add({ openid: openid, unionid: unionid, session_key: session_key, register_time: Date.now(), onlyId: ref.data.count, distinctId: distinctId, isDebug: isDebug, levelAmount: migratedUser?.levelAmount ?? 0, coinAmount: migratedUser?.coinAmount ?? 0, healthAmount: migratedUser?.healthAmount ?? 5, healthtimestamp: migratedUser?.healthtimestamp ?? 0, freezeAmount: migratedUser?.freezeAmount ?? 3, hammerAmount: migratedUser?.hammerAmount ?? 3, magicAmount: migratedUser?.magicAmount ?? 3, userPowerTime: migratedUser?.userPowerTime ?? 0, monthCardReward: migratedUser?.monthCardReward ?? 0, monthCardTime: migratedUser?.monthCardTime ?? 0, rebornGiftCount: 0, rebornGiftTime: 0, useravatar: Math.floor(Math.random() * 4) + "", username: "user", pay_user: migratedUser?.pay_user ?? false, isWhite: false, isFirst: true, starter_pack: migratedUser?.starter_pack ?? 0, starter_packState: migratedUser?.starter_packState ?? 0, winStreak: migratedUser?.winStreak ?? 0, passCheck: migratedUser?.passCheck ?? null, endLevelNum: migratedUser?.endLevelNum ?? 0, totalLevel: migratedUser?.totalLevel ?? 0, userLevel: migratedUser?.userLevel ?? 0, film: migratedUser?.film ?? 0, careerAmount: 0, useravatarIcon: "0", userCat: (Math.floor(Math.random() * 3) + 1) + "", forcedUpdate: !!migratedUser, ...(dbname === "users" ? { wucai_type: migratedUser ? WUCAI_TYPE_AUTO_MIGRATED : WUCAI_TYPE_NONE, } : {}), headArr: migratedUser?.headArr ?? [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], catArr: migratedUser?.catArr ?? [1, 2, 3], token: token, ...(dbname === "users" ? { abTests: abTests } : {}) }); } else { const migrationUpdate: any = {}; if (dbname === "users") { const hasStoredWucaiType = Object.prototype.hasOwnProperty.call(userData, "wucai_type"); if (hasStoredWucaiType && !isValidWucaiType(userData.wucai_type)) { return wucaiLoginFail("WUCAI_INVALID_STATE", "五彩迁移状态异常,请联系客服"); } let wucaiType = hasStoredWucaiType ? Number(userData.wucai_type) : WUCAI_TYPE_NONE; if (!hasStoredWucaiType) { migrationUpdate.wucai_type = WUCAI_TYPE_NONE; } if ((wucaiType === WUCAI_TYPE_AUTO_MIGRATED || wucaiType === WUCAI_TYPE_PENDING_CHOICE) && !supportsWucaiMigration) { return wucaiLoginFail("WUCAI_CLIENT_UPGRADE_REQUIRED", "当前账号需要更新游戏后继续五彩账号迁移"); } let source = null; if (wucaiType === WUCAI_TYPE_PENDING_CHOICE || (wucaiType === WUCAI_TYPE_NONE && from === "wucai")) { source = await fetchWucaiUser(unionid || userData.unionid); if (!source) { return wucaiLoginFail("WUCAI_SOURCE_UNAVAILABLE", "五彩数据获取失败,请重新进入游戏后重试"); } } if (wucaiType === WUCAI_TYPE_NONE && from === "wucai") { const hasOrder = await hasUserOrder(userData.openid); if (isPristineWucaiTarget(userData, hasOrder)) { Object.assign(migrationUpdate, normalizeWucaiUser(source), { wucai_type: WUCAI_TYPE_AUTO_MIGRATED, forcedUpdate: true, }); wucaiType = WUCAI_TYPE_AUTO_MIGRATED; } else { migrationUpdate.wucai_type = WUCAI_TYPE_PENDING_CHOICE; wucaiType = WUCAI_TYPE_PENDING_CHOICE; wucaiPreview = buildWucaiPreview(source); } } else if (wucaiType === WUCAI_TYPE_PENDING_CHOICE) { wucaiPreview = buildWucaiPreview(source); } } if (dbname === "users") { abTests = await getActiveABAssignments(openid, userData.abTests, false); shouldPersistABTests = JSON.stringify(userData.abTests) !== JSON.stringify(abTests); } let taskTime = userData.taskTime || 0; let task = userData.task; if (task) { task = JSON.parse(task); } else { task = { levelPass: { value: 0, state: 0, }, share: { value: 0, state: 0, }, useEnergy: { value: 0, state: 0, }, useProp: { value: 0, state: 0, } } } const now = new Date(Date.now() + 8 * 3600000); now.setHours(0, 0, 0, 0); // 直接修改当前Date对象 const timestamp = now.getTime(); if (timestamp > taskTime) { for (var key in task) { task[key].value = 0; task[key].state = 0; } } const loginUpdate: any = { session_key: session_key, ...(unionid ? { unionid } : {}), distinctId: distinctId, isDebug: isDebug, isFirst: false, task: JSON.stringify(task), taskTime: Date.now() + 8 * 3600000, token: token, ...migrationUpdate, }; if (!Array.isArray(userData.catArr)) { loginUpdate.catArr = [1, 2, 3]; } if (userData.userCat == null || userData.userCat === "") { loginUpdate.userCat = (Math.floor(Math.random() * 3) + 1) + ""; } await db.collection(dbname) .where({ _id: userData._id }) .update(loginUpdate); if (shouldPersistABTests) { try { await db.collection(dbname) .where({ _id: userData._id }) .update({ abTests: abTests }); } catch (e) { console.log("AB测试分组写回失败", e); } } } await delayExecution(0.1); userData = await getUserData(dbname, openid); let outTradeNo: any = []; if (!isNewUser) { let res = null; if (userData.openid) { res = await db.collection("order").where({ state: 1, openid: userData.openid }).get(); } if (res.data && res.data.length > 0) { for (let i = 0; i < res.data.length; i++) { outTradeNo.push(res.data[i]); } } if (dbname == "users") { let ret = await getIosOrder(userData.openid, ctx); outTradeNo = outTradeNo.concat(ret); } } if (dbname == "users") { userData.outTradeNo = outTradeNo; } if (!userData.onlyId) { let ref = await db.collection("idcount").field({ count: 1 }).where({ _id: idname }).getOne(); await db.collection("idcount").where({ _id: idname }).update({ count: ref.data.count + 1 }); userData.onlyId = ref.data.count; await db.collection(dbname).where({ _id: userData._id }).update({ session_key: session_key, onlyId: ref.data.count }); } let share = userData.shareLv; if (share) { userData.shareLv = JSON.parse(share); } else { userData.shareLv = []; } userData.careerAmount = userData.careerAmount || 0; if (dbname === "users") { userData.abTests = abTests; } userData.rebornGiftCount = 0; if (userData.isWhite && !userData.film && userData.film != 0) { userData.film = userData.levelAmount; } userData.canIos = canIos; userData.version = version; if (dbname !== "users") { delete userData.jungleTreasureState; delete userData.jungleTreasureServerNow; delete userData.pendingJungleSettlement; } else if (Object.prototype.hasOwnProperty.call(userData, "jungleTreasureState")) { // 仅随登录返回服务器时间,不在登录阶段创建或修改活动状态。 userData.jungleTreasureServerNow = Date.now(); } if (wucaiPreview) { userData.wucai_preview = wucaiPreview; } delete userData.isWuCai; return { code: 1, data: userData } } function stringToHash(input: string, algorithm: 'sha1' | 'sha256' | 'sha512' = 'sha256'): string { return createHash(algorithm).update(input).digest('hex'); } async function hasUserOrder(openid: string): Promise { if (!openid) { return false; } const result = await db.collection("order").where({ openid }).get(); return Array.isArray(result.data) && result.data.length > 0; } function wucaiLoginFail(errorCode: string, msg: string) { return { code: 0, errorCode, data: null, msg }; } function getTodayUTCMidnightTimestamp(): number { const now = new Date(); // 1. 获取当前 UTC 时间 const utcMidnight = Date.UTC( now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), 0, 0, 0, 0 ); // 2. 加上 8 小时(北京时间 = UTC+8) return utcMidnight + 8 * 60 * 60 * 1000; } const getClientIp = (req) => { return ( req.headers['x-forwarded-for']?.split(',')[0]?.trim() || // 代理链中的第一个 IP req.headers['x-real-ip'] || // Nginx 配置的 real-ip req.socket.remoteAddress // 原始 IP(可能是代理) ); }; const propName = { "金币包1": "gold_1", "金币包2": "gold_2", "金币包3": "gold_3", "金币包4": "gold_4", "金币包5": "gold_5", "金币包6": "gold_6", "无限体力组合包1": "unlimited_health_bundle_1", "无限体力组合包2": "unlimited_health_bundle_2", "无限体力组合包3": "unlimited_health_bundle_3", "局内购买冻结": "freeze_in_game", "局内购买锤子": "hammer_in_game", "局内购买魔棒": "wand_in_gamerefill", "补满体力": "health", "宝藏1": "jungle_treasure_1", "宝藏2": "jungle_treasure_2", "宝藏3": "jungle_treasure_3", "宝藏4": "jungle_treasure_4", "宝藏5": "jungle_treasure_5", "宝藏6": "jungle_treasure_6", "宝藏7": "jungle_treasure_7", "宝藏8": "jungle_treasure_8" }; async function getWXOpenId(code, dbname) { let appid = "wx30f74367b3d3e7a6"; let appsecret = process.env.WX_MINIGAME_APP_SECRET; if (dbname == "usersAd") { appid = ""; appsecret = ""; } const api_url = `https://api.weixin.qq.com/sns/jscode2session`; const param = `appid=${appid}&secret=${appsecret}&js_code=${code}&grant_type=authorization_code`; let res = null; try { res = await cloud.fetch(`${api_url}?${param}`, { headers: { "Accept-Encoding": 'gzip' } }); } catch { } if (res == null || res.data.errcode > 0) { return null } //console.log(res.data); return [res?.data?.openid, res?.data?.session_key, res?.data?.unionid] } /**获取当前用户数据 */ async function getUserData(dbname: string, openid: any, isFirst?) { let { data } = await db.collection(dbname).where({ openid: openid }).get(); if (data.length) { return data[0]; } return null; } async function delayExecution(seconds: number) { return new Promise((resolve) => { setTimeout(() => { // 在这里编写需要延时执行的代码 resolve("延时执行完成"); }, seconds * 1000); }); } async function getIosOrder(openid, ctx) { let res = await db.collection("order").where({ state: 0, type: "ios", openid: openid }).get(); let res1 = await db.collection("users").where({ openid: openid }).getOne(); let list = []; if (res.data.length > 0) { for (let i = 0; i < res.data.length; i++) { let ret = await getOrder(res.data[i].outTradeNo); if (ret.trade_state == "SUCCESS") { list.push(res.data[i]); sead(res.data[i], res1.data, getClientIp(ctx)); const jungleTier = JungleConfig.getJunglePaidTierByProductId(res.data[i].itemid); if (jungleTier) { const unlockResult = JungleConfig.unlockJungleTierByProductId( res1.data.jungleTreasureState, res.data[i].itemid, res.data[i].outTradeNo, ); const userUpdate: any = { pay_user: true }; if (unlockResult.ok && unlockResult.changed && unlockResult.state) { userUpdate.jungleTreasureState = JungleConfig.serializeJungleState(unlockResult.state); res1.data.jungleTreasureState = unlockResult.state; } await db.collection("users").where({ _id: res1.data._id }).update(userUpdate); } else switch (res.data[i].itemid) { case "month_Card": // 月卡有效期由 monthlyCard 使用订单号统一结算,避免补单与客户端重复加时。 await db.collection("users").where({ _id: res1.data._id }).update({ pay_user: true }); break; case "reborn_Gift": db.collection("users").where({ _id: res1.data._id }).update({ rebornGiftTime: Date.now(), rebornGiftCount: 0, pay_user: true }); break; case "starter_pack": db.collection("users").where({ _id: res1.data._id }).update({ starter_packState: 1, pay_user: true }); break; default: db.collection("users").where({ _id: res1.data._id }).update({ pay_user: true }); break; } // 月卡补单确认成功后进入待客户端补发状态。 if (res.data[i].itemid === "month_Card" || jungleTier) { await db.collection("order").where({ outTradeNo: res.data[i].outTradeNo }).update({ state: 1 }); } } else { if (Date.now() - res.data[i].time > 3 * 3600000) { let ret = await db.collection('order').where({ outTradeNo: res.data[i].outTradeNo }).remove({ multi: true }); } } } } return list; } async function sead(order, userInfo, ip) { if (!userInfo.distinctId) { return; } let id = '95993f9ab6f1402a87abe5147827e5e0'; if (userInfo.isDebug != "true") { id = '40e3d5c5f2af49a4a074205564ce5dbb'; } let teSDK = ThinkingData.initWithBatchMode(id, 'https://data.nika4fun.com/sync_data', { dryRun: false, // report data to TE or not deviceId: "123456789",//设备唯一标识 }); let trackEvent = { accountId: order.openid, distinctId: userInfo.distinctId,//访客id event: 'payment', time: new Date(Date.now() + 8 * 3600000), ip: ip, properties: { order_id: order.outTradeNo, pay_amount: order.goodsPrice, payment_name: order.itemid, payment_num: order.itemCount, payment_type: "ios", is_on_table: Number(userInfo.addLevel) >= 5, current_level: userInfo.lv, //当前关卡等级 number current_health: userInfo.healthAmount, //当前体力值 tmp_coin: userInfo.coinAmount,//当前金币 version: "1.6", user_id: userInfo._id, //用户id pay_user: userInfo.pay_user, abTest_layer_one: getABTestGroupValue(userInfo.abTests, "layer_1"), abTest_layer_two: getABTestGroupValue(userInfo.abTests, "layer_2"), abTest_layer_three: getABTestGroupValue(userInfo.abTests, "layer_3") }, callback(e) { if (e) { console.log(e); } } }; teSDK.track(trackEvent); teSDK.close(); return; } function getABTestGroupValue(abTests: any, layerKey: string) { if (!abTests) { return 0; } if (typeof abTests === "string") { try { abTests = JSON.parse(abTests); } catch { return 0; } } const groupMap = { A: 1, B: 2, C: 3, D: 4, E: 5 }; const group = String(abTests[layerKey]?.group || "").toUpperCase(); return groupMap[group] || 0; } /**获取订单*/ async function getOrder(outTradeNo) { const url = `https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/${outTradeNo}?mchid=${mchid}`; const nonceStr = crypto.randomBytes(16).toString('hex'); const timestamp = Math.floor(Date.now() / 1000).toString(); let au = await sign(outTradeNo, nonceStr, timestamp); let res = await fetch(url, { method: 'GET', // 默认是 GET,可省略 headers: { 'Authorization': au, // 认证令牌 'Content-Type': 'application/json', // 声明请求体类型(GET 请求通常不需要) 'Accept': 'application/json', // 声明期望的响应类型 } }) const data = await res.json(); return data; } async function sign(outTradeNo, nonceStr, timestamp) { const data = `GET\n/v3/pay/transactions/out-trade-no/${outTradeNo}?mchid=${mchid}\n${timestamp}\n${nonceStr}\n\n` // 创建签名对象 const sign = crypto.createSign('RSA-SHA256'); // 更新签名内容 sign.update(data); // 获取存储桶 const bucket = cloud.storage.bucket('j3k323ol-colorblock-oss'); // 获取文件列表 const res = await bucket.readFile('apiclient_key.pem'); const privateKeyPem = await res.Body.transformToString(); // 使用私钥签名(PKCS1 填充) const signature = sign.sign({ key: privateKeyPem, padding: crypto.constants.RSA_PKCS1_PADDING }, 'base64'); // 构建Authorization头 const authorization = `WECHATPAY2-SHA256-RSA2048 mchid="${mchid}",nonce_str="${nonceStr}",signature="${signature}",timestamp="${timestamp}",serial_no="${serialNo}"`; return authorization; } async function getActiveABAssignments(openid: string, oldAbTests: any, isNewUser: boolean) { const now = Date.now(); const abTests = normalizeABTests(oldAbTests, now); let configs = []; try { const ret = await db.collection(AB_CONFIG_COLLECTION).get(); configs = pickConfigByLayer(ret.data || []); } catch (e) { return ensureManagedABLayers(abTests, now); } const configByLayer: any = {}; for (const config of configs) { configByLayer[getABLayerKey(config.layer)] = config; } const nextAbTests: any = { ...abTests }; for (const layer of MANAGED_AB_LAYERS) { const layerKey = getABLayerKey(layer); const experiment = configByLayer[layerKey]; const oldAssignment = abTests[layerKey]; if (!experiment) { nextAbTests[layerKey] = buildDefaultABAssignment(layer, null, oldAssignment, now, 0); continue; } const resetVersion = getABResetVersion(experiment.resetVersion); if (experiment.enabled === false) { nextAbTests[layerKey] = buildDefaultABAssignment( layer, experiment, oldAssignment, now, resetVersion ); continue; } const shouldReset = experiment.resetAssignments === true && resetVersion > 0 && getABResetVersion(oldAssignment?.appliedResetVersion) !== resetVersion; if (isNewUser || shouldReset) { const variant = pickABVariant(openid, experiment); nextAbTests[layerKey] = variant ? buildABAssignment(layer, experiment, variant.group, now, resetVersion) : buildDefaultABAssignment(layer, experiment, oldAssignment, now, resetVersion); continue; } if (oldAssignment) { nextAbTests[layerKey] = { ...oldAssignment, experimentId: getABText(experiment.experimentId), layer: layer, name: getABText(experiment.name), enabled: true, }; } else { nextAbTests[layerKey] = buildDefaultABAssignment(layer, experiment, null, now, 0); } } return nextAbTests; } function ensureManagedABLayers(abTests: any, now: number) { const completed: any = { ...abTests }; for (const layer of MANAGED_AB_LAYERS) { const layerKey = getABLayerKey(layer); if (!completed[layerKey]) { completed[layerKey] = buildDefaultABAssignment(layer, null, null, now, 0); } } return completed; } function buildDefaultABAssignment(layer: string, experiment: any, oldAssignment: any, now: number, resetVersion: number) { const wasDefault = normalizeABGroup(oldAssignment?.group) === DEFAULT_AB_GROUP; return { experimentId: experiment ? getABText(experiment.experimentId) : null, layer: layer, name: experiment ? getABText(experiment.name) : null, enabled: !!experiment && experiment.enabled !== false, group: DEFAULT_AB_GROUP, assignedAt: wasDefault ? oldAssignment.assignedAt : now, appliedResetVersion: resetVersion, }; } function buildABAssignment(layer: string, experiment: any, group: any, assignedAt: number, resetVersion: number) { return { experimentId: getABText(experiment.experimentId), layer: layer, name: getABText(experiment.name), enabled: true, group: normalizeABGroup(group), assignedAt: assignedAt, appliedResetVersion: resetVersion, }; } function normalizeABTests(abTests: any, now = Date.now()) { if (!abTests) { return {}; } if (typeof abTests == "string") { try { abTests = JSON.parse(abTests); } catch { return {}; } } if (typeof abTests !== "object" || Array.isArray(abTests)) { return {}; } const normalized: any = {}; for (const key in abTests) { const item = abTests[key]; const layer = String(item?.layer || "").trim(); const group = normalizeABGroup(item?.group); if (!layer || !group || key !== getABLayerKey(layer)) { continue; } const assignedAt = Number(item.assignedAt); normalized[key] = { experimentId: getABText(item.experimentId), layer: layer, name: getABText(item.name), enabled: item.enabled !== false, group: group, assignedAt: Number.isFinite(assignedAt) && assignedAt > 0 ? assignedAt : now, appliedResetVersion: getABResetVersion(item.appliedResetVersion), }; } return normalized; } function normalizeABGroup(group: any) { const normalized = String(group || "").trim(); return normalized.toLowerCase() === "default" ? DEFAULT_AB_GROUP : normalized; } function getABText(value: any) { if (value === null || value === undefined || value === "") { return null; } return String(value); } function getABResetVersion(value: any) { const version = Number(value); return Number.isFinite(version) && version > 0 ? version : 0; } function getABLayerKey(layer: any) { return `layer_${String(layer || "").trim()}`; } function pickConfigByLayer(configs: any[]) { const byLayer: any = {}; for (let i = 0; i < configs.length; i++) { const config = configs[i]; if (!isValidExperiment(config)) { continue; } const layerKey = getABLayerKey(config.layer); const old = byLayer[layerKey]; if (!old || Number(config.updatedAt || config.createdAt || 0) >= Number(old.updatedAt || old.createdAt || 0)) { byLayer[layerKey] = config; } } return Object.keys(byLayer).map(key => byLayer[key]); } function isValidExperiment(experiment: any) { if (!experiment?.experimentId || !experiment?.layer || !Array.isArray(experiment?.variants)) { return false; } return true; } function pickABVariant(openid: string, experiment: any) { const variants = experiment.variants || []; const totalRatio = variants.reduce((sum: number, variant: any) => sum + Number(variant.ratio || 0), 0); if (totalRatio <= 0) { return null; } const seed = `${openid}:${experiment.layer}:${experiment.experimentId}`; const hash = createHash("sha256").update(seed).digest("hex"); const bucket = parseInt(hash.slice(0, 8), 16) % 10000; const target = bucket / 10000 * totalRatio; let cursor = 0; for (let i = 0; i < variants.length; i++) { cursor += Number(variants[i].ratio || 0); if (target < cursor) { return variants[i]; } } return variants[variants.length - 1]; } /** * 根据 IP 获取省份名称 * @param {string} ip - 用户 IP 地址 * @returns {string} 省份名称(如 "广东省"),未知时返回 "未知" */ function getProvinceByIP(ip) { try { const result = query.search(ip); //console.log(result); if (!result || !result.country.includes('中国')) { return '其他'; } // ip2region 返回的格式示例:"中国|0|广东省|深圳市|电信" const parts = result.province; const provinceCode = parts[2]; // 省份代码在第三部分 // 检查是否是已知的省份代码 // if (PROVCE_CODE_MAP[provinceCode]) { // return PROVINCE_CODE_MAP[provinceCode]; // } // 如果未映射,返回原始代码(可能是数据库更新后的新代码) return parts || '其他'; } catch (error) { console.error('IP 解析失败:', error); return '其他'; } }