企业微信通知增加原始输入

This commit is contained in:
andy committed 2026-09-10 15:58:22 +08:00
1 parent 7ea7c211cc
commit be7d765018
5 files changed
+242 -11

No files matched your search

@@ -98,6 +98,17 @@ function jsonObject(value: unknown): Record<string, unknown> {
: {};
}
export function readLeaderSummaryOriginalText(config: AppConfig, ciphertext: unknown): string | null {
if (typeof ciphertext !== 'string' || !ciphertext) return null;
try {
return decryptText(config, ciphertext);
} catch {
// One unreadable historical input must not stop this batch's notifications.
// Neither ciphertext nor decrypted instructions belong in diagnostics.
return null;
}
}
export class LeaderNotificationService {
private projectorTimer: NodeJS.Timeout | null = null;
private projectorInFlight: Promise<void> | null = null;
@@ -433,6 +444,7 @@ export class LeaderNotificationService {
task.business_route_id,
task.success_receipt,
task.created_at,
task.original_text_ciphertext,
assignee.username AS assignee_username,
source_event.id AS source_outbox_event_id,
COALESCE(delivery_state.has_exposed_needs_review, false) AS has_exposed_needs_review
@@ -545,6 +557,7 @@ export class LeaderNotificationService {
businessRouteId: text(row.business_route_id) || null,
assigneeUsername: text(row.assignee_username),
createdAt: String(row.created_at),
originalText: readLeaderSummaryOriginalText(this.config, row.original_text_ciphertext),
successReceipt: jsonObject(row.success_receipt),
hadNeedsReview: hadExposedNeedsReview
});
+37 -10
View File
@@ -9,6 +9,7 @@ export interface LeaderTaskSummaryInput {
businessRouteId: string | null;
assigneeUsername: string;
createdAt: string | Date;
originalText?: string | null;
successReceipt?: Record<string, unknown> | null;
hadNeedsReview?: boolean;
}
@@ -38,6 +39,29 @@ const NEEDS_REVIEW_STATUSES = new Set([
'uncertain'
]);
// A local display budget; only the original input may be abbreviated. Reserve
// space for the outcome so a long instruction cannot hide the actual result.
export const LEADER_SUMMARY_MAX_MESSAGE_BYTES = 4_000;
const INPUT_TRUNCATION_NOTICE = '\n…(原文过长,剩余内容请在平台查看)';
function originalInputText(value: string | null | undefined, byteBudget: number): string {
const original = typeof value === 'string'
? value.replace(/\r\n?/gu, '\n').replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/gu, '').trim()
: '';
if (!original) return '原始输入暂不可用,请在平台查看。';
if (Buffer.byteLength(original, 'utf8') <= byteBudget) return original;
const prefixBudget = Math.max(0, byteBudget - Buffer.byteLength(INPUT_TRUNCATION_NOTICE, 'utf8'));
let prefix = '';
let bytes = 0;
for (const character of original) {
const nextBytes = Buffer.byteLength(character, 'utf8');
if (bytes + nextBytes > prefixBudget) break;
prefix += character;
bytes += nextBytes;
}
return prefix + INPUT_TRUNCATION_NOTICE;
}
function singleLine(value: unknown, maxLength: number): string {
return String(value ?? '')
.normalize('NFKC')
@@ -131,23 +155,26 @@ export function buildLeaderTaskSummary(
const route = businessRouteById(input.businessRouteId);
const businessLabel = singleLine(route?.directive || '其他任务', 120) || '其他任务';
const assignee = singleLine(input.assigneeUsername, 160) || '未知员工';
const taskId = singleLine(input.taskId, 200) || '任务编号未记录';
const header = milestone === 'resolved' ? '【员工任务摘要·结果更新】' : '【员工任务摘要】';
const prefix = [
header,
`员工:${assignee}`,
`业务:${businessLabel}`,
`状态:${statusLabel}`,
`提交:${formatShanghaiTimestamp(input.createdAt)}`,
'',
'原始输入:',
''
].join('\n');
const suffix = `\n\n结果:${resultText.slice(0, 300)}`;
const inputBudget = LEADER_SUMMARY_MAX_MESSAGE_BYTES - Buffer.byteLength(prefix + suffix, 'utf8');
return {
milestone,
deliveryStatus,
statusLabel,
businessLabel,
resultText: resultText.slice(0, 300),
messageText: [
header,
`员工:${assignee}`,
`业务:${businessLabel}`,
`状态:${statusLabel}`,
`任务:${taskId}`,
`提交:${formatShanghaiTimestamp(input.createdAt)}`,
`结果:${resultText.slice(0, 300)}`
].join('\n')
messageText: prefix + originalInputText(input.originalText, inputBudget) + suffix
};
}