feat: 审计链路
Some checks failed
构建并部署前端到测试环境 / build-and-deploy (push) Failing after 59s

This commit is contained in:
luo
2026-08-07 18:33:37 +08:00
parent fa3d7088f9
commit b8b2854aa6
54 changed files with 5448 additions and 12 deletions

137
src/utils/business/audit.ts Normal file
View File

@@ -0,0 +1,137 @@
import type {
AuditActorKind,
AuditCategory,
AuditResult,
AuditRiskLevel,
AuditScopeType,
AuditSource,
IntegrationResultCategory
} from '@/types/api'
export const auditCategoryLabels: Record<AuditCategory, string> = {
configuration: '配置',
reliability: '可靠性',
asset: '资产',
security: '安全',
identity: '身份',
business: '业务'
}
export const auditActorKindLabels: Record<AuditActorKind, string> = {
account: '人工账号',
personal_customer: '个人客户',
openapi: '开放接口账号',
system_task: '系统任务',
scheduled_job: '计划任务',
external_system: '外部系统'
}
export const auditScopeTypeLabels: Record<AuditScopeType, string> = {
platform: '平台',
shop: '店铺',
personal_customer: '个人客户'
}
export const auditSourceLabels: Record<AuditSource, string> = {
admin_api: '后台管理 API',
personal_api: '个人客户 API',
openapi: '代理 OpenAPI',
worker: '异步 Worker',
scheduler: '计划任务',
callback: '外部系统回调'
}
export const auditResourceRelationLabels: Record<string, string> = {
primary: '主要资源',
affected: '受影响资源',
reference: '引用资源'
}
export const auditSubjectVisibilityLabels: Record<string, string> = {
internal_only: '仅平台可见',
subject_result: '主体可见结论',
subject_detail: '主体可见安全详情'
}
export const auditResourceTypeLabels: Record<string, string> = {
iot_card: 'IoT 卡',
device: '设备',
asset_allocation_record: '资产分配记录',
exchange_order: '换卡订单',
shop: '店铺',
enterprise: '企业',
order: '订单',
refund: '退款',
integration_log: '外部集成记录'
}
export const auditResultMeta: Record<
AuditResult,
{ label: string; type: 'success' | 'danger' | 'warning' | 'info' }
> = {
success: { label: '成功', type: 'success' },
failed: { label: '失败', type: 'danger' },
denied: { label: '拒绝', type: 'danger' },
partial: { label: '部分成功', type: 'warning' },
unknown: { label: '未知', type: 'info' }
}
const auditResultLabels: Record<string, string> = {
success: '成功',
failed: '失败',
denied: '拒绝',
partial: '部分成功',
unknown: '未知',
pending: '处理中',
not_found: '未找到',
invalid_payload: '无效载荷',
conflict: '冲突',
ignored: '已忽略',
merged: '已合并',
rate_limited: '已限流',
completed: '已完成',
cancelled: '已取消'
}
export function auditResultDisplay(result?: string | null, resultName?: string | null): string {
const name = resultName?.trim()
if (name) return name
return result ? auditResultLabels[result] || result : '-'
}
export const auditRiskMeta: Record<
AuditRiskLevel,
{ label: string; type: 'success' | 'danger' | 'warning' | 'info' }
> = {
low: { label: '低', type: 'success' },
normal: { label: '普通', type: 'info' },
high: { label: '高', type: 'warning' },
critical: { label: '严重', type: 'danger' }
}
export const integrationCategoryMeta: Record<
IntegrationResultCategory,
{ label: string; type: 'success' | 'danger' | 'warning' | 'info' }
> = {
processing: { label: '处理中', type: 'warning' },
succeeded: { label: '成功', type: 'success' },
indeterminate: { label: '结果不确定', type: 'warning' },
failed: { label: '失败', type: 'danger' },
not_sent: { label: '未发送', type: 'info' }
}
export function toRfc3339(value?: string | Date | null): string | undefined {
if (!value) return undefined
const date = value instanceof Date ? value : new Date(value)
return Number.isNaN(date.getTime()) ? undefined : date.toISOString()
}
export function fenDisplay(value?: number | null): string {
return typeof value === 'number' ? `¥${(value / 100).toFixed(2)}` : '-'
}
export function auditJson(value: unknown): string {
if (value === null || value === undefined) return '-'
if (typeof value === 'string') return value
return JSON.stringify(value, null, 2)
}

View File

@@ -0,0 +1,69 @@
import type { AuditSubjectResourceType } from '@/types/api'
import type { AuditFinanceField, AuditInvestigationTarget } from '@/components/business/audit/types'
interface AuditResourceRouteOptions {
userType?: number
resourceType: AuditSubjectResourceType | string
internalId?: string | number | null
businessIdentifier?: string | null
}
/** 根据当前主体选择平台内部 ID 或主体安全业务标识。 */
export function resolveAuditResourceTarget(
options: AuditResourceRouteOptions
): AuditInvestigationTarget | null {
const { userType, resourceType, internalId, businessIdentifier } = options
const normalizedBusinessIdentifier = businessIdentifier?.trim()
if (userType === 3) {
if (
!normalizedBusinessIdentifier ||
![
'iot_card',
'device',
'asset_allocation_record',
'exchange_order',
'shop',
'enterprise'
].includes(resourceType)
)
return null
return {
mode: 'agent',
resourceType: resourceType as AuditSubjectResourceType,
id: normalizedBusinessIdentifier
}
}
if (userType === 4) {
if (!normalizedBusinessIdentifier || !['iot_card', 'device'].includes(resourceType)) return null
return {
mode: 'enterprise',
resourceType: resourceType as AuditSubjectResourceType,
id: normalizedBusinessIdentifier
}
}
if (
internalId === undefined ||
internalId === null ||
internalId === '' ||
(typeof internalId === 'string' && !internalId.trim()) ||
(typeof internalId === 'number' && (!Number.isFinite(internalId) || internalId <= 0))
)
return null
return { mode: 'resource', resourceType, id: String(internalId).trim() }
}
export function resolveFinanceAuditTarget(
field: AuditFinanceField,
value?: string | number | null
): AuditInvestigationTarget | null {
const normalizedValue = typeof value === 'string' ? value.trim() : value
if (
normalizedValue === undefined ||
normalizedValue === null ||
normalizedValue === '' ||
(typeof normalizedValue === 'number' &&
(!Number.isFinite(normalizedValue) || normalizedValue <= 0))
)
return null
return { mode: 'finance', field, value: normalizedValue }
}

View File

@@ -9,8 +9,16 @@ const resolveTargetRoute = (
target: NotificationTarget
): TargetRoute | null => {
const targetId = target.target_id === null ? '' : String(target.target_id)
const targetType = target.target_type || refType
switch (refType) {
if (targetType === 'integration_log') {
const integrationId = target.target_key?.trim()
return integrationId
? { path: `/audit/integrations/${encodeURIComponent(integrationId)}` }
: null
}
switch (targetType) {
case 'system_config':
return { path: RoutesAlias.SystemConfigs }
case 'refund':
@@ -27,7 +35,6 @@ const resolveTargetRoute = (
return { path: RoutesAlias.ExpiringAssets }
case 'shop_fund':
return { path: RoutesAlias.AgentFundOverview }
case 'integration_log':
case 'package':
case 'asset':
case 'wecom_approval':