摘要:本文深入探讨天地图核心功能的进阶应用,系统解析底图切换策略、地图操作优化、图层控制机制及后端API接口设计。通过实际场景案例,结合现代WebGIS开发模式、微服务架构与AI技术融合思路,提供从基础配置到高级集成的全链路解决方案。文章包含详细的流程图、接口设计、对比表格和实操代码片段,兼顾理论深度与实践指导,助力开发者构建高可用、高性能的智能地图应用。

关键词:天地图,WebGIS,RESTful API,微服务,空间数据库,AI地图

引言:数字中国的地理基座

天地图作为国家地理信息公共服务平台,为各类应用提供了权威、标准、统一的地理信息"数字基座"。在数字化转型浪潮中,如何充分发挥天地图的基础功能,并融合现代技术进行深度应用,已成为GIS开发者必须掌握的核心技能。本文将从三大基础功能(底图切换、地图操作、图层控制)出发,结合后端API设计的最佳实践,深入探索其在现代应用架构中的高级应用模式。

第一部分:底图切换的艺术与科学

1.1 底图类型全解析:不只是"背景图"

天地图提供多种底图服务,每种都有其特定应用场景和性能特点:

底图类型 分辨率 适用场景 加载性能 风格特点 API支持级别
矢量底图 数据可视化、交互分析 快速 简洁现代,支持样式定制 ⭐⭐⭐⭐⭐
影像底图 超高 实地对照、精准测量 中等 真实地表影像,直观性强 ⭐⭐⭐⭐
地形底图 中高 三维分析、地形研究 较慢 等高线+晕渲,立体感强 ⭐⭐⭐
墨卡托底图 专业地图、标准参考 快速 标准投影,专业性强 ⭐⭐⭐⭐⭐
深色底图 夜间模式、数据突出 快速 低亮度,减少视觉干扰 ⭐⭐⭐⭐

特殊

标准

标准

底图选择决策

应用场景分析

数据密集型应用

空间分析应用

移动端应用

专业展示应用

矢量底图
高交互性

影像/地形底图
高精度

深色底图
低功耗

墨卡托底图
标准化

性能优化?

精度要求?

网络环境?

投影需求?

瓦片缓存+懒加载

LOD多级细节

压缩切片+CDN

投影转换预处理

最终底图配置

1.2 智能底图切换策略

传统底图切换通常基于简单的手动选择,而现代应用需要更智能的切换逻辑,这通常需要后端API支持:

智能底图切换API接口设计:

// 智能底图切换服务接口
@RestController
@RequestMapping("/api/v1/basemap")
public class SmartBasemapController {
    
    @Autowired
    private BasemapRecommendationService recommendationService;
    
    @Autowired
    private BasemapCacheService cacheService;
    
    /**
     * 获取推荐的底图配置
     * POST /api/v1/basemap/recommend
     */
    @PostMapping("/recommend")
    public ResponseEntity<ApiResponse<BasemapRecommendation>> recommendBasemap(
            @RequestBody @Valid BasemapRequest request) {
        
        // 收集上下文信息
        MapContext context = MapContext.builder()
            .viewport(request.getViewport())
            .userPreference(request.getUserPreference())
            .deviceInfo(request.getDeviceInfo())
            .networkCondition(request.getNetworkCondition())
            .timeOfDay(LocalDateTime.now())
            .build();
        
        // 调用推荐引擎
        BasemapRecommendation recommendation = recommendationService.recommend(context);
        
        // 预加载推荐底图
        cacheService.prefetch(recommendation.getBasemapLayers());
        
        return ApiResponse.success(recommendation);
    }
    
    /**
     * 获取底图瓦片(支持智能压缩)
     * GET /api/v1/basemap/tiles/{z}/{x}/{y}
     */
    @GetMapping("/tiles/{z}/{x}/{y}")
    public ResponseEntity<byte[]> getTile(
            @PathVariable int z,
            @PathVariable int x,
            @PathVariable int y,
            @RequestParam(required = false) String style,
            @RequestHeader("Accept-Encoding") String acceptEncoding) {
        
        // 构建瓦片键
        TileKey tileKey = TileKey.builder()
            .z(z).x(x).y(y)
            .style(style)
            .build();
        
        // 检查缓存
        byte[] tileData = cacheService.getTile(tileKey);
        if (tileData != null) {
            return ResponseEntity.ok()
                .header("X-Cache", "HIT")
                .contentType(MediaType.IMAGE_PNG)
                .body(tileData);
        }
        
        // 从天地图获取(带重试机制)
        tileData = tiandituService.getTile(tileKey);
        
        // 智能压缩
        if (acceptEncoding.contains("br")) {
            tileData = compressionService.compressBrotli(tileData);
        } else if (acceptEncoding.contains("gzip")) {
            tileData = compressionService.compressGzip(tileData);
        }
        
        // 缓存瓦片
        cacheService.cacheTile(tileKey, tileData);
        
        return ResponseEntity.ok()
            .header("X-Cache", "MISS")
            .contentType(MediaType.IMAGE_PNG)
            .body(tileData);
    }
    
    /**
     * 批量预加载瓦片
     * POST /api/v1/basemap/tiles/prefetch
     */
    @PostMapping("/tiles/prefetch")
    public ResponseEntity<ApiResponse<PrefetchResult>> prefetchTiles(
            @RequestBody PrefetchRequest request) {
        
        List<TileKey> tileKeys = request.getViewports().stream()
            .flatMap(viewport -> calculateTiles(viewport, request.getZoomRange()).stream())
            .distinct()
            .collect(Collectors.toList());
        
        PrefetchResult result = cacheService.prefetchTiles(tileKeys, request.getPriority());
        
        return ApiResponse.success(result);
    }
}

智能底图推荐服务实现:

@Service
@Slf4j
public class BasemapRecommendationServiceImpl implements BasemapRecommendationService {
    
    @Autowired
    private AIService aiService;
    
    @Autowired
    private UserBehaviorService userBehaviorService;
    
    @Autowired
    private BasemapRuleEngine ruleEngine;
    
    @Override
    public BasemapRecommendation recommend(MapContext context) {
        
        // 规则引擎推荐
        List<BasemapOption> ruleBasedOptions = ruleEngine.evaluate(context);
        
        // AI模型推荐
        List<BasemapOption> aiBasedOptions = aiService.recommendBasemap(context);
        
        // 融合推荐结果
        List<BasemapOption> mergedOptions = mergeRecommendations(
            ruleBasedOptions, 
            aiBasedOptions
        );
        
        // 个性化调整
        List<BasemapOption> personalizedOptions = personalizeRecommendations(
            mergedOptions, 
            context.getUserId()
        );
        
        // 构建推荐结果
        return BasemapRecommendation.builder()
            .recommendedLayers(personalizedOptions)
            .confidence(calculateConfidence(personalizedOptions))
            .reason(generateExplanation(personalizedOptions, context))
            .estimatedPerformance(estimatePerformance(personalizedOptions, context))
            .build();
    }
    
