| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179 |
- from __future__ import annotations
- import json
- from pathlib import Path
- from typing import Dict, List, Optional
- from fastapi import HTTPException
- from ..schemas import TemplateCreate, TemplateRecord, TemplateUpdate
- class TemplateStore:
- """Simple JSON-file based store for template definitions."""
- def __init__(self, data_path: Path):
- self.data_path = data_path
- self.data_path.parent.mkdir(parents=True, exist_ok=True)
- if not self.data_path.exists():
- self.data_path.write_text(json.dumps([], ensure_ascii=False, indent=2))
- self._cache: Dict[str, TemplateRecord] = {}
- self._load()
- def _load(self) -> None:
- raw = json.loads(self.data_path.read_text(encoding='utf-8'))
- self._cache = {item["id"]: TemplateRecord(**item) for item in raw}
- def _persist(self) -> None:
- # Use JSON mode so datetime fields are converted to ISO strings.
- serializable = [record.model_dump(mode="json") for record in self._cache.values()]
- self.data_path.write_text(
- json.dumps(serializable, ensure_ascii=False, indent=2),
- encoding='utf-8'
- )
- def list_templates(self, machine_model: str = None, device_type: str = None, template_type: str = None) -> List[TemplateRecord]:
- templates = list(self._cache.values())
- if machine_model:
- templates = [t for t in templates if t.machine_model == machine_model]
- if device_type:
- templates = [t for t in templates if t.device_type == device_type]
- if template_type:
- templates = [t for t in templates if t.template_type == template_type]
- return templates
- def get_template(self, template_id: str) -> TemplateRecord:
- try:
- record = self._cache[template_id]
- # 如果是复用模板,返回源模板的内容(但保留当前模板的元数据)
- if record.reusable_from:
- source = self._cache.get(record.reusable_from)
- if source:
- # 返回源模板,但保留当前模板的元数据
- merged = source.model_copy()
- merged.id = record.id
- merged.name = record.name
- merged.category = record.category
- merged.device_type = record.device_type
- merged.machine_model = record.machine_model
- merged.template_type = record.template_type
- merged.type_id = record.type_id
- merged.zone_ids = record.zone_ids
- merged.card_type = record.card_type
- merged.reusable_from = record.reusable_from
- merged.reuse_targets = record.reuse_targets
- merged.description = record.description
- merged.created_at = record.created_at
- merged.updated_at = record.updated_at
- return merged
- return record
- except KeyError as exc:
- raise HTTPException(status_code=404, detail="模板不存在") from exc
- def create_template(self, payload: TemplateCreate) -> TemplateRecord:
- record = TemplateRecord(**payload.model_dump())
- self._cache[record.id] = record
- self._persist()
- return record
- def update_template(self, template_id: str, payload: TemplateUpdate) -> TemplateRecord:
- record = self.get_template(template_id)
- update_data = payload.model_dump(exclude_none=True)
- updated = record.model_copy(update=update_data)
- from datetime import datetime
- updated.updated_at = datetime.utcnow()
- self._cache[template_id] = updated
- self._persist()
- return updated
- def delete_template(self, template_id: str) -> None:
- if template_id not in self._cache:
- raise HTTPException(status_code=404, detail="模板不存在")
- record = self._cache[template_id]
- # 如果该模板被其他模板复用,禁止删除
- if record.reuse_targets:
- raise HTTPException(status_code=400, detail=f"该模板被 {len(record.reuse_targets)} 个模板复用,无法删除")
- # 如果这是复用模板,需要从源模板的 reuse_targets 中移除
- if record.reusable_from and record.reusable_from in self._cache:
- source = self._cache[record.reusable_from]
- if template_id in source.reuse_targets:
- source.reuse_targets.remove(template_id)
- del self._cache[template_id]
- self._persist()
- def generate_redis_keys(self, record: TemplateRecord) -> dict:
- """根据模板信息自动生成 Redis 一级 key 列表"""
- if not record.type_id or not record.zone_ids:
- return {"second": record.redis_key_second or "", "first": []}
- # 二级 key
- card = record.card_type or "single_card"
- second_key = f"broadcast-template-jinja:{card}:{record.device_type}:{record.template_type}"
- # 模板类型到 card_type 和 recommend_type 的映射
- template_type_map = {
- "common": ("common", None),
- "time_popular": ("time_made_popularity", "1"),
- "time_custom": ("time_made_popularity", "2"),
- "time_season": ("time_season_storage", None), # 同时有 time_season_in
- "time_care": ("time_care", None),
- "time_environmental": ("time_environmental", None),
- "time_weather": ("time_weather", None),
- "time_health": ("time_health", None),
- }
- card_type_key, recommend_type = template_type_map.get(record.template_type, (record.template_type, None))
- first_keys = []
- for zone_id in record.zone_ids:
- if record.template_type == "common":
- key = f"broadcast-template-jinja:{record.type_id}:{zone_id}:common"
- first_keys.append(key)
- else:
- # 非 common 模板,需要为每个 zone_id 和 recommend_type 生成 key
- if recommend_type:
- key = f"broadcast-template-jinja:{record.type_id}:{zone_id}:{card_type_key}:{recommend_type}"
- first_keys.append(key)
- else:
- # 对于 season,同时生成 storage 和 in
- if record.template_type == "time_season":
- first_keys.append(f"broadcast-template-jinja:{record.type_id}:{zone_id}:time_season_storage:1")
- first_keys.append(f"broadcast-template-jinja:{record.type_id}:{zone_id}:time_season_in:1")
- else:
- key = f"broadcast-template-jinja:{record.type_id}:{zone_id}:{card_type_key}:1"
- first_keys.append(key)
- return {"second": second_key, "first": first_keys}
- def list_categories(self, machine_model: str = None) -> List[str]:
- templates = self._cache.values()
- if machine_model:
- templates = [t for t in templates if t.machine_model == machine_model]
- categories = {record.category for record in templates}
- return sorted(categories)
- def list_machine_models(self) -> List[str]:
- models = {record.machine_model for record in self._cache.values()}
- return sorted(models)
- def list_device_types(self, machine_model: str = None) -> List[str]:
- templates = self._cache.values()
- if machine_model:
- templates = [t for t in templates if t.machine_model == machine_model]
- device_types = {record.device_type for record in templates}
- return sorted(device_types)
- def list_template_types(self, machine_model: str = None, device_type: str = None) -> List[str]:
- templates = self._cache.values()
- if machine_model:
- templates = [t for t in templates if t.machine_model == machine_model]
- if device_type:
- templates = [t for t in templates if t.device_type == device_type]
- template_types = {record.template_type for record in templates}
- return sorted(template_types)
- def load_store(base_dir: Path) -> TemplateStore:
- data_path = base_dir / "data" / "templates.json"
- return TemplateStore(data_path)
|