Spring Boot智慧校园综合服务平台设计与全流程实践
上线一个校园综合服务平台的完整流程从架构选型到部署落地我踩了不少坑也总结了不少经验。这篇文章就围绕“Spring Boot智慧校园综合服务平台”的设计与实现来写不整虚的全是实操总结。这个项目说白了就是把学校里原本散落的业务——课程查询、实验室预约、评优评先、后勤报修、请假审批、文件下发——全部集中到一个平台里统一身份认证、统一权限控制、统一流程审批。技术上选Spring Boot 2.7.x作为核心框架前端用Vue写管理端和用户端数据库MySQL配Redis缓存流程审批集成Flowable工作流引擎。整套系统我从零搭了一遍过程中折腾过版本冲突、自动建表失败、大文件上传超时、部署时资源映射404等各种问题。这篇文章把这些都复盘一遍给想做同类项目或者正在做毕业设计的同学一个完整的参考。1. 项目整体设计与技术选型思路1.1 大学校园场景下系统最核心的需求是什么先说需求。大学的业务系统有个特点就是“麻雀虽小五脏俱全”。学生要查课表、查成绩、选课、预约实验室、提交请假申请、报名评优老师要审批请假、录入成绩、管理实验设备后勤要处理报修单、管理物资、发布通知辅导员要在评优评先里做资格审核。这些流程看起来不复杂但每个流程都有状态流转而且不同角色对同一条数据的操作权限完全不一样。我当时做需求梳理的时候把整个平台定位成“一个中心、三大门户”以统一身份认证为中心面向学生、教师、管理员三个角色门户。每个门户下面挂不同的业务模块比如学生端有成绩查询、实验室预约、请假申请、评优报名教师端有成绩管理、审批中心、实验课程安排管理端负责用户管理、权限分配、流程配置、数据统计。这套设计逻辑很直接先解决“身份信任”的问题再解决“流程审批”的问题最后才是“业务数据”的展示和维护。如果一上来就铺开十几个功能模块代码会写成一锅粥权限也会失控。所以我在设计阶段就强制把模块边界划清楚每个模块只跟 user_id 和 role_id 打交道模块之间不做直接调用全部走统一的服务接口。1.2 为什么选Spring Boot而不是微服务或者PHP选型的时候我认真对比过三套方案Spring Cloud微服务、Spring Boot单体、PHP或Python的快糙猛方案。校园综合服务平台有个现实条件并发量不大高峰期就是选课那几天平时在线用户几百人但业务流程复杂、角色多、状态流转多。在这种场景下微服务的注册中心、网关、链路追踪、分布式事务全都是负担运维成本远超收益。Spring Boot单体架构配合模块化分包是最合适的选择。Spring Boot的自动装配机制省去了大量XML配置内嵌Tomcat让部署变成一条java -jar命令Starter机制让mybatis、redis、security这些组件一行依赖就接进来。再加上Spring生态本身对事务管理、缓存抽象、安全框架的支持非常成熟写业务逻辑时基本不用操心底层基础设施。还有一个重要考量团队协作。这个项目如果是一个小组在开发单体架构的分包规范做得好的话A同学写认证模块、B同学写实验室预约模块、C同学写流程审批模块代码冲突会很少。微服务的服务拆分和接口联调成本对一个校级平台来说完全是浪费。1.3 单体架构下怎么保持代码不烂单体架构最大的风险是“后期腐烂”。我见过很多单体项目三个月之后一个Controller上千行Service层互相调用绕成蜘蛛网。为了避免这个局面我用了严格的分层和领域分包策略。基础包结构是这样的com.campus.platform ├── common // 通用类返回结果、异常处理、工具类 ├── config // 配置类Redis、MyBatis、Security、Flowable ├── security // 认证授权JWT工具、登录过滤器、权限注解 ├── modules // 业务模块按领域划分 │ ├── auth // 登录注册模块 │ ├── course // 课程成绩模块 │ ├── lab // 实验室预约模块 │ ├── repair // 后勤报修模块 │ ├── review // 评优评先模块 │ └── workflow // 流程审批模块对接Flowable每个业务模块内部再拆controller、service、mapper、entity、dto、vo。我给自己定了一条规矩Controller里不允许出现业务逻辑只做参数接收和结果包装Service里不允许出现SQL语句只调Mapper接口跨模块调用只允许通过Service接口不允许直接操作别的模块的Mapper。这条规矩在Code Review的时候反复强调过程中虽然效率打了折扣但代码质量保持得不错。2. 核心功能模块设计与关键实现2.1 统一认证与权限控制用户认证我选的是Spring Security JWT。为什么不用Session因为平台做了前后端分离Vue前端和Spring Boot后端分开部署Session的跨域处理和集群会话同步都比较麻烦JWT天然适合这种场景。JWT的流程不复杂用户登录成功后后端生成一个包含用户ID、用户名、角色列表的Token返回给前端前端每次请求在Header里带上Authorization: Bearer 后端写一个OncePerRequestFilter每个请求都校验Token合法性和有效期然后把用户信息塞进SecurityContext里。核心代码思路如下Component public class JwtAuthenticationFilter extends OncePerRequestFilter { Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String token getTokenFromRequest(request); if (StringUtils.hasText(token) jwtTokenProvider.validateToken(token)) { Long userId jwtTokenProvider.getUserId(token); ListString roles jwtTokenProvider.getRoles(token); // 加载用户权限放入SecurityContext UsernamePasswordAuthenticationToken authentication new UsernamePasswordAuthenticationToken(userId, null, roles.stream().map(SimpleGrantedAuthority::new).toList()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } chain.doFilter(request, response); } }权限模型是标准的RBAC用户表、角色表、菜单权限表、用户-角色关联表、角色-菜单关联表。控制粒度上接口级权限用Spring Security的PreAuthorize注解菜单级权限由前端根据后端返回的权限码渲染。实际操作中我踩过一个坑PreAuthorize默认是方法运行时判断如果开启了全局方法安全但没配CGLIB代理某些情况下注解会不生效。解决方式是启动类上明确加EnableGlobalMethodSecurity(prePostEnabled true)。2.2 实验室预约模块与全流程闭环实验室预约是智慧校园里的高频业务。学生要预约实验室老师要审核预约实验室管理员要管理实验室的基本信息和使用状态。最初我做的第一版预约逻辑极其简单学生填一个时间段后台查一下有没有冲突没有就预约成功。后来发现完全不行因为实际业务里牵涉到“课程优先、课余开放”“单次时长限制”“同一实验室同一时段不能双约”这三条硬规则。最终的模型设计是这样lab表实验室基础信息包括编号、名称、位置、容纳人数、负责人。lab_device表实验设备清单一个实验室对应多台设备。lab_reservation表预约记录包含lab_id、user_id、start_time、end_time、status、audit_remark。预约时的核心逻辑是冲突检测。用SQL做重叠判断比在Java里遍历列表靠谱得多SELECT COUNT(*) FROM lab_reservation WHERE lab_id #{labId} AND status IN (APPROVED, PENDING) AND start_time #{endTime} AND end_time #{startTime}这条SQL思路很直白要插入的预约时间段和已有的预约时间段有交集就说明冲突。交集判断就是“新的开始时间小于旧的结束时间且新的结束时间大于旧的开始时间”。这个逻辑我最初写成两个独立判断结果漏掉了跨天预约的情况后来改成这样一条SQL才堵住漏洞。审核状态我用的是字符串枚举PENDING、APPROVED、REJECTED、CANCELLED因为状态是会变的用数字后面还要维护映射直接存字符串更直观。预约审批我一开始是自己写状态字段手动改后来引入了Flowable把审批流程抽出去了。后面专门开一节讲工作流的事情。2.3 评优评先与流程审批模块评优评先是另一个典型的多角色协同业务。班主任发起评选学生在线报名并提交材料辅导员审核资格学工办终审每一级都可能退回补充材料最后还要公示。这种“多级审批可退回”的业务用状态字段硬写会写得非常痛苦不仅状态多还要记录每一步的操作人和操作时间更重要的是流程可能随时调整。我最终决定引入Flowable工作流引擎来统一处理平台里的审批类业务。Flowable是流程引擎市面上还有Activiti两者同源Flowable社区活跃度更高对Spring Boot的集成也更友好。核心思路是把审批流程做成BPMN 2.0流程定义部署到Flowable引擎里业务代码通过流程实例ID关联业务数据。以评优评先为例我画了一条简单的流程process idreviewProcess name评优评先审批流程 startEvent idstart/ userTask idclassTeacherAudit name辅导员审核 flowable:assignee${classTeacher}/ userTask idofficeAudit name学工办审核 flowable:assignee${officeUser}/ endEvent idend/ sequenceFlow idflow1 sourceRefstart targetRefclassTeacherAudit/ sequenceFlow idflow2 sourceRefclassTeacherAudit targetRefofficeAudit/ sequenceFlow idflow3 sourceRefofficeAudit targetRefend/ /process注意这里我用的是fix assignee实际项目中人员变量是从业务表里查出来的在启动流程实例的时候作为变量传入这样每一步谁审核都是动态的。流程启动后业务表里存一个process_instance_id需要查“我待办的申请”时直接调Flowable的TaskService查询该用户的任务再反查业务表。这部分的体验很关键Flowable自带的数据表和业务表混在一起会让数据库显得很乱但千万不能去动Flowable的ACT_开头的表版本升级或者流程查询都会出问题。我建议单独给Flowable建一个数据库账号或者单独schema和业务库隔离开。3. 关键技术的实操细节与性能优化3.1 MyBatis Plus自动填充和多表联查的踩坑经验平台里大部分单表CRUD我用的MyBatis Plus。这个框架给我的最大帮助是省掉了大量重复的XML文件和繁琐的BaseMapper实现。create_time、update_time这种字段每个表都有如果每张表都在插入和更新时手动set不仅重复而且容易漏。MyBatis Plus提供了MetaObjectHandler接口做自动填充Component public class AuditMetaObjectHandler implements MetaObjectHandler { Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, createTime, LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } }实体类上对应字段加TableField(fill FieldFill.INSERT)和TableField(fill FieldFill.INSERT_UPDATE)注解插入和更新就自动带上了时间。注意strictInsertFill的第三个参数是字段类型我用的是LocalDateTime如果实体里用的是Date类型这里类型不一致会导致填充失败且不报错排查起来比较费劲。多表联查是MyBatis Plus相对弱鸡的地方一对多、多对多场景下我选择直接用XML自定义SQL不用它自带的Wrapper联表否则SQL可读性会很差。比如课程成绩查询要join学生表、课程表、成绩表我就在CourseScoreMapper.xml里写原生的JOIN语句把需要的字段查出来映射到VO对象里。MyBatis Plus的Wrapper做多表查询写起来很痛苦性能也很难控制。3.2 Redis缓存怎么用才不对业务造成干扰平台里的热门数据比如公告列表、课表查询、实验室空闲状态是典型的读多写少场景直接用MySQL扛会遇到两个问题一是数据库连接被占满二是查询延迟高。我引入Redis做了一层缓存。缓存策略我用的是“先更新数据库再删除缓存”的模式。为什么不是先删缓存再更新数据库因为并发情况下可能读到旧数据。为什么不是更新数据库后直接更新缓存因为如果缓存里的数据结构复杂更新逻辑容易出错直接删除让下次查询回填是最稳妥的。Spring Boot里接Redis很简单引入spring-boot-starter-data-redis依赖配置一下连接信息然后我封装了一个RedisCacheUtil工具类处理JSON序列化。这里有个细节值得说默认的JdkSerializationRedisSerializer会把对象序列化成二进制存到Redis Desktop Manager里看跟乱码一样排障很不方便。我配置了GenericJackson2JsonRedisSerializer同时在类上加JsonIgnoreProperties(ignoreUnknown true)防止反序列化时字段对不上报错。还有一个重要的坑缓存穿透。如果请求查的数据在MySQL里也不存在那么缓存里永远不可能有数据请求每次都打到数据库。我的方案是在查不到数据时往缓存里放一个空值占位过期时间设短一些比如三分钟防止恶意或异常请求把数据库打垮。3.3 大文件上传下载的可靠方案校园平台里经常需要上传文件特别是评优材料或者课程资料一个PDF几十MB很常见后来教务处还提了上传大视频课件的需求直接扔给传统上传接口肯定不行。Spring Boot默认单次请求大小限制是1MB改配置可以放宽但一次传2GB内存直接爆掉。我当时的方案是分片上传加断点续传前端把文件切成5MB一个分片每个分片带一个uploadId前端生成的UUID、当前分片序号、总分片数。后端收到分片后写到临时目录文件名是 uploadId_分片序号。所有分片传完后前端调用一个merge接口后端按序号顺序合并成大文件存到指定的存储目录再把文件元数据写入数据库。核心接口大概长这样PostMapping(/upload/chunk) public Result uploadChunk(RequestParam(file) MultipartFile file, RequestParam(uploadId) String uploadId, RequestParam(chunkIndex) Integer chunkIndex) { String chunkPath fileStorageProperties.getChunkDir() / uploadId _ chunkIndex; file.transferTo(new File(chunkPath)); return Result.success(); } PostMapping(/upload/merge) public Result mergeChunks(RequestParam(uploadId) String uploadId, RequestParam(fileName) String fileName) { File chunkDir new File(fileStorageProperties.getChunkDir()); File[] chunks chunkDir.listFiles((dir, name) - name.startsWith(uploadId _)); // 按分片序号排序后逐个写入目标文件 try (BufferedOutputStream bos new BufferedOutputStream( new FileOutputStream(targetFile))) { // 合并逻辑 } // 删除临时分片 }分片上传能应对对象存储扩展后续如果学校要接入MinIO或者阿里云OSS只需要把落盘逻辑替换成对象存储的分片接口即可。这里要注意一个点merge接口需要做幂等处理如果前端重试合并请求后端不能生成两份文件。我用的方案是目标文件存在时直接返回成功同时用数据库的唯一索引做兜底。下载方面大文件如果直接返回文件流会在网关或Tomcat层占大量内存。我的方案是后端返回文件的下载URL由前端用流式下载然后同时在代码里设置Content-Disposition响应头来实现文件名中文编码。4. 数据库设计、自动建表与多环境配置4.1 核心业务表的建模思路整套系统的表加起来能到60张以上除了业务表还有流程引擎的ACT_表。核心核心的表其实不多我把它们分成四类组织用户类用户、角色、权限、班级、学院、教务类课程、课表、成绩、选课、流程类流程定义、流程实例、任务记录、业务类预约、报修、评优、公告。设计的时候有几个原则我是坚决执行的。第一每张业务表必须有主键主键用数据库自增ID第二每张表必须有create_time和update_time字段天塌下来都要有不然后期出了问题没法追溯数据变更时间第三所有表名、字段名用下划线风格避免MySQL字段名和关键字冲突比如desc、order这种都要加反引号第四逻辑删除优先于物理删除用deleted字段标记方便数据找回和审计。举一个实验室预约表的设计CREATE TABLE lab_reservation ( id bigint(20) NOT NULL AUTO_INCREMENT, lab_id bigint(20) NOT NULL COMMENT 实验室ID, user_id bigint(20) NOT NULL COMMENT 预约人ID, schedule_date date NOT NULL COMMENT 预约日期, start_time datetime NOT NULL COMMENT 开始时间, end_time datetime NOT NULL COMMENT 结束时间, purpose varchar(255) DEFAULT NULL COMMENT 用途说明, status varchar(20) NOT NULL DEFAULT PENDING COMMENT PENDING/APPROVED/REJECTED/CANCELLED, audit_user_id bigint(20) DEFAULT NULL COMMENT 审核人ID, audit_time datetime DEFAULT NULL COMMENT 审核时间, audit_remark varchar(255) DEFAULT NULL COMMENT 审核意见, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, deleted tinyint(1) NOT NULL DEFAULT 0, PRIMARY KEY (id), KEY idx_lab_id (lab_id), KEY idx_user_id (user_id) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4 COMMENT实验室预约表;索引设计上我坚决遵守“最左前缀”原则。比如按lab_id和schedule_date查询预约冲突索引就建(lab_id, schedule_date, start_time)覆盖冲突检测的查询路径。4.2 表不存在自动建表的懒人方案这个功能是热词里高频出现的我在项目里也确实遇到了。学校参数设置多环境多开发库、测试库、生产库的表结构经常漂移。后来我研究了一种相对靠谱的方案基于Flyway做数据库版本管理。最开始我想用MyBatis Plus的自动建表功能网上有不少方案是用MyBatis的DDL执行器。实操下来问题很多MySQL方言兼容、字段类型映射、索引自动创建这些都不可控更重要的是表的演进没法记录。Flyway的方案就科学多了在resources/db/migration目录下放V1__init.sql、V2__add_lab_reservation.sql这样按版本排序的脚本Spring Boot启动时自动执行未执行过的脚本。这样既解决了“表不存在自动建表”的痛点又保证了多环境表结构一致。实际操作中要注意Flyway的校验规则已经执行过的脚本严禁修改哪怕只是改一个注释也会导致checksum校验失败应用直接启动不了。正确做法是新建一个Vx__xxx.sql脚本去做增量变更。我们团队刚开始不习惯这个约束经常有人改旧脚本导致启动失败后来习惯了反而觉得这个机制是个保护伞它逼着你规范管理表结构变更。4.3 多环境配置和敏感信息加密Spring Boot的application.yml支持多环境配置我把公共配置放application.yml每个环境单独一个文件# application.yml 公共配置 spring: profiles: active: dev # application-dev.yml spring: datasource: url: jdbc:mysql://localhost:3306/campus?useUnicodetruecharacterEncodingutf-8 username: root password: 123456 redis: host: 127.0.0.1 port: 6379生产环境的数据库密码、Redis密码不能明文写在配置文件里。我用的方案是Jasypt加密。引入jasypt-spring-boot-starter之后配置里的密码变成ENC(密文)格式应用启动时通过环境变量传入解密密钥。spring: datasource: password: ENC(Xr6K8dF2vBq0nX5Vd3Zt8g)解密的密钥通过启动命令传入java -jar campus-platform.jar --jasypt.encryptor.password${JASYPT_SECRET}这样即使配置文件泄露数据库密码还是安全的。这个细节在毕业设计和企业级项目里都很加分但很多教程都不会提。5. 部署上线与性能优化实战5.1 用Docker包一套可重复的部署方案这个项目我最终用Docker Compose做编排。一个完整的部署环境包括MySQL、Redis、应用服务三个容器。写一个school.yml的compose文件version: 3.8 services: mysql: image: mysql:8.0 container_name: campus-mysql environment: MYSQL_ROOT_PASSWORD: root123456 MYSQL_DATABASE: campus ports: - 3306:3306 volumes: - /data/mysql:/var/lib/mysql redis: image: redis:7-alpine container_name: campus-redis ports: - 6379:6379 campus-app: build: context: . dockerfile: Dockerfile container_name: campus-app depends_on: - mysql - redis environment: SPRING_PROFILES_ACTIVE: prod JASYPT_ENCRYPTOR_PASSWORD: your-secret-here ports: - 8080:8080应用的Dockerfile我用多阶段构建先用Maven镜像编译打包再用JRE镜像运行这样最终的镜像只有一百多MBFROM maven:3.8-openjdk-17 AS builder WORKDIR /build COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn package -DskipTests FROM openjdk:17-jdk-slim WORKDIR /app COPY --frombuilder /build/target/campus-platform.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]部署上线后的问题是静态资源。Vue前端构建后是一堆js/css文件如果和后端分开部署在Nginx上就干净了。初始时我把Vue的dist目录直接扔到Spring Boot的static目录下用同一个端口跑。这样省了Nginx配置但问题是前端的路由和后端接口混在一起配置拦截器的时候容易误伤静态资源。实际验证下来还是老老实实拆开部署前端用Nginx托管并做API反向代理前后端通过API路径区分互不干扰。5.2 接口响应慢和并发问题排查部署完测试过来反馈说成绩查询接口要三四秒这肯定不正常。我查了应用日志发现是N1查询问题查询成绩列表时先查成绩表然后逐条查询学生表、课程表、班级表20条成绩数据就发了40多次SQL。解决办法是代码里用join一把查出来或者用MyBatis Plus的批量映射。把Mapper的XML改成JOIN查询后接口耗时降到500毫秒以内。第二个并发问题是选课高峰期的数据库连接池打满。HikariCP默认连接池大小是10选课开始时所有学生同时请求10个连接显然不够。这个问题不是无脑调大连接数就行的机器内存有限连接数越大每个连接分配的内存越多。我的处理是结合Redis预扣库存选课请求先进Redis做名额扣减真正写数据库的操作通过异步队列处理把峰值流量削平。5.3 日志规范与监控告警日志是排查问题最重要手的段。一开始我项目里的日志全是System.out.println部署到服务器上根本找不到而且System.out打印到控制台不走日志框架的分级和格式。后来全部换成SLF4J门面加Logback实现沿用Spring Boot默认的logback-spring.xml配置按天滚动保留30天。我还在代码里做了一件事所有接口的入口和出口都打一条包含耗时、用户ID、请求路径的日志方便链路追踪。留下的日志规范是这样的2025-01-12 14:23:45.123 INFO [http-nio-8080-exec-3] c.c.platform.common.aspect.LogAspect : [接口] POST /api/lab/reserve [用户] 10023 [耗时] 156ms监控方面没有搭Prometheus那套重型方案而是用Spring Boot Actuator暴露健康检查端点然后在Docker Compose里加了一个Uptime Kuma监控容器定时探测健康检查接口服务挂了就发邮件告警。这套轻量方案对中小项目完全够用。6. 常见问题排查与避坑锦囊6.1 后端启动失败的三个高频原因项目写完后在部署环境上最容易遇到的就是启动报错。第一类问题是数据库连接失败报错信息通常是“Cannot create PoolableConnectionFactory”。除了检查连接地址、账号密码之外最容易漏的是数据库没建MySQL里没有campus这个database应用启动时不会自动创建数据库。我写了一个init.sql第一次部署时手动执行初始化建库和建表后面交给Flyway。第二类是Redis连接失败。Spring Boot 2.x中如果配置了Redis但用不到其实不影响启动但如果引入了spring-boot-starter-data-redis且缓存注解用到了RedisTemplate连接不上会直接抛异常。常见就是Redis密码没配对或者是protected-mode开启了拒绝外部连接。第三类是端口被占用8080被别的进程占了或者服务器防火墙没放行。Linux下排查命令很熟悉了netstat -tunlp | grep 8080 firewall-cmd --list-ports这操作虽然简单但是最容易卡住新人。6.2 Flowable集成时表结构和版本冲突Flowable整合进项目的过程中我踩过一个版本坑。pom里引入flowable-spring-boot-starter时它的版本默认可能依赖Spring Boot的某个特定版本。Flowable 6.x对应Spring Boot 2.x没问题如果项目用的是Spring Boot 3.x那必须用Flowable 7.x因为Spring Boot 3基于Jakarta命名空间老版本依赖的javax.*类在运行时直接NoClassDefFoundError。这个问题的排查过程很有代表性应用启动时控制台打了几行WARN但是没失败首次访问流程相关接口时才报ClassNotFoundException。原因是Flowable的自动配置懒加载了某些类。解决方式也很直接把Flowable版本升到7.x并保持Spring Boot版本在当前维护的LTS范围内。6.3 资源映射404和跨域问题怎么一次到位前端资源404这个事十有八九是改了resources配置没引对路径。Spring Boot 2.x时的静态资源默认映射是classpath:/META-INF/resources/、classpath:/resources/、classpath:/static/、classpath:/public/。如果自定义了WebMvcConfigurer一定要记得调用super.addResourceHandlers(registry)否则默认映射被覆盖dist目录下的index.html直接404。跨域问题则是前后端分离绕不开的坎。我在后端写了一个全局CorsFilter配置允许指定的前端地址跨域并支持携带凭证Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOrigin(http://localhost:5173); config.addAllowedMethod(*); config.addAllowedHeader(*); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }注意如果项目里同时配了Spring SecurityCORS配置一定要放在Security的过滤器链之前否则Security拦截器先返回401跨域响应头没加上前端拿到的错误信息看不出来是跨域还是认证失败。6.4 大文件上传常见问题和JWT过期处理文件上传常见的问题有几个第一个是Spring Boot默认的spring.servlet.multipart.max-file-size只有1MB会导致超过大小的请求在进入Controller之前就被拦截。这个配置要显式写大同时也要在Nginx层配client_max_body_size两边都要放行。第二个问题是分片合并时文件名中文乱码。MultipartFile的getOriginalFilename在不同浏览器下的编码行为不一样特别是上传中文文件名时很容易乱码。我在后端做了统一处理把文件名重新用URLDecoder解码存储用UUID重命名原始文件名只存数据库避免文件系统和HTTP编码的问题。JWT过期这个需要前端配合处理。我的方案是accessToken设2小时有效期refreshToken设7天。前端拦截到401响应时不直接跳登录页而是先调用刷新接口换新的accessToken然后再重放原来的请求。这个逻辑虽然不复杂但是能显著改善用户体验用户不用每天反复登录。7. 这套架构还能怎么扩展整套平台做完之后后续扩展空间其实很大。如果学校要对接统一身份认证CAS协议或企业微信Spring Security本身支持OAuth2/CAS的适配认证层可以平滑升级。如果要做数据大屏展示校园运行状态课程预约数据、报修数据、评优数据都按规范存下来了写个聚合查询接口配合前端可视化组件就行。要是以后并发量真的大到单体撑不住了也可以按业务模块把课程、审批、后勤拆出去做微服务因为前期模块边界已经划清楚了拆分的成本是可控的。我在实际做这个项目过程里最大的体会是技术选型和架构设计不能追新而是要算清楚“场景下的人力成本和维护成本”。Spring Boot单体加模块化、标准化的数据库设计、成熟的流程引擎、规范的发布流程这套组合对校园级平台来说是在复杂度、成本和可靠性之间最平衡的方案。与其纠结是不是微服务不如先把业务边界和权限模型设计扎实。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →