关闭 allowJs;集中 JsonObject 定义并替换分散 any 别名;parseTaskContext 返回 TaskContext;任务详情 API 标注 AdminTaskDetailView;测试减少 as any。
142 lines
4.3 KiB
TypeScript
142 lines
4.3 KiB
TypeScript
import { logWarn } from '../../utils/logger.js'
|
|
import type { JsonObject } from '../../types/json.js'
|
|
import {
|
|
getNotificationConfig,
|
|
listEnabledBarkRecipients,
|
|
listEnabledWpushRecipients,
|
|
} from './config-service.js'
|
|
import { sendBarkNotification } from './bark-service.js'
|
|
import { sendWpushNotification } from './wpush-service.js'
|
|
|
|
type NotificationResult = ReturnType<typeof mapNotificationResult>
|
|
type NotificationInput = {
|
|
title?: string
|
|
body?: string
|
|
category?: string
|
|
url?: string
|
|
}
|
|
|
|
export async function sendInternalNotification(input: NotificationInput = {}) {
|
|
const config = getNotificationConfig()
|
|
const bark = (config.channels?.bark || {}) as { serverUrl?: string }
|
|
const barkRecipients = listEnabledBarkRecipients(config)
|
|
const wpushRecipients = listEnabledWpushRecipients(config)
|
|
const title = String(input.title || '订单系统通知').trim() || '订单系统通知'
|
|
const body = String(input.body || '').trim()
|
|
const category = String(input.category || 'system').trim() || 'system'
|
|
|
|
if (config.enabled === false) {
|
|
return {
|
|
enabled: false,
|
|
channel: 'internal',
|
|
successCount: 0,
|
|
failedCount: 0,
|
|
skippedCount: barkRecipients.length + wpushRecipients.length,
|
|
results: [] as NotificationResult[],
|
|
}
|
|
}
|
|
|
|
const results = [
|
|
...(await Promise.all(barkRecipients.map(async (recipient: JsonObject) => {
|
|
try {
|
|
const result = await sendBarkNotification({
|
|
recipient,
|
|
title,
|
|
body,
|
|
group: `订单系统/${category}`,
|
|
...(bark.serverUrl !== undefined ? { serverUrl: bark.serverUrl } : {}),
|
|
...(input.url !== undefined ? { url: input.url } : {}),
|
|
})
|
|
return mapNotificationResult('bark', recipient, recipient.deviceKey, true, '', result.status, result.response)
|
|
} catch (error) {
|
|
logWarn('[notification/bark]', 'Bark 内部通知发送失败', {
|
|
recipientId: recipient.id,
|
|
recipientName: recipient.name,
|
|
error,
|
|
})
|
|
return mapNotificationResult(
|
|
'bark',
|
|
recipient,
|
|
recipient.deviceKey,
|
|
false,
|
|
error instanceof Error ? error.message : 'Bark 内部通知发送失败',
|
|
isPlainObject(error) ? Number(error.statusCode || 0) || 0 : 0,
|
|
isPlainObject(error) ? error.context || null : null,
|
|
)
|
|
}
|
|
}))),
|
|
...(await Promise.all(wpushRecipients.map(async (recipient: JsonObject) => {
|
|
try {
|
|
const result = await sendWpushNotification({
|
|
recipient,
|
|
title,
|
|
body,
|
|
})
|
|
return mapNotificationResult('wpush', recipient, recipient.apiKey, true, '', result.status, result.response)
|
|
} catch (error) {
|
|
logWarn('[notification/wpush]', 'WPush 内部通知发送失败', {
|
|
recipientId: recipient.id,
|
|
recipientName: recipient.name,
|
|
error,
|
|
})
|
|
return mapNotificationResult(
|
|
'wpush',
|
|
recipient,
|
|
recipient.apiKey,
|
|
false,
|
|
error instanceof Error ? error.message : 'WPush 内部通知发送失败',
|
|
isPlainObject(error) ? Number(error.statusCode || 0) || 0 : 0,
|
|
isPlainObject(error) ? error.context || null : null,
|
|
)
|
|
}
|
|
}))),
|
|
]
|
|
|
|
return {
|
|
enabled: true,
|
|
channel: 'internal',
|
|
successCount: results.filter((item) => item.ok).length,
|
|
failedCount: results.filter((item) => !item.ok).length,
|
|
skippedCount: 0,
|
|
results,
|
|
}
|
|
}
|
|
|
|
function mapNotificationResult(
|
|
channel: string,
|
|
recipient: JsonObject,
|
|
recipientKey: unknown,
|
|
ok: boolean,
|
|
errorMessage: string,
|
|
status: number,
|
|
response: unknown,
|
|
) {
|
|
return {
|
|
channel,
|
|
recipientId: String(recipient.id || '').trim(),
|
|
recipientName: String(recipient.name || '').trim(),
|
|
recipientKeyMasked: maskSecretKey(recipientKey),
|
|
ok,
|
|
status,
|
|
errorMessage,
|
|
response,
|
|
}
|
|
}
|
|
|
|
function maskSecretKey(value: unknown) {
|
|
const normalized = String(value || '').trim()
|
|
if (!normalized) {
|
|
return ''
|
|
}
|
|
|
|
if (normalized.length <= 10) {
|
|
return `${normalized.slice(0, 2)}****${normalized.slice(-2)}`
|
|
}
|
|
|
|
return `${normalized.slice(0, 6)}****${normalized.slice(-6)}`
|
|
}
|
|
|
|
function isPlainObject(value: unknown): value is JsonObject {
|
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
|
|
}
|