# 多源遥感数据集成与智能分析综合可视化平台 — 软件开发设计说明书（SDD）

> 文档编号：WX-SDD-001 | 版本：v2.0 | 密级：内部机密
> 编制日期：2026-06-13 | 编制单位：卫星陕西项目组
> 配套文档：[CODE_WIKI.md](./CODE_WIKI.md) | [REQUIREMENTS.md](./REQUIREMENTS.md) | [DEPLOYMENT.md](./DEPLOYMENT.md)

---

## 1. 引言

### 1.1 编写目的

本文档为"多源遥感数据集成与智能分析综合可视化平台"（以下简称"本平台"）的软件开发设计说明书（Software Design Description, SDD），依据 GB/T 8567-2006《计算机软件文档编制规范》及 GJB 438B-2009《军用软件开发文档通用要求》编制。

本文档面向以下读者：
- **系统架构师**：指导系统总体架构设计与技术选型决策
- **开发工程师**：明确模块划分、接口定义、编码规范与实现约束
- **测试工程师**：制定测试策略、设计测试用例与性能基准
- **运维工程师**：理解部署拓扑、配置参数与监控指标体系
- **项目管理层**：评估技术风险、资源投入与交付里程碑

### 1.2 项目背景

遥感数据具有多源、多尺度、多维度、高时效性等特点，传统软件工程方法难以直接适配其处理流程。本平台需满足以下行业特性约束：

| 行业特性 | 技术影响 | 设计对策 |
|---------|---------|---------|
| 数据体量大（单景影像可达数十GB） | 内存无法全量加载，I/O成为瓶颈 | 流式处理、内存映射、分块读写 |
| 空间参考系统多样（CGCS2000/WGS84/UTM/高斯-克吕格等） | 坐标转换频繁，精度损失敏感 | 统一内部坐标系（优先CGCS2000/EPSG:4490）、延迟重投影、缓存转换参数 |
| 元数据标准繁杂（ISO 19115/OGC/GB/T 36301/GB/T 39608等） | 解析逻辑碎片化，扩展成本高 | 元数据驱动架构、插件化解析器 |
| 瓦片金字塔层级深（L0-L21） | 预计算耗时长，存储膨胀 | COG按需切图、异步任务、分布式缓存 |
| AI模型推理资源密集 | GPU显存受限，并发度低 | 模型服务化、请求队列、批处理推理 |
| 安全合规等级高（等保三级+2025数据安全新规） | 网络隔离、数据分类分级、审计追溯 | 零外网架构、内网微隔离、全链路审计、国密算法 |

### 1.3 术语与缩略语

| 术语/缩略语 | 英文全称 | 中文释义 |
|------------|---------|---------|
| SDD | Software Design Description | 软件设计说明 |
| SOA | Service-Oriented Architecture | 面向服务架构 |
| DDD | Domain-Driven Design | 领域驱动设计 |
| CQRS | Command Query Responsibility Segregation | 命令查询职责分离 |
| COG | Cloud Optimized GeoTIFF | 云优化GeoTIFF（内嵌金字塔的按需访问格式） |
| CGCS2000 | China Geodetic Coordinate System 2000 | 2000国家大地坐标系（EPSG:4490） |
| API | Application Programming Interface | 应用程序编程接口 |
| REST | Representational State Transfer | 表述性状态转移 |
| ORM | Object-Relational Mapping | 对象关系映射 |
| DTO | Data Transfer Object | 数据传输对象 |
| DAO | Data Access Object | 数据访问对象 |
| CI/CD | Continuous Integration / Continuous Deployment | 持续集成/持续部署 |
| SLA | Service Level Agreement | 服务等级协议 |
| QPS | Queries Per Second | 每秒查询数 |
| TPS | Transactions Per Second | 每秒事务数 |
| GPU | Graphics Processing Unit | 图形处理器 |
| VRAM | Video Random Access Memory | 显存 |

### 1.4 参考文档

- GB/T 8567-2006《计算机软件文档编制规范》
- GJB 438B-2009《军用软件开发文档通用要求》
- GB/T 22239-2019《信息安全技术 网络安全等级保护基本要求》
- OGC WMS 1.3.0 / WMTS 1.0.0 / WCS 2.0.1
- ISO 19115-1:2014《地理信息 元数据》
- [REQUIREMENTS.md](./REQUIREMENTS.md) — 软件需求规格说明书
- [CODE_WIKI.md](./CODE_WIKI.md) — 项目代码Wiki

---

## 2. 总体设计

### 2.1 设计原则与约束

#### 2.1.1 架构设计原则

| 原则 | 说明 | 落地方案 |
|------|------|---------|
| **高内聚低耦合** | 模块内部功能紧密相关，模块间依赖最小化 | 按领域拆分微服务，接口契约化 |
| **关注点分离** | 数据存储、业务逻辑、表现层独立演进 | 四层架构，DTO隔离领域模型 |
| **防御性设计** | 假设外部输入均不可信，内部状态可能异常 | 参数校验、限流熔断、优雅降级 |
| **可观测性** | 系统状态对运维人员透明，故障可定位 | 结构化日志、指标采集、链路追踪 |
| **安全左移** | 安全需求在架构设计阶段即纳入考量 | 零信任网络、最小权限、国密算法 |

#### 2.1.2 技术约束

| 约束项 | 约束内容 | 影响范围 |
|--------|---------|---------|
| 网络环境 | 完全物理隔离，禁止任何外网访问 | 所有组件必须本地化部署，禁止云API调用 |
| 数据安全 | 遥感影像数据密级为"内部机密" | 传输加密、存储加密、访问审计、数据分类分级 |
| 合规要求 | 等保三级（含2025数据安全新规）、GJB5799 | 身份鉴别、访问控制、安全审计、数据完整性、内网微隔离 |
| 硬件环境 | 服务器为国产信创环境（鲲鹏/飞腾+麒麟OS） | 优先选用ARM架构兼容组件 |
| 开源许可 | 禁止GPL/AGPL强传染性许可证组件二次分发 | 技术选型需法务合规审查 |

### 2.2 系统总体架构

#### 2.2.1 逻辑架构

本平台采用**分层架构（Layered Architecture）**与**微服务架构（Microservices Architecture）**相结合的混合架构模式，自顶向下划分为四个逻辑层次：

```mermaid
graph TB
    subgraph PL["表现层 Presentation Layer"]
        WEB["Web前端<br/>Vue3 + OpenLayers + CesiumJS"]
        ADMIN["管理后台<br/>Vue3 + Element Plus"]
    end

    subgraph SL["业务服务层 Service Layer"]
        API_GW["API网关<br/>Spring Cloud Gateway"]
        AUTH["认证中心<br/>Spring Security + JWT"]
        BE["业务中台<br/>Spring Boot + MyBatis Plus"]
        IMG_SVC["影像服务<br/>FastAPI + GDAL"]
        AI_SVC["AI推理服务<br/>FastAPI + vLLM + MMDetection"]
    end

    subgraph PRL["数据处理层 Processing Layer"]
        CELERY["任务调度<br/>Celery + RabbitMQ"]
        GDAL_PROC["影像处理引擎<br/>GDAL + Rasterio"]
        TILE_ENG["瓦片引擎<br/>MapProxy + 自研切图"]
        AI_ENG["AI推理引擎<br/>PyTorch + vLLM"]
    end

    subgraph STL["数据存储层 Storage Layer"]
        PG[("关系数据库<br/>PostgreSQL + PostGIS")]
        ES[("搜索引擎<br/>Elasticsearch")]
        MINIO[("对象存储<br/>MinIO")]
        REDIS[("缓存/队列<br/>Redis")]
        NAS[("NAS存储<br/>影像原始文件")]
    end

    WEB --> API_GW
    ADMIN --> API_GW
    API_GW --> AUTH
    API_GW --> BE
    API_GW --> IMG_SVC
    API_GW --> AI_SVC
    BE --> PG
    BE --> ES
    BE --> REDIS
    IMG_SVC --> CELERY
    IMG_SVC --> MINIO
    AI_SVC --> AI_ENG
    CELERY --> GDAL_PROC
    CELERY --> TILE_ENG
    GDAL_PROC --> NAS
    TILE_ENG --> MINIO
```

