2026-07-27 11:28:33 +08:00

449 lines
13 KiB
JavaScript
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.

const request = require("../request");
const utils = require("../utils");
const information = require("../externalcontact-info");
const token = require("../token");
const dayjs = require("dayjs");
const api = require("../../api");
exports.main = async (context) => {
switch (context.type) {
case "editExternalContact":
return await exports.editExternalContact(context);
case "transferExternalcontact":
return await exports.transferExternalcontact(context);
case "removeExternalContact":
return await exports.removeExternalContact(context);
case "asyncExternalContactRemark":
return await exports.asyncExternalContactRemark(context);
case "batchGetBehaviorData":
return await exports.batchGetBehaviorData(context);
case "getCorpAllStaffUseBehaviorDataToday":
return await exports.getCorpAllStaffUseBehaviorDataToday(context);
case "getAllCorpYesterdayBehaviorData":
return await exports.getAllCorpYesterdayBehaviorData(context);
default: {
return {
success: false,
message: "未找到对应的操作类型",
};
}
}
};
exports.removeExternalContact = async (context) => {
return true;
};
// handoverUserId 是 string 离职成员的userid
// externalUserIds 是 array 客户的external_userid列表每次最多支持100个客户
// takeoverUserId 是 string 接替成员的userid
exports.transferExternalcontact = async (context) => {
const {
corpId,
externalUserIds = [],
handoverUserId,
takeoverUserId,
permanentCode,
} = context;
const accessToken = await token.getToken({
corpId,
permanentCode,
});
let url = `https://qyapi.weixin.qq.com/cgi-bin/externalcontact/transfer_customer?access_token=${accessToken}`;
let params = {
external_userid: externalUserIds,
handover_userid: handoverUserId,
takeover_userid: takeoverUserId,
};
let { errcode, errmsg } = await request.main(url, params, "POST");
if (errcode !== 0) {
return {
context,
success: false,
message: errmsg,
errcode,
};
}
return {
success: true,
message: "转移客户成功",
};
};
/**
* 编辑外部联系人
* @param {string} corpId 企业ID
* @param {string} externalUserId 外部联系人的userid
* @param {string} userId 企业成员的userid
* @param {string} env 云开发环境ID
*/
exports.editExternalContact = async ({ corpId, externalUserId, userId }) => {
let res = await information.getNameByexternalUserId({
corpId,
externalUserId,
type: "getNameByexternalUserId",
});
if (!res || !res.data) return;
let { follow_user, external_contact } = res.data;
let tags = [];
const user =
Array.isArray(follow_user) &&
follow_user.find((item) => item.userid === userId);
let remark = external_contact.name;
if (user) remark = user.remark;
follow_user.forEach((item) => {
if (item.userid === userId && item.tags.length > 0) {
item.tags.forEach((tag) => {
tags.push(tag.tag_id);
});
}
});
console.log("编辑外部联系人tags", tags);
console.log("编辑外部联系人", res.data);
console.log("remark", remark);
return await api.getMemberApi({
type: "asyncCustomerTag",
corpId,
name: remark,
externalUserId,
userId,
tags,
});
};
/**
* 同步备注名
*/
/**
* 同步备注名
*/
exports.asyncExternalContactRemark = async ({
corpId,
externalUserId,
list = [],
permanentCode = "",
}) => {
const accessToken = await token.getToken({
corpId,
permanentCode,
});
if (!externalUserId)
return {
success: false,
message: "externalUserId不能为空",
};
console.log("accessToken", accessToken);
let res = await information.getNameByexternalUserId({
corpId,
externalUserId,
type: "getNameByexternalUserId",
});
console.log("外部联系人信息", res);
let { follow_user } = res.data;
let userIds = follow_user.map((item) => item.userid);
if (userIds.length === 0) {
return {
success: false,
message: "该客户不是本机构的外部联系人",
};
}
// 把 list 排序 本人在前,其他人在后
list.sort((a, b) => {
if (a.relationship === "本人") {
return -1;
} else {
return 1;
}
});
// 逗号隔开
let mySelf = list
.filter((item) => item.relationship === "本人")
.map((item) => item.name)
.join("、");
let other = list
.filter((item) => item.relationship !== "本人")
.map((item) => item.name)
.join("、");
let remark = "";
if (mySelf) remark = `${mySelf}(本人)`;
if (other) remark = remark ? `${remark},${other}(家属)` : `${other}(家属)`;
const handler = async (userId) => {
let url = `https://qyapi.weixin.qq.com/cgi-bin/externalcontact/remark?access_token=${accessToken}`;
let params = {
userid: userId,
external_userid: externalUserId,
remark,
};
await request.main(url, params, "POST");
};
await utils.processInBatches(userIds, handler, 10);
return {
success: true,
message: "同步备注名成功",
};
};
exports.batchGetBehaviorData = async ({
dates,
permanentCode,
accessToken,
corpId,
}) => {
if (!accessToken)
accessToken = await token.getToken({ corpId, permanentCode });
let startTime = "",
endTime = "";
if (dates && Array.isArray(dates) && dates.length === 2) {
startTime = dayjs(dates[0]).startOf("day").unix();
endTime = dayjs(dates[1]).endOf("day").unix();
}
console.log("startTime", startTime);
console.log("endTime", endTime);
const { data } = await api.getCorpApi({
corpId,
permanentCode,
accessToken,
type: "getOpenedAccount",
});
const userIds = data && Array.isArray(data) ? data.map((i) => i.userid) : [];
const query = {
corpId,
permanentCode,
startTime,
endTime,
};
const taskFn = (userId) => {
return addBehaviorStatistics({ ...query, userId });
};
await utils.executeWithConcurrency(userIds, taskFn);
return {
success: true,
message: "批量获取成功",
};
};
async function addBehaviorStatistics({
userId,
corpId,
permanentCode,
startTime,
endTime,
}) {
const { success, data } = await exports.getUseBehaviorData({
startTime,
endTime,
userIds: [userId],
corpId,
permanentCode,
});
if (success) {
const list = data
.filter(
(i) =>
i.newContactCount !== 0 ||
i.newApplyCnt !== 0 ||
i.chatCnt !== 0 ||
i.negativeFeedbackCnt !== 0 ||
i.avgReplyTime !== 0 ||
i.replyPercentage !== 0 ||
i.messageCnt !== 0
)
.map((i) => {
return {
...i,
corpId,
userId,
};
});
await addBehaviorStatisticsInBatches(list);
return {
success: true,
message: "获取成功",
userId,
};
} else {
return data;
}
}
async function addBehaviorStatisticsInBatches(data, maxConcurrency = 10) {
const results = [];
let index = 0;
while (index < data.length) {
const batch = data.slice(index, index + maxConcurrency); // 获取当前批次
const promises = batch.map((doc) =>
api.getCorpApi({
type: "addBehaviorStatistics",
data: doc,
})
);
// 等待当前批次完成
const batchResults = await Promise.all(promises);
results.push(...batchResults);
// 更新索引,进入下一个批次
index += maxConcurrency;
}
return results;
}
/**
*
* @param
* new_apply_cnt 发起申请数,成员通过「搜索手机号」、「扫一扫」、「从微信好友中添加」、「从群聊中添加」、「添加共享、分配给我的客户」、「添加单向、双向删除好友关系的好友」、「从新的联系人推荐中添加」等渠道主动向客户发起的好友申请数量。
new_contact_cnt 新增客户数,成员新添加的客户数量。
chat_cnt 聊天总数, 成员有主动发送过消息的单聊总数。
message_cnt 发送消息数,成员在单聊中发送的消息总数。
reply_percentage 已回复聊天占比,浮点型,客户主动发起聊天后,成员在一个自然日内有回复过消息的聊天数/客户主动发起的聊天数比例,不包括群聊,仅在确有聊天时返回。
avg_reply_time 平均首次回复时长,单位为分钟,即客户主动发起聊天后,成员在一个自然日内首次回复的时长间隔为首次回复时长,所有聊天的首次回复总时长/已回复的聊天总数即为平均首次回复时长,不包括群聊,仅在确有聊天时返回。
negative_feedback_cnt 删除/拉黑成员的客户数,即将成员删除或加入黑名单的客户数。
* @returns
*/
exports.getUseBehaviorData = async ({
corpId,
userIds,
startTime,
endTime,
accessToken,
permanentCode,
}) => {
try {
// 如果没有传递 accessToken则获取一个新的 token
if (!accessToken)
accessToken = await token.getToken({ corpId, permanentCode });
// 构建请求 URL 和请求参数
const url = `https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_user_behavior_data?access_token=${accessToken}`;
const params = {
userid: userIds,
start_time: startTime,
end_time: endTime,
};
// 发送 POST 请求
const response = await request.main(url, params, "POST");
// 检查请求结果
if (response.errcode === 0) {
// 假设 errcode 为 0 表示成功
let list = response.behavior_data;
if (list && Array.isArray(list)) {
list = list.map((i) => {
return {
statTime: i.stat_time, // 时间
newContactCount: i.new_contact_cnt || 0,
newApplyCnt: i.new_apply_cnt || 0,
chatCnt: i.chat_cnt || 0,
negativeFeedbackCnt: i.negative_feedback_cnt || 0,
avgReplyTime: i.avg_reply_time || 0,
replyPercentage: i.reply_percentage || 0,
messageCnt: i.message_cnt || 0,
};
});
}
return {
success: true,
data: list, // 假设接口返回的数据字段是 behavior_data
message: "请求成功",
};
} else {
return {
success: false,
errcode: response.errcode,
message: response.errmsg || "请求失败",
};
}
} catch (err) {
console.error("请求失败:", err); // 打印错误日志以便调试
return {
success: false,
message: "请求失败,系统错误",
};
}
};
exports.getAllCorpYesterdayBehaviorData = async () => {
// 获取所以机构
try {
const corpRes = await api.getCorpApi({
type: "getCorpList",
});
const corpIds =
corpRes.data && Array.isArray(corpRes.data)
? corpRes.data.map((i) => i.corpId)
: [];
const yesterdayTime = dayjs().subtract(1, "day").format("YYYY-MM-DD");
const dates = [yesterdayTime, yesterdayTime];
const taskFn = (corpId) => {
return this.batchGetBehaviorData({ dates, corpId });
};
await utils.executeWithConcurrency(corpIds, taskFn);
return {
success: true,
message: "批量获取成功",
};
} catch (err) {
return {
success: false,
message: "批量获取失败",
};
}
};
exports.getCorpAllStaffUseBehaviorDataToday = async ({
corpId,
userIds,
dates,
}) => {
let accessToken;
try {
// 获取 accessToken
accessToken = await token.getToken({ corpId });
} catch (error) {
console.error("Error getting access token:", error);
return { success: false, message: "获取访问令牌失败" }; // 获取令牌失败,返回错误信息
}
const maxConcurrency = 10; // 设置最大并发数
const results = []; // 存储所有的结果
let index = 0;
// 设置查询的开始和结束时间(单位:秒)
const startTime = dayjs().startOf("day").unix();
const endTime = dayjs().endOf("day").unix();
try {
while (index < userIds.length) {
const batch = userIds.slice(index, index + maxConcurrency); // 当前批次的用户ID
const promises = batch.map(async (userId) => {
try {
const { data } = await exports.getUseBehaviorData({
corpId,
userIds: [userId],
startTime,
endTime,
accessToken,
});
const item = data && Array.isArray(data) ? data[0] : {};
return { userId, ...item };
} catch (error) {
console.error(`Error fetching data for userId: ${userId}`, error);
return []; // 失败时返回空数组,继续处理其他用户
}
});
// 等待当前批次完成
const batchResults = await Promise.all(promises);
results.push(...batchResults); // 合并结果
// 更新索引,进入下一个批次
index += maxConcurrency;
}
// 返回最终结果
return {
success: true,
data: results,
message: "获取成功",
};
} catch (error) {
console.error("Error in processing the batch requests:", error);
return { success: false, message: "批量请求处理失败" }; // 捕获批量处理过程中的异常
}
};