TemplateService.php 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. <?php
  2. namespace App\Services\Template;
  3. use App\Facades\RedisCache;
  4. use App\Services\Template\Renderers\RendererFactory;
  5. use Illuminate\Support\Facades\Log;
  6. class TemplateService
  7. {
  8. private const CACHE_VERSION_KEY = 'template/version';
  9. private TemplateParser $parser;
  10. private bool $cacheEnabled;
  11. private int $cacheTtl;
  12. public function __construct(bool $cacheEnabled = true, int $cacheTtl = 3600)
  13. {
  14. $this->parser = new TemplateParser;
  15. $this->cacheEnabled = $cacheEnabled;
  16. $this->cacheTtl = $cacheTtl;
  17. }
  18. /**
  19. * 解析并渲染内容
  20. */
  21. public function parseAndRender(string $content, string $format = 'json'): array
  22. {
  23. try {
  24. // 生成缓存键
  25. $cacheKey = $this->generateCacheKey($content, $format);
  26. if ($this->cacheEnabled && RedisCache::has($cacheKey)) {
  27. return RedisCache::get($cacheKey);
  28. }
  29. // 解析内容
  30. $document = $this->parser->parse($content);
  31. // 渲染内容
  32. $renderer = RendererFactory::create($format);
  33. $renderedContent = $renderer->render($document);
  34. $result = [
  35. 'data' => $format === 'json' ? json_decode($renderedContent, true) : $renderedContent,
  36. 'meta' => $document->meta,
  37. ];
  38. // 缓存结果
  39. if ($this->cacheEnabled) {
  40. RedisCache::put($cacheKey, $result, $this->cacheTtl);
  41. }
  42. return $result;
  43. } catch (\Exception $e) {
  44. Log::error('Template parsing failed', [
  45. 'content' => substr($content, 0, 200),
  46. 'format' => $format,
  47. 'error' => $e->getMessage(),
  48. ]);
  49. throw $e;
  50. }
  51. }
  52. /**
  53. * 仅解析,不渲染
  54. */
  55. public function parse(string $content): ParsedDocument
  56. {
  57. return $this->parser->parse($content);
  58. }
  59. /**
  60. * 仅渲染已解析的文档
  61. */
  62. public function render(ParsedDocument $document, string $format): string
  63. {
  64. $renderer = RendererFactory::create($format);
  65. return $renderer->render($document);
  66. }
  67. /**
  68. * 注册新模板
  69. */
  70. public function registerTemplate(string $name, array $config): void
  71. {
  72. $registry = $this->parser->getRegistry();
  73. $registry->registerTemplate($name, $config);
  74. // 清除相关缓存
  75. if ($this->cacheEnabled) {
  76. $this->clearTemplateCache($name);
  77. }
  78. }
  79. /**
  80. * 获取可用模板列表
  81. */
  82. public function getAvailableTemplates(): array
  83. {
  84. $registry = $this->parser->getRegistry();
  85. $templates = [];
  86. // 这里需要添加一个方法来获取所有模板
  87. // 为了示例,我们返回一些基本信息
  88. $basicTemplates = ['note', 'info', 'warning'];
  89. foreach ($basicTemplates as $templateName) {
  90. $template = $registry->getTemplate($templateName);
  91. if ($template) {
  92. $templates[$templateName] = [
  93. 'name' => $templateName,
  94. 'config' => $template,
  95. 'example' => $this->generateTemplateExample($templateName, $template),
  96. ];
  97. }
  98. }
  99. return $templates;
  100. }
  101. /**
  102. * 生成模板使用示例
  103. */
  104. private function generateTemplateExample(string $name, array $config): string
  105. {
  106. $params = [];
  107. $defaultParams = $config['defaultParams'] ?? [];
  108. foreach ($defaultParams as $index => $paramName) {
  109. $params[] = $paramName.'=示例值'.($index + 1);
  110. }
  111. return '{{'.$name.'|'.implode('|', $params).'}}';
  112. }
  113. /**
  114. * 生成缓存键。
  115. *
  116. * 键里带版本号:模板定义变了就把版本号加一,旧版本的缓存不再被读到、到期自然消失。
  117. */
  118. private function generateCacheKey(string $content, string $format): string
  119. {
  120. $version = RedisCache::get(self::CACHE_VERSION_KEY, 0);
  121. return "template/v{$version}/{$format}/".md5($content);
  122. }
  123. /**
  124. * 让所有模板渲染缓存失效。
  125. *
  126. * 缓存键是内容的 md5,按模板名查不出受影响的键,所以整体换版本号。
  127. * 原来用的是 Cache::flush(),在 Redis Cluster 上会清空所有 master,
  128. * 连队列和其他业务缓存一起清掉。
  129. */
  130. private function clearTemplateCache(string $templateName): void
  131. {
  132. RedisCache::increment(self::CACHE_VERSION_KEY);
  133. }
  134. /**
  135. * 批量处理内容
  136. */
  137. public function batchProcess(array $contents, string $format = 'json'): array
  138. {
  139. $results = [];
  140. foreach ($contents as $index => $content) {
  141. try {
  142. $results[$index] = $this->parseAndRender($content, $format);
  143. } catch (\Exception $e) {
  144. $results[$index] = [
  145. 'success' => false,
  146. 'error' => $e->getMessage(),
  147. ];
  148. }
  149. }
  150. return $results;
  151. }
  152. /**
  153. * 验证模板语法
  154. */
  155. public function validateSyntax(string $content): array
  156. {
  157. $errors = [];
  158. try {
  159. $document = $this->parser->parse($content);
  160. // 检查是否有未知模板
  161. foreach ($document->content as $node) {
  162. if ($node instanceof TemplateNode) {
  163. $registry = $this->parser->getRegistry();
  164. if (! $registry->hasTemplate($node->name)) {
  165. $errors[] = [
  166. 'type' => 'unknown_template',
  167. 'template' => $node->name,
  168. 'position' => $node->position,
  169. 'message' => "Unknown template: {$node->name}",
  170. ];
  171. }
  172. }
  173. }
  174. } catch (\Exception $e) {
  175. $errors[] = [
  176. 'type' => 'parse_error',
  177. 'message' => $e->getMessage(),
  178. ];
  179. }
  180. return $errors;
  181. }
  182. }