    private List<BasemapOption> mergeRecommendations(
            List<BasemapOption> ruleBased, 
            List<BasemapOption> aiBased) {
        
        // 加权融合算法
        Map<String, Double> scores = new HashMap<>();
        
        // 规则引擎得分
        for (int i = 0; i < ruleBased.size(); i++) {
            String key = ruleBased.get(i).getLayerId();
            double score = (ruleBased.size() - i) * 0.6; // 权重0.6
            scores.put(key, scores.getOrDefault(key, 0.0) + score);
        }
        
        // AI模型得分
        for (int i = 0; i < aiBased.size(); i++) {
            String key = aiBased.get(i).getLayerId();
            double score = (aiBased.size() - i) * 0.4; // 权重0.4
            scores.put(key, scores.getOrDefault(key, 0.0) + score);
        }
        
        // 按得分排序
        return scores.entrySet().stream()
            .sorted(Map.Entry.<String, Double>comparingByValue().reversed())
            .limit(5)
            .map(entry -> {
                String layerId = entry.getKey();
                return ruleBased.stream()
                    .filter(opt -> opt.getLayerId().equals(layerId))
                    .findFirst()
                    .orElse(aiBased.stream()
                        .filter(opt -> opt.getLayerId().equals(layerId))
                        .findFirst()
                        .orElse(null));
            })
            .filter(Objects::nonNull)
            .collect(Collectors.toList());
    }
}

第二部分:地图操作的高级技巧

2.1 流畅交互设计模式

地图操作的核心是用户体验,后端API需要支持高效的交互处理:

用户交互

前端SDK

交互类型

视图操作

查询操作

编辑操作

视图状态API

空间查询API

数据编辑API

视图状态服务

空间查询服务

数据编辑服务

空间数据库

响应处理

数据优化

返回前端

地图操作API接口设计:

@RestController
@RequestMapping("/api/v1/map")
@Slf4j
public class MapOperationController {
    
    @Autowired
    private ViewportService viewportService;
    
    @Autowired
    private SpatialQueryService spatialQueryService;
    
    @Autowired
    private MapStateService mapStateService;
    
    /**
     * 保存地图视图状态
     * POST /api/v1/map/viewport/save
     */
    @PostMapping("/viewport/save")
    public ResponseEntity<ApiResponse<ViewportState>> saveViewport(
            @RequestBody @Valid SaveViewportRequest request,
            @RequestHeader("X-User-Id") String userId) {
        
        ViewportState state = ViewportState.builder()
            .viewport(request.getViewport())
            .layers(request.getLayers())
            .timestamp(System.currentTimeMillis())
            .userId(userId)
            .sessionId(request.getSessionId())
            .metadata(request.getMetadata())
            .build();
        
        // 保存到数据库
        String stateId = viewportService.save(state);
        state.setStateId(stateId);
        
        // 发布状态变更事件
        eventPublisher.publishEvent(new ViewportSavedEvent(state));
        
        return ApiResponse.success(state);
    }
    
    /**
     * 空间查询接口
     * POST /api/v1/map/query/spatial
     */
    @PostMapping("/query/spatial")
    public ResponseEntity<ApiResponse<SpatialQueryResult>> spatialQuery(
            @RequestBody @Valid SpatialQueryRequest request) {
        
        // 构建查询条件
        SpatialQueryCondition condition = SpatialQueryCondition.builder()
            .geometry(request.getGeometry())
            .spatialRelation(request.getRelation())
            .layers(request.getLayerIds())
            .attributes(request.getAttributes())
            .maxResults(request.getMaxResults())
            .build();
        
        // 执行空间查询
        SpatialQueryResult result = spatialQueryService.query(condition);
        
        // 结果优化
        SpatialQueryResult optimizedResult = optimizeQueryResult(
            result, 
            request.getDeviceInfo()
        );
        
        return ApiResponse.success(optimizedResult);
    }
    
    /**
     * 批量地图操作
     * POST /api/v1/map/operations/batch
     */
    @PostMapping("/operations/batch")
    public ResponseEntity<ApiResponse<BatchOperationResult>> batchOperations(
            @RequestBody @Valid BatchOperationRequest request) {
        
        // 验证操作序列
        validateOperations(request.getOperations());
        
        // 执行批量操作
        List<OperationResult> results = new ArrayList<>();
        for (MapOperation operation : request.getOperations()) {
            try {
                OperationResult result = executeOperation(operation);
                results.add(result);
                
                // 如果操作失败且设置了停止条件
                if (!result.isSuccess() && request.isStopOnFailure()) {
                    break;
                }
            } catch (Exception e) {
                log.error("Operation failed: {}", operation.getType(), e);
                results.add(OperationResult.failure(operation.getId(), e.getMessage()));
                if (request.isStopOnFailure()) break;
            }
        }
        
        BatchOperationResult batchResult = BatchOperationResult.builder()
            .operations(results)
            .successCount((int) results.stream().filter(OperationResult::isSuccess).count())
            .totalCount(results.size())
            .build();
        
        return ApiResponse.success(batchResult);
    }
    
    /**
     * 地图历史记录
     * GET /api/v1/map/history
     */
    @GetMapping("/history")
    public ResponseEntity<ApiResponse<Page<MapHistory>>> getMapHistory(
            @RequestParam String userId,
            @RequestParam(required = false) Long startTime,
            @RequestParam(required = false) Long endTime,
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "20") int size) {
        
        Pageable pageable = PageRequest.of(page, size, Sort.by("timestamp").descending());
        
        Specification<MapHistory> spec = Specification.where(
            MapHistorySpecifications.byUserId(userId)
        );
        
        if (startTime != null && endTime != null) {
            spec = spec.and(MapHistorySpecifications.betweenTimestamp(startTime, endTime));
        }
        
        Page<MapHistory> historyPage = mapHistoryRepository.findAll(spec, pageable);
        
        return ApiResponse.success(historyPage);
    }
}

空间查询服务的优化实现:

@Service
@Slf4j
public class AdvancedSpatialQueryServiceImpl implements SpatialQueryService {
    
    @Autowired
    private DataSource dataSource;
    
    @Autowired
    private QueryCacheService cacheService;
    
    @Autowired
    private SpatialIndexService indexService;
    
