这两年经常有朋友私信问我,说想找一个能真正练手、又能写进简历的项目。Java SpringBoot + Android 的宠物饲养管理系统,是我觉得非常适合拿来当毕业设计或者Java求职项目的一个方向。它不只是一个简单的CRUD,而是从移动端到服务端完整闭环的工程,带源码、文档、运行视频和讲解视频,适合不同基础的人反复对照学习。这篇文章我会把整个项目的核心思路、技术选型、数据库设计、联调过程和踩坑经验完整拆一遍,如果你正准备做类似项目,可以直接照着思路来。
先说明一点,我也是从图书馆管理系统、学生管理系统这些老掉牙的选题走过来的。宠物饲养管理的区别在于,它的业务场景非常贴近生活,需求拆起来很自然:你得知道宠物什么时候该喂食、疫苗什么时候到期、驱虫多久做一次、体重有没有异常。这些需求背后对应的就是一套数据模型和接口设计,学到的都是以后做真实业务要用的东西,而不是为了做项目而做项目。
1. 项目核心分析:一个宠物饲养管理系统到底管什么
1.1 需求拆解:从养宠真实痛点出发
很多人一拿到题目就急着建表、写接口,这是最容易翻车的思路。做项目一定要先问一个问题:谁在用这个系统,他在什么场景下会觉得“这东西有用”?
养宠人群的真实痛点其实很集中。第一类是喂养记录混乱,今天喂了多少克粮、几点喂的、吃了什么牌子的粮,基本靠脑子记,一旦换人照顾宠物就全断片了。第二类是健康管理缺失,疫苗本纸质版容易丢,驱虫日期记不住,宠物不舒服了想翻历史记录也翻不到。第三类是提醒不智能,什么时候该打加强针、什么时候该做体内外驱虫,专业宠物医生会交代,但普通人转头就忘。
所以这个系统的核心功能就应该围绕这几个痛点来设计:宠物档案管理、日常喂养记录、健康档案(体重、体温、精神状态)、疫苗与驱虫计划、待办提醒。页面可以做得花哨,但底层逻辑必须先把这几条线理清楚。这一点在配套文档里也会反复强调,面试的时候被问到“你为什么做这个功能”,你就可以直接回答是因为养宠场景里有这个真实需求,而不是拍脑袋想的。
1.2 整体设计方案:Android端加SpringBoot服务端的双层架构
整体架构采用Android原生客户端 + SpringBoot后端服务端,数据统一存到MySQL。为什么不用纯本地存储?一开始我确实想过用SQLite把数据全存在手机里,做起来更简单,但仔细想就发现问题了:用户换手机怎么办?家里另一个人也要看宠物记录怎么办?所以一定要有服务端做数据汇聚和业务逻辑处理。
为什么前端选Android原生而不是微信小程序?一是题目的技术栈限定就是Android,二是原生应用确实有一些小程序替代不了的能力,比如桌面快捷方式、桌面小组件、本地图片缓存、更流畅的列表滑动体验。这个项目里我加了自定义动态图标和主题色切换,小程序做这些东西限制会比较多,原生实现起来就很顺手。
分层设计上,服务端遵循标准的Controller-Service-Mapper三层结构。Controller只做参数接收和结果封装,Service负责业务逻辑和事务控制,Mapper负责数据库操作。Android端则采用MVC思路,Activity/Fragment负责界面展示和交互,网络请求封装成独立工具类,数据模型和接口Bean单独放一个包。前后端通过JSON格式交互,接口语义尽量做成RESTful风格。
2. 技术选型与开发环境准备
2.1 服务端技术栈:SpringBoot + MyBatis + MySQL
后端用了SpringBoot作为主框架,数据库访问用MyBatis,数据库用MySQL。这套组合在Java后端求职项目中占有率极高,几乎成了标配,原因也很简单。
SpringBoot的价值在于自动配置和起步依赖。以前用SSH或者SSM写一个Web项目,要配一大堆XML文件,光是理解配置文件就能劝退新人。SpringBoot把常用场景的配置都封装好了,你只需要在pom.xml里引入对应的starter,框架会自动完成装配。比如引入spring-boot-starter-web,内嵌的Tomcat就直接起效,这就是“约定大于配置”的思想。
版本选择上要特别提一句,因为很多人在这一步踩坑。SpringBoot 2.x和3.x差异很大,3.x要求JDK17及以上,而很多教材和视频还停留在JDK8。如果你是新手,建议选择SpringBoot 2.7.x + JDK8这种经典组合,跟绝大多数教程兼容,社区资料也多。等你把基础跑通了,再自己去了解SpringBoot 3.x的改动点。
MyBatis这边我不建议用 MyBatis-Plus ,它确实省代码,但面试的时候人家看你项目里全是BaseMapper的现成方法,问一个SQL优化你根本不理解,减分比加分还快。用原生MyBatis写XML映射,至少你清楚每条SQL在干什么,动态SQL、结果映射、参数传递这些核心概念都能练到。
2.2 Android端技术选型:Java原生加关键组件
Android端也用Java语言开发,好处是前后端语言统一,思维切换成本低。开发工具用Android Studio,这是目前官方唯一推荐的IDE,没有之一。
网络请求这块我推荐用OkHttp + Retrofit的组合。OkHttp负责底层的HTTP连接和请求分发,Retrofit负责把接口定义变成Java方法,配合Gson解析JSON,写起来非常舒服。你可以提前定义一个统一的ApiClient单例,把baseUrl、超时时间、拦截器都配好,后面的接口调用基本上就是加一个方法的事。
UI层面有几个组件值得重点掌握。首页用ViewPager2做宠物卡片轮播,配合TabLayout切换不同宠物;列表页用RecyclerView搭配SwipeRefreshLayout实现下拉刷新,这是Android最经典的列表组合;详情页用CoordinatorLayout+AppBarLayout实现头部的联动折叠效果,这是很多App首页都有的交互,面试官看到你用了这个细节会明显加分。数据图表可以用MPAndroidChart这个开源库,体重趋势折线图、喂食量柱状图都能快速画出来,比用Canvas硬画省十倍力气。
2.3 开发环境搭建与构建工具
后端构建工具用Maven,版本3.6以上即可。IDEA启动SpringBoot项目时,要在Edit Configurations里确认Main类正确,再检查启动端口有没有被占用。常见的问题就是上次项目没关干净,8080端口还活着,新项目起不来,这时候把端口改了或者把旧进程结束掉就行。
Android端构建工具是Gradle,它相比Maven更灵活,但第一次下载依赖确实慢。建议在build.gradle里配置阿里云镜像仓库,下载速度能快好几倍。还有一个问题很多人问,就是Android Studio怎么设置中文。新版本直接插件市场搜Chinese,安装中文语言包重启即可;旧版本可以在Settings -> Plugins里同样操作。不过我个人建议,界面用中文上手没问题,但代码里的命名和注释尽量用英文,养成习惯对以后看国外文档有好处。
环境搭好之后,先用一个空的SpringBoot项目把Hello World跑通,再用一个空Android项目把请求接口跑通。前后端能通信了,再往里面叠业务功能,这样定位问题会非常快。
3. 数据库设计与核心功能落地
3.1 核心表结构设计
表结构是这套系统的地基,设计得好后面写代码就是填表,设计不好后期改起来想哭。我基于常见实践给出最核心的五张表:用户表、宠物档案表、喂养记录表、健康档案表、疫苗与驱虫记录表。
用户表字段相对简单:id、username、password(存MD5或BCrypt加密后的值)、phone、avatar、create_time。密码千万不要存明文,这是安全底线,面试问起来也是一道送分题。
宠物档案表要重点设计:id、user_id表示归属用户、name宠物昵称、type宠物类型(猫/狗/其他)、breed品种、birthday生日、weight当前体重、avatar宠物照片地址、gender性别、is_neutered是否绝育、remark备注。这里有个小细节,宠物体重单独存当前体重,但历史体重要放到健康档案表里,这样画趋势图才有数据可查。
喂养记录表:id、pet_id关联宠物、feed_time喂养时间、food_type粮食类型(猫粮/狗粮/零食/营养膏)、food_brand品牌、food_weight喂食量(克)、note备注。这个表是日常使用频率最高的表,量级也会最大,所以查询的时候一定要带pet_id条件,并且按feed_time倒序分页,否则数据多了之后慢查询是必然的。
疫苗与驱虫记录表:id、pet_id、type类型区分疫苗还是驱虫、disease_name对应防治的疾病(猫三联、狂犬、体内驱虫等)、vaccine_time接种时间、next_time下次应接种时间、hospital接种地点、remark。next_time这个字段非常关键,后续的提醒功能就靠WHERE next_time BETWEEN 今天 AND 7天后来筛选。
如果你要用这套表结构,记得把时间字段统一设计成datetime类型,不要用varchar存日期字符串,否则做范围查询的时候你会被各种隐式类型转换坑死。这个也是我在项目文档里反复强调的点。
3.2 后端API设计与实现
后端接口设计遵循RESTful风格,资源用名词表示,操作用HTTP方法区分。用户模块是POST /api/user/register注册、POST /api/user/login登录、GET /api/user/info获取个人信息;宠物模块是GET /api/pet/list查询当前用户宠物列表、POST /api/pet/add新增宠物、PUT /api/pet/update修改宠物、DELETE /api/pet/{id}删除宠物;记录模块是围绕/api/record展开的喂养记录增删改查。
统一返回值类型很有必要,我习惯定义一个Result<T>类,包含code、message、data三个字段。成功时code为200,业务异常时code为500,未登录时code为401。这样Android端解析响应时只看一个code就能决定要不要弹Toast,而不是每个接口都写一堆重复判断。
登录认证我用的是Token机制,简单说就是用户登录成功后,服务端生成一个唯一Token返回给客户端,客户端后续请求都带上这个Token,服务端通过拦截器校验。Token的生成方案有很多,教学项目直接用UUID加上Redis存储,或者用简单的JWT,都不复杂。核心是让Android端在OkHttp拦截器里统一给请求添加Authorization请求头,而不是每个接口里手动传,这个习惯要早点培养。
分页查询是必考的,直接用MyBatis的PageHelper插件可以少写很多代码。前端传pageNum和pageSize,后端返回总条数和列表数据。Android端用RecyclerView的加载更多逻辑配合,就能实现无限滚动列表。
3.3 Android端页面与交互实现
Android端的代码组织建议按照功能模块分包:activity放页面,adapter放列表适配器,model放数据Bean,network放网络请求封装,utils放工具方法。全部代码堆在MainActivity里是新手最容易犯的问题,一个文件两千行,自己维护都崩溃。
登录注册页是这个系统的第一个门槛,也决定了第一印象。注册时做两次密码一致性校验,登录时做非空校验,网络请求发出去之后要禁用按钮防止重复提交。服务端返回错误信息时,用runOnUiThread切回主线程弹Toast提示,不要在主线程做耗时操作。
首页建议用Fragment承载不同的Tab页,底部导航用BottomNavigationView实现四个Tab:首页(宠物概览)、记录(喂养记录列表)、健康(体重和疫苗信息)、我的(个人中心)。首页顶部用Banner轮播宠物卡片,每个卡片显示宠物头像、昵称和当前体重,点击进入宠物详情。详情页里再放三个子入口:喂养记录、健康档案、疫苗计划。
网络层封装我单独说明一下。定义一个ApiService接口,用注解声明请求方式和路径:
public interface ApiService { @POST("api/user/login") Call<Result<LoginInfo>> login(@Body LoginRequest request); @GET("api/pet/list") Call<Result<List<PetInfo>>> getPetList(@Header("Authorization") String token); @POST("api/record/feed/add") Call<Result<Void>> addFeedRecord(@Body FeedRecordRequest request); }Retrofit会自动把方法调用变成HTTP请求,配合Gson把响应JSON解析成Result<T>对象,代码非常干净。每个页面拿到Call之后,用call.enqueue(callback)发起异步请求,在onResponse里更新UI,在onFailure里提示网络异常。注意一定要在onDestroy里调用call.cancel(),否则页面已经销毁了,回调还是会执行,内存泄漏就出现了。
4. 实操过程:从源码导入到跑通全流程
4.1 启动前环境核对
不管你是拿到这套源码跟着敲,还是自己从头写,跑起来之前都要先核对环境。我的建议是先做一次清单检查,不要等报错再回头找原因。
后端需要准备的是JDK8或JDK17(取决于SpringBoot版本)、Maven 3.6+、MySQL 5.7+或8.0、IDEA或Eclipse。Android端需要准备Android Studio最新稳定版、SDK Platform对应你手机系统的版本、Gradle环境(一般Android Studio自带)。如果是模拟器联调,推荐用夜神或者MuMu,比Android Studio自带的AVD启动速度快很多,内存占用也低。
如果你是第一次跑SpringBoot项目,我建议先把MySQL密码重置成简单一点的,比如root/123456,后面配置文件里填这个连接串成功率最高。真用生产密码也行,但记得改application.yml里的配置,别拿着默认配置一顿操作最后发现数据库密码不对。
4.2 后端启动步骤
后端启动顺序有讲究。第一步先建数据库,在MySQL命令行执行源码带的pet_system.sql,把表结构和初始数据导入。执行完用SHOW TABLES;检查一下表是否都建出来了。第二步用IDEA打开后端源码目录,等Maven把依赖全部下载完,这个过程第一次可能比较久,耐心等。第三步修改application.yml里的数据库连接、端口和文件上传路径。
配置文件里的关键参数我贴到这里:
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/pet_system?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.pet.system.model注意serverTimezone必须设置,否则MySQL 8.0会报时区相关的连接错误。
最后运行主类里带@SpringBootApplication注解的main方法,看到Spring Boot的启动日志里出现Started PetSystemApplication in x.xx seconds就说明后端起来了。这时候你在浏览器访问http://localhost:8080/api/user/info,应该能看到一个JSON格式的返回结果或者404错误页面,总之说明服务在监听状态。
4.3 Android端联调步骤
Android端跑起来的重点在改baseUrl。模拟器访问电脑本机的后端服务,不能用localhost,因为模拟器里的localhost指向模拟器自己。正确写法是http://10.0.2.2:8080,这个是Android模拟器专门用来访问宿主机的特殊地址。真机调试的话,改成你电脑在局域网里的IP,比如http://192.168.1.101:8080,手机和电脑要连同一个WiFi。
还有Android 9.0及以上版本默认禁止HTTP明文请求,而我们的后端地址通常是http://而不是https://,所以要在AndroidManifest.xml里配置usesCleartextTraffic="true",否则会抛Cleartext HTTP traffic not permitted异常。这个报错信息非常经典,网上搜一下一大片,但我希望你看完这篇文章后不用搜就能解决。
配置好之后,先注册一个账号,然后登录,如果一切正常,首页会出现一个空宠物列表。这时候就可以添加第一只宠物了,填名字、选类型、选生日、传照片,保存后再回到首页,宠物卡片就能显示出来。至此前后端联调成功,后面就是一条条功能地过。
4.4 运行视频与讲解视频的使用建议
这套工程配套的运行视频和讲解视频,我建议不要当电视剧刷,要当实验手册用。第一遍先快速看完运行视频,知道系统最后的形态是什么样,这样你写代码的时候脑子里有目标。第二遍再看讲解视频,重点关注数据库设计和接口设计这两块,这是整个系统的骨架,理解透了你改功能、加功能都容易。
正确使用视频的办法是边看边暂停。视频里讲到某个接口实现,你就切到自己IDE里找到对应代码看一遍,然后在模拟器里操作一遍。三段联动下来,一个知识点才算真正掌握。尤其要注意的是讲解视频里老师如果提到了某个坑或者某个异常,一定记下来,因为那都是人家跑通之后回过头来补充的经验,比功能实现本身更值钱。
5. 常见问题与排查技巧实录
5.1 高频问题速查表
我把这个项目里最容易出问题的点整理成了一张表,按出现频率排序,你遇到问题可以先查表。
| 问题现象 | 可能原因 | 解决办法 |
|---|---|---|
| 后端启动失败,提示端口被占用 | 上次项目没关干净,8080占着 | 结束Java进程,或把端口改成8081 |
| 连接数据库失败,Access denied | MySQL密码和配置文件不一致 | 重置密码或改配置文件,保证连接串和密码匹配 |
| 依赖下载慢或总是失败 | 默认走了国外Maven仓库 | 在pom.xml配置阿里云镜像,重新刷新 |
| Android模拟器访问不到后端 | baseUrl用了localhost | 改成10.0.2.2:8080 |
| 请求报Cleartext HTTP not permitted | Android 9+默认禁明文HTTP | Manifest里加usesCleartextTraffic="true" |
| 中文乱码 | 数据库和连接串字符集不一致 | 数据库建库时指定utf8mb4,连接串加characterEncoding=utf8 |
| 接口能通但登录一直失败 | 密码加密方式不匹配 | 确认注册和登录使用了相同的加密逻辑 |
| Gradle同步失败 | Gradle版本和插件版本不匹配 | 按提示降低或升高Gradle版本,用官方推荐的对应关系 |
| 上传图片不显示 | 文件保存路径不对 | 检查配置文件里的上传路径和访问路径是否一致 |
| 分页加载重复数据显示 | 页码从0开始或从1开始没统一 | 约定前后端pageNum的起始值,统一从1开始 |
这里每一个坑我都实际踩过,尤其是密码加密不匹配和图片路径不一致这两个,排查时间最久,原因也最隐蔽。建议你联调的时候一个模块一个模块地过,不要全做完再联调,不然多个问题叠加在一起,排查难度翻倍。
5.2 独家避坑技巧
第一个技巧是后端的Banner自定义。SpringBoot启动时会打印一行默认的ASCII字符Banner,网上有很多在线Banner生成器,可以生成属于自己项目的图案。这个虽然不是功能需求,但演示的时候打眼一看就是你自己写的项目,代入感很强,面试官也容易记住你。
第二个技巧是接口写完一定要自测。我不建议每次都启动Android端去点按钮测试,推荐直接用Postman或者Apifox调接口,5秒钟就能发一个请求看到返回结果。后端接口返回没问题了,再联动Android端,这样出了问题你能确定是前端还是后端的锅。
第三个技巧是给宠物实体加一个age的格式化输出。界面上一堆时间戳和日期字符串,用户看着不直观。写个简单的工具方法,根据birthday字段计算出X岁X个月,虽然是个小功能,但对体验提升很大,属于花了十分钟却能在演示时加分的细节。
第四个技巧是代码注释别偷懒。不要一个方法写一堆废话,而是在业务关键位置写清楚这个逻辑是为了解决什么问题。比如疫苗提醒的SQL,注释写“查询未来7天内需要接种疫苗的记录,提醒用户预约医院”,比写“查询疫苗表”强得多。这个习惯在面试做项目讲解的时候特别占便宜,看着注释就能流畅讲完整套业务。
6. 从项目到Offer:Java面试怎么讲这个项目
6.1 面试官最可能问的5个问题
做完了项目只是第一步,能把项目讲明白才是真正拉开差距的地方。我总结了一下基于这个系统面试官最可能问的五个问题。
第一个问题:为什么用SpringBoot而不用传统的SSM?你要答出自动配置、内嵌服务器、起步依赖、约定大于配置这几个关键词,再结合项目里具体哪个功能受益于这些特性。第二个问题:MySQL索引怎么设计?你可以拿喂养记录表举例,因为它的查询条件是pet_id和feed_time,所以应该建联合索引idx_pet_time(pet_id, feed_time),索引下推的理论也能顺手带出来。第三个问题:MyBatis的#{}和${}有什么区别?明确说#{}是预编译占位符防SQL注入,${}是字符串拼接有风险,项目中任何位置都没用${}拼SQL。第四个问题:事务怎么控制?以添加喂养记录的接口为例,如果涉及到修改宠物体重和写入喂养日志两步操作,必须加@Transactional保证原子性,面试他问这个是想考察你有没有分布式事务和并发意识。第五个问题:Android端和前端怎么交互的?答RESTful接口加JSON格式,登录用Token做无状态认证,图片走静态资源映射。
6.2 如何把项目讲出亮点
讲项目的时候有一个技巧,就是不要从注册登录开始讲,那听起来太像培训机构的标准模板。你要从一个用户故事切入,比如说“有个朋友养了一只猫,每次疫苗记录都找不到,所以我做了一个系统帮他记录所有饲养数据并提前提醒”,一句话就把用户痛点和产品价值挂钩了。
然后顺着故事节奏讲表设计、接口、关键页面。重点展示几个细节:一是宠物卡片轮播和CoordinatorLayout的联动效果,体现你的Android原生功底;二是疫苗提醒接口的SQL逻辑,体现你的数据查询能力;三是Token拦截器的设计,体现你的安全意识和代码组织能力。
还有一个小技巧是准备一两个自发优化点。比如你可以说“原本图片是Base64直传数据库,后来改成上传文件到本地磁盘、数据库只存路径,因为数据库更倾向于存结构化数据,大字段放库会导致表膨胀查询变慢”。这种主动思考过的点,比背一百道面试题都打动人。
这套系统本身也是一个很好的可扩展项目。如果你想继续加点难度,可以试试集成消息推送功能实现主动提醒,或者集成二维码扫码添加宠物,再或者做一个简单的数据分析页面统计喂养习惯。每一次扩展都是在增加你简历上的技术点,而且因为是在你自己跑通的项目上加的,遇到问题你能讲到细节,面试官就很难问倒你。
最后再分享一个小经验:跑通项目只是起点,真正让你成长的是跑通之后的问题排查过程。把每次报错、定位、解决的过程记录下来,你会发现这比代码本身还要宝贵。否则项目做完就忘,面试官深问两句你就会露怯。带着这套思路去把项目啃下来,你的收获会远远超过交差本身。