开始多平台 多店铺整改
This commit is contained in:
@@ -0,0 +1,460 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { DatabaseSync } from 'node:sqlite'
|
||||
|
||||
const CURRENT_DIR = path.dirname(fileURLToPath(import.meta.url))
|
||||
const PROJECT_ROOT = path.resolve(CURRENT_DIR, '..')
|
||||
const DEFAULT_DB_PATH = path.join(PROJECT_ROOT, 'data', 'order-site.db')
|
||||
|
||||
const PRESETS = {
|
||||
'codex-debug': {
|
||||
platformOrderIds: ['DEBUG-ORDER-001', '2067719225654999', '2067719225655000', '2067719225655001'],
|
||||
shopIds: ['debug-shop', 'shop-10001', 'shop-10002', 'shop-10003'],
|
||||
},
|
||||
}
|
||||
|
||||
const options = parseArgs(process.argv.slice(2))
|
||||
|
||||
if (options.help) {
|
||||
printHelp()
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const preset = options.preset ? PRESETS[options.preset] : null
|
||||
if (options.preset && !preset) {
|
||||
console.error(`未知 preset: ${options.preset}`)
|
||||
printHelp()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const selectors = {
|
||||
provider: options.provider,
|
||||
platform: options.platform,
|
||||
orderIds: normalizeNumberList(options.orderIds),
|
||||
platformOrderIds: uniqueList([...(preset?.platformOrderIds || []), ...options.platformOrderIds]),
|
||||
shopIds: uniqueList([...(preset?.shopIds || []), ...options.shopIds]),
|
||||
shopNames: uniqueList(options.shopNames),
|
||||
}
|
||||
|
||||
if (!hasAnySelector(selectors)) {
|
||||
console.error('至少需要一个筛选条件,或使用 --preset codex-debug')
|
||||
printHelp()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const dbPath = resolveDbPath(options.dbPath)
|
||||
const db = new DatabaseSync(dbPath)
|
||||
db.exec('PRAGMA foreign_keys = ON;')
|
||||
db.exec('PRAGMA busy_timeout = 3000;')
|
||||
|
||||
const matched = collectMatches(db, selectors)
|
||||
|
||||
printSummary(dbPath, selectors, matched, options.apply)
|
||||
|
||||
if (!options.apply) {
|
||||
console.log('\n当前为预览模式。确认后追加 `--apply` 执行删除。')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
db.exec('BEGIN')
|
||||
|
||||
try {
|
||||
deleteByIds(db, 'message_deliveries', matched.messageDeliveryIds)
|
||||
deleteByIds(db, 'webhook_events', matched.webhookEventIds)
|
||||
deleteByIds(db, 'orders', matched.orderIds)
|
||||
db.exec('COMMIT')
|
||||
console.log('\n清理完成。若后台服务正在运行,建议重启一次 backend 容器,确保管理后台视图立即刷新。')
|
||||
} catch (error) {
|
||||
db.exec('ROLLBACK')
|
||||
console.error('\n清理失败:', error instanceof Error ? error.message : String(error || '未知错误'))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
function collectMatches(db, selectors) {
|
||||
const orderWhere = buildOrderWhere(selectors)
|
||||
const orders = db.prepare(`
|
||||
SELECT id, provider, platform, shop_id, shop_name, platform_order_id
|
||||
FROM orders
|
||||
${orderWhere.sql}
|
||||
ORDER BY id DESC
|
||||
`).all(...orderWhere.params)
|
||||
|
||||
const orderIds = orders.map((item) => Number(item.id))
|
||||
const taskIds = orderIds.length > 0
|
||||
? db.prepare(`
|
||||
SELECT id
|
||||
FROM delivery_tasks
|
||||
WHERE order_id IN (${placeholders(orderIds.length)})
|
||||
ORDER BY id DESC
|
||||
`).all(...orderIds).map((item) => Number(item.id))
|
||||
: []
|
||||
|
||||
const webhookWhere = buildWebhookWhere(selectors, orderIds)
|
||||
const webhookEvents = db.prepare(`
|
||||
SELECT id, provider, platform, shop_id, shop_name, event_type, event_key, related_order_id
|
||||
FROM webhook_events
|
||||
${webhookWhere.sql}
|
||||
ORDER BY id DESC
|
||||
`).all(...webhookWhere.params)
|
||||
|
||||
const messageWhere = buildMessageWhere(selectors, orderIds, taskIds)
|
||||
const messageDeliveries = db.prepare(`
|
||||
SELECT id, provider, platform, shop_id, shop_name, platform_order_id, status
|
||||
FROM message_deliveries
|
||||
${messageWhere.sql}
|
||||
ORDER BY id DESC
|
||||
`).all(...messageWhere.params)
|
||||
|
||||
return {
|
||||
orders,
|
||||
orderIds,
|
||||
taskIds,
|
||||
webhookEvents,
|
||||
webhookEventIds: webhookEvents.map((item) => Number(item.id)),
|
||||
messageDeliveries,
|
||||
messageDeliveryIds: messageDeliveries.map((item) => Number(item.id)),
|
||||
}
|
||||
}
|
||||
|
||||
function buildOrderWhere(selectors) {
|
||||
const baseFilters = []
|
||||
const matchFilters = []
|
||||
const params = []
|
||||
const matchParams = []
|
||||
|
||||
if (selectors.provider) {
|
||||
baseFilters.push('provider = ?')
|
||||
params.push(selectors.provider)
|
||||
}
|
||||
|
||||
if (selectors.platform) {
|
||||
baseFilters.push('platform = ?')
|
||||
params.push(selectors.platform)
|
||||
}
|
||||
|
||||
if (selectors.orderIds.length > 0) {
|
||||
matchFilters.push(`id IN (${placeholders(selectors.orderIds.length)})`)
|
||||
matchParams.push(...selectors.orderIds)
|
||||
}
|
||||
|
||||
if (selectors.platformOrderIds.length > 0) {
|
||||
matchFilters.push(`platform_order_id IN (${placeholders(selectors.platformOrderIds.length)})`)
|
||||
matchParams.push(...selectors.platformOrderIds)
|
||||
}
|
||||
|
||||
if (selectors.shopIds.length > 0) {
|
||||
matchFilters.push(`shop_id IN (${placeholders(selectors.shopIds.length)})`)
|
||||
matchParams.push(...selectors.shopIds)
|
||||
}
|
||||
|
||||
if (selectors.shopNames.length > 0) {
|
||||
matchFilters.push(`shop_name IN (${placeholders(selectors.shopNames.length)})`)
|
||||
matchParams.push(...selectors.shopNames)
|
||||
}
|
||||
|
||||
const filters = [...baseFilters]
|
||||
if (matchFilters.length > 0) {
|
||||
filters.push(`(${matchFilters.join(' OR ')})`)
|
||||
params.push(...matchParams)
|
||||
}
|
||||
|
||||
return {
|
||||
sql: filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '',
|
||||
params,
|
||||
}
|
||||
}
|
||||
|
||||
function buildWebhookWhere(selectors, orderIds) {
|
||||
const baseFilters = []
|
||||
const matchFilters = []
|
||||
const params = []
|
||||
const matchParams = []
|
||||
|
||||
if (selectors.provider) {
|
||||
baseFilters.push('provider = ?')
|
||||
params.push(selectors.provider)
|
||||
}
|
||||
|
||||
if (selectors.platform) {
|
||||
baseFilters.push('platform = ?')
|
||||
params.push(selectors.platform)
|
||||
}
|
||||
|
||||
if (orderIds.length > 0) {
|
||||
matchFilters.push(`related_order_id IN (${placeholders(orderIds.length)})`)
|
||||
matchParams.push(...orderIds)
|
||||
}
|
||||
|
||||
if (selectors.shopIds.length > 0) {
|
||||
matchFilters.push(`shop_id IN (${placeholders(selectors.shopIds.length)})`)
|
||||
matchParams.push(...selectors.shopIds)
|
||||
}
|
||||
|
||||
if (selectors.shopNames.length > 0) {
|
||||
matchFilters.push(`shop_name IN (${placeholders(selectors.shopNames.length)})`)
|
||||
matchParams.push(...selectors.shopNames)
|
||||
}
|
||||
|
||||
if (selectors.platformOrderIds.length > 0) {
|
||||
const eventKeyFilters = selectors.platformOrderIds.map(() => 'event_key LIKE ?').join(' OR ')
|
||||
const bodyFilters = selectors.platformOrderIds.map(() => 'body_json LIKE ?').join(' OR ')
|
||||
matchFilters.push(`(${eventKeyFilters})`)
|
||||
matchParams.push(...selectors.platformOrderIds.map((item) => `%${item}%`))
|
||||
matchFilters.push(`(${bodyFilters})`)
|
||||
matchParams.push(...selectors.platformOrderIds.map((item) => `%${item}%`))
|
||||
}
|
||||
|
||||
const filters = [...baseFilters]
|
||||
if (matchFilters.length > 0) {
|
||||
filters.push(`(${matchFilters.join(' OR ')})`)
|
||||
params.push(...matchParams)
|
||||
}
|
||||
|
||||
return {
|
||||
sql: filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '',
|
||||
params,
|
||||
}
|
||||
}
|
||||
|
||||
function buildMessageWhere(selectors, orderIds, taskIds) {
|
||||
const baseFilters = []
|
||||
const matchFilters = []
|
||||
const params = []
|
||||
const matchParams = []
|
||||
|
||||
if (selectors.provider) {
|
||||
baseFilters.push('provider = ?')
|
||||
params.push(selectors.provider)
|
||||
}
|
||||
|
||||
if (selectors.platform) {
|
||||
baseFilters.push('platform = ?')
|
||||
params.push(selectors.platform)
|
||||
}
|
||||
|
||||
if (orderIds.length > 0) {
|
||||
matchFilters.push(`order_id IN (${placeholders(orderIds.length)})`)
|
||||
matchParams.push(...orderIds)
|
||||
}
|
||||
|
||||
if (taskIds.length > 0) {
|
||||
matchFilters.push(`task_id IN (${placeholders(taskIds.length)})`)
|
||||
matchParams.push(...taskIds)
|
||||
}
|
||||
|
||||
if (selectors.shopIds.length > 0) {
|
||||
matchFilters.push(`shop_id IN (${placeholders(selectors.shopIds.length)})`)
|
||||
matchParams.push(...selectors.shopIds)
|
||||
}
|
||||
|
||||
if (selectors.shopNames.length > 0) {
|
||||
matchFilters.push(`shop_name IN (${placeholders(selectors.shopNames.length)})`)
|
||||
matchParams.push(...selectors.shopNames)
|
||||
}
|
||||
|
||||
if (selectors.platformOrderIds.length > 0) {
|
||||
matchFilters.push(`platform_order_id IN (${placeholders(selectors.platformOrderIds.length)})`)
|
||||
matchParams.push(...selectors.platformOrderIds)
|
||||
}
|
||||
|
||||
const filters = [...baseFilters]
|
||||
if (matchFilters.length > 0) {
|
||||
filters.push(`(${matchFilters.join(' OR ')})`)
|
||||
params.push(...matchParams)
|
||||
}
|
||||
|
||||
return {
|
||||
sql: filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '',
|
||||
params,
|
||||
}
|
||||
}
|
||||
|
||||
function deleteByIds(db, tableName, ids) {
|
||||
if (!Array.isArray(ids) || ids.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
db.prepare(`DELETE FROM ${tableName} WHERE id IN (${placeholders(ids.length)})`).run(...ids)
|
||||
}
|
||||
|
||||
function resolveDbPath(overridePath) {
|
||||
const candidates = [
|
||||
String(overridePath || '').trim(),
|
||||
DEFAULT_DB_PATH,
|
||||
].filter(Boolean)
|
||||
|
||||
for (const candidate of candidates) {
|
||||
const resolved = path.resolve(candidate)
|
||||
if (fs.existsSync(resolved)) {
|
||||
return resolved
|
||||
}
|
||||
}
|
||||
|
||||
console.error(`未找到数据库文件。默认查找路径: ${DEFAULT_DB_PATH}`)
|
||||
console.error('可使用 `--db-path <path>` 手动指定。')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
function parseArgs(argv) {
|
||||
const output = {
|
||||
help: false,
|
||||
apply: false,
|
||||
dbPath: '',
|
||||
preset: '',
|
||||
provider: '',
|
||||
platform: '',
|
||||
orderIds: [],
|
||||
platformOrderIds: [],
|
||||
shopIds: [],
|
||||
shopNames: [],
|
||||
}
|
||||
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const current = String(argv[index] || '').trim()
|
||||
|
||||
if (!current) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (current === '--help' || current === '-h') {
|
||||
output.help = true
|
||||
continue
|
||||
}
|
||||
|
||||
if (current === '--apply') {
|
||||
output.apply = true
|
||||
continue
|
||||
}
|
||||
|
||||
if (current === '--db-path') {
|
||||
output.dbPath = String(argv[index + 1] || '').trim()
|
||||
index += 1
|
||||
continue
|
||||
}
|
||||
|
||||
if (current === '--preset') {
|
||||
output.preset = String(argv[index + 1] || '').trim()
|
||||
index += 1
|
||||
continue
|
||||
}
|
||||
|
||||
if (current === '--provider') {
|
||||
output.provider = String(argv[index + 1] || '').trim()
|
||||
index += 1
|
||||
continue
|
||||
}
|
||||
|
||||
if (current === '--platform') {
|
||||
output.platform = String(argv[index + 1] || '').trim()
|
||||
index += 1
|
||||
continue
|
||||
}
|
||||
|
||||
if (current === '--order-id') {
|
||||
output.orderIds.push(String(argv[index + 1] || '').trim())
|
||||
index += 1
|
||||
continue
|
||||
}
|
||||
|
||||
if (current === '--platform-order-id') {
|
||||
output.platformOrderIds.push(String(argv[index + 1] || '').trim())
|
||||
index += 1
|
||||
continue
|
||||
}
|
||||
|
||||
if (current === '--shop-id') {
|
||||
output.shopIds.push(String(argv[index + 1] || '').trim())
|
||||
index += 1
|
||||
continue
|
||||
}
|
||||
|
||||
if (current === '--shop-name') {
|
||||
output.shopNames.push(String(argv[index + 1] || '').trim())
|
||||
index += 1
|
||||
continue
|
||||
}
|
||||
|
||||
console.error(`未知参数: ${current}`)
|
||||
printHelp()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
function printSummary(dbPath, selectors, matched, apply) {
|
||||
console.log(`数据库: ${dbPath}`)
|
||||
console.log(`模式: ${apply ? '执行删除' : '预览'}`)
|
||||
console.log('筛选条件:')
|
||||
console.log(JSON.stringify(selectors, null, 2))
|
||||
|
||||
console.log('\n匹配结果:')
|
||||
console.log(`orders: ${matched.orders.length}`)
|
||||
printRows(matched.orders, ['id', 'provider', 'platform', 'shop_id', 'shop_name', 'platform_order_id'])
|
||||
console.log(`delivery_tasks: ${matched.taskIds.length}`)
|
||||
console.log(`webhook_events: ${matched.webhookEvents.length}`)
|
||||
printRows(matched.webhookEvents, ['id', 'provider', 'platform', 'shop_id', 'shop_name', 'event_type', 'related_order_id'])
|
||||
console.log(`message_deliveries: ${matched.messageDeliveries.length}`)
|
||||
printRows(matched.messageDeliveries, ['id', 'provider', 'platform', 'shop_id', 'shop_name', 'platform_order_id', 'status'])
|
||||
}
|
||||
|
||||
function printRows(rows, keys) {
|
||||
if (!Array.isArray(rows) || rows.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const preview = rows.slice(0, 10).map((row) => Object.fromEntries(keys.map((key) => [key, row[key]])))
|
||||
console.log(JSON.stringify(preview, null, 2))
|
||||
}
|
||||
|
||||
function placeholders(count) {
|
||||
return new Array(count).fill('?').join(', ')
|
||||
}
|
||||
|
||||
function uniqueList(values) {
|
||||
return [...new Set(values.map((item) => String(item || '').trim()).filter(Boolean))]
|
||||
}
|
||||
|
||||
function normalizeNumberList(values) {
|
||||
return uniqueList(values)
|
||||
.map((item) => Number(item))
|
||||
.filter((item) => Number.isInteger(item) && item > 0)
|
||||
}
|
||||
|
||||
function hasAnySelector(selectors) {
|
||||
return Boolean(
|
||||
selectors.provider ||
|
||||
selectors.platform ||
|
||||
selectors.orderIds.length > 0 ||
|
||||
selectors.platformOrderIds.length > 0 ||
|
||||
selectors.shopIds.length > 0 ||
|
||||
selectors.shopNames.length > 0,
|
||||
)
|
||||
}
|
||||
|
||||
function printHelp() {
|
||||
console.log(`
|
||||
用法:
|
||||
node scripts/cleanup-dev-data.js [筛选条件] [--apply]
|
||||
|
||||
常用参数:
|
||||
--platform-order-id <id> 按平台订单号删除,可重复传入
|
||||
--shop-id <id> 按店铺 ID 删除,可重复传入
|
||||
--shop-name <name> 按店铺名删除,可重复传入
|
||||
--order-id <id> 按内部订单 ID 删除,可重复传入
|
||||
--provider <name> 限定 provider,例如 agiso
|
||||
--platform <name> 限定业务平台,例如 xianyu
|
||||
--db-path <path> 手动指定数据库文件
|
||||
--preset codex-debug 清理当前对话里生成的调试样例
|
||||
--apply 真正执行删除;不传时只预览
|
||||
|
||||
示例:
|
||||
npm run cleanup:dev-data -- --platform-order-id DEBUG-ORDER-001
|
||||
npm run cleanup:dev-data -- --shop-id shop-10003 --apply
|
||||
npm run cleanup:dev-data -- --preset codex-debug --apply
|
||||
`)
|
||||
}
|
||||
Reference in New Issue
Block a user