    @Override
    public SpatialQueryResult query(SpatialQueryCondition condition) {
        
        // 生成查询缓存键
        String cacheKey = generateCacheKey(condition);
        
        // 检查缓存
        SpatialQueryResult cachedResult = cacheService.get(cacheKey);
        if (cachedResult != null) {
            log.debug("Cache hit for query: {}", cacheKey);
            return cachedResult;
        }
        
        // 使用空间索引优化查询
        List<String> candidateIds = indexService.search(condition.getGeometry(), 
            condition.getSpatialRelation());
        
        // 构建SQL查询
        String sql = buildSpatialQuerySQL(condition, candidateIds);
        
        // 执行查询
        List<SpatialFeature> features = executeSpatialQuery(sql, condition);
        
        // 构建结果
        SpatialQueryResult result = SpatialQueryResult.builder()
            .features(features)
            .totalCount(features.size())
            .queryGeometry(condition.getGeometry())
            .timestamp(System.currentTimeMillis())
            .build();
        
        // 结果后处理
        result = postProcessResult(result, condition);
        
        // 缓存结果
        cacheService.put(cacheKey, result, getCacheTTL(condition));
        
        return result;
    }
    
    private String buildSpatialQuerySQL(SpatialQueryCondition condition, 
                                      List<String> candidateIds) {
        
        StringBuilder sql = new StringBuilder();
        sql.append("SELECT ");
        
        // 选择字段
        if (condition.getAttributes() != null && !condition.getAttributes().isEmpty()) {
            sql.append(String.join(", ", condition.getAttributes()));
        } else {
            sql.append("*");
        }
        
        sql.append(", ST_AsGeoJSON(geometry) as geometry_json ");
        sql.append("FROM spatial_features ");
        
        // 构建WHERE条件
        List<String> whereConditions = new ArrayList<>();
        
        // 空间条件
        if (!candidateIds.isEmpty()) {
            String geometryCondition = buildGeometryCondition(condition);
            whereConditions.add(geometryCondition);
        }
        
        // 属性过滤条件
        if (condition.getFilters() != null && !condition.getFilters().isEmpty()) {
            String filterCondition = buildFilterCondition(condition.getFilters());
            whereConditions.add(filterCondition);
        }
        
        // 图层过滤
        if (condition.getLayers() != null && !condition.getLayers().isEmpty()) {
            String layerCondition = "layer_id IN (" + 
                condition.getLayers().stream()
                    .map(layer -> "'" + layer + "'")
                    .collect(Collectors.joining(", ")) + 
                ")";
            whereConditions.add(layerCondition);
        }
        
        if (!whereConditions.isEmpty()) {
            sql.append("WHERE ").append(String.join(" AND ", whereConditions));
        }
        
        // 限制结果数量
        if (condition.getMaxResults() > 0) {
            sql.append(" LIMIT ").append(condition.getMaxResults());
        }
        
        return sql.toString();
    }
    
    private String buildGeometryCondition(SpatialQueryCondition condition) {
        // 使用PostGIS空间函数
        switch (condition.getSpatialRelation()) {
            case "WITHIN":
                return String.format("ST_Within(geometry, ST_GeomFromText('%s', 4326))", 
                    condition.getGeometry().toText());
            case "INTERSECTS":
                return String.format("ST_Intersects(geometry, ST_GeomFromText('%s', 4326))", 
                    condition.getGeometry().toText());
            case "DISTANCE_WITHIN":
                return String.format("ST_DWithin(geometry, ST_GeomFromText('%s', 4326), %f)", 
                    condition.getGeometry().toText(), condition.getDistance());
            default:
                return "1=1";
        }
    }
}

第三部分:图层控制的深度应用

3.1 智能图层管理体系

图层管理需要后端提供强大的图层元数据管理和状态同步能力:

图层管理API设计:

@RestController
@RequestMapping("/api/v1/layers")
@Tag(name = "图层管理", description = "图层配置、状态管理和权限控制")
public class LayerManagementController {
    
    @Autowired
    private LayerMetadataService layerMetadataService;
    
    @Autowired
    private LayerStateService layerStateService;
    
    @Autowired
    private LayerPermissionService permissionService;
    
    /**
     * 获取图层树
     * GET /api/v1/layers/tree
     */
    @GetMapping("/tree")
    public ResponseEntity<ApiResponse<LayerTreeNode>> getLayerTree(
            @RequestParam(required = false) String context,
            @RequestParam(required = false) String userId) {
        
        // 获取用户有权限访问的图层
        List<String> accessibleLayers = permissionService.getAccessibleLayers(userId);
        
        // 构建图层树
        LayerTreeNode root = layerMetadataService.buildLayerTree(
            accessibleLayers, 
            context
        );
        
        // 注入图层状态
        injectLayerStates(root, userId);
        
        return ApiResponse.success(root);
    }
    
    /**
     * 获取图层详情
     * GET /api/v1/layers/{layerId}
     */
    @GetMapping("/{layerId}")
    public ResponseEntity<ApiResponse<LayerDetail>> getLayerDetail(
            @PathVariable String layerId,
            @RequestParam(required = false) String userId) {
        
        // 检查权限
        if (!permissionService.hasAccess(userId, layerId, Permission.READ)) {
            return ApiResponse.error("无权访问该图层");
        }
        
        // 获取图层元数据
        LayerMetadata metadata = layerMetadataService.getLayerMetadata(layerId);
        
        // 获取图层数据统计
        LayerStatistics statistics = layerMetadataService.getLayerStatistics(layerId);
        
        // 获取图层样式配置
        LayerStyle style = layerMetadataService.getLayerStyle(layerId);
        
        // 构建响应
        LayerDetail detail = LayerDetail.builder()
            .metadata(metadata)
            .statistics(statistics)
            .style(style)
            .permissions(permissionService.getPermissions(userId, layerId))
            .lastUpdated(layerMetadataService.getLastUpdated(layerId))
            .build();
        
        return ApiResponse.success(detail);
    }
    
    /**
     * 更新图层状态
     * PUT /api/v1/layers/{layerId}/state
     */
    @PutMapping("/{layerId}/state")
    public ResponseEntity<ApiResponse<LayerState>> updateLayerState(
            @PathVariable String layerId,
            @RequestBody @Valid UpdateLayerStateRequest request,
            @RequestHeader("X-User-Id") String userId) {
        
        // 检查权限
        if (!permissionService.hasAccess(userId, layerId, Permission.WRITE)) {
            return ApiResponse.error("无权修改该图层状态");
        }
        
        // 更新图层状态
        LayerState newState = layerStateService.updateState(
            layerId, 
            userId, 
            request.getState()
        );
        
        // 发布状态变更事件
        eventPublisher.publishEvent(new LayerStateChangedEvent(
            layerId, 
            userId, 
            newState
        ));
        
        return ApiResponse.success(newState);
    }
    
