ARTICLE DETAIL

资讯详情

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

社区疫情信息管理系统源码拆解:SpringBoot+Vue+MySQL实战部署

社区疫情信息管理系统源码拆解:SpringBoot+Vue+MySQL实战部署 我第一次拿到这套中小社区疫情信息管理系统源码的时候第一反应也是被标题里“可直接运行”四个字吸引。后来真正在服务器上部署又把前后端代码完整翻了一遍才发现这句话只对了一半它确实开箱即用但只有在你搞懂数据库配置、角色权限、前后端跨域这些问题之后才能算真正开箱即用。这套项目的技术栈非常标准SpringBoot后端加Vue前端加MySQL存储没有微服务没有消息队列也没有多余的中间件单体部署基本就能覆盖中小社区的日常健康数据管理需求。如果你正在找一份能二次开发的社区信息化源码或者想通过一个完整项目把SpringBootVueMySQL这条链路彻底打通这篇文章会把源码拆开讲清楚数据库怎么设计、后端接口链路怎么走、前端页面怎么组织、本地怎么跑起来以及真正部署时会被哪些问题卡住。我会尽量按我实际操作时的顺序来写有些地方带点个人判断供你参考。1. 这套系统到底解决什么问题社区级信息台账不是城市级大平台很多读者一看到“疫情信息管理系统”就下意识做各种复杂联想以为里面会有病例追踪、密接分析、风险等级研判之类的东西。实际上这只是一个面向社区工作者的信息管理后台核心用户不是疾控中心不是医院而是社区网格员和物业工作人员。它的业务闭环其实很朴素先把居民档案建成台账再把每天的体温和症状记录登记进去顺手维护出入和出行信息最后通过公告和报表完成日常工作。1.1 主流程只解决一件事把居民健康台账数字化从源码里能看出这个系统的核心实体就是“居民”和“日记录”。我建议你拿到项目后先不急着启动打开数据库脚本看一眼基本就能判断出源码质量。因为这类系统的复杂度和价值几乎全部体现在数据模型上。这套系统解决的主流程可以拆成五步社区工作人员登录后台分配不同角色和权限。维护居民基本档案包含姓名、身份证号、联系方式、楼栋单元房间号。每位居民每天录入健康状态例如体温、有无症状、整体是否正常。登记居民的出行和往返记录字段不需要太多够用就行。系统在首页展示统计看板管理者能直观看到今日上报了多少人、异常了多少人。说实话这五步对应到实际社区工作场景已经非常够用。社区级系统最忌讳的就是把需求做大一旦想在一套单体内塞进流调、检测、物资分配甚至跨部门协同反而容易变成谁都用不上的半成品。1.2 角色权限和菜单怎么配这套源码里的权限模型很精简没有走复杂的Spring Security权限框架而是自己用拦截器加角色字段实现的。理解它不需要看一堆源码知道角色和菜单的对应关系就行。角色定位可见功能admin系统管理员全部菜单包括用户管理和所有业务模块operator业务填报员居民档案、健康上报、出行登记、公告发布viewer只读查看者统计看板、查询页面不开放增删改我特别喜欢viewer这个角色的设计。中小社区经常需要上级部门或者物业领导查看每日数据但他们不应该拥有录入权限。单独留一个只读角色既不需要改动代码又能避免误操作把居民数据改乱。实际使用中你只要在sys_user表里给对应账号设置role_code为viewer登录后菜单自然就会过滤掉编辑按钮。这里有个容易忽略的细节源码里的菜单权限往往只是“页面级”权限。如果你要做按钮级权限需要在后端接口上再加一道校验。很多人在这一步偷懒只靠前端隐藏按钮这会导致一个明显漏洞——懂一点接口调用的人可以直接通过工具发请求新增数据。所以后面我会专门讲后端权限校验该怎么补。2. 数据库设计七张表把社区健康数据串起来拿到这种单体全栈项目我向来建议先看数据库脚本。因为后端代码和前端页面都可以改但数据模型一旦设计得不合理后续写什么功能都别扭。这套系统的表数量不多胜在语义清楚单看字段就能猜出业务逻辑。2.1 表清单及每张表的存在理由表名职责核心字段sys_user登录账号id, username, password, real_name, role_code, statussys_role角色定义id, role_code, role_name, descriptioncommunity_resident居民档案id, name, id_card, phone, building, unit, room, statushealth_check每日健康上报id, resident_id, check_date, temperature, symptom_desc, check_resulttravel_record出行登记id, resident_id, from_time, to_time, destination, transport, remarkcommunity_notice社区公告id, title, content, publisher_id, publish_time, statusnotice_read公告已读记录id, notice_id, reader_id, read_time为什么是这些表而不是把健康记录和出行记录都塞到一张大表里原因很简单健康上报按“人日期”去重出行记录按“时间段”查询两者时间维度不同。如果塞在一起查询当天哪些人没上报就不得不做复杂的SQL性能和维护体验都会变差。分开建表虽然看起来多做一步关联查询但每个模块的边界都清晰了。2.2 核心建表SQL可以直接粘到Navicat执行如果你拿到源码时没有SQL脚本或者准备自己重新建库下面三张核心表的DDL可以作为参考模板。这套结构我在多个社区项目里验证过中小规模下配合索引完全够用。CREATE TABLE community_resident ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT 姓名, id_card VARCHAR(18) NOT NULL COMMENT 身份证号, phone VARCHAR(20) DEFAULT NULL COMMENT 联系电话, building VARCHAR(50) DEFAULT NULL COMMENT 楼栋, unit VARCHAR(50) DEFAULT NULL COMMENT 单元, room VARCHAR(50) DEFAULT NULL COMMENT 房间号, status TINYINT NOT NULL DEFAULT 1 COMMENT 状态 1正常 0注销, create_time DATETIME DEFAULT NULL, update_time DATETIME DEFAULT NULL, UNIQUE KEY uk_id_card (id_card) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT居民档案表;身份证号加唯一索引非常关键。社区场景下身份证是居民最稳定的标识没有唯一索引录入数据时很容易因为手误或重复导入生成两条相似记录后面统计就会双份计数。建表时就把它堵住比在业务代码里做去重简单十倍。CREATE TABLE health_check ( id BIGINT PRIMARY KEY AUTO_INCREMENT, resident_id BIGINT NOT NULL COMMENT 居民ID, check_date DATE NOT NULL COMMENT 健康上报日期, temperature DECIMAL(4,2) DEFAULT NULL COMMENT 体温, symptom_desc VARCHAR(255) DEFAULT NULL COMMENT 症状描述, check_result VARCHAR(20) DEFAULT normal COMMENT 判定结果 normal正常 abnormal异常, create_time DATETIME DEFAULT NULL, update_time DATETIME DEFAULT NULL, UNIQUE KEY uk_resident_date (resident_id, check_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT每日健康上报表;这张表的细节在于UNIQUE KEY uk_resident_date。它保证同一天同一个居民只能有一条健康记录是防止重复上报的最后一道防线。即使后端代码真的出现并发双提交唯一索引也会让数据库拒绝第二条记录并且报出一个明确的Duplicate entry错误方便你快速定位。CREATE TABLE travel_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, resident_id BIGINT NOT NULL COMMENT 居民ID, from_time DATETIME NOT NULL COMMENT 出发时间, to_time DATETIME DEFAULT NULL COMMENT 返回时间, destination VARCHAR(255) NOT NULL COMMENT 目的地, transport VARCHAR(50) DEFAULT NULL COMMENT 交通方式, remark VARCHAR(255) DEFAULT NULL COMMENT 备注, create_time DATETIME DEFAULT NULL, update_time DATETIME DEFAULT NULL, KEY idx_resident_time (resident_id, from_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT出行登记表;出行登记按居民ID和出发时间建立联合索引是因为最常见的查询场景是看“某个人某段时间去过哪里”或者“某个楼栋最近有哪些人外出过”。联合索引能同时覆盖这两个过滤条件查询速度会快很多。2.3 status、create_time、update_time这些公共字段不是凑数的我见过不少开发者在建表时嫌麻烦把status字段去掉删除居民信息直接执行DELETE语句。这种习惯在中小系统里短期看不出问题一旦误删数据恢复起来极其痛苦。这套源码里几乎所有表都保留了status字段注销居民时只需要更新状态原来的历史记录还能留底报表统计也能根据状态排除或包含这些数据。create_time和update_time同样不是摆设。健康数据和出行数据最容易产生纠纷“这条记录到底什么时候提交的”“为什么同一人同一天有两条记录”查create_time一目了然。update_time则能在排查异常修改时发挥作用比如居民电话被误改先看更新时间再对操作记录比对着代码猜快得多。如果你希望数据库自动维护这两个时间字段可以在DDL里加上create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP不过很多SpringBoot项目会在实体类里使用MyBatis-Plus的自动填充功能二选一即可别在数据库和实体类里同时写否则时间字段会被覆盖得莫名其妙。3. SpringBoot后端拆解认证、接口、聚合查询三层逻辑后端部分是这套系统的重头戏。源码结构如果不算复杂说明这个项目的作者知道自己在做什么。最怕的是那种把所有方法都堆在一个类里的写法看起来能跑加一个功能就得改一圈代码。这套系统的分层比较常规属于标准的Controller-Service-Mapper三层配合实体类和工具类结构清爽。3.1 后端目录分层Controller里别写业务逻辑一个合理的后端分包大概是这样的com.example.communityhealth ├── Config // 配置类拦截器、跨域配置等 ├── Controller // 接口层只负责接收参数和返回结果 ├── Service // 业务层处理校验逻辑和事务 │ └── impl // Service实现类 ├── Mapper // 数据访问层写SQL或MyBatis-Plus查询 ├── Entity // 数据库实体 ├── Common // 统一返回对象、自定义异常、常量 ├── Utils // JWT工具类、日期工具类等 └── Interceptor // 登录拦截器很多社区项目的通病是Controller层写了两百行既要做参数校验又要查数据库还要循环统计出了问题根本不知道去哪里定位。我的建议是Controller里只做两件事第一把前端传来的参数绑定到对象第二调用Service并返回统一结果对象。所有业务规则都下沉到Service层这样单测也好写排查也好做。3.2 JWT登录认证和拦截器实现登录流程不复杂用户输入账号密码后端校验通过后生成一个JWT令牌返回前端前端保存令牌后续请求在Header里带上Authorization字段。后端写一个拦截器在请求进入Controller之前解析令牌解析失败就返回401。核心拦截器代码大概是下面这个套路public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行预检请求 if (OPTIONS.equals(request.getMethod())) { return true; } String token request.getHeader(Authorization); if (StringUtils.isEmpty(token)) { throw new BusinessException(401, 未登录或登录已过期); } Claims claims JwtUtil.parse(token); request.setAttribute(userId, claims.get(userId)); return true; } }把这套逻辑想通之后你会发现这个拦截器还顺手解决了一个大问题后面写任何Controller只要不主动从登录状态里取数据接收请求时就天然有了“当前用户是谁”的上下文。社区公告发布时会把userId写入publisher_id不再需要前端每次把用户ID当参数传过来既省事又安全。注册拦截器时要注意放行登录接口、静态资源和Swagger文档等路径。很多新手在这里踩坑拦截器写好了但没放行/api/auth/login结果登录接口自己都返回401页面卡在登录弹窗里出不去。3.3 健康上报接口的最小实现健康上报是整个系统最常用的接口它逻辑上的关键点在于“先查重再插入”。Controller层的代码完全可以只写一层薄壳RestController RequestMapping(/api/health) public class HealthCheckController { Resource private HealthCheckService healthCheckService; PostMapping(/submit) public ResultVoid submit(RequestBody Valid HealthCheckSubmitDTO dto) { healthCheckService.submit(dto); return Result.success(); } }Service层的submit方法里第一步通过resident_id check_date查询是否已有记录。已有则直接抛出业务异常提示“该居民今日已上报请勿重复提交”没有则把dto转换为实体并把check_result自动计算好再插入。这里有一个值得借鉴的设计check_result字段不是把复杂判断逻辑塞在SQL里而是在上报时就算好。比如temperature大于等于37.3或者symptom_desc非空就判为abnormal否则是normal。这样后续报表统计时只需要按check_result分组SQL简单很多也方便以后调整判定规则。3.4 统计报表用SQL聚合别在Java里做循环我见过太多人拿到这种项目后想把统计做成“先查出所有记录再在Java里用for循环计数”。数据量小的时候确实能跑但社区累积三个月数据后每次打开首页都要等两三秒体验会很糟糕。正确做法是让数据库帮我们算出结果。以“近7天每天上报人数和异常人数”为例一句SQL就能完成SELECT check_date, COUNT(*) AS total_count, SUM(CASE WHEN check_result abnormal THEN 1 ELSE 0 END) AS abnormal_count FROM health_check WHERE check_date BETWEEN #{startDate} AND #{endDate} GROUP BY check_date ORDER BY check_date;聚合查询写出来之后后端Service只需要调用Mapper方法把结果原样封装返回。前端拿到后直接渲染折线图或者柱状图就行。这个思路很重要能用数据库完成的计算就不要搬到业务代码里不仅速度快代码还更容易维护。4. Vue前端开发路由、状态、Axios三件事处理明白前端这块的技术栈很常规Vue搭配Element UI组件库页面结构基本就是登录页加后台管理布局。如果你想深读源码我建议抓住三个地方目录结构、请求封装、路由守卫。这三件事搞明白了整个前端就能串起来。4.1 前端目录结构和组件边界一个正常的前端项目目录大概长这样src/ ├── api/ // 按模块拆分的接口请求 │ ├── auth.js │ ├── resident.js │ └── health.js ├── assets/ // 静态资源图片、样式 ├── components/ // 公共组件例如上传组件、搜索栏 ├── router/ // 路由配置 ├── store/ // Vuex或Pinia状态管理 ├── utils/ │ └── request.js // Axios实例封装 └── views/ ├── Login.vue ├── Dashboard.vue ├── ResidentList.vue ├── HealthCheckList.vue ├── TravelRecordList.vue └── NoticeList.vue如果你拿到的源码是Vue 2加Element UI我建议先别急着升级Vue 3因为Element Plus和Vue 2的Element UI在组件API上有一堆细节差异贸然升级等于给自己挖坑。项目能跑、业务满足需求优先保证稳定性。想升级可以等熟悉业务链路之后再逐步迁移。4.2 Axios拦截器统一处理登录态和异常前端请求后端时最容易出现的问题就是后端报错只会看浏览器控制台里的一堆英文响应用户体验基本为零。好的做法是在Axios封装里统一处理错误。import axios from axios import router from /router const service axios.create({ baseURL: /api, timeout: 10000 }) service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] token } return config }) service.interceptors.response.use( response { const res response.data if (res.code 401) { localStorage.removeItem(token) router.push(/login) return Promise.reject(new Error(登录已过期)) } if (res.code ! 200) { // 这里统一弹出错误提示 return Promise.reject(new Error(res.message || 接口异常)) } return res }, error { // 网络错误或后端异常时的兜底提示 return Promise.reject(error) } )这样写的好处是业务代码里的try-catch不用重复弹提示后端返回的错误信息能直接展示给用户。遇到401时自动清理token并跳转登录页用户不会被一堆乱码JSON唬住。4.3 路由守卫和按钮级权限的落地做法路由守卫是前端权限控制的标配。上不上登录页、能不能访问某个模块在路由跳转前拦一道。router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (!token to.path ! /login) { next(/login) } else if (token to.path /login) { next(/) } else { next() } })但只看路由守卫还不够。很多源码把菜单显示做成“根据角色过滤”这没问题可页面按钮如果也直接渲染给viewer角色对方照样能看到“删除”“新增”按钮只是点击后权限不够才被后端拒绝。这种交互不够干脆。我建议做一个简单的按钮级指令Vue.directive(permission, { inserted(el, binding) { const roles store.state.user.roles || [] if (binding.value !roles.some(role binding.value.includes(role))) { el.parentNode el.parentNode.removeChild(el) } } })使用时这样写el-button v-permission[admin, operator]新增居民/el-button。没有权限的人连按钮都看不到比看到按钮再弹“无权限”舒服得多。5. 从空环境到登录页本地运行完整操作链路标题既然写了“可直接运行”这一节就重点解决怎么把它跑起来。很多人卡住其实不是代码问题而是环境问题。我按我平时推荐的顺序写一遍完整流程你照着操作基本不会跑偏。5.1 环境版本参考与选择原因组件推荐版本说明JDK1.8或11Spring Boot 2.7用8完全没问题不想折腾装11也行Maven3.6以上别用太老的版本依赖下载容易出问题MySQL5.7或8.0推荐8.0utf8mb4支持更好Node.js14或16Vue 2项目常见node-sassNode过高会编译失败前端包管理器npm或yarnyarn更稳但npm也够用很多人上来就装最新版Node 20然后跑npm install报一串python和node-gyp错误直接把心态搞崩。实际上问题不在你的代码而是node-sass版本跟Node版本不匹配。遇到这种情况最稳的办法是安装Node 14然后重新执行npm install。5.2 初始化数据库和启动后端的顺序先把数据库建好。推荐在命令行执行避免图形工具编码格式不对导致乱码mysql -uroot -p -e CREATE DATABASE community_health DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; mysql -uroot -p community_health db/community_health.sql如果你的源码没有sql文件就手动执行前面第二部分的DDL再自行补一条测试账号。接着修改application.yml中的连接信息server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/community_health?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: yourpassword后端启动通常有两种方式。在IDEA里直接运行CommunityHealthApplication主类最方便喜欢命令行的话先执行mvn clean package -DskipTests再执行java -jar target/community-health.jar。首次启动要耐心等Maven下载依赖如果报“程序包不存在”之类的错误多数是本地仓库依赖损坏执行mvn clean package强制重新构建一次即可。5.3 前端启动、代理和Nginx配置进入前端目录后npm install npm run serve启动成功后默认地址一般是http://localhost:3000但前端请求的是后端8080端口所以必须配置开发代理。很多源码在vue.config.js里已经写好了module.exports { devServer: { port: 3000, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } }如果你拿到的源码没有这个文件自己补上就行。这样前端页面里所有以/api开头的请求都会被代理转发到后端也就不存在跨域问题。真正上线部署时后端一般打成JAR放服务器前端构建成静态文件交给Nginx托管。Nginx需要同时做两件事托管前端静态页面并把/api反向代理到本机8080端口。server { listen 80; server_name your-domain.com; root /usr/share/nginx/html; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location / { try_files $uri $uri/ /index.html; } }try_files这行非常关键后面第六节我会专门讲为什么少了它会白屏。5.4 怎么确认系统真的跑起来了启动完成后打开前端地址用初始账号登录。如果登录成功能看到首页统计看板基本说明数据库连接、后端接口、前端代理这一整条链路是通的。登录后打开浏览器F12切到Network面板刷新页面时看请求列表里是否有红色报错一个都没有说明接口全部正常。另一个快速验证方式是直接访问后端接口地址http://localhost:8080/api/auth/login用Postman发一个POST请求如果能返回token说明后端和数据库没有问题。这时候如果前端还是登录失败问题基本就锁定在代理配置或者后端跨域配置上。6. 实测高频问题清单跨域、时区、部署、分页逐个说清从网上找到的“可直接运行”源码通常默认环境跟作者的开发机一模一样。我们自己的环境不可能完全相同所以出问题几乎是必然的。我把实际运行过程中最常见的几个问题按出现频率排了个序每个都附上解决办法。6.1 跨域报错开发环境请求被拦截的根因浏览器报No Access-Control-Allow-Origin看着像后端没开跨域其实在本地开发时最优雅的解决方案是用代理。前面提到的vue.config.js代理一旦生效浏览器里看到的前端和后端是同一个域名加端口根本不存在跨域所以也就不需要后端配置Cors。如果你选择不用代理而是让前端直接把请求发到http://localhost:8080那后端就必须配置跨域。我用过比较稳的方案是注册一个WebMvcConfigurerConfiguration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowCredentials(true) .maxAge(3600); } }需要提醒一点allowedOrigins(*)和allowCredentials(true)在Spring Boot新版本里是不允许同时使用的浏览器会直接拒绝带Cookie或认证信息的跨域请求。用allowedOriginPatterns(*)再配合allowCredentials(true)是目前最不容易踩坑的写法。6.2 MySQL连接报错Public Key Retrieval与SSL如果你用的是MySQL 8启动后端时经常能看到Public Key Retrieval is not allowed或一堆SSL连接报错。这两个问题都是JDBC连接串里的参数没加全导致的。统一改成下面这段url: jdbc:mysql://localhost:3306/community_health?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrueuseSSLfalse是为了避免本地连接时SSL握手警告allowPublicKeyRetrievaltrue是MySQL 8的密码加密方式要求的。生产环境如果你有安全顾虑可以自行调整但本地开发加上这两个参数能省下大量折腾时间。6.3 时间显示差了8个小时时区问题前端页面上看到的创建时间和数据库里的时间差了8小时这是典型的时区问题。数据库里存的是UTC时间查询结果返回时按本地服务器时区序列化两端不一致就会偏移。最干脆的办法是在application.yml里固定serverTimezone为Asia/Shanghai同时给日期字段的JSON序列化加上格式注解JsonFormat(pattern yyyy-MM-dd HH:mm:ss, timezone GMT8) private LocalDateTime createTime;健康上报日期建议用LocalDate只保存年日月完全不涉及时区换算比Date类型省心很多。这也是我在第二个章节里强调check_date DATE的原因之一。6.4 Vue history路由部署后刷新404开发模式下打开http://localhost:3000/resident刷新没问题一旦打包上线部署到Nginx后在某个子路径下按F5刷新页面直接404。原因很简单前端是单页应用路由切换只是修改了地址栏并没有真正存在/resident这个物理目录。刷新时Nginx去磁盘找这个目录自然找不到。解决办法就是我前面写的Nginx配置里那行location / { try_files $uri $uri/ /index.html; }try_files的作用是匹配不到真实文件时把请求交给index.html由前端路由接管然后按地址渲染对应页面。如果实在不想处理Nginx也可以把路由改成hash模式地址栏会多一个#刷新大概率不会出问题但看起来不如history模式美观。6.5 分页失效少了MybatisPlus拦截器源码用MyBatis-Plus的Page对象做分页时常见现象是前端传了current和size返回结果也有total但SQL里没有LIMIT每次把所有数据都查出来。原因是没有注册分页插件。在配置类里加一个Bean就能解决Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }如果加了配置还是不分页检查一下实体类里是否用了TableId注解主键没映射对会让条件查询产生诡异问题严重时连分页计数都失真。6.6 端口占用后的连锁反应后端默认8080前端默认3000。如果启动时提示端口被占用很多人会直接改端口却忘了改前端代理的target地址结果前端页面打开了接口请求却全部失败。正确操作是同时修改两处后端application.yml里的server.port以及前端vue.config.js里proxy的target。排查端口时可以用netstat -ano | grep 8080看到占用进程后能关就关不能关就统一改端口。我见过有人只改了后端端口前端代理没改登录页面转圈半小时才发现问题非常浪费生命。7. 上线之前我建议按这个顺序做增强源码能跑通只是第一步。真正落地到社区使用还需要一些安全性和易用性上的增强。这一节我给的建议都偏轻量级不想把它变成火箭工程毕竟社区系统的核心诉求是稳定和够用。7.1 密码加密从明文到BCrypt很多教学向源码为了演示方便密码字段直接存了明文甚至统一写成admin123。项目如果上线这是绝对不能接受的。服务器一旦被入侵数据库里的人名和密码会被直接拖走容易引发连锁风险。我的建议是不要为了加密去引入整个Spring Security只要引入一个轻量工具就能把密码哈希掉。Hutool工具包里的BCrypt模块用起来很简单// 注册时保存密文 String hashpw BCrypt.hashpw(rawPassword, BCrypt.gensalt()); // 登录时校验 boolean checkpw BCrypt.checkpw(inputPassword, storedHash);如果担心改造成本也可以保留原来的拦截器逻辑只把密码比较的地方换成BCrypt其余代码不用动。已经有明文历史数据的话写一个定时任务或者SQL更新脚本让用户下次登录时自动升级密码存储方式这个方案最平滑。7.2 用定时任务做未上报提醒社区场景下工作人员不可能每天上午逐个打电话问居民“今天体温多少”。利用定时任务可以每天早上自动查询前一天或当天未上报的居民名单然后把提醒消息写入公告或者站内信。SpringBoot内置的Scheduled就能干这个活不需要额外引入中间件。Component public class HealthRemindTask { Resource private ResidentService residentService; Scheduled(cron 0 30 9 * * ?) public void remindUncheckedResidents() { ListResident unCheckedList residentService.listUncheckedByDate(LocalDate.now()); // 生成提醒记录或调用消息推送接口 } }定时任务上线的第一周建议把cron时间设在早上9点半给居民留出吃完早餐上报的时间。如果社区里有部分老人不会用手机以后再扩展二维码扫码上报或者家人代报这是后话。7.3 数据备份和Excel导出任何管理系统上线前都要先想好备份方案。社区数据量不大只需要在服务器上做定时备份即可mysqldump -uroot -p community_health /backup/community_health_$(date %Y%m%d).sql配合crontab每天凌晨执行一次保留最近7天备份基本能覆盖绝大多数故障场景。Excel导出是“社区上报数据”最常见的需求。导出居民台账、按日期导出健康记录领导几乎每周都要。推荐使用EasyExcel相比Apache POI它的内存占用低、写法简单导出几千行数据非常流畅。给导出接口加上权限校验确保只有admin和operator角色能调用顺手再给文件名加上日期比如“健康上报明细20250920.xlsx”下载后归档也方便。最后说点实际操作中的体会这套源码最值得学习的不是某个炫技功能而是它把一套业务主线完整走通的工程能力。拿到代码后先别急着跑效果沉下心追一条链路从数据库的health_check表开始依次看Mapper接口、Service实现、Controller参数、前端api文件再到列表页组件和路由。一条线走通之后你会发现居民管理和出行登记都是同一套套路的变体字段换一换、接口换一换逻辑基本是共通的。我自己的习惯是先把数据库脚本完整执行一遍然后打开后端日志看SQL打印再打开前端Network面板看接口请求。三次验证全部通过才敢说这个系统真正跑懂了。如果你能在这一套标准流程里反复操作几遍不仅能把SpringBootVueMySQL的链路玩明白下次遇到类似的社区类管理系统也能直接套用这套分析方法这才是源码之外最有价值的东西。
返回列表