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

283 lines
8.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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();