    /**
     * 批量操作图层
     * POST /api/v1/layers/batch
     */
    @PostMapping("/batch")
    public ResponseEntity<ApiResponse<BatchLayerResult>> batchOperation(
            @RequestBody @Valid BatchLayerRequest request,
            @RequestHeader("X-User-Id") String userId) {
        
        BatchLayerResult result = new BatchLayerResult();
        List<LayerOperationResult> operationResults = new ArrayList<>();
        
        for (LayerOperation operation : request.getOperations()) {
            try {
                LayerOperationResult operationResult = executeLayerOperation(
                    operation, 
                    userId
                );
                operationResults.add(operationResult);
                
                if (!operationResult.isSuccess() && request.isStopOnError()) {
                    break;
                }
            } catch (Exception e) {
                log.error("Layer operation failed: {}", operation.getType(), e);
                operationResults.add(LayerOperationResult.failure(
                    operation.getLayerId(), 
                    e.getMessage()
                ));
                if (request.isStopOnError()) break;
            }
        }
        
        result.setOperations(operationResults);
        result.setSuccessCount((int) operationResults.stream()
            .filter(LayerOperationResult::isSuccess)
            .count());
        
        return ApiResponse.success(result);
    }
    
    /**
     * 图层订阅接口
     * POST /api/v1/layers/{layerId}/subscribe
     */
    @PostMapping("/{layerId}/subscribe")
    public ResponseEntity<ApiResponse<SubscriptionResult>> subscribeLayer(
            @PathVariable String layerId,
            @RequestBody @Valid SubscriptionRequest request,
            @RequestHeader("X-User-Id") String userId) {
        
        // 创建订阅
        Subscription subscription = Subscription.builder()
            .layerId(layerId)
            .userId(userId)
            .type(request.getType())
            .config(request.getConfig())
            .createdAt(System.currentTimeMillis())
            .build();
        
        // 保存订阅
        String subscriptionId = subscriptionService.subscribe(subscription);
        
        // 如果是WebSocket订阅,建立连接
        if (request.getType() == SubscriptionType.WEBSOCKET) {
            websocketService.registerSubscription(subscriptionId, userId, layerId);
        }
        
        SubscriptionResult result = SubscriptionResult.builder()
            .subscriptionId(subscriptionId)
            .layerId(layerId)
            .type(request.getType())
            .status(SubscriptionStatus.ACTIVE)
            .build();
        
        return ApiResponse.success(result);
    }
}

图层元数据管理服务:

@Service
@Slf4j
public class LayerMetadataServiceImpl implements LayerMetadataService {
    
    @Autowired
    private LayerRepository layerRepository;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Value("${layer.cache.ttl:3600}")
    private long cacheTtl;
    
    @Override
    public LayerMetadata getLayerMetadata(String layerId) {
        // 尝试从缓存获取
        String cacheKey = "layer:metadata:" + layerId;
        LayerMetadata cached = (LayerMetadata) redisTemplate.opsForValue().get(cacheKey);
        
        if (cached != null) {
            return cached;
        }
        
        // 从数据库获取
        LayerMetadata metadata = layerRepository.findById(layerId)
            .orElseThrow(() -> new ResourceNotFoundException("图层不存在: " + layerId));
        
        // 从天地图服务获取最新元数据
        LayerMetadata tiandituMetadata = tiandituService.getLayerMetadata(layerId);
        
        // 合并元数据
        metadata = mergeMetadata(metadata, tiandituMetadata);
        
        // 缓存结果
        redisTemplate.opsForValue().set(cacheKey, metadata, cacheTtl, TimeUnit.SECONDS);
        
        return metadata;
    }
    
    @Override
    public LayerTreeNode buildLayerTree(List<String> accessibleLayers, String context) {
        // 获取所有图层
        List<LayerMetadata> allLayers = layerRepository.findAll();
        
        // 过滤有权限的图层
        List<LayerMetadata> filteredLayers = allLayers.stream()
            .filter(layer -> accessibleLayers.contains(layer.getId()))
            .filter(layer -> isLayerInContext(layer, context))
            .collect(Collectors.toList());
        
        // 构建图层树
        Map<String, LayerTreeNode> nodeMap = new HashMap<>();
        LayerTreeNode root = new LayerTreeNode("root", "所有图层");
        
        for (LayerMetadata layer : filteredLayers) {
            LayerTreeNode node = convertToTreeNode(layer);
            nodeMap.put(layer.getId(), node);
        }
        
        // 建立父子关系
        for (LayerMetadata layer : filteredLayers) {
            LayerTreeNode node = nodeMap.get(layer.getId());
            LayerTreeNode parent = layer.getParentId() != null ? 
                nodeMap.get(layer.getParentId()) : root;
            
            if (parent != null) {
                parent.addChild(node);
            } else {
                root.addChild(node);
            }
        }
        
        // 排序
        sortTreeNodes(root);
        
        return root;
    }
    
    @Override
    public LayerStatistics getLayerStatistics(String layerId) {
        String cacheKey = "layer:stats:" + layerId;
        
        // 尝试从缓存获取
        LayerStatistics cached = (LayerStatistics) redisTemplate.opsForValue().get(cacheKey);
        if (cached != null) {
            return cached;
        }
        
        // 查询数据库统计
        LayerStatistics stats = calculateLayerStatistics(layerId);
        
        // 缓存统计结果(较长时间)
        redisTemplate.opsForValue().set(
            cacheKey, 
            stats, 
            cacheTtl * 24,  // 缓存24小时
            TimeUnit.SECONDS
        );
        
        return stats;
    }
    
    private LayerStatistics calculateLayerStatistics(String layerId) {
        // 获取图层空间范围
        BoundingBox bbox = spatialDataService.getLayerBoundingBox(layerId);
        
        // 获取要素数量
        long featureCount = spatialDataService.countFeatures(layerId);
        
        // 获取属性统计
        Map<String, AttributeStatistics> attrStats = spatialDataService
            .calculateAttributeStatistics(layerId);
        
        // 获取空间分布
        SpatialDistribution distribution = spatialDataService
            .analyzeSpatialDistribution(layerId);
        
        // 获取更新频率
        UpdateFrequency updateFreq = spatialDataService
            .getUpdateFrequency(layerId);
        
        return LayerStatistics.builder()
            .layerId(layerId)
            .boundingBox(bbox)
            .featureCount(featureCount)
            .attributeStatistics(attrStats)
            .spatialDistribution(distribution)
            .updateFrequency(updateFreq)
            .calculatedAt(System.currentTimeMillis())
            .build();
    }
}

第四部分:微服务架构设计与API网关

4.1 微服务拆分策略

API网关

认证服务

