
去年底做跨境电商后台的时候运营提了一个需求海外仓商品的详情页上要展示本地实时天气方便买家判断收货时会不会碰上暴雨暴雪。项目组讨论了一圈最后定下来用百度天气API来对接海外城市天气由Java服务端统一封装查询入口。标题里的“海外城市实时天气”听起来不大但真正落地时涉及AK申请、经纬度坐标、HTTP客户端选型、JSON解析、缓存和重试每一个环节都有可以写出来的细节。这篇文章就把完整的实现过程和踩过的坑记录下来给要做类似国际业务、又不想引太大第三方依赖的Java开发者做个参考。1. 这个需求是怎么来的一个海外仓页面上的“实时天气”接在哪1.1 业务场景拆解先说需求本身。跨境电商后台的商品详情页有一个区块需要展示“发货地天气”发货地是海外仓所在城市比如东京、纽约、悉尼。天气信息不是核心订单数据但要稳定展示、加载快、不能因为天气服务挂掉拖垮整个页面。这个场景有几个硬约束接口必须在国内网络环境下可直连数据更新频次不需要毫秒级天气本身15到30分钟更新一次就够了成本要低不能为一个小功能单独采购商业天气数据源后续要支持的城市不固定可能随时加新仓接口参数要好配。表面上看查天气是一个很成熟的能力但真正接起来并没有想象中那么无脑。光是在“用哪家数据源”这个问题上项目组就做了两轮调研。1.2 为什么最终选了百度而不是高德、和风和OpenWeatherMap先看几个候选方案。高德的天气API做得不错返回字段也全但它的城市代码体系主要面向国内行政区划海外城市支持很弱官方文档里基本只覆盖国内直接Pass。和风天气的海外数据源质量很好覆盖全球城市开发者文档也清晰但免费版每日调用量有限申请开发者权限流程相对重做小功能有点杀鸡用牛刀。而且一旦业务量上来需要付费套餐商务流程又要走一遍。OpenWeatherMap在国际开发者里很流行支持全球城市但国内部分网络环境访问它不够稳定响应延迟经常让人怀疑人生不适合做线上页面直连。最后看百度天气API。它在百度地图开放平台里服务端接口可以通过经纬度精确查询海外城市天气国内网络直连快个人开发者免费配额对中小项目足够返回结构也比较简单。海外仓这种“按城市查天气”的场景反而是所有方案里最省事的。最终技术方案定下来Java后端封装内部先维护一份仓库城市坐标表定时请求百度天气接口本地做TTL缓存页面通过JSON接口消费。2. 百度天气API的调用前提AK申请套路与海外城市的坐标约束2.1 服务端AK的申请流程用百度天气API的前提是有一个合法的AK。这里有两个容易搞混的点先说明白。第一天气API不是单独注册产品而是百度地图开放平台里的一项服务。你需要先注册百度账号进入控制台在“应用管理”里创建一个应用。创建时应用类型要选“服务端”选错成“浏览器端”的话后面做服务端调用会有一堆莫名其妙的鉴权问题。第二创建完成后拿到的AK就是请求参数里的ak不需要额外做签名也不用计算sn校验直接拼到URL里就能用。创建应用时通常会让你配置IP白名单。开发阶段建议先把白名单留空等代码部署到服务器之后再把服务器出口IP填进去。这个配置坑得很后文会专门说。2.2 location和district_id海外城市必须走经纬度百度天气API有两类查询参数国内城市可以用district_id这是行政区划编码比如北京是110000。但district_id这套编码体系只覆盖国内海外城市查不到。海外城市要用location参数格式是经度,纬度注意顺序先经度后纬度。比如东京是139.6917,35.6895拼出来的请求就是https://api.map.baidu.com/weather/v1/?location139.6917,35.6895data_typeallak你的AK同时需要传data_type可选值是now实况、forecast预报、all实况加预报。如果页面只需要当前天气建议用now响应体小解析也快。这里有个容易忽略的点location只接受经纬度不接收城市名。你传location东京是查不到数据的。所以要在服务端维护一份“城市名 - 经纬度”的映射。2.3 常用海外城市坐标参考表下面是我实际维护的仓库城市坐标表按城市名映射经纬度城市名用英文小写方便做路径参数或者缓存key。城市经度纬度tokyo东京139.691735.6895osaka大阪135.502334.6937seoul首尔126.978037.5665bangkok曼谷100.501813.7563singapore新加坡103.81981.3521london伦敦-0.127651.5074paris巴黎2.352248.8566berlin柏林13.405052.5200newyork纽约-74.006040.7128losangeles洛杉矶-118.243734.0522sydney悉尼151.2093-33.8688dubai迪拜55.270825.2048注意北半球纬度为正南半球的悉尼纬度为负。经纬度是双精度浮点数不要用int类型放否则会把0.5度这种精度丢掉。3. Java调用层实现从URL拼接、HTTP请求到JSON解析的完整封装3.1 技术选型JDK 11 HttpClient Jackson为什么不引入Spring先回应一个常见疑问用Java调HTTP接口为什么不直接上Spring的RestTemplate或者Feign原因是这个功能在项目里只是一个很小的内部模块不希望为了一个天气查询把Spring全家桶的依赖带进来。JDK 11开始自带的java.net.http.HttpClient足够稳定支持超时、异步、连接池对天气这种低频接口完全够用。JSON解析用Jackson只引一个jackson-databind依赖其余的都不需要。Maven依赖就这一个dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId version2.17.1/version /dependency如果你用的是Gradle把坐标换成就行。3.2 请求封装URL构造与HTTP调用的细节先定义响应模型WeatherResult这个类承载解析结果后续给上层业务使用。public class WeatherResult { private String city; private String country; private double temperature; private double feelsLike; private String text; private int humidity; private String windDir; private String windClass; private String lastUpdate; // getter / setter 省略可以交给 IDE 生成 Override public String toString() { return String.format(%s(%s)%s当前温度 %.1f℃体感 %.1f℃湿度 %d%%%s %s数据时间 %s, city, country, text, temperature, feelsLike, humidity, windDir, windClass, lastUpdate); } }核心客户端类BaiduWeatherClient负责构造URL、发起请求、解析响应。下面这段代码是完整可运行的import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; public class BaiduWeatherClient { private static final String WEATHER_API https://api.map.baidu.com/weather/v1/; private final String ak; private final HttpClient httpClient; private final ObjectMapper objectMapper; public BaiduWeatherClient(String ak) { this.ak ak; this.httpClient HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(10)) .build(); this.objectMapper new ObjectMapper(); } /** * 查询海外城市实时天气 * * param lng 经度 * param lat 纬度 * return 解析后的天气结果 */ public WeatherResult getOverseasWeather(double lng, double lat) throws IOException, InterruptedException { String url buildUrl(lng, lat); HttpRequest request HttpRequest.newBuilder() .uri(URI.create(url)) .timeout(Duration.ofSeconds(10)) .GET() .build(); HttpResponseString response httpClient.send(request, HttpResponse.BodyHandlers.ofString()); return parse(response.body()); } private String buildUrl(double lng, double lat) { // location 参数格式固定为“经度,纬度”经纬度都是数字和逗号无需额外 URL 编码 return WEATHER_API ?location lng , lat data_typeallak ak; } private WeatherResult parse(String responseBody) throws IOException { JsonNode root objectMapper.readTree(responseBody); int status root.path(status).asInt(-1); String message root.path(message).asText(); if (status ! 0) { throw new RuntimeException(百度天气接口返回异常status status , message