#### 2.2.2 物理部署架构

```mermaid
graph TB
    subgraph DMZ["DMZ隔离区"]
        LB["负载均衡<br/>Nginx/HAProxy"]
        WAF["Web应用防火墙"]
    end

    subgraph APP["应用服务区"]
        GW1["API网关实例1"]
        GW2["API网关实例2"]
        BE1["业务中台实例1"]
        BE2["业务中台实例2"]
        IMG1["影像服务实例1"]
        AI1["AI推理服务<br/>GPU节点"]
    end

    subgraph DATA["数据服务区"]
        PG_M["PostgreSQL主库"]
        PG_S["PostgreSQL从库"]
        ES_C["Elasticsearch集群"]
        MINIO_C["MinIO集群"]
        REDIS_M["Redis主从"]
        MQ["RabbitMQ集群"]
    end

    subgraph STORAGE["存储区"]
        NAS_P["NAS主存储"]
        NAS_B["NAS备份存储"]
    end

    LB --> WAF
    WAF --> GW1
    WAF --> GW2
    GW1 --> BE1
    GW2 --> BE2
    BE1 --> IMG1
    BE1 --> AI1
    BE1 --> PG_M
    BE1 --> ES_C
    BE1 --> REDIS_M
    BE1 --> MQ
    IMG1 --> MINIO_C
    IMG1 --> MQ
    AI1 --> MQ
    PG_M --> PG_S
    MINIO_C --> NAS_P
    NAS_P --> NAS_B
```

#### 2.2.3 技术选型决策矩阵

| 技术领域 | 选型方案 | 备选方案 | 决策依据 |
|---------|---------|---------|---------|
| 后端框架 | Spring Boot 3.2+ | Quarkus / Micronaut | 生态成熟、团队熟悉度高、信创兼容性好 |
| 影像服务 | FastAPI 0.110+ | Django / Flask | 异步性能优异、OpenAPI自动生成、Python生态 |
| 数据库 | PostgreSQL 16 + PostGIS 3.4 | MySQL + 自研空间扩展 | 空间数据原生支持、OGC标准兼容、开源协议友好 |
| 对象存储 | MinIO | SeaweedFS / Ceph | S3 API兼容、部署简单、性能优异 |
| 搜索引擎 | Elasticsearch 8.13+ | Apache Solr | 分布式原生、聚合分析能力强、生态丰富 |
| 缓存/队列 | Redis 7.2+ | Memcached / RabbitMQ独立 | 数据结构丰富、持久化支持、Pub/Sub能力 |
| 消息队列 | RabbitMQ 3.13+ | Apache Kafka / RocketMQ | 消息可靠性高、管理界面完善、AMQP标准 |
| 地图前端 | OpenLayers 9+ + CesiumJS 1.115+ | Leaflet / Mapbox GL JS | 2D/3D能力完备、开源免费、无Token依赖 |
| AI推理 | vLLM 0.5+ + MMDetection 3.3+ | TensorRT / ONNX Runtime | 开源生态、批处理优化、本地部署 |
| 容器编排 | Docker Compose | Kubernetes | 当前规模适中、运维复杂度可控、快速迭代 |

### 2.3 子系统划分与职责

| 子系统 | 英文代号 | 核心职责 | 部署形态 |
|--------|---------|---------|---------|
| 数据管理子系统 | DM | 遥感数据接入、元数据管理、目录组织、生命周期管理 | Spring Boot服务 |
| 检索引擎子系统 | SE | 全文检索、空间检索、联合查询、结果排序 | Spring Boot + Elasticsearch |
| 可视化子系统 | VS | 二维地图、三维地球、图层管理、时空动画 | Vue3前端 + 瓦片服务 |
| 智能分析子系统 | AI | 目标检测、语义分割、图像描述、模型管理 | FastAPI + GPU推理服务 |
| 影像处理子系统 | IP | 影像解析、金字塔构建、投影转换、瓦片切分 | FastAPI + Celery异步任务 |
| 运维管理子系统 | OM | 监控告警、日志审计、性能分析、备份恢复 | Spring Boot + Prometheus/Grafana |
| 系统管理子系统 | SM | 用户权限、组织管理、系统配置、操作审计 | Spring Boot服务 |

---

## 3. 详细设计

### 3.1 后端业务中台设计

#### 3.1.1 模块结构

后端采用**多模块Maven项目**结构，遵循领域驱动设计（DDD）分层模型：

```mermaid
graph TD
    subgraph API["zkxg-api 接口层"]
        CTRL["controller/ 控制器"]
        DTO_IN["dto/in/ 入参DTO"]
        DTO_OUT["dto/out/ 出参DTO"]
        ADVICE["advice/ 全局异常处理"]
        CONFIG["config/ 配置类"]
    end

    subgraph SVC["zkxg-service 业务层"]
        SVC_PKG["service/ 业务服务"]
        SVC_IMPL["service/impl/ 业务实现"]
        DOMAIN["domain/ 领域对象"]
        EVENT["event/ 领域事件"]
    end

    subgraph INFRA["zkxg-infrastructure 基础设施层"]
        MAPPER["mapper/ 数据访问"]
        ENTITY["entity/ 持久化实体"]
        REPO["repository/ 仓储实现"]
        CLIENT["client/ 外部服务客户端"]
    end

    subgraph COMMON["zkxg-common 公共模块"]
        UTIL["util/ 工具类"]
        EX["exception/ 异常定义"]
        ENUM["enums/ 枚举定义"]
        CONST["constants/ 常量定义"]
    end

    CTRL --> SVC_PKG
    CTRL --> DTO_IN
    CTRL --> DTO_OUT
    SVC_PKG --> DOMAIN
    SVC_PKG --> MAPPER
    SVC_IMPL --> EVENT
    MAPPER --> ENTITY
    REPO --> ENTITY
    CLIENT --> DTO_OUT
    API --> COMMON
    SVC --> COMMON
    INFRA --> COMMON
```

#### 3.1.2 核心领域模型

**RemoteData（遥感数据实体）**

```java
@Data
@TableName("remote_data")
public class RemoteData {
    @TableId(type = IdType.ASSIGN_UUID)
    private String id;

    private String fileName;
    private String filePath;
    private String dataType;       // ORIGINAL:原始数据 PRODUCT:产品数据 EXTENDED:扩展数据
    private String dataLevel;      // L0:原始/L1:辐射校正/L2:系统几何校正/L3:正射校正/L4:专题产品
    private Long fileSize;
    private String source;         // LOCAL:本地上传 SERVER:服务器同步
    private String status;         // UPLOADING:上传中 PROCESSING:处理中 READY:可用 ERROR:错误
    private LocalDateTime uploadTime;
    private LocalDateTime updateTime;

    @TableField(exist = false)
    private Metadata metadata;
    
    @TableField(exist = false)
    private List<CatalogNode> catalogNodes;
}
```

**Metadata（元数据实体）**

```java
@Data
@TableName("metadata")
public class Metadata {
    @TableId(type = IdType.ASSIGN_UUID)
    private String id;

    private String dataId;
    private LocalDateTime imagingTime;           // 成像时间
    private String satellite;                     // 卫星名称（如GF-1/2/6/7、ZY-3、SJ-9等）
    private String sensor;                        // 传感器类型（如PMS/PAN/MSS/SAR等）
    private Double cloudCover;                    // 云量百分比 0-100
    private String geometryWkt;                   // 空间范围WKT
    private String coordinateSystem;              // 坐标系EPSG代码（国内优先EPSG:4490/CGCS2000）
    private Double resolution;                    // 空间分辨率（米）
    private String extraJson;                     // 扩展元数据JSON
    
    // 业务方法：计算数据时效性
    public boolean isNearRealTime() {
        return imagingTime != null && 
               imagingTime.isAfter(LocalDateTime.now().minusHours(24));
    }
}
```

