统一前后端代码格式化配置
This commit is contained in:
@@ -13,9 +13,7 @@ import {
|
||||
notifyCloudtentaclesAssetLow,
|
||||
notifyCloudtentaclesAuthExpired,
|
||||
} from '../notification/domain-notifications.js'
|
||||
import {
|
||||
retryCloudtentaclesPendingReturns,
|
||||
} from './cloudtentacles-number-recycle-service.js'
|
||||
import { retryCloudtentaclesPendingReturns } from './cloudtentacles-number-recycle-service.js'
|
||||
|
||||
type CloudtentaclesHealthAccountResult = {
|
||||
sourceKey: string
|
||||
@@ -50,11 +48,12 @@ export async function runCloudtentaclesHealthJob(job: JsonObject) {
|
||||
const summary = buildCloudtentaclesHealthSummary(results)
|
||||
const status = resolveCloudtentaclesHealthStatus(results)
|
||||
const orphanTaskCount = Number(returnRetryResult?.orphanCount || 0)
|
||||
const recycleNote = orphanTaskCount > 0
|
||||
? `,另有 ${orphanTaskCount} 个孤儿号码待人工/上游清理`
|
||||
: returnRetryResult?.returned
|
||||
? ',发货后退号重试正常'
|
||||
: ''
|
||||
const recycleNote =
|
||||
orphanTaskCount > 0
|
||||
? `,另有 ${orphanTaskCount} 个孤儿号码待人工/上游清理`
|
||||
: returnRetryResult?.returned
|
||||
? ',发货后退号重试正常'
|
||||
: ''
|
||||
const finalMessage = `${summary}${recycleNote}`
|
||||
|
||||
return {
|
||||
@@ -66,12 +65,11 @@ export async function runCloudtentaclesHealthJob(job: JsonObject) {
|
||||
okCount: okResults.length,
|
||||
lowAssetCount: lowAssetResults.length,
|
||||
failedCount: failedResults.length,
|
||||
asset: assetValues.length > 0
|
||||
? Math.min(...assetValues)
|
||||
: null,
|
||||
threshold: checkedResults.length > 0
|
||||
? Math.max(...checkedResults.map((item) => Number(item.threshold || 0)))
|
||||
: normalizeNonNegativeInteger(job.config?.assetThreshold, 500),
|
||||
asset: assetValues.length > 0 ? Math.min(...assetValues) : null,
|
||||
threshold:
|
||||
checkedResults.length > 0
|
||||
? Math.max(...checkedResults.map((item) => Number(item.threshold || 0)))
|
||||
: normalizeNonNegativeInteger(job.config?.assetThreshold, 500),
|
||||
accounts: results,
|
||||
pendingReturnRetry: returnRetryResult,
|
||||
}
|
||||
@@ -84,7 +82,8 @@ async function runCloudtentaclesHealthAccount(
|
||||
const sourceKey = String(account.sourceKey || '').trim() || 'default'
|
||||
const source = getCloudtentaclesSourceByKey(sourceKey)
|
||||
const session: JsonObject = getCloudtentaclesSessionStateByKey(sourceKey) || {}
|
||||
const label = String(account.label || source?.label || source?.username || sourceKey).trim() || sourceKey
|
||||
const label =
|
||||
String(account.label || source?.label || source?.username || sourceKey).trim() || sourceKey
|
||||
const threshold = normalizeNonNegativeInteger(account.assetThreshold, 500)
|
||||
const checkedAt = new Date().toISOString()
|
||||
|
||||
@@ -170,9 +169,10 @@ async function runCloudtentaclesHealthAccount(
|
||||
return {
|
||||
ok: true,
|
||||
status: asset < threshold ? 'asset_low' : 'ok',
|
||||
message: asset < threshold
|
||||
? `账号 ${label} 余额 ${asset} 低于阈值 ${threshold}`
|
||||
: `账号 ${label} 余额 ${asset} 正常`,
|
||||
message:
|
||||
asset < threshold
|
||||
? `账号 ${label} 余额 ${asset} 低于阈值 ${threshold}`
|
||||
: `账号 ${label} 余额 ${asset} 正常`,
|
||||
asset,
|
||||
threshold,
|
||||
sourceKey,
|
||||
@@ -230,16 +230,11 @@ function resolveCloudtentaclesHealthAccounts(job: JsonObject) {
|
||||
const configured = configuredMap.get(sourceKey)
|
||||
return {
|
||||
sourceKey,
|
||||
label: String(
|
||||
configured?.label || source.label || source.username || sourceKey,
|
||||
).trim() || sourceKey,
|
||||
enabled: configured
|
||||
? configured.enabled !== false
|
||||
: source.enabled !== false,
|
||||
assetThreshold: normalizeNonNegativeInteger(
|
||||
configured?.assetThreshold,
|
||||
defaultThreshold,
|
||||
),
|
||||
label:
|
||||
String(configured?.label || source.label || source.username || sourceKey).trim() ||
|
||||
sourceKey,
|
||||
enabled: configured ? configured.enabled !== false : source.enabled !== false,
|
||||
assetThreshold: normalizeNonNegativeInteger(configured?.assetThreshold, defaultThreshold),
|
||||
}
|
||||
})
|
||||
.filter(Boolean) as JsonObject[]
|
||||
@@ -258,12 +253,14 @@ function resolveCloudtentaclesHealthAccounts(job: JsonObject) {
|
||||
|
||||
// 兼容旧配置:没有任何平台账号时,仍检查历史 default。
|
||||
const fallback = configuredMap.get('default')
|
||||
return [{
|
||||
sourceKey: 'default',
|
||||
label: String(fallback?.label || '默认账号').trim() || '默认账号',
|
||||
enabled: fallback ? fallback.enabled !== false : true,
|
||||
assetThreshold: normalizeNonNegativeInteger(fallback?.assetThreshold, defaultThreshold),
|
||||
}]
|
||||
return [
|
||||
{
|
||||
sourceKey: 'default',
|
||||
label: String(fallback?.label || '默认账号').trim() || '默认账号',
|
||||
enabled: fallback ? fallback.enabled !== false : true,
|
||||
assetThreshold: normalizeNonNegativeInteger(fallback?.assetThreshold, defaultThreshold),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
function normalizeCloudtentaclesHealthAccount(
|
||||
@@ -322,7 +319,9 @@ function buildCloudtentaclesHealthSummary(results: CloudtentaclesHealthAccountRe
|
||||
lowAssetResults.length > 0 ? `余额预警 ${lowAssetResults.length} 个` : '',
|
||||
failedResults.length > 0 ? `异常 ${failedResults.length} 个` : '',
|
||||
disabledCount > 0 ? `停用 ${disabledCount} 个` : '',
|
||||
].filter(Boolean).join(',')
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(',')
|
||||
}
|
||||
|
||||
return `检查 ${checkedResults.length} 个账号,余额均正常${disabledCount > 0 ? `,停用 ${disabledCount} 个` : ''}`
|
||||
@@ -334,7 +333,11 @@ function resolveCloudtentaclesHealthStatus(results: CloudtentaclesHealthAccountR
|
||||
return 'skipped'
|
||||
}
|
||||
|
||||
if (checkedResults.some((item) => ['auth_missing', 'source_missing', 'failed'].includes(item.status))) {
|
||||
if (
|
||||
checkedResults.some((item) =>
|
||||
['auth_missing', 'source_missing', 'failed'].includes(item.status),
|
||||
)
|
||||
) {
|
||||
return 'failed'
|
||||
}
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ async function clearStaleKuaishouCloudBinding(task: TaskRow, errorMessage: strin
|
||||
errorMessage,
|
||||
actor: { source: 'system' },
|
||||
},
|
||||
now
|
||||
now,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ export async function recycleCloudtentaclesStaleNumbers(): Promise<RecycleNumber
|
||||
ORDER BY ft.id DESC
|
||||
LIMIT $2
|
||||
`,
|
||||
[staleBeforeIso, RECYCLE_SCAN_LIMIT]
|
||||
[staleBeforeIso, RECYCLE_SCAN_LIMIT],
|
||||
)
|
||||
|
||||
for (const task of rows.rows) {
|
||||
@@ -142,7 +142,7 @@ export async function recycleCloudtentaclesStaleNumbers(): Promise<RecycleNumber
|
||||
}
|
||||
|
||||
const flow = normalizeKuaishouCloudFlow(
|
||||
parseJsonObject(task.context_json).kuaishouCloudFulfillment
|
||||
parseJsonObject(task.context_json).kuaishouCloudFulfillment,
|
||||
)
|
||||
|
||||
// 已发货/兑换中:禁止换号覆盖,跳过
|
||||
@@ -171,7 +171,7 @@ export async function recycleCloudtentaclesStaleNumbers(): Promise<RecycleNumber
|
||||
logWarn(
|
||||
'[scheduler/cloudtentacles-number-recycle]',
|
||||
'旧绑定号码无操作权限(号码已退/已回收),已清空任务绑定,等待重新取号',
|
||||
{ taskId, error: message }
|
||||
{ taskId, error: message },
|
||||
)
|
||||
} catch (clearError) {
|
||||
result.failed += 1
|
||||
@@ -225,7 +225,7 @@ export async function retryCloudtentaclesPendingReturns(): Promise<{
|
||||
ORDER BY ft.id DESC
|
||||
LIMIT $2
|
||||
`,
|
||||
[TASK_STATUS.DISPATCHED_PENDING_RETURN, RECYCLE_SCAN_LIMIT]
|
||||
[TASK_STATUS.DISPATCHED_PENDING_RETURN, RECYCLE_SCAN_LIMIT],
|
||||
)
|
||||
|
||||
for (const task of rows.rows) {
|
||||
@@ -238,7 +238,7 @@ export async function retryCloudtentaclesPendingReturns(): Promise<{
|
||||
continue
|
||||
}
|
||||
const flow = normalizeKuaishouCloudFlow(
|
||||
parseJsonObject(task.context_json).kuaishouCloudFulfillment
|
||||
parseJsonObject(task.context_json).kuaishouCloudFulfillment,
|
||||
)
|
||||
if (!flow.binding.vnId || !flow.binding.vnKey) {
|
||||
result.failed += 1
|
||||
@@ -258,7 +258,7 @@ export async function retryCloudtentaclesPendingReturns(): Promise<{
|
||||
logWarn(
|
||||
'[scheduler/cloudtentacles-number-recycle]',
|
||||
'发货收尾退号无操作权限(号码已退/已回收),请人工确认任务收尾',
|
||||
{ taskId, error: message }
|
||||
{ taskId, error: message },
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -3,10 +3,7 @@ import type { JsonObject } from '../../types/json.js'
|
||||
|
||||
import { APP_CONFIG_KEYS } from '../../config/app-config-keys.js'
|
||||
import { PROJECT_ROOT } from '../../config/runtime.js'
|
||||
import {
|
||||
readAppConfigEntry,
|
||||
saveAppConfigEntry,
|
||||
} from '../config/app-config-store.js'
|
||||
import { readAppConfigEntry, saveAppConfigEntry } from '../config/app-config-store.js'
|
||||
|
||||
const SCHEDULED_JOBS_FILE_PATH = path.join(PROJECT_ROOT, 'data', 'scheduled-jobs.json')
|
||||
const CLOUDTENTACLES_HEALTH_JOB_ID = 'cloudtentacles-health'
|
||||
@@ -36,26 +33,35 @@ export function saveScheduledJobsConfig(rawValue: unknown) {
|
||||
}
|
||||
|
||||
export function getCloudtentaclesHealthJob(config: JsonObject = getScheduledJobsConfig()) {
|
||||
return (Array.isArray(config.jobs) ? config.jobs : [])
|
||||
.find((item) => String(item.id || '').trim() === CLOUDTENTACLES_HEALTH_JOB_ID)
|
||||
|| createDefaultCloudtentaclesHealthJob()
|
||||
return (
|
||||
(Array.isArray(config.jobs) ? config.jobs : []).find(
|
||||
(item) => String(item.id || '').trim() === CLOUDTENTACLES_HEALTH_JOB_ID,
|
||||
) || createDefaultCloudtentaclesHealthJob()
|
||||
)
|
||||
}
|
||||
|
||||
export function getWorkOrderTimeoutJob(config: JsonObject = getScheduledJobsConfig()) {
|
||||
return (Array.isArray(config.jobs) ? config.jobs : [])
|
||||
.find((item) => String(item.id || '').trim() === WORK_ORDER_TIMEOUT_JOB_ID)
|
||||
|| createDefaultWorkOrderTimeoutJob()
|
||||
return (
|
||||
(Array.isArray(config.jobs) ? config.jobs : []).find(
|
||||
(item) => String(item.id || '').trim() === WORK_ORDER_TIMEOUT_JOB_ID,
|
||||
) || createDefaultWorkOrderTimeoutJob()
|
||||
)
|
||||
}
|
||||
|
||||
export function getDepositUnfreezeJob(config: JsonObject = getScheduledJobsConfig()) {
|
||||
return (Array.isArray(config.jobs) ? config.jobs : [])
|
||||
.find((item) => String(item.id || '').trim() === DEPOSIT_UNFREEZE_JOB_ID)
|
||||
|| createDefaultDepositUnfreezeJob()
|
||||
return (
|
||||
(Array.isArray(config.jobs) ? config.jobs : []).find(
|
||||
(item) => String(item.id || '').trim() === DEPOSIT_UNFREEZE_JOB_ID,
|
||||
) || createDefaultDepositUnfreezeJob()
|
||||
)
|
||||
}
|
||||
|
||||
export function getScheduledJobById(jobId: unknown, config: JsonObject = getScheduledJobsConfig()) {
|
||||
return (Array.isArray(config.jobs) ? config.jobs : [])
|
||||
.find((item) => String(item.id || '').trim() === String(jobId || '').trim()) || null
|
||||
return (
|
||||
(Array.isArray(config.jobs) ? config.jobs : []).find(
|
||||
(item) => String(item.id || '').trim() === String(jobId || '').trim(),
|
||||
) || null
|
||||
)
|
||||
}
|
||||
|
||||
export function normalizeScheduledJobsConfig(rawValue: unknown) {
|
||||
@@ -159,10 +165,15 @@ function normalizeCloudtentaclesHealthAccounts(config: JsonObject, defaultAssetT
|
||||
.filter(Boolean) as ReturnType<typeof normalizeCloudtentaclesHealthAccount>[]
|
||||
|
||||
if (accounts.length === 0) {
|
||||
accounts.push(normalizeCloudtentaclesHealthAccount({
|
||||
sourceKey: config.sourceKey || 'default',
|
||||
assetThreshold: defaultAssetThreshold,
|
||||
}, defaultAssetThreshold))
|
||||
accounts.push(
|
||||
normalizeCloudtentaclesHealthAccount(
|
||||
{
|
||||
sourceKey: config.sourceKey || 'default',
|
||||
assetThreshold: defaultAssetThreshold,
|
||||
},
|
||||
defaultAssetThreshold,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
const seen = new Set<string>()
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { logError, logInfo, logWarn } from '../../utils/logger.js'
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
import {
|
||||
getScheduledJobById,
|
||||
getScheduledJobsConfig,
|
||||
} from './config-service.js'
|
||||
import { getScheduledJobById, getScheduledJobsConfig } from './config-service.js'
|
||||
import { runCloudtentaclesHealthJob } from './cloudtentacles-health-job.js'
|
||||
import { runDepositUnfreezeJob } from './deposit-unfreeze-job.js'
|
||||
import { runWorkOrderTimeoutJob } from './work-order-timeout-job.js'
|
||||
@@ -52,7 +49,9 @@ export function getScheduledJobRuntimeStates() {
|
||||
|
||||
export async function runScheduledJobNow(jobId: unknown) {
|
||||
const config = getScheduledJobsConfig()
|
||||
const job = config.jobs.find((item) => String(item.id || '').trim() === String(jobId || '').trim())
|
||||
const job = config.jobs.find(
|
||||
(item) => String(item.id || '').trim() === String(jobId || '').trim(),
|
||||
)
|
||||
|
||||
if (!job) {
|
||||
throw createHttpError('定时任务不存在', {
|
||||
@@ -131,8 +130,7 @@ async function runJob(job: JsonObject, { manual = false }: { manual?: boolean }
|
||||
|
||||
try {
|
||||
const result = await dispatchJob(job)
|
||||
const summary =
|
||||
result && typeof result === 'object' ? (result as JsonObject) : {}
|
||||
const summary = result && typeof result === 'object' ? (result as JsonObject) : {}
|
||||
updateJobState(jobId, {
|
||||
running: false,
|
||||
lastFinishedAt: new Date().toISOString(),
|
||||
|
||||
Reference in New Issue
Block a user