开始多平台 多店铺整改

This commit is contained in:
yml
2026-04-08 22:21:11 +08:00
parent d5b10e3f7f
commit a2b09c89e8
40 changed files with 1750 additions and 685 deletions
+460
View File
@@ -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
`)
}
+1 -2
View File
@@ -2,8 +2,7 @@
set -euo pipefail
BASE_URL="http://127.0.0.1:3000"
CONFIG_FILE="/Users/yml/codes/order-site-backend/config/local.cjs"
APP_SECRET=$(node -p "require('$CONFIG_FILE').platforms.agiso.appSecret")
APP_SECRET=$(cd /Users/yml/codes/order-site-workspace/apps/backend && node --input-type=module -e "import { runtimeConfig } from './src/config/runtime.js'; process.stdout.write(String(runtimeConfig.platforms.agiso.appSecret || ''))")
ORDER_ID="${1:-SIM-ORDER-20260408-0001}"
SKU_ID="32768"