#### 3.1.3 统一响应与异常设计

**ApiResponse（统一响应封装）**

```java
@Data
@Schema(description = "统一API响应")
public class ApiResponse<T> {
    @Schema(description = "业务状态码", example = "200")
    private int code;
    
    @Schema(description = "响应消息", example = "success")
    private String message;
    
    @Schema(description = "响应数据")
    private T data;
    
    @Schema(description = "服务器时间戳")
    private long timestamp;
    
    @Schema(description = "请求追踪ID")
    private String traceId;

    public static <T> ApiResponse<T> success(T data) {
        ApiResponse<T> response = new ApiResponse<>();
        response.setCode(ErrorCode.SUCCESS.getCode());
        response.setMessage(ErrorCode.SUCCESS.getMessage());
        response.setData(data);
        response.setTimestamp(System.currentTimeMillis());
        response.setTraceId(MDC.get("traceId"));
        return response;
    }

    public static <T> ApiResponse<T> error(ErrorCode errorCode) {
        ApiResponse<T> response = new ApiResponse<>();
        response.setCode(errorCode.getCode());
        response.setMessage(errorCode.getMessage());
        response.setTimestamp(System.currentTimeMillis());
        response.setTraceId(MDC.get("traceId"));
        return response;
    }
}
```

**ErrorCode（错误码枚举）**

| 错误码 | 标识符 | 说明 | HTTP状态码 |
|--------|--------|------|-----------|
| 200 | SUCCESS | 成功 | 200 |
| 400 | BAD_REQUEST | 请求参数错误 | 400 |
| 401 | UNAUTHORIZED | 未认证或Token过期 | 401 |
| 403 | FORBIDDEN | 无权限访问 | 403 |
| 404 | NOT_FOUND | 资源不存在 | 404 |
| 409 | CONFLICT | 资源冲突（如重复上传） | 409 |
| 413 | PAYLOAD_TOO_LARGE | 文件超过大小限制 | 413 |
| 429 | TOO_MANY_REQUESTS | 请求频率超限 | 429 |
| 500 | INTERNAL_ERROR | 服务器内部错误 | 500 |
| 503 | SERVICE_UNAVAILABLE | 依赖服务不可用 | 503 |

#### 3.1.4 数据库设计

**核心表结构**

```sql
-- 启用PostGIS扩展
CREATE EXTENSION IF NOT EXISTS postgis;

-- 遥感数据主表
CREATE TABLE remote_data (
    id VARCHAR(36) PRIMARY KEY,
    file_name VARCHAR(500) NOT NULL,
    file_path VARCHAR(1000) NOT NULL,
    data_type VARCHAR(20) NOT NULL CHECK (data_type IN ('ORIGINAL', 'PRODUCT', 'EXTENDED')),
    data_level VARCHAR(50),
    file_size BIGINT DEFAULT 0,
    source VARCHAR(20) NOT NULL CHECK (source IN ('LOCAL', 'SERVER')),
    status VARCHAR(20) NOT NULL DEFAULT 'UPLOADING' CHECK (status IN ('UPLOADING', 'PROCESSING', 'READY', 'ERROR')),
    upload_time TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    update_time TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_remote_data_status ON remote_data(status);
CREATE INDEX idx_remote_data_upload_time ON remote_data(upload_time);

-- 元数据表（与remote_data 1:1关系）
CREATE TABLE metadata (
    id VARCHAR(36) PRIMARY KEY,
    data_id VARCHAR(36) NOT NULL UNIQUE REFERENCES remote_data(id) ON DELETE CASCADE,
    imaging_time TIMESTAMP WITH TIME ZONE,
    satellite VARCHAR(100),
    sensor VARCHAR(100),
    cloud_cover DOUBLE PRECISION CHECK (cloud_cover >= 0 AND cloud_cover <= 100),
    geometry GEOMETRY(Geometry, 4490),  -- 优先使用CGCS2000国家大地坐标系
    coordinate_system VARCHAR(50),
    resolution DOUBLE PRECISION,
    extra_json JSONB,
    create_time TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 空间索引（GIST索引对几何查询至关重要）
CREATE INDEX idx_metadata_geometry ON metadata USING GIST(geometry);
CREATE INDEX idx_metadata_imaging_time ON metadata(imaging_time);
CREATE INDEX idx_metadata_satellite ON metadata(satellite);
CREATE INDEX idx_metadata_cloud_cover ON metadata(cloud_cover);

-- 目录节点表（支持无限层级）
CREATE TABLE catalog_node (
    id VARCHAR(36) PRIMARY KEY,
    parent_id VARCHAR(36) REFERENCES catalog_node(id) ON DELETE CASCADE,
    name VARCHAR(200) NOT NULL,
    level INTEGER NOT NULL DEFAULT 0,
    sort_order INTEGER DEFAULT 0,
    node_type VARCHAR(20) DEFAULT 'FOLDER' CHECK (node_type IN ('FOLDER', 'LEAF')),
    create_time TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_catalog_node_parent ON catalog_node(parent_id);

-- 数据-目录关联表（多对多）
CREATE TABLE data_catalog (
    id VARCHAR(36) PRIMARY KEY,
    data_id VARCHAR(36) NOT NULL REFERENCES remote_data(id) ON DELETE CASCADE,
    catalog_id VARCHAR(36) NOT NULL REFERENCES catalog_node(id) ON DELETE CASCADE,
    create_time TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    UNIQUE(data_id, catalog_id)
);

-- 操作审计日志表（等保三级要求）
CREATE TABLE operation_log (
    id VARCHAR(36) PRIMARY KEY,
    user_id VARCHAR(36) NOT NULL,
    user_name VARCHAR(100),
    operation_type VARCHAR(50) NOT NULL,
    operation_target VARCHAR(200),
    operation_detail TEXT,
    ip_address VARCHAR(50),
    user_agent TEXT,
    operation_time TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    status VARCHAR(20) DEFAULT 'SUCCESS' CHECK (status IN ('SUCCESS', 'FAILURE')),
    duration_ms INTEGER
);

CREATE INDEX idx_operation_log_user ON operation_log(user_id);
CREATE INDEX idx_operation_log_time ON operation_log(operation_time);
CREATE INDEX idx_operation_log_type ON operation_log(operation_type);

-- 用户表（RBAC权限模型）
CREATE TABLE sys_user (
    id VARCHAR(36) PRIMARY KEY,
    username VARCHAR(100) NOT NULL UNIQUE,
    password_hash VARCHAR(200) NOT NULL,
    real_name VARCHAR(100),
    department VARCHAR(100),
    role VARCHAR(50) NOT NULL DEFAULT 'USER' CHECK (role IN ('ADMIN', 'OPERATOR', 'USER', 'AUDITOR')),
    status VARCHAR(20) DEFAULT 'ACTIVE' CHECK (status IN ('ACTIVE', 'LOCKED', 'DISABLED')),
    last_login_time TIMESTAMP WITH TIME ZONE,
    create_time TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 系统配置表
CREATE TABLE sys_config (
    id VARCHAR(36) PRIMARY KEY,
    config_key VARCHAR(200) NOT NULL UNIQUE,
    config_value TEXT,
    config_desc VARCHAR(500),
    update_time TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
```

### 3.2 影像处理服务设计

#### 3.2.1 处理流水线架构

影像处理采用**管道-过滤器（Pipe-Filter）**模式，每个处理步骤独立、可组合、可复用。针对国产遥感数据（GF-1/2/6/7等），处理流程需严格遵循数据分级标准（L0→L1→L2→L3→L4）：

