2 Комити 422c587837 ... 9074d27d09

Аутор SHA1 Порука Датум
  yangtaodemon 9074d27d09 Merge remote-tracking branch 'origin/lili' into ding пре 3 дана
  yes-yes-yes-k 763ce9e57b 可以从数据库获取json类型数据 пре 3 дана

+ 16 - 6
app/api/vector.py

@@ -7,7 +7,7 @@ from ..services import vector_service
 from ..services.admin_boundary_service import get_boundary_geojson_by_name
 from ..services.admin_boundary_service import get_boundary_geojson_by_name
 import os
 import os
 import shutil
 import shutil
-from typing import List
+from typing import List, Optional
 
 
 router = APIRouter()
 router = APIRouter()
 
 
@@ -52,18 +52,25 @@ async def export_vector_data(vector_id: int, db: Session = Depends(get_db)):
         background=BackgroundTasks().add_task(shutil.rmtree, result["temp_dir"]) if "temp_dir" in result else None
         background=BackgroundTasks().add_task(shutil.rmtree, result["temp_dir"]) if "temp_dir" in result else None
     )
     )
 
 
-@router.get("/export/all", summary="导出矢量数据", description="将指定的矢量数据表导出为GeoJSON文件")
-async def export_all_vector_data_api(table_name: str = "vector_data", db: Session = Depends(get_db)):
-    """导出指定矢量数据表为GeoJSON文件
+@router.get("/export/all", summary="导出矢量数据", description="将指定的矢量数据表导出为GeoJSON或JSON文件")
+async def export_all_vector_data_api(table_name: str = "vector_data", db: Session = Depends(get_db),format:Optional[str] = "geojson"):
+    """导出指定矢量数据表为GeoJSON文件或普通JSON文件
     
     
     Args:
     Args:
         table_name (str): 要导出的矢量数据表名,默认为'vector_data'
         table_name (str): 要导出的矢量数据表名,默认为'vector_data'
         db (Session): 数据库会话
         db (Session): 数据库会话
+        format (str,optional):导出格式支持'geojson'和'json',默认为'geojson'
         
         
     Returns:
     Returns:
         FileResponse: GeoJSON文件下载响应
         FileResponse: GeoJSON文件下载响应
     """
     """
-    result = vector_service.export_all_vector_data(db, table_name)
+    #验证格式参数
+    if format not in ["geojson" , "json"]:
+        raise HTTPException(
+            status_code=400,
+            detail="只支持'geojson'和'json'格式"
+        )
+    result = vector_service.export_all_vector_data(db, table_name,export_format=format)#传递导出格式
     
     
     # 检查文件是否存在
     # 检查文件是否存在
     if not os.path.exists(result["file_path"]):
     if not os.path.exists(result["file_path"]):
