@@ -942,6 +942,109 @@ def scenario_compare():
942942 return ok (report )
943943
944944
945+ # ==========================================
946+ # 8b. TLE 导入与缓存 API
947+ # ==========================================
948+ from backend .physics .tle_source import ( # noqa: E402
949+ fetch_group ,
950+ fetch_by_catnr ,
951+ get_cache_status ,
952+ inject_tle_into_constellation ,
953+ SUPPORTED_GROUPS ,
954+ )
955+
956+
957+ @app .route ('/api/tle/import' , methods = ['POST' ])
958+ @require_role ('admin' )
959+ @rate_limit (5 , 60 )
960+ def tle_import ():
961+ """从 Celestrak 导入指定分组或编号的 TLE 星历并缓存到本地。
962+
963+ 请求体(JSON)::
964+
965+ {
966+ "group": "starlink", # 星座分组名,与 catnr 二选一
967+ "catnr": 25544, # NORAD 卫星编号,与 group 二选一
968+ "force_refresh": false, # 是否强制刷新(可选,默认 false)
969+ "inject": false # 是否将 TLE 注入仿真引擎星座(可选,默认 false)
970+ }
971+
972+ 响应示例::
973+
974+ {"code": 0, "data": {
975+ "group": "starlink",
976+ "entry_count": 6000,
977+ "injected": 12,
978+ "first_epoch": "2024-05-01T00:00:00Z",
979+ "cached_at": "2024-05-01T12:00:00Z"
980+ }}
981+ """
982+ body = request .get_json (silent = True ) or {}
983+ group = str (body .get ("group" , "" )).strip ()
984+ catnr = body .get ("catnr" )
985+ force_refresh = bool (body .get ("force_refresh" , False ))
986+ do_inject = bool (body .get ("inject" , False ))
987+
988+ if not group and catnr is None :
989+ return error_response ("VALIDATION_ERROR" , "必须提供 'group'(星座名)或 'catnr'(NORAD 编号)之一" )
990+
991+ try :
992+ if catnr is not None :
993+ try :
994+ catnr_int = int (catnr )
995+ except (TypeError , ValueError ):
996+ return error_response ("VALIDATION_ERROR" , "'catnr' 必须为整数" )
997+ entries = fetch_by_catnr (catnr_int , force_refresh = force_refresh )
998+ used_group = f"catnr_{ catnr_int } "
999+ else :
1000+ entries = fetch_group (group , force_refresh = force_refresh )
1001+ used_group = group
1002+
1003+ injected = 0
1004+ if do_inject and entries :
1005+ with simulation_engine .lock :
1006+ sats = list (simulation_engine .leo_satellites ) + list (simulation_engine .meo_satellites )
1007+ injected = inject_tle_into_constellation (entries , sats )
1008+
1009+ first_epoch = entries [0 ][3 ] if entries else ""
1010+ return ok ({
1011+ "group" : used_group ,
1012+ "entry_count" : len (entries ),
1013+ "injected" : injected ,
1014+ "first_epoch" : first_epoch ,
1015+ "supported_groups" : SUPPORTED_GROUPS ,
1016+ })
1017+ except Exception :
1018+ logger .exception ("TLE 导入失败" )
1019+ return error_response ("INTERNAL_ERROR" )
1020+
1021+
1022+ @app .route ('/api/tle/status' , methods = ['GET' ])
1023+ def tle_status ():
1024+ """查看本地 TLE 缓存的新鲜度与条目统计。
1025+
1026+ 响应示例::
1027+
1028+ {"code": 0, "data": {
1029+ "cache_dir": "/path/to/data/tle_cache",
1030+ "total_groups": 3,
1031+ "total_tles": 18000,
1032+ "entries": [
1033+ {"group": "starlink", "fetched_at": "...", "age_seconds": 300,
1034+ "is_fresh": true, "entry_count": 6000}
1035+ ],
1036+ "supported_groups": [...]
1037+ }}
1038+ """
1039+ try :
1040+ status = get_cache_status ()
1041+ status ["supported_groups" ] = SUPPORTED_GROUPS
1042+ return ok (status )
1043+ except Exception :
1044+ logger .exception ("TLE 状态查询失败" )
1045+ return error_response ("INTERNAL_ERROR" )
1046+
1047+
9451048# ==========================================
9461049# 9. 仿真快照 / 回放 API
9471050# ==========================================
0 commit comments