```mermaid
graph LR
    UPLOAD["影像上传"] --> PARSER["元数据解析<br/>GDAL/Rasterio"]
    PARSER --> VALIDATOR["数据校验<br/>格式/完整性/坐标系"]
    VALIDATOR --> PYRAMID["金字塔构建<br/>GDAL Overview / COG"]
    PYRAMID --> TILE["瓦片切分<br/>COG按需切图/MarpProxy"]
    TILE --> INDEX["索引更新<br/>PostGIS/Elasticsearch"]
    INDEX --> NOTIFY["状态通知<br/>WebSocket/RabbitMQ"]
    
    VALIDATOR -.->|校验失败| ERROR["错误处理"]
    PYRAMID -.->|构建失败| ERROR
    TILE -.->|切分失败| ERROR
```

#### 3.2.2 核心服务实现

**GdalService（GDAL影像处理服务）**

```python
"""GDAL影像处理核心服务

本模块封装GDAL/Rasterio库，提供遥感影像的元数据解析、
金字塔构建、投影转换、波段运算等基础处理能力。
"""
from osgeo import gdal, osr
import rasterio
from rasterio.warp import calculate_default_transform, reproject, Resampling
from shapely.geometry import box
from typing import Dict, List, Optional
import logging

logger = logging.getLogger(__name__)


class GdalService:
    """GDAL影像处理服务
    
    设计说明：
    - 所有方法均为静态方法，无状态设计便于并发调用
    - 大文件处理采用分块读取策略，避免内存溢出
    - 异常处理遵循"快速失败"原则，向上层抛出具体异常
    """

    @staticmethod
    def parse_metadata(file_path: str) -> Dict:
        """解析影像元数据
        
        Args:
            file_path: 影像文件绝对路径
            
        Returns:
            包含宽度、高度、空间范围、坐标系、分辨率等信息的字典
            
        Raises:
            ValueError: 文件无法打开或格式不支持
            RuntimeError: GDAL内部错误
        """
        ds = gdal.Open(file_path)
        if ds is None:
            raise ValueError(f"无法打开文件: {file_path}")

        try:
            width = ds.RasterXSize
            height = ds.RasterYSize
            geo_transform = ds.GetGeoTransform()
            projection = ds.GetProjection()

            # 计算空间范围
            min_x = geo_transform[0]
            max_y = geo_transform[3]
            max_x = min_x + geo_transform[1] * width
            min_y = max_y + geo_transform[5] * height

            # 坐标系信息
            srs = osr.SpatialReference()
            srs.ImportFromWkt(projection)
            epsg = srs.GetAttrValue("AUTHORITY", 1) if (srs.IsProjected() or srs.IsGeographic()) else None

            # 分辨率（取X方向分辨率）
            resolution = abs(geo_transform[1])

            return {
                "width": width,
                "height": height,
                "extent": {
                    "min_x": min_x, "min_y": min_y,
                    "max_x": max_x, "max_y": max_y
                },
                "epsg": epsg,
                "resolution": resolution,
                "geometry_wkt": box(min_x, min_y, max_x, max_y).wkt,
                "band_count": ds.RasterCount,
                "data_type": gdal.GetDataTypeName(ds.GetRasterBand(1).DataType)
            }
        finally:
            ds = None  # 显式释放GDAL数据集

    @staticmethod
    def build_pyramid(file_path: str, levels: int = 8, 
                      resampling: str = "NEAREST") -> str:
        """构建影像金字塔（Overview）
        
        金字塔可显著提升大影像的浏览性能，通过预计算多分辨率层级，
        避免全分辨率数据实时重采样。
        
        Args:
            file_path: 影像文件路径
            levels: 金字塔层级数（默认8级，对应1/2^8=1/256分辨率）
            resampling: 重采样算法（NEAREST/BILINEAR/CUBIC/AVERAGE）
            
        Returns:
            金字塔文件路径（.ovr）
        """
        ds = gdal.Open(file_path, gdal.GA_Update)
        if ds is None:
            raise ValueError(f"无法打开文件: {file_path}")

        try:
            # 配置压缩与大文件支持
            gdal.SetConfigOption('COMPRESS_OVERVIEW', 'DEFLATE')
            gdal.SetConfigOption('BIGTIFF_OVERVIEW', 'IF_NEEDED')
            
            overview_levels = [2**i for i in range(1, levels + 1)]
            ds.BuildOverviews(resampling, overview_levels)
            
            ovr_path = file_path + ".ovr"
            logger.info(f"金字塔构建完成: {ovr_path}, 层级: {levels}")
            return ovr_path
        finally:
            ds = None

    @staticmethod
    def reproject_image(src_path: str, dst_path: str, 
                        target_epsg: int = 4326,
                        resampling: Resampling = Resampling.nearest) -> str:
        """影像投影转换
        
        将影像从源坐标系转换为目标坐标系，同步处理分辨率与范围变化。
        采用Rasterio的warp模块，支持多种重采样算法。
        
        Args:
            src_path: 源影像路径
            dst_path: 输出影像路径
            target_epsg: 目标EPSG代码（默认4326-WGS84）
            resampling: 重采样算法
            
        Returns:
            输出影像路径
        """
        with rasterio.open(src_path) as src:
            transform, width, height = calculate_default_transform(
                src.crs, f"EPSG:{target_epsg}",
                src.width, src.height, *src.bounds
            )
            kwargs = src.meta.copy()
            kwargs.update({
                'crs': f"EPSG:{target_epsg}",
                'transform': transform,
                'width': width,
                'height': height,
                'compress': 'deflate',
                'bigtiff': 'IF_NEEDED'
            })
            with rasterio.open(dst_path, 'w', **kwargs) as dst:
                for band in range(1, src.count + 1):
                    reproject(
                        source=rasterio.band(src, band),
                        destination=rasterio.band(dst, band),
                        src_transform=src.transform,
                        src_crs=src.crs,
                        dst_transform=transform,
                        dst_crs=f"EPSG:{target_epsg}",
                        resampling=resampling
                    )
        return dst_path
```

#### 3.2.3 异步任务调度

**Celery任务定义**

```python
"""影像处理异步任务队列

采用Celery + RabbitMQ实现分布式任务调度，支持：
- 任务优先级（高优先级任务优先执行）
- 重试机制（指数退避策略）
- 任务状态追踪（PENDING -> STARTED -> SUCCESS/FAILURE）
- 结果持久化（Redis结果后端）
"""
from celery import Celery
from celery.exceptions import MaxRetriesExceededError
from services.gdal_service import GdalService
from services.metadata_parser import MetadataParser
from services.pyramid_builder import PyramidBuilder
from services.tile_cutter import TileCutter
import logging

app = Celery('imaging', broker='amqp://user:pass@rabbitmq:5672//',
             backend='redis://redis:6379/1')

# 任务队列路由配置
app.conf.task_routes = {
    'imaging.process_upload': {'queue': 'imaging.high'},
    'imaging.build_pyramid': {'queue': 'imaging.normal'},
    'imaging.cut_tiles': {'queue': 'imaging.normal'},
    'imaging.ai_detect': {'queue': 'ai.inference'},
}

logger = logging.getLogger(__name__)


@app.task(bind=True, max_retries=3, default_retry_delay=60)
def process_uploaded_image(self, file_path: str, data_id: str):
    """处理上传的影像文件（主流程编排）
    
    处理流程：
    1. 解析元数据 -> 2. 保存元数据 -> 3. 构建金字塔 -> 4. 生成瓦片 -> 5. 更新状态
    
    Args:
        file_path: 上传后的临时文件路径
        data_id: 数据记录ID
    """
    try:
        # 步骤1：解析元数据
        metadata = GdalService.parse_metadata(file_path)
        
        # 步骤2：保存元数据到数据库
        MetadataParser.save_metadata(data_id, metadata)
        
        # 步骤3：构建影像金字塔（异步子任务）
        build_pyramid_task = build_pyramid_task.s(file_path)
        
        # 步骤4：生成瓦片（依赖金字塔完成）
        cut_tiles_task = cut_tiles_task.s(file_path, data_id)
        
        # 使用任务链确保执行顺序
        chain = build_pyramid_task | cut_tiles_task
        chain.apply_async()
        
        # 更新数据状态为处理中
        update_data_status(data_id, "PROCESSING")
        
    except Exception as exc:
        logger.error(f"影像处理失败 data_id={data_id}: {exc}")
        update_data_status(data_id, "ERROR")
        try:
            raise self.retry(exc=exc, countdown=60 * (2 ** self.request.retries))
        except MaxRetriesExceededError:
            logger.critical(f"影像处理重试耗尽 data_id={data_id}")
            # 发送告警通知
            send_alert.delay("影像处理失败", f"data_id={data_id}, error={exc}")


@app.task(bind=True, max_retries=2, default_retry_delay=120)
def process_ai_detection(self, data_id: str, model_name: str):
    """AI目标检测任务
    
    调用AI推理服务进行遥感影像目标检测，结果保存为矢量标注数据。
    """
    try:
        file_path = get_file_path(data_id)
        result = call_ai_service("detect", file_path, model_name)
        save_ai_result(data_id, result)
        logger.info(f"AI检测完成 data_id={data_id}, model={model_name}")
    except Exception as exc:
        logger.error(f"AI检测失败 data_id={data_id}: {exc}")
        raise self.retry(exc=exc, countdown=120)
```

