ziwei-mcp
Version:
紫微斗数 MCP (Ziwei Doushu Model Context Protocol) - 基于 MCP 协议的紫微斗数命理分析工具
800 lines (693 loc) • 22.3 kB
JavaScript
/**
* SVG模板管理器
* 负责模板的加载、缓存、动态替换和优化
*/
class SVGTemplateManager {
constructor() {
this.templates = new Map();
this.compiledTemplates = new Map();
this.templateCache = new Map();
this.variableRegistry = new Map();
// 性能配置
this.config = {
enableCache: true,
maxCacheSize: 100,
enableCompression: false,
enableMinification: true,
};
// 初始化内置模板
this.initializeBuiltinTemplates();
}
/**
* 初始化内置模板
*/
initializeBuiltinTemplates() {
// 传统命盘模板
this.registerTemplate("traditional_chart", {
name: "传统命盘",
category: "chart",
variables: [
"width",
"height",
"viewBox",
"theme.*",
"structure.*",
"palaceDividers",
"palaceContents",
"centerContent",
"decorations",
],
template: `
<svg width="{{width}}" height="{{height}}" viewBox="{{viewBox}}"
xmlns="http://www.w3.org/2000/svg" class="ziwei-chart traditional">
<!-- 定义区域 -->
<defs>
{{#each gradients}}
<linearGradient id="{{id}}" {{attributes}}>
{{#each stops}}
<stop offset="{{offset}}" stop-color="{{color}}" stop-opacity="{{opacity}}"/>
{{/each}}
</linearGradient>
{{/each}}
{{#each patterns}}
<pattern id="{{id}}" {{attributes}}>
{{content}}
</pattern>
{{/each}}
{{#each filters}}
<filter id="{{id}}" {{attributes}}>
{{content}}
</filter>
{{/each}}
</defs>
<!-- 背景层 -->
<g class="background-layer">
<rect width="100%" height="100%" fill="{{theme.background}}" class="chart-background"/>
{{#if theme.backgroundPattern}}
<rect width="100%" height="100%" fill="url(#{{theme.backgroundPattern}})" opacity="0.1"/>
{{/if}}
</g>
<!-- 结构层 -->
<g class="structure-layer">
<!-- 外圈 -->
<circle cx="{{structure.center.x}}" cy="{{structure.center.y}}"
r="{{structure.outerRadius}}" fill="none"
stroke="{{theme.borderColor}}" stroke-width="{{theme.borderWidth}}"
class="outer-circle"/>
<!-- 内圈 -->
<circle cx="{{structure.center.x}}" cy="{{structure.center.y}}"
r="{{structure.innerRadius}}" fill="none"
stroke="{{theme.borderColor}}" stroke-width="{{theme.borderWidth}}"
class="inner-circle"/>
<!-- 宫位分割线 -->
{{palaceDividers}}
</g>
<!-- 内容层 -->
<g class="content-layer">
{{palaceContents}}
</g>
<!-- 中心层 -->
<g class="center-layer">
{{centerContent}}
</g>
<!-- 装饰层 -->
<g class="decoration-layer">
{{decorations}}
</g>
<!-- 标签层 -->
<g class="label-layer">
{{labels}}
</g>
</svg>
`,
partials: {
palaceDivider: `
<line x1="{{x1}}" y1="{{y1}}" x2="{{x2}}" y2="{{y2}}"
stroke="{{color}}" stroke-width="{{width}}" class="palace-divider"/>
`,
palaceContent: `
<g class="palace palace-{{id}}" transform="translate({{x}}, {{y}})">
<!-- 宫位背景 -->
<rect x="{{bounds.x}}" y="{{bounds.y}}" width="{{bounds.width}}" height="{{bounds.height}}"
fill="{{backgroundColor}}" stroke="{{borderColor}}" stroke-width="1"
rx="{{borderRadius}}" class="palace-background"/>
<!-- 宫位名称 -->
<text x="{{namePosition.x}}" y="{{namePosition.y}}"
font-family="{{theme.fontFamily}}" font-size="{{theme.palaceNameSize}}"
fill="{{theme.textColor}}" text-anchor="middle" class="palace-name">
{{name}}
</text>
<!-- 星曜列表 -->
<g class="stars" transform="translate({{starsPosition.x}}, {{starsPosition.y}})">
{{#each stars}}
{{> starItem}}
{{/each}}
</g>
<!-- 干支 -->
{{#if showGanZhi}}
<text x="{{ganZhiPosition.x}}" y="{{ganZhiPosition.y}}"
font-family="{{theme.fontFamily}}" font-size="{{theme.ganZhiSize}}"
fill="{{theme.secondaryTextColor}}" class="gan-zhi">
{{heavenlyStem}}{{earthlyBranch}}
</text>
{{/if}}
</g>
`,
starItem: `
<g class="star star-{{type}}" transform="translate({{x}}, {{y}})">
<!-- 星曜背景 -->
{{#if showBackground}}
<rect x="-{{width/2}}" y="-{{height/2}}" width="{{width}}" height="{{height}}"
fill="{{backgroundColor}}" stroke="{{borderColor}}" stroke-width="0.5"
rx="2" class="star-background"/>
{{/if}}
<!-- 星曜名称 -->
<text x="0" y="{{textOffset}}"
font-family="{{theme.fontFamily}}" font-size="{{fontSize}}"
fill="{{textColor}}" text-anchor="middle" class="star-name">
{{name}}
</text>
<!-- 亮度标记 -->
{{#if brightness}}
<text x="{{brightnessOffset.x}}" y="{{brightnessOffset.y}}"
font-family="{{theme.fontFamily}}" font-size="{{brightnessSize}}"
fill="{{brightnessColor}}" class="brightness">
{{brightness}}
</text>
{{/if}}
<!-- 四化标记 -->
{{#if sihua}}
<circle cx="{{sihuaOffset.x}}" cy="{{sihuaOffset.y}}" r="3"
fill="{{sihuaColor}}" class="sihua-marker"/>
{{/if}}
</g>
`,
},
});
// 现代轮盘模板
this.registerTemplate("modern_wheel", {
name: "现代轮盘",
category: "chart",
variables: [
"width",
"height",
"viewBox",
"theme.*",
"structure.*",
"palaceSectors",
"palaceContents",
"centerContent",
"decorations",
],
template: `
<svg width="{{width}}" height="{{height}}" viewBox="{{viewBox}}"
xmlns="http://www.w3.org/2000/svg" class="ziwei-chart modern-wheel">
<defs>
{{> commonDefs}}
<!-- 扇形渐变 -->
{{#each sectorGradients}}
<radialGradient id="{{id}}" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="{{innerColor}}" stop-opacity="{{innerOpacity}}"/>
<stop offset="100%" stop-color="{{outerColor}}" stop-opacity="{{outerOpacity}}"/>
</radialGradient>
{{/each}}
</defs>
<!-- 背景 -->
<rect width="100%" height="100%" fill="url(#backgroundGradient)" class="chart-background"/>
<!-- 宫位扇形 -->
<g class="palace-sectors">
{{palaceSectors}}
</g>
<!-- 宫位内容 -->
<g class="palace-contents">
{{palaceContents}}
</g>
<!-- 中心圆 -->
<g class="center-area">
<circle cx="{{structure.center.x}}" cy="{{structure.center.y}}"
r="{{structure.centerRadius}}"
fill="url(#centerGradient)" stroke="{{theme.borderColor}}"
stroke-width="{{theme.borderWidth}}" class="center-circle"/>
{{centerContent}}
</g>
<!-- 装饰元素 -->
<g class="decorations">
{{decorations}}
</g>
</svg>
`,
partials: {
palaceSector: `
<path d="{{pathData}}" fill="url(#sector-{{id}})" stroke="{{theme.borderColor}}"
stroke-width="1" class="palace-sector palace-{{id}}"
data-palace="{{id}}" data-name="{{name}}"/>
`,
},
});
// 网格布局模板
this.registerTemplate("grid_layout", {
name: "网格布局",
category: "chart",
variables: [
"width",
"height",
"viewBox",
"theme.*",
"grid.*",
"gridCells",
"palaceContents",
"connections",
"annotations",
],
template: `
<svg width="{{width}}" height="{{height}}" viewBox="{{viewBox}}"
xmlns="http://www.w3.org/2000/svg" class="ziwei-chart grid-layout">
<defs>
{{> commonDefs}}
</defs>
<!-- 背景 -->
<rect width="100%" height="100%" fill="{{theme.background}}" class="chart-background"/>
<!-- 网格线 -->
<g class="grid-lines">
{{#each gridLines}}
<line x1="{{x1}}" y1="{{y1}}" x2="{{x2}}" y2="{{y2}}"
stroke="{{color}}" stroke-width="{{width}}" stroke-dasharray="{{dashArray}}"
class="grid-line"/>
{{/each}}
</g>
<!-- 网格单元格 -->
<g class="grid-cells">
{{gridCells}}
</g>
<!-- 宫位内容 -->
<g class="palace-contents">
{{palaceContents}}
</g>
<!-- 连接线 -->
<g class="connections">
{{connections}}
</g>
<!-- 注释 -->
<g class="annotations">
{{annotations}}
</g>
</svg>
`,
partials: {
gridCell: `
<g class="grid-cell" transform="translate({{x}}, {{y}})">
<rect width="{{width}}" height="{{height}}"
fill="{{backgroundColor}}" stroke="{{borderColor}}" stroke-width="1"
rx="{{borderRadius}}" class="cell-background"/>
</g>
`,
},
});
// 通用部分模板
this.registerPartial(
"commonDefs",
`
<!-- 通用渐变 -->
<linearGradient id="backgroundGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="{{theme.background}}"/>
<stop offset="100%" stop-color="{{theme.backgroundSecondary}}"/>
</linearGradient>
<radialGradient id="centerGradient" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="{{theme.centerFill}}"/>
<stop offset="100%" stop-color="{{theme.centerBorder}}"/>
</radialGradient>
<!-- 通用滤镜 -->
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="2" dy="2" stdDeviation="3" flood-opacity="0.3"/>
</filter>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="3" result="coloredBlur"/>
<feMerge>
<feMergeNode in="coloredBlur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
`,
);
}
/**
* 注册模板
*/
registerTemplate(id, templateConfig) {
const template = {
id: id,
...templateConfig,
registeredAt: new Date().toISOString(),
compiled: null,
};
this.templates.set(id, template);
// 注册变量
if (templateConfig.variables) {
this.registerVariables(id, templateConfig.variables);
}
console.log(`模板已注册: ${id}`);
}
/**
* 注册部分模板
*/
registerPartial(name, template) {
if (!this.partials) {
this.partials = new Map();
}
this.partials.set(name, template);
}
/**
* 注册变量
*/
registerVariables(templateId, variables) {
this.variableRegistry.set(templateId, variables);
}
/**
* 编译模板
*/
compileTemplate(templateId) {
const template = this.templates.get(templateId);
if (!template) {
throw new Error(`模板不存在: ${templateId}`);
}
if (template.compiled) {
return template.compiled;
}
try {
// 预处理模板
let processedTemplate = this.preprocessTemplate(template.template);
// 编译部分模板
if (template.partials) {
processedTemplate = this.compilePartials(
processedTemplate,
template.partials,
);
}
// 优化模板
if (this.config.enableMinification) {
processedTemplate = this.minifyTemplate(processedTemplate);
}
// 创建编译后的模板函数
const compiled = this.createTemplateFunction(processedTemplate);
template.compiled = compiled;
this.compiledTemplates.set(templateId, compiled);
console.log(`模板编译完成: ${templateId}`);
return compiled;
} catch (error) {
console.error(`模板编译失败: ${templateId}`, error);
throw error;
}
}
/**
* 预处理模板
*/
preprocessTemplate(template) {
// 移除多余的空白
let processed = template.replace(/\s+/g, " ").trim();
// 处理条件语句
processed = this.processConditionals(processed);
// 处理循环语句
processed = this.processLoops(processed);
return processed;
}
/**
* 处理条件语句
*/
processConditionals(template) {
// 处理 {{#if condition}} ... {{/if}} 语法
return template.replace(
/\{\{#if\s+([^}]+)\}\}([\s\S]*?)\{\{\/#if\}\}/g,
(match, condition, content) => {
return `{{#if:${condition}}}${content}{{/if:${condition}}}`;
},
);
}
/**
* 处理循环语句
*/
processLoops(template) {
// 处理 {{#each array}} ... {{/each}} 语法
return template.replace(
/\{\{#each\s+([^}]+)\}\}([\s\S]*?)\{\{\/#each\}\}/g,
(match, array, content) => {
return `{{#each:${array}}}${content}{{/each:${array}}}`;
},
);
}
/**
* 编译部分模板
*/
compilePartials(template, partials) {
let compiled = template;
// 替换部分模板引用
Object.keys(partials).forEach((partialName) => {
const partialRegex = new RegExp(`{{>\\s*${partialName}\\s*}}`, "g");
compiled = compiled.replace(partialRegex, partials[partialName]);
});
// 处理全局部分模板
if (this.partials) {
this.partials.forEach((partialTemplate, partialName) => {
const partialRegex = new RegExp(`{{>\\s*${partialName}\\s*}}`, "g");
compiled = compiled.replace(partialRegex, partialTemplate);
});
}
return compiled;
}
/**
* 压缩模板
*/
minifyTemplate(template) {
return template
.replace(/\s+/g, " ") // 合并空白
.replace(/> </g, "><") // 移除标签间空白
.replace(/\s*([<>])\s*/g, "$1") // 移除标签周围空白
.trim();
}
/**
* 创建模板函数
*/
createTemplateFunction(template) {
return (data) => {
return this.renderTemplate(template, data);
};
}
/**
* 渲染模板
*/
renderTemplate(template, data) {
let rendered = template;
// 处理简单变量替换
rendered = this.replaceSimpleVariables(rendered, data);
// 处理条件语句
rendered = this.renderConditionals(rendered, data);
// 处理循环语句
rendered = this.renderLoops(rendered, data);
// 处理嵌套对象
rendered = this.replaceNestedVariables(rendered, data);
return rendered;
}
/**
* 替换简单变量
*/
replaceSimpleVariables(template, data) {
return template.replace(/\{\{([^#\/][^}]*)\}\}/g, (match, variable) => {
const value = this.getNestedValue(data, variable.trim());
return value !== undefined ? value : match;
});
}
/**
* 渲染条件语句
*/
renderConditionals(template, data) {
return template.replace(
/\{\{#if:([^}]+)\}\}([\s\S]*?)\{\{\/#if:\1\}\}/g,
(match, condition, content) => {
const value = this.getNestedValue(data, condition.trim());
return value ? content : "";
},
);
}
/**
* 渲染循环语句
*/
renderLoops(template, data) {
return template.replace(
/\{\{#each:([^}]+)\}\}([\s\S]*?)\{\{\/each:\1\}\}/g,
(match, arrayPath, content) => {
const array = this.getNestedValue(data, arrayPath.trim());
if (!Array.isArray(array)) return "";
return array
.map((item, index) => {
let itemContent = content;
// 替换循环变量
itemContent = itemContent.replace(/\{\{this\}\}/g, item);
itemContent = itemContent.replace(/\{\{@index\}\}/g, index);
// 如果item是对象,替换其属性
if (typeof item === "object") {
Object.keys(item).forEach((key) => {
const regex = new RegExp(`\\{\\{${key}\\}\\}`, "g");
itemContent = itemContent.replace(regex, item[key]);
});
}
return itemContent;
})
.join("");
},
);
}
/**
* 替换嵌套变量
*/
replaceNestedVariables(template, data) {
// 处理 theme.* 等嵌套变量
return template.replace(/{{([^}]+\.[^}]+)}}/g, (match, variable) => {
const value = this.getNestedValue(data, variable.trim());
return value !== undefined ? value : match;
});
}
/**
* 获取嵌套值
*/
getNestedValue(obj, path) {
return path.split(".").reduce((current, key) => {
return current && current[key] !== undefined ? current[key] : undefined;
}, obj);
}
/**
* 渲染模板(主入口)
*/
render(templateId, data, options = {}) {
const cacheKey = this.generateCacheKey(templateId, data, options);
// 检查缓存
if (this.config.enableCache && this.templateCache.has(cacheKey)) {
return this.templateCache.get(cacheKey);
}
try {
// 编译模板
const compiledTemplate = this.compileTemplate(templateId);
// 渲染
const result = compiledTemplate(data);
// 缓存结果
if (this.config.enableCache) {
this.cacheResult(cacheKey, result);
}
return result;
} catch (error) {
console.error(`模板渲染失败: ${templateId}`, error);
throw error;
}
}
/**
* 生成缓存键
*/
generateCacheKey(templateId, data, options) {
const dataHash = this.hashObject(data);
const optionsHash = this.hashObject(options);
return `${templateId}:${dataHash}:${optionsHash}`;
}
/**
* 对象哈希
*/
hashObject(obj) {
const str = JSON.stringify(obj, Object.keys(obj).sort());
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash = hash & hash; // 转换为32位整数
}
return hash.toString(36);
}
/**
* 缓存结果
*/
cacheResult(key, result) {
// 检查缓存大小限制
if (this.templateCache.size >= this.config.maxCacheSize) {
// 删除最旧的缓存项
const firstKey = this.templateCache.keys().next().value;
this.templateCache.delete(firstKey);
}
this.templateCache.set(key, result);
}
/**
* 清理缓存
*/
clearCache() {
this.templateCache.clear();
console.log("模板缓存已清理");
}
/**
* 获取模板信息
*/
getTemplateInfo(templateId) {
const template = this.templates.get(templateId);
if (!template) return null;
return {
id: template.id,
name: template.name,
category: template.category,
variables: this.variableRegistry.get(templateId) || [],
registeredAt: template.registeredAt,
compiled: !!template.compiled,
};
}
/**
* 获取所有模板
*/
getAllTemplates() {
const templates = [];
this.templates.forEach((template, id) => {
templates.push(this.getTemplateInfo(id));
});
return templates;
}
/**
* 删除模板
*/
removeTemplate(templateId) {
const removed = this.templates.delete(templateId);
this.compiledTemplates.delete(templateId);
this.variableRegistry.delete(templateId);
// 清理相关缓存
const keysToDelete = [];
this.templateCache.forEach((value, key) => {
if (key.startsWith(templateId + ":")) {
keysToDelete.push(key);
}
});
keysToDelete.forEach((key) => this.templateCache.delete(key));
return removed;
}
/**
* 验证模板
*/
validateTemplate(templateId, data) {
const variables = this.variableRegistry.get(templateId);
if (!variables) return { valid: true };
const missing = [];
const invalid = [];
variables.forEach((variable) => {
if (variable.endsWith("*")) {
// 通配符变量,检查对象是否存在
const basePath = variable.slice(0, -2);
const value = this.getNestedValue(data, basePath);
if (!value || typeof value !== "object") {
missing.push(basePath);
}
} else {
const value = this.getNestedValue(data, variable);
if (value === undefined) {
missing.push(variable);
}
}
});
return {
valid: missing.length === 0 && invalid.length === 0,
missing: missing,
invalid: invalid,
};
}
/**
* 获取统计信息
*/
getStats() {
return {
totalTemplates: this.templates.size,
compiledTemplates: this.compiledTemplates.size,
cacheSize: this.templateCache.size,
maxCacheSize: this.config.maxCacheSize,
cacheHitRate: this.calculateCacheHitRate(),
};
}
/**
* 计算缓存命中率
*/
calculateCacheHitRate() {
// 这里需要实际的统计数据
return 0.85; // 示例值
}
}
module.exports = {
SVGTemplateManager,
};