十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

SpringBoot+Vue前后端分离的疫情隔离管理系统实战

SpringBoot+Vue前后端分离的疫情隔离管理系统实战 1. 项目概述与需求拆解1.1 项目背景与核心需求分析先说说这个疫情隔离管理系统到底是什么。简单来讲这是一套典型的Java Web全栈管理系统后端用SpringBoot搭接口前端用Vue做页面数据层用MySQL存数据MyBatis负责数据库操作。它的核心场景是解决疫情期间社区、学校或企业单位对隔离人员的信息化管理问题谁在隔离、从哪回来、隔离到什么时候、每天的体温和健康状况怎么样、物资和出入怎么登记这些如果靠Excel表格去维护数据一多就全乱套了。我当时做这个项目的起因很简单一个做社区工作的朋友跟我吐槽说他们还在用共享表格登记隔离信息几十个表格来回传稍不注意版本就冲突了。所以这个系统的实际需求非常具体需要一个能够替代手工登记、人人都能简单上手的信息化管理工具。从用户角色上看核心用户有三类系统管理员负责配置基础数据和账号工作人员负责录入隔离人员信息、每天登记健康情况普通用户隔离人员本人可以查看自己的隔离状态和提交健康信息。技术选型上这个项目属于非常标准的SpringBootVue前后端分离架构。这种组合在当前Java就业市场上几乎是标配也是很多初学者或者毕业设计选择它的核心原因——学完这一套动手做一个完整的全栈项目对理解企业级开发流程非常有帮助。如果你正在学Java和前端想找一个能打通全链路的练手项目这套系统再合适不过。另外这个项目还有一个好处代码组织清晰模块边界分明。它不像很多教学项目那样把所有代码堆在几个类里而是按照实际工程规范做了分层。后面我会把每一层的设计思路和关键代码都拆开讲保证就算是刚接触SpringBoot的人照着也能一步步把项目跑起来。1.2 系统边界与功能定位在动手写代码之前最重要的事情是把功能边界划清楚。很多初学者一上来就想着把所有功能都做了结果项目越写越乱。我这个系统的定位非常明确就是隔离管理核心业务链路围绕隔离人员这个主体展开。系统整体分为六大功能模块登录与权限模块基于角色的访问控制不同角色登录后看到不同的菜单和操作按钮。用户信息管理管理员维护系统用户工作人员维护隔离人员档案支持新增、修改、查询。隔离登记管理录入隔离人员信息包括来源地、隔离开始时间、预计结束时间、隔离地点等。健康打卡管理每天记录隔离人员的体温和健康状况支持按日期范围查询。出入登记管理隔离期间的外出或物资交接记录做到有据可查。数据统计与导出按天、按周统计在隔人数、解除人数支持导出Excel表格。这里有个设计取舍想单独说一下——为什么没有做复杂的审批流因为隔离管理的业务链路虽然涉及多个角色但实际流程相对固定基本是登记→每日打卡→到期解除的线性结构。用SpringBoot做一套完整的流程引擎比如集成Flowable成本很高而且在这个场景下属于过度设计。所以我的做法是设计一张状态字段用简单的状态机迁移来控制流程既满足业务需求又不会把项目复杂度推高到不可控的水平。数据库层面这个项目一共设计了8张核心表用户表、角色表、隔离人员表、健康打卡表、出入登记表、隔离点表、公告表、操作日志表。这个表数量不多不少刚好覆盖业务闭环又不需要花太多精力去维护数据关系。后面我会把重要的表结构设计拿出来逐字段讲这部分对面试和实际开发都有参考价值。2. 技术选型分析与架构设计2.1 为什么选择SpringBootVue这套组合这个技术栈组合放在2024年的Java生态里依然是能打的。我见过不少人在技术选型上纠结其实搞明白每一层解决什么问题就好选了。SpringBoot解决的是后端快速开发和配置简化的问题。在没有SpringBoot的年代搭一个SpringMVC项目要写一堆XML配置光配置文件就能让人劝退。SpringBoot用自动配置和起步依赖把这些全部封装掉你只需要在pom.xml里引入几个starter写几行application.yml就能跑起来一个Web服务。Vue解决的是前端页面复杂交互的问题。传统的JSPJQuery模式下页面和Java代码耦合严重改个按钮样式还要重启服务。Vue的组件化开发模式让界面拆分成一个个独立组件每个组件管理自己的数据和行为配合Vue Router做路由跳转、Axios做HTTP请求前后端职责非常清晰。MyBatis在这个体系里干的是数据库访问层的活。可能有人会问为什么不直接用Spring Data JPA我的答案很简单MyBatis的SQL可控性更强。这个系统里有很多动态查询场景比如根据隔离状态、来源地、时间范围组合筛选人员动态SQL是MyBatis的强项。而且对于国内大多数公司来说MyBatis的使用率远高于JPA学这个更贴近实际工作。至于MySQL那就是数据库里的常青树了。开源免费、性能稳定、社区资料丰富中小型管理系统用MySQL完全够用而且招聘市场上熟悉MySQL几乎是Java岗位的标配要求。这套组合的本质逻辑是用最主流、最成熟、学习成本最低的技术把业务系统做好而不是为了炫技引入各种花哨的新框架。这是我做项目选型的一贯原则。2.2 数据库设计与核心表结构数据库设计是决定系统质量的地基很多问题如果表结构设计得合理后面写代码会非常顺畅。先把8张表的总体关系说一下然后挑几张核心表重点讲解。用户表sys_user是最基础的表字段设计如下CREATE TABLE sys_user ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键ID, username varchar(50) NOT NULL COMMENT 登录名, password varchar(100) NOT NULL COMMENT 密码(MD5加密), real_name varchar(50) DEFAULT NULL COMMENT 真实姓名, phone varchar(20) DEFAULT NULL COMMENT 手机号, role_id bigint(20) DEFAULT NULL COMMENT 角色ID, status tinyint(1) DEFAULT 1 COMMENT 状态1正常 0禁用, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4 COMMENT系统用户表;这里有两个容易踩坑的点一是用户名字段一定要加唯一索引否则会出现重复账号二是密码不要明文存储虽然做的是学习项目但加密习惯要从一开始养成。我这里用的是MD5加盐的方式实际生产环境建议用BCrypt。隔离人员表isolation_person是业务核心表CREATE TABLE isolation_person ( id bigint(20) NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL COMMENT 姓名, id_card varchar(18) NOT NULL COMMENT 身份证号, phone varchar(20) DEFAULT NULL COMMENT 联系电话, gender tinyint(1) DEFAULT NULL COMMENT 性别1男 2女, age int(11) DEFAULT NULL COMMENT 年龄, address varchar(200) DEFAULT NULL COMMENT 现住址, source_location varchar(100) DEFAULT NULL COMMENT 来源地, isolation_point_id bigint(20) DEFAULT NULL COMMENT 隔离点ID, start_date date DEFAULT NULL COMMENT 隔离开始日期, end_date date DEFAULT NULL COMMENT 预计结束日期, status tinyint(1) DEFAULT 1 COMMENT 状态1隔离中 2已解除 3异常, remark varchar(500) DEFAULT NULL COMMENT 备注, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_status (status), KEY idx_id_card (id_card) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT隔离人员表;身份证号加索引非常关键。这个系统里面查重、统计、按人检索全靠它不加索引数据量大了必慢。状态字段用tinyint存储配合代码里的状态枚举比直接用字符串存储更省空间查询效率也更高。健康打卡表health_record的设计要点是每天每人一条记录CREATE TABLE health_record ( id bigint(20) NOT NULL AUTO_INCREMENT, person_id bigint(20) NOT NULL COMMENT 隔离人员ID, temperature decimal(4,2) DEFAULT NULL COMMENT 体温, health_status varchar(20) DEFAULT NULL COMMENT 健康状况正常/咳嗽/乏力等, is_fever tinyint(1) DEFAULT 0 COMMENT 是否发烧1是 0否, record_date date DEFAULT NULL COMMENT 记录日期, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_person_date (person_id,record_date), KEY idx_record_date (record_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT健康打卡记录表;这里最值得学习的就是uk_person_date这个联合唯一索引。它的作用是确保一个人同一天只能有一条打卡记录在数据库层面就杜绝了重复数据。这个设计比在代码里先查再插要靠谱得多而且配合INSERT ... ON DUPLICATE KEY UPDATE可以轻松实现有则更新无则插入的逻辑。2.3 项目目录结构与分层设计好的分层设计让项目具有可维护性。一般Java后端项目分层是Controller接口层→ Service业务层 → Mapper数据访问层Model实体层贯穿其中。这个项目的目录结构如下src/main/java/com/example/isolation ├── controller # 接口控制层 │ ├── AuthController.java │ ├── PersonController.java │ └── HealthRecordController.java ├── service # 业务逻辑层 │ ├── PersonService.java │ └── impl/ │ └── PersonServiceImpl.java ├── mapper # MyBatis数据访问层 │ ├── PersonMapper.java │ └── xml/ │ └── PersonMapper.xml ├── entity # 数据库实体类 │ ├── SysUser.java │ ├── IsolationPerson.java │ └── HealthRecord.java ├── dto # 数据传输对象 │ ├── LoginDTO.java │ └── PersonQueryDTO.java ├── common # 通用工具类 │ ├── Result.java │ ├── JwtUtil.java │ └── ExcelUtil.java └── config # 配置类 └── WebConfig.java这个分层的好处非常明显。Controller只负责接收参数和返回结果不写业务逻辑业务逻辑统一收拢在Service层方便复用和事务控制SQL写在XML文件里跟Java代码分离修改SQL不需要重新编译Java类。有一个常见问题很多人喜欢在Controller里直接注入Mapper把业务逻辑写在Controller里图省事。一旦出现多个接口复用同一个查询逻辑你就得复制粘贴后面维护会非常痛苦。所以我强烈建议遵守这个分层规范。前端Vue项目的目录结构我采用的是标准Vue CLI创建的项目结构src ├── api # 所有接口请求封装 │ ├── login.js │ └── person.js ├── router # 路由配置 │ └── index.js ├── store # Vuex状态管理 │ └── modules/ │ └── user.js ├── views # 页面组件 │ ├── Login.vue │ ├── Dashboard.vue │ └── person/ │ ├── PersonList.vue │ └── PersonEdit.vue ├── components # 通用组件 │ └── Pagination.vue └── utils # 工具函数 ├── request.js # Axios封装 └── auth.js # Token存取接口请求统一封装在request.js里在里面配置Axios的baseURL、请求拦截器加Token、响应拦截器统一处理错误码。这样每个页面的API调用只需要写业务接口路径代码看起来非常清爽。3. 核心功能模块逐一拆解3.1 登录认证与权限控制的实现方式登录认证是管理系统的门面这部分做得是否规范直接影响整个系统的安全性。我采用的技术方案是JWTJSON Web Token 拦截器这也是当前前后端分离项目最主流的认证方案。JWT的核心思路是用户登录成功后后端生成一个带签名和有效期信息的Token字符串返回给前端前端把Token存到LocalStorage里每次请求都把它放在HTTP Header里传给后端后端通过拦截器校验Token的合法性和有效性从而判断用户身份。看一下JWT工具类的核心代码public class JwtUtil { // 密钥实际生产环境应该放在配置文件中不要硬编码 private static final String SECRET_KEY isolation-system-secret-2024; // 过期时间24小时 private static final long EXPIRE_TIME 24 * 60 * 60 * 1000; public static String generateToken(Long userId, String username, Integer roleId) { Date now new Date(); Date expireDate new Date(now.getTime() EXPIRE_TIME); return Jwts.builder() .setHeaderParam(typ, JWT) .setSubject(username) .claim(userId, userId) .claim(roleId, roleId) .setIssuedAt(now) .setExpiration(expireDate) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } public static Claims parseToken(String token) { return Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody(); } }这里我用的是io.jsonwebtoken:jjwt这个库。生成Token的时候把用户ID和角色ID都放到claim中后面拦截器校验的时候不需要再查数据库就能拿到用户身份信息减少了一次数据库查询。接下来是拦截器的实现。自定义一个LoginInterceptor实现HandlerInterceptor接口在preHandle方法中校验Tokenpublic class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (StringUtils.isBlank(token)) { response.setStatus(401); response.setContentType(application/json;charsetutf-8); response.getWriter().write({\code\:401,\msg\:\未登录或登录已过期\}); return false; } try { // 去掉 Bearer 前缀 token token.replace(Bearer , ); Claims claims JwtUtil.parseToken(token); request.setAttribute(userId, claims.get(userId)); request.setAttribute(roleId, claims.get(roleId)); return true; } catch (Exception e) { response.setStatus(401); response.getWriter().write({\code\:401,\msg\:\Token无效\}); return false; } } }权限控制这里我选择在拦截器中只做登录认证不做繁琐的接口级权限校验。因为系统只有三种角色页面按钮级别的权限控制放在前端路由守卫里做就够了。后端层面对于管理员的专属接口我在代码里通过RequiresRole之类的自定义注解来处理。这样既保证了安全性又没有引入Spring Security或Shiro这种重量级框架项目复杂度控制得很好。3.2 隔离人员管理模块的前后端联动隔离人员管理是系统的核心模块包含列表查询、新增、编辑、解除隔离、详情查看五个功能点。这里重点讲讲列表查询和新增保存。列表查询是一个典型的动态多条件查询场景。用户可能按姓名模糊查询也可能按状态筛选还可能按来源地精确匹配。如果用传统方式拼接SQL代码会非常啰嗦且容易出错。MyBatis的动态SQL就是为这个场景设计的。看一下 PersonMapper.xml 中的核心查询语句select idselectPageList resultTypecom.example.isolation.entity.IsolationPerson SELECT * FROM isolation_person where if testname ! null and name ! AND name LIKE CONCAT(%, #{name}, %) /if if teststatus ! null AND status #{status} /if if testsourceLocation ! null and sourceLocation ! AND source_location #{sourceLocation} /if if testidCard ! null and idCard ! AND id_card #{idCard} /if /where ORDER BY create_time DESC LIMIT #{offset}, #{pageSize} /selectwhere标签会自动帮我们处理掉第一个条件前面的AND关键字这个细节如果自己拼SQL很容易漏。另外分页我这里是手动用LIMIT #{offset}, #{pageSize}实现的没有引入PageHelper插件。原因是这个系统数据量不大手动分页足够少一个依赖就少一份兼容性问题而且面试的时候手写分页还能加分。新增保存需要考虑一个业务规则同一身份证号的隔离人员如果当前状态是隔离中不能重复录入。这个校验放在Service层实现Override Transactional(rollbackFor Exception.class) public boolean addPerson(IsolationPerson person) { // 1. 查出这个人当前是否有未解除的隔离记录 IsolationPerson existing personMapper.selectActiveByIdCard(person.getIdCard()); if (existing ! null) { throw new BusinessException(该身份证号已有未解除的隔离记录); } // 2. 默认状态为隔离中 person.setStatus(1); // 3. 计算隔离结束日期默认14天 if (person.getEndDate() null) { Calendar calendar Calendar.getInstance(); calendar.setTime(person.getStartDate()); calendar.add(Calendar.DAY_OF_MONTH, 14); person.setEndDate(calendar.getTime()); } return personMapper.insert(person) 0; }这里有两个关键点。第一Transactional注解保证了事务一致性如果后面还要写健康记录的初始化数据任何一步失败都会整体回滚第二隔离结束日期默认按开始日期加14天计算这也是疫情管控中常见的隔离周期但如果业务上有特殊需求页面上也可以手动修改。前端页面用Vue Element UI实现。列表页的关键结构大致是这样的template div classperson-list !-- 搜索栏 -- el-form :inlinetrue :modelqueryParams el-form-item label姓名 el-input v-modelqueryParams.name placeholder请输入姓名 clearable / /el-form-item el-form-item label状态 el-select v-modelqueryParams.status placeholder请选择状态 clearable el-option label隔离中 :value1 / el-option label已解除 :value2 / el-option label异常 :value3 / /el-select /el-form-item el-form-item el-button typeprimary clickloadData查询/el-button el-button clickresetQuery重置/el-button /el-form-item /el-form !-- 数据表格 -- el-table :datatableData v-loadingloading border stripe el-table-column propname label姓名 width100 / el-table-column propidCard label身份证号 width180 / el-table-column propsourceLocation label来源地 width120 / el-table-column propstartDate label开始日期 width120 / el-table-column propendDate label结束日期 width120 / el-table-column label状态 width90 template slot-scopescope el-tag :typestatusTagType(scope.row.status) {{ statusText(scope.row.status) }} /el-tag /template /el-table-column el-table-column label操作 width180 template slot-scopescope el-button typetext clickhandleDetail(scope.row)详情/el-button el-button typetext clickhandleEdit(scope.row)编辑/el-button el-button typetext stylecolor: #f56c6c clickhandleRelease(scope.row) v-ifscope.row.status 1 解除隔离 /el-button /template /el-table-column /el-table !-- 分页 -- el-pagination size-changehandleSizeChange current-changehandleCurrentChange :current-pagequeryParams.pageNum :page-sizes[10, 20, 50, 100] :page-sizequeryParams.pageSize layouttotal, sizes, prev, pager, next, jumper :totaltotal /el-pagination /div /template这套前端代码里el-form负责搜索条件输入el-table负责数据展示el-pagination负责分页。这三个组件组合起来就是一个标准的后台管理列表页模板。把这个模板掌握好以后做任何系统的增删改查页面都能套用。API请求的封装长这样// src/api/person.js import request from /utils/request export function getPersonPage(data) { return request({ url: /api/person/page, method: post, data }) } export function addPerson(data) { return request({ url: /api/person/add, method: post, data }) } export function updatePerson(data) { return request({ url: /api/person/update, method: post, data }) } export function releasePerson(id) { return request({ url: /api/person/release/${id}, method: put }) }Axios 封装在request.js里统一处理了Token注入、HTTP状态码校验、业务错误码提示这些逻辑页面上只需要关心接口调用和数据展示。3.3 健康打卡与数据统计模块的设计思路健康打卡模块的逻辑比较直白就是每天上报体温和症状但有几个地方需要仔细设计。前端打卡页面要解决今天是否已打卡的状态展示。用户在页面加载时先调一个今天是否已打卡的接口如果已打卡就显示今日记录并禁用提交按钮没打卡就展示可用的表单。这个逻辑我在前端用到了computed计算属性来判断打卡状态computed: { // 根据今天是否已打卡动态计算按钮文本和禁用状态 submitBtnText() { return this.todayRecord ? 今日已打卡请勿重复提交 : 提交今日健康情况 }, isDisabled() { return !!this.todayRecord } }有读者可能不理解为什么这里用 computed 而不是直接在 data 里定义一个布尔值。原因在于 computed 具有响应式依赖的特性——只要todayRecord发生改变所有依赖它的模板和计算属性都会自动更新。如果用普通变量你还需要手动在多个方法里同步修改这个状态不仅代码冗余还容易漏。后端保存打卡记录的关键代码Override public boolean saveHealthRecord(HealthRecord record) { // 同一天同一人只能有一条记录数据库层面已有唯一索引兜底 int rows healthRecordMapper.upsert(record); // 如果体温超过37.3度自动将隔离人员状态标记为异常 if (record.getTemperature() ! null record.getTemperature().compareTo(new BigDecimal(37.3)) 0) { personMapper.updateStatus(record.getPersonId(), 3); } return rows 0; }这里的upsert用的是MyBatis的insert标签配合ON DUPLICATE KEY UPDATE语法实现存在则更新不存在则插入的原子操作避免并发情况下的重复数据问题。数据统计模块是给管理员看的大屏报表。我实现了三个核心指标当前在隔人数、今日新增隔离人数、累计解除人数以及最近7天隔离人数的趋势折线图。前端的图表用的是ECharts后端提供统计数据接口GetMapping(/stats/overview) public ResultMapString, Object getOverview() { MapString, Object result new HashMap(); // 当前在隔人数 result.put(isolatingCount, personMapper.countByStatus(1)); // 今日新增 result.put(todayAddCount, personMapper.countTodayAdd()); // 累计解除 result.put(totalReleasedCount, personMapper.countByStatus(2)); // 近7天趋势 result.put(trendList, personMapper.selectLast7DaysTrend()); return Result.success(result); }统计SQL里用到了一些日期函数比如DATE_FORMAT(create_time, %Y-%m-%d)来按天分组统计。MySQL的日期函数是面试常考点建议熟练掌握DATE_FORMAT、DATE_ADD、DATEDIFF这几个。4. 完整的环境搭建与部署实操4.1 后端环境配置JDK、Maven、MySQL这一步是很多新手的第一个拦路虎。我尽量把每一步写清楚跟着操作就能成功。JDK版本选择JDK 1.8原因很现实SpringBoot 2.x 系列全面兼容JDK 8而且目前大部分公司的生产环境还在用JDK 8。虽然JDK 17或更高版本性能更好但对于学习和求职来讲JDK 8足够用了。JDK环境变量配置需要注意的是JAVA_HOME和PATH两个变量不能配错JAVA_HOME D:\Java\jdk1.8.0_202 PATH %JAVA_HOME%\binMaven我用的是 3.6.3 版本。装完之后一定要改两个配置第一个是conf/settings.xml里的本地仓库路径不要默认放在C盘否则项目依赖一多C盘空间会告急localRepositoryD:\Java\maven-repository/localRepository第二个是镜像推荐用阿里云镜像不然下载SpringBoot依赖慢到怀疑人生mirror idaliyunmaven/id mirrorOfcentral/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/central/url /mirrorMySQL我选用的是 MySQL 5.7因为它的稳定性和资料丰富度是社区公认最好的。安装时需要注意字符集必须选utf8mb4否则存不了emoji表情后期还会出现中文乱码。连接工具可以用官方的MySQL Workbench也可以用Navicat看个人习惯。数据库建好后在application.yml里配置连接信息spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/isolation_system?useUnicodetruecharacterEncodingutf8mb4useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.isolation.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl这里有几个关键配置逐一说一下serverTimezoneAsia/Shanghai用于解决MySQL时区不一致导致的日期时间差8小时问题。map-underscore-to-camel-case这个配置非常实用它让MyBatis自动把数据库的create_time字段映射为Java实体里的createTime属性不需要手动写resultMap。log-impl设为StdOutImpl可以在控制台打印SQL日志调试的时候能直观看到MyBatis执行的每一条SQL语句。4.2 前端环境配置Node、Vue CLI前端环境配置相对简单但要注意版本兼容问题。Node.js我推荐使用 14.x 或 16.x 的稳定版。Node版本太高比如18/20在某些Vue CLI旧项目里会出现OpenSSL Error的问题我最早做项目时就被这个问题折磨了很久后面单独讲排查思路。安装完Node.js后npm是自带配套的。但是国内直接使用npm官方源下载依赖速度很慢所以建议换成淘宝源npm config set registry https://registry.npm.taobao.org然后全局安装Vue CLI脚手架工具npm install -g vue/cli检查版本vue --version使用Vue CLI创建项目vue create isolation-web创建过程中会询问使用哪种配置我选择的是Manually select features然后勾选Babel、Router、Vuex。Vue版本选择2.x不是3.x。这里我需要解释一下这个决策本项目选择Vue 2的一个重要原因是我使用的Element UI组件库生态最成熟的是Vue 2版本。虽然在Vue 3里也有Element Plus但Vue 2 Element UI在市场上存量项目依然很大学会它不亏。当然如果你是新学直接从Vue 3起步也完全可以技术选型没有绝对的对错只有适不适合当下场景。项目创建完成后安装Element UI和Axiosnpm install element-ui --save npm install axios --save npm install sass sass-loader10 --save-devnpm install之所以要把sass-loader版本锁到10是因为新版的sass-loader默认使用webpack 5而Vue CLI 4使用的还是webpack 4不锁版本就会报编译错误。4.3 前后端联调与项目启动项目启动是整个开发过程中最刺激的环节。先说后端启动步骤# 1. 进入项目目录 cd isolation-server # 2. 编译打包跳过测试 mvn clean package -DskipTests # 3. 启动项目 java -jar target/isolation-server-0.0.1-SNAPSHOT.jar或者直接用IDEA打开项目运行IsolationApplication.java里的main方法就行。启动成功后控制台会输出SpringBoot的Logo和 Tomcat started on port(s): 8080 这样的日志。然后启动前端cd isolation-web npm run serve启动成功后浏览器访问http://localhost:8081。注意前端默认端口是8081后端是8080两者不同端口必然会产生跨域问题所以需要在后端配置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); } }这里有个细节特别注意如果使用了拦截器拦截器处理OPTIONS请求时会直接返回401导致跨域预检失败。所以我的登录拦截器中对 OPTIONS 请求做了放行处理if (OPTIONS.equalsIgnoreCase(request.getMethod())) { return true; }这个坑非常经典很多人前后端联调时明明跨域配置没有错但请求就是进不来十有八九是拦截器没放行OPTIONS预检请求。5. 开发中遇到的典型问题与排查思路5.1 高版本依赖引发的兼容性故障做这个项目的过程中我踩了不少坑最典型的要数SpringBoot版本与MyBatis集成时的兼容性问题。网上很多教程用的是SpringBoot 2.x早期版本使用较新版本的SpringBoot比如2.7.x或3.x集成MyBatis时可能会遇到启动报错或依赖下载卡住的问题。例如我一开始用的SpringBoot 2.7.7版本引入MyBatis的starter用的是mybatis-spring-boot-starter2.3.1版本启动项目时一直报Failed to configure a DataSource的错误。排查下来发现问题核心是SpringBoot 2.7.x对数据源自动配置的机制做了调整不再自动从spring.datasource读取部分配置。但问题的真实根源在于我引入的MyBatis starter版本较低与SpringBoot 2.7的自动化配置不兼容。解决办法很直接——把mybatis-spring-boot-starter升级到 3.0.0 以上版本问题立刻消失。或者反方向把SpringBoot降级到2.5.x保持整体版本梯队统一。经验遇到SpringBoot相关依赖问题时先确认版本兼容矩阵。不要盲目升级到最新版很多基础框架的最新版并未及时适配SpringBoot的新版本。国内技术社区最稳定的组合仍然是SpringBoot 2.7.x MyBatis starter 3.0.x。5.2 MyBatis动态SQL报错与结果映射问题另一个高频问题出现在MyBatis动态SQL的编写上。很多初学者会写出这样的代码select idselectByCondition resultTypeIsolationPerson SELECT * FROM isolation_person WHERE 11 if testname ! null and name ! AND name LIKE %${name}% /if /select这里用了${name}做字符串拼接MyBatis执行时直接替换为字符串不会加单引号实际生成的SQL会变成AND name LIKE %张三%还好但如果用户传入的值含单引号就会引发SQL注入风险。正确做法是用#{}参数占位if testname ! null and name ! AND name LIKE CONCAT(%, #{name}, %) /if还有resultType映射的问题。如果你的实体类属性和数据库字段命名不一致又没开map-underscore-to-camel-case这个配置查出来的数据会发现createTime字段是null。排查的时候可以直接在控制台打印SQL执行结果如果SQL能查到数据但Java对象里字段为null大概率就是驼峰映射没开或者没写resultMap。5.3 前端运行时的Node版本兼容问题前端部分遇到的最多的坑就是Node.js版本问题。Vue CLI 4.x 项目在Node 17以上的版本里运行时经常会报Error: error:0308010C:digital envelope routines::unsupported这个报错的原因是Node.js 17开始用OpenSSL 3.0而webpack 4还在用OpenSSL 1.1的算法。解决方案有两个一是把Node版本降到16.x二是在package.json里设置环境变量scripts: { serve: set NODE_OPTIONS--openssl-legacy-provider vue-cli-service serve, build: set NODE_OPTIONS--openssl-legacy-provider vue-cli-service build }我个人推荐直接用nvm管理Node版本需要哪个版本就切换哪个版本一劳永逸。装多个Node版本的环境中nvm是最好用的版本管理工具Windows下用nvm-windowsmacOS/Linux下用nvm。5.4 常见问题速查表整理了一份这个项目开发过程中最常遇到的问题对照表方便大家直接对照排查问题现象可能原因处理办法启动报Failed to configure a DataSource数据源配置错误或MyBatis版本不兼容检查application.yml的url/用户名/密码确认依赖版本匹配SQL打印到控制台但查询结果为空表名或字段名错误或resultType映射不对对比数据库字段与Java实体属性命名开启驼峰映射中文插入数据库显示问号数据库字符集不是utf8mb4建库时指定DEFAULT CHARSETutf8mb4连接串加characterEncodingutf8前端请求接口返回401Token未传或Token过期检查Axios请求拦截器是否设置Authorization头Token有效期是否太短跨域请求失败CORS配置缺失或拦截器拦截OPTIONS请求后端配置CorsConfig拦截器放行OPTIONS预检请求vue: OutOfMemoryErrorNode内存不足设置NODE_OPTIONS--max-old-space-size4096Maven下载依赖卡住中央仓库网络慢切换到阿里云镜像Element UI按钮点击无响应Vue实例未正确挂载组件检查main.js中是否正确注册了ElementUI6. 细节优化与个人经验总结6.1 系统安全性和性能的进阶优化如果你打算把这个项目用于毕业设计或者写到简历里有几个地方的优化能明显拉开和其他同学的差距。密码加密方面虽然项目里用MD5加盐做加密但在描述项目时建议主动提一下后续可升级为BCrypt加密。BCrypt是自适应哈希算法能自动加盐并且内置了防止暴力破解的强度因子。Spring Security里默认就是用它实际开发中更主流。防SQL注入这块除了前面说的MyBatis用#{}避免SQL注入之外还有一点容易被忽略。有些人在做排序功能时会把排序字段直接拼到SQL里这样也是危险的。因为排序字段一般没办法用占位符所以后端必须做白名单校验// 允许排序的字段白名单 private static final ListString ALLOWED_ORDER_COLUMNS Arrays.asList(create_time, start_date, end_date, temperature); if (!ALLOWED_ORDER_COLUMNS.contains(orderByColumn)) { throw new BusinessException(非法的排序字段); }接口幂等性方面在解除隔离这种操作上要考虑重复提交的问题。我的做法是前端在点击按钮后立刻置为loading状态禁用按钮后端调用解除接口时加一层状态校验——只有当前状态是隔离中的记录才允许执行解除操作这就从两个层面防止了重复操作。数据导出性能方面Excel导出用的是EasyExcel库它在处理大数据量导出时不会把全部数据加载到内存而是流式读取数据库分批写入文件。如果你的系统隔离人员数量很大全量加载到内存再生成文件的做法很容易内存溢出。6.2 为什么推荐你完整地把这个项目做一遍聊点实际经验。很多初学者面对这类系统容易陷入一个误区到处找现成的源码拿来直接跑通就完事代码一行没写业务逻辑一概不知。这样的学习效果几乎为零面试官随便问一个你的项目里权限是怎么做的你就卡壳了。我的建议是拿到一套完整的项目源码之后按顺序做三件事第一读懂表结构设计。把每张表、每个关键字段的作用搞清楚。为什么要给status字段加索引为什么health_record要有联合唯一索引把这些想通了数据库设计能力会有一个质的提升。第二手写一遍核心流程。不要复制粘贴从零开始自己写一遍登录接口、隔离人员新增接口、健康打卡接口。这三个接口覆盖了参数校验、业务逻辑、事务管理、状态切换等所有核心知识点写完之后你对SpringBoot MyBatis的运作机制会理解透彻很多。第三在原有基础上加一个功能。比如加一个隔离点房间管理功能将隔离人员分配到具体房间这样势必要新增房间表、修改隔离人员表、增加分配逻辑整个过程中你会强迫自己思考表结构设计、接口设计、页面交互。这个过程比单纯照抄源码有价值得多。提示学习技术最忌讳看会了的错觉。代码只有跑起来、改起来、报错起来才是真正被吸收了。6.3 下一步功能扩展建议系统做完了之后还可以继续向前走这里有三个我觉得性价比很高的扩展方向大家可以根据自己的精力来选择增加消息通知功能当隔离人员临近解除日期时系统自动发送短信或邮件通知工作人员。技术实现可以用Spring Boot自带的 JavaMailSender 集成QQ邮箱或企业邮箱也可以接入阿里云短信服务。引入Redis缓存把热点数据比如统计大屏上的在隔人数缓存到Redis设置5分钟过期降低数据库压力。面试时能讲清楚缓存更新策略和穿透、击穿、雪崩的区别是个明显的加分项。用Vue 3 TypeScript重构前端如果你打算之后走纯前端方向可以试试用Vue 3的组合式APIComposition API把列表页和表单页重写一遍对比Vue 2选项式API在逻辑复用上的优劣体验会非常直观。6.4 最后的几点真心话项目写到这基本就圆满了最后分享几点我自己的体会。做这类管理系统技术本身并不复杂最大的难点在于业务的梳理和边界感的把控。什么功能该做、什么功能不该做、表结构的设计能不能支撑未来的扩展这些问题比写代码本身更考验功力。我在做这个系统的过程中反复改了两次数据库设计第一次就是功能贪多加了审核流程和排班管理结果发现和核心的隔离管理业务纠缠在一起代码写得别提多别扭。后来砍掉那些功能系统反而清爽好用了。所以我想说的第一点是需求分析阶段多花点时间把边界想清楚后面会省很多事。第二点是关于学习的。我见过很多朋友学SpringBoot时在B站刷了一个又一个视频笔记记了一本又一本但到自己写项目的时候依然无从下手。其实最有效的学习方式很简单——就是动手做。哪怕你从最简单的登录注册做起会跑通第一个接口的那一刻很多东西就通了。技术这东西不是看会的是写会的。希望这篇文章能帮你顺利把项目跑起来。如果你在搭建或者写代码的过程中遇到任何问题欢迎随时交流这套系统的技术栈很经典遇到的问题也大多是共性的我们可以一起讨论解决。
返回列表