#### 3.2.4 MapProxy瓦片服务配置

```yaml
# mapproxy.yaml — WMTS/WMS服务配置

services:
  wmts:
    restful: true
    kvp: true
    md:
      title: "多源遥感数据瓦片服务"
      abstract: "提供遥感影像的标准化瓦片访问接口"
      contact:
        person: "卫星陕西项目组"
  wms:
    srs: ['EPSG:4326', 'EPSG:3857', 'EPSG:4490']
    image_formats: ['image/png', 'image/jpeg']
    md:
      title: "遥感影像WMS服务"

sources:
  rs_imagery:
    type: tile
    grid: global_geodetic
    directory: /data/tiles/{data_id}/{z}/{x}/{y}.png
    transparent: true
    
  rs_original:
    type: wms
    req:
      url: http://imaging-service:8001/wms
      layers: remote_sensing
      transparent: true

caches:
  imagery_cache:
    grids: [global_geodetic]
    sources: [rs_imagery]
    cache:
      type: file
      directory: /data/mapproxy_cache
      directory_layout: tms
    format: image/png
    request_format: image/png

grids:
  global_geodetic:
    srs: EPSG:4326
    origin: ll
    tile_size: [256, 256]
    res_factor: 2
    num_levels: 22

layers:
  - name: remote_sensing
    title: "遥感影像"
    sources: [imagery_cache]
```

### 3.3 AI推理服务设计

#### 3.3.1 推理服务架构

AI推理服务采用**模型即服务（Model-as-a-Service）**架构，通过统一的REST API屏蔽底层模型差异：

```mermaid
graph TB
    CLIENT["客户端"] --> API["FastAPI网关<br/>/api/ai/*"]
    API --> ROUTER["请求路由"]
    ROUTER --> DET["目标检测<br/>MMDetection"]
    ROUTER --> SEG["语义分割<br/>MMSegmentation"]
    ROUTER --> DESC["图像描述<br/>vLLM + Qwen2-VL"]
    
    DET --> MODEL_MGR["模型管理器<br/>动态加载/卸载"]
    SEG --> MODEL_MGR
    DESC --> MODEL_MGR
    
    MODEL_MGR --> GPU["GPU显存池<br/>NVIDIA A100/V100"]
    
    subgraph CACHE["推理缓存层"]
        REDIS_C["Redis<br/>结果缓存"]
        MEM_C["内存缓存<br/>LRU策略"]
    end
    
    DET --> CACHE
    SEG --> CACHE
```

#### 3.3.2 推理API设计

```python
"""AI推理服务API

提供遥感影像的AI分析能力，包括目标检测、语义分割、图像描述。
所有接口均支持异步回调模式，大影像推理通过分块策略处理。
"""
from fastapi import FastAPI, HTTPException, BackgroundTasks
from pydantic import BaseModel, Field
from typing import List, Optional
import logging

app = FastAPI(title="AI推理服务", version="2.0.0", 
              description="遥感影像智能分析推理服务")

logger = logging.getLogger(__name__)


class DetectRequest(BaseModel):
    """目标检测请求"""
    file_path: str = Field(..., description="影像文件路径")
    model_name: str = Field(default="remote-sensing-detect", 
                           description="模型名称")
    confidence: float = Field(default=0.5, ge=0.0, le=1.0,
                             description="置信度阈值")
    classes: Optional[List[str]] = Field(default=None,
                                        description="指定检测类别")
    callback_url: Optional[str] = Field(default=None,
                                       description="异步回调地址")


class DetectResult(BaseModel):
    """目标检测结果"""
    class_name: str
    confidence: float
    bbox: List[float]  # [xmin, ymin, xmax, ymax]
    area: float


class SegmentRequest(BaseModel):
    """语义分割请求"""
    file_path: str = Field(..., description="影像文件路径")
    model_name: str = Field(default="land-cover-seg",
                           description="分割模型名称")
    classes: Optional[List[str]] = Field(default=None,
                                        description="指定分割类别")
    output_format: str = Field(default="geojson",
                              description="输出格式: geojson/png")


class DescribeRequest(BaseModel):
    """图像描述请求"""
    file_path: str = Field(..., description="影像文件路径")
    prompt: str = Field(default="描述这张遥感影像的内容",
                       description="提示词")
    max_tokens: int = Field(default=512, ge=1, le=2048,
                           description="最大生成token数")
    temperature: float = Field(default=0.7, ge=0.0, le=2.0,
                              description="采样温度")


@app.post("/api/ai/detect", response_model=ApiResponse[List[DetectResult]])
async def detect(req: DetectRequest, background_tasks: BackgroundTasks):
    """遥感影像目标检测
    
    检测影像中的地物目标（如建筑物、道路、车辆、飞机等），
    返回目标的类别、置信度和边界框坐标。
    """
    try:
        from services.detection_service import DetectionService
        
        # 检查缓存
        cache_key = f"detect:{req.file_path}:{req.model_name}:{req.confidence}"
        cached = await get_cache(cache_key)
        if cached:
            return ApiResponse.success(cached)
        
        result = DetectionService.detect(
            req.file_path, req.model_name, req.confidence, req.classes
        )
        
        # 写入缓存（TTL=1小时）
        await set_cache(cache_key, result, ttl=3600)
        
        return ApiResponse.success(result)
    except Exception as e:
        logger.error(f"目标检测失败: {e}")
        raise HTTPException(status_code=500, detail=str(e))


@app.post("/api/ai/segment")
async def segment(req: SegmentRequest):
    """遥感影像语义分割
    
    对影像进行像素级分类，识别每个像素的地物类别
    （如植被、水体、建筑、道路等）。
    """
    try:
        from services.segmentation_service import SegmentationService
        result = SegmentationService.segment(
            req.file_path, req.model_name, req.classes, req.output_format
        )
        return ApiResponse.success(result)
    except Exception as e:
        logger.error(f"语义分割失败: {e}")
        raise HTTPException(status_code=500, detail=str(e))


@app.post("/api/ai/describe")
async def describe(req: DescribeRequest):
    """遥感影像智能描述
    
    基于多模态大模型（Qwen2-VL）对遥感影像进行自然语言描述，
    支持自定义提示词引导生成内容。
    """
    try:
        from services.vllm_service import VllmService
        result = VllmService.describe(
            req.file_path, req.prompt, req.max_tokens, req.temperature
        )
        return ApiResponse.success(result)
    except Exception as e:
        logger.error(f"图像描述失败: {e}")
        raise HTTPException(status_code=500, detail=str(e))
```

#### 3.3.3 模型管理规范

