简介:这是一套面向高校计算机相关专业毕业设计的完整项目源码,基于Java与MVC三层架构开发,同时包含PC端与安卓Android手机客户端,适合需要完成天气类或生活服务类毕设的学生参考与二次开发。项目以天气预报为核心,管理员可在服务器端发布各地区天气与穿衣搭配公告,用户查询所在地区气温变化并获取搭配建议,客户端还支持虚拟人物风格展示。压缩包共424个文件,约2.73MB,其中93个java源文件承载业务逻辑,86个xml与31个jsp构成界面与配置,另有gif、png、jpg等图片素材及sql数据库脚本、class编译文件,结构完整便于导入MyEclipse、Eclipse、Idea或Android Studio运行。目前已有265人学习下载,读者可获得从数据库设计、实体建模到客户端通信的完整赛题方案,并借助分层代码理解MVC思想与XML、JSON数据交互方式,快速搭建可演示的毕设系统。
1. 从一份毕业设计说起:Java+MVC 的天气预报穿衣搭配 APP 到底在做什么
每年毕业季,计算机专业的选题里总有一类特别受欢迎——既有真实数据来源,又能做出看得见的界面,还能把 Java 后端、Android 客户端、数据库设计串成一条完整链路。天气预报穿衣搭配 APP 就是这类选题的典型代表。它的核心逻辑并不复杂:拉取天气数据,根据温度、湿度、风力、天气状况等参数,结合一套穿衣建议规则,给用户推荐今天穿什么。但真正动手做的时候,你会发现从天气 API 选型、MVC 三层架构拆分、PC 端和 Android 端的数据同步,到 Android Studio 里的网络请求适配,每一步都有具体的坑。
这个方案适合正在准备毕业设计的学生,也适合想用 Java 技术栈练手一个完整前后端项目的开发者。PC 端可以用 Java Swing 或 JavaFX 做桌面应用,Android 端用原生 Android SDK 配合 Retrofit 或 OkHttp 请求后端接口,后端用 Spring MVC 或 Servlet+JSP 搭建 RESTful 服务,数据库用 MySQL 存储用户信息和穿衣规则。整套技术栈都是 Java 生态里最成熟、资料最多的组合,遇到问题容易搜到答案。下面我会按实际开发顺序,把架构设计、天气数据接入、穿衣推荐算法、Android 端实现和调试排错逐一讲清楚。
2. 架构选型与 MVC 三层拆分:为什么不用 Spring Boot 一把梭
2.1 毕业设计场景下的技术栈取舍
很多同学一上来就想用 Spring Boot + Vue + 微服务,结果光环境配置就耗掉两周,核心功能反而没时间做。毕业设计的评判标准是功能完整、代码规范、架构清晰,不是技术栈有多新。Java+MVC 这个组合的好处是:Servlet 容器(Tomcat)直接跑,不需要额外学 Spring Boot 的自动配置原理;MVC 三层架构(Model-View-Controller)是教科书级别的设计模式,答辩时老师一听就懂;PC 端和 Android 端共用同一套后端接口,工作量可控。
具体选型建议:
| 层次 | 技术选择 | 理由 |
|---|---|---|
| 后端框架 | Spring MVC 或 Servlet+JSP | 轻量,配置透明,适合展示 MVC 理解 |
| 数据库 | MySQL 5.7/8.0 | 免费,资料多,JDBC 连接稳定 |
| 数据访问 | JDBC + DAO 模式 或 MyBatis | JDBC 更能体现底层理解,MyBatis 更省代码 |
| PC 端 | Java Swing / JavaFX | 纯 Java,无需额外前端技术栈 |
| Android 端 | 原生 Android SDK + OkHttp | 直接调 REST API,逻辑清晰 |
| 天气数据 | 和风天气 / 高德天气 API | 免费额度够用,返回 JSON 格式规范 |
| JSON 解析 | Gson / FastJson | Gson 与 Android 兼容性最好 |
如果你的学校要求必须用 SSH(Struts+Spring+Hibernate),那就按学校要求来,但核心的 MVC 分层思想是一样的。我一般会建议学生先用 Servlet+JSP 把流程跑通,再决定要不要换成 Spring MVC,这样至少有一个能跑的版本保底。
2.2 MVC 三层在项目里的具体落地
MVC 不是三个文件夹那么简单,关键是职责边界要清晰。以「查询某城市今天天气并返回穿衣建议」这个功能为例:
- Model(模型层):封装 Weather 实体类(城市、温度、湿度、风力、天气状况)和 ClothingAdvice 实体类(上衣建议、下装建议、外套建议、 accessories)。同时包含 DAO 类,负责从 MySQL 读取穿衣规则表,以及从天气 API 获取数据后的解析逻辑。
- View(视图层):PC 端是 Swing 的 JFrame 面板,Android 端是 Activity 的 XML 布局。视图层只负责展示数据,不包含任何业务判断。
- Controller(控制层):接收前端请求,调用 Service 层获取天气数据和穿衣建议,再把结果返回给 View。Controller 里不写 SQL,也不写穿衣规则的具体判断。
后端目录结构可以这样组织:
src/ ├── com.weather.model/ # 实体类 │ ├── Weather.java │ └── ClothingAdvice.java ├── com.weather.dao/ # 数据访问 │ ├── WeatherDao.java │ └── ClothingRuleDao.java ├── com.weather.service/ # 业务逻辑 │ ├── WeatherService.java │ └── ClothingService.java ├── com.weather.controller/ # 控制层 │ └── WeatherServlet.java └── com.weather.util/ # 工具类 ├── DBUtil.java └── HttpUtil.javaAndroid 端的目录结构对应为:
app/src/main/java/com/weather/app/ ├── model/ # 与后端对应的实体类 ├── network/ # OkHttp 请求封装 ├── ui/ # Activity 和 Adapter └── util/ # 工具类注意:实体类在 PC 端、Android 端、后端三处都要有一份,字段名保持一致,否则 JSON 解析时会丢字段。建议用 Gson 的 @SerializedName 注解做映射,避免因为命名风格不一致导致解析失败。
2.3 数据库表设计的最小可用集
毕业设计不需要几十张表,三张核心表就能撑起整个系统:
-- 用户表 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, default_city VARCHAR(50) DEFAULT '北京', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 穿衣规则表 CREATE TABLE clothing_rule ( id INT PRIMARY KEY AUTO_INCREMENT, min_temp INT NOT NULL, max_temp INT NOT NULL, weather_condition VARCHAR(50), tops_advice VARCHAR(200), bottoms_advice VARCHAR(200), outerwear_advice VARCHAR(200), accessories_advice VARCHAR(200) ); -- 查询历史表 CREATE TABLE query_history ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT, city VARCHAR(50), query_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, weather_json TEXT, FOREIGN KEY (user_id) REFERENCES user(id) );穿衣规则表的数据可以预先插入十几条覆盖常见温度区间,比如 28°C 以上推荐短袖短裤,15-27°C 推荐长袖薄外套,5-14°C 推荐毛衣加风衣,5°C 以下推荐羽绒服加保暖内衣。weather_condition 字段用来处理特殊情况,比如下雨天要加雨具建议,下雪天要加防滑提醒。
3. 天气数据接入与穿衣推荐算法:从 API 返回到可读建议
3.1 天气 API 的选型与请求封装
国内可用的免费天气 API 里,和风天气和高德天气是毕业设计最常用的两个。和风天气的免费版每天有 1000 次调用额度,返回字段包括温度、体感温度、湿度、风向、风力、天气状况码等,足够支撑穿衣推荐。高德天气的优势是定位和城市搜索接口更完善,但天气字段相对少一些。
以和风天气为例,请求 URL 格式如下:
https://devapi.qweather.com/v7/weather/now?location=101010100&key=你的KEY其中 location 是城市 ID,需要先通过城市查询接口获取。返回的 JSON 结构里,now 对象包含 temp(温度)、feelsLike(体感温度)、humidity(湿度)、windScale(风力等级)、text(天气状况文字)等字段。
后端封装 HTTP 请求的工具类:
public class HttpUtil { public static String doGet(String url) throws IOException { OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url(url) .build(); try (Response response = client.newCall(request).execute()) { if (response.body() != null) { return response.body().string(); } return ""; } } }这段代码用 OkHttp 发起同步 GET 请求,返回响应体字符串。参数说明:url 是完整的请求地址,包含 API Key 和城市 ID。实际使用时建议把 API Key 放在配置文件里,不要硬编码在 Java 源码中,否则提交代码时会泄露。Android 端同样可以用 OkHttp,但要注意网络请求必须放在子线程,否则会抛 NetworkOnMainThreadException。
3.2 穿衣推荐算法的规则引擎设计
穿衣推荐的核心是一个基于温度区间和天气状况的规则匹配。最简单的实现方式是查表法:根据当前温度落在哪个区间,从 clothing_rule 表里查对应的建议。但实际体验要好,还需要叠加几个修正因子:
- 体感温度优先:如果体感温度和实际温度差超过 3°C,以体感温度为准。比如夏天湿度高时体感温度会明显高于实际温度。
- 风力修正:风力达到 4 级以上时,建议增加防风外套。
- 天气状况修正:下雨天增加雨具提醒,下雪天增加防滑鞋建议,雾霾天增加口罩提醒。
- 时段修正:早晚温差大的季节,建议采用洋葱式穿法,方便增减。
后端 Service 层的核心逻辑:
public ClothingAdvice getAdvice(Weather weather) { // 优先使用体感温度 int temp = weather.getFeelsLike() != 0 ? weather.getFeelsLike() : weather.getTemp(); // 查询基础规则 ClothingRule rule = clothingRuleDao.findByTemp(temp); if (rule == null) { return new ClothingAdvice("暂无建议", "暂无建议", "暂无建议", ""); } String outerwear = rule.getOuterwearAdvice(); String accessories = rule.getAccessoriesAdvice(); // 风力修正 if (weather.getWindScale() >= 4) { outerwear += ",建议加一件防风外套"; } // 天气状况修正 String condition = weather.getText(); if (condition.contains("雨")) { accessories += ",记得带伞"; } else if (condition.contains("雪")) { accessories += ",注意穿防滑鞋"; } else if (condition.contains("雾") || condition.contains("霾")) { accessories += ",建议戴口罩"; } return new ClothingAdvice( rule.getTopsAdvice(), rule.getBottomsAdvice(), outerwear, accessories ); }这段代码的逻辑是:先取体感温度(如果没有则用实际温度),然后从数据库查基础规则,再根据风力和天气状况做二次修正。参数说明:weather 对象由天气 API 解析而来,clothingRuleDao 是 DAO 层实例。注意 findByTemp 方法的 SQL 要处理边界情况,比如温度正好等于区间端点时,应该归入较暖的那个区间,避免出现“无匹配规则”的情况。
3.3 后端接口设计与 JSON 返回格式
PC 端和 Android 端共用同一套接口,所以返回格式要统一。建议用如下 JSON 结构:
{ "code": 200, "message": "success", "data": { "city": "北京", "temp": 22, "feelsLike": 24, "humidity": 45, "windScale": 3, "condition": "晴", "advice": { "tops": "长袖T恤或薄衬衫", "bottoms": "牛仔裤或休闲裤", "outerwear": "薄外套", "accessories": "无需特殊配件" } } }Controller 层用 Servlet 实现时,注意设置响应头和编码:
@WebServlet("/api/weather") public class WeatherServlet extends HttpServlet { private WeatherService weatherService = new WeatherService(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("application/json;charset=UTF-8"); String city = req.getParameter("city"); if (city == null || city.isEmpty()) { city = "北京"; } Weather weather = weatherService.getWeather(city); ClothingAdvice advice = weatherService.getAdvice(weather); Map<String, Object> result = new HashMap<>(); result.put("code", 200); result.put("message", "success"); Map<String, Object> data = new HashMap<>(); data.put("city", city); data.put("temp", weather.getTemp()); data.put("feelsLike", weather.getFeelsLike()); data.put("humidity", weather.getHumidity()); data.put("windScale", weather.getWindScale()); data.put("condition", weather.getText()); data.put("advice", advice); result.put("data", data); resp.getWriter().write(new Gson().toJson(result)); } }这段 Servlet 代码处理 GET 请求,接收 city 参数,调用 Service 层获取天气和穿衣建议,最后用 Gson 序列化为 JSON 返回。参数说明:city 为空时默认北京,实际项目中应该从用户表读取默认城市。注意 Gson 序列化 Map 时,如果 value 是自定义对象,需要确保该对象的字段有 getter 方法,否则会序列化为空对象。
4. Android 端与 PC 端的实现差异:网络、线程与界面适配
4.1 Android 端网络请求与权限配置
Android 端和 PC 端最大的差异在于网络请求必须处理线程切换和权限声明。在 AndroidManifest.xml 中需要添加:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />如果 targetSdkVersion 在 28 以上,还需要在 application 标签中设置:
android:usesCleartextTraffic="true"否则 HTTP 请求会被系统拦截。虽然现在推荐用 HTTPS,但毕业设计阶段如果后端部署在本地 Tomcat 上,可能只有 HTTP,这个配置能省去很多调试时间。
Android 端用 OkHttp 请求后端接口的封装:
public class WeatherApi { private static final String BASE_URL = "http://你的服务器IP:8080/weather/api/weather"; private final OkHttpClient client = new OkHttpClient(); public void getWeather(String city, Callback callback) { HttpUrl url = HttpUrl.parse(BASE_URL) .newBuilder() .addQueryParameter("city", city) .build(); Request request = new Request.Builder() .url(url) .build(); client.newCall(request).enqueue(new okhttp3.Callback() { @Override public void onFailure(Call call, IOException e) { callback.onFailure(e.getMessage()); } @Override public void onResponse(Call call, Response response) throws IOException { if (response.body() != null) { String json = response.body().string(); // 在主线程更新 UI new Handler(Looper.getMainLooper()).post(() -> { callback.onSuccess(json); }); } } }); } public interface Callback { void onSuccess(String json); void onFailure(String error); } }这段代码用 OkHttp 的 enqueue 方法发起异步请求,避免阻塞主线程。参数说明:BASE_URL 需要替换成实际服务器地址,如果是本地测试,Android 模拟器访问宿主机要用 10.0.2.2 而不是 localhost。onResponse 回调在子线程执行,更新 UI 必须通过 Handler 切回主线程,否则会崩溃。
4.2 PC 端 Swing 界面的数据绑定
PC 端用 Swing 做界面时,网络请求可以放在 SwingWorker 里执行,避免界面卡死。核心代码结构:
public class WeatherPanel extends JPanel { private JLabel cityLabel, tempLabel, adviceLabel; private JButton queryButton; private JTextField cityField; public WeatherPanel() { setLayout(new BorderLayout()); JPanel topPanel = new JPanel(); cityField = new JTextField(10); queryButton = new JButton("查询"); topPanel.add(new JLabel("城市:")); topPanel.add(cityField); topPanel.add(queryButton); JPanel centerPanel = new JPanel(new GridLayout(3, 1)); cityLabel = new JLabel("城市:--"); tempLabel = new JLabel("温度:--"); adviceLabel = new JLabel("建议:--"); centerPanel.add(cityLabel); centerPanel.add(tempLabel); centerPanel.add(adviceLabel); add(topPanel, BorderLayout.NORTH); add(centerPanel, BorderLayout.CENTER); queryButton.addActionListener(e -> queryWeather()); } private void queryWeather() { String city = cityField.getText().trim(); if (city.isEmpty()) { JOptionPane.showMessageDialog(this, "请输入城市名"); return; } new SwingWorker<String, Void>() { @Override protected String doInBackground() throws Exception { return HttpUtil.doGet("http://localhost:8080/weather/api/weather?city=" + city); } @Override protected void done() { try { String json = get(); // 解析 JSON 并更新界面 updateUI(json); } catch (Exception ex) { JOptionPane.showMessageDialog(WeatherPanel.this, "查询失败:" + ex.getMessage()); } } }.execute(); } }这段代码用 SwingWorker 把网络请求放到后台线程,done 方法在主线程执行,可以安全更新界面。参数说明:cityField 接收用户输入的城市名,queryButton 绑定查询事件。注意 PC 端访问本机 Tomcat 用 localhost 即可,但如果 Tomcat 和 PC 端不在同一台机器,需要改成实际 IP。
4.3 两端数据同步与缓存策略
PC 端和 Android 端共用后端接口,数据一致性由后端保证。但移动端网络不稳定,建议加一层本地缓存:用 SharedPreferences 存储最近一次查询结果,下次打开 APP 时先展示缓存数据,再发起网络请求更新。这样即使网络不好,用户也能看到上次的穿衣建议。
缓存逻辑可以这样实现:
// 保存缓存 SharedPreferences sp = getSharedPreferences("weather_cache", MODE_PRIVATE); sp.edit().putString("last_weather", json).apply(); // 读取缓存 String cached = sp.getString("last_weather", ""); if (!cached.isEmpty()) { updateUI(cached); // 先展示缓存 } // 再发起网络请求 api.getWeather(city, callback);注意:缓存不要设置太长的过期时间,天气数据变化快,建议缓存有效期设为 30 分钟。可以在保存时同时存一个时间戳,读取时判断是否过期。
5. 避坑与排查:那些让毕业设计卡壳的典型问题
5.1 中文乱码:从 Tomcat 到 Android 的全链路排查
现象:PC 端查询返回的 JSON 里中文显示为问号或乱码,Android 端同样。
原因:Tomcat 默认编码、Servlet 响应编码、JDBC 连接编码、Android 端解析编码,四个环节任何一个不一致都会导致乱码。
解决:Tomcat 的 server.xml 中 Connector 标签加 URIEncoding="UTF-8";Servlet 里 resp.setContentType("application/json;charset=UTF-8");JDBC URL 加 useUnicode=true&characterEncoding=UTF-8;Android 端 OkHttp 读取响应时用 response.body().string() 默认按 UTF-8 解析,一般不需要额外设置。四个地方都确认一遍,基本能解决。
5.2 Android 9.0 以上 HTTP 请求被拦截
现象:Android 模拟器或真机上请求后端接口,直接走 onFailure,报错 Cleartext HTTP traffic not permitted。
原因:Android 9.0(API 28)开始默认禁止明文 HTTP 请求。
解决:在 AndroidManifest.xml 的 application 标签加 android:usesCleartextTraffic="true"。如果后端已经上了 HTTPS,则不需要这个配置。毕业设计阶段后端通常部署在本地,加这个配置最快。
5.3 天气 API 返回城市 ID 不匹配
现象:请求天气接口返回 404 或 city not found。
原因:和风天气的城市 ID 是固定的数字编码,不是城市名。直接传“北京”会失败,需要先调城市查询接口获取 location ID。
解决:先请求https://geoapi.qweather.com/v2/city/lookup?location=北京&key=你的KEY,从返回结果里取 id 字段,再用这个 id 请求天气接口。建议把常用城市的 ID 缓存在本地数据库或配置文件里,减少 API 调用次数。
5.4 穿衣规则表温度区间不连续
现象:某些温度下查询不到穿衣建议,返回“暂无建议”。
原因:clothing_rule 表的 min_temp 和 max_temp 区间没有覆盖所有温度,比如 14°C 到 15°C 之间有空隙。
解决:设计规则时确保区间连续,后一个区间的 min_temp 等于前一个区间的 max_temp。查询 SQL 用WHERE min_temp <= ? AND max_temp > ?,注意边界用 > 而不是 >=,避免端点重复匹配。插入数据后手动检查一遍所有区间是否无缝衔接。
5.5 PC 端打包后连接不上数据库
现象:在 IDE 里运行正常,打成 JAR 包后双击运行报数据库连接失败。
原因:IDE 里 MySQL 驱动 JAR 在 classpath 中,打包时没有把驱动打进去,或者数据库连接 URL 写的是 localhost 但目标机器上没有 MySQL。
解决:用 Maven 的 assembly 插件或 shade 插件把依赖一起打包;数据库连接信息写到外部配置文件里,打包后可以修改。如果答辩时是在自己电脑上演示,确保 MySQL 服务已启动,并且用户名密码正确。
6. 让穿衣建议更准一步:体感温度加权与用户反馈微调
基础版的穿衣推荐只用了温度区间查表,实际体验中会发现同一个温度下,不同湿度、不同风力给人的感觉差别很大。我在做这类项目时习惯加一层体感温度加权计算,让建议更贴近真实感受。体感温度的计算可以参考澳大利亚体感温度公式(AT),它同时考虑温度和湿度:
public static double calculateApparentTemp(double temp, double humidity) { // 简化版体感温度公式,适用于温度 20-50°C 的场景 double e = (humidity / 100.0) * 6.105 * Math.exp(17.27 * temp / (237.7 + temp)); return temp + 0.348 * e - 0.70 * (windSpeed) - 4.25; }参数说明:temp 是实际温度(摄氏度),humidity 是相对湿度(百分比),windSpeed 是风速(米/秒)。这个公式在高温高湿场景下会算出比实际温度更高的体感温度,此时穿衣建议应该更偏向清凉。低温场景下可以用风寒指数公式替代。实际使用时,可以把计算结果和 API 返回的 feelsLike 字段做对比,取两者中更极端的一个作为推荐依据。
另一个提升准确度的方式是加用户反馈。在 Android 端加一个简单的“建议是否合适”按钮,用户点击“偏冷”或“偏热”后,把当前温度、湿度和反馈结果存到数据库。积累几十条数据后,可以在规则表里对特定温度区间做微调,比如把 20-24°C 区间的外套建议从“薄外套”改成“可选外套”。这个功能不需要机器学习,纯 SQL 统计就能做,但能让答辩时的演示效果提升一个档次。
验证方法也很直接:找五个同学,让他们在早上出门前用你的 APP 查一次建议,晚上回来反馈当天穿着是否合适。收集一周数据,统计“合适”的比例。如果低于 70%,就检查是温度区间划分太粗,还是修正因子权重不对。我自己的经验是,把温度区间从 10°C 一档细化到 5°C 一档,合适率能提升 15% 左右。希望这些经验能帮到你,少走一些我当年踩过的弯路。
本文还有配套的精品资源,点击获取