ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SpringBoot+Vue3+MyBatis实战:流浪动物救助系统全栈开发解析

SpringBoot+Vue3+MyBatis实战:流浪动物救助系统全栈开发解析 流浪动物救助这个场景说实话在IT圈里一直是个被低估的“练手做实”的好方向业务链路完整涉及用户端、管理端、状态流转、文件上传、权限控制技术覆盖面足够广又不像电商那样动不动就是高并发分布式。我最近就用 Java SpringBoot Vue3 MyBatis MySQL 把一个流浪猫狗救助救援网站系统完整做出来了前后端分离整套源码可以直接跑。这篇文章就把它从头到尾拆给你看适合两类人一是准备找Java全栈开发工作、想要一个能讲清楚技术点的项目的人二是真正想给本地救助站搭一套管理系统的运营方。1. 流浪猫狗救助系统的整体设计与技术选型拆解1.1 为什么选SpringBootVue3MyBatisMySQL这套组合先说结论这套组合不是配置最复杂的也不是性能天花板的但它是综合成本最低、最接近实际开发环境、最容易被人看懂的。后端用SpringBoot理由很朴素——它是Java后端当前的主流形态。自动配置省掉了一大堆XML配置内嵌Tomcat让部署变成“一个jar包跑起来”。我见过不少公司内部的管理系统连买服务器都不愿意就靠一台普通PC跑SpringBoot服务终端用户也不在乎什么微服务。这个项目的定位就是“中小规模的信息化平台”SpringBoot的边界感非常合适。Vue3作为前端框架最大的感受是Composition API带来的逻辑复用能力。以前写Vue2的Options API一个组件里data、methods、computed、watch割裂开业务一复杂代码跳来跳去。Vue3的setup语法糖让关联逻辑能聚在一起比如一个“救助列表页”的查询条件、加载状态、数据请求可以写成前后挨着的一段团队成员互相review代码时舒服得多。再加上Vite开发启动速度肉眼可见比Webpack快热更新几乎秒开。MyBatis是我个人的偏好也建议你再犹豫也选它。原因很简单救助系统里筛选条件极多——按动物类型、按城市、按救助状态、按时间范围组合查询这种场景MyBatis的动态SQL写起来是真顺手。JPA当然也能做但遇到复杂的多表关联查询JPA生成的SQL性能不好排查远没有自己掌控SQL来得踏实。而且从面试角度看MyBatis的一级缓存、二级缓存、TypeHandler、XML解析流程都是高频问题一个项目里真正用过这些点聊起来就是“实战经验”而不是“背答案”。MySQL则没啥可说的免费稳定对非盈利的救助站没有授权成本社区资料又全遇到问题一搜就有答案。信息管理类系统的并发量远没到需要上PostgreSQL或者分布式数据库的程度MySQL绰绰有余。1.2 前后台功能模块怎么划分这个系统我在设计时把功能拆成前台和后台两条线本质上对应两类用户普通市民发现流浪动物的人、想领养的人、志愿者和救助站管理员受理工单、维护宠物档案的人。前台核心模块包括首页公益展示放轮播图和最新救助动态流浪动物展示列表支持按城市、动物类型、救助状态筛选救助申请入口市民可以填写发现地点、上传照片、描述情况领养申请流程需要提交家庭条件和工作情况志愿者注册与排班查询。后台管理模块更偏事务处理宠物档案管理支持录入、编辑、上下架救助工单管理这是整条业务链的枢纽工单状态从待受理一路流转到完成领养申请审核管理员要确认申请人是否具备养宠条件通知公告发布数据统计看板按月统计救助数量、领养成功率。模块划分上有一个教训别把前台和后台的权限塞进同一个逻辑里。市民只能看到“可领养”状态的动物管理员能看全部。这种差异不仅仅靠前端隐藏按钮后端每个接口都要按角色做数据范围控制。后面我会详细说权限设计。1.3 核心业务链路与数据库表设计思路先想清楚业务链路再建表这是避免反复返工的关键。这个系统的核心链路是市民发现流浪动物 → 提交救助信息 → 管理员受理工单 → 出勤救助 → 动物到站或送医 → 健康评估 → 可领养或放归 → 市民提交领养申请 → 管理员审核 → 领养完成。围绕这条链路数据库的表可以这样规划用户表、宠物档案表、救助记录表、领养申请表、公告表、志愿者表。宠物表和救助记录表是两条平行的业务线宠物表描述“动物现在是什么状态”救助记录表描述“一次救助事件走到了哪一步”两者通过pet_id关联但不混在一张表里。字段设计上我会建议状态字段全部用TINYINT数字比如宠物状态0待救助、1救助中、2可领养、3已领养、4已放归。用数字的好处是后端可以用枚举类去约束流转而且存储开销小于字符串。更重要的是你要记录的是“状态变更的过程”不仅仅是“当前状态”所以救助记录表里应该有report_time、handle_time等多时间字段为以后做数据统计留好基础。2. 后端核心实现SpringBoot工程搭建与MyBatis实操要点2.1 工程分层结构与application.yml关键配置工程用Spring Initializr生成注意版本选型如果选Spring Boot 2.7.xJDK8或JDK11都可以如果选Spring Boot 3.x必须JDK17及以上。考虑到很多公司实际项目还在用2.7我这次用2.7系列等你工作后在老项目里也不至于手足无措。包结构我按下面的方式组织com.example.rescue ├── controller # 接口层只做参数接收和结果封装 ├── service # 业务层事务、状态流转都在这里 ├── mapper # MyBatis的Mapper接口 ├── entity # 数据库映射实体 ├── vo # 给前端展示用的视图对象 ├── config # 配置类比如CORS、JWT拦截器 ├── common # 统一返回结果、异常处理、常量 └── RescueApplication.java我见过很多新手把业务逻辑直接写在Controller里接口一多就乱成一锅粥。以“提交救助申请”为例Controller只接收DTO参数Service里要同时做三件事插入宠物档案、插入救助记录、给管理员生成一条待办通知。这三步要么都成功要么都回滚必须放在Service层的一个方法里。如果Controller里各调各的Mapper事务边界根本控制不住。application.yml的核心配置如下spring: datasource: url: jdbc:mysql://localhost:3306/rescue?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.rescue.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl逐项解释里面的坑characterEncodingutf8防止中文乱码serverTimezoneAsia/Shanghai不配的话MySQL驱动会报时区错误driver-class-name用com.mysql.cj.jdbc.Driver因为MySQL 8.x已经移除了旧驱动类map-underscore-to-camel-case开启后数据库的create_time能自动映射到Java属性createTime不用每个字段写别名StdOutImpl会在控制台把SQL和参数打出来联调排查问题几乎离不开它上线前记得关掉。2.2 MyBatis动态SQL、resultMap与缓存取舍宠物列表页是动态SQL最有代表性的场景用户可能不选城市可能不选类型也可能任意组合。用where标签比手动拼接where 11干净得多它会在条件成立时自动加上WHERE关键字并去掉第一个条件多余的AND。select idselectPetPage resultTypecom.example.rescue.entity.Pet select id, name, type, breed, gender, age, status, city, address, photo, description, create_time, update_time from pet where if testtype ! null and type ! and type #{type} /if if teststatus ! null and status ! and status #{status} /if if testcity ! null and city ! and city like concat(%, #{city}, %) /if /where order by create_time desc limit #{offset}, #{pageSize} /select注意like那里不要写成%${city}%${}是字符串替换存在SQL注入风险concat(%, #{city}, %)既安全又兼容性好。分页我建议手动limit不引入PageHelper。这个项目数据量不大手动分页逻辑清晰也不会遇到PageHelper和低版本MyBatis的兼容问题。等以后数据量真的上来再考虑MyBatis-Plus或PageHelper也不迟。resultMap在多表关联时很有用。比如救助记录列表要同时展示“救助人姓名”和“宠物简述”我建了一个RescueRecordVO用别名映射就够了select idselectRescueRecordPage resultTypecom.example.rescue.vo.RescueRecordVO select r.id, r.status, r.report_time, r.description, p.name as pet_name, u.real_name as reporter_name from rescue_record r left join pet u_pet? ... /select实际上会涉及多次join字段多的时候在XML里写列名和VO属性一一对应很容易错。我的做法是创建VO类时加注释然后认真核对每一列开发期打开SQL日志看是否全部映射成功。MyBatis缓存这里值得多说一句。一级缓存是SqlSession级别的同一个SqlSession里两次完全相同的查询会直接走缓存二级缓存是namespace级别的默认关闭要显式开启。在这个项目里宠物列表是频繁变化的数据我没有开二级缓存因为一旦有人改了宠物状态另一个SqlSession的缓存不会自动失效用户看到的就是脏数据。招聘里常问“MyBatis缓存你项目里怎么配置的”能说清楚“为什么这个场景不开二级缓存”比给一个“开启并配置了LRU”的千篇一律答案更有说服力。2.3 救助工单状态流转与事务控制救助工单的状态我用一个枚举类来约束比直接用数字散落在代码里要安全得多。public enum RescueStatus { PENDING(0, 待受理), ACCEPTED(1, 已受理), IN_PROGRESS(2, 救助中), COMPLETED(3, 已完成); private final int code; private final String desc; RescueStatus(int code, String desc) { this.code code; this.desc desc; } }状态流转本身是业务规则不是随便就能跳的。比如从“待受理”只能流转到“已受理”不能直接跳到“已完成”。我会在Service里写一个状态变更方法先校验当前状态是否允许变更到目标状态不允许就直接抛业务异常Transactional(rollbackFor Exception.class) public void acceptRescue(Long rescueId) { RescueRecord record rescueRecordMapper.selectById(rescueId); if (record.getStatus() ! RescueStatus.PENDING.getCode()) { throw new BusinessException(当前状态不可受理); } record.setStatus(RescueStatus.ACCEPTED.getCode()); record.setHandleTime(new Date()); rescueRecordMapper.updateById(record); Pet pet petMapper.selectById(record.getPetId()); pet.setStatus(PetStatus.RESCUING.getCode()); petMapper.updateById(pet); }为什么用Transactional(rollbackFor Exception.class)而不是默认的Transactional因为Spring默认只对RuntimeException回滚如果你抛的是自定义的受检异常或者非运行时异常事务不会回滚。救助工单受理时同时更新“工单状态”和“宠物状态”任何一步失败都必须整体回滚否则会出现“工单已受理但宠物还是待救助”的脏数据。2.4 JWT鉴权与统一接口返回登录和权限是管理端和市民端都必须有的。这里用JWT方案登录成功后服务端把userId和角色等信息加密生成Token设置合理过期时间。我设置的是一天救助站管理员通常白天工作下班后Token过期第二天重新登录安全性和体验比较平衡。密码不能存明文用BCryptPasswordEncoder做哈希就算数据库泄露也没法反推出原始密码。这一点在涉及用户手机号、家庭地址的系统中属于底线要求。统一返回结果结构如下Data public class ResultT { private Integer code; private String msg; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.code 200; result.msg 操作成功; result.data data; return result; } public static T ResultT fail(Integer code, String msg) { ResultT result new Result(); result.code code; result.msg msg; return result; } }这样做的好处是前端Axios拦截器统一判断code等于200不成功就弹出错误信息不用每个接口单独写if判断。JWT校验我放在一个HandlerInterceptor里只拦截/api/admin/**这样的管理端路径市民端的公开接口不拦截。3. 前端Vue3关键模块开发与接口联调实践3.1 用Vite初始化Vue3工程并配置代理创建Vue3工程我建议直接用Vite官方脚手架npm create vuelatest按提示勾选Router、Pinia。然后安装必要依赖npm install axios element-plus sassElement Plus做后台管理界面效率非常高表格、表单、弹窗、消息提示都齐了。选Sass是因为Element Plus的样式定制需要它。vite.config.js里要做两件重要的事配置路径别名、配置开发代理。import { fileURLToPath, URL } from node:url import { defineConfig } from vite export default defineConfig({ resolve: { alias: { : fileURLToPath(new URL(./src, import.meta.url)) } }, server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })开发环境下前端跑在5173端口后端是8080浏览器直接访问后端必然触发跨域。配置代理后前端请求/api/pet/listVite会转发到http://localhost:8080/api/pet/list在开发环境上这比后端开CORS要干净。后端接口统一加/api前缀这样代理规则清晰生产环境也能通过Nginx同样转发。3.2 救助信息列表页的组合式API写法拿“流浪动物列表”页面举例这个页面有筛选条件、列表数据、加载状态、分页。用Vue3的setup语法糖相关逻辑集中在一起script setup import { ref, reactive, onMounted } from vue import { getPetList } from /api/pet const petList ref([]) const loading ref(false) const total ref(0) const query reactive({ type: , status: , city: , pageNum: 1, pageSize: 10 }) async function fetchList() { loading.value true try { const res await getPetList(query) petList.value res.data.list total.value res.data.total } finally { loading.value false } } function handleSearch() { query.pageNum 1 fetchList() } onMounted(fetchList) /script这里说一下ref和reactive的选择。列表数据用ref因为查询回来后是整体替换查询条件用reactive因为里面的字段会频繁独立修改。如果都用ref写法会变成query.value.type 又丑陋又容易忘写.value。这个细节面试中也经常被问到用项目里的实际选择去解释非常加分。加载状态用loading.value配合Element Plus的v-loading指令切换页面时用户体验会好很多。3.3 路由守卫、Axios封装与权限控制管理端页面必须要登录才能访问。在路由配置里给需要鉴权的路由加meta标记{ path: /admin, component: () import(/layout/AdminLayout.vue), meta: { requiresAuth: true, role: ROLE_ADMIN }, children: [ { path: pets, component: () import(/views/admin/PetList.vue) } ] }全局前置守卫router.beforeEach((to, from, next) { const token localStorage.getItem(token) const role localStorage.getItem(role) if (to.meta.requiresAuth !token) { next({ path: /login, query: { redirect: to.fullPath } }) return } if (to.meta.role to.meta.role ! role) { next(/) return } next() })注意一个原则前端守卫只是体验优化后端才是安全底线。哪怕前端路由没拦住后端的JWT拦截器和角色校验一样会拦住越权请求。我见过有人只做前端隐藏按钮后端接口直接裸奔接口一暴露就能随意删除数据。这个坑一定要回避。Axios封装我放在src/utils/request.js里import axios from axios import { ElMessage } from element-plus import router from /router const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) request.interceptors.response.use( response { const res response.data if (res.code ! 200) { ElMessage.error(res.msg) return Promise.reject(new Error(res.msg)) } return res }, error { if (error.response error.response.status 401) { localStorage.removeItem(token) router.push(/login) } ElMessage.error(网络异常请稍后重试) return Promise.reject(error) } )请求拦截器里统一加Token响应拦截器里统一解包401时自动跳登录页。业务代码里不用关心鉴权和错误弹窗只需要关心数据本身。3.4 前后端联调细节与文件上传处理图片上传是这个项目避免不了的功能市民发现流浪猫狗要上传照片管理员也要上传救助现场照片。开发阶段我把图片存到本地上传目录然后通过后端配置一个静态资源映射暴露出来Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceHandler(file: uploadPath); } }前端上传接口用FormData注意不要手动设置Content-Type头让浏览器自动带multipart/form-data; boundary...手动指定会丢失boundary导致后端解析失败。返回给前端的是图片访问URL存到数据库里。生产环境的图片存储我建议升级到对象存储服务把上传和访问都交给专业服务还自带CDN加速。本地文件存储适合开发和内网部署上线公网后图片多起来会很吃力。4. 数据库表设计、MySQL安装配置与性能优化4.1 核心建表语句与字段设计要点宠物表是系统最核心的表之一完整建表语句如下CREATE TABLE pet ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) DEFAULT COMMENT 动物名字, type TINYINT NOT NULL COMMENT 1-猫 2-狗, breed VARCHAR(50) DEFAULT COMMENT 品种, gender TINYINT DEFAULT 0 COMMENT 0-未知 1-公 2-母, age INT DEFAULT 0 COMMENT 估算年龄, status TINYINT NOT NULL DEFAULT 0 COMMENT 0-待救助 1-救助中 2-可领养 3-已领养 4-已放归, photo VARCHAR(255) DEFAULT COMMENT 照片URL, city VARCHAR(50) DEFAULT COMMENT 所在城市, address VARCHAR(255) DEFAULT COMMENT 发现地址, description VARCHAR(1000) DEFAULT COMMENT 描述, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, is_deleted TINYINT DEFAULT 0 COMMENT 逻辑删除标识, INDEX idx_type_status_city (type, status, city) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT流浪动物档案表;几个设计心得字符集必须用utf8mb4而不是utf8。MySQL的utf8其实最多存3字节遇到生僻字或emoji会直接报错。流浪动物救助场景里昵称、留言内容很可能出现emoji用utf8mb4一次性避免。用逻辑删除is_deleted不用物理删除。救助站的每一只动物都是一个完整的救助痕迹运营时要按月份统计数据一旦删除就找不回了。create_time和update_time用数据库自动维护Java代码里不用手动set时间避免不同服务器时间不一致的问题。引擎用InnoDB而不是MyISAM支持事务和外键约束这个归属是原则性的。救助记录表里我特别设计了reporter_id提交人、handler_id接单人、report_time、handle_time、status、result把一次救助从发现到完成的关键节点全部记录下来。这样统计“平均受理时长”时可以直接用handle_time - report_time不需要额外日志表。4.2 组合索引设计与分页查询宠物列表页的核心查询模式是按type、status、city组合筛选最新发布的排前面。我给这张表建了组合索引(type, status, city)。组合索引字段顺序有讲究。type和status是等值查询city是模糊查询区分度上type相对稳定、status变化较多。等值查询的字段放前面模糊查询放最后符合索引最左前缀原则。如果用户只按status查这个索引也能使用到status那一列前面的部分如果把city放最前用户只按type查时索引就完全用不上。救助记录的查询通常是“查看我提交的工单”或“管理员查看待处理工单”所以在rescue_record表上建了idx_reporter(reporter_id, status)和idx_handler(handler_id, status)两个索引。有人会问数据量这么小有必要建索引吗我的看法是建立索引的主要目的不是解决当前性能问题而是模拟一个真实系统的设计习惯。这个项目如果将来接入本地多个救助站数据膨胀速度远超想象到时候再回头补索引代价比现在大得多。4.3 MySQL 8.x安装与连接常见坑这一节记载的是我实际踩过、以及热搜里高频出现的问题几乎每个用MySQL 8.x的人都逃不掉。第一个是驱动类选择。MySQL 8.x必须用com.mysql.cj.jdbc.Driver旧版5.x用的com.mysql.jdbc.Driver已经废弃。如果配错启动时会报ClassNotFoundException或者驱动类不存在第一反应就是检查这里。第二个是认证插件问题。MySQL 8.x默认认证插件是caching_sha2_password如果你用的连接工具是旧版本会直接报Authentication plugin caching_sha2_password cannot be loaded。解决办法有两个升级连接工具或者创建用户时指定老插件CREATE USER rescuelocalhost IDENTIFIED WITH mysql_native_password BY your_password; GRANT ALL PRIVILEGES ON rescue.* TO rescuelocalhost; FLUSH PRIVILEGES;我建议用独立业务账号而不是root连库。原因很简单业务代码里写死root一旦泄露攻击者直接拿到最高权限。给项目单独建一个rescue账号只授权rescue.*库权限边界清晰。第三个是时区和SSL报错。启动时报The server time zone value Öйú±ê׼ʱ¼ä is unrecognized是因为驱动和数据库时区不一致URL里加serverTimezoneAsia/Shanghai即可。报SSL connection error开发环境URL里加useSSLfalse生产环境建议开启SSL或走内网连接。还有一个很容易忽视的参数是allowPublicKeyRetrievaltrue某些JDBC版本连接caching_sha2_password时如果没有这个参数会报错。端口被占用也是高频问题。Windows上执行netstat -ano | findstr 3306看PID后再去任务管理器确认是不是MySQL服务如果被其他程序占用了改my.ini里的端口号。4.4 Jackson时间格式与数据统一后端返回给前端的时间格式如果不统一前端展示时会非常痛苦。有的接口返回时间戳有的返回2024-01-01 12:00:00前端要各写一套格式化逻辑。我在application.yml里统一配置了Jacksonspring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai这样LocalDateTime和Date都会自动按约定格式序列化。这也是团队协作的一部分——接口文档里约定好格式前后端各做各的不用反复沟通。5. 联调过程常见问题与排查技巧实录5.1 跨域请求、代理失效怎么排查前后端联调最烦躁的就是跨域。如果你配了Vite代理但还是报CORS我建议按下面的顺序排查先打开浏览器F12看Network确认请求URL到底是http://localhost:5173/api/pet/list还是http://localhost:8080/api/pet/list。如果是后者说明前端代码里请求地址写死了localhost:8080根本没走代理。正确的做法是API请求统一用相对路径/api/...让代理接管。如果是前者但响应里还是报跨域检查后端是否又额外配置了CORS而和代理产生冲突——走了同源代理后其实不需要后端CORS两套配置同时存在反而会出现“allowOrigin不能为*”之类的奇怪错误。生产环境部署时前端构建后的静态文件放到NginxNginx把/api反代到后端服务。这样根本不用开CORS浏览器只认Nginx的同源地址Nginx配一次就行location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }5.2 MyBatis报错排查速查这几个错是搜索引擎里最高频的也是新手最容易碰到的Invalid bound statement (not found): Mapper接口找到了但对应的XML没有正确加载。查两件事application.yml里mapper-locations: classpath:mapper/*.xml是否配置XML文件里的namespace是否写成了对应的接口全限定名。这个错误我栽过好几次每次都是namespace少写了一个包名。A query was run and no Result Maps were found: select语句没有指定resultType或resultMap。单表查询用resultType指向实体类多表关联返回VO时创建一个VO类指定resultType即可。Parameter xxx not found: Mapper接口方法有多个参数时没加Param注解。MyBatis这时不知道参数名映射解决办法是每个参数都加Param(xxx)。还有一种情况是查询返回的字段全是null。先看数据库列名和Java属性名的映射如果开启了map-underscore-to-camel-casecreate_time会自动映射到createTime如果没开启要么手动开要么在XML里写别名。顺便说一句不要过度依赖resultMap字段少时别名反而更直观。5.3 SpringBoot启动失败与数据库连接异常SpringBoot启动失败的第一件事是看启动日志里APPLICATION FAILED TO START后面那段具体描述而不是整个日志扫雷。常见的几类端口被占用会提示Port 8080 was already in use释放端口或者改配置即可。数据库连不上会报Connection refused或者Access denied for user。前者检查MySQL服务是否启动Linux上执行systemctl status mysqldWindows上查看服务列表后者检查账号密码和授权。还有一种隐蔽情况数据库启动成功但指定的rescue库不存在MySQL连接成功后在执行第一条SQL时报Table doesnt exist。在建库之前先执行CREATE DATABASE rescue DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;依赖版本冲突会导致各种莫名其妙的编译或启动错误。Maven项目统一在pom.xml里用dependencyManagement管理版本不要一个地方写死2.7.2、另一个地方写2.5.0。5.4 前后端路径不一致与接口文档联调404或405大多是前后端路径或请求方式对不上。比如后端用的是PostMapping(/apply)前端却用axios.get(/apply)结果就是405。最好的解决办法是项目里集成Swagger/Knife4jController写好后自动生成接口文档前端按文档调试路径、参数、返回结构一目了然。但我还要提醒一句Swagger只是辅助核心是前后端的契约思维。每个接口的定义包括URL、请求方式、请求参数、返回数据结构。联调前先对一遍四个要素而不是调试时才发现对不上。6. 项目后续扩展与个人实操体会6.1 可以继续扩展的方向这个系统的完整版如果只做CRUD那它就是一个普通的后台管理系统。想让它更有价值有几个方向是可以继续做的地图定位集成市民提交救助信息时标记发现地点坐标管理员在后台看到分布地图出勤路线规划能直观很多。消息通知救助站人员不是实时盯着工单列表的把“新工单待受理”通过短信或公众号模板消息推送给管理员响应速度会大幅提升。对于城市流浪动物救助站来说响应速度直接关系到动物的安全。移动端适配市民使用场景集中在手机上可以让现有Vue3项目适配移动端或者单独开发一个基于H5的轻量入口。微信公众号嵌入是成本较低的方案不需要上架流程。数据可视化基于救助记录表的时间维度统计各月救助量、领养成功率和志愿者活跃度做一个管理看板能让救助站运营方看清自己的实际效能。6.2 我做完这个项目最想提醒你的事如果只让我说一条经验那就是开发这类系统时业务规则的权重远大于技术实现。技术选型、代码结构都有章可循真正花时间的是想清楚“待受理”和“已受理”之间有哪些合法转换、领养申请被驳回后能不能再次提交、救助站人员之间怎么交接工单。这些规则一旦想清楚代码就是水到渠成的事。我一开始想当然地让“救助完成”状态直接关联“宠物已放归”结果救助站实际的工作流是救助完成后动物要送医观察观察期结束才算真正完成。这个差异导致我返工了救助记录表和宠物状态枚举。所以我的建议是动手写代码前先找真实的业务人员或者运营方聊一次哪怕只对话半小时也比自己在屋里猜三天强。还有一点实际建议项目的包结构和命名规范从第一天就要严格。Mapper、Service、Controller的分层不要因为“先跑通”而破坏后端接口统一/api前缀前端请求统一走封装后的Axios。这些看上去“费事”的约束在整个系统超过20个接口后会给你省下大量排查时间。
返回列表