底图服务

图层服务

数据服务

分析服务

缓存服务

瓦片服务

样式服务

投影服务

元数据服务

权限服务

状态服务

查询服务

编辑服务

订阅服务

空间分析

路径分析

AI分析

Redis缓存

CDN加速

本地缓存

用户数据库

空间数据库

4.2 API网关配置示例

# api-gateway.yml
spring:
  cloud:
    gateway:
      routes:
        - id: basemap-service
          uri: lb://basemap-service
          predicates:
            - Path=/api/v1/basemap/**
          filters:
            - name: CircuitBreaker
              args:
                name: basemapService
                fallbackUri: forward:/fallback/basemap
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10
                redis-rate-limiter.burstCapacity: 20
            - name: Retry
              args:
                retries: 3
                series: SERVER_ERROR
        
        - id: layer-service
          uri: lb://layer-service
          predicates:
            - Path=/api/v1/layers/**
          filters:
            - name: AuthenticationFilter
            - name: CircuitBreaker
              args:
                name: layerService
                fallbackUri: forward:/fallback/layer
        
        - id: spatial-service
          uri: lb://spatial-service
          predicates:
            - Path=/api/v1/spatial/**
          filters:
            - name: AuthenticationFilter
            - name: CircuitBreaker
              args:
                name: spatialService
                fallbackUri: forward:/fallback/spatial
        
        - id: analysis-service
          uri: lb://analysis-service
          predicates:
            - Path=/api/v1/analysis/**
          filters:
            - name: AuthenticationFilter
            - name: CircuitBreaker
              args:
                name: analysisService
                fallbackUri: forward:/fallback/analysis
      
      default-filters:
        - name: GlobalLoggingFilter
        - name: RequestTimeFilter
        - name: AddResponseHeader
          args:
            name: X-API-Version
            value: v1.0.0

# 熔断器配置
resilience4j:
  circuitbreaker:
    instances:
      basemapService:
        slidingWindowSize: 10
        failureRateThreshold: 50
        waitDurationInOpenState: 10000
        permittedNumberOfCallsInHalfOpenState: 3
        automaticTransitionFromOpenToHalfOpenEnabled: true
  ratelimiter:
    instances:
      basemapService:
        limitForPeriod: 10
        limitRefreshPeriod: 1s
        timeoutDuration: 0s

4.3 服务注册与发现

// 服务注册配置
@Configuration
@EnableDiscoveryClient
public class ServiceDiscoveryConfig {
    
    @Bean
    public ServiceRegistry serviceRegistry() {
        return new ServiceRegistry();
    }
    
    @Bean
    @LoadBalanced
    public RestTemplate loadBalancedRestTemplate() {
        return new RestTemplate();
    }
}

// 健康检查端点
@RestController
@RequestMapping("/actuator")
public class HealthController {
    
    @Autowired
    private HealthIndicator healthIndicator;
    
    @Autowired
    private DataSource dataSource;
    
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;
    
    @GetMapping("/health")
    public ResponseEntity<Health> health() {
        Health.Builder builder = new Health.Builder();
        
        // 数据库健康检查
        try {
            dataSource.getConnection().close();
            builder.withDetail("database", "UP");
        } catch (Exception e) {
            builder.down().withDetail("database", "DOWN");
        }
        
        // Redis健康检查
        try {
            redisConnectionFactory.getConnection().ping();
            builder.withDetail("redis", "UP");
        } catch (Exception e) {
            builder.down().withDetail("redis", "DOWN");
        }
        
        // 天地图服务健康检查
        try {
            boolean tiandituHealthy = tiandituService.healthCheck();
            builder.withDetail("tianditu", tiandituHealthy ? "UP" : "DOWN");
        } catch (Exception e) {
            builder.down().withDetail("tianditu", "DOWN");
        }
        
        return ResponseEntity.ok(builder.build());
    }
    
    @GetMapping("/metrics/map")
    public ResponseEntity<MapMetrics> mapMetrics() {
        MapMetrics metrics = MapMetrics.builder()
            .activeSessions(sessionService.getActiveCount())
            .requestRate(requestRateCalculator.getRate())
            .cacheHitRate(cacheService.getHitRate())
            .averageResponseTime(responseTimeTracker.getAverage())
            .errorRate(errorRateCalculator.getRate())
            .build();
        
        return ResponseEntity.ok(metrics);
    }
}

第五部分:性能优化与监控

5.1 缓存策略设计

@Service
@Slf4j
public class MultiLevelCacheServiceImpl implements CacheService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private CaffeineCacheManager caffeineCacheManager;
    
    @Autowired
    private DatabaseCacheRepository dbCacheRepository;
    
    @Value("${cache.levels:2}")
    private int cacheLevels;
    
    @Override
    public <T> T get(String key, Class<T> type) {
        // 第一级:本地缓存
        T value = caffeineCacheManager.getCache("local").get(key, type);
        if (value != null) {
            log.debug("L1 cache hit: {}", key);
            return value;
        }
        
        // 第二级:Redis缓存
        value = (T) redisTemplate.opsForValue().get(key);
        if (value != null) {
            log.debug("L2 cache hit: {}", key);
            // 回写到本地缓存
            caffeineCacheManager.getCache("local").put(key, value);
            return value;
        }
        
        if (cacheLevels >= 3) {
            // 第三级:数据库缓存
            value = dbCacheRepository.findByKey(key).map(CacheEntry::getValue)
                .map(v -> jsonUtil.fromJson(v, type))
                .orElse(null);
            
            if (value != null) {
                log.debug("L3 cache hit: {}", key);
                // 回写到上层缓存
                redisTemplate.opsForValue().set(key, value, 1, TimeUnit.HOURS);
                caffeineCacheManager.getCache("local").put(key, value);
                return value;
            }
        }
        
        log.debug("Cache miss: {}", key);
        return null;
    }
    
    @Override
    public void put(String key, Object value, long ttl, TimeUnit unit) {
        if (value == null) return;
        
        // 计算各级缓存的TTL
        long l1Ttl = unit.toMillis(ttl) / 3;
        long l2Ttl = unit.toMillis(ttl) * 2 / 3;
        
        // 第一级:本地缓存
        caffeineCacheManager.getCache("local").put(key, value);
        
        // 第二级:Redis缓存
        redisTemplate.opsForValue().set(key, value, l2Ttl, TimeUnit.MILLISECONDS);
        
        if (cacheLevels >= 3) {
            // 第三级:数据库缓存
            CacheEntry entry = CacheEntry.builder()
                .key(key)
                .value(jsonUtil.toJson(value))
                .expireAt(System.currentTimeMillis() + unit.toMillis(ttl))
                .createdAt(System.currentTimeMillis())
                .build();
            
            dbCacheRepository.save(entry);
        }
    }
    
    @Override
    public double getHitRate() {
        long hits = stats.getHits();
        long misses = stats.getMisses();
        long total = hits + misses;
        
        return total > 0 ? (double) hits / total : 0.0;
    }
}

5.2 性能监控API

@RestController
@RequestMapping("/api/v1/monitor")
@Slf4j
public class PerformanceMonitorController {
    
    @Autowired
    private MetricsCollector metricsCollector;
    
    @Autowired
    private AlertService alertService;
    
    /**
     * 获取性能指标
     * GET /api/v1/monitor/metrics
     */
    @GetMapping("/metrics")
    public ResponseEntity<ApiResponse<PerformanceMetrics>> getMetrics(
            @RequestParam(required = false) Long startTime,
            @RequestParam(required = false) Long endTime,
            @RequestParam(defaultValue = "1h") String interval) {
        
        PerformanceMetrics metrics = metricsCollector.collectMetrics(
            startTime, 
            endTime, 
            interval
        );
        
        // 检查异常指标
        checkAnomalies(metrics);
        
        return ApiResponse.success(metrics);
    }
    
    /**
     * 获取慢查询分析
     * GET /api/v1/monitor/slow-queries
     */
    @GetMapping("/slow-queries")
    public ResponseEntity<ApiResponse<Page<SlowQuery>>> getSlowQueries(
            @RequestParam(defaultValue = "1000") long threshold,
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "20") int size) {
        
        Pageable pageable = PageRequest.of(page, size, Sort.by("duration").descending());
        Page<SlowQuery> slowQueries = slowQueryRepository.findByDurationGreaterThan(
            threshold, 
            pageable
        );
        
        return ApiResponse.success(slowQueries);
    }
    
    /**
     * 性能分析报告
     * POST /api/v1/monitor/analyze
     */
    @PostMapping("/analyze")
    public ResponseEntity<ApiResponse<PerformanceReport>> analyzePerformance(
            @RequestBody PerformanceAnalysisRequest request) {
        
        PerformanceReport report = PerformanceReport.builder()
            .timeRange(request.getTimeRange())
            .metrics(metricsCollector.collectMetrics(
                request.getStartTime(), 
                request.getEndTime(), 
                "5m"
            ))
            .analysis(analyzePerformanceMetrics(request))
            .recommendations(generateOptimizationRecommendations(request))
            .generatedAt(System.currentTimeMillis())
            .build();
        
        return ApiResponse.success(report);
    }
    
    private void checkAnomalies(PerformanceMetrics metrics) {
        // 检查响应时间异常
        if (metrics.getAverageResponseTime() > 1000) { // 超过1秒
            alertService.sendAlert(Alert.builder()
                .level(AlertLevel.WARNING)
                .type(AlertType.PERFORMANCE)
                .title("高响应时间告警")
                .message(String.format("平均响应时间: %.2fms", 
                    metrics.getAverageResponseTime()))
                .timestamp(System.currentTimeMillis())
                .build());
        }
        
        // 检查错误率异常
        if (metrics.getErrorRate() > 0.05) { // 错误率超过5%
            alertService.sendAlert(Alert.builder()
                .level(AlertLevel.ERROR)
                .type(AlertType.ERROR_RATE)
                .title("高错误率告警")
                .message(String.format("错误率: %.2f%%", 
                    metrics.getErrorRate() * 100))
                .timestamp(System.currentTimeMillis())
                .build());
        }
        
        // 检查缓存命中率异常
        if (metrics.getCacheHitRate() < 0.7) { // 缓存命中率低于70%
            alertService.sendAlert(Alert.builder()
                .level(AlertLevel.WARNING)
                .type(AlertType.CACHE)
                .title("低缓存命中率告警")
                .message(String.format("缓存命中率: %.2f%%", 
                    metrics.getCacheHitRate() * 100))
                .timestamp(System.currentTimeMillis())
                .build());
        }
    }
}