@@ -71,11 +78,14 @@ async def export_all_vector_data_api(table_name: str = "vector_data", db: Sessio
             shutil.rmtree(result["temp_dir"])
             shutil.rmtree(result["temp_dir"])
         raise HTTPException(status_code=404, detail="导出文件不存在")
         raise HTTPException(status_code=404, detail="导出文件不存在")
     
     
+    #根据格式设置正确的媒体类型
+    media_type = "application/geo+json" if format == "geojson" else "application/json"
+
     # 返回文件下载
     # 返回文件下载
     return FileResponse(
     return FileResponse(
         path=result["file_path"],
         path=result["file_path"],
         filename=os.path.basename(result["file_path"]),
         filename=os.path.basename(result["file_path"]),
-        media_type="application/json",
+        media_type=media_type,
         background=BackgroundTasks().add_task(shutil.rmtree, result["temp_dir"]) if "temp_dir" in result else None
         background=BackgroundTasks().add_task(shutil.rmtree, result["temp_dir"]) if "temp_dir" in result else None
     )
     )
 
 

+ 30 - 3
app/database.py

@@ -1,4 +1,5 @@
-from sqlalchemy import create_engine
+from sqlalchemy import create_engine , text
+from app.models.base import Base
 from sqlalchemy.orm import sessionmaker
 from sqlalchemy.orm import sessionmaker
 from sqlalchemy.ext.declarative import declarative_base
 from sqlalchemy.ext.declarative import declarative_base
 import os
 import os
@@ -13,7 +14,7 @@ logging.getLogger('sqlalchemy.engine').setLevel(logging.WARNING)
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
 # 创建Base类
 # 创建Base类
-Base = declarative_base()
+#Base = declarative_base()
 Base.metadata.clear()
 Base.metadata.clear()
 
 
 # 加载环境变量
 # 加载环境变量
@@ -98,11 +99,37 @@ def execute_sql(sql_statement):
 # 新增:自动创建数据库表(关键!)
 # 新增:自动创建数据库表(关键!)
 def create_tables():
 def create_tables():
     try:
     try:
+         # ✨ 新增:同时启用 PostGIS 核心和 Raster 扩展
+        with engine.begin() as conn:
+            # 启用 PostGIS 核心(矢量)
+            conn.execute(text("CREATE EXTENSION IF NOT EXISTS postgis;")) # pyright: ignore[reportUndefinedVariable]
+            # 启用 PostGIS Raster(栅格)
+            conn.execute(text("CREATE EXTENSION IF NOT EXISTS postgis_raster;")) # pyright: ignore[reportUndefinedVariable]
+            logger.info("PostGIS 核心及 Raster 扩展已启用(或已存在)")
         # 必须导入所有模型,否则 Base 不知道要创建哪些表
         # 必须导入所有模型,否则 Base 不知道要创建哪些表
         # 替换成你项目中实际的模型文件路径(根据你的目录结构调整)
         # 替换成你项目中实际的模型文件路径(根据你的目录结构调整)
         from app.models.orm_models import Base  # 确保模型继承自这个 Base
         from app.models.orm_models import Base  # 确保模型继承自这个 Base
         from app.models.vector import VectorData  # 导入需要创建的表
         from app.models.vector import VectorData  # 导入需要创建的表
-        
+        #from app.models.raster import RasterData  # 补充其他模型
+        from app.models.county import County
+        from app.models.farmland import FarmlandData
+
+        from app.models.agricultural import AgriculturalData  # 假设类名是 Agricultural
+        from app.models.assessment import Assessment
+        from app.models.atmo_company import AtmoCompany
+        from app.models.atmo_sample import AtmoSampleData
+        from app.models.CropCd_input import CropCdInputData
+        from app.models.CropCd_output import CropCdOutputData
+        from app.models.cross_section import CrossSection
+        from app.models.EffCd_input import EffCdInputData
+        from app.models.EffCd_output import EffCdOutputData
+        from app.models.FluxCd_input import FluxCdInputData
+        from app.models.FluxCd_output import FluxCdOutputData
+        from app.models.MSM_input import MSMInputData
+        from app.models.MSM_output import MSMOutputData
+        from app.models.parameters import Parameters
+        from app.models.soil import SoilData  
+        from app.models.water_sample import WaterSampleData
         # 创建所有表
         # 创建所有表
         Base.metadata.create_all(bind=engine)
         Base.metadata.create_all(bind=engine)
         logger.info("数据库表自动创建成功!")
         logger.info("数据库表自动创建成功!")

+ 1 - 1
app/models/CropCd_input.py

@@ -1,6 +1,6 @@
 from sqlalchemy import Column, Integer, Float
 from sqlalchemy import Column, Integer, Float
 from sqlalchemy import ForeignKeyConstraint
 from sqlalchemy import ForeignKeyConstraint
-from app.database import Base  # 统一的基础模型
+from app.models.base import Base
 
 
 
 
 class CropCdInputData(Base):
 class CropCdInputData(Base):

+ 1 - 1
app/models/CropCd_output.py

@@ -1,6 +1,6 @@
 from sqlalchemy import Column, Integer, Float
 from sqlalchemy import Column, Integer, Float
 from sqlalchemy import ForeignKeyConstraint
 from sqlalchemy import ForeignKeyConstraint
-from app.database import Base  # 统一的基础模型
+from app.models.base import Base
 
 
 
 
 class CropCdOutputData(Base):
 class CropCdOutputData(Base):

+ 1 - 1
app/models/EffCd_input.py

@@ -1,6 +1,6 @@
 from sqlalchemy import Column, Integer, Float
 from sqlalchemy import Column, Integer, Float
 from sqlalchemy import ForeignKeyConstraint
 from sqlalchemy import ForeignKeyConstraint
-from app.database import Base  # 统一的Base导入
+from app.models.base import Base
 
 
 
 
 class EffCdInputData(Base):
 class EffCdInputData(Base):

+ 1 - 1
app/models/EffCd_output.py

@@ -1,7 +1,7 @@
 # app/models/effcd_output.py
 # app/models/effcd_output.py
 from sqlalchemy import Column, Integer, Float
 from sqlalchemy import Column, Integer, Float
 from sqlalchemy import ForeignKeyConstraint
 from sqlalchemy import ForeignKeyConstraint
-from app.database import Base  # 统一的基础模型
+from app.models.base import Base
 
 
 
 
 class EffCdOutputData(Base):
 class EffCdOutputData(Base):

+ 1 - 1
app/models/FluxCd_input.py

@@ -1,6 +1,6 @@
 from sqlalchemy import Column, Integer, Float
 from sqlalchemy import Column, Integer, Float
 from sqlalchemy import ForeignKeyConstraint
 from sqlalchemy import ForeignKeyConstraint
-from app.database import Base  # 统一的基础模型
+from app.models.base import Base
 
 
 
 
 class FluxCdInputData(Base):
 class FluxCdInputData(Base):

+ 2 - 59
app/models/FluxCd_output.py

@@ -1,6 +1,5 @@
 from sqlalchemy import Column, Integer, Float, ForeignKeyConstraint
 from sqlalchemy import Column, Integer, Float, ForeignKeyConstraint
-from app.database import Base
-from app.models import FluxCdInputData, SoilData
+from app.models.base import Base
 
 
 
 
 class FluxCdOutputData(Base):
 class FluxCdOutputData(Base):
@@ -32,60 +31,4 @@ class FluxCdOutputData(Base):
             ['Farmland_data.Farmland_ID', 'Farmland_data.Sample_ID']
             ['Farmland_data.Farmland_ID', 'Farmland_data.Sample_ID']
         ),
         ),
         {'comment': '通量镉预测模型输出数据'}
         {'comment': '通量镉预测模型输出数据'}
