cb/server/laf-cloud/functions/login.ts
2026-07-02 16:42:19 +08:00

392 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.91";
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);
if (!userData) {
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
});
} else {
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;
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;
}
/**
* 根据 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 '其他';
}
}