使用Java集成高德地图MCP服务,打造智能化的定制旅游路线规划系统。本文将带你从零开始,掌握高德地图Java SDK的使用技巧,实现完整的旅游路线规划功能。
高德地图MCP简介
高德地图MCP(Maps Content Platform)是高德地图提供的一套开放平台服务,开发者可以通过API调用获取丰富的地图数据和地理信息服务。高德地图Java SDK提供了完整的Java封装,让Java开发者能够轻松集成高德地图的各项功能。
为什么选择高德地图MCP?
- 数据丰富准确:中国最权威的地图数据提供商
- 服务稳定可靠:企业级SLA保证,服务可用性99.9%
- 功能全面:覆盖路径规划、地理编码、POI搜索等全方位服务
- Java SDK完善:官方维护的Java SDK,更新及时
- 成本可控:按需付费,适合各种规模应用
- 合规安全:符合中国法律法规,支持数据加密传输
核心功能详解
1. POI搜索(兴趣点搜索)
POI(Point of Interest)搜索是地图应用的基础功能,用于查找附近的景点、餐厅、酒店等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| @Service @Slf4j public class PoiSearchService {
private final AmapRestTemplate restTemplate; private final String apiKey;
public PoiSearchService(@Value("${amap.api-key}") String apiKey) { this.apiKey = apiKey; this.restTemplate = new AmapRestTemplate(); }
public List<PoiInfo> searchNearbyAttractions(String location, int radius) { String url = String.format( "https://restapi.amap.com/v3/place/around?key=%s&location=%s&radius=%d&types=110200|110201", apiKey, location, radius );
try { AmapPoiResponse response = restTemplate.getForObject(url, AmapPoiResponse.class);
if (response != null && "1".equals(response.getStatus())) { return response.getPois().stream() .map(this::convertToPoiInfo) .collect(Collectors.toList()); }
return Collections.emptyList(); } catch (Exception e) { log.error("POI搜索失败", e); return Collections.emptyList(); } }
public List<PoiInfo> searchAttractionsByKeyword(String keyword, String city) { String url = String.format( "https://restapi.amap.com/v3/place/text?key=%s&keywords=%s&city=%s&types=110200", apiKey, URLEncoder.encode(keyword, StandardCharsets.UTF_8), city );
try { AmapPoiResponse response = restTemplate.getForObject(url, AmapPoiResponse.class);
if (response != null && "1".equals(response.getStatus())) { return response.getPois().stream() .map(this::convertToPoiInfo) .collect(Collectors.toList()); }
return Collections.emptyList(); } catch (Exception e) { log.error("关键词搜索失败", e); return Collections.emptyList(); } }
private PoiInfo convertToPoiInfo(AmapPoi amapPoi) { return PoiInfo.builder() .id(amapPoi.getId()) .name(amapPoi.getName()) .location(amapPoi.getLocation()) .address(amapPoi.getAddress()) .tel(amapPoi.getTel()) .tag(amapPoi.getTag()) .website(amapPoi.getWebsite()) .photos(parsePhotos(amapPoi.getPhotos())) .build(); }
private List<String> parsePhotos(String photos) { if (StringUtils.isEmpty(photos)) { return Collections.emptyList(); }
return Arrays.stream(photos.split(";")) .filter(StringUtils::isNotEmpty) .collect(Collectors.toList()); } }
|
2. 路径规划
路径规划是旅游应用的核心功能,支持驾车、公交、地铁等多种出行方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
| @Service public class RoutePlanningService {
private final AmapRestTemplate restTemplate; private final String apiKey;
public RoutePlanningService(@Value("${amap.api-key}") String apiKey) { this.apiKey = apiKey; this.restTemplate = new AmapRestTemplate(); }
public DrivingRoute planDrivingRoute(String origin, String destination) { String url = String.format( "https://restapi.amap.com/v3/direction/driving?key=%s&origin=%s&destination=%s&extensions=all", apiKey, origin, destination );
try { AmapDrivingResponse response = restTemplate.getForObject(url, AmapDrivingResponse.class);
if (response != null && "1".equals(response.getStatus())) { return convertToDrivingRoute(response); }
throw new RoutePlanningException("路径规划失败:" + response.getInfo()); } catch (Exception e) { log.error("驾车路径规划失败", e); throw new RoutePlanningException("路径规划服务暂时不可用", e); } }
public BusRoute planBusRoute(String origin, String destination, String city) { String url = String.format( "https://restapi.amap.com/v3/direction/transit/integrated?key=%s&origin=%s&destination=%s&city=%s&extensions=all", apiKey, origin, destination, city );
try { AmapBusResponse response = restTemplate.getForObject(url, AmapBusResponse.class);
if (response != null && "1".equals(response.getStatus())) { return convertToBusRoute(response); }
throw new RoutePlanningException("公交路径规划失败:" + response.getInfo()); } catch (Exception e) { log.error("公交路径规划失败", e); throw new RoutePlanningException("公交路径规划服务暂时不可用", e); } }
public WalkingRoute planWalkingRoute(String origin, String destination) { String url = String.format( "https://restapi.amap.com/v3/direction/walking?key=%s&origin=%s&destination=%s", apiKey, origin, destination );
try { AmapWalkingResponse response = restTemplate.getForObject(url, AmapWalkingResponse.class);
if (response != null && "1".equals(response.getStatus())) { return convertToWalkingRoute(response); }
throw new RoutePlanningException("步行路径规划失败:" + response.getInfo()); } catch (Exception e) { log.error("步行路径规划失败", e); throw new RoutePlanningException("步行路径规划服务暂时不可用", e); } }
private DrivingRoute convertToDrivingRoute(AmapDrivingResponse response) { return DrivingRoute.builder() .distance(response.getRoute().getPaths().get(0).getDistance()) .duration(response.getRoute().getPaths().get(0).getDuration()) .taxiCost(response.getRoute().getPaths().get(0).getTaxiCost()) .steps(convertDrivingSteps(response.getRoute().getPaths().get(0).getSteps())) .build(); }
private List<DrivingStep> convertDrivingSteps(List<AmapDrivingStep> steps) { return steps.stream() .map(step -> DrivingStep.builder() .instruction(step.getInstruction()) .orientation(step.getOrientation()) .road(step.getRoad()) .distance(step.getDistance()) .duration(step.getDuration()) .polyline(step.getPolyline()) .build()) .collect(Collectors.toList()); } }
|
3. 地理编码与逆地理编码
地理编码可以将地址转换为坐标,逆地理编码可以将坐标转换为地址信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
| @Service public class GeocodingService {
private final AmapRestTemplate restTemplate; private final String apiKey;
public GeocodingService(@Value("${amap.api-key}") String apiKey) { this.apiKey = apiKey; this.restTemplate = new AmapRestTemplate(); }
public List<GeocodeResult> geocode(String address, String city) { String url = String.format( "https://restapi.amap.com/v3/geocode/geo?key=%s&address=%s&city=%s", apiKey, URLEncoder.encode(address, StandardCharsets.UTF_8), URLEncoder.encode(city, StandardCharsets.UTF_8) );
try { AmapGeocodeResponse response = restTemplate.getForObject(url, AmapGeocodeResponse.class);
if (response != null && "1".equals(response.getStatus())) { return response.getGeocodes().stream() .map(this::convertToGeocodeResult) .collect(Collectors.toList()); }
return Collections.emptyList(); } catch (Exception e) { log.error("地理编码失败", e); return Collections.emptyList(); } }
public RegeocodeResult regeocode(String location) { String url = String.format( "https://restapi.amap.com/v3/geocode/regeo?key=%s&location=%s&extensions=all", apiKey, location );
try { AmapRegeocodeResponse response = restTemplate.getForObject(url, AmapRegeocodeResponse.class);
if (response != null && "1".equals(response.getStatus())) { return convertToRegeocodeResult(response); }
throw new GeocodingException("逆地理编码失败:" + response.getInfo()); } catch (Exception e) { log.error("逆地理编码失败", e); throw new GeocodingException("逆地理编码服务暂时不可用", e); } }
public List<GeocodeResult> batchGeocode(List<String> addresses, String city) { return addresses.stream() .map(address -> geocode(address, city)) .flatMap(List::stream) .collect(Collectors.toList()); }
private GeocodeResult convertToGeocodeResult(AmapGeocode geocode) { return GeocodeResult.builder() .formattedAddress(geocode.getFormattedAddress()) .location(geocode.getLocation()) .province(geocode.getProvince()) .city(geocode.getCity()) .district(geocode.getDistrict()) .township(geocode.getTownship()) .neighborhood(geocode.getNeighborhood()) .building(geocode.getBuilding()) .adcode(geocode.getAdcode()) .build(); }
private RegeocodeResult convertToRegeocodeResult(AmapRegeocodeResponse response) { AmapRegeocode regeocode = response.getRegeocode();
return RegeocodeResult.builder() .formattedAddress(regeocode.getFormattedAddress()) .addressComponent(convertAddressComponent(regeocode.getAddressComponent())) .roads(convertRoads(regeocode.getRoads())) .pois(convertPois(regeocode.getPois())) .aois(convertAois(regeocode.getAois())) .build(); } }
|
4. 距离测量
计算两个地点之间的距离,支持直线距离和驾车距离。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
| @Service public class DistanceService {
private final AmapRestTemplate restTemplate; private final String apiKey;
public DistanceService(@Value("${amap.api-key}") String apiKey) { this.apiKey = apiKey; this.restTemplate = new AmapRestTemplate(); }
public double calculateStraightDistance(String origin, String destination) { try { String[] originCoords = origin.split(","); String[] destCoords = destination.split(",");
double lat1 = Double.parseDouble(originCoords[1]); double lon1 = Double.parseDouble(originCoords[0]); double lat2 = Double.parseDouble(destCoords[1]); double lon2 = Double.parseDouble(destCoords[0]);
return calculateHaversineDistance(lat1, lon1, lat2, lon2); } catch (Exception e) { log.error("计算直线距离失败", e); return 0.0; } }
public DrivingDistance calculateDrivingDistance(String origin, String destination) { String url = String.format( "https://restapi.amap.com/v3/distance?key=%s&origins=%s&destination=%s&type=1", apiKey, origin, destination );
try { AmapDistanceResponse response = restTemplate.getForObject(url, AmapDistanceResponse.class);
if (response != null && "1".equals(response.getStatus())) { AmapDistanceResult result = response.getResults().get(0); return DrivingDistance.builder() .distance(result.getDistance()) .duration(result.getDuration()) .build(); }
throw new DistanceCalculationException("驾车距离计算失败:" + response.getInfo()); } catch (Exception e) { log.error("计算驾车距离失败", e); throw new DistanceCalculationException("距离计算服务暂时不可用", e); } }
public List<DrivingDistance> batchCalculateDistances(String origins, String destination) { String url = String.format( "https://restapi.amap.com/v3/distance?key=%s&origins=%s&destination=%s&type=1", apiKey, origins, destination );
try { AmapDistanceResponse response = restTemplate.getForObject(url, AmapDistanceResponse.class);
if (response != null && "1".equals(response.getStatus())) { return response.getResults().stream() .map(result -> DrivingDistance.builder() .distance(result.getDistance()) .duration(result.getDuration()) .build()) .collect(Collectors.toList()); }
return Collections.emptyList(); } catch (Exception e) { log.error("批量计算距离失败", e); return Collections.emptyList(); } }
private double calculateHaversineDistance(double lat1, double lon1, double lat2, double lon2) { final int EARTH_RADIUS = 6371;
double latDistance = Math.toRadians(lat2 - lat1); double lonDistance = Math.toRadians(lon2 - lon1);
double a = Math.sin(latDistance / 2) * Math.sin(latDistance / 2) + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * Math.sin(lonDistance / 2) * Math.sin(lonDistance / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return EARTH_RADIUS * c; } }
|
实战案例:智能旅游路线规划系统
项目结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| java-amap-tour-planning/ ├── src/main/java/com/example/amap/ │ ├── config/ # 配置类 │ │ ├── AmapConfig.java # 高德地图配置 │ │ └── WebConfig.java # Web配置 │ ├── controller/ # 控制器 │ │ ├── TourController.java # 旅游路线接口 │ │ └── MapController.java # 地图服务接口 │ ├── service/ # 业务服务 │ │ ├── PoiSearchService.java # POI搜索服务 │ │ ├── RoutePlanningService.java # 路线规划服务 │ │ ├── GeocodingService.java # 地理编码服务 │ │ └── DistanceService.java # 距离计算服务 │ ├── model/ # 数据模型 │ │ ├── request/ # 请求模型 │ │ │ ├── TourPlanRequest.java │ │ │ └── RouteRequest.java │ │ ├── response/ # 响应模型 │ │ │ ├── TourPlanResponse.java │ │ │ └── RouteResponse.java │ │ └── entity/ # 实体模型 │ │ ├── PoiInfo.java │ │ ├── Route.java │ │ └── Location.java │ ├── exception/ # 异常处理 │ │ ├── AmapApiException.java │ │ └── GlobalExceptionHandler.java │ └── utils/ # 工具类 │ ├── AmapRestTemplate.java # 高德API工具 │ └── LocationUtils.java # 位置工具类 ├── src/main/resources/ │ ├── application.yml # 应用配置 │ └── static/ # 静态资源 └── pom.xml # Maven依赖
|
Maven依赖配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
<dependency> <groupId>com.amap.api</groupId> <artifactId>amap-java-sdk</artifactId> <version>1.0.0</version> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency>
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency>
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>
|
核心控制器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| @RestController @RequestMapping("/api/tour") @RequiredArgsConstructor @Slf4j public class TourController {
private final TourPlanningService tourPlanningService; private final PoiSearchService poiSearchService; private final RoutePlanningService routePlanningService;
@PostMapping("/plan") public ResponseEntity<TourPlanResponse> createTourPlan(@Valid @RequestBody TourPlanRequest request) { log.info("创建旅游计划:{}", request.getDestination());
try { TourPlanResponse response = tourPlanningService.createTourPlan(request); return ResponseEntity.ok(response); } catch (Exception e) { log.error("创建旅游计划失败", e); return ResponseEntity.internalServerError() .body(TourPlanResponse.error("创建旅游计划失败:" + e.getMessage())); } }
@GetMapping("/attractions") public ResponseEntity<List<PoiInfo>> searchAttractions( @RequestParam String location, @RequestParam(defaultValue = "5000") int radius) {
log.info("搜索景点:位置={}, 半径={}", location, radius);
try { List<PoiInfo> attractions = poiSearchService.searchNearbyAttractions(location, radius); return ResponseEntity.ok(attractions); } catch (Exception e) { log.error("搜索景点失败", e); return ResponseEntity.internalServerError().build(); } }
@PostMapping("/route") public ResponseEntity<RouteResponse> planRoute(@Valid @RequestBody RouteRequest request) { log.info("规划路线:从{}到{}", request.getOrigin(), request.getDestination());
try { RouteResponse response = routePlanningService.planRoute(request); return ResponseEntity.ok(response); } catch (Exception e) { log.error("路线规划失败", e); return ResponseEntity.internalServerError() .body(RouteResponse.error("路线规划失败:" + e.getMessage())); } }
@GetMapping("/attractions/search") public ResponseEntity<List<PoiInfo>> searchAttractionsByKeyword( @RequestParam String keyword, @RequestParam String city) {
log.info("关键词搜索景点:keyword={}, city={}", keyword, city);
try { List<PoiInfo> attractions = poiSearchService.searchAttractionsByKeyword(keyword, city); return ResponseEntity.ok(attractions); } catch (Exception e) { log.error("关键词搜索景点失败", e); return ResponseEntity.internalServerError().build(); } } }
|
旅游路线规划服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
| @Service @Slf4j public class TourPlanningService {
private final PoiSearchService poiSearchService; private final RoutePlanningService routePlanningService; private final GeocodingService geocodingService; private final DistanceService distanceService;
public TourPlanningService(PoiSearchService poiSearchService, RoutePlanningService routePlanningService, GeocodingService geocodingService, DistanceService distanceService) { this.poiSearchService = poiSearchService; this.routePlanningService = routePlanningService; this.geocodingService = geocodingService; this.distanceService = distanceService; }
public TourPlanResponse createTourPlan(TourPlanRequest request) { try { List<PoiInfo> attractions = poiSearchService.searchNearbyAttractions( request.getDestination(), request.getSearchRadius());
List<PoiInfo> filteredAttractions = filterAttractionsByPreferences( attractions, request.getPreferences());
List<PoiInfo> sortedAttractions = sortAttractionsByPopularity(filteredAttractions);
List<TourDay> tourDays = generateTourSchedule( sortedAttractions, request.getDays(), request.getStartLocation());
TourStatistics statistics = calculateTourStatistics(tourDays);
return TourPlanResponse.builder() .destination(request.getDestination()) .days(request.getDays()) .attractions(sortedAttractions) .tourDays(tourDays) .statistics(statistics) .success(true) .build();
} catch (Exception e) { log.error("创建旅游计划失败", e); return TourPlanResponse.error("创建旅游计划失败:" + e.getMessage()); } }
private List<PoiInfo> filterAttractionsByPreferences(List<PoiInfo> attractions, List<String> preferences) { if (preferences == null || preferences.isEmpty()) { return attractions; }
return attractions.stream() .filter(attraction -> preferences.stream() .anyMatch(pref -> attraction.getTag().contains(pref))) .collect(Collectors.toList()); }
private List<PoiInfo> sortAttractionsByPopularity(List<PoiInfo> attractions) { return attractions.stream() .sorted((a, b) -> Integer.compare(b.getName().length(), a.getName().length())) .collect(Collectors.toList()); }
private List<TourDay> generateTourSchedule(List<PoiInfo> attractions, int days, String startLocation) { List<TourDay> tourDays = new ArrayList<>();
int attractionsPerDay = (int) Math.ceil((double) attractions.size() / days);
for (int day = 1; day <= days; day++) { int startIndex = (day - 1) * attractionsPerDay; int endIndex = Math.min(startIndex + attractionsPerDay, attractions.size());
List<PoiInfo> dayAttractions = attractions.subList(startIndex, endIndex);
List<Route> dayRoutes = new ArrayList<>(); if (!dayAttractions.isEmpty()) { dayRoutes = planDayRoutes(startLocation, dayAttractions); }
TourDay tourDay = TourDay.builder() .day(day) .attractions(dayAttractions) .routes(dayRoutes) .build();
tourDays.add(tourDay); }
return tourDays; }
private List<Route> planDayRoutes(String startLocation, List<PoiInfo> attractions) { List<Route> routes = new ArrayList<>();
String currentLocation = startLocation;
for (PoiInfo attraction : attractions) { try { RouteRequest routeRequest = RouteRequest.builder() .origin(currentLocation) .destination(attraction.getLocation()) .transportMode("driving") .build();
RouteResponse routeResponse = routePlanningService.planRoute(routeRequest);
if (routeResponse.isSuccess()) { routes.add(routeResponse.getRoute()); currentLocation = attraction.getLocation(); } } catch (Exception e) { log.warn("规划路线失败:{} -> {}", currentLocation, attraction.getLocation(), e); } }
return routes; }
private TourStatistics calculateTourStatistics(List<TourDay> tourDays) { int totalAttractions = tourDays.stream() .mapToInt(day -> day.getAttractions().size()) .sum();
double totalDistance = tourDays.stream() .flatMap(day -> day.getRoutes().stream()) .mapToDouble(route -> route.getDistance()) .sum();
long totalDuration = tourDays.stream() .flatMap(day -> day.getRoutes().stream()) .mapToLong(route -> route.getDuration()) .sum();
return TourStatistics.builder() .totalAttractions(totalAttractions) .totalDistance(totalDistance) .totalDuration(totalDuration) .build(); } }
|
应用配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| spring: application: name: java-amap-tour-planning profiles: active: dev
server: port: 8080
amap: api-key: ${AMAP_API_KEY:your-api-key-here} connect-timeout: 5000 read-timeout: 10000
logging: level: com.example.amap: DEBUG webapi.amap: INFO
management: endpoints: web: exposure: include: health,info,metrics endpoint: health: show-details: when-authorized
|
异常处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @ControllerAdvice @Slf4j public class GlobalExceptionHandler {
@ExceptionHandler(AmapApiException.class) public ResponseEntity<ErrorResponse> handleAmapApiException(AmapApiException e) { log.error("高德API异常", e); return ResponseEntity.badRequest() .body(ErrorResponse.builder() .message("地图服务异常:" + e.getMessage()) .timestamp(System.currentTimeMillis()) .build()); }
@ExceptionHandler(Exception.class) public ResponseEntity<ErrorResponse> handleGenericException(Exception e) { log.error("系统异常", e); return ResponseEntity.internalServerError() .body(ErrorResponse.builder() .message("系统内部错误,请稍后重试") .timestamp(System.currentTimeMillis()) .build()); } }
|
部署和优化
Docker部署
1 2 3 4 5 6 7 8 9 10
| FROM openjdk:17-jdk-slim
WORKDIR /app
COPY target/*.jar app.jar
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]
|
1 2 3 4 5 6 7 8 9 10
| version: '3.8' services: amap-tour-app: build: . ports: - "8080:8080" environment: - AMAP_API_KEY=${AMAP_API_KEY} restart: unless-stopped
|
性能优化策略
缓存机制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Configuration @EnableCaching public class CacheConfig {
@Bean public CacheManager cacheManager() { CaffeineCacheManager cacheManager = new CaffeineCacheManager(); cacheManager.setCaffeine(Caffeine.newBuilder() .maximumSize(1000) .expireAfterWrite(Duration.ofMinutes(30)) .recordStats()); return cacheManager; } }
@Service @Cacheable("poi-search") public List<PoiInfo> cachedPoiSearch(String location, int radius) { return poiSearchService.searchNearbyAttractions(location, radius); }
|
异步处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Service public class AsyncTourService {
@Async public CompletableFuture<TourPlanResponse> createTourPlanAsync(TourPlanRequest request) { return CompletableFuture.supplyAsync(() -> tourPlanningService.createTourPlan(request)); }
public CompletableFuture<List<PoiInfo>> searchAttractionsAsync(String location, int radius) { return CompletableFuture.supplyAsync(() -> poiSearchService.searchNearbyAttractions(location, radius)); } }
|
最佳实践
1. API密钥安全管理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| @Configuration public class AmapConfig {
@Bean @ConfigurationProperties(prefix = "amap") public AmapProperties amapProperties() { return new AmapProperties(); }
@Bean public AmapClient amapClient(AmapProperties properties, @Value("${amap.api-key}") String apiKey) { return AmapClient.builder() .apiKey(apiKey) .connectTimeout(properties.getConnectTimeout()) .readTimeout(properties.getReadTimeout()) .build(); } }
@Data @ConfigurationProperties(prefix = "amap") public class AmapProperties { private int connectTimeout = 5000; private int readTimeout = 10000; private String apiKey; }
|
2. 请求限流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| @Configuration public class RateLimitConfig {
@Bean public RateLimiter amapRateLimiter() { return RateLimiter.create(100.0); } }
@Aspect @Component public class RateLimitAspect {
private final RateLimiter rateLimiter;
public RateLimitAspect(RateLimiter rateLimiter) { this.rateLimiter = rateLimiter; }
@Around("@annotation(com.example.amap.annotation.RateLimited)") public Object rateLimit(ProceedingJoinPoint joinPoint) throws Throwable { if (!rateLimiter.tryAcquire()) { throw new RuntimeException("请求过于频繁,请稍后再试"); } return joinPoint.proceed(); } }
|
3. 监控和告警
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| @Service public class AmapMetricsService {
private final Counter apiCallCounter; private final Counter apiErrorCounter; private final Timer apiCallTimer;
public AmapMetricsService(MeterRegistry registry) { this.apiCallCounter = Counter.builder("amap.api.calls") .description("Number of Amap API calls") .register(registry);
this.apiErrorCounter = Counter.builder("amap.api.errors") .description("Number of Amap API errors") .register(registry);
this.apiCallTimer = Timer.builder("amap.api.duration") .description("Amap API call duration") .register(registry); }
public <T> T executeWithMetrics(Supplier<T> supplier, String operation) { apiCallCounter.increment();
return apiCallTimer.recordCallable(() -> { try { return supplier.get(); } catch (Exception e) { apiErrorCounter.increment(); throw e; } }); } }
|
总结与注意事项
技术要点总结
- 高德地图MCP是中国领先的地图服务平台,提供丰富的地理信息服务
- Java SDK提供了完整的Java封装,支持POI搜索、路径规划、地理编码等功能
- Spring Boot集成可以快速构建企业级的旅游路线规划应用
- 性能优化通过缓存、异步处理等手段提升系统响应速度
- 监控告警确保系统稳定运行和问题及时发现
使用注意事项
- 合理管理API调用频率,避免超出高德地图的限制
- 注意数据安全和隐私保护,遵守相关法律法规
- 在生产环境中使用HTTPS进行API调用
- 定期更新高德地图SDK版本,获取最新功能和安全补丁
- 监控API调用情况和系统性能指标
扩展阅读建议
- 深入学习高德地图MCP官方文档
- 了解地理信息系统(GIS)的相关概念
- 学习Spring Boot微服务架构
- 研究地图可视化和交互设计
- 关注旅游行业数字化转型趋势
通过本文的介绍,你已经掌握了使用Java集成高德地图MCP开发定制旅游路线系统的核心技能。高德地图MCP以其丰富的数据资源和稳定的服务,为旅游应用开发提供了坚实的技术基础。
参考资料
- 高德地图MCP官方文档
- 高德地图Java SDK
- Spring Boot官方文档
- 地理编码服务指南
- 路径规划API文档
- POI搜索API文档
- Docker最佳实践
- Spring Cloud限流指南