feat: 项目负责人+内部任务群+portal 内部身份领取+群主链接归属
- 迁移 0012:projects.owner_userid / tasks.task_chat_id / claims.owner_partner_id - lib/task-group-service.ts:懒建 appchat 任务群(首个内部群主领取时创建,成员 diff 补员),播报/卡片文案 - wecom-client 新增 appchat create/update/get/send 封装 - OAuth ticket 增可选 uid(v:1 兼容),内部成员领取按 wecom_userid 解析群主身份 - 外部 OAuth 领取人 upsert 打 partner_type='group_owner' - portal 链接带 ?owner= 穿透 OAuth 往返,claims 记录归属群主 - 后台任务卡「群主链接」面板:复制链接/推送内部群主/任务群状态 - 催办与超期升级同步播报进任务群
This commit is contained in:
@@ -96,7 +96,7 @@ function parseDueDays(value: string | undefined) {
|
||||
return Math.min(30, Math.floor(parsed));
|
||||
}
|
||||
|
||||
function hasAppCredentials(config: WecomConfig) {
|
||||
export function hasAppCredentials(config: WecomConfig) {
|
||||
return Boolean(config.corpId && config.agentId && config.secret);
|
||||
}
|
||||
|
||||
@@ -635,3 +635,135 @@ export async function fetchWecomExternalContactName(
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function postAppChat(
|
||||
config: WecomConfig,
|
||||
path: string,
|
||||
body: Record<string, unknown>,
|
||||
errorLabel: string,
|
||||
fetchImpl: FetchLike,
|
||||
): Promise<Record<string, unknown>> {
|
||||
if (!hasAppCredentials(config)) {
|
||||
throw new WecomClientError(
|
||||
"企业微信自建应用未配置(WECOM_CORP_ID / WECOM_AGENT_ID / WECOM_SECRET)",
|
||||
400,
|
||||
);
|
||||
}
|
||||
const token = await fetchAccessToken(config, fetchImpl);
|
||||
const url = `${WECOM_API_ORIGIN}${path}?access_token=${encodeURIComponent(token)}`;
|
||||
const response = await fetchImpl(url, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json; charset=utf-8" },
|
||||
body: JSON.stringify(body),
|
||||
signal: AbortSignal.timeout(12_000),
|
||||
});
|
||||
const payload = await readEnvelope(response, errorLabel);
|
||||
if (!response.ok) {
|
||||
throw new WecomClientError(`${errorLabel}(HTTP ${response.status})`, 502);
|
||||
}
|
||||
ensureOk(payload, errorLabel);
|
||||
return payload;
|
||||
}
|
||||
|
||||
export async function createAppChat(
|
||||
config: WecomConfig,
|
||||
params: { name: string; userlist: string[]; chatid: string },
|
||||
fetchImpl: FetchLike = fetch,
|
||||
): Promise<{ chatid: string }> {
|
||||
const userlist = params.userlist
|
||||
.map((id) => bindingValue(id))
|
||||
.filter((id) => id.length > 0);
|
||||
if (userlist.length < 2) {
|
||||
throw new WecomClientError("创建群聊会话至少需要 2 名成员", 400);
|
||||
}
|
||||
const chatid = bindingValue(params.chatid);
|
||||
if (!chatid || chatid.length > 32) {
|
||||
throw new WecomClientError("群聊 chatid 缺失或超过 32 字符", 400);
|
||||
}
|
||||
const name = bindingValue(params.name).slice(0, 20);
|
||||
const payload = await postAppChat(
|
||||
config,
|
||||
"/cgi-bin/appchat/create",
|
||||
{ name, owner: Number(config.agentId), userlist, chatid },
|
||||
"创建企业微信群聊会话失败",
|
||||
fetchImpl,
|
||||
);
|
||||
return { chatid: bindingValue(payload.chatid) || chatid };
|
||||
}
|
||||
|
||||
export async function updateAppChat(
|
||||
config: WecomConfig,
|
||||
params: {
|
||||
chatid: string;
|
||||
addUserList?: string[];
|
||||
delUserList?: string[];
|
||||
},
|
||||
fetchImpl: FetchLike = fetch,
|
||||
): Promise<void> {
|
||||
const body: Record<string, unknown> = {
|
||||
chatid: bindingValue(params.chatid),
|
||||
};
|
||||
const addUserList = (params.addUserList ?? [])
|
||||
.map((id) => bindingValue(id))
|
||||
.filter((id) => id.length > 0);
|
||||
const delUserList = (params.delUserList ?? [])
|
||||
.map((id) => bindingValue(id))
|
||||
.filter((id) => id.length > 0);
|
||||
if (addUserList.length === 0 && delUserList.length === 0) return;
|
||||
if (addUserList.length > 0) body.add_user_list = addUserList;
|
||||
if (delUserList.length > 0) body.del_user_list = delUserList;
|
||||
await postAppChat(
|
||||
config,
|
||||
"/cgi-bin/appchat/update",
|
||||
body,
|
||||
"更新企业微信群聊会话失败",
|
||||
fetchImpl,
|
||||
);
|
||||
}
|
||||
|
||||
export async function getAppChat(
|
||||
config: WecomConfig,
|
||||
chatid: string,
|
||||
fetchImpl: FetchLike = fetch,
|
||||
): Promise<{ chatId: string; name: string; userlist: string[] } | null> {
|
||||
try {
|
||||
const payload = await postAppChat(
|
||||
config,
|
||||
"/cgi-bin/appchat/get",
|
||||
{ chatid: bindingValue(chatid) },
|
||||
"获取企业微信群聊会话失败",
|
||||
fetchImpl,
|
||||
);
|
||||
const detail = payload.chat_info as
|
||||
| { chatid?: unknown; name?: unknown; userlist?: unknown[] }
|
||||
| undefined;
|
||||
if (!detail) return null;
|
||||
return {
|
||||
chatId: bindingValue(detail.chatid),
|
||||
name: bindingValue(detail.name),
|
||||
userlist: Array.isArray(detail.userlist)
|
||||
? detail.userlist.map((id: unknown) => bindingValue(id)).filter(Boolean)
|
||||
: [],
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function sendAppChatMessage(
|
||||
config: WecomConfig,
|
||||
params: { chatid: string; text: string },
|
||||
fetchImpl: FetchLike = fetch,
|
||||
): Promise<void> {
|
||||
const chatid = bindingValue(params.chatid);
|
||||
const content = bindingValue(params.text);
|
||||
if (!chatid || !content) return;
|
||||
await postAppChat(
|
||||
config,
|
||||
"/cgi-bin/appchat/send",
|
||||
{ chatid, safe: 0, msgtype: "text", text: { content } },
|
||||
"发送企业微信群聊消息失败",
|
||||
fetchImpl,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user