196 lines
6.4 KiB
JavaScript
196 lines
6.4 KiB
JavaScript
|
|
import { createRequire } from 'module';
|
|||
|
|
import PocketBase from 'pocketbase';
|
|||
|
|
|
|||
|
|
const require = createRequire(import.meta.url);
|
|||
|
|
|
|||
|
|
let runtimeConfig = {};
|
|||
|
|
try {
|
|||
|
|
runtimeConfig = require('../pocket-base/bai_api_pb_hooks/bai_api_shared/config/runtime.js');
|
|||
|
|
} catch (_error) {
|
|||
|
|
runtimeConfig = {};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const PB_URL = (process.env.PB_URL || 'https://bai-api.blv-oa.com/pb').replace(/\/+$/, '');
|
|||
|
|
const AUTH_TOKEN = process.env.POCKETBASE_AUTH_TOKEN || runtimeConfig.POCKETBASE_AUTH_TOKEN || '';
|
|||
|
|
|
|||
|
|
if (!AUTH_TOKEN) {
|
|||
|
|
console.error('❌ 缺少 POCKETBASE_AUTH_TOKEN,无法执行字典建表。');
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const pb = new PocketBase(PB_URL);
|
|||
|
|
|
|||
|
|
const collectionData = {
|
|||
|
|
name: 'tbl_system_dict',
|
|||
|
|
type: 'base',
|
|||
|
|
fields: [
|
|||
|
|
{ name: 'system_dict_id', type: 'text', required: true },
|
|||
|
|
{ name: 'dict_name', type: 'text', required: true },
|
|||
|
|
{ name: 'dict_word_enum', type: 'text' },
|
|||
|
|
{ name: 'dict_word_description', type: 'text' },
|
|||
|
|
{ name: 'dict_word_image', type: 'text' },
|
|||
|
|
{ name: 'dict_word_is_enabled', type: 'bool' },
|
|||
|
|
{ name: 'dict_word_sort_order', type: 'text' },
|
|||
|
|
{ name: 'dict_word_parent_id', type: 'text' },
|
|||
|
|
{ name: 'dict_word_remark', type: 'text' },
|
|||
|
|
],
|
|||
|
|
indexes: [
|
|||
|
|
'CREATE UNIQUE INDEX idx_system_dict_id ON tbl_system_dict (system_dict_id)',
|
|||
|
|
'CREATE UNIQUE INDEX idx_dict_name ON tbl_system_dict (dict_name)',
|
|||
|
|
'CREATE INDEX idx_dict_word_parent_id ON tbl_system_dict (dict_word_parent_id)',
|
|||
|
|
],
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
function normalizeFieldPayload(field, existingField) {
|
|||
|
|
const payload = existingField
|
|||
|
|
? Object.assign({}, existingField)
|
|||
|
|
: {
|
|||
|
|
name: field.name,
|
|||
|
|
type: field.type,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
if (existingField && existingField.id) {
|
|||
|
|
payload.id = existingField.id;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
payload.name = field.name;
|
|||
|
|
payload.type = field.type;
|
|||
|
|
|
|||
|
|
if (typeof field.required !== 'undefined') {
|
|||
|
|
payload.required = field.required;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return payload;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function buildCollectionPayload(target, existingCollection) {
|
|||
|
|
if (!existingCollection) {
|
|||
|
|
return {
|
|||
|
|
name: target.name,
|
|||
|
|
type: target.type,
|
|||
|
|
fields: target.fields.map((field) => normalizeFieldPayload(field, null)),
|
|||
|
|
indexes: target.indexes,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const targetFieldMap = new Map(target.fields.map((field) => [field.name, field]));
|
|||
|
|
const fields = (existingCollection.fields || []).map((existingField) => {
|
|||
|
|
const nextField = targetFieldMap.get(existingField.name);
|
|||
|
|
if (!nextField) {
|
|||
|
|
return existingField;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
targetFieldMap.delete(existingField.name);
|
|||
|
|
return normalizeFieldPayload(nextField, existingField);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
for (const field of targetFieldMap.values()) {
|
|||
|
|
fields.push(normalizeFieldPayload(field, null));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
name: target.name,
|
|||
|
|
type: target.type,
|
|||
|
|
fields: fields,
|
|||
|
|
indexes: target.indexes,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function normalizeFieldList(fields) {
|
|||
|
|
return (fields || []).map((field) => ({
|
|||
|
|
name: field.name,
|
|||
|
|
type: field.type,
|
|||
|
|
required: !!field.required,
|
|||
|
|
}));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function createOrUpdateCollection(target) {
|
|||
|
|
console.log(`🔄 正在处理表: ${target.name} ...`);
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const existing = await pb.collections.getOne(target.name);
|
|||
|
|
await pb.collections.update(existing.id, buildCollectionPayload(target, existing));
|
|||
|
|
console.log(`♻️ ${target.name} 已存在,已按最新结构更新。`);
|
|||
|
|
} catch (error) {
|
|||
|
|
if (error.status === 404) {
|
|||
|
|
await pb.collections.create(buildCollectionPayload(target, null));
|
|||
|
|
console.log(`✅ ${target.name} 创建完成。`);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.error(`❌ 处理集合 ${target.name} 失败:`, {
|
|||
|
|
status: error.status,
|
|||
|
|
message: error.message,
|
|||
|
|
response: error.response?.data,
|
|||
|
|
});
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function verifyCollection(target) {
|
|||
|
|
console.log('\n🔍 开始校验字典表结构与索引...');
|
|||
|
|
const remote = await pb.collections.getOne(target.name);
|
|||
|
|
const remoteFields = normalizeFieldList(remote.fields);
|
|||
|
|
const targetFields = normalizeFieldList(target.fields);
|
|||
|
|
const remoteFieldMap = new Map(remoteFields.map((field) => [field.name, field.type]));
|
|||
|
|
const remoteRequiredMap = new Map(remoteFields.map((field) => [field.name, field.required]));
|
|||
|
|
const missingFields = [];
|
|||
|
|
const mismatchedTypes = [];
|
|||
|
|
const mismatchedRequired = [];
|
|||
|
|
|
|||
|
|
for (const field of targetFields) {
|
|||
|
|
if (!remoteFieldMap.has(field.name)) {
|
|||
|
|
missingFields.push(field.name);
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (remoteFieldMap.get(field.name) !== field.type) {
|
|||
|
|
mismatchedTypes.push(`${field.name}:${remoteFieldMap.get(field.name)}!=${field.type}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (remoteRequiredMap.get(field.name) !== !!field.required) {
|
|||
|
|
mismatchedRequired.push(`${field.name}:${remoteRequiredMap.get(field.name)}!=${!!field.required}`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const remoteIndexes = new Set(remote.indexes || []);
|
|||
|
|
const missingIndexes = target.indexes.filter((indexSql) => !remoteIndexes.has(indexSql));
|
|||
|
|
|
|||
|
|
if (!missingFields.length && !mismatchedTypes.length && !mismatchedRequired.length && !missingIndexes.length) {
|
|||
|
|
console.log(`✅ ${target.name} 校验通过。`);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (missingFields.length) {
|
|||
|
|
console.log(` - 缺失字段: ${missingFields.join(', ')}`);
|
|||
|
|
}
|
|||
|
|
if (mismatchedTypes.length) {
|
|||
|
|
console.log(` - 字段类型不匹配: ${mismatchedTypes.join(', ')}`);
|
|||
|
|
}
|
|||
|
|
if (mismatchedRequired.length) {
|
|||
|
|
console.log(` - 字段必填属性不匹配: ${mismatchedRequired.join(', ')}`);
|
|||
|
|
}
|
|||
|
|
if (missingIndexes.length) {
|
|||
|
|
console.log(` - 缺失索引: ${missingIndexes.join(' | ')}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
throw new Error(`${target.name} 结构与预期不一致`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function init() {
|
|||
|
|
try {
|
|||
|
|
console.log(`🔄 正在连接 PocketBase: ${PB_URL}`);
|
|||
|
|
pb.authStore.save(AUTH_TOKEN, null);
|
|||
|
|
console.log('✅ 已使用 POCKETBASE_AUTH_TOKEN 载入认证状态。');
|
|||
|
|
|
|||
|
|
await createOrUpdateCollection(collectionData);
|
|||
|
|
await verifyCollection(collectionData);
|
|||
|
|
console.log('\n🎉 字典表结构初始化并校验完成!');
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('❌ 初始化失败:', error.response?.data || error.message);
|
|||
|
|
process.exitCode = 1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
init();
|