第六部分:安全设计与API防护

6.1 认证与授权

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Autowired
    private JwtTokenProvider jwtTokenProvider;
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeRequests()
                .antMatchers("/api/v1/auth/**").permitAll()
                .antMatchers("/api/v1/public/**").permitAll()
                .antMatchers("/api/v1/basemap/**").hasAnyRole("USER", "ADMIN")
                .antMatchers("/api/v1/layers/**").hasAnyRole("USER", "ADMIN")
                .antMatchers("/api/v1/spatial/**").hasAnyRole("USER", "ADMIN")
                .antMatchers("/api/v1/analysis/**").hasAnyRole("ANALYST", "ADMIN")
                .antMatchers("/api/v1/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            .and()
            .apply(new JwtConfigurer(jwtTokenProvider))
            .and()
            .exceptionHandling()
                .authenticationEntryPoint(new JwtAuthenticationEntryPoint())
                .accessDeniedHandler(new JwtAccessDeniedHandler());
    }
    
    @Bean
    public JwtTokenFilter jwtTokenFilter() {
        return new JwtTokenFilter(jwtTokenProvider);
    }
}

@Component
public class LayerPermissionEvaluator implements PermissionEvaluator {
    
    @Autowired
    private LayerPermissionService permissionService;
    
    @Override
    public boolean hasPermission(Authentication authentication, 
                               Object targetDomainObject, 
                               Object permission) {
        if (authentication == null || !authentication.isAuthenticated()) {
            return false;
        }
        
        String userId = authentication.getName();
        String layerId = (String) targetDomainObject;
        Permission requiredPermission = Permission.valueOf(permission.toString());
        
        return permissionService.hasAccess(userId, layerId, requiredPermission);
    }
    
    @Override
    public boolean hasPermission(Authentication authentication, 
                               Serializable targetId, 
                               String targetType, 
                               Object permission) {
        return hasPermission(authentication, targetId, permission);
    }
}

@RestController
@RequestMapping("/api/v1/auth")
public class AuthController {
    
    @Autowired
    private AuthenticationManager authenticationManager;
    
    @Autowired
    private JwtTokenProvider jwtTokenProvider;
    
    @Autowired
    private UserService userService;
    
    /**
     * 用户登录
     * POST /api/v1/auth/login
     */
    @PostMapping("/login")
    public ResponseEntity<ApiResponse<AuthResponse>> login(
            @RequestBody @Valid LoginRequest request) {
        
        Authentication authentication = authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(
                request.getUsername(),
                request.getPassword()
            )
        );
        
        SecurityContextHolder.getContext().setAuthentication(authentication);
        
        String jwt = jwtTokenProvider.createToken(
            authentication.getName(),
            authentication.getAuthorities()
        );
        
        User user = userService.findByUsername(request.getUsername());
        
        AuthResponse response = AuthResponse.builder()
            .token(jwt)
            .tokenType("Bearer")
            .expiresIn(jwtTokenProvider.getValidityInSeconds())
            .user(user)
            .build();
        
        return ApiResponse.success(response);
    }
    
    /**
     * 刷新Token
     * POST /api/v1/auth/refresh
     */
    @PostMapping("/refresh")
    public ResponseEntity<ApiResponse<AuthResponse>> refreshToken(
            @RequestHeader("Authorization") String authorizationHeader) {
        
        String token = authorizationHeader.substring(7); // 去掉"Bearer "
        
        if (jwtTokenProvider.validateToken(token)) {
            String username = jwtTokenProvider.getUsername(token);
            User user = userService.findByUsername(username);
            
            String newToken = jwtTokenProvider.createToken(
                username,
                user.getAuthorities()
            );
            
            AuthResponse response = AuthResponse.builder()
                .token(newToken)
                .tokenType("Bearer")
                .expiresIn(jwtTokenProvider.getValidityInSeconds())
                .user(user)
                .build();
            
            return ApiResponse.success(response);
        }
        
        return ApiResponse.error("无效的Token");
    }
}

6.2 速率限制与防攻击

@Configuration
public class RateLimitConfig {
    
    @Bean
    public KeyResolver ipKeyResolver() {
        return exchange -> Mono.just(
            exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
        );
    }
    
    @Bean
    public KeyResolver userKeyResolver() {
        return exchange -> {
            String token = exchange.getRequest().getHeaders()
                .getFirst("Authorization");
            if (token != null && token.startsWith("Bearer ")) {
                String username = jwtTokenProvider.getUsername(token.substring(7));
                return Mono.just(username);
            }
            return Mono.just("anonymous");
        };
    }
    
    @Bean
    public KeyResolver apiKeyResolver() {
        return exchange -> {
            String apiKey = exchange.getRequest().getQueryParams()
                .getFirst("api_key");
            if (apiKey != null) {
                return Mono.just(apiKey);
            }
            return Mono.just("default");
        };
    }
}

@Service
public class ApiSecurityServiceImpl implements ApiSecurityService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Value("${security.rate-limit.enabled:true}")
    private boolean rateLimitEnabled;
    
    @Value("${security.rate-limit.ip.limit:100}")
    private int ipLimit;
    
    @Value("${security.rate-limit.ip.period:3600}")
    private int ipPeriod;
    
    @Value("${security.rate-limit.user.limit:1000}")
    private int userLimit;
    
    @Value("${security.rate-limit.user.period:3600}")
    private int userPeriod;
    
    @Override
    public boolean checkRateLimit(HttpServletRequest request, String userId) {
        if (!rateLimitEnabled) {
            return true;
        }
        
        String ip = getClientIp(request);
        String userKey = userId != null ? userId : "anonymous";
        
        // IP限制检查
        if (!checkIpLimit(ip)) {
            log.warn("IP rate limit exceeded: {}", ip);
            return false;
        }
        
        // 用户限制检查
        if (!checkUserLimit(userKey)) {
            log.warn("User rate limit exceeded: {}", userKey);
            return false;
        }
        
        // API密钥限制检查
        String apiKey = request.getParameter("api_key");
        if (apiKey != null && !checkApiKeyLimit(apiKey)) {
            log.warn("API key rate limit exceeded: {}", apiKey);
            return false;
        }
        
        return true;
    }
    
    private boolean checkIpLimit(String ip) {
        String key = "rate_limit:ip:" + ip;
        return checkLimit(key, ipLimit, ipPeriod);
    }
    
    private boolean checkUserLimit(String userKey) {
        String key = "rate_limit:user:" + userKey;
        return checkLimit(key, userLimit, userPeriod);
    }
    
    private boolean checkApiKeyLimit(String apiKey) {
        String key = "rate_limit:api_key:" + apiKey;
        ApiKeyInfo apiKeyInfo = apiKeyService.getApiKeyInfo(apiKey);
        int limit = apiKeyInfo != null ? apiKeyInfo.getRateLimit() : 100;
        int period = apiKeyInfo != null ? apiKeyInfo.getRatePeriod() : 3600;
        return checkLimit(key, limit, period);
    }
    
    private boolean checkLimit(String key, int limit, int period) {
        Long current = redisTemplate.opsForValue().increment(key, 1);
        
        if (current == 1) {
            redisTemplate.expire(key, period, TimeUnit.SECONDS);
        }
        
        return current <= limit;
    }
    
    @Override
    public boolean checkRequestValidity(HttpServletRequest request) {
        // 检查请求头完整性
        if (!checkHeaders(request)) {
            return false;
        }
        
        // 检查参数合法性
        if (!checkParameters(request)) {
            return false;
        }
        
        // 检查请求频率
        if (!checkRequestFrequency(request)) {
            return false;
        }
        
        // 检查恶意请求模式
        if (isMaliciousRequest(request)) {
            return false;
        }
        
        return true;
    }
    
    @Override
    public void logSecurityEvent(SecurityEvent event) {
        SecurityLog securityLog = SecurityLog.builder()
            .eventType(event.getEventType())
            .userId(event.getUserId())
            .ip(event.getIp())
            .userAgent(event.getUserAgent())
            .requestPath(event.getRequestPath())
            .requestParams(event.getRequestParams())
            .timestamp(System.currentTimeMillis())
            .build();
        
        // 保存到数据库
        securityLogRepository.save(securityLog);
        
        // 发送到消息队列进行实时分析
        kafkaTemplate.send("security-events", securityLog);
        
        // 检查是否需要触发警报
        checkAndTriggerAlert(event);
    }
}

第七部分:部署与运维

7.1 Docker容器化部署

# Dockerfile
FROM openjdk:11-jre-slim

# 设置环境变量
ENV SPRING_PROFILES_ACTIVE=prod
ENV JAVA_OPTS="-Xms512m -Xmx2g -XX:+UseG1GC"
ENV TZ=Asia/Shanghai

# 创建应用目录
WORKDIR /app

# 添加时区配置
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

# 复制应用
COPY target/tianditu-api.jar app.jar
COPY config/application-prod.yml config/
COPY config/logback-spring.xml config/
COPY scripts/entrypoint.sh .

# 创建非root用户
RUN groupadd -r spring && useradd -r -g spring spring
RUN chown -R spring:spring /app
USER spring

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
  CMD curl -f http://localhost:8080/actuator/health || exit 1

# 暴露端口
EXPOSE 8080

# 启动应用
ENTRYPOINT ["./entrypoint.sh"]
# docker-compose.yml
version: '3.8'

