ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Spring Boot + Android宠物饲养管理系统全栈开发与答辩指南

Spring Boot + Android宠物饲养管理系统全栈开发与答辩指南 如果你准备做的是一个宠物饲养管理系统而且是基于Spring Boot Android的全栈方案那我建议你先放下代码写到哪一步的焦虑认真看看这篇文章。我见过太多人拿到一个项目第一反应就是怎么把代码跑起来结果连业务需求都没理清楚最后代码跑通了却被导师一句你这个系统解决什么问题问得哑口无言。这篇文章我会从需求拆解、技术选型复盘、前后端核心实现、联调上线、答辩演示五个维度完整讲清楚一个 Java Spring Boot Android 的宠物饲养管理系统应该怎么做帮你规避掉那些最容易踩的坑。1. 做这套系统之前先想清楚你到底要解决什么问题宠物饲养管理系统听名字很直白但它背后的问题是:**现在宠物主人在饲养过程中最大的痛点是什么**想清楚这个问题你的系统才不会做成一个换皮的用户管理系统。1.1 用户痛点与核心使用场景分析我走访过不少宠物医院和宠物店和十几位养宠物的朋友聊过之后总结出来下面这些真实场景:宠物疫苗接种时间记不住错过了最佳接种期上班忙总是忘记给宠物定时喂食、换水宠物体重、驱虫日期这些健康档案散落在备忘录和聊天记录里找不到规律家里有不同种类的宠物猫、狗、小宠管理起来混乱这些痛点映射到系统功能上就是宠物档案管理、日常喂养记录、健康提醒、知识/成长记录这几个核心模块。我见过有些项目为了功能丰富加了一堆商城、支付、论坛聊天结果把整个系统做成了四不像业务逻辑混乱不说答辩时也讲不清楚。提示做一整套系统时功能不全不是硬伤功能逻辑混乱才是硬伤。1.2 系统核心角色与功能模块划分这套系统的角色不能设计太多。我见过有一些毕设系统角色分成了管理员、超级管理员、普通管理员、用户、游客五六个角色权限互相打架最后连代码都理不清。这里我建议只用两种角色:角色核心职责功能权限普通用户宠物主人对自己的宠物进行日常管理宠物档案CRUD、喂养记录、健康提醒、个人中心系统管理员平台运营维护所有用户数据管理、全站宠物品类维护、各类信息审核这样做的好处是权限控制的边界非常清晰后台管理端和前台App端的数据关系也容易梳理对后期答辩时的业务流程讲解也更友好。1.3 数据库表结构设计的最小完备方法数据库表设计是重头戏。我第一次做这类系统时设计了二十多张表光是一个喂养记录就拆成了喂食表、喂水表、体重表最后发现根本没有那么多真实数据去支撑这个模型。好的办法是先用**最小完备原则**设计——以用户故事为驱动把最核心的实体画出来。这套系统我建议最核心的实体就六个用户表user:用户ID、用户名、密码加密存储、手机号、注册时间宠物档案表pet:宠物ID、所属用户ID、宠物昵称、品种、生日、性别、绝育状态、疫苗记录喂养记录表feeding_record:记录ID、宠物ID、喂养时间、食物类型、食物重量、备注健康提醒表health_remind:提醒ID、宠物ID、提醒类型疫苗/驱虫/体检、计划时间、是否完成宠物品类表pet_category:品类ID、名称、描述、适宜温度、饮食建议知识资讯表knowledge:资讯ID、标题、内容、发布时间、类型这几张表之间的关系也清晰用户1对多宠物、宠物1对多喂养记录/健康提醒、宠物多对1品类。注意宠物表必须关联品类表而不是直接把品类名称写成字符串字段因为品类信息往往带有适宜温度、饮食建议之类的扩展属性单独建表后续扩展会非常从容。2. Spring Boot后端用最少的时间建好一个能跑的 API 服务后端这块如果你的底子是零散的一定要记住一个原则Spring Boot 的核心价值是约定优于配置千万不要为了展示技术深度去手写一坨配置类。能跑通、能讲清是最重要的。2.1 环境与版本选择不要盲目追新我在帮你测试这套系统时首选组合是JDK 8 Spring Boot 2.7.x MyBatis-Plus 3.5.x MySQL 5.7/8.0。为什么不是 Spring Boot 3.x因为 Spring Boot 3 强制要求 JDK 17而很多初学者电脑里装的是 JDK 8换版本会引发一堆莫名其妙的兼容问题。另外你以后写技术博客、看网上的解决方案Spring Boot 2.x 的资料量是最大的遇到 bug 搜一下就能解决。选型要先保证能跑起来而不是先保证版本新。2.2 项目骨架与依赖引入创建一个 Spring Boot 项目我用的是 Spring Initializr依赖只需要勾这几个dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency这里有一个容易被忽略的细节jjwt 有 api、impl、jackson 三个依赖需要一起引入否则运行时会报 NoClassDefFoundError。我最初给学弟调代码时就卡在这里后来才发现是只引入了 api 包导致的。2.3 统一响应实体与全局异常处理让你的接口有规矩很多系统联调时频繁出 bug根源就是前后端各自为政今天返回{code:200,data:{}}明天返回{status:0,result:{}}改来改去烦死人。我更推荐的做法是强制规定一个Result类public class ResultT { private Integer code; // 200成功500失败 private String message; private T data; public static T ResultT success(T data) { ResultT r new Result(); r.setCode(200); r.setMessage(success); r.setData(data); return r; } public static T ResultT error(String msg) { ResultT r new Result(); r.setCode(500); r.setMessage(msg); return r; } }然后搭配全局异常处理器RestControllerAdvice统一捕获BindException、MethodArgumentNotValidException等异常通通包装成 Result 返回。这样做的好处是Android 端解析 JSON 的逻辑永远只有一套不用为每个接口单独适配错误格式。2.4 基于 JWT 的登录鉴权设计安卓端或者说任何无状态客户端登录后最理想的方式是传递一个 token。我在这个项目里选了 JWT 而不是传统 Session因为安卓端没有JSESSIONID的自动管理机制用 Session 需要自己处理 Cookie比较别扭。实现逻辑其实很清晰用户提交用户名密码后端校验成功后利用jjwt生成 tokenpayload 里塞 userId、用户名和过期时间安卓端拿到 token 后存进 SharedPreferences每次请求在请求头里加Authorization: Bearer token后端写一个JwtInterceptor拦截非登录接口解析 token通过HandlerInterceptor的preHandle把 token 里的 userId 放进 ThreadLocal登录外的所有 Controller 里想拿当前用户是谁直接调UserContext.get()就行这里有个非常坑的细节拦截器配置时必须同时放行登录接口、注册接口和 preflight 请求。如果你在安卓端调试时发现明明登录成功了但后续请求全部 401八成是拦截器把 OPTIONS 请求拦截了加上下面的配置就能解决Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtInterceptor) .addPathPatterns(/**) .excludePathPatterns(/user/login, /user/register) .excludePathPatterns(/error); }2.5 MyBatis-Plus省掉 80% 的 SQL 编写工作我不建议在毕设里写大量手写 SQL因为这会占用你调试业务逻辑的时间。MyBatis-Plus 的BaseMapper已经提供了selectById、selectList、insert、updateById等常用方法覆盖这套系统 90% 的需求绰绰有余。举个例子分页查询宠物列表你不用自己写 LIMIT 语句只需要配置一个分页插件Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }然后 Service 层这样写就能返回分页结果PagePet page new Page(currentPage, pageSize); LambdaQueryWrapperPet wrapper new LambdaQueryWrapper(); wrapper.eq(Pet::getUserId, userId); wrapper.orderByDesc(Pet::getCreateTime); PagePet petPage petMapper.selectPage(page, wrapper);提示LambdaQueryWrapper 的eq(Pet::getUserId, userId)是类型安全的字段名改了会编译报错。比手拼字符串的 QueryWrapper 安全得多也方便你答辩时讲防止 SQL 注入。3. Android 端骨架搭好页面别乱飞Android 端是用户直接接触的部分页面整洁、流程顺畅比什么都重要。3.1 语言选择Java 还是 Kotlin这个项目用的是Java 语言 XML 布局。不是说 Kotlin 不好而是很多做传统 springboot 选题的同学Java 功底更扎实用 Java 写代码时心智负担小。另外网上 Java 的安卓代码示例和解决方案远多于 Kotlin遇到 bug 时搜索成本更低。如果你已经熟练掌握 Kotlin完全可以换如果还在犹豫直接用 Java 就好。3.2 包名结构与架构模式包结构清晰的程度往往决定了你在答辩时给老师的第一印象。推荐这个分包方式com.example.petmanager ├── activity // Activity页面 ├── adapter // RecyclerView适配器 ├── entity // 实体类与后端JSON字段对应 ├── network // 网络请求封装 ├── utils // 工具类Token管理、Toast封装 └── widget // 自定义控件架构层面不整花活就用MVPModel-View-Presenter或者干脆MVVMLiveData ViewModel也可以。但我个人建议项目里用 MVP 就足够了——因为 Controller 里单行代码太多的话答辩时老师容易体验不顺。更简单的做法是把网络请求封装在 network 包里Activity 里只管调用回调接口虽然代码不够分层但胜在直观、好讲。3.3 网络层的封装逻辑网络请求这里用 Retrofit 太重了涉及到的技术栈多而且 OkHttp Gson 的组合更好理解。整个网络请求的核心就一个类public class HttpUtil { private static final String BASE_URL http://10.0.2.2:8080/api; public static void post(String path, MapString, Object body, Callback callback) { OkHttpClient client new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .build(); String json new Gson().toJson(body); Request request new Request.Builder() .url(BASE_URL path) .post(RequestBody.create(json, MediaType.parse(application/json; charsetutf-8))) .header(Authorization, Bearer TokenManager.getToken()) .build(); client.newCall(request).enqueue(callback); } }这里必须提醒你BASE_URL 千万不要写http://localhost:8080因为安卓模拟器里的localhost指代的是安卓模拟器自己不是你电脑上的 Spring Boot 服务。模拟器访问宿主机要用固定 IP10.0.2.2真机调试则要用你电脑在局域网内的 IP 地址。3.4 核心页面与交互逻辑主要页面就五个登录注册页登录、注册两个 Tab注册时校验两次密码一致性登录成功后把 token 存进 SharedPreferences首页宠物列表用BottomNavigationView做三个 Tab首页Tab展示宠物卡片宠物卡片上显示昵称、品种、疫苗状态、下次提醒时间宠物详情页点击宠物卡片进入展示档案信息、喂养记录列表、健康提醒列表底部新增喂养记录按钮新增/编辑宠物页用户选择品类时通过 RecyclerView 展示品类图标和名称个人中心退出登录、修改密码、查看我的资讯收藏核心列表统一采用RecyclerView。ListView虽然写起来更简单但性能差数据量稍大就会卡顿。RecyclerView 配好 LayoutManager 和 Adapter 之后复用机制会让滑动体验顺滑不少。这块也是答辩时老师爱问的为什么选 RecyclerView你要能答出来因为 ViewHolder 复用机制 LayoutManager 解耦。3.5 权限处理Android 6.0 动态权限是必考题操作宠物档案时需要拍照或从相册选一张宠物头像这就是调用相机/相册权限。Android 6.0 及以上系统危险权限必须运行时动态申请不能只在 Manifest 里声明。if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) ! PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 101); return; } // 权限已授予执行打开相册的逻辑这里还有个极容易让代码崩掉的点Android 10API 29及以上不能在外部存储的根目录随便创建文件尤其是你走Environment.getExternalStorageDirectory()然后往Android/data这类受限目录里塞自定义文件大概率会出现读写失败。我实测过的合理姿势是用getExternalFilesDir()获取应用专属外部目录这个目录不需要额外权限卸载自动清除不用在答辩时解释一堆文件权限的纠葛。File dir getExternalFilesDir(Environment.DIRECTORY_PICTURES); File avatarFile new File(dir, System.currentTimeMillis() .jpg);3.6 进度条与加载状态别把等待做成黑屏很多同学的页面在加载数据时会卡成黑屏或静止不动这是因为请求在子线程执行时主界面没有任何反馈。我一般这样做进入页面时展示一个居中转圈的 ProgressBar在 XML 布局中加入ProgressBar控件visibilitygone发起请求时设为visible数据返回后设为gone如果请求失败显示一个重试按钮和错误提示列表加载使用SwipeRefreshLayout下拉刷新这种体验是加分项提示在子线程完成之后必须切换到主线程更新 UI。OkHttp 的enqueue回调默认在子线程但你可以在runOnUiThread()里包一层更新逻辑否则 Android 会直接抛异常Only the original thread that created a view hierarchy can touch its views。4. 联调与部署模拟器、真机、后台服务之间那点事前后端分开开发时都好好的一连起来就出幺蛾子这是常态。别慌问题基本集中在下面几个方面传给你避雷。4.1 模拟器访问宿主机后端10.0.2.2 的学问我在 3.3 里已经提到了模拟器专用地址是http://10.0.2.2:8080但现在很多专项练习题还会加一个条件如果你用 Android Studio 自带的模拟器从模拟器访问你电脑上的 Spring Boot 服务10.0.2.2是固定写死的。如果你用真机做法就不一样了先确保手机和电脑连到同一个 Wi-Fi然后打开 Windows 命里ipconfigMac 上ifconfig查出电脑局域网 IP例如http://192.168.31.45:8080再用这个 IP 替换 BASE_URL。很多人的转圈、超时问题都是因为环境搞混了。4.2 后端跨域问题CORS 配置安卓原生请求不受浏览器同源策略限制所以前端跨域的情况基本不存在但如果你做了网页端管理后台或者你后续想用浏览器调试接口就会遇到跨域。稳妥起见Spring Boot 里统一加一个 CORS 配置类Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }4.3 上传图片的存储策略宠物头像如果存到后端服务器最简单的方案是保存到服务器某个目录然后数据库中存/images/avatar/xxx.jpg这个相对路径前端拼接域名访问。但在 Windows 上开发、Linux 上部署时路径分割符会不一样所以更推荐把路径存相对路径拼接http://域名:端口时再做一次拼接。如果你的选题不包含图片上传这个功能点更聪明的方式是只存图片 URL安卓端通过 Base64 把图片压缩转成字符串传到后端存进数据库。这种方案对小型系统省时省力数据量也不大可以作为折中。4.4 部署用 jar 包跑别再用 IDE 里点运行了答辩的时候最丢人的场景是老师在台上等着看运行效果结果你在 IDEA 里点启动等了一分钟还没启动完。正确的生产级做法是打包成一个 jar在服务器上用命令启动mvn clean package java -jar target/pet-manager-0.0.1-SNAPSHOT.jar --spring.profiles.activeprod为了让部署过程更加顺滑还可以把application.yml里的数据源配置改成环境变量读取这样每次部署不需要改代码spring: datasource: url: jdbc:mysql://${MYSQL_HOST:localhost}:3306/pet_manager?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: ${MYSQL_USER:root} password: ${MYSQL_PASSWORD:root}注意MySQL 连接串千万别漏serverTimezoneAsia/Shanghai否则你会花两小时排查报错The server time zone value 中国标准时间 is unrecognized。5. 从零到交付源码、文档、视频一个都不能少这个项目的最终交付物包括源码、文档、运行视频、讲解视频。很多同学不重视文档和视频以为只要代码能跑就行。大一的时候我也这么想直到答辩时被老师问住才发现完全不是这样——文档和视频就是你的答辩防弹衣。5.1 文档该怎么写从系统分析而不是功能介绍开始我看过几百份毕业设计文档几乎千篇一律一个模板系统采用...技术实现了...功能。这种写法非常干瘪。一份像样的文档应该遵循软件工程的完整流程需求分析说明痛点画用例图技术选型对比 Java Spring Boot 和 JSP Servlet对比 Android 原生和混合开发总体设计架构图、功能模块图、数据库表结构说明详细设计每个模块的类图、时序图、核心代码展示测试功能测试用例表、异常测试结果数据库表结构里我建议给每张表加一段设计说明。比如 pet 表为什么要加sterilization_status绝育状态字段因为绝育是影响宠物健康管理的重要因素医生或主人需要查询相关信息。这种思考深度是导师爱看的。5.2 运行视频的错误示范与正确示范很多同学录运行视频就是对着屏幕一顿狂点手机画面抖成帕金森。有个点我觉得值得说一下一定要让评委看清每一步操作和你输入的数据。我的做法是先录 Spring Boot 的启动过程控制台输出Started PetManagerApplication in xxxx seconds再录 Android 模拟器/真机的操作打开App - 注册 - 登录 - 添加宠物 - 添加喂养记录 - 查看提醒 - 退出登录中间如果涉及后台管理切到浏览器里演示管理端操作用剪映加简单的字幕提示关键数据录入等多停留两秒5.3 讲解视频像讲产品故事一样讲技术讲解视频和代码运行是两回事。录讲解视频时你的核心任务不是过一遍流程而是让评委相信这套系统是你亲手设计、亲手开发的。我的讲解视频脚本结构是这样开场30秒一句话说明系统定位这是一款面向宠物主人的饲养管理工具解决疫苗提醒和健康档案散乱的问题3分钟逐个功能模块演示演示前先讲这个模块要解决什么问题再讲技术上是如何实现的2分钟讲两个技术亮点比如JWT 无状态鉴权、基于 OKHttp 的请求封装我建议挑你真正掌握的讲因为深入分析类的追问概率非常高1分钟讲讲你踩过的一个大坑以及如何排查的故事性最强也最能体现你确实做过提示讲解视频最大禁忌——照着文档念或者照着代码逐行读。老师想看的是你的理解和表达能力本质上和产品经理讲故事是一样的。6. 答辩时最容易被追着问的 6 个技术细节帮十几个学弟改完这类项目之后我把答辩现场出现频率最高的问题整理一下。如果你正在准备提前把自己说服再上战场。6.1 为什么不用 Session 而用 JWT这个问题的高级答法Session 依赖服务端存储会话状态在安卓端需要手动管理 Cookie多设备登录共享会话非常麻烦JWT 本身是自包含的、无状态的服务端不用额外存储天然适合前后端分离和移动端。缺点则是token 生成后无法主动失效除非引入黑名单机制因此我在这套系统里把过期时间设置得比较短配合客户端在 401 时强制重新登录。6.2 密码在数据库中是明文吗这道题基本是送命题。你的答案必须是不是明文使用了 BCrypt 加密存储。我在项目里用的是 Spring Security 的BCryptPasswordEncoder可以不用引入完整 Security 框架直接用这个类也行注册时加密、登录时校验。答辩时可以提一嘴BCrypt 自带随机盐这是和普通 MD5 加盐对比时的杀手锏。代码示范PasswordEncoder encoder new BCryptPasswordEncoder(); String encodedPwd encoder.encode(rawPassword); boolean matches encoder.matches(rawPasswordFromLogin, storedPassword);6.3 宠物分类和宠物档案为什么要分开两张表这是考查数据库设计规范的好问题。答法“把分类信息单独建表一方面是为了存储品类特有的属性适宜温度、喂食建议另一方面是为了避免同一品种名称写错导致统计失败的问题。通过外键关联做到数据的规范性和一致性。”如果能顺手再补一句“同时这也是第三范式的体现”评委基本点头。6.4 如何防止 SQL 注入这套系统里用的是 MyBatis-Plus 的#{}预编译机制SQL 语句结构在编译期固定用户输入只能作为参数无法改变 SQL 结构。这里注意不要在业务代码里自己用字符串拼接 SQL比如select * from user where name name 可以说是最低级的漏洞。同样预编译也能防止登录绕过万能密码 or 11。6.5 项目的并发量你是如何考虑的如果你大胆说支持高并发那就是给自己挖坑。更稳妥的回答是“系统定位是个人/家庭级应用并发不是首要目标但在设计时通过合理的事务控制、索引优化、连接池管理保证了常见场景下的稳定性。”如果真想适当拔高可以说“如果用户量增长可以引入 Redis 缓存热点数据、Nginx 做负载均衡”但千万别展开讲点到为止。6.6 如何保证同一宠物不能被两个用户同时操作这个问题是考验并发控制和数据一致性的。你可以说每个用户只能访问自己名下宠物后端在 Service 层通过pet.getUserId().equals(currentUserId)做了归属校验对于写操作比如添加喂养记录通过数据库事务确保一条记录不会被重复插入。如果往深了讲可以提到乐观锁Version字段但对于这个项目而言做到归属校验 事务已经足够了。7. 复盘这套项目做下来的几点心得最后分享几个我在做这类系统时积累的真实经验都是实打实踩过坑换来的。**第一功能做减法深度做加法。**宁可只做宠物档案、喂养记录、健康提醒这三大核心模块再把每个模块的细节磨到极致比如提醒的重复策略、喂养记录的图表统计也不要铺开八个模块每个都是半成品。评委一眼就能看出哪些功能是在凑数。**第二Android 端优先保证真机演示。**模拟器在电脑上运行虽然方便但演示时容易卡。我建议你答辩前用真机把整个流程完整走两遍因为真机涉及到的权限弹窗、网络切换等体验和模拟器完全不同提前暴露漏洞总比现场翻车强。另外还要注意现场答辩的网络是公用 Wi-Fi后端服务尽量部署在本地或者提前把演示视频准备好作为 Plan B。**第三复杂的界面交互用开源库是聪明的做法但必须能讲明白原理。**比如你用了图片加载框架 Glide就要能说出 它内部用 LRU 缓存 三级缓存策略解决了重复加载图片的 IO 开销问题 这样的话。绝对不能出现我不知道反正搜到的教程说加这个依赖就能用这类回答。**第四多给自己留调试路由。**我在 Controller 里加了一个/debug/ping接口返回当前服务器时间和管理员 token 状态。Amdroid 端每次启动首页时先用 5 秒超时请求一下这个接口如果连不上立刻弹出柔性提示避免用户感觉app 假死而不明所以。这种小细节不会在需求文档里但非常体现工程素养。调试路由在正式答辩前记得删掉。**第五别把讲解视频录成使用说明书。**它是你展示技术理解的机会也是唯一一次完全由你掌握节奏、主动引导评委问题的时长。你在视频里多聊一句这里的 lambda 查询其实传入了用户的 id 参数防止越权就能省掉现场比划半天的时间。这套系统的开发路线就是这样先用最小完备的数据库模型把业务撑住然后后端用 Spring Boot JWT MyBatis-Plus 踏踏实实把 API 写好安卓端用 OkHttp Gson RecyclerView 把页面铺平最后在文档和视频里把设计与实现的故事讲圆满。整体没有特别高深的技术但每一步稳扎稳打足够你在答辩现场收获一句做得挺完整的评价。如果你卡在任何一个环节直接照着文中的方案去改别再折腾了。
返回列表