import cloud from '@lafjs/cloud' import { createHash } from 'crypto'; const db = cloud.database(); import crypto from 'crypto'; const mchid = "1719895437";//商户id const serialNo = "2A97ED6ED441E55600EBC8830B9EC889ED41496E";//证书序列号 const ThinkingData = require('thinkingdata-node'); import Ip2region from 'ip2region'; const query = new Ip2region(); const canIos = false; const version = "1.9.93"; const AB_CONFIG_COLLECTION = "ab_config"; export default async function (ctx: FunctionContext) { let code = ctx.body.code; let distinctId = ctx.body.distinctId; let isDebug = ctx.body.isDebug; let dbname = "users"; const gameName = ctx.body.gameName; if (gameName == "iaa") { dbname = "usersAd"; } let idname = "687a0e79c03536d5ff982f77"; if (gameName == "iaa") { idname = "68e90168b30b6944c56bdb74"; } 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); let token = await stringToHash(Date.now() + openid); let abTests = {}; if (!userData) { abTests = await getActiveABAssignments(openid, null); 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: 0, coinAmount: 0, healthAmount: 5, healthtimestamp: 0, freezeAmount: 3, hammerAmount: 3, magicAmount: 3, userPowerTime: 0, monthCardReward: 0, monthCardTime: 0, rebornGiftCount: 0, rebornGiftTime: 0, useravatar: Math.floor(Math.random() * 4) + "", username: "user", pay_user: false, isWhite: false, isFirst: true, starter_pack: 0, starter_packState: 0, careerAmount: 0, useravatarIcon: "0", forcedUpdate: false, token: token, abTests: abTests }); } else { abTests = normalizeABTests(userData.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; } } await db.collection(dbname).where({ _id: userData._id }).update({ session_key: session_key, distinctId: distinctId, isDebug: isDebug, isFirst: false, unionid: unionid, task: JSON.stringify(task), taskTime: Date.now() + 8 * 3600000, token: token }); } if (userData?.freeze) { return { code: 0, data: "账号封禁", msg: "账号封禁" }; } await delayExecution(0.1); userData = await getUserData(dbname, openid); let res = null; if (userData.openid) { res = await db.collection("order").where({ state: 1, openid: userData.openid }).get(); } let outTradeNo: any = []; 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); 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; userData.abTests = normalizeABTests(abTests); let rebornGiftTime = userData?.rebornGiftTime || 0; let times = getTodayStartTimestamp(); if (rebornGiftTime < times) { userData.rebornGiftCount = 0; } if (userData.isWhite && !userData.film && userData.film != 0) { userData.film = userData.levelAmount; } userData.canIos = canIos; userData.version = version; //console.log(userData) //console.log("查询时间:"); return { code: 1, data: userData } } function stringToHash(input: string, algorithm: 'sha1' | 'sha256' | 'sha512' = 'sha256'): string { return createHash(algorithm).update(input).digest('hex'); } 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" }; function getTodayStartTimestamp() { const date = new Date(); date.setHours(0, 0, 0, 0); return date.getTime(); } async function getWXOpenId(code, dbname) { let appid = "wxdd145ced49158a1e"; let appsecret = "7f295245ffc4884b03306c64e30654db"; if (dbname == "usersAd") { appid = "wxe69a671ea2c93af3"; appsecret = "1eef42cd8810024eaea6dac961f9b3b0"; } 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)); switch (res.data[i].itemid) { case "month_Card": const latestUser = await db.collection("users").where({ _id: res1.data._id }).getOne(); const now = Date.now(); const oldMonthCardTime = Number(latestUser.data?.monthCardTime) || 0; const monthCardTime = Math.max(oldMonthCardTime, now) + 2592000000; const monthCardReward = oldMonthCardTime > now ? (Number(latestUser.data?.monthCardReward) || 0) : 0; await db.collection("users").where({ _id: res1.data._id }).update({ monthCardTime: monthCardTime, monthCardReward: monthCardReward, pay_user: true }); await db.collection("order").where({ outTradeNo: res.data[i].outTradeNo }).update({ monthCardGranted: true }); break; case "reborn_Gift": db.collection("users").where({ _id: res1.data._id }).update({ rebornGiftTime: Date.now(), rebornGiftCount: 1, 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") { 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 = '87d18958cea145f29d3265470ecd3486'; if (userInfo.isDebug != "true") { id = '121591378fc1423893deb12041413eb3'; } 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", 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 }, callback(e) { if (e) { console.log(e); } } }; teSDK.track(trackEvent); teSDK.close(); return; } /**获取订单*/ async function getOrder(outTradeNo) { const url = `https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/${outTradeNo}?mchid=1719895437`; 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=1719895437\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) { let abTests = normalizeABTests(oldAbTests); let configs = []; try { let ret = await db.collection(AB_CONFIG_COLLECTION).get(); // console.log("AB config count:", ret.data?.length || 0, ret.data); configs = pickConfigByLayer(ret.data || []); } catch (e) { // console.log("AB配置读取失败", e); return abTests; } const now = Date.now(); const nextAbTests: any = {}; for (let i = 0; i < configs.length; i++) { const experiment = configs[i]; if (!isValidExperiment(experiment)) { continue; } const layerKey = getABLayerKey(experiment.layer); if (abTests[layerKey] && experiment.resetAssignments !== true) { nextAbTests[layerKey] = { ...abTests[layerKey], experimentId: experiment.experimentId, layer: String(experiment.layer), name: experiment.name || abTests[layerKey].name || "", enabled: experiment.enabled !== false, }; continue; } const variant = pickABVariant(openid, experiment); if (!variant) { continue; } nextAbTests[layerKey] = { experimentId: experiment.experimentId, layer: String(experiment.layer), name: experiment.name || "", enabled: experiment.enabled !== false, group: variant.group, assignedAt: now, }; } return nextAbTests; } function normalizeABTests(abTests: any) { if (!abTests) { return {}; } if (typeof abTests == "string") { try { abTests = JSON.parse(abTests); } catch { return {}; } } const normalized: any = {}; for (const key in abTests) { const item = abTests[key]; if (!item || !item.layer || key !== getABLayerKey(item.layer)) { continue; } normalized[key] = { experimentId: item.experimentId || "", layer: String(item.layer), name: item.name || "", enabled: item.enabled !== false, group: item.group, assignedAt: item.assignedAt || Date.now(), }; } return normalized; } 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 '其他'; } }