-    )
-
-    def calculate_fluxes(self, input_data: FluxCdInputData):
-        """根据输入数据计算通量"""
-        self.in_cd = input_data.input_flux()
-        self.out_cd = input_data.output_flux()  # 使用输出通量方法
-        self.net_cd = self.in_cd - self.out_cd
-
-    # 在 app/models/effcd_output.py 的 FluxCdOutputData 类中添加以下方法
-    def calculate_end_cd(self, db_session):
-        """
-        计算当年Cd浓度(mg/kg)
-        公式: (Initial_Cd + Net_Cd) / (2000 * bd020_90)
-
-        @param db_session: 数据库会话
-        @return: 计算后的Cd浓度值
-        """
-        try:
-            # 1. 获取初始Cd浓度(Initial_Cd) - 从FluxCdInputData表
-            input_data = db_session.query(FluxCdInputData).filter(
-                FluxCdInputData.farmland_id == self.farmland_id,
-                FluxCdInputData.sample_id == self.sample_id
-            ).first()
-
-            if not input_data:
-                raise ValueError("未找到对应的输入通量数据")
-
-            initial_cd = input_data.initial_cd  # 单位: g/ha
-
-            # 2. 获取土壤容重(bd020_90) - 从SoilData表
-            soil_data = db_session.query(SoilData).filter(
-                SoilData.farmland_id == self.farmland_id,
-                SoilData.sample_id == self.sample_id
-            ).first()
-
-            if not soil_data:
-                raise ValueError("未找到对应的土壤数据")
-
-            bd020_90 = soil_data.bd020_90  # 单位: g/cm³
-
-            # 3. 计算当年Cd浓度
-            # 注意单位转换:
-            # - Initial_Cd和Net_Cd单位是g/ha
-            # - 1 ha = 10,000 m²
-            # - 假设计算深度为20cm(0.2m),所以体积为2000 m³/ha
-            # - bd020_90单位是g/cm³ = 1000 kg/m³
-            # 公式推导:
-            # (g/ha) / (2000 m³/ha * 1000 kg/m³) = (g/ha) / (2,000,000 kg/ha) = mg/kg
-
-            end_cd = (initial_cd + self.net_cd) / (2000 * bd020_90)
-
-            # 更新并返回结果
-            self.end_cd = end_cd
-            return end_cd
-
-        except Exception as e:
-            raise ValueError(f"计算当年Cd浓度失败: {str(e)}")
+    )

