Files
Web_BAI_Manage_ApiServer/script/migrate-product-parameters-to-array.js

283 lines
8.5 KiB
JavaScript
Raw Normal View History

import { createRequire } from 'module';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const require = createRequire(import.meta.url);
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
let runtimeConfig = {};
try {
runtimeConfig = require('../pocket-base/bai_api_pb_hooks/bai_api_shared/config/runtime.js');
} catch (_error) {
runtimeConfig = {};
}
function readEnvFile(filePath) {
if (!fs.existsSync(filePath)) return {};
const content = fs.readFileSync(filePath, 'utf8');
const result = {};
for (const rawLine of content.split(/\r?\n/)) {
const line = rawLine.trim();
if (!line || line.startsWith('#')) continue;
const index = line.indexOf('=');
if (index === -1) continue;
const key = line.slice(0, index).trim();
const value = line.slice(index + 1).trim();
result[key] = value;
}
return result;
}
const backendEnv = readEnvFile(path.resolve(__dirname, '..', 'back-end', '.env'));
const PB_URL = (process.env.PB_URL || backendEnv.POCKETBASE_API_URL || 'https://bai-api.blv-oa.com/pb').replace(/\/+$/, '');
const AUTH_TOKEN = process.env.POCKETBASE_AUTH_TOKEN || runtimeConfig.POCKETBASE_AUTH_TOKEN || '';
const DRY_RUN = String(process.env.DRY_RUN || '').trim() === '1';
if (!AUTH_TOKEN) {
console.error('❌ 缺少认证信息,请提供 POCKETBASE_AUTH_TOKEN。');
process.exit(1);
}
function getUrlCandidates(url) {
const trimmed = String(url || '').replace(/\/+$/, '');
const candidates = [];
if (trimmed) {
candidates.push(trimmed);
}
const withoutPb = trimmed.replace(/\/pb$/i, '');
if (withoutPb && candidates.indexOf(withoutPb) === -1) {
candidates.push(withoutPb);
}
return candidates;
}
function normalizeText(value) {
return String(value || '').trim();
}
function toParameterArray(value) {
if (value === null || typeof value === 'undefined' || value === '') {
return [];
}
let source = value;
if (typeof source === 'string') {
try {
source = JSON.parse(source);
} catch (_error) {
const raw = normalizeText(source);
if (raw.indexOf('map[') === 0 && raw.endsWith(']')) {
const mapped = {};
const body = raw.slice(4, -1).trim();
const pairs = body ? body.split(/\s+/) : [];
for (let i = 0; i < pairs.length; i += 1) {
const pair = pairs[i];
const separatorIndex = pair.indexOf(':');
if (separatorIndex <= 0) continue;
const key = normalizeText(pair.slice(0, separatorIndex));
if (!key) continue;
mapped[key] = pair.slice(separatorIndex + 1);
}
source = mapped;
} else {
return [];
}
}
}
const rows = [];
const indexByName = {};
function upsert(nameValue, rawValue) {
const name = normalizeText(nameValue);
if (!name) return;
const valueText = rawValue === null || typeof rawValue === 'undefined' ? '' : String(rawValue);
const existingIndex = indexByName[name];
if (typeof existingIndex === 'number') {
rows[existingIndex].value = valueText;
return;
}
indexByName[name] = rows.length;
rows.push({ name, value: valueText });
}
if (Array.isArray(source)) {
for (let i = 0; i < source.length; i += 1) {
const item = source[i] && typeof source[i] === 'object' ? source[i] : null;
if (!item) continue;
upsert(item.name || item.key, item.value);
}
return rows;
}
if (typeof source !== 'object') {
return [];
}
try {
source = JSON.parse(JSON.stringify(source));
} catch (_error) {}
const keys = Object.keys(source || {});
for (let i = 0; i < keys.length; i += 1) {
upsert(keys[i], source[keys[i]]);
}
return rows;
}
function isSameArray(left, right) {
return JSON.stringify(left || []) === JSON.stringify(right || []);
}
async function requestPbJson(baseUrl, method, apiPath, token, body) {
const res = await fetch(baseUrl + apiPath, {
method: method,
headers: Object.assign(
{
Authorization: `Bearer ${token}`,
},
body ? { 'Content-Type': 'application/json' } : {},
),
body: body ? JSON.stringify(body) : undefined,
});
const text = await res.text();
let json = {};
try {
json = text ? JSON.parse(text) : {};
} catch (_error) {
json = { raw: text };
}
if (!res.ok) {
const err = new Error(json.message || `HTTP ${res.status}`);
err.status = res.status;
err.response = json;
throw err;
}
return json;
}
async function migrate() {
try {
const urlCandidates = getUrlCandidates(PB_URL);
let activeUrl = '';
let lastError = null;
for (let i = 0; i < urlCandidates.length; i += 1) {
const candidate = urlCandidates[i];
try {
await requestPbJson(
candidate,
'GET',
'/api/collections/tbl_product_list/records?page=1&perPage=1',
AUTH_TOKEN,
);
activeUrl = candidate;
console.log(`✅ 已加载 POCKETBASE_AUTH_TOKEN${candidate})。`);
break;
} catch (error) {
lastError = error;
console.log(`⚠️ 候选地址探测失败(${candidate}:`, {
status: error && error.status,
message: error && error.message,
});
}
}
if (!activeUrl) {
throw lastError || new Error('无法连接 PocketBase 或认证失败');
}
console.log(`🔄 开始迁移 tbl_product_list.prod_list_parametersPB: ${activeUrl}`);
if (DRY_RUN) {
console.log('🧪 当前为 DRY_RUN=1仅预览不会写入数据库。');
}
let page = 1;
const perPage = 200;
let total = 0;
let changed = 0;
let skipped = 0;
let failed = 0;
while (true) {
const list = await requestPbJson(
activeUrl,
'GET',
`/api/collections/tbl_product_list/records?page=${page}&perPage=${perPage}`,
AUTH_TOKEN,
);
const items = Array.isArray(list.items) ? list.items : [];
if (!items.length) {
break;
}
for (let i = 0; i < items.length; i += 1) {
const item = items[i];
total += 1;
try {
const current = item.prod_list_parameters;
const normalized = toParameterArray(current);
if (isSameArray(current, normalized)) {
skipped += 1;
continue;
}
if (!DRY_RUN) {
await requestPbJson(
activeUrl,
'PATCH',
`/api/collections/tbl_product_list/records/${item.id}`,
AUTH_TOKEN,
{ prod_list_parameters: normalized },
);
}
changed += 1;
console.log(`✅ 已迁移: ${item.prod_list_id || item.id}`);
} catch (error) {
failed += 1;
console.error(`❌ 迁移失败: ${item.prod_list_id || item.id}`, error.message || error);
}
}
const totalPages = Number(list.totalPages || 1);
if (page >= totalPages) {
break;
}
page += 1;
}
console.log('\n🎯 迁移完成');
console.log(`- 总记录: ${total}`);
console.log(`- 已变更: ${changed}`);
console.log(`- 跳过不变: ${skipped}`);
console.log(`- 失败: ${failed}`);
} catch (error) {
console.error('❌ 迁移任务执行失败:', {
status: error && error.status,
message: error && error.message,
response: error && error.response,
});
process.exitCode = 1;
}
}
migrate();