china_region_tree.py 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. """中国行政区划(bas_china_region)树形结构,供运营端与小程序共用。"""
  2. from __future__ import annotations
  3. import copy
  4. import threading
  5. import time
  6. from collections import defaultdict
  7. from typing import Dict, List
  8. from sqlalchemy.orm import Session
  9. from app.models.models import BasChinaRegion
  10. _CHINA_REGION_TREE_CACHE_TTL_SEC = 600
  11. _china_region_tree_cache = {"expires_at": 0.0, "tree": None}
  12. _china_region_tree_cache_lock = threading.Lock()
  13. def query_china_regions_active(db: Session) -> List[BasChinaRegion]:
  14. return (
  15. db.query(
  16. BasChinaRegion.id,
  17. BasChinaRegion.parent_id,
  18. BasChinaRegion.region_code,
  19. BasChinaRegion.region_name,
  20. BasChinaRegion.sort_order,
  21. )
  22. .filter(BasChinaRegion.is_del == 0)
  23. .order_by(BasChinaRegion.sort_order.asc(), BasChinaRegion.region_code.asc())
  24. .all()
  25. )
  26. def build_china_region_tree(rows: List[BasChinaRegion]) -> List[dict]:
  27. """{ value: region_code, label: region_name, children?: [...] },与历史 china-region-cascader.json 结构一致。"""
  28. children_map: Dict[str, List[BasChinaRegion]] = defaultdict(list)
  29. for r in rows:
  30. key = getattr(r, "parent_id", None) or ""
  31. children_map[key].append(r)
  32. for key in children_map:
  33. children_map[key].sort(key=lambda x: (getattr(x, "sort_order", 0), getattr(x, "region_code", "")))
  34. def to_node(r: BasChinaRegion) -> dict:
  35. rid = getattr(r, "id", None)
  36. subs = children_map.get(rid, [])
  37. node: dict = {"value": getattr(r, "region_code", None), "label": getattr(r, "region_name", None)}
  38. if subs:
  39. node["children"] = [to_node(c) for c in subs]
  40. return node
  41. roots = children_map.get("", [])
  42. return [to_node(r) for r in roots]
  43. def get_china_region_tree_cached(db: Session) -> List[dict]:
  44. """获取中国行政区划树,带进程内短 TTL 缓存。"""
  45. now = time.time()
  46. with _china_region_tree_cache_lock:
  47. cache_tree = _china_region_tree_cache.get("tree")
  48. expires_at = float(_china_region_tree_cache.get("expires_at") or 0.0)
  49. if cache_tree is not None and now < expires_at:
  50. return copy.deepcopy(cache_tree)
  51. rows = query_china_regions_active(db)
  52. tree = build_china_region_tree(rows)
  53. with _china_region_tree_cache_lock:
  54. _china_region_tree_cache["tree"] = tree
  55. _china_region_tree_cache["expires_at"] = now + _CHINA_REGION_TREE_CACHE_TTL_SEC
  56. return copy.deepcopy(tree)