+ 1 - 1
app/models/MSM_input.py

@@ -1,7 +1,7 @@
 from sqlalchemy import Column, Integer, Float, String
 from sqlalchemy import Column, Integer, Float, String
 from sqlalchemy import ForeignKey, ForeignKeyConstraint
 from sqlalchemy import ForeignKey, ForeignKeyConstraint
 from geoalchemy2 import Geometry
 from geoalchemy2 import Geometry
-from app.database import Base
+from app.models.base import Base
 
 
 
 
 class MSMInputData(Base):
 class MSMInputData(Base):

+ 1 - 1
app/models/MSM_output.py

@@ -1,6 +1,6 @@
 from sqlalchemy import Column, Integer, Float, Text
 from sqlalchemy import Column, Integer, Float, Text
 from sqlalchemy import ForeignKey, ForeignKeyConstraint
 from sqlalchemy import ForeignKey, ForeignKeyConstraint
-from app.database import Base  # 统一的基础模型
+from app.models.base import Base
 
 
 
 
 class MSMOutputData(Base):
 class MSMOutputData(Base):

+ 1 - 1
app/models/agricultural.py

@@ -1,6 +1,6 @@
 from sqlalchemy import Column, Integer, Float, String
 from sqlalchemy import Column, Integer, Float, String
 from sqlalchemy.dialects.postgresql import TEXT
 from sqlalchemy.dialects.postgresql import TEXT
-from app.database import Base
+from app.models.base import Base
 
 
 
 
 class AgriculturalData(Base):
 class AgriculturalData(Base):

+ 1 - 1
app/models/assessment.py

@@ -1,7 +1,7 @@
 from sqlalchemy import Column, Integer, Float, ForeignKey
 from sqlalchemy import Column, Integer, Float, ForeignKey
 from sqlalchemy.orm import relationship, backref
 from sqlalchemy.orm import relationship, backref
 from geoalchemy2 import Geometry
 from geoalchemy2 import Geometry
-from app.database import Base
+from app.models.base import Base
 from sqlalchemy import ForeignKeyConstraint
 from sqlalchemy import ForeignKeyConstraint
 
 
 
 

+ 1 - 1
app/models/atmo_company.py

@@ -1,5 +1,5 @@
 from sqlalchemy import Column, Integer, Float, String
 from sqlalchemy import Column, Integer, Float, String
-from app.database import Base
+from app.models.base import Base
 
 
 
 
 class AtmoCompany(Base):
 class AtmoCompany(Base):

+ 1 - 1
app/models/atmo_sample.py

@@ -1,6 +1,6 @@
 from sqlalchemy import Column, String, Float
 from sqlalchemy import Column, String, Float
 from sqlalchemy.dialects.postgresql import TEXT
 from sqlalchemy.dialects.postgresql import TEXT
-from app.database import Base
+from app.models.base import Base
 
 
 
 
 class AtmoSampleData(Base):
 class AtmoSampleData(Base):

+ 1 - 1
app/models/city.py

