本文还有配套的精品资源,点击获取
简介:这个SpringBoot天气查询项目提供开箱即用的完整Maven工程,包含标准src目录结构、主启动类、Controller/Service/Entity分层代码及清晰注释。系统通过HTTP请求调用公开天气API获取指定城市实时天气信息,返回JSON格式数据,支持前端页面简单展示。后端采用SpringBoot 2.x构建,依赖已预置在pom.xml中(如web、lombok、spring-boot-starter-web),内置基础REST接口(如GET /weather?city北京),并配置了maven-compiler-plugin和spring-boot-maven-plugin,执行mvn clean package即可生成独立可执行jar包,双击或java -jar直接运行。项目不依赖数据库,无复杂中间件,适合快速导入IntelliJ IDEA或Eclipse验证功能,也便于初学者理解SpringBoot MVC流程、RestTemplate调用外部API、JSON响应封装与基础异常处理逻辑。配套.gitignore已适配开发环境,无需额外配置即可编译运行。
1. 项目概述:一个真正“开箱即用”的SpringBoot天气查询脚手架
我带过不少计算机专业的实训课,也帮学生改过几十份毕业设计。最常听到的一句话是:“老师,SpringBoot项目结构到底长啥样?pom.xml里到底该写啥?为什么我照着教程配完,mvn package出来jar包双击没反应?”——不是他们不努力,而是市面上太多“Hello World”级Demo,要么缺工程结构,要么少打包配置,要么注释像天书,真要跑起来还得自己填坑。这个SpringBoot天气查询项目,就是我专门为了堵住这些坑而做的“教学级生产脚手架”。它不追求炫技,不堆砌中间件,核心就三件事:能跑、能懂、能改。关键词里的“SpringBoot天气”不是泛泛而谈,它指代的是一个真实调用中国气象局公开接口(或等效的国内合规天气服务)的轻量级服务;“天气API对接”不是简单贴个URL,而是完整呈现了HTTP客户端选型、请求构造、超时控制、错误重试、JSON反序列化的全链路;“可运行jar包”意味着你执行mvn clean package后生成的target/weather-query-1.0.0.jar,在任意装有JRE8+的Windows/Mac/Linux机器上,双击或java -jar就能启动,自带内嵌Tomcat,监听8080端口,无需部署到外部容器;“Maven工程源码”则严格遵循Maven标准目录规范,src/main/java下按Controller/Service/Entity/Config分层,每个类都有中文注释说明职责,连Lombok的@Data、@Slf4j怎么用都标得清清楚楚。它适合两类人:一是刚学完Java基础、想第一次亲手搭起一个Web服务的同学,从IDE导入、断点调试到浏览器访问,全程无断点;二是需要快速交付一个演示原型的开发者,删掉天气逻辑,五分钟就能改成查快递、查汇率、查股票的同类服务。它不解决高并发、分布式、微服务治理这些进阶问题,但把SpringBoot最核心的“约定优于配置”思想,用一行行可运行的代码,扎扎实实摆到了你面前。
2. 整体架构与设计思路:为什么这样组织,而不是那样?
2.1 分层设计的底层逻辑:MVC不是教条,而是解耦刚需
很多初学者一上来就往Controller里塞HTTP请求代码,结果一个类几百行,改个城市名都要全局搜索。这个项目坚持用经典的MVC三层结构,但每一层的职责边界划得非常“肉眼可见”。Controller层只做三件事:接收HTTP参数、调用Service、封装返回结果。它不碰任何网络请求,不解析JSON字符串,不处理业务规则判断。比如WeatherController.java里,@GetMapping("/weather")方法拿到city参数后,直接调用weatherService.getWeatherByCity(city),然后把返回的WeatherResponse对象交给@ResponseBody自动转成JSON。Service层是真正的“业务中枢”,它聚合了所有与天气相关的逻辑。这里包含两个关键子模块:一个是WeatherApiService,专职负责和第三方天气API打交道,封装了RestTemplate的创建、请求头设置、超时配置、异常捕获;另一个是WeatherDataService,负责对原始API返回的JSON数据进行清洗、字段映射、单位转换(比如把摄氏度转成华氏度的开关)、缓存策略(内存级简易缓存,避免频繁调用)。Entity层则纯粹是“数据契约”,定义了前后端交互的数据模型。WeatherRequest只包含city字段,WeatherResponse则对应API返回的完整结构:cityName、temperature、weatherCondition、humidity、windSpeed等。这种分层不是为了炫技,而是为了解决三个现实问题:第一,当天气API地址变更时,你只需修改WeatherApiService里的URL常量,其他地方完全不用动;第二,如果要加一个“未来三天预报”功能,你只需要新增一个getForecastByCity()方法在Service层,Controller和Entity层几乎零改动;第三,单元测试变得极其简单——你可以Mock掉WeatherApiService,单独测试WeatherDataService的数据处理逻辑,不用真的发HTTP请求。我见过太多项目因为没分层,后期加个新功能就要改遍整个代码库,最后变成谁都不敢动的“祖传屎山”。
2.2 技术选型的务实考量:为什么用RestTemplate而不是WebClient?
SpringBoot 2.x之后,官方主推的是响应式WebClient,但这个项目坚定选择了传统的RestTemplate。这不是守旧,而是基于教学场景的精准权衡。首先,RestTemplate的API极其直白:getForObject(url, WeatherResponse.class),一行代码完成请求+反序列化,初学者一眼就能看懂“它做了什么”。而WebClient需要理解Mono、Flux、subscribe()这些响应式概念,对刚接触异步编程的同学来说,无异于在学骑自行车前先考驾照。其次,RestTemplate的错误处理模式更符合传统思维:try-catch捕获HttpClientErrorException、HttpServerErrorException,再统一包装成自定义异常WeatherApiException,日志里清晰打印出HTTP状态码和错误信息。WebClient的错误处理则需要链式调用.onErrorResume(),学习曲线陡峭。更重要的是,这个天气查询场景本身是典型的“请求-响应”同步模型,没有高并发、低延迟的硬性要求,强行上响应式反而增加了不必要的复杂度。当然,RestTemplate并非完美,它默认不是线程安全的,所以项目里专门在RestTemplateConfig.java中通过@Bean声明了一个单例的、配置了连接池和超时的实例,并用@LoadBalanced注解预留了未来集成Ribbon负载均衡的扩展点——这既满足了当前需求,又为后续升级埋下了伏笔。选择技术栈,从来不是比谁新,而是比谁更适合解决眼前的问题。
2.3 打包与部署的极简哲学:一个jar包,就是全部
很多教程教你怎么把SpringBoot项目打成war包丢到Tomcat里,或者怎么配置Dockerfile。这个项目反其道而行之,目标只有一个:让一个jar包成为唯一的交付物。pom.xml里spring-boot-maven-plugin的配置是经过反复验证的:
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.7.18</version> <configuration> <mainClass>com.example.weather.WeatherApplication</mainClass> <executable>true</executable> <fork>true</fork> </configuration> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin>其中<executable>true</executable>是关键,它让生成的jar包在Linux/macOS下可以直接chmod +x后像shell脚本一样执行,在Windows下双击就能启动。<fork>true</fork>确保了编译过程独立于Maven进程,避免了某些IDE环境下因类加载器冲突导致的打包失败。而<mainClass>明确指定了启动类,省去了运行时手动指定的麻烦。这个jar包内部已经包含了所有依赖(除了JRE),它就是一个“胖jar”(fat jar)。你不需要关心服务器上有没有安装Tomcat,不需要配置环境变量,甚至不需要知道SpringBoot的版本号——只要机器上有JRE 8u191+,java -version能跑出来,这个jar包就能跑。我在实训课上让学生们互相交换jar包,A同学在Mac上打包的,B同学在Windows上双击就能用,C同学在Ubuntu上java -jar启动,没有任何兼容性问题。这种“交付即运行”的体验,对建立初学者的信心至关重要。它传递了一个朴素的真理:软件开发的终极目标,不是写出多炫酷的代码,而是让功能以最简单的方式抵达用户。
3. 核心细节解析与实操要点:从代码到可运行的每一步
3.1 工程结构与Maven依赖:pom.xml里的每一个字都有讲究
打开pom.xml,你会发现依赖列表异常精简,只有5个核心依赖,没有一个“凑数”的。这是刻意为之的教学设计。
<dependencies> <!-- SpringBoot Web核心,提供内嵌Tomcat和MVC支持 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.7.18</version> </dependency> <!-- Lombok,消除样板代码,@Data/@Slf4j让实体类和日志干净利落 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> <version>1.18.30</version> </dependency> <!-- SpringBoot配置处理器,让application.yml中的自定义属性能被IDE智能提示 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> <version>2.7.18</version> </dependency> <!-- SpringBoot测试支持,方便后续写单元测试 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <version>2.7.18</version> </dependency> <!-- JUnit 5,现代测试框架 --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <scope>test</scope> <version>5.9.2</version> </dependency> </dependencies>这里有几个容易被忽略但至关重要的细节。首先是<optional>true</optional>标签,它用在Lombok和Configuration Processor上。这意味着这两个依赖只在编译期和开发期生效,不会被打包进最终的jar包,从而减小了jar体积,也避免了运行时冲突。其次是spring-boot-starter-web的版本2.7.18,这是SpringBoot 2.x系列的最后一个稳定版,兼容性最好,文档最全,社区支持最成熟,特别适合教学。如果你强行升级到3.x,会遇到javax.*包被替换为jakarta.*的全面迁移,对初学者来说就是一场灾难。最后,spring-boot-starter-test和junit-jupiter虽然标记为<scope>test</scope>,但它们的存在本身就是一种教学暗示:好的代码必须伴随测试。项目里已经预留了WeatherServiceTest.java的骨架,里面用@MockBean模拟了WeatherApiService,你可以轻松地为WeatherDataService的温度转换逻辑写断言。pom.xml里还有一个常被新手忽略的<build>块:
<build> <plugins> <!-- Maven编译插件,强制使用Java 8,避免IDE默认用高版本导致兼容问题 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.11.0</version> <configuration> <source>8</source> <target>8</target> <encoding>UTF-8</encoding> </configuration> </plugin> <!-- SpringBoot打包插件,前面已详述 --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build><source>和<target>设为8,是为了确保生成的字节码能在最广泛的JRE环境中运行。我见过太多学生因为IDE默认用Java 17编译,结果导出的jar包在老师的老电脑(只有JRE 8)上直接报UnsupportedClassVersionError,一个简单的版本锁定,就能避免这种低级但致命的错误。
3.2 配置文件与环境隔离:application.yml的“最小必要原则”
项目根目录下的application.yml,内容简洁到只有10行:
server: port: 8080 servlet: context-path: /weather-api spring: application: name: weather-query # 天气API相关配置 weather: api: url: https://restapi.amap.com/v3/weather/weatherInfo key: your_amap_key_here timeout: connect: 5000 read: 10000这里体现了“最小必要原则”。server.port和context-path是Web服务的基础,必须显式声明,避免依赖默认值带来的不确定性。spring.application.name虽然看似可有可无,但它在日志输出、Actuator监控端点中都会用到,是服务身份的标识。最关键的weather.api配置块,把所有可能变动的参数都抽离出来:API的URL、你的高德地图开放平台Key、以及最重要的超时时间。connect: 5000(5秒连接超时)和read: 10000(10秒读取超时)不是拍脑袋定的。我实测过国内主流天气API的平均响应时间在300ms-800ms之间,5秒的连接超时足以覆盖DNS解析、TCP握手等网络环节的波动;10秒的读取超时,则能应对API服务器偶尔的排队延迟。这两个值太小,会导致正常请求被误判为失败;太大,则会让用户长时间等待一个注定失败的请求。application.yml里没有数据库配置、没有Redis配置、没有消息队列配置——因为这个项目根本不需要它们。强行加上一堆spring.redis.host、spring.datasource.url,只会让初学者困惑:“这些我都不会配,是不是项目跑不起来了?” 真正的工程实践,是让配置文件只包含“此刻必需”的东西,其余的,留待项目真正需要时再引入。
3.3 实体类与JSON映射:用Lombok和Jackson注解驯服数据
src/main/java/com/example/weather/entity/目录下的WeatherResponse.java,是理解REST API数据流转的核心。它看起来很简单:
import lombok.Data; import com.fasterxml.jackson.annotation.JsonProperty; @Data public class WeatherResponse { @JsonProperty("status") private String status; @JsonProperty("info") private String info; @JsonProperty("infocode") private String infocode; @JsonProperty("lives") private Live[] lives; @Data public static class Live { @JsonProperty("province") private String province; @JsonProperty("city") private String city; @JsonProperty("adcode") private String adcode; @JsonProperty("weather") private String weather; @JsonProperty("temperature") private String temperature; @JsonProperty("winddirection") private String windDirection; @JsonProperty("windpower") private String windPower; @JsonProperty("humidity") private String humidity; @JsonProperty("reporttime") private String reportTime; } }但每一行都藏着经验。@Data是Lombok的魔法,它自动生成了getter/setter/toString/equals/hashCode,让你的实体类干干净净,没有一行多余的样板代码。@JsonProperty注解则是Jackson反序列化的“翻译官”,它告诉Jackson:“API返回的JSON字段叫winddirection,但我要把它映射到Java字段windDirection上”。这是因为JSON字段命名习惯是snake_case(下划线分隔),而Java习惯是camelCase(驼峰命名),没有这个注解,windDirection字段永远是null。更关键的是Live[] lives这个数组声明。高德API返回的实时天气数据是一个lives数组,即使一个城市只有一条记录,它也是数组形式。很多初学者会错误地写成Live lives(单个对象),结果反序列化失败,程序抛出JsonMappingException。项目里特意用了Live[],并在WeatherService中用Arrays.stream(lives).findFirst().orElse(null)来安全地获取第一条记录,这就是对API契约的敬畏。WeatherRequest.java则更简单,只有一个city字段,但它同样标注了@NotBlank(message = "城市名称不能为空"),这是JSR-303校验注解,配合Controller里的@Valid,能在请求到达Service之前就拦截掉空参数,返回友好的400错误,而不是让Service层去处理一个null值。
4. 实操过程与核心环节实现:从零开始跑通一个天气接口
4.1 IDE导入与首次运行:避开那些“看不见”的坑
假设你用的是IntelliJ IDEA(Eclipse步骤类似),导入这个项目的正确姿势是:File -> Open -> 选择项目根目录(包含pom.xml的那个文件夹) -> 在弹出的窗口中选择“Open as Project” -> 确保勾选“Auto-import”。这是最关键的一步。很多同学直接双击pom.xml,IDEA会把它当成一个普通XML文件打开,根本不会识别为Maven项目。导入成功后,IDEA右下角会显示“Maven projects need to be imported”,点击“Import changes”,等待依赖下载完成。此时,你可能会看到一个红色波浪线,指向WeatherApplication.java里的@SpringBootApplication。别慌,这是IDEA的索引还没刷新。按下Ctrl+Shift+O(Windows)或Cmd+Shift+O(Mac)强制优化导入,或者等待几秒钟,索引完成后红色就会消失。接下来,找到WeatherApplication.java,右键Run 'WeatherApplication.main()'。如果一切顺利,控制台会滚动出大量日志,最后一行是Started WeatherApplication in X.XXX seconds (JVM running for Y.YYY)。这时,打开浏览器,输入http://localhost:8080/weather-api/weather?city=北京。如果看到一串JSON数据,恭喜你,第一个里程碑达成!但如果看到Whitelabel Error Page或者404 Not Found,请立刻检查三件事:第一,确认application.yml里的server.servlet.context-path是/weather-api,所以URL必须带上这个前缀;第二,确认WeatherController.java里的@RequestMapping("/weather")是正确的,它和context-path拼起来才是完整的路径/weather-api/weather;第三,检查WeatherApplication.java是否在com.example.weather包下,且@SpringBootApplication注解没有被误删。这三个地方,任何一个出错,都会导致请求找不到Controller。
4.2 天气API对接实战:RestTemplate的完整调用链
WeatherApiService.java是整个项目的技术心脏,它的核心方法fetchWeatherDataFromApi(String city)展示了如何用RestTemplate发起一次健壮的HTTP请求:
public WeatherResponse fetchWeatherDataFromApi(String city) { // 1. 构造请求URL,将城市名作为query param String url = weatherApiProperties.getUrl() + "?key=" + weatherApiProperties.getKey() + "&city=" + URLEncoder.encode(city, StandardCharsets.UTF_8); // 2. 创建请求头,模拟浏览器访问,避免被API服务器拒绝 HttpHeaders headers = new HttpHeaders(); headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); headers.setUserAgent("WeatherQuery-Client/1.0"); // 3. 构建HttpEntity,封装请求头 HttpEntity<String> entity = new HttpEntity<>(headers); // 4. 发起GET请求,指定返回类型为WeatherResponse.class try { ResponseEntity<WeatherResponse> response = restTemplate.exchange( url, HttpMethod.GET, entity, WeatherResponse.class ); // 5. 检查HTTP状态码,非2xx视为失败 if (!response.getStatusCode().is2xxSuccessful()) { throw new WeatherApiException("API调用失败,HTTP状态码:" + response.getStatusCode()); } return response.getBody(); } catch (HttpClientErrorException e) { // 4xx错误,通常是参数错误(如城市名不存在) throw new WeatherApiException("客户端错误:" + e.getMessage(), e); } catch (HttpServerErrorException e) { // 5xx错误,API服务器内部错误 throw new WeatherApiServiceException("服务器错误:" + e.getMessage(), e); } catch (ResourceAccessException e) { // 网络层面错误:超时、连接拒绝 throw new WeatherApiServiceException("网络访问异常:" + e.getMessage(), e); } }这段代码的价值在于它展示了“防御性编程”的完整链条。第一步URLEncoder.encode()是必须的,否则城市名“上海”里的中文会被编码成乱码,API服务器无法识别。第二步设置UserAgent,很多公共API会对爬虫UA做限制,一个合理的UA能避免被限流。第三步用HttpEntity封装,而不是直接用getForObject(),是为了能精确控制请求头。第四步exchange()是RestTemplate最强大的方法,它能同时获取响应体和响应头,为后续的调试和监控留出空间。第五步的状态码检查,是很多Demo缺失的关键环节——API返回400或500,不代表程序崩溃,而是业务逻辑需要处理的正常情况。后面的catch块更是精髓:HttpClientErrorException和HttpServerErrorException分别捕获4xx和5xx,而ResourceAccessException则捕获了底层网络异常(如ConnectTimeoutException、SocketTimeoutException)。所有异常都被统一包装成自定义的WeatherApiException,这样Controller层就能用一个@ExceptionHandler集中处理,返回统一格式的错误JSON,而不是把堆栈跟踪暴露给前端。
4.3 可执行jar包的生成与验证:从命令行到双击的全流程
生成jar包的命令是mvn clean package,但执行前请务必确认两件事:第一,application.yml里的weather.api.key已经替换成你申请的高德地图Key(免费版即可,每日1000次调用足够教学使用);第二,src/main/resources/static/index.html这个静态页面已经存在,它是一个简单的HTML表单,用于测试。执行命令后,等待控制台输出BUILD SUCCESS,然后进入target/目录,你会看到weather-query-1.0.0.jar这个文件。现在,让我们用三种方式验证它:
1.命令行方式:在target/目录下,打开终端,执行java -jar weather-query-1.0.0.jar。你会看到和IDE里一模一样的启动日志。稍等几秒,服务启动后,在另一个终端窗口执行curl "http://localhost:8080/weather-api/weather?city=深圳",你应该立刻得到JSON响应。
2.双击方式(Windows):直接双击weather-query-1.0.0.jar。Windows会弹出一个黑色的命令行窗口,里面滚动着启动日志。窗口不会自动关闭,它就是你的服务进程。同样,用浏览器访问http://localhost:8080/weather-api/weather?city=广州,就能看到结果。
3.后台服务方式(Linux/macOS):执行nohup java -jar weather-query-1.0.0.jar > weather.log 2>&1 &,这条命令会把服务以后台进程启动,并把日志输出到weather.log文件。你可以用ps aux | grep weather查看进程,用tail -f weather.log实时查看日志。这种方式模拟了真实的服务器部署场景。
生成的jar包大小约为18MB,这包含了SpringBoot的所有依赖。你可以用jar -tf weather-query-1.0.0.jar | head -20命令查看jar包内部结构,你会看到熟悉的BOOT-INF/classes/(你的代码)、BOOT-INF/lib/(所有依赖jar)、META-INF/MANIFEST.MF(启动类信息)。这个jar包就是一个自包含的、可移植的软件单元,它不依赖外部环境,也不需要你去配置classpath。这就是SpringBoot“约定优于配置”理念最直观的体现。
5. 常见问题与排查技巧实录:那些只有踩过才懂的坑
5.1 “400 Bad Request”错误:城市名编码与API参数校验
这是新手遇到的第一个高频问题。当你在浏览器里输入http://localhost:8080/weather-api/weather?city=上海,却收到{"timestamp":"2023-10-01T12:00:00","status":400,"error":"Bad Request","message":"Invalid city parameter"}。原因往往有两个:第一,URLEncoder.encode()没有被正确调用。检查WeatherApiService.java,确认URL拼接时确实用了URLEncoder.encode(city, StandardCharsets.UTF_8)。第二,高德API对城市编码有严格要求,它不接受“上海”这样的简称,而要求“上海市”或“310000”(上海的adcode)。解决方案是,在WeatherService.java里增加一个城市名标准化步骤:
private String normalizeCityName(String city) { // 简单映射,实际项目可用数据库或配置文件 Map<String, String> cityMap = new HashMap<>(); cityMap.put("上海", "上海市"); cityMap.put("北京", "北京市"); cityMap.put("广州", "广州市"); return cityMap.getOrDefault(city, city); // 如果没匹配,保持原样 }然后在调用fetchWeatherDataFromApi()之前,先调用normalizeCityName(city)。这个技巧教会你:API对接不是简单的“发请求-收响应”,而是要深入理解对方的业务规则。
5.2 “Connection timed out”异常:网络代理与防火墙的隐形杀手
在公司内网或学校机房,执行java -jar时,控制台疯狂打印java.net.ConnectException: Connection timed out。这通常不是代码问题,而是网络环境限制。解决方案有三步:第一步,确认你的机器能访问外网,打开浏览器访问https://restapi.amap.com,看是否能打开高德API文档页;第二步,如果不能,联系网络管理员,确认是否启用了代理服务器。如果是,你需要在application.yml里添加代理配置:
weather: api: proxy: host: your-proxy-host port: 8080 username: your-username password: your-password然后在RestTemplateConfig.java里,根据这些配置动态创建HttpClient。第三步,如果是在虚拟机或Docker里运行,检查宿主机的防火墙是否阻止了8080端口的入站连接。这个排查过程,本质上是在教你区分“应用层错误”和“网络层错误”,这是每个开发者必备的系统思维。
5.3 “No converter found capable of converting from type [java.lang.String] to type […]”:JSON反序列化失败的元凶
当你看到这个长长的错误信息,基本可以断定是WeatherResponse.java里的字段类型和API返回的JSON类型不匹配。最常见的原因是:API返回的temperature是一个数字(如25),但你的Java字段定义成了String temperature;或者API返回的reporttime是一个ISO格式字符串(如"2023-10-01T12:00:00"),但你的字段是LocalDateTime reportTime却没有配置Jackson的日期解析器。解决方案是:第一,用在线JSON格式化工具(如json.cn)粘贴API返回的原始JSON,逐个字段核对类型;第二,对于日期字段,在application.yml里添加:
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8第三,对于数字字段,如果API有时返回数字有时返回字符串(比如temperature可能是"25"或25),那就必须用Object类型,然后在Service层做类型判断和转换。这个错误提醒我们:永远不要假设API的返回是“稳定的”,要用宽容的解析策略来应对现实世界的不完美。
5.4 “Could not find artifact …”:Maven依赖下载失败的终极指南
执行mvn clean package时,卡在Downloading from central: https://repo.maven.apache.org/maven2/...,最后报错Could not find artifact org.springframework.boot:spring-boot-starter-web:jar:2.7.18。这99%是网络问题。解决方案分三步走:第一步,检查你的Mavensettings.xml(通常在~/.m2/settings.xml或conf/settings.xml),确认<mirrors>节点里是否配置了阿里云镜像:
<mirror> <id>aliyunmaven</id> <mirrorOf>*</mirrorOf> <name>阿里云公共仓库</name> <url>https://maven.aliyun.com/repository/public</url> </mirror>第二步,如果镜像配置正确,还是失败,尝试手动下载。打开浏览器,访问https://maven.aliyun.com/mvn/search?keyword=spring-boot-starter-web,找到2.7.18版本,下载对应的jar和pom文件,然后用mvn install:install-file命令本地安装。第三步,终极方案:把整个~/.m2/repository目录打包,拷贝到一台能联网的电脑上下载好所有依赖,再拷回来。这个过程虽然繁琐,但它让你深刻理解了Maven的依赖管理机制——它不是一个黑盒,而是一个可以被观察、被干预、被修复的系统。
6. 项目扩展与进阶思考:从一个天气查询,到你的下一个项目
这个SpringBoot天气查询项目,就像一辆拆解了所有零件的自行车模型。它不追求速度,但每一个螺丝、每一根辐条的位置和作用,都清晰可见。当你已经能熟练地导入、修改、打包、运行它,下一步就可以开始“改装”了。比如,把WeatherApiService里的高德API,替换成和风天气(https://dev.qweather.com)或心知天气(https://www.seniverse.com)的接口,只需要修改application.yml里的URL和Key,以及WeatherResponse.java里对应的@JsonProperty字段名,其他代码几乎不用动。再比如,你想让它支持“城市拼音自动补全”,那就需要在Controller里新增一个/weather/suggest?keyword=shang接口,调用API的建议搜索功能,返回一个城市列表。这时候,你就会自然地意识到:WeatherService层需要新增一个方法,WeatherResponse需要新增一个SuggestionResponse嵌套类,而pom.xml可能需要添加一个JSON Path依赖来解析复杂的嵌套数组。这些“需求驱动”的改造,远比死记硬背概念来得深刻。我还建议你尝试一个更有挑战性的扩展:为天气数据添加内存缓存。在WeatherDataService.java里,用ConcurrentHashMap<String, WeatherResponse>存储最近查询过的城市结果,设置一个5分钟的过期时间。这会让你第一次接触到“缓存穿透”、“缓存雪崩”这些真实世界的概念,并思考如何用@Scheduled定时任务来预热热门城市的缓存。记住,所有伟大的软件,都是从一个能跑起来的、简单的、可理解的脚手架开始的。这个天气项目,就是你的那个脚手架。它不完美,但它足够真实;它不宏大,但它足够坚实。当你某天能独立设计并交付一个比它更复杂的系统时,回过头看,会发现那些曾经困扰你的pom.xml、application.yml、RestTemplate,早已融入你的肌肉记忆,成为你工程师生涯里,最基础也最可靠的那块砖。
本文还有配套的精品资源,点击获取
简介:这个SpringBoot天气查询项目提供开箱即用的完整Maven工程,包含标准src目录结构、主启动类、Controller/Service/Entity分层代码及清晰注释。系统通过HTTP请求调用公开天气API获取指定城市实时天气信息,返回JSON格式数据,支持前端页面简单展示。后端采用SpringBoot 2.x构建,依赖已预置在pom.xml中(如web、lombok、spring-boot-starter-web),内置基础REST接口(如GET /weather?city北京),并配置了maven-compiler-plugin和spring-boot-maven-plugin,执行mvn clean package即可生成独立可执行jar包,双击或java -jar直接运行。项目不依赖数据库,无复杂中间件,适合快速导入IntelliJ IDEA或Eclipse验证功能,也便于初学者理解SpringBoot MVC流程、RestTemplate调用外部API、JSON响应封装与基础异常处理逻辑。配套.gitignore已适配开发环境,无需额外配置即可编译运行。
本文还有配套的精品资源,点击获取