1. 项目定位与技术选型解析
先把这个项目看明白。表面上是“宠物中心信息管理系统”加“宠物领养”,实际上这是一个典型的移动端 + 服务端分离架构的毕业设计项目,技术栈锁定在 Java、SpringBoot 和 Android 三件套上。这三个词放在一起,基本就代表了当前高校里最常见的全栈玩法:后端用 SpringBoot 快速搭 RESTful API,移动端用 Android 原生开发,数据层走 MySQL + MyBatis。为什么这套组合如此泛滥?因为它的学习曲线相对平缓,生态成熟度极高,而且网上资料多到你想踩坑都难。
先说这个系统的核心价值。宠物领养不是简单的“展示—申请—领养”三步走,它背后牵涉宠物信息管理、用户认证、领养审核、待领养状态流转、甚至健康记录等子模块。结合项目标题里的“源码 + 文档 + 运行视频 + 讲解视频”,可以判断这套东西的目标人群很明确:在校学生、需要快速上手 SpringBoot + Android 联动开发的初学者,以及正在做毕设需要一套能跑能讲能答辩的项目的人。
从技术层面拆解,整个系统的信息流大概是这样的:Android 客户端通过 HTTP 请求访问 SpringBoot 后端接口,后端再操作 MySQL 数据库,数据以 JSON 格式返回给客户端解析渲染。整个链路里每个环节都有值得展开的细节,比如接口如何设计、图片怎么处理、领养状态如何流转、多表查询怎么优化。接下来的内容,我会以一个实际做过类似项目的人的视角,把这个系统从零到一拆给你看。
2. 服务端核心设计:从建表到接口,一步一步踩实
2.1 数据库设计:五张核心表和一张辅助表
一个合格的领养系统,数据库设计决定了功能天花板。我见过太多人一上来就建一张大表,把宠物信息和用户信息全怼进去,后面写接口时各种 JOIN 痛苦到怀疑人生。这个项目里,最合理的设计是拆出这几张核心表:用户表、宠物信息表、领养申请表、领养记录表、管理员表,外加一张用于存储图片路径的辅助表。
用户表至少要包含 id、用户名、密码(建议 BCrypt 加密存储)、手机号、邮箱、角色标识(区分普通用户和管理员)、注册时间这些字段。宠物信息表更关键,它需要记录宠物名称、品种、年龄、性别、健康状况描述、是否绝育、当前状态(待领养/已领养/暂不可领养)、照片路径、创建时间。这里有个容易忽略的细节:宠物状态字段一定要设计成 int 或 tinyint,用数字表示状态而不是直接存字符串。比如 0 表示待领养、1 表示申请中、2 表示已领养、3 表示下架。为什么?因为后续写 SQL 时WHERE status = 0比WHERE status = '待领养'性能好得多,而且改了文案不用改代码。
领养申请表是这个系统的核心流转表。字段至少包括 id、宠物ID、用户ID、申请时间、审核状态、审核备注。审核状态同样用数字标识:0 待审核、1 审核通过、2 审核拒绝。领养记录表则是在审核通过后生成的一条正式记录,包含领养人ID、宠物ID、领养时间、备注信息。这两张表分开的好处是职责单一:申请可以多次提交,但一条宠物最终只能产生一条成功领养记录。
我实际用过的设计里还给照片单独建了一张表,因为宠物可能需要多张照片展示,单靠一个 photo_url 字段存逗号分隔的字符串虽然也能用,但后期做照片轮播、删除单张照片时就会非常痛苦。拆一张图片表,关联宠物ID,代码清晰度提升一个档次。
2.2 SpringBoot 接口设计与分层架构
后端项目结构我强烈建议遵循 SpringBoot 标准分层模式:controller、service、mapper 三层,外加 entity/dto 和 config。有人觉得这种分层太死板,但对这个项目而言,分层清晰是能直接转化成答辩分数的。你自己想想,面试官或者老师问“你这个项目架构是什么样”的时候,你能说清楚每一层的职责和调用关系,这印象分直接拉满。
Controller 层只做参数接收和结果封装,Service 层处理业务逻辑,Mapper 层用 MyBatis 操作数据库。接口设计上,RESTful 风格是必然选择。宠物相关的接口大致是这些:GET 获取宠物列表、GET 获取宠物详情、POST 发布宠物信息、PUT 更新宠物信息、DELETE 删除宠物。用户相关接口包括注册、登录、获取用户信息。领养相关接口包括提交领养申请、查看我的申请列表、管理员审核申请。
以宠物列表接口为例,前端拿到的 JSON 结构大概长这样:
{ "code": 0, "message": "success", "data": { "total": 23, "list": [ { "petId": 1, "petName": "豆豆", "breed": "柯基", "age": 2, "gender": "公", "avatarUrl": "http://your-server.com/images/pet/1.jpg", "status": 0 } ] } }这里有一个非常实用的经验:统一返回格式。我会在项目里定义一个 Result 类,包含 code、message、data 三个字段,所有接口都返回这个格式。好处不用多说——客户端解析逻辑一致,错误处理统一,再也不用为“这个接口返回数组、那个接口返回对象”这种事抓狂。这个东西在真实项目中叫统一响应体,看起来简单,但很多人做毕设时为图省事直接返回裸数据,后面写 Android 端解析时各种 if-else 判断类型,纯属给自己挖坑。
2.3 登录鉴权:从 Session 到 Token 的实际选择
登录模块是很多人会卡住的地方。这里有一个关键决策:SpringBoot 和 Android 分离部署时,Session 方案在跨域场景下坑很多,所以我个人经验是强烈建议直接上 Token 机制。具体实现很简单,用户登录成功后,服务端生成一个唯一 Token(可以用 UUID,也可以用 JWT),存到 Redis 或者数据库里返回给客户端。Android 端把 Token 保存起来,后续每次请求在 Header 里带上它,服务端通过拦截器校验。
这里有一个我踩过的坑要重点说:用户密码千万不要明文存储,也千万不要用 MD5 直接加密存。正确的操作是用 BCrypt 加密,Spring Security 里有现成的类可以用,或者直接引入 jbcrypt 这个库。BCrypt 的特点是不需要你额外存盐值,每次加密结果都不一样,安全性比 MD5 高很多。哪怕有人说“毕设而已,不用这么讲究”,我还是劝你写上,因为答辩时老师问到“密码安全性怎么处理”的概率极高,这是个白送的加分点。
拦截器的实现逻辑大致如下:自定义一个 HandlerInterceptor,在 preHandle 里从 request 的 Header 里取 Token,查 Redis 或数据库校验有效性,无效就直接返回 401。然后注册拦截器,放行登录注册接口,拦截其他业务接口。这里有个细节,如果你用了权限区分(普通用户和管理员),可以用一个角色字段来判断是否允许访问管理员专用接口,同样在拦截器里做,不用额外引入 Spring Security 全家桶。
3. Android 端实现:从界面到网络层,把体验做实
3.1 项目架构:MVP 还是 MVVM?
Android 端的技术选型,直接影响你写代码时的手感和后期维护的难度。我见过不少毕设项目直接把所有逻辑全写在 Activity 里,几百行代码堆在一个文件里,能跑,但答辩时被追问“你的代码是如何组织的”就会很尴尬。建议用 MVVM + LiveData + ViewModel 这套组合,这个方案在网上的资料极多,而且 Android 官方也在推,相关的中文博客一搜一大把。
ViewModel 负责持有数据,LiveData 负责观察数据变化并更新 UI。用户点击“申请领养”按钮,ViewModel 调仓库层的网络请求方法,请求成功后在回调里把结果 post 到 LiveData,Activity 观察到数据变化再去更新界面。这套逻辑用代码表示就是这样:
public class PetDetailViewModel extends ViewModel { private MutableLiveData<PetDetailBean> petDetailLiveData = new MutableLiveData<>(); public void loadPetDetail(int petId) { PetRepository.getInstance() .getPetDetail(petId, new Callback<PetDetailBean>() { @Override public void onSuccess(PetDetailBean data) { petDetailLiveData.postValue(data); } }); } }Activity 里只需要观察这个 LiveData 并且更新 UI。这套模式写起来确实比一把梭要多敲几行代码,但带来的好处是逻辑清晰、职责分明,调试效率高很多。而且如果老师问起“你有没有考虑过项目的可维护性”,你就能拿出这套架构来讲,这就是加分项。
网络层方面,Retrofit + OkHttp 是这个项目的最佳搭档。Gson 负责 JSON 解析,OkHttp 负责底层连接,Retrofit 负责把接口定义和调用方式做封装。接口定义看代码就很直观:
public interface ApiService { @POST("user/login") Call<BaseResult<UserBean>> login(@Body LoginRequestBean request); @GET("pet/list") Call<BaseResult<PetListBean>> getPetList(@Query("page") int page); @POST("adopt/apply") Call<BaseResult<Void>> submitAdoptApply(@Body AdoptApplyRequestBean request); }这里有一个实用的建议:API 地址不要写死在代码里,在项目里单独建一个 Constants 类统一管理 BASE_URL,这样从模拟器的10.0.2.2:8080切真机调试的局域网 IP 时只需要改一处。这个细节实操中极其常见,一旦忘了,会花掉很多时间的排查精力。
3.2 核心页面拆解:从列表到详情再到申请流程
Android 端整个页面的观察顺序和用户的真实使用路径一致,我建议页面做成这样的结构。
首页是宠物信息流,用一个 RecyclerView 展示每只宠物的头像、昵称、品种和当前状态。Adapter 里绑定数据时,图片加载统一用 Glide,在绑定回调里判断状态字段,已领养的就给一张灰色图片盖一层遮罩,加一个“已被领养”的文字标识,体验上比只改一行文字精致很多。
宠物详情页是这个系统功能最密集的页面。上半部分是图片轮播区,用 ViewPager2 循环展示图集里所有照片;中间是宠物基本信息、性格描述、健康记录;底部是一个主导航按钮,根据宠物状态动态变化——状态为 0 时显示“我要领养”并触发领养申请流程,状态为 1 和 2 时按钮置灰,文案变成“申请审核中”或“已被领养”。
领养申请流程里,有一个很关键的交互:申请表单。这个表单除了基本的姓名联系方式之外,最好加上一段自定义的“领养理由”输入框,这个设计既是产品功能上的需要,也给系统后端审核提供了判断依据。表单校验也值得写,用户名非空、手机号 11 位、理由不少于 10 个字,这些都在 Android 端先用代码校验,再提交接口,能减少不少无效请求。
个人中心页面要展示的信息包括用户头像、手机号、我发布的宠物列表、我的领养记录。领养记录里每一条都要能显示宠物信息、申请时间、审核状态和审核备注。这个页面的复用程度是最高的,宠物的列表 RecyclerView 可以复用首页的 Adapter 或者单独写一个简化版,避免过度设计。
3.3 个性化细节:进度条与状态反馈
话题中提到了 android 进度条和 banner,这里我展开说说。网络请求是异步的,用户点了按钮之后界面必须第一时间给出反馈,不能让人干等。我实际做的方案是在加载宠物列表时显示一个居中的圆形进度条,写法上直接用原生 ProgressBar 即可:
progressBar.setVisibility(View.VISIBLE);请求完成或失败后把它隐藏。这个看起来基础的组件,实际应用中容易忽略一个问题:Activity 销毁时,网络请求的回调里再操作进度条就会闪退。所以必须在回调里先判断if (getActivity() == null || !isAdded()) return;,或者用 LiveData 方案让生命周期自己处理。千万不要为了省事,在这里写裸回调。
Banner 轮播图方面,如果你的首页要放运营位,可以不用重复造轮子,GitHub 上 Banner 库很多,选一个 star 多的,引入之后配置好图片地址就行。但要注意权限问题,新版 Android 要求网络图片必须先申请 INTERNET 权限,并且 HTTPS 证书如果有问题,Glide 会直接加载失败。这些说起来都是小细节,可是新手很容易在这些地方耗费大量时间,我在第 5 节会专门说几个典型的坑。
4. 联调实战:Android 后端连接的过程与策略
4.1 模拟器与真机的联调差异
很多初学者在跑通这个项目时,最大的障碍不是写代码,而是怎么让 Android 模拟器访问到电脑本地的 SpringBoot 服务。这里有一个常被忽略的常识:Android 模拟器里的localhost指向的是模拟器自己,而不是你的电脑。因此你要在电脑上访问自己本机的 SpringBoot 服务,模拟器里必须用10.0.2.2这个特殊地址。如果你的后端启动在 8080 端口,那么 Android 端 BASE_URL 就该写成http://10.0.2.2:8080。
用真机调试时,这个地址又要换成你电脑在局域网里的 IP,用ipconfig或ifconfig查出来,然后让手机和电脑连接同一个 WiFi,拼成http://192.168.x.x:8080。有个配套的问题是,SpringBoot 默认只监听 localhost,你需要在 application.yml 里把 address 改成0.0.0.0,否则局域网其他设备根本访问不到你的服务。这个配置很多人不知道,真机联调时白折腾一下午。
真实踩坑提醒:Android 9 及以上默认禁止明文 HTTP 流量,直接用 HTTP 请求http://10.0.2.2:8080,应用会直接拒绝连接并报CLEARTEXT communication not permitted。解决方案有两个,第一是在 AndroidManifest 的 application 标签上加android:usesCleartextTraffic="true",第二是写一个网络安全配置文件放行特定域名。对于毕设项目来说,第一种方案最快。
4.2 前后端接口联调的顺序与技巧
接口联调的效率,很大程度上取决于你写的日志够不够用。我的做法是后端在 Controller 里用 SLF4J 打印所有请求的参数和耗时,Android 端在 OkHttp 层加一个日志拦截器。这样一旦两边对不上,先看后端日志里有没有接到请求,再看 Android 日志里请求发了什么参数、返回了什么错误码,两步就能定位问题。
联调的推荐顺序是:先跑通登录注册,拿到 Token 并验证拦截器;再跑通宠物列表和宠物详情,验证图片加载;最后再跑领养申请和审核流程,验证状态流转。按照这个顺序来,每个环节的问题都能及时暴露,不会出现最后一天联调发现基础接口全崩、通宵补洞的尴尬局面。
一个常见问题:接口数据拿到了但 App 上不显示数据。这种问题大概率是解析字段对不上,比如后端返回的是petName,前端 Bean 里写成了name。这就是为什么我前面提到要统一返回格式,并且在 Bean 里用@SerializedName注解标好字段名。实在对不上的时候,把一个接口的原始 JSON 打印出来,跟前端 Bean 字段逐个对着查,通常肉眼就能发现。
5. 典型问题与排查心得:从数据库到部署,逐个击破
5.1 环境类问题速查表
这里把项目从拿到源码到最终跑通,环境方面最容易碰见的问题列成一张表,省得你反复试错。
| 症状 | 可能原因 | 解决方案 |
|---|---|---|
| SpringBoot 启动后端口被占用 | 8080 被其他程序占了 | 改 application.yml 的 server.port |
| MySQL 连不上 | 账号密码不对或服务没启动 | 检查服务状态,核对 username/password |
| Maven 依赖下载失败 | 仓库源不稳定 | 把阿里云镜像加进 settings.xml |
| Android 构建报 SDK 版本不对 | 项目编译版本高于本地 SDK | 在 build.gradle 里改 compileSdkVersion |
| 真机安装 App 时签名冲突 | 之前装过同包名的不同签名应用 | 卸载旧应用,或统一用同一个 keystore 签名 |
| 后端返回 JSON 中文乱码 | 字符集设置不正确 | 在 SpringBoot 的 application.yml 配置server.servlet.encoding.force=true |
5.2 业务逻辑与数据流转的坑
第二类问题集中在业务逻辑本身,这里特别提一个我印象很深刻的问题:宠物领养的状态一致性。假设用户提交了领养申请,但后端代码里只申请了审批,没有同步把宠物状态从“待领养”改成“申请中”,那么另一用户再刷新列表,就能看到同一条宠物再次点击“申请领养”。更严重的是,宠物被成功领养后,如果状态没改,它还会继续出现在首页列表里,这时候整个系统的核心业务逻辑就是错的。
解决办法是后端在提交申请接口里开启事务,在 Service 方法上加@Transactional注解,同时更新领养申请表和宠物状态表。这是一个非常标准的业务闭环,也特别适合在答辩时拿出来讲“你是如何保证数据一致性的”。同理,管理员拒绝领养申请时,一定要把宠物状态改回“待领养”,这条逻辑很多人写着写着就漏了。
还有一个常见问题是图片上传。如果你的版本支持用户发布宠物时上传图片,推荐把图片存到服务器本地目录,数据库只存路径。本地路径的映射有个 SpringBoot 技巧:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/images/**") .addResourceHandler("file:" + uploadDir + "/"); } }这样 Android 端拿到images/xxx.jpg路径后拼上服务器地址就能直接访问。上传接口用 MultipartFile 接收文件,注意大小限制:
spring: servlet: multipart: max-file-size: 5MB max-request-size: 10MB超过默认的 1MB 会报一个FileSizeLimitExceededException,这个错误信息非常容易让人困惑。
5.3 Android 端常见崩溃点
最后一个高频崩溃场景:访问网络接口时在主线程直接发请求,然后NetworkOnMainThreadException迎面就来。这个问题在入门阶段特别常见,解决办法就是确保网络请求都在子线程或 Retrofit 的异步回调里执行。
还有一个在旧项目兼容新机型时的典型问题:Android 6.0 及以上版本,一些危险权限(比如相机、定位)需要运行时动态申请,而不像以前在清单文件里声明就行。如果你做的功能涉及“拍照上传宠物照片”,又希望能把这块跑通,就绕不开运行时权限申请。
我的建议是——如果你的项目层面允许对功能做取舍,优先用“从相册选择照片”作为默认交互,它的权限申请流程相对简洁,在系统弹窗处理上也少一些边界情况。如果要两张都支持,也建议先把相册路径跑通,再补拍照方案,千万别一上来就陷到两套流程同时推进的复杂度里。运行时权限申请用 ActivityCompat.requestPermissions 可以解决,但要注意回调里分别处理同意和拒绝,并给一个合理提示。
最后,关于项目压缩包里的运行视频和讲解视频,我的建议是不要只看视频里跑通的流程,要自己动手把项目从零启动一遍。因为视频演示的环境和你的本机环境往往有细微差别,自己走一遍启动流程,把数据库脚本导入、改配置、启后端、开模拟器这套链路过过手,你在答辩时的底气会完全不一样。
6. 给自己留一手
这篇文章如果浓缩成一句话,就是:宠物领养系统真正的难点,从来不是某个单独的技术点,而是各个模块之间如何顺畅协作。我在实际开发里比较满意的一个决定,是先把数据库表和接口文档先确定下来,再动工写具体代码,光这一步就让后面的联调少走了许多弯路。你在复现或者改造这个项目时,也可以先顺着数据流转方向读一遍代码,然后把状态流转用文字整理出来,再动手加功能。照这个思路做下来,这个项目的源码会真正变成你自己能讲清楚的东西。