cb/server/laf-cloud/functions/syncUserUnionId.ts
2026-09-03 17:16:26 +08:00

161 lines
4.2 KiB
TypeScript
Raw Permalink 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'
const db = cloud.database();
const TA_RECEIVER_URL = "https://data.nika4fun.com/sync_data";
const TA_PRODUCTION_APP_ID = "87d18958cea145f29d3265470ecd3486";
const TA_TEST_APP_ID = "121591378fc1423893deb12041413eb3";
const UPLOAD_CONFIRMATION = "UPLOAD";
export default async function (ctx: FunctionContext) {
const body = ctx.body || {};
const configuredToken = String((globalThis as any).process?.env?.TA_UNIONID_SYNC_TOKEN || "");
const adminToken = String(body.adminToken || "");
if (!configuredToken) {
return fail("服务端未配置 TA_UNIONID_SYNC_TOKEN");
}
if (!adminToken || adminToken !== configuredToken) {
return fail("unauthorized");
}
const openid = typeof body.openid === "string" ? body.openid.trim() : "";
if (!openid) {
return fail("openid required");
}
const collectionName = body.gameName === "iaa" ? "usersAd" : "users";
const result = await db.collection(collectionName).where({ openid: openid }).getOne();
const user = result.data;
if (!user) {
return fail(`未在 ${collectionName} 中找到该 openid`);
}
const unionid = typeof user.unionid === "string" ? user.unionid.trim() : "";
if (!unionid) {
return fail("该用户没有有效的 unionid");
}
const taProject = resolveTaProject(user.isDebug);
if (!taProject) {
return fail("用户 isDebug 字段缺失或无效,无法安全判断数数项目");
}
const distinctId = typeof user.distinctId === "string" ? user.distinctId.trim() : "";
const preview = {
collection: collectionName,
taProject: taProject.name,
accountId: openid,
distinctId: distinctId || null,
properties: {
unionid: unionid,
},
};
if (body.confirm !== UPLOAD_CONFIRMATION) {
return {
code: 1,
data: { dryRun: true, ...preview },
msg: `预览成功;确认无误后传 confirm=${UPLOAD_CONFIRMATION} 才会上传`,
};
}
try {
const receiverResponse = await uploadUnionId(
taProject.appId,
openid,
distinctId,
unionid,
);
return {
code: 1,
data: { uploaded: true, ...preview, receiverResponse: receiverResponse },
msg: "unionid 已提交到数数",
};
} catch (error) {
const message = getErrorMessage(error);
console.error("syncUserUnionId upload failed:", message);
return fail(message);
}
}
function resolveTaProject(isDebug: any) {
const releaseFlag = normalizeBoolean(isDebug);
if (releaseFlag === null) {
return null;
}
// 当前客户端把“正式版”记录为 isDebug=true,这里与现有登录逻辑保持一致。
return releaseFlag
? { name: "production", appId: TA_PRODUCTION_APP_ID }
: { name: "test", appId: TA_TEST_APP_ID };
}
function normalizeBoolean(value: any): boolean | null {
if (value === true || value === "true" || value === 1 || value === "1") {
return true;
}
if (value === false || value === "false" || value === 0 || value === "0") {
return false;
}
return null;
}
async function uploadUnionId(
appId: string,
accountId: string,
distinctId: string,
unionid: string,
) {
const data: any = {
"#account_id": accountId,
"#type": "user_set",
properties: {
unionid: unionid,
},
};
if (distinctId) {
data["#distinct_id"] = distinctId;
}
const formData = [
`appid=${encodeURIComponent(appId)}`,
`data=${encodeURIComponent(JSON.stringify(data))}`,
"debug=0",
].join("&");
const response = await cloud.fetch({
url: TA_RECEIVER_URL,
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
},
data: formData,
timeout: 10000,
});
const receiverResponse = parseReceiverResponse(response?.data);
if (!receiverResponse || Number(receiverResponse.code) !== 0) {
throw new Error(`数数接收失败: ${JSON.stringify(receiverResponse ?? response?.data)}`);
}
return receiverResponse;
}
function parseReceiverResponse(value: any) {
if (typeof value !== "string") {
return value;
}
try {
return JSON.parse(value);
} catch {
return null;
}
}
function getErrorMessage(error: any) {
return error instanceof Error ? error.message : String(error);
}
function fail(msg: string) {
return { code: 0, data: null, msg: msg };
}