2025-11-17 15:16:53 +08:00

697 lines
20 KiB
JavaScript
Raw 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.

'use strict';
const DEFAULT_OPENAPI_VERSION = '3.0.1';
const DEFAULT_API_VERSION = '1.0.0';
const BASE_PREFIX = 'https://www.dev.ideas.cnpc/api/dms';
const DEFAULT_SERVICE_ID = 'well_kd_wellbore_ideas01';
const DEFAULT_API_SEGMENT = 'v1';
const FALLBACK_BASE_URL = `${BASE_PREFIX}/${DEFAULT_SERVICE_ID}/${DEFAULT_API_SEGMENT}`;
const FALLBACK_HEADERS = {
'Content-Type': 'application/json',
'Authorization': '1',
'Dataregion': 'ZZLH',
};
const SERVICE_DOMAIN_CATALOG = {
'well_kd_wellbore_ideas01': {
name: '井筒',
id: 'well_kd_wellbore_ideas01',
keywords: ['wb', 'wb_dr', 'wb_ml', 'wb_wl', 'wb_tp', 'wb_dh', 'wb_fr'],
},
'geo_kd_res_ideas01': {
name: '采油气',
id: 'geo_kd_res_ideas01',
keywords: ['pc', 'pc_op', 'pc_oe', 'pc_ge'],
},
'kd_cr_ideas01': {
name: '分析化验',
id: 'kd_cr_ideas01',
keywords: ['cr', 'cr_se'],
},
'kd_rs_ideas01': {
name: '油气藏',
id: 'kd_rs_ideas01',
keywords: ['rs', 'rs_rd', 'rs_rm', 'rs_gs', 'rs_in'],
},
};
let schema;
try {
// 优先使用 HTTP In 提供的 req.body.schema缺省时回退到 msg.payload。
const schemaInput = extractSchemaInput(msg);
schema = parseSchema(schemaInput);
} catch (error) {
node.error(`DMS -> Swagger 解析失败:${error.message}`, msg);
msg.error = error.message;
return msg;
}
const resourceTitle = typeof schema.title === 'string' && schema.title.trim()
? schema.title.trim()
: 'DMS Resource';
const resourceName = pascalCase(resourceTitle);
const collectionName = pluralize(kebabCase(resourceTitle));
const identityField = Array.isArray(schema.identityId) && schema.identityId.length > 0
? String(schema.identityId[0])
: 'id';
const schemaComponent = buildComponentSchema(resourceName, schema);
const identitySchema = schemaComponent.properties && schemaComponent.properties[identityField]
? clone(schemaComponent.properties[identityField])
: { type: 'string' };
const crudConfig = Object.assign({}, msg.crudConfig || {});
const dmsMeta = extractDmsMeta(msg);
msg.dms_meta = dmsMeta;
const serviceInfo = resolveServiceInfo(dmsMeta && dmsMeta.domain);
const apiVersionSegment = DEFAULT_API_SEGMENT;
const serviceId = serviceInfo.id;
const { resourceSegment, versionSegment } = deriveResourceSegments(dmsMeta, schema, collectionName);
const listPath = buildListPath(resourceSegment, versionSegment);
const singlePath = buildSinglePath(resourceSegment);
const detailPath = buildDetailPath(resourceSegment, versionSegment, identityField);
const createRequestSchema = buildCreateRequestSchema(resourceName);
const updateRequestSchema = buildCreateRequestSchema(resourceName);
const deleteRequestSchema = buildDeleteRequestSchema(identityField);
const listRequestSchema = buildListRequestSchema();
const listResponseSchema = buildListResponseSchema(resourceName);
const openApiDocument = {
openapi: DEFAULT_OPENAPI_VERSION,
info: {
title: `${resourceTitle} API`,
version: DEFAULT_API_VERSION,
description: schema.description || `${schema.$id || ''}`.trim(),
'x-dms-sourceId': schema.$id || undefined,
},
paths: buildCrudPaths({
resourceName,
identityField,
identitySchema,
listPath,
createPath: singlePath,
detailPath,
createRequestSchema,
updateRequestSchema,
deleteRequestSchema,
listRequestSchema,
listResponseSchema,
}),
components: {
schemas: {
[resourceName]: schemaComponent,
},
},
};
if (!crudConfig.baseUrl) {
crudConfig.baseUrl = `${BASE_PREFIX}/${serviceId}/${apiVersionSegment}`;
}
const headers = Object.assign({}, FALLBACK_HEADERS, crudConfig.headers || {});
const dataRegionValue = extractDataRegion(schema, headers.Dataregion);
if (dataRegionValue) {
headers.Dataregion = dataRegionValue;
crudConfig.dataRegion = dataRegionValue;
}
crudConfig.headers = headers;
crudConfig.identityField = crudConfig.identityField || identityField;
crudConfig.service = crudConfig.service || {
id: serviceId,
name: serviceInfo.name,
};
crudConfig.list = crudConfig.list || {};
if (!crudConfig.list.path) {
crudConfig.list.path = listPath;
}
crudConfig.list.method = crudConfig.list.method || 'POST';
crudConfig.create = crudConfig.create || {};
if (!crudConfig.create.path) {
crudConfig.create.path = singlePath;
}
crudConfig.create.method = crudConfig.create.method || 'POST';
crudConfig.delete = crudConfig.delete || {};
if (!crudConfig.delete.path) {
crudConfig.delete.path = singlePath;
}
crudConfig.delete.method = crudConfig.delete.method || 'POST';
crudConfig.detailPath = crudConfig.detailPath || detailPath;
crudConfig.version = crudConfig.version || versionSegment;
msg.crudConfig = crudConfig;
msg.headers = Object.assign({}, headers);
msg.oas_def = openApiDocument;
msg.payload = openApiDocument;
return msg;
function extractDataRegion(dmsSchema, fallback) {
if (!dmsSchema || typeof dmsSchema !== 'object') {
return fallback;
}
if (typeof dmsSchema.dataRegion === 'string' && dmsSchema.dataRegion.trim()) {
return dmsSchema.dataRegion.trim();
}
if (dmsSchema.defaultShow && Array.isArray(dmsSchema.defaultShow)) {
const candidate = dmsSchema.defaultShow.find(item => item && typeof item === 'string' && item.toLowerCase().includes('dataregion'));
if (candidate) {
return fallback;
}
}
const props = dmsSchema.properties;
if (props && typeof props === 'object' && props.dataRegion) {
if (typeof props.dataRegion.default === 'string' && props.dataRegion.default.trim()) {
return props.dataRegion.default.trim();
}
}
return fallback;
}
function extractSchemaInput(message) {
if (message && message.req && message.req.body && typeof message.req.body === 'object') {
if (message.req.body.dms_schema !== undefined) {
return message.req.body.dms_schema;
}
if (looksLikeDmsSchema(message.req.body)) {
return message.req.body;
}
}
if (message && message.payload !== undefined) {
if (message.payload && typeof message.payload === 'object') {
if (message.payload.dms_schema !== undefined) {
return message.payload.dms_schema;
}
if (looksLikeDmsSchema(message.payload)) {
return message.payload;
}
}
return message.payload;
}
throw new Error('未找到schema请在请求体的schema字段或msg.payload中提供');
}
function parseSchema(source) {
if (typeof source === 'string') {
try {
return JSON.parse(source);
} catch (error) {
throw new Error(`JSON 解析失败:${error.message}`);
}
}
if (!source || typeof source !== 'object') {
throw new Error('schema 必须是 DMS 定义对象或 JSON 字符串');
}
return source;
}
function buildComponentSchema(resourceName, dmsSchema) {
const { properties = {}, required = [], groupView, identityId, naturalKey, defaultShow } = dmsSchema;
const openApiProps = {};
for (const [propName, propSchema] of Object.entries(properties)) {
openApiProps[propName] = mapProperty(propSchema);
}
return {
type: 'object',
required: Array.isArray(required) ? required.slice() : [],
properties: openApiProps,
description: dmsSchema.description || dmsSchema.title || resourceName,
'x-dms-groupView': groupView || undefined,
'x-dms-identityId': identityId || undefined,
'x-dms-naturalKey': naturalKey || undefined,
'x-dms-defaultShow': defaultShow || undefined,
};
}
function mapProperty(propSchema) {
if (!propSchema || typeof propSchema !== 'object') {
return { type: 'string' };
}
const result = {};
const type = normalizeType(propSchema.type);
result.type = type.type;
if (type.format) {
result.format = type.format;
}
if (type.items) {
result.items = type.items;
}
if (propSchema.description) {
result.description = propSchema.description;
} else if (propSchema.title) {
result.description = propSchema.title;
}
if (propSchema.enum) {
result.enum = propSchema.enum.slice();
}
if (propSchema.default !== undefined) {
result.default = propSchema.default;
}
if (propSchema.mask) {
result['x-dms-mask'] = propSchema.mask;
}
if (propSchema.geom) {
result['x-dms-geom'] = propSchema.geom;
}
if (propSchema.title) {
result['x-dms-title'] = propSchema.title;
}
if (propSchema.type) {
result['x-dms-originalType'] = propSchema.type;
}
return result;
}
function normalizeType(typeValue) {
const normalized = typeof typeValue === 'string' ? typeValue.toLowerCase() : undefined;
switch (normalized) {
case 'number':
case 'integer':
case 'long':
case 'float':
case 'double':
return { type: 'number' };
case 'boolean':
return { type: 'boolean' };
case 'array':
return { type: 'array', items: { type: 'string' } };
case 'date':
return { type: 'string', format: 'date' };
case 'date-time':
return { type: 'string', format: 'date-time' };
case 'object':
return { type: 'object' };
case 'string':
default:
return { type: 'string' };
}
}
function buildCrudPaths({
resourceName,
identityField,
identitySchema,
listPath,
createPath,
detailPath,
createRequestSchema,
updateRequestSchema,
deleteRequestSchema,
listRequestSchema,
listResponseSchema,
}) {
const ref = `#/components/schemas/${resourceName}`;
const paths = {};
const normalisedListPath = normalisePath(listPath);
const normalisedCreatePath = normalisePath(createPath);
const normalisedDetailPath = detailPath ? normalisePath(detailPath) : null;
paths[normalisedListPath] = {
post: {
operationId: `list${resourceName}s`,
summary: `List ${resourceName} resources`,
requestBody: {
required: false,
content: {
'application/json': {
schema: listRequestSchema,
},
},
},
responses: {
200: {
description: 'Successful response',
content: {
'application/json': {
schema: listResponseSchema || {
type: 'array',
items: { $ref: ref },
},
},
},
},
},
},
};
paths[normalisedCreatePath] = {
post: {
operationId: `create${resourceName}`,
summary: `Create a ${resourceName}`,
requestBody: {
required: true,
content: {
'application/json': {
schema: createRequestSchema,
},
},
},
responses: {
201: {
description: 'Created',
content: {
'application/json': {
schema: { $ref: ref },
},
},
},
},
},
put: {
operationId: `update${resourceName}`,
summary: `Update a ${resourceName}`,
requestBody: {
required: true,
content: {
'application/json': {
schema: updateRequestSchema,
},
},
},
responses: {
200: {
description: 'Successful update',
content: {
'application/json': {
schema: { $ref: ref },
},
},
},
},
},
delete: {
operationId: `delete${resourceName}`,
summary: `Delete ${resourceName} resources`,
requestBody: {
required: true,
content: {
'application/json': {
schema: deleteRequestSchema,
},
},
},
responses: {
200: { description: 'Deleted' },
},
},
};
if (normalisedDetailPath) {
paths[normalisedDetailPath] = {
parameters: [
{
name: identityField,
in: 'path',
required: true,
schema: identitySchema,
},
],
get: {
operationId: `get${resourceName}`,
summary: `Get a single ${resourceName}`,
responses: {
200: {
description: 'Successful response',
content: {
'application/json': {
schema: { $ref: ref },
},
},
},
404: { description: `${resourceName} not found` },
},
},
};
}
return paths;
}
function normalisePath(path) {
if (!path) {
return '/';
}
return path.startsWith('/') ? path : `/${path}`;
}
function pascalCase(input) {
return input
.replace(/[^a-zA-Z0-9]+/g, ' ')
.split(' ')
.filter(Boolean)
.map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
.join('') || 'Resource';
}
function kebabCase(input) {
return input
.replace(/([a-z])([A-Z])/g, '$1-$2')
.replace(/[^a-zA-Z0-9]+/g, '-')
.replace(/^-+|-+$/g, '')
.toLowerCase() || 'resource';
}
function pluralize(word) {
if (word.endsWith('s')) {
return word;
}
if (word.endsWith('y')) {
return word.slice(0, -1) + 'ies';
}
return `${word}s`;
}
function clone(value) {
return JSON.parse(JSON.stringify(value));
}
function looksLikeDmsSchema(candidate) {
if (!candidate || typeof candidate !== 'object') {
return false;
}
if (candidate.properties && typeof candidate.properties === 'object' && candidate.title) {
return true;
}
if (candidate.$id && candidate.type && candidate.type.toLowerCase && candidate.type.toLowerCase() === 'object') {
return true;
}
return false;
}
function extractDmsMeta(message) {
const candidateReq = message && message.req && message.req.body && message.req.body.dms_meta;
const parsedReq = parseMetaCandidate(candidateReq);
if (parsedReq) {
return parsedReq;
}
const candidateMsg = message && message.dms_meta;
const parsedMsg = parseMetaCandidate(candidateMsg);
if (parsedMsg) {
return parsedMsg;
}
return null;
}
function parseMetaCandidate(candidate) {
if (!candidate) {
return null;
}
if (typeof candidate === 'string') {
try {
const value = JSON.parse(candidate);
return parseMetaCandidate(value);
} catch (err) {
return null;
}
}
if (typeof candidate === 'object') {
return clone(candidate);
}
return null;
}
function resolveServiceInfo(domain) {
if (!domain || typeof domain !== 'string') {
return SERVICE_DOMAIN_CATALOG[DEFAULT_SERVICE_ID];
}
const normalized = domain.toLowerCase();
let best = null;
for (const entry of Object.values(SERVICE_DOMAIN_CATALOG)) {
const matched = entry.keywords.some(keyword => {
if (typeof keyword !== 'string') return false;
const normKeyword = keyword.toLowerCase();
return normalized.includes(normKeyword) || normKeyword.includes(normalized);
});
if (matched) {
best = entry;
break;
}
}
return best || SERVICE_DOMAIN_CATALOG[DEFAULT_SERVICE_ID];
}
function deriveResourceSegments(meta, schema, collectionName) {
const defaultResource = (collectionName || '').replace(/^\//, '');
let resourceSegment = defaultResource;
let versionSegment = null;
if (meta && typeof meta === 'object') {
if (typeof meta.id === 'string' && meta.id.trim()) {
const parts = meta.id.trim().split('.');
if (parts.length > 0 && parts[0]) {
resourceSegment = parts[0];
}
if (parts.length > 1) {
versionSegment = parts.slice(1).join('.');
}
} else if (typeof meta.name === 'string' && meta.name.trim()) {
resourceSegment = meta.name.trim();
}
}
if (!resourceSegment && schema && schema.title) {
resourceSegment = kebabCase(schema.title);
}
return {
resourceSegment: resourceSegment || defaultResource || 'resource',
versionSegment: versionSegment,
};
}
function buildListPath(resourceSegment, versionSegment) {
if (versionSegment) {
return `/${resourceSegment}/${versionSegment}`;
}
return `/${resourceSegment}`;
}
function buildSinglePath(resourceSegment) {
return `/${resourceSegment}`;
}
function buildDetailPath(resourceSegment, versionSegment, identityField) {
if (versionSegment) {
return `/${resourceSegment}/${versionSegment}/{${identityField}}`;
}
return `/${resourceSegment}/{${identityField}}`;
}
function buildCreateRequestSchema(resourceName) {
const ref = `#/components/schemas/${resourceName}`;
return {
type: 'object',
required: ['version', 'act', 'data'],
properties: {
version: { type: 'string', default: '1.0.0' },
act: { type: 'integer', default: -1 },
data: {
type: 'array',
items: { $ref: ref },
},
},
};
}
function buildDeleteRequestSchema(identityField) {
return {
type: 'object',
required: ['version', 'data'],
properties: {
version: { type: 'string', default: '1.0.0' },
data: {
type: 'array',
items: {
type: 'string',
description: `Value of ${identityField}`,
},
},
},
};
}
function buildListRequestSchema() {
return {
type: 'object',
properties: {
version: { type: 'string', default: '1.0.0' },
data: {
type: 'object',
properties: {
pageNo: { type: 'integer', default: 1 },
pageSize: { type: 'integer', default: 20 },
isSearchCount: { type: 'boolean', default: true },
filters: {
type: 'array',
items: { type: 'object' },
},
},
},
},
};
}
function buildListResponseSchema(resourceName) {
const ref = `#/components/schemas/${resourceName}`;
return {
type: 'object',
properties: {
code: { type: 'integer' },
message: { type: 'string' },
data: {
type: 'object',
properties: {
list: {
type: 'array',
items: { $ref: ref },
},
total: { type: 'integer' },
},
},
},
};
}
function looksLikeDmsSchema(candidate) {
if (!candidate || typeof candidate !== 'object') {
return false;
}
if (candidate.properties && typeof candidate.properties === 'object' && candidate.title) {
return true;
}
if (candidate.$id && candidate.type && candidate.type.toLowerCase && candidate.type.toLowerCase() === 'object') {
return true;
}
return false;
}