import json import os from typing import Optional, List, Dict # 缓存文件路径 CACHE_FILE_PATH = os.path.join(os.path.dirname(__file__), 'area_cache.json') class AreaInfo: def __init__(self, code: str, name: str, level: str, geoname_id: int = None): self.code = code self.name = name self.level = level self.geoname_id = geoname_id self.sub_districts: List[AreaInfo] = [] def to_dict(self): return { "code": self.code, "name": self.name, "level": self.level, "geoname_id": self.geoname_id, "sub_districts": [sd.to_dict() for sd in self.sub_districts] if self.sub_districts else [], } @classmethod def from_dict(cls, data: dict): area = cls( code=data.get("code"), name=data.get("name"), level=data.get("level"), geoname_id=data.get("geoname_id"), ) sub_districts = data.get("sub_districts", []) for sub in sub_districts: area.sub_districts.append(cls.from_dict(sub)) return area def parse_district_data(district_data: dict) -> AreaInfo: """解析巨量接口返回的区域数据""" area = AreaInfo( code=district_data.get("code"), name=district_data.get("name"), level=district_data.get("level"), geoname_id=district_data.get("geoname_id"), ) sub_districts = district_data.get("sub_districts") if sub_districts: for sub in sub_districts: area.sub_districts.append(parse_district_data(sub)) return area def save_area_cache(areas: List[AreaInfo]) -> None: """将区域数据保存到缓存文件""" data = [area.to_dict() for area in areas] with open(CACHE_FILE_PATH, 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False, indent=2) def load_area_cache() -> Optional[List[AreaInfo]]: """从缓存文件加载区域数据""" if not os.path.exists(CACHE_FILE_PATH): return None try: with open(CACHE_FILE_PATH, 'r', encoding='utf-8') as f: data = json.load(f) return [AreaInfo.from_dict(item) for item in data] except (json.JSONDecodeError, IOError): return None def clear_area_cache() -> None: """清除缓存文件""" if os.path.exists(CACHE_FILE_PATH): os.remove(CACHE_FILE_PATH) def filter_by_level(areas: List[AreaInfo], target_level: str) -> List[AreaInfo]: """ 根据级别过滤区域信息 :param areas: 区域列表 :param target_level: ONE_LEVEL / TWO_LEVEL / THREE_LEVEL :return: 指定级别的区域列表 """ result = [] def traverse(area: AreaInfo): if area.level == target_level: filtered = AreaInfo( code=area.code, name=area.name, level=area.level, geoname_id=area.geoname_id, ) result.append(filtered) if area.sub_districts: for sub in area.sub_districts: traverse(sub) for area in areas: traverse(area) return result def get_first_level_areas(areas: List[AreaInfo]) -> List[AreaInfo]: """获取所有一级区域(省/直辖市)""" return filter_by_level(areas, "ONE_LEVEL") def get_second_level_areas(areas: List[AreaInfo], parent_code: str = None) -> List[AreaInfo]: """ 获取二级区域(市) :param areas: 区域列表 :param parent_code: 一级区域code,不传则返回所有二级区域 :return: 二级区域列表 """ if parent_code: def find_parent_and_get_children(area: AreaInfo): if area.code == parent_code: return [AreaInfo( code=sub.code, name=sub.name, level=sub.level, geoname_id=sub.geoname_id, ) for sub in area.sub_districts] if area.sub_districts else [] if area.sub_districts: for sub in area.sub_districts: result = find_parent_and_get_children(sub) if result: return result return [] for area in areas: result = find_parent_and_get_children(area) if result: return result return [] else: return filter_by_level(areas, "TWO_LEVEL") def get_third_level_areas(areas: List[AreaInfo], parent_code: str) -> List[AreaInfo]: """ 获取三级区域(区/县) :param areas: 区域列表 :param parent_code: 二级区域code :return: 三级区域列表 """ def find_parent_and_get_children(area: AreaInfo): if area.code == parent_code: return [AreaInfo( code=sub.code, name=sub.name, level=sub.level, geoname_id=sub.geoname_id, ) for sub in area.sub_districts] if area.sub_districts else [] if area.sub_districts: for sub in area.sub_districts: result = find_parent_and_get_children(sub) if result: return result return [] for area in areas: result = find_parent_and_get_children(area) if result: return result return [] def get_area_by_level(areas: List[AreaInfo], level: str, parent_code: str = None) -> List[AreaInfo]: """ 根据级别获取区域信息 :param areas: 区域列表 :param level: ONE_LEVEL / TWO_LEVEL / THREE_LEVEL :param parent_code: 父级区域code(TWO_LEVEL和THREE_LEVEL时可选/必填) :return: 区域列表 """ level = level.upper() if level == "ONE_LEVEL": return get_first_level_areas(areas) elif level == "TWO_LEVEL": return get_second_level_areas(areas, parent_code) elif level == "THREE_LEVEL": if not parent_code: raise ValueError("获取三级区域需要提供二级区域code") return get_third_level_areas(areas, parent_code) else: raise ValueError(f"不支持的级别: {level}") async def fetch_and_cache_area_data(oauth_id: str, advertiser_id: str = 1836693172153543, code: str = "CN") -> List[AreaInfo]: """ 从接口获取区域数据并缓存到文件 :param oauth_id: 授权ID :param code: 行政区域编码,默认中国CN :return: 区域列表 """ from app.utils.douyinApi import DouyinApi params = { "advertiser_id": advertiser_id, "codes": json.dumps([code]), "language": "ZH_CN", "sub_district": "THREE_LEVEL", "version": "V2_3_2" } area_response = await DouyinApi().get_area(oauth_id=oauth_id, params=params) if area_response.get("code") != 0: raise Exception(f"获取区域信息失败: {area_response.get('message', '未知错误')}") districts_data = area_response.get("data", {}).get("districts", []) if not districts_data: raise Exception("接口返回的区域数据为空") area_list = [parse_district_data(d) for d in districts_data] save_area_cache(area_list) return area_list def get_cached_area_data() -> Optional[List[AreaInfo]]: """获取缓存的区域数据""" return load_area_cache()