services:
  # API网关
  api-gateway:
    build: ./api-gateway
    ports:
      - "80:8080"
      - "443:8443"
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - EUREKA_CLIENT_SERVICEURL_DEFAULTZONE=http://discovery:8761/eureka
    depends_on:
      - discovery
      - config-server
    networks:
      - tianditu-network
    deploy:
      replicas: 3
      update_config:
        parallelism: 1
        delay: 10s
      restart_policy:
        condition: on-failure
        delay: 5s
      resources:
        limits:
          cpus: '1'
          memory: 1G
        reservations:
          cpus: '0.5'
          memory: 512M

  # 服务发现
  discovery:
    image: netflixoss/eureka:2.0.0
    ports:
      - "8761:8761"
    networks:
      - tianditu-network

  # 配置中心
  config-server:
    build: ./config-server
    environment:
      - SPRING_PROFILES_ACTIVE=native
    volumes:
      - ./config:/config
    networks:
      - tianditu-network

  # 底图服务
  basemap-service:
    build: ./basemap-service
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - REDIS_HOST=redis
      - POSTGRES_HOST=postgres
    depends_on:
      - redis
      - postgres
    networks:
      - tianditu-network
    deploy:
      replicas: 2
      resources:
        limits:
          cpus: '2'
          memory: 2G

  # 图层服务
  layer-service:
    build: ./layer-service
    environment:
      - SPRING_PROFILES_ACTIVE=prod
    networks:
      - tianditu-network

  # 数据库
  postgres:
    image: postgis/postgis:13-3.1
    environment:
      - POSTGRES_DB=tianditu
      - POSTGRES_USER=tianditu
      - POSTGRES_PASSWORD=tianditu123
    volumes:
      - postgres-data:/var/lib/postgresql/data
      - ./initdb:/docker-entrypoint-initdb.d
    ports:
      - "5432:5432"
    networks:
      - tianditu-network
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 4G

  # Redis缓存
  redis:
    image: redis:6-alpine
    command: redis-server --appendonly yes --requirepass tianditu123
    volumes:
      - redis-data:/data
    ports:
      - "6379:6379"
    networks:
      - tianditu-network

  # 监控
  prometheus:
    image: prom/prometheus
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"
    networks:
      - tianditu-network

  grafana:
    image: grafana/grafana
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin123
    volumes:
      - grafana-data:/var/lib/grafana
    ports:
      - "3000:3000"
    networks:
      - tianditu-network

  # 日志收集
  elk:
    image: sebp/elk
    ports:
      - "5601:5601"
      - "9200:9200"
      - "5044:5044"
    networks:
      - tianditu-network

networks:
  tianditu-network:
    driver: bridge

volumes:
  postgres-data:
  redis-data:
  grafana-data:

7.2 Kubernetes部署配置

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tianditu-api
  namespace: tianditu
  labels:
    app: tianditu-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: tianditu-api
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: tianditu-api
    spec:
      containers:
      - name: tianditu-api
        image: registry.example.com/tianditu-api:1.0.0
        imagePullPolicy: Always
        ports:
        - containerPort: 8080
        env:
        - name: SPRING_PROFILES_ACTIVE
          value: "k8s"
        - name: JAVA_OPTS
          value: "-Xms512m -Xmx2g -XX:+UseG1GC"
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "2Gi"
            cpu: "1"
        livenessProbe:
          httpGet:
            path: /actuator/health/liveness
            port: 8080
          initialDelaySeconds: 60
          periodSeconds: 10
          timeoutSeconds: 5
          failureThreshold: 3
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 5
          timeoutSeconds: 3
        startupProbe:
          httpGet:
            path: /actuator/health/startup
            port: 8080
          failureThreshold: 30
          periodSeconds: 10
        volumeMounts:
        - name: config
          mountPath: /app/config
        - name: logs
          mountPath: /app/logs
      volumes:
      - name: config
        configMap:
          name: tianditu-config
      - name: logs
        emptyDir: {}
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - tianditu-api
              topologyKey: kubernetes.io/hostname
      tolerations:
      - key: "dedicated"
        operator: "Equal"
        value: "tianditu"
        effect: "NoSchedule"
---
# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: tianditu-service
  namespace: tianditu
spec:
  selector:
    app: tianditu-api
  ports:
  - name: http
    port: 80
    targetPort: 8080
  - name: https
    port: 443
    targetPort: 8443
  type: LoadBalancer
  sessionAffinity: ClientIP
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 10800
---
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: tianditu-ingress
  namespace: tianditu
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    nginx.ingress.kubernetes.io/rewrite-target: /$2
    nginx.ingress.kubernetes.io/proxy-body-size: "20m"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - api.tianditu.example.com
    secretName: tianditu-tls
  rules:
  - host: api.tianditu.example.com
    http:
      paths:
      - path: /api(/|$)(.*)
        pathType: Prefix
        backend:
          service:
            name: tianditu-service
            port:
              number: 80
      - path: /
        pathType: Prefix
        backend:
          service:
            name: tianditu-service
            port:
              number: 80
---
# hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: tianditu-api-hpa
  namespace: tianditu
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: tianditu-api
  minReplicas: 3
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80
  - type: Pods
    pods:
      metric:
        name: http_requests_per_second
      target:
        type: AverageValue
        averageValue: 100
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
      - type: Percent
        value: 10
        periodSeconds: 60
    scaleUp:
      stabilizationWindowSeconds: 0
      policies:
      - type: Percent
        value: 100
        periodSeconds: 60

结语

天地图作为国家地理信息公共服务平台,其深度应用需要前后端协同设计。本文从前端操作到后端API,从单体架构到微服务,从基础功能到AI赋能,系统性地介绍了天地图的全面应用方案。

核心要点总结:

  1. 前后端分离架构:通过RESTful API和WebSocket实现前后端解耦
  2. 微服务化设计:将系统拆分为独立可部署的服务单元
  3. 智能化优化:利用AI技术实现智能推荐、预测和优化
  4. 性能与安全:多级缓存、限流熔断、全方位安全防护
  5. 可观测性:完善的监控、日志和告警体系
  6. 云原生部署:容器化和Kubernetes编排实现弹性伸缩

未来发展方向:

  1. Serverless架构:将部分服务改造为无服务器函数
  2. 边缘计算:在边缘节点部署GIS计算能力
  3. 区块链存证:利用区块链技术确保地理数据的不可篡改性
  4. AI原生设计:从设计之初就深度集成AI能力

在数字中国建设的大背景下,天地图的深度应用不仅是技术挑战,更是业务创新的机会。希望本文能够为您的地理信息应用开发提供全面的技术参考和架构指导。


欢迎在评论区交流探讨,共同推动地理信息技术的发展与创新!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