```
ai/models/
├── remote-sensing-detect/           # 目标检测模型（MMDetection）
│   ├── config.py                     # 模型配置文件
│   ├── model.pth                     # 模型权重（PyTorch格式）
│   ├── labels.txt                    # 类别标签映射
│   └── metadata.json                 # 模型元数据（版本、精度、mAP）
│
├── land-cover-seg/                  # 地物分类模型（MMSegmentation）
│   ├── config.py
│   ├── model.pth
│   ├── classes.txt                   # 地物类别定义
│   └── color_map.txt                 # 可视化颜色映射
│
└── qwen2-vl-7b/                     # 多模态大模型（vLLM推理）
    ├── config.json                   # Transformer配置
    ├── tokenizer.json                # 分词器
    ├── model-00001-of-00008.safetensors
    ├── ...
    └── generation_config.json        # 生成参数配置
```

> **模型部署规范**：
> - 所有模型文件须提前下载至内网服务器，运行时零联网
> - 模型版本采用语义化版本（Semantic Versioning），如 v1.2.3
> - 模型加载采用延迟初始化策略，首次请求时加载，空闲超时后自动卸载
> - GPU显存占用监控，超过阈值时拒绝新请求并触发告警

### 3.4 前端可视化设计

#### 3.4.1 前端架构

前端采用**单页应用（SPA）**架构，基于Vue3组合式API + TypeScript开发：

```mermaid
graph TD
    subgraph APP["Vue3 Application"]
        ROUTER["Vue Router 4<br/>路由管理"]
        PINIA["Pinia<br/>状态管理"]
        AXIOX["Axios<br/>HTTP客户端"]
        
        subgraph VIEWS["页面层"]
            DATA_M["数据管理页"]
            SEARCH_M["检索页"]
            MAP_2D["二维地图页"]
            MAP_3D["三维地球页"]
            AI_RES["AI分析结果页"]
        end
        
        subgraph COMP["组件层"]
            OL_MAP["OpenLayersMap<br/>二维地图组件"]
            CS_MAP["CesiumViewer<br/>三维地球组件"]
            DATA_TABLE["DataTable<br/>数据表格"]
            SEARCH_PANEL["SearchPanel<br/>检索面板"]
            LAYER_MGR["LayerManager<br/>图层管理器"]
        end
        
        subgraph COMP_LIB["组件库"]
            EL["Element Plus<br/>UI组件"]
        end
    end
    
    ROUTER --> VIEWS
    VIEWS --> COMP
    COMP --> PINIA
    COMP --> AXIOX
    COMP --> COMP_LIB
```

#### 3.4.2 二维地图组件

```vue
<template>
  <div ref="mapContainer" class="map-container"></div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch } from 'vue'
import Map from 'ol/Map'
import View from 'ol/View'
import TileLayer from 'ol/layer/Tile'
import WMTS from 'ol/source/WMTS'
import WMTSTileGrid from 'ol/tilegrid/WMTS'
import { fromLonLat } from 'ol/proj'

interface Props {
  dataId?: string
  epsg?: number
  center?: [number, number]
  zoom?: number
}

const props = withDefaults(defineProps<Props>(), {
  epsg: 4326,
  center: () => [104.0, 35.0],  // 中国中心点
  zoom: 5
})

const mapContainer = ref<HTMLDivElement>()
let map: Map | null = null

/**
 * 创建WMTS影像图层
 * 
 * 设计要点：
 * - 使用EPSG:4326坐标系，与遥感数据原生坐标系一致
 * - 瓦片网格采用全球地理网格（origin: [-180, 90]）
 * - 分辨率数组按2的幂次递减，共22级
 */
function createWmtsLayer(dataId: string): TileLayer<WMTS> {
  const projection = `EPSG:${props.epsg}`
  const resolutions = Array.from({ length: 22 }, (_, i) => 
    180 / (256 * Math.pow(2, i))
  )
  
  return new TileLayer({
    source: new WMTS({
      url: `/api/visualization/tiles/${dataId}/{TileMatrix}/{TileCol}/{TileRow}`,
      layer: 'remote_sensing',
      matrixSet: projection,
      format: 'image/png',
      style: 'default',
      tileGrid: new WMTSTileGrid({
        origin: [-180, 90],
        resolutions: resolutions,
        matrixIds: Array.from({ length: 22 }, (_, i) => String(i)),
      }),
      wrapX: true,
    }),
  })
}

onMounted(() => {
  map = new Map({
    target: mapContainer.value!,
    view: new View({
      projection: `EPSG:${props.epsg}`,
      center: props.center,
      zoom: props.zoom,
      maxZoom: 21,
      minZoom: 1,
    }),
    controls: [],  // 自定义控件，移除默认控件
  })
  
  if (props.dataId) {
    map.addLayer(createWmtsLayer(props.dataId))
  }
})

onUnmounted(() => {
  map?.setTarget(undefined)
  map = null
})

// 监听dataId变化，动态切换图层
watch(() => props.dataId, (newId, oldId) => {
  if (map && newId && newId !== oldId) {
    // 移除旧图层，添加新图层
    const layers = map.getLayers()
    layers.clear()
    layers.push(createWmtsLayer(newId))
  }
})
</script>

<style scoped>
.map-container {
  width: 100%;
  height: 100%;
  background: #1a1a1a;
}
</style>
```

#### 3.4.3 三维地球组件

```vue
<template>
  <div ref="cesiumContainer" class="cesium-container"></div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import * as Cesium from 'cesium'

interface Props {
  dataId?: string
  terrainUrl?: string
}

const props = withDefaults(defineProps<Props>(), {
  terrainUrl: '/api/terrain'
})

const cesiumContainer = ref<HTMLDivElement>()
let viewer: Cesium.Viewer | null = null

onMounted(() => {
  // 关键安全设置：禁用Cesium Ion所有外部服务
  Cesium.Ion.defaultAccessToken = undefined
  
  viewer = new Cesium.Viewer(cesiumContainer.value!, {
    // 使用本地地形服务
    terrainProvider: new Cesium.CesiumTerrainProvider({
      url: props.terrainUrl,
      requestVertexNormals: true,
    }),
    // 禁用所有外部依赖功能
    baseLayerPicker: false,
    geocoder: false,
    homeButton: true,
    sceneModePicker: true,
    navigationHelpButton: false,
    animation: false,
    timeline: false,
    fullscreenButton: false,
    // 使用本地影像底图
    imageryProvider: new Cesium.TileMapServiceImageryProvider({
      url: '/api/tms/base-map',
    }),
  })

  if (props.dataId) {
    // 加载本地WMTS遥感影像
    viewer.imageryLayers.addImageryProvider(
      new Cesium.WebMapTileServiceImageryProvider({
        url: `/api/visualization/tiles/${props.dataId}/{TileMatrix}/{TileCol}/{TileRow}`,
        layer: 'remote_sensing',
        style: 'default',
        tileMatrixSetID: 'EPSG:4326',
        maximumLevel: 21,
        format: 'image/png',
      })
    )
  }
})

onUnmounted(() => {
  viewer?.destroy()
  viewer = null
})
</script>

<style scoped>
.cesium-container {
  width: 100%;
  height: 100%;
}
</style>
```

---

## 4. 接口设计

### 4.1 后端API接口规范

#### 4.1.1 RESTful API设计原则

| 原则 | 说明 | 示例 |
|------|------|------|
| 资源命名 | 使用名词复数，避免动词 | `/api/remote-data` 而非 `/api/getData` |
| HTTP方法 | GET查询、POST创建、PUT更新、DELETE删除 | `GET /api/remote-data/{id}` |
| 状态码 | 使用标准HTTP状态码 | 200成功、201创建、204无内容、400参数错误 |
| 版本控制 | URL路径中包含版本号 | `/api/v1/remote-data` |
| 分页规范 | 统一分页参数和响应格式 | `?pageNum=1&pageSize=20` |

#### 4.1.2 核心接口列表

