前阵子折腾了一套中小社区的疫情信息管理系统源码,技术栈是 SpringBoot + Vue + MySQL,后端 Java 接口、前端 Vue 页面、MySQL 做持久化,三件套齐全,拿到手把环境配好就能直接跑起来。这套东西比较适合做毕设、课程设计,或者作为社区信息管理的二次开发底子。文章把这套系统的整体结构、前后端核心模块、跑通步骤和踩坑记录都过一遍,算是给准备动手的同学一份参考。
我分几个部分来写:先讲这套系统的整体架构和设计思路,再分别拆解后端、前端、数据库的核心实现,然后给出从零到一跑起来的完整步骤,最后把实操中容易踩的坑汇总成一张问题排查表。内容会写得比较细,既有代码层面的说明,也有部署层面的经验,适合对着源码一起看。
1. 项目定位与整体架构思路
1.1 这套系统到底解决了什么问题
中小社区的信息管理场景有个特点:数据量不算大,但业务角色杂、状态变化频繁。居民要登记基本信息,工作人员要维护每日健康打卡,还要记录出入情况,特殊人群需要单独跟踪,社区公告也要及时通知到人。如果用 Excel 或纸质表格去管这些,数据分散、版本混乱、统计困难,后期想找某一个人的完整记录,要翻好几个文件。
这个源码项目的定位就是用一套轻量级 Web 系统把这堆事集中管理起来。居民端能自助登记信息、提交每日健康情况;管理端能维护居民档案、审核记录、发布公告、查看统计报表。整体围绕"一人一档、每日一报、进出有记录、异常可跟踪"这个核心思路来设计,信息流闭环清晰,没有刻意做得复杂。
1.2 为什么选 SpringBoot + Vue + MySQL 这套组合
这个技术组合在国内中小型管理系统里非常主流,原因很简单:生态成熟、上手门槛适中、招人容易、部署也轻量。
SpringBoot 负责后端接口,它最大的优势是"约定大于配置",起步依赖帮你把大部分常用组件都封装好了。你不需要像 SSH 时代那样写一堆 XML 配置,几个注解就能把接口暴露出去。MyBatis-Plus 再往上叠一层,连基础的 CRUD 都不用写 XML,直接调用封装好的方法就行。
Vue 负责前端页面,核心是组件化和响应式。同一个页面区块可以抽成组件复用,数据变了界面自动更新,不用自己手动去操作 DOM。配合 Element UI 这套现成的组件库,后台管理类的页面基本是拼积木,表单、表格、弹窗、菜单都有现成的轮子。
MySQL 负责数据存储,胜在稳定可靠、资料丰富。中小社区的数据量撑死几十万条记录,MySQL 完全扛得住,不用上 Redis、MongoDB 这类额外组件增加复杂度。
这三个组合在一起的直接好处是:拿这套源码跑通一次,就能理解一个完整 Web 应用的骨架。前端怎么调接口、后端怎么访问数据库、数据怎么流动,整条链路是闭环的,对新手特别友好。
提示:这套系统属于典型的 CRUD + 权限 + 统计类应用,技术上没有特别炫的点,但胜在结构完整。如果你是想学"如何从零搭一个可用系统",它的参考价值比单纯学某个框架的高很多。
2. 后端工程结构与核心模块实现
2.1 SpringBoot 工程的目录划分
后端代码的包结构决定了你能不能快速找到想改的地方。这套系统的工程目录大致是:
src/main/java/com/community/health ├── controller // 接收前端请求,返回 JSON 数据 ├── service // 业务逻辑层,处理具体的业务规则 ├── mapper // 数据访问层,操作数据库表 ├── entity // 实体类,对应数据库表结构 ├── config // 配置类,注册拦截器、跨域过滤器等 ├── common // 通用工具类、统一返回结果封装 └── HealthApplication.java // SpringBoot 启动入口controller 层只做一件事:接收 HTTP 请求、调用 service 层、返回统一格式的结果。不要把业务逻辑堆在 controller 里,否则后期改一个业务规则要翻遍所有接口。
service 层是核心,所有业务规则都在这里处理。比如提交健康登记时,要先判断当天是否已经提交过,如果重复提交需要给出明确提示,这种判断就必须放在 service 层,不能指望前端去拦截。
entity 层每个类对应一张表,字段和表的列一一对应。这里有个细节很多人刚接触时容易忽略:表的列名如果是下划线命名(比如building_no),实体类字段用驼峰命名(buildingNo),需要在 application.yml 里开启map-underscore-to-camel-case: true,否则查询结果映射不上,字段全是 null。
2.2 核心业务的表结构与接口设计
管理系统最重要的就是数据,表结构设计直接决定了系统的表达能力。这套系统里几张核心表的设计思路值得说一下。
我先用表格梳理一下核心表的作用:
| 表名 | 用途 | 关键字段 |
|---|---|---|
| sys_user | 系统用户(管理员、工作人员) | username、password、role |
| resident_info | 居民基本信息档案 | name、id_card、building_no、unit_no、room_no、phone |
| health_record | 每日健康登记记录 | resident_id、temperature、symptom、travel_history、status |
| access_record | 小区出入登记记录 | resident_id、access_type、access_time、visitor_phone |
| quarantine_info | 隔离人员专项跟踪信息 | resident_id、start_date、end_date、quarantine_address、status |
| nucleic_acid_record | 核酸采样与结果记录 | resident_id、test_date、result、organization |
| notice_info | 社区公告信息 | title、content、publisher_id、publish_time |
resident_info表是核心,其他业务表基本都通过resident_id关联到它。设计上用了建筑编号加房间号的层级结构,通过building_no+unit_no+room_no三个字段精确定位一个居民住在哪里,这样按楼栋筛选、导出统计都非常方便。
health_record里的status字段很典型,用状态码表示当前健康状态:1 代表正常,2 代表异常待关注,0 代表未填报。用数字存状态,比直接存文字更规范,也方便统计聚合。前端展示的时候再通过字典映射成文字标签,这是一种非常常见的状态管理玩法。
接口设计遵循 RESTful 风格,基础路径以/api开头,例如:
POST /api/login // 登录获取 token GET /api/resident/list // 分页查询居民列表 POST /api/resident/save // 新增或修改居民信息 DELETE /api/resident/{id} // 删除居民 GET /api/health/today/statistic // 今日健康打卡统计 POST /api/health/record // 提交健康登记 GET /api/quarantine/list // 隔离人员列表 GET /api/notice/list // 公告列表每个接口返回的数据格式是统一的 JSON 结构,包含状态码、消息和数据三个部分:
{ "code": 200, "message": "操作成功", "data": { "total": 186, "records": [] } }统一返回格式的好处是前端处理逻辑可以简化为:先判断 code 是否为 200,不是就直接弹错误提示,是就取 data 渲染页面。不需要每个接口单独判断不同的返回结构。
2.3 登录认证与权限控制怎么做
管理系统的权限控制是逃不掉的需求。这套源码采用的是 JWT(JSON Web Token)方案,核心思路是:用户登录成功后,后端根据用户信息生成一个带过期时间的 token,前端拿到 token 存在本地,之后每次请求都在 header 里带上,后端用一个拦截器统一校验。
实现上,关键代码大致是这个思路:
@Component public class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录接口 if (request.getRequestURI().contains("/login")) { return true; } String token = request.getHeader("Authorization"); // 校验 token 的有效性、是否过期 if (token == null || !JwtUtil.verify(token)) { response.setStatus(401); return false; } return true; } }拦截器写好后,在 config 类里注册拦截路径就生效:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new JwtInterceptor()) .addPathPatterns("/api/**") .excludePathPatterns("/api/login"); } }角色权限这块分为管理员和普通工作人员两级。管理员拥有全部权限,能管理用户分配;工作人员只能操作居民信息登记和健康记录查询。简单的做法是给用户表加一个role字段,接口处理前判断当前用户的角色,不匹配就直接拒绝。
实操建议:如果项目只做单机部署或者只服务一个社区,权限做成这种轻量级控制就够了,不用引入 Spring Security + OAuth2 那套重型框架,否则反而是负担。
3. 前端页面设计与交互细节
3.1 Vue 工程结构与路由配置
前端工程的标准结构大致是这样:
src ├── api // 封装 axios 请求,统一管理接口地址 ├── assets // 静态资源,图片、全局样式 ├── components // 通用组件,比如分页、弹窗 ├── router // 路由配置 ├── store // 全局状态管理(Vuex) ├── views // 页面视图,按模块划分 ├── App.vue // 根组件 └── main.js // 入口文件路由是前端跳转的核心。Vue Router 的路由配置比较常规,但也需要理解两个概念:静态路由和动态路由。
静态路由指不依赖用户角色、登录后就能确定的页面,比如登录页、首页。动态路由则根据用户权限动态挂载,管理员登录后能看到用户管理菜单,普通工作人员登录后看不到。实现方式是登录后根据角色生成不同的路由表,用router.addRoutes动态追加,这个设计贴合不同角色看到不同功能菜单的真实需求。
前端路由配置代码大致长这样:
const routes = [ { path: '/login', component: () => import('@/views/Login.vue') }, { path: '/dashboard', component: () => import('@/layout/Layout.vue'), redirect: '/dashboard/home', children: [ { path: 'home', component: () => import('@/views/Dashboard.vue'), meta: { title: '数据看板' } }, { path: 'resident', component: () => import('@/views/ResidentList.vue'), meta: { title: '居民管理' } } ] } ]这里用到了路由懒加载,() => import(...)这种方式让页面在访问时才加载对应的 JS 文件,而不是打包时一次性全部加载。项目小的时候体感不明显,但页面多了之后,首屏加载速度差距很大,属于值得保持的好习惯。
3.2 核心页面的实现思路
居民信息管理页面是这套系统最典型的 CRUD 页面,包含搜索表单、数据表格、新增/编辑弹窗三个部分。
搜索表单通过绑定响应式数据、点击查询按钮重新请求列表接口实现。分页是后台管理页面的标配,数据表格加上pagination组件,切换页码时触发接口重新查询,把pageNum和pageSize传给后端。
新增和编辑用同一个弹窗组件,通过判断是否传入初始数据来决定是新增还是编辑。表单校验用 Element UI 自带的rules规则,身份证号格式、手机号格式、必填项校验都在这层做。
健康登记页面稍微特殊一点,需要处理"当天重复提交"的逻辑。前端的做法是进入页面时先调用一个查询接口,看今天是否已登记,如果已登记就把表单禁用,展示已经提交的数据;没登记就开放表单填写。
数据看板页面是这类管理系统的亮点所在。它用 ECharts 渲染图表,展示几个关键统计:今日打卡人数、异常记录趋势、各楼栋登记人数占比、近七天的登记情况。这个页面虽然难度不大,但视觉冲击最强,也是评审或展示时最容易被关注的模块。
3.3 前后端联调与接口对接
前后端分离开发时,联调是最容易出问题的环节。这套源码的做法是在vue.config.js里配置代理,把前端请求转发到后端服务,避免开发环境跨域:
module.exports = { devServer: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } }开发时的域名是http://localhost:8081,前端请求路径写/api/resident/list,代理会把请求转发到http://localhost:8080/api/resident/list。这样浏览器里不产生跨域请求,前端不用处理跨域,后端也不用费劲去配置 CORS,联调体验非常顺滑。
前端调用接口统一封装在api目录下,类似这样:
import request from '@/utils/request' export function getResidentList(params) { return request({ url: '/api/resident/list', method: 'get', params }) }request模块里统一配置了 axios 实例、请求拦截器(自动附加 token)和响应拦截器(统一处理错误码)。这种统一封装的好处是后续切接口地址、换后端地址,只需要改一个地方。
4. 数据库设计要点与初始化
4.1 建库建表与初始化数据
数据库是整套系统的地基。拿到源码后,建议先认真过一遍 SQL 脚本,而不是直接执行完就完事。脚本里包含了建库语句、建表语句和初始数据,包括默认的管理员账号。
以 MySQL 5.7 或 8.0 为例,核心操作是:
CREATE DATABASE IF NOT EXISTS community_health DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE community_health;建表时几个关键的细节值得注意:
一是字符集一定要用utf8mb4,不要用utf8。utf8在 MySQL 里最多存 3 个字节,一些特殊字符无法存储,utf8mb4才是完整的 UTF-8 支持。居民信息里有各种生僻字或者特殊符号,用了utf8mb4就不会出现写入乱码或报错。
二是主键用自增BIGINT就好,不要在这里过度设计。中小社区场景没有分布式的需求,自增主键简单可靠,还方便关联查询。
身份证号这类编号字段要注意用VARCHAR而不是BIGINT。身份证号通常以文本形式存储,因为可能有前导特征的号码段,用数字类型会丢失信息,而且身份证号长度超过 BIGINT 的可读极限,容易造成精度丢失。
4.2 核心业务表的设计细节
resident_info表设计上通过几个字段实现精细定位:building_no、unit_no、room_no。这样的层级字段设计,配合 MySQL 的索引,按楼栋统计或筛选时效率很高,SQL 写起来也简洁:
SELECT building_no, COUNT(*) FROM resident_info GROUP BY building_no;health_record表为了应对高频登记,在resident_id和create_date上加了联合索引,确保"查某个人在某个日期的登记记录"这个高频查询走索引,不扫全表。这类设计属于细节经验,数据量小的时候无所谓,量大了就是性能和体验的差别。
nucleic_acid_record表记录每次检测的机构、时间、结果。设计时要考虑记录追加的特性,一次采样对应一条记录,居民多次采样就会有同一个人多条记录。查询时用resident_id关联查最近几次的检测结果,排序用test_date DESC。
提示:SQL 脚本里通常还会附带一些测试数据,建议保留这些数据。不要因为觉得是"脏数据"就全部删掉,跑通前后端联调时测试数据能帮助你及时发现接口字段对不上的问题。
5. 环境搭建与部署运行全流程
5.1 后端环境准备与启动
先把后端跑起来,这是整套系统运行的前置条件。
本机需要准备的环境是:JDK 8(如果源码版本比较新,可能需要 JDK 11 或 17,以 pom.xml 里的配置为准)、Maven 3.6+、IDEA 开发工具、MySQL 5.7 或 8.0。
第一步是导入数据库脚本,用 Navicat 或命令行执行即可:
mysql -uroot -p < community_health.sql第二步是修改后端配置。打开application.yml,重点看数据源配置这一块:
server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/community_health?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true username: root password: 123456这里我特别标注两个参数:serverTimezone=Asia/Shanghai是为了避免数据库连接时区和本机时区不一致导致的日期时间偏差;useSSL=false是因为本地开发通常不需要走 SSL 加密,MySQL 更高版本如果服务端开启了强制 SSL,连接时可能报SSL connection error,这个参数能直接避免这类报错。
第三步是启动后端。IDEA 里直接运行主类HealthApplication.java,或者在项目根目录执行:
mvn clean package java -jar target/community-health.jar启动成功后,打开浏览器访问http://localhost:8080/api/login,用 GET 请求试一下,如果返回 405 表示服务正常,因为接口已经收到请求但方法不对;如果返回 404 就要检查路径和启动是否报错。
5.2 前端环境准备与启动
前端的依赖工具是 Node.js 和 npm。安装好 Node.js 后,建议在项目根目录执行:
npm install这一步会安装 package.json 里声明的所有依赖。网络状况不好时可能失败,这时可以换成国内镜像源:
npm config set registry https://registry.npmmirror.com npm install依赖安装完成后启动开发服务器:
npm run serve启动成功后终端会打印本地访问地址,一般是http://localhost:8081。用配置好的管理员账号登录,就能看到整套系统的完整页面。
5.3 打包部署到服务器
开发环境跑通之后,如果要部署到服务器,前后的处理方式不同。
前端执行打包:
npm run build打包产物会生成在dist目录,里面是纯静态文件。
后端同样是打包成可执行 jar:
mvn clean package部署方式这里推荐两种,按项目体量选择:
第一种是简单粗暴,前端打包后的dist目录下的文件,直接复制到后端工程的src/main/resources/static目录下,跟着后端打包进 jar,一个 SpringBoot 服务同时提供接口和页面。这种方式适合内部试用、单机部署,一台服务器一个 Java 进程搞定一切,非常省事。
第二种是正式一点的前后端分离部署。后端 jar 跑在 8080 端口,前端静态文件放在 Nginx 的 html 目录下,Nginx 监听 80 端口,把/api路径的请求反向代理到后端的 8080 服务。这种方式更利于后期扩展,静态资源由 Nginx 高性能处理,后端只专注于接口服务。
服务器上跑 jar 包,推荐用后台方式启动并记录日志:
nohup java -jar community-health.jar > app.log 2>&1 &查看日志用tail -f app.log,很多启动问题都能在日志里找到根源。
6. 常见问题排查与避坑实录
6.1 数据库连接与数据问题
| 现象 | 原因 | 解决方案 |
|---|---|---|
启动报Access denied for user 'root'@'localhost' | 用户名或密码错误 | 检查 application.yml 中的 username 和 password,注意密码不要带多余空格 |
启动报Unknown database 'community_health' | 数据库没有创建或名字不一致 | 执行建库语句,确认库名与配置完全一致 |
启动报Public Key Retrieval is not allowed | MySQL 8.0 的 caching_sha2_password 认证机制 | JDBC URL 增加allowPublicKeyRetrieval=true |
| 页面中文字乱码 | 数据库字符集或连接字符集配置不当 | 确认建库使用 utf8mb4,URL 带characterEncoding=utf8 |
| 查询接口报 SQL 语法错误 | 表字段名与实体类字段不一致 | 开启驼峰映射配置map-underscore-to-camel-case: true |
数据库连接相关的报错占了整套系统运行问题的很大比例,而且往往不是 SQL 本身的问题,而是环境参数配置问题。遇到这类报错第一反应不要去改代码,优先检查这三个地方:账号密码是否正确、库名是否一致、JDBC URL 参数是否完整。
6.2 SpringBoot 启动和运行异常
SpringBoot 启动失败,先看启动日志的最后几行,重点看 Caused by 部分,那才是根本原因。常见的有几类:
端口被占用是最简单的一种,报错是Port 8080 was already in use。处理方式一个是改配置里的 server.port,另一个是找到占用进程干掉。如果这台机器上还有别的服务在用 8080,建议把系统端口改成 8081 或 8090。
版本兼容性问题比较隐蔽。pom.xml 里 SpringBoot 版本如果过高,同时 JDK 版本又是 8,可能因为 SpringBoot 高版本要求 JDK 17+ 而直接启动失败。解决方案是确认 pom.xml 里的父依赖版本,配合对应的 JDK 版本。目前网上很多源码用的 SpringBoot 2.7.x,对应 JDK 8 是稳妥的组合。
依赖冲突也会导致启动异常,常表现为某个类找不到或者方法签名不匹配。这种问题处理起来比较烦,优先用 IDEA 自带的 Maven 面板看依赖树,找到重复依赖后,在 pom.xml 里排除冲突项。
6.3 Vue 构建和运行问题
前端常见的问题是 npm install 卡死或报错。处理思路:先清理缓存再装一次:
npm cache clean --force rm -rf node_modules package-lock.json npm install如果还不行,检查 Node.js 版本。Vue 2 的项目用 Node 16、Node 14 这类老版本更稳妥,用 Node 20 以上有时候会因为一些依赖包不兼容而失败。
开发服务器能启动但页面空白,打开浏览器控制台看报错。最常见的两类:一类是路由路径问题,访问根路径时没有匹配到任何路由,一片空白,检查路由配置中 path 的写法;另一类是 JS 报错,通常是某个组件引入路径写错导致编译失败,这种在终端里会直接看到编译错误提示。
6.4 前后端联调问题
联调阶段出现频率最高的是 404 和跨域问题。
404 要分清是谁返回的 404。如果浏览器 Network 面板显示请求发出了,但响应体是后端风格错误信息,说明请求确实到了后端,多半是接口路径写错了。如果响应体是 Nginx 或者开发服务器的默认 404 页面,说明请求根本没到后端,要检查代理配置和后端服务是否在运行。
跨域报错在开发环境基本是代理配置没生效导致。检查vue.config.js里 devServer.proxy 配置是否匹配前端的请求前缀。如果前端请求路径没有以/api开头,但代理只匹配/api,请求就会直接从前端端口发出,触发跨域错误。
关于登录后请求自动携带 token,需要在 axios 请求拦截器中统一设置:
service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = token } return config })6.5 部署到服务器之后的常见问题
本地跑通,部署到服务器后打不开,这类问题也不少见。优先级从高到低排查:
先看防火墙有没有放行对应端口。云服务器不同于家用的路由器开端口,控制台的安全组也得放行对应端口,服务器本机的firewalld或ufw也要检查。
再确认数据库是不是只绑定了本地地址。如果数据库和应用在同一台服务器,JDBC URL 写成localhost没问题;如果数据库单独一台机器,要确认配置的地址正确,并且数据库用户权限允许远程访问。MySQL 默认root用户一般只允许本机访问,远程连接时需要单独授权。
最后看静态资源路径。打包后如果发现图片不显示、样式丢失,多半是路径用了绝对路径,部署到子目录时找不到资源。建议打包前把publicPath配置成相对路径:
// vue.config.js module.exports = { publicPath: './' }这样打包出来的静态资源引用都是相对路径,不管部署到根路径还是子目录都能正常访问。
7. 二次开发与扩展方向
7.1 给新手:如何快速理解并修改源码
拿到源码后建议按照这样的顺序去读:先跑通整个项目,然后按照"前端发起请求 → 后端 controller 接收 → service 处理 → mapper 操作数据库"这条链路去追踪一个完整的业务功能。比如跟着"管理员新增居民"这个操作,把前端页面到后端接口到 SQL 执行完整走一遍,基本上就理解了这个项目的运行机制。
修改功能时的基本原则是:先看有没有现成的相似模块,复制改。比如要新增一个"疫苗接种预约"模块,就可以参照现有的"核酸记录"模块,表结构、页面、接口都有现成的参照,比从零开始写快得多。
7.2 给进阶:适合扩展的方向
这套系统本身是典型的中间型项目,扩展空间很大。
一是数据可视化,当前看板只有基础统计图,可以引入更丰富的报表,比如按时间段做趋势对比、按楼栋做占比分析,前端图表组件用到的 ECharts 扩展能力很足。
二是消息通知,可以接入短信或微信通知能力,比如公告发布时给居民推送通知。这类功能在中小社区场景下有真实需求。
三是导入导出,社区工作人员手里通常有现成的 Excel 居民台账,开发 CSV/Excel 导入功能,能显著降低录入成本。后端用 EasyExcel 或者 POI,前端下载模板、上传解析,这套流程很成熟。
四是访问日志和操作审计,对管理系统来说,操作留痕是刚需,可以增加一个简单的操作日志表,记录谁在什么时间做了什么操作。
我个人在实际操作中的体会是,这套项目最值钱的部分不在某一段代码写得多精妙,而在它给你提供了一套完整的闭环语义:从用户登录、数据录入、审批管理、统计展示,到最后部署上线,每个环节都能看到实际的产物。跑通它之后,再回头看 SpringBoot 官方文档、Vue 官方文档,理解深度完全不一样。那些单独学框架时看不懂的配置项,放在这样一个真实项目里,一下就通了。
最后再分享一个小技巧:这套系统跑通之后,建议自己动手改两个地方,一个是把首页的统计接口改成按楼栋维度查询,另一个是给居民管理增加一个 Excel 导出按钮。这两个小改动涉及数据库聚合查询、文件流下载、前端文件解析,每一个都是实际项目里高频用到、但教程里很少讲透的技能点。改完这两个功能,你对这套系统以及这套技术栈的掌握程度会有一个明显的提升。