@@ -1,7 +1,7 @@
 from sqlalchemy import Column, Integer, String, Text, Index, JSON
 from sqlalchemy import Column, Integer, String, Text, Index, JSON
 from sqlalchemy.dialects.postgresql import JSONB
 from sqlalchemy.dialects.postgresql import JSONB
 from geoalchemy2 import Geometry
 from geoalchemy2 import Geometry
-from app.database import Base
+from app.models.base import Base
 from shapely.geometry import shape, MultiPolygon, Polygon
 from shapely.geometry import shape, MultiPolygon, Polygon
 import json
 import json
 
 

+ 3 - 8
app/models/county.py

@@ -1,8 +1,7 @@
 from sqlalchemy import Column, Integer, String, Text, Index, JSON
 from sqlalchemy import Column, Integer, String, Text, Index, JSON
 from sqlalchemy.dialects.postgresql import JSONB
 from sqlalchemy.dialects.postgresql import JSONB
 from geoalchemy2 import Geometry
 from geoalchemy2 import Geometry
-from app.database import Base
-from shapely.geometry import shape, MultiPolygon, Polygon
+from app.models.base import Base
 import json
 import json
 
 
 class County(Base):
 class County(Base):
@@ -60,13 +59,9 @@ class County(Base):
             for k, v in properties.items()
             for k, v in properties.items()
         }
         }
         
         
-        # 将GeoJSON geometry转换为EWKT格式(SRID=4326)
+        # 将GeoJSON geometry转换为WKT格式
         geometry = feature['geometry']
         geometry = feature['geometry']
-        geom = shape(geometry)
-        # 统一为 MultiPolygon 以匹配列类型
-        if isinstance(geom, Polygon):
-            geom = MultiPolygon([geom])
-        wkt = f"SRID=4326;{geom.wkt}"
+        wkt = f"SRID=4326;{json.dumps(geometry)}"
         
         
         # 创建实例
         # 创建实例
         county = cls(
         county = cls(

+ 1 - 1
app/models/cross_section.py

@@ -1,5 +1,5 @@
 from sqlalchemy import Column, Integer, Float, String, Text
 from sqlalchemy import Column, Integer, Float, String, Text
-from app.database import Base
+from app.models.base import Base
 
 
 
 
 class CrossSection(Base):
 class CrossSection(Base):

+ 1 - 1
app/models/farmland.py

@@ -6,7 +6,7 @@
 
 
 from sqlalchemy import Column, Integer, Float, ForeignKey
 from sqlalchemy import Column, Integer, Float, ForeignKey
 from geoalchemy2 import Geometry
 from geoalchemy2 import Geometry
-from app.database import Base
+from app.models.base import Base
 
 
 
 
 class FarmlandData(Base):
 class FarmlandData(Base):

+ 1 - 1
app/models/orm_models.py

@@ -140,7 +140,7 @@ class FiftyThousandSurveyDatum(Base):
     geom = Column(Geometry('POINT', from_text='ST_GeomFromEWKT', name='geometry'), index=True, comment='几何位置信息')
     geom = Column(Geometry('POINT', from_text='ST_GeomFromEWKT', name='geometry'), index=True, comment='几何位置信息')
 
 
 class RasterTable(Base):
 class RasterTable(Base):
-    __tablename__ = 'raster_table'
+    __tablename__ = 'raster_table_v2'
 
 
     id = Column(Integer, primary_key=True, autoincrement=True)
     id = Column(Integer, primary_key=True, autoincrement=True)
     rast = Column(Raster(from_text='raster', name='raster'), index=False)
     rast = Column(Raster(from_text='raster', name='raster'), index=False)

+ 1 - 1
app/models/parameters.py

@@ -1,6 +1,6 @@
 from sqlalchemy import Column, Integer, Float, String, create_engine
 from sqlalchemy import Column, Integer, Float, String, create_engine
 from sqlalchemy.orm import declarative_base
 from sqlalchemy.orm import declarative_base
-from app.database import Base
+from app.models.base import Base
 
 
 
 
 class Parameters(Base):
 class Parameters(Base):

+ 1 - 1
app/models/province.py

@@ -1,7 +1,7 @@
 from sqlalchemy import Column, Integer, String, Text, Index, JSON
 from sqlalchemy import Column, Integer, String, Text, Index, JSON
 from sqlalchemy.dialects.postgresql import JSONB
 from sqlalchemy.dialects.postgresql import JSONB
 from geoalchemy2 import Geometry
 from geoalchemy2 import Geometry
-from app.database import Base
+from app.models.base import Base
 from shapely.geometry import shape, MultiPolygon, Polygon
 from shapely.geometry import shape, MultiPolygon, Polygon
 import json
 import json
 
 

+ 1 - 1
app/models/raster.py

@@ -2,7 +2,7 @@
 
 
 from sqlalchemy import Column, Integer
 from sqlalchemy import Column, Integer
 from geoalchemy2 import Raster
 from geoalchemy2 import Raster
-from ..database import Base
+from app.models.base import Base
 
 
 class RasterData(Base):
 class RasterData(Base):
     __tablename__ = "raster_table"
     __tablename__ = "raster_table"

+ 1 - 1
app/models/soil.py

@@ -1,6 +1,6 @@
 from sqlalchemy import Column, Integer, Float, ForeignKey, ForeignKeyConstraint
 from sqlalchemy import Column, Integer, Float, ForeignKey, ForeignKeyConstraint
 from geoalchemy2 import Geometry
 from geoalchemy2 import Geometry
-from app.database import Base
+from app.models.base import Base
 
 
 
 
 class SoilData(Base):
 class SoilData(Base):

+ 2 - 2
app/models/vector.py

@@ -1,12 +1,12 @@
 # 从数据库配置里导入 Base(用来管理表)
 # 从数据库配置里导入 Base(用来管理表)
-from app.database import Base
+from app.models.base import Base
 from sqlalchemy import Table, Column, Integer, String, Float, MetaData
 from sqlalchemy import Table, Column, Integer, String, Float, MetaData
 
 
 # 关联到 Base,让它能自动创建表
 # 关联到 Base,让它能自动创建表
 metadata = Base.metadata
 metadata = Base.metadata
 # 新增 ORM 类定义(服务层需要的 VectorData)
 # 新增 ORM 类定义(服务层需要的 VectorData)
 class VectorData(Base):
 class VectorData(Base):
-    __tablename__ = "surveydata"  # 使用相同的表名
+    __tablename__ = "vector_surveydata"  # 使用相同的表名
     
     
     id = Column(Integer, primary_key=True)
     id = Column(Integer, primary_key=True)
     name = Column(String(100))
     name = Column(String(100))

+ 1 - 1
app/models/water_sample.py

@@ -1,6 +1,6 @@
 from sqlalchemy import Column, Integer, Float, String, Text, DateTime
 from sqlalchemy import Column, Integer, Float, String, Text, DateTime
 from geoalchemy2 import Geometry
 from geoalchemy2 import Geometry
-from app.database import Base
+from app.models.base import Base
 
 
 
 
 class WaterSampleData(Base):
 class WaterSampleData(Base):

+ 26 - 4
app/services/vector_service.py

@@ -165,20 +165,42 @@ def export_vector_data_batch(db: Session, vector_ids: List[int]):
     return _export_vector_data_to_file(vector_data_list, f"export_batch_{'_'.join(map(str, vector_ids))}", "surveydata")
     return _export_vector_data_to_file(vector_data_list, f"export_batch_{'_'.join(map(str, vector_ids))}", "surveydata")
 
 
 
 
-def export_all_vector_data(db: Session, table_name: str = "surveydata"):
-    """导出指定的矢量数据表为GeoJSON格式并保存到文件"""
+def export_all_vector_data(
+    db: Session, 
+    table_name: str = "surveydata", 
+    export_format: str = "geojson"  # 新增:格式参数,默认geojson
+):
+    """导出指定表数据为GeoJSON或普通JSON格式"""
     try:
     try:
+        # 1. 查询表数据(保持原有逻辑)
         query = text(f'SELECT * FROM "{table_name}"')
         query = text(f'SELECT * FROM "{table_name}"')
         result = db.execute(query)
         result = db.execute(query)
         columns = [col.name for col in result.cursor.description]
         columns = [col.name for col in result.cursor.description]
-        vector_data_list = [dict(zip(columns, row)) for row in result.fetchall()]  # 类型修正
-        return _export_vector_data_to_file(vector_data_list, f"export_{table_name}", table_name)
+        # 转换为字典列表(原生查询结果处理)
+        data_list = [dict(zip(columns, row)) for row in result.fetchall()]
+        
+        # 2. 根据格式生成不同文件
+        if export_format == "geojson":
+            # 原有逻辑:生成GeoJSON(依赖你的build_geojson工具)
+            return _export_vector_data_to_file(data_list, f"export_{table_name}", table_name)
+        else:
+            # 新增逻辑:生成普通JSON
+            return _export_json_data_to_file(data_list, f"export_{table_name}", table_name)
+            
     except Exception as e:
     except Exception as e:
         raise HTTPException(
         raise HTTPException(
             status_code=500,
             status_code=500,
             detail=f"查询表{table_name}失败:{str(e)}"
             detail=f"查询表{table_name}失败:{str(e)}"
         )
         )
 
 
+# 新增函数:生成普通JSON文件
+def _export_json_data_to_file(data_list, base_filename, table_name):
+    temp_dir = tempfile.mkdtemp()
+    file_path = os.path.join(temp_dir, f"{base_filename}.json")  # 后缀改为json
+    # 直接写入原始数据(无需GeoJSON转换)
+    with open(file_path, "w", encoding="utf-8") as f:
+        json.dump(data_list, f, ensure_ascii=False, indent=2)
+    return {"file_path": file_path, "temp_dir": temp_dir}
 
 
 def parse_geom_field(geom_value) -> dict:
 def parse_geom_field(geom_value) -> dict:
     """解析 geom 字段为 GeoJSON 格式的 geometry"""
     """解析 geom 字段为 GeoJSON 格式的 geometry"""

+ 11 - 8
main.py

@@ -1,19 +1,22 @@
-from app.main import app
+# 1. 先创建 FastAPI 应用实例(只创建一次)
 from fastapi import FastAPI
 from fastapi import FastAPI
 from fastapi.middleware.cors import CORSMiddleware  # 导入 CORS 模块
 from fastapi.middleware.cors import CORSMiddleware  # 导入 CORS 模块
+
+app = FastAPI()  # 核心实例,只定义一次
+# 3. 注册路由(必须显式挂载,否则路由不生效)
 if __name__ == "__main__":
 if __name__ == "__main__":
     import uvicorn
     import uvicorn
     # uvicorn.run(app, host="0.0.0.0", port=8000, ssl_keyfile="ssl/cert.key", ssl_certfile="ssl/cert.crt")
     # uvicorn.run(app, host="0.0.0.0", port=8000, ssl_keyfile="ssl/cert.key", ssl_certfile="ssl/cert.crt")
     uvicorn.run("app.main:app", host="0.0.0.0", port=8000, reload=True)
     uvicorn.run("app.main:app", host="0.0.0.0", port=8000, reload=True)
 
 
-# 创建 FastAPI 应用实例
-app = FastAPI()
 
 
-# ========= 新增 CORS 配置 =========
+# 2. 配置 CORS(在注册路由前配置)
 app.add_middleware(
 app.add_middleware(
     CORSMiddleware,
     CORSMiddleware,
-    allow_origins=["http://localhost:5173"],  # 允许前端地址
-    allow_credentials=True,
-    allow_methods=["*"],  # 允许所有方法
-    allow_headers=["*"],  # 允许所有头
+    allow_origins=["http://localhost:5173"],  # 允许前端 Vite 服务地址
+    allow_credentials=True,  # 允许携带 cookies
+    allow_methods=["*"],  # 允许所有 HTTP 方法(GET/POST等)
+    allow_headers=["*"],  # 允许所有请求
 )
 )
+
+

+ 0 - 0
scripts/python