| 接口路径 | 方法 | 功能说明 | 权限要求 |
|---------|------|---------|---------|
| `/api/v1/auth/login` | POST | 用户登录获取JWT | 公开 |
| `/api/v1/auth/refresh` | POST | 刷新访问令牌 | 已认证 |
| `/api/v1/remote-data` | GET | 分页查询遥感数据列表 | USER+ |
| `/api/v1/remote-data` | POST | 上传遥感数据（支持分片） | OPERATOR+ |
| `/api/v1/remote-data/{id}` | GET | 获取数据详情 | USER+ |
| `/api/v1/remote-data/{id}` | DELETE | 删除数据（逻辑删除） | OPERATOR+ |
| `/api/v1/metadata/{dataId}` | GET | 获取元数据 | USER+ |
| `/api/v1/metadata/search` | POST | 时空联合检索 | USER+ |
| `/api/v1/catalog` | GET | 获取目录树 | USER+ |
| `/api/v1/visualization/tiles/{dataId}/{z}/{x}/{y}` | GET | 获取瓦片 | USER+ |
| `/api/v1/ai/detect` | POST | 目标检测 | OPERATOR+ |
| `/api/v1/ai/segment` | POST | 语义分割 | OPERATOR+ |
| `/api/v1/ai/describe` | POST | 图像描述 | USER+ |
| `/api/v1/system/logs` | GET | 操作日志查询 | ADMIN |
| `/api/v1/system/config` | PUT | 系统配置更新 | ADMIN |

### 4.2 服务间通信接口

#### 4.2.1 同步通信（HTTP/gRPC）

| 调用方 | 被调用方 | 协议 | 用途 |
|--------|---------|------|------|
| API网关 | 业务中台 | HTTP/REST | 业务请求转发 |
| API网关 | 影像服务 | HTTP/REST | 影像处理请求 |
| API网关 | AI推理服务 | HTTP/REST | AI分析请求 |
| 业务中台 | Elasticsearch | HTTP/REST | 检索查询 |

#### 4.2.2 异步通信（消息队列）

| 生产者 | 消费者 | 队列/Topic | 消息内容 |
|--------|--------|-----------|---------|
| 业务中台 | 影像服务 | `imaging.upload` | 影像上传事件 |
| 影像服务 | 任务调度 | `task.pyramid` | 金字塔构建任务 |
| 任务调度 | 瓦片引擎 | `task.tiles` | 瓦片切分任务 |
| 业务中台 | AI推理服务 | `ai.inference` | AI推理请求 |
| AI推理服务 | 业务中台 | `ai.result` | AI推理结果 |

---

## 5. 安全设计

### 5.1 身份认证与访问控制

#### 5.1.1 JWT认证流程

```mermaid
sequenceDiagram
    participant C as 客户端
    participant GW as API网关
    participant AUTH as 认证中心
    participant SVC as 业务服务
    
    C->>AUTH: POST /auth/login<br/>{username, password}
    AUTH->>AUTH: 校验密码（BCrypt）
    AUTH->>C: 返回 {accessToken, refreshToken}
    
    C->>GW: 请求 /api/xxx<br/>Header: Authorization: Bearer {accessToken}
    GW->>GW: 验证JWT签名与过期时间
    GW->>SVC: 转发请求（携带用户上下文）
    SVC->>SVC: RBAC权限校验
    SVC->>C: 返回业务数据
    
    Note over C,AUTH: Access Token过期（默认24h）
    C->>AUTH: POST /auth/refresh<br/>{refreshToken}
    AUTH->>C: 返回新的Access Token
```

#### 5.1.2 RBAC权限模型

| 角色 | 标识 | 权限范围 |
|------|------|---------|
| 系统管理员 | ADMIN | 全部功能，包括用户管理、系统配置 |
| 业务操作员 | OPERATOR | 数据上传、AI分析、目录管理 |
| 普通用户 | USER | 数据检索、可视化浏览、下载 |
| 审计员 | AUDITOR | 仅查看操作日志和审计报表 |

### 5.2 数据安全

| 安全域 | 措施 | 实现方案 |
|--------|------|---------|
| 传输安全 | TLS 1.3加密 | Nginx反向代理统一配置HTTPS |
| 存储安全 | 敏感字段加密 | 数据库字段级AES-256加密 |
| 密码安全 | 单向哈希 | BCrypt算法，cost factor=12 |
| 会话安全 | Token过期与刷新 | Access Token 24h，Refresh Token 7d |
| 审计追溯 | 全链路操作日志 | 操作日志表 + 结构化日志文件 |
| 数据分类分级 | 按密级和业务敏感度分级 | 影像数据按分辨率/区域分级标记，不同级别差异化访问控制 |
| 内网微隔离 | 业务域间流量管控 | 按等保三级2025新规，DMZ/应用区/数据区/存储区隔离 |

### 5.3 安全防护

| 威胁类型 | 防护措施 | 实现位置 |
|---------|---------|---------|
| SQL注入 | 参数化查询 + ORM | MyBatis Plus数据访问层 |
| XSS攻击 | 输入过滤 + 输出编码 | 前端Vue模板转义 + 后端校验 |
| CSRF攻击 | Token验证 + SameSite Cookie | Spring Security配置 |
| 路径遍历 | 文件路径白名单校验 | 影像上传服务 |
| 暴力破解 | 登录失败锁定 + 验证码 | 认证中心 |
| DDoS攻击 | 限流熔断 | API网关（Sentinel） |

---

## 6. 性能设计

### 6.1 性能指标定义

| 指标类别 | 指标项 | 目标值 | 测量方法 |
|---------|--------|--------|---------|
| 响应时间 | 简单查询API P99 | <= 200ms | APM工具 |
| 响应时间 | 复杂空间检索P99 | <= 2s | APM工具 |
| 响应时间 | 瓦片加载P99 | <= 500ms | 浏览器Performance API |
| 吞吐量 | 并发上传 | >= 10路 | 压力测试 |
| 吞吐量 | 瓦片服务QPS | >= 1000 | 压力测试 |
| 资源使用 | CPU利用率 | <= 70% | 监控告警 |
| 资源使用 | 内存利用率 | <= 80% | 监控告警 |
| 资源使用 | GPU显存利用率 | <= 85% | nvidia-smi |

### 6.2 性能优化策略

| 优化点 | 策略 | 实现方案 |
|--------|------|---------|
| 数据库查询 | 索引优化 + 查询重写 | PostGIS空间索引（GIST）、覆盖索引 |
| 瓦片服务 | 多级缓存 + COG按需切图 | 浏览器缓存 -> MapProxy缓存 -> COG按需读取 -> 源服务 |
| 影像处理 | 异步化 + 并行化 | Celery分布式任务 + 多进程GDAL |
| AI推理 | 批处理 + 模型量化 | vLLM连续批处理 + INT8量化 |
| 前端渲染 | 懒加载 + 虚拟滚动 | 瓦片金字塔按需加载、表格虚拟滚动 |
| 坐标转换 | 延迟重投影 + 缓存 | 内部统一CGCS2000存储，展示时按需转换 |

---

## 7. 可靠性设计

### 7.1 高可用架构

| 组件 | 高可用方案 | RTO | RPO |
|------|-----------|-----|-----|
| API网关 | Nginx主备 + Keepalived | < 30s | 0 |
| 业务中台 | 多实例负载均衡 | < 60s | 0 |
| PostgreSQL | 主从复制 + 自动故障转移 | < 5min | < 1min |
| Elasticsearch | 3节点集群 + 副本分片 | < 2min | 0 |
| MinIO | 分布式纠删码 | < 2min | 0 |
| Redis | 主从 + Sentinel哨兵 | < 1min | < 1min |

### 7.2 灾备策略

| 灾备级别 | 方案 | 备份频率 | 保留周期 |
|---------|------|---------|---------|
| 数据库 | pg_dump逻辑备份 + WAL归档 | 每日全量 + 实时增量 | 30天 |
| 影像文件 | NAS异步复制 | 实时同步 | 版本保留 |
| 系统配置 | Git版本控制 + 配置中心导出 | 变更即备份 | 永久 |
| 模型文件 | 离线介质冷备 | 版本发布时 | 永久 |

