ARTICLE DETAIL

资讯详情

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

SpringBoot+Vue实战:中小社区疫情信息管理系统从零到部署

SpringBoot+Vue实战:中小社区疫情信息管理系统从零到部署 做毕业设计或者练全栈项目的时候这套基于SpringBootVue的中小社区疫情信息管理系统绝对算得上一个经典选题。我最早接触这个项目是帮人调一个跑不起来的毕设后来陆陆续续发现每年都有大量学生选这个题目网上的源码质量也参差不齐要么版本太老要么缺模块缺得厉害。今天我就把这套系统的完整实现拆开来讲从需求分析、数据库设计到后端SpringBoot接口开发、前端Vue页面搭建再到最后的部署和常见坑位一篇讲透。适合正在写毕业设计的、想系统走一遍SpringBootVue整合流程的以及想找一个真实业务场景练手的初级开发参考。1. 项目整体设计与需求拆解1.1 明确系统边界管三类数据分两个角色先别急着写代码把需求边界说清楚。中小社区的信息管理规模也就是几百户到几千户和大型政务平台完全不是一个量级。大型平台要讲高并发、分布式、消息队列这套系统的应用场景其实就是社区管理员和居民之间的一条信息通道。所以设计的第一原则不是高可用而是够用、简单、跑得起来。核心要管的数据有三类居民基础信息、居民每日健康上报信息、异常事件处理信息。围绕这三类数据系统分成两个端。居民端负责填报个人信息、提交每日上报、查看公告和查看自己的记录管理员端负责维护居民档案、处理异常登记、发布公告、查看统计报表。如果用最简单的方案两个端就在同一个系统里用角色字段区分权限不需要搭两个独立的前端项目。功能规模控制在这个范围正好覆盖了一个完整全栈项目该有的所有要点登录认证、增删改查、状态流转、统计图表、权限控制。比单纯的学生管理系统多一层业务逻辑又比电商类项目少很多复杂事务非常适合作为学习和答辩项目。1.2 技术选型背后的真实考量这个组合被培训机构当作标准答案不是没道理的每一层都有明确分工。SpringBoot负责把后端工程跑起来内置Tomcat约定大于配置写接口的效率比传统SSM高很多。Vue负责前端页面和数据绑定的交互组件化写法适合社区这种页面不少但结构相似的项目。MyBatis管数据库访问动态SQL在写统计类查询的时候是真的方便。MySQL则负责把所有数据落盘开源、免费、资料多遇到问题一搜就有答案。选这套方案还有一个很现实的原因就业市场和教学体系里JavaSpringBoot仍然是国内后端开发的主流路径之一。用这套技术栈做出来的项目后续往微服务、Spring Cloud方向扩展也顺理成章。相比之下如果选个冷门框架写起来可能更爽但答辩时和面试时能聊的共同语言就少很多。有两点需要提醒。第一SpringBoot版本不要无脑追新。2.7.x这个版本非常成熟MyBatis starter、各种第三方库的兼容性都已经验证过了。如果直接上3.xjavax命名空间变成了jakarta很多老教程的代码会直接编译报错对毕设来说完全没有必要冒这个风险。第二前端框架Vue 2和Vue 3差异不小如果拿到的是Vue 2的老源码Element UI对应的版本是2.x新做项目建议直接用Vue 3加Element Plus生态已经非常成熟了。1.3 功能模块与权限划分速览把功能矩阵列出来一眼就知道自己还缺什么功能模块居民端管理员端登录认证登录、修改密码登录、修改密码居民档案填写、查看本人信息新增、编辑、删除、检索全体居民每日上报提交今日健康信息、查看历史查看上报列表、按日期统计异常记录查看本人异常记录登记异常、处理、标注状态数据统计查看个人上报曲线查看社区整体统计图表公告管理查看公告列表、详情发布、编辑、删除公告这张表就是整个开发阶段的验收清单。我在实际做的时候先把这张表贴在电脑旁边每完成一个格子就划掉避免边写边发散功能导致拖期。2. 数据库设计先想清楚表关系再动手写接口2.1 按业务拆解核心表数据库设计是这类项目最容易翻车的地方。很多同学上来就建一张大宽表把所有字段塞进去结果后期写统计查询的时候各种别扭。我建议按业务拆成6张表用户表user、社区表community、居民信息表resident_info、每日上报表daily_report、异常记录表abnormal_info、公告表notice。关联关系也很直接一个社区有多个用户一个用户有一条居民信息一个用户有多条每日上报记录和多条异常记录。全部是一对多关系不需要中间表逻辑清晰写SQL联查的时候也简单。社区表可以做得非常简单甚至只有id和name但保留这张表是有价值的后续扩展多社区管理的时候不用改表结构而且统计报表按社区分组也有据可依。2.2 核心表结构与建表SQL参考直接给出一版可以直接用的建表脚本字段命名统一用下划线风格配合MyBatis的驼峰映射配置能省掉大量手工映射。CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(64) NOT NULL COMMENT 密码(MD5加密后), real_name VARCHAR(50) NOT NULL COMMENT 真实姓名, role TINYINT NOT NULL DEFAULT 0 COMMENT 角色:0-居民 1-管理员, phone VARCHAR(20) COMMENT 手机号, id_card VARCHAR(18) COMMENT 身份证号, community_id INT COMMENT 所属社区, status TINYINT NOT NULL DEFAULT 1 COMMENT 状态:1-正常 0-禁用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; CREATE TABLE resident_info ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL COMMENT 关联用户ID, gender TINYINT COMMENT 性别:0-女 1-男, age INT COMMENT 年龄, building VARCHAR(20) COMMENT 楼栋号, unit VARCHAR(20) COMMENT 单元号, room VARCHAR(20) COMMENT 门牌号, vaccine_status TINYINT DEFAULT 0 COMMENT 疫苗状态:0-未接种 1-已接种, health_status TINYINT DEFAULT 0 COMMENT 健康状态:0-正常 1-异常, remark VARCHAR(255) COMMENT 备注, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT居民信息表; CREATE TABLE daily_report ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, report_date DATE NOT NULL COMMENT 上报日期, temperature DECIMAL(4,1) COMMENT 体温, health_status TINYINT DEFAULT 0 COMMENT 健康状态, is_contact TINYINT DEFAULT 0 COMMENT 是否接触异常人员, is_abnormal TINYINT DEFAULT 0 COMMENT 是否本人异常, location VARCHAR(100) COMMENT 当前所在位置, remark VARCHAR(255), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_user_date (user_id, report_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT每日上报表;这版设计里有几个细节是实际踩过坑之后才体会到的。daily_report表加了一个联合唯一索引uk_user_date目的就是保证一个人一天只能上报一次这个约束在数据库层面兜底比在代码里先查询再判断要可靠得多。在并发场景下先查再插会出现竞态条件而唯一索引会让第二次插入直接报错配合代码捕获异常转成友好提示逻辑无懈可击。2.3 三个容易被忽略的设计细节第一个是日期字段的类型选择。日期用DATE带时分秒的用DATETIME不要图省事全用VARCHAR存。只有用真正的日期类型SQL里按日期分组统计的时候才能直接GROUP BY否则各种格式转换能让你怀疑人生。第二个是状态字段用整数枚举。用户角色、健康状态、记录状态全部用TINYINT对应含义写在注释里。这样数据库体积小查询快Java代码里定义好常量或者枚举类语义同样清晰。千万别直接存中文排序、比较、统计都麻烦。第三个是删除策略。用户、上报记录这类业务数据建议加status字段做逻辑删除不要真DELETE。一来是保留历史数据方便回溯二来是外键关联的记录不会被delete卡住。只有公告这类非核心数据物理删除问题也不大看具体情况取舍。3. 后端SpringBoot实现从工程结构到业务接口3.1 工程分层与基础配置后端工程建议按标准三层结构分包controller、service、mapper、entity、common。controller只做参数接收和结果返回service负责业务逻辑mapper对应MyBatis的数据库操作entity是实体类common放统一返回结果、工具类和异常处理。pom.xml的核心依赖有这样几个spring-boot-starter-web、mybatis-spring-boot-starter、mysql-connector-java、lombok、jjwt做Token用。我用的版本组合是SpringBoot 2.7.x加MyBatis Starter 2.3.x运行非常稳定。application.yml里有一个关键配置map-underscore-to-camel-case一定要开这样数据库字段user_id才能自动映射到Java属性userId否则你每个结果集都要手写ResultMap工作量翻倍。server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/community_health?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.demo.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl注意driver-class-name这一行。MySQL 5.x用的是com.mysql.jdbc.DriverMySQL 8.x用的是com.mysql.cj.jdbc.Driver这两个类弄混了启动必报错。如果你本机装的是MySQL 8.0就用我上面这个写法。另外url里的serverTimezoneAsia/Shanghai也不能少不然会遇到时区相关报错。3.2 登录认证用Token而不是Session这类管理系统用Session方案也能跑但前后端分离项目用Token更合适。流程是用户登录成功后后端用JWT生成一个包含用户id和角色的Token返回给前端前端存在localStorage里每次请求在Header里带上后端用一个拦截器统一校验。拦截器的核心逻辑是这样Component public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录接口 if (request.getRequestURI().contains(/auth/login)) { return true; } String token request.getHeader(Authorization); if (token null || !JwtUtil.verify(token)) { response.setStatus(401); return false; } // 解析出userId存入request域后续接口直接用 Integer userId JwtUtil.getUserId(token); request.setAttribute(userId, userId); return true; } }这里有个小技巧把解析出的userId放到request的attribute里controller在业务处理时用RequestAttribute(userId)直接取就不需要每个接口都重新解析一次Token了。密码必须要加密存储。我见过太多毕设源码直接明文存密码虽然答辩演示看不出来但这是重大安全隐患。自己实现一个MD5加盐工具类或者用Spring Security的BCrypt都比明文强得多。3.3 核心业务接口实现接口设计遵循REST风格但也不必过度纠结保证语义清晰就行。核心接口清单如下POST /api/auth/login登录GET /api/user/info获取当前用户信息POST /api/resident/save保存居民信息POST /api/report/submit提交每日上报GET /api/report/stats获取统计数据POST /api/abnormal/save登记异常GET /api/abnormal/list查询异常列表POST /api/notice/save发布公告GET /api/notice/list公告列表。统一返回结构是必做的一步。我习惯用Result类包一层code表示状态码msg表示提示信息data放业务数据。这样前端Axios处理响应时逻辑统一错误提示也好做。Data public class ResultT { private Integer code; private String msg; private T data; public static T ResultT success(T data) { ResultT r new Result(); r.setCode(200); r.setMsg(操作成功); r.setData(data); return r; } public static T ResultT error(String msg) { ResultT r new Result(); r.setCode(500); r.setMsg(msg); return r; } }每日上报接口是最典型的业务逻辑体现。前端传过来userId、体温、健康状态、是否接触异常人员等信息后端先判断今天是否已经上报过如果上报过就返回今日已上报请勿重复提交。在数据库有唯一索引的兜底下即便并发请求漏过了判断第二次插入也会被数据库挡住。3.4 MyBatis动态SQL实战统计查询是重头戏日常增删改查就是固定SQL没什么好说。真正体现MyBatis价值的是统计类查询。比如管理员要按日期看整个社区的提交数量走势用动态SQL加日期格式化就能写得很干净select idcountReportByDate resultTypejava.util.Map SELECT DATE_FORMAT(report_date, %Y-%m-%d) AS reportDate, COUNT(*) AS count FROM daily_report r JOIN user u ON r.user_id u.id WHERE u.community_id #{communityId} if teststartDate ! null AND r.report_date gt; #{startDate} /if if testendDate ! null AND r.report_date lt; #{endDate} /if GROUP BY DATE_FORMAT(report_date, %Y-%m-%d) ORDER BY report_date /select动态SQL里的 标签用得最多尤其是列表查询配合多条件筛选场景。管理员查居民列表可能按楼栋筛选、按状态筛选、按姓名模糊搜索这些条件都非必填用 拼SQL再合适不过。注意XML里小于号要用转义这个坑能把报错埋在运行时排查很费劲。再补一点MyBatis的缓存机制。一级缓存默认开启作用范围是同一个SqlSession日常开发基本无感知。二级缓存默认不开启需要时在Mapper XML里配置 。这个项目不建议开二级缓存因为数据更新频繁缓存失效问题会让看到的数据不对对毕设来说是负优化。4. 前端Vue实现页面、路由与接口对接4.1 前端工程化准备前端我用Vue 3加Element Plus加Vite这套组合比Vue CLI的Webpack方案启动快很多依赖安装也更省心。如果电脑上还没有Node环境去官网下载LTS版本安装完在终端执行node -v确认版本顺手把npm源换成国内镜像不然npm install能卡到你怀疑人生。创建一个新项目npm create vitelatest community-web -- --template vue cd community-web npm install npm install element-plus element-plus/icons-vue axios vue-router4 echartsElement Plus按需引入还是全量引入这个规模的项目直接全量引入省事体积大一点无所谓。在main.js里注册import { createApp } from vue import App from ./App.vue import ElementPlus from element-plus import element-plus/dist/index.css import router from ./router const app createApp(App) app.use(ElementPlus) app.use(router) app.mount(#app)4.2 路由守卫与Axios请求封装前端路由要配合登录状态做控制不然用户手动改URL就能进管理页拦截器形同虚设。Vue Router的全局前置守卫是标准写法router.beforeEach((to, from, next) { const token localStorage.getItem(token) // 白名单页面不校验 if (to.path /login) { next() } else if (!token) { next(/login) } else { next() } })路由跳转的时候顺便做一下角色判断管理员专属的路由如果当前用户的role不是1就强制跳回首页。这样前端后端两道防线都有。Axios封装也是必做项。统一baseURL、超时时间、请求拦截器附带Token、响应拦截器统一处理错误码。特别是401状态码一旦捕获就清除本地Token并跳回登录页import axios from axios const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] token } return config }) request.interceptors.response.use( response response.data, error { if (error.response error.response.status 401) { localStorage.removeItem(token) window.location.href /login } return Promise.reject(error) } ) export default requestbaseURL用/api还有个好处后端统一Controller的RequestMapping都带/api前缀前后端联调的时候路径语义清晰部署到同一个容器下也不会出现路径冲突。4.3 核心页面实现与统计图表居民档案页和信息填报页其实就是表单页Element Plus的el-form组件一套下来很快。注意要验证身份证号格式和手机号格式el-form的rules规则了解一下就能用。每日上报页面核心是几个单选按钮、一个体温数字输入框和一个提交按钮提交成功后清空表单并提示明日再来。统计页面用ECharts这是整个项目视觉上最出彩的部分。管理员端展示两类图表折线图看每日上报人数趋势柱状图看各楼栋上报情况。ECharts在Vue里使用的常规方式是从echarts包引入核心模块在onMounted生命周期里初始化实例然后用自适应resize监听窗口变化。数据来源就是后端写的统计接口前端拿Map数据组装成ECharts要的option格式。这里有个很实际的经验ECharts图表初始化一定要在DOM渲染完成之后执行否则容器宽度是0图表显示不出来。在Vue 3组合式API里就是onMounted里放要初始化逻辑千万别在setup同步代码里直接调。5. 部署实操与高频故障排查5.1 本地开发环境清单复现这个项目需要准备的本地环境按顺序列出来JDK 1.8或11千万别装太新的版本有些老依赖会不兼容、Maven 3.6以上用来拉取后端依赖并打包、Node 16以上用来跑前端、MySQL 5.7或8.0数据库、Navicat或命令行工具导入SQL脚本、IDEA开发IDE。MySQL安装这一步建议直接装8.0版本安装过程中记好端口号3306和root密码。装完后用root账号执行建库命令和导入脚本。很多毕设源码会附一个.sql文件直接在Navicat里运行即可如果没有SQL文件照着前面我给的表结构自己建一遍顺便理解字段含义。5.2 前端打包整合进SpringBoot部署有两种方案。方案一后端SpringBoot单独跑8080端口前端Vue用vite dev跑5173端口本地开发时通过Vite的proxy配置把/api请求代理到8080。方案二正式部署时把前端打包产物放进SpringBoot的静态资源目录这样只有一个服务一台服务器一个端口。方案二的具体操作是前端执行npm run build生成dist目录把dist里的所有文件拷贝到后端项目的src/main/resources/static目录下重新打包SpringBoot启动后直接访问http://localhost:8080就能看到整个系统。这本质上就是Vue SPA的history路由模式问题要注意一下静态文件被SpringBoot托管后刷新一个非根路径的页面会出现404因为SpringBoot找不到对应的静态资源。解决办法是配置一个路由回退让非静态资源的请求都转发到index.html。5.3 高频故障清单这个项目前前后后我调过太多遍遇到的坑高度集中整理成一张速查表现象原因解决办法启动报错Driver类找不到MySQL驱动类名写错换成com.mysql.cj.jdbc.Driver连接数据库报时区错误url没配置serverTimezone加上serverTimezoneAsia/Shanghai前端npm install失败网络或镜像问题切换npm源为国内镜像用cnpm或pnpm接口返回中文乱码数据库或连接串字符集不对建库用utf8mb4url加characterEncodingutf8页面刷新出现404SPA路由刷新未回退配置转发到index.html前端请求跨域前后端未代理或未配CORS开发用Vite proxy生产用同端口MyBatis模糊查询失效XML里#{}用法错误模糊查询用CONCAT(%, #{name}, %)ECharts图表空白DOM未渲染完成就初始化把初始化放到onMounted里执行关于跨域再补一句开发阶段前端跑5173端口直接请求后端8080端口必然跨域。最省事的方案是配置Vite的proxy在vite.config.js里把/api开头的请求转发到http://localhost:8080这样浏览器看到的请求是同源的完全绕开CORS。生产环境按前面说的前端打包进后端自然也没有跨域问题。写在最后的几个实在建议整个项目从零到跑起来我估计一个熟悉基本语法的开发者需要三到五天新手拖到两周也正常。过程中最省时间的做法是先把数据库脚本跑通、用Postman把后端接口调通再碰前端页面否则前后端一起出问题根本分不清是谁的错。我自己调试这类系统时最快的排查办法是先看MyBatis控制台打印的SQL绝大多数接口问题都是SQL语句拼出来不符合预期日志一开很快就能定位。这套系统做完之后往上扩展的方向也很多加一个轻量级的消息推送让大家在微信里填上报用Redis缓存热点统计数据提升访问速度或者把SpringBoot拆成微服务加个网关。但这些都是后话先把基础的CRUD和统计功能做得扎实、把每一步的边界条件处理干净这个项目带来的收获就已经足够值回时间了。
返回列表