---

## 8. 开发流程规范

### 8.1 Git分支策略（Git Flow简化版）

```mermaid
%%{init: { 'gitGraph': { 'mainBranchName': 'main' } }%%
gitGraph
    commit id: "init"
    branch develop
    checkout develop
    commit id: "dev-start"

    branch feature/upload
    checkout feature/upload
    commit id: "upload-api"
    commit id: "upload-ui"
    checkout develop
    merge feature/upload id: "merge-upload"

    branch feature/search
    checkout feature/search
    commit id: "search-engine"
    commit id: "search-ui"
    checkout develop
    merge feature/search id: "merge-search"

    branch feature/visualization
    checkout feature/visualization
    commit id: "2d-map"
    commit id: "3d-scene"
    checkout develop
    merge feature/visualization id: "merge-viz"

    branch feature/ai
    checkout feature/ai
    commit id: "detect-model"
    commit id: "segment-model"
    checkout develop
    merge feature/ai id: "merge-ai"

    checkout main
    merge develop id: "v1.0.0"

    branch hotfix/perf
    checkout hotfix/perf
    commit id: "fix-memory"
    checkout main
    merge hotfix/perf id: "v1.0.1"
```

| 分支 | 用途 | 生命周期 |
|------|------|---------|
| main | 生产环境代码，仅接受合并 | 永久 |
| develop | 开发集成分支，功能合并目标 | 永久 |
| feature/* | 新功能开发 | 合并后删除 |
| hotfix/* | 生产环境紧急修复 | 合并后删除 |
| release/* | 版本发布准备 | 发布后删除 |

### 8.2 提交规范（Conventional Commits）

```
<type>(<scope>): <subject>

<body>

<footer>
```

| Type | 说明 | 示例 |
|------|------|------|
| feat | 新功能 | `feat(api): 实现数据批量上传接口` |
| fix | 缺陷修复 | `fix(imaging): 修复大文件金字塔构建内存溢出` |
| docs | 文档更新 | `docs: 更新部署说明` |
| style | 代码格式（不影响功能） | `style: 统一缩进为4空格` |
| refactor | 重构（不新增功能也不修复bug） | `refactor(api): 优化检索查询SQL` |
| test | 测试相关 | `test(api): 增加上传接口单元测试` |
| chore | 构建/工具链 | `chore: 升级Spring Boot至3.2.5` |

### 8.3 代码审查（Code Review）规范

| 审查项 | 检查内容 | 通过标准 |
|--------|---------|---------|
| 功能正确性 | 是否满足需求 | 所有验收用例通过 |
| 代码规范 | 命名、格式、注释 | ESLint/Checkstyle零警告 |
| 安全漏洞 | SQL注入、XSS、路径遍历 | SonarQube严重问题为零 |
| 性能影响 | 查询效率、内存使用 | 无N+1查询、无内存泄漏 |
| 测试覆盖 | 单元测试、集成测试 | 新增代码覆盖率>=70% |

---

## 9. 测试策略

### 9.1 测试金字塔

```mermaid
graph TD
    subgraph E2E["端到端测试 E2E<br/>占比10%"]
        CYPRESS["Cypress<br/>用户场景模拟"]
    end
    
    subgraph INT["集成测试 Integration<br/>占比20%"]
        SBT["Spring Boot Test<br/>API契约测试"]
        PYTEST_INT["pytest<br/>服务集成测试"]
    end
    
    subgraph UNIT["单元测试 Unit<br/>占比70%"]
        JUNIT["JUnit 5<br/>Java单元测试"]
        PYTEST["pytest<br/>Python单元测试"]
        VITEST["Vitest<br/>前端单元测试"]
    end
    
    UNIT --> INT
    INT --> E2E
```

### 9.2 测试覆盖要求

| 测试类型 | 工具 | 覆盖率要求 | 关键场景 |
|---------|------|-----------|---------|
| 单元测试 | JUnit 5 / pytest / Vitest | >= 70% | 核心业务逻辑、工具类 |
| 集成测试 | Spring Boot Test / pytest | 核心流程100% | 数据上传->解析->入库->检索全链路 |
| API测试 | Postman / httpx | 所有接口 | 正常/异常/边界/并发场景 |
| 前端测试 | Vitest + Cypress | 核心组件 | 地图交互、表单验证、数据表格 |
| 性能测试 | JMeter / Locust | 基准测试 | 瓦片服务QPS、检索响应时间 |
| 安全测试 | OWASP ZAP | 高危漏洞为零 | SQL注入、XSS、越权访问 |

### 9.3 关键测试场景

| 模块 | 测试场景 | 预期结果 |
|------|---------|---------|
| 数据上传 | 单文件10GB上传 | 断点续传成功，MD5校验一致 |
| 数据上传 | 100个文件并发上传 | 无数据丢失，状态正确 |
| 元数据解析 | GeoTIFF/IMG/HDF5格式 | 元数据提取完整，坐标系识别正确 |
| 空间检索 | 百万级数据范围查询 | 响应时间<=2s，结果准确 |
| 瓦片服务 | 1000并发瓦片请求 | 成功率>=99.9%，P99<=500ms |
| AI推理 | 8K影像目标检测 | 推理完成，显存不溢出 |
| 安全 | 无Token访问受保护接口 | 返回401 Unauthorized |
| 安全 | SQL注入尝试 | 请求被拦截，数据库无异常 |

---

## 10. 附录

### 10.1 Maven依赖清单（pom.xml）

```xml
<?xml version="1.0" encoding="UTF-8"?>
<project>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
    </parent>
    
    <dependencies>
        <!-- Spring Boot 基础 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        
        <!-- 数据库 -->
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>42.7.3</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.5</version>
        </dependency>
        
        <!-- 对象存储 -->
        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
            <version>8.5.9</version>
        </dependency>
        
        <!-- 搜索引擎 -->
        <dependency>
            <groupId>co.elastic.clients</groupId>
            <artifactId>elasticsearch-java</artifactId>
            <version>8.13.4</version>
        </dependency>
        
        <!-- JWT认证 -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.12.5</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.12.5</version>
        </dependency>
        
        <!-- API文档 -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>2.5.0</version>
        </dependency>
        
        <!-- 监控 -->
        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-registry-prometheus</artifactId>
        </dependency>
        
        <!-- 测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>
```

### 10.2 Python依赖清单（requirements.txt）

```
# Web框架
fastapi==0.111.0
uvicorn[standard]==0.30.1

# 任务调度
celery==5.4.0
redis==5.0.7
amqp==5.2.0

# 影像处理
gdal==3.8.5
rasterio==1.3.10
shapely==2.0.4
pyproj==3.6.1
pillow==10.4.0
numpy==1.26.4

# 数据存储
minio==7.2.7
psycopg2-binary==2.9.9
sqlalchemy==2.0.31

# AI推理
torch==2.3.1
torchvision==0.18.1
vllm==0.5.4
mmdet==3.3.0
mmsegmentation==1.2.2
openmim==0.3.9

# 工具
pydantic==2.7.4
python-multipart==0.0.9
python-jose[cryptography]==3.3.0
passlib[bcrypt]==1.7.4

# 测试
pytest==8.2.2
pytest-asyncio==0.23.7
httpx==0.27.0
```

### 10.3 文档变更记录

| 版本 | 日期 | 修订人 | 修订内容 |
|------|------|--------|---------|
| v1.0 | 2026-06-11 | 项目组 | 初始版本，技术选型与基础规范 |
| v2.0 | 2026-06-13 | 项目组 | 重构为SDD标准格式，补充架构设计、安全设计、性能设计、可靠性设计、测试策略 |
| v2.1 | 2026-06-13 | 项目组 | 基于行业真实数据验证修正：补充CGCS2000坐标系、COG格式、遥感数据分级标准、等保2025新规、国产卫星型号、DOTA基准 |
