news 2026/10/7 3:05:22

SpringBoot智慧校园平台实战:从架构设计到部署避坑全记录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot智慧校园平台实战:从架构设计到部署避坑全记录

做智慧校园这个方向之前,我其实已经带团队接过不少类似的管理系统项目,但很多都是“小切口”式的:做一个失物招领、做一个课程表、做一个报修单。真正把“综合服务平台”这个概念落地,SpringBoot才是我绕不开的选择。这篇不是来科普SpringBoot基础语法的,而是把我自己从框架选型、数据库建模、权限设计、功能拆解,到最终打包部署、整理文档和答辩材料这一整条链路踩过的坑和最终沉淀下来的方案,系统地讲一遍。如果你正在做毕设、或者公司想快速搭一套内部校园服务平台,这篇文章应该能让你少走至少两周的弯路。

1. 项目整体定位与架构设计思路

1.1 先想清楚:这个平台到底要解决什么问题

很多同学一上来就急着建表、写接口,结果做着做着发现模块之间互相打架,或者用户角色根本分不清。我一开始定的目标是:面向高校师生,提供一个统一的线上服务入口,把教务、生活、后勤、通知这些碎片化场景聚合到一个平台里。

具体来说,平台要支撑三类用户:

  • 学生端:查课表、查成绩、报修、预约场地、在线缴费、失物招领、接收通知。
  • 教师端:课程管理、成绩录入、监考安排、审批学生申请。
  • 管理员端:用户管理、系统配置、数据统计、内容发布、工单分派。

从复杂度来看,这本质是一个多角色、多模块、高扩展性的业务系统。选型的时候我考虑过用若依这样的快速开发框架,但后来弃了,原因很简单:若依确实快,但代码耦合度高,且很多底层逻辑不适合二次深度定制,尤其是权限这块,毕设或者商业项目里导师或者甲方一旦追问底层细节,你很难讲清楚。

最终我确定的技术栈是:

  • 后端:SpringBoot 2.7.x + MyBatis-Plus + Spring Security + JWT
  • 缓存:Redis(Token存储、热点数据缓存)、Caffeine(本地缓存)
  • 数据库:MySQL 8.0(InnoDB引擎,utf8mb4)
  • 文件存储:本地磁盘 + Nginx静态映射
  • 前端:Vue 3 + Element Plus + Axios(这里前端不是主角,但联调逻辑我会讲)
  • 接口文档:Knife4j(基于Swagger的增强)

这套组合的好处很直接:SpringBoot提供自动化配置和生态支持,MyBatis-Plus解决单表CRUD效率问题,Spring Security做安全管控,JWT做无状态认证,Redis扛住高频读取。整个过程不需要引入太重的东西,每一层都可以讲清楚原理。

1.2 分层架构与模块边界划分

这个项目我没有用微服务,没必要。一个高校的综合服务平台,初期用户量撑死几千并发,单体应用完全够用,关键是模块边界要清晰,保证后续可以平滑拆分。

我采用的是标准的分层架构:

  • Controller层:只做参数接收、调用Service、返回统一结果集
  • Service层:业务逻辑处理,事务控制在这里
  • Mapper层:基于MyBatis-Plus的BaseMapper,复杂查询用XML/注解SQL
  • common模块:统一返回体、全局异常、常量定义、工具类
  • config模块:安全配置、Redis配置、跨域配置、文件上传配置

模块划分上我按业务域拆包:

com.campus.platform ├── common // 通用工具、返回体 ├── config // 配置类 ├── security // JWT过滤器、权限注解 ├── module │ ├── auth // 认证模块 │ ├── user // 用户中心 │ ├── course // 教务课程 │ ├── repair // 报修模块 │ ├── booking // 场地预约 │ ├── payment // 缴费模块 │ ├── notice // 公告通知 │ └── statistics // 数据看板

这样分包有一个非常实际的好处:每个模块都是独立的小项目,写代码的时候不会在几十个Controller里迷路。我曾经接过一个项目,所有Controller堆在一个包里,每次改需求光找文件就要五分钟,那种痛你能懂吧。

1.3 为什么认证方案选中JWT而不是Session

这是导师和面试官问得最多的问题。Session依赖服务端存储,集群环境下就得引入Session共享,比如Spring Session + Redis,麻烦不说,还有CSRF防护的问题。智慧校园这个场景,用户可能同时用Web端和移动端,前端的域名和后端的域名大概率还不一样,跨域情况下Session的Cookie处理更头疼。

JWT就简单直白多了:用户登录成功后,服务端生成一个包含用户ID、角色、过期时间的Token,前端存起来,每次请求放在Authorization头里。服务端不需要存储会话状态,天然支持水平扩展。

当然,JWT不是没有弱点,主要就是无法主动失效。解决方式我用了双Token机制:AccessToken有效期设置2小时,RefreshToken有效期7天,AccessToken过期后前端用RefreshToken去刷新,既保证了安全,又避免了频繁登录带来的糟糕体验。

关键配置大家参考一下:

jwt: secret: ${JWT_SECRET:your-512bit-secret-key-here} access-token-expire: 7200 # 秒 refresh-token-expire: 604800 # 7天

注意:JWT的Secret在实际生产环境必须放到环境变量或者配置中心,绝不能硬编码在yml里提交到Git仓库。这个坑我踩过,某次项目代码泄露,别人拿着你的Secret分分钟伪造管理员Token。

2. 数据建模与核心模块设计

2.1 数据库设计:一切都是围绕“人”和“事”展开的

综合服务平台说白了就是让数据在正确的人之间流转。我的数据库设计原则只有一条:高内聚低耦合,凡是能独立维护的数据一律拆表,凡是经常连表查询的字段合理冗余。

核心表设计如下:

  • sys_user:用户主表,存通用字段(用户名、密码、手机号、邮箱、状态),身份证、学号这类信息放扩展表
  • sys_role:角色表,学生、教师、管理员、宿管、财务等
  • sys_user_role:用户角色关联表,一个用户可以有多个角色(比如学生兼助教)
  • sys_permission:权限表(菜单/按钮/接口)
  • sys_role_permission:角色权限关联表
  • course_info:课程表
  • course_schedule:课表(含学期、周次、节次、教室)
  • repair_order:报修单
  • booking_record:预约记录
  • payment_order:缴费订单
  • notice_info:公告通知
  • attachment_info:附件表(统管所有上传文件)

我见过很多学生项目喜欢用一张大宽表把东西全塞进去,表面上看省事,实际上后面统计报表、权限隔离、字段扩展全都会痛苦不堪。举个例子,报修单如果单独搞一张表,状态流转时加字段很容易;如果跟预约、缴费混在一张表里,光是表结构维护就是一场灾难。

2.2 用户认证与权限模型

权限模型我选的是经典的RBAC(基于角色的访问控制),三层:用户-角色-权限。这可能是很多人觉得“老土”的方案,但在校园平台这种角色固定、权限粒度不需要到字段级的场景里,RBAC反而是最稳的。那些花里胡哨的ABAC、PBAC模型,没有可视化配置界面支持的话,维护成本极高。

接口权限的实现方式,我用的Spring Security + 自定义注解@PreAuthorize:

@PreAuthorize("hasRole('ADMIN') or hasAnyAuthority('repair:order:audit')") @PostMapping("/audit") public Result<String> audit(@RequestBody RepairAuditDTO dto) { repairService.audit(dto); return Result.success(); }

这里要强调一个细节:权限字符串的命名规范。我统一用模块:操作:资源的格式,比如repair:order:create、course:score:update。这样的好处是,后面做权限配置页面的时候,可以按模块自动分组展示,管理员配置起来非常直观。如果你随便命名,比如update或者coursemanage,后面维护权限列表绝对原地爆炸。

另外一个非常重要的经验:登录时一次性加载权限并放入Redis。千万不要每次请求都查一遍数据库权限表,高并发下数据库扛不住的。我在Redis里存的是用户角色和权限字符串的Set集合,Key格式为login:user:permission:{userId},过期时间跟AccessToken对齐。

2.3 课程与课表模块的时序模型

课表是教务模块里最容易做砸的。很多人设计课表表结构,就真是“星期几+第几节+第几周”三个字段组合,然后写出来各种Bug,比如调课、补课、单双周、节假日顺延,一改调度逻辑全乱。

我的处理方式是用时间维度拆解:

  • 学期表:维护学期开始日期、结束日期、教学周数
  • 课程定义表:课程名称、学分、授课教师、课程性质
  • 教学计划表:关联课程与班级/专业,确定这门课在哪个学期开
  • 具体排课表:学期ID + 教学班ID + 周次 + 星期 + 节次 + 教室

这样设计的好处是,调课和补课本质上是对具体排课表做变更,不会影响其他维度的数据。前端课表展示时,后端按学期参数一次性返回整个学期的所有排课记录,前端按周次和节次二维渲染。刚开始我觉得这样会一次查太多数据,实测下来一个学期的记录数大概也就几百条,根本不算压力。

2.4 报修与预约模块的状态机设计

报修、预约这类流程型模块,一定要把状态机提前设计好。我吃过的亏是:报修单一开始只设计了待处理/已处理两个状态,结果后来发现还需要派单、维修中、待评价、已关闭、已驳回。改表结构倒不难,难的是所有牵扯到状态判断的前后端逻辑全部要跟着改,那个工作量谁改谁知道。

这次我一开始就定义清楚:

  • 报修单状态:待派单 -> 已派单 -> 维修中 -> 待验收 -> 已完成 -> 已关闭
  • 预约状态:待确认 -> 已确认 -> 已取消 -> 已使用 -> 已过期

状态流转的核心逻辑放在Service层,不允许前端直接修改状态字段,所有状态变更必须通过对应的业务方法触发。比如assignOrder()、startRepair()、completeOrder()。这样的好处是,每个状态节点都可以插入校验逻辑,比如派单必须要有维修工ID,完成必须上传维修图片。

代码示例:

@Transactional(rollbackFor = Exception.class) public void assignOrder(Long orderId, Long workerId) { RepairOrder order = getById(orderId); if (order.getStatus() != RepairStatus.PENDING_ASSIGN.getCode()) { throw new BizException("当前状态不可派单"); } order.setStatus(RepairStatus.ASSIGNED.getCode()); order.setWorkerId(workerId); updateById(order); // 发送站内信通知维修工人 notifyService.send(workerId, "您有新的报修任务"); }

这里@Transactional必须加,因为派单涉及到更新订单、发送通知两个动作,要么全成功要么全失败。

3. 后端关键功能实现与联调细节

3.1 统一返回体与全局异常

一个成熟的项目,接口返回格式必须是统一的。我的格式很简单:

{ "code": 200, "message": "success", "data": {} }

前端Axios拦截器中统一判断code,如果等于200就返回data,否则弹错误提示。这里有一个细节很容易被忽略:关于code的设计。我用的是HTTP状态码之外的业务码,200是成功,401是未认证,403是无权限,500是服务端异常,业务各模块从1001开始编排。为什么不用HTTP原生的状态码?因为HTTP 200在网关层就是成功,而业务上可能需要传业务错误给前端;前端也不用所有错误都理解为网络层错误。

全局异常处理用@RestControllerAdvice,分成三类:

  • BizException:业务异常,比如“库存不足”“当前状态不可操作”,HTTP状态码还是200,code为业务码
  • AccessDeniedException:权限不足,统一返回403
  • Exception:兜底,记录日志并返回500

这个设计看着简单,但能保证前端拿到的永远是同一套JSON结构。我之前接过一个项目,有的接口返回{message: "error"},有的返回{"msg": "xxx"},前端联调时只能一个个接口去适配,痛苦至极。

3.2 多角色数据的权限隔离

智慧校园里最难处理的往往不是功能多少,而是同一份数据,不同角色看到的范围不同。

举几个实际场景:

  • 学生只能看到自己的课表和成绩,但辅导员可以看到整个班级的
  • 维修工只能看到分派给自己的报修单,但后勤管理员能看到全校的
  • 教师只能看到自己授课班级的名单,系主任可以看全系

MyBatis-Plus有一个很好用的功能叫DataPermissionInterceptor,或者你也可以自己在SQL层面手动拼接权限条件。我这里的做法更加直白:每个Mapper方法的SQL都强制接收一个权限过滤上下文。

比如查询报修单列表:

public IPage<RepairOrderVO> selectRepairPage(Page<RepairOrder> page, @Param("query") RepairQuery query, @Param("scope") DataScope scope) { // scope中封装了当前用户ID、角色、可见范围 }

对应的XML SQL里:

<select id="selectRepairPage" resultType="com.campus.platform.module.repair.vo.RepairOrderVO"> SELECT r.*, u.nick_name AS workerName FROM repair_order r LEFT JOIN sys_user u ON r.worker_id = u.id <where> <if test="query.status != null"> AND r.status = #{query.status} </if> <if test="scope.isAdmin == false"> AND ( r.create_by = #{scope.userId} OR r.worker_id = #{scope.userId} OR r.dept_id IN (SELECT dept_id FROM sys_user_dept WHERE user_id = #{scope.userId}) ) </if> </where> ORDER BY r.create_time DESC </select>

这个方案的核心思路是:永远不要在Service里先查全部数据再到内存里过滤,而是把过滤条件下推到SQL层。一旦数据量上万,内存过滤的性能问题就会直接暴露出来。

3.3 文件上传与访问的最佳实践

校园平台一定会涉及图片上传:报修照片、用户头像、失物招领图片、活动海报。我刚开始用的是直接把Base64传给后端,结果一个图片几兆的Base64字符串动辄几十MB,接口直接卡死。后来规规矩矩做了multipart/form-data上传。

这里的关键配置:

spring: servlet: multipart: max-file-size: 10MB max-request-size: 50MB

存储路径我建议不要放在项目的src目录下,而是放在服务器的一个独立目录,比如/data/campus/upload。然后通过配置类映射为静态资源:

@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceLocations("file:" + uploadPath + "/"); } }

生产环境更合理的方式是Nginx直接映射:

location /upload/ { alias /data/campus/upload/; expires 30d; }

一个核心经验:文件名不要用原始文件名。一是中文文件名会有URL编码问题,二是同一文件名的重复覆盖问题。我用的是UUID重命名(保留原始扩展名),同时把原始文件名存数据库。这样磁盘上的文件永远不重复,数据库里记录原文件信息用于展示和下载。

3.4 防重复提交与接口幂等性

这个问题几乎每个真实系统都会遇到,但看毕设项目很少见到有人认真处理。场景是这样的:学生在缴费页面点了一下“确认支付”,因为网络慢了,又点了一下,结果生成了两笔订单。或者报修表单双击了提交按钮,产生了重复工单。

我的通用方案是基于Redis的分布式锁做接口幂等:

public <T> T executeWithIdempotency(String key, long timeout, Supplier<T> supplier) { String lockKey = "idempotent:lock:" + key; Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", Duration.ofSeconds(timeout)); if (Boolean.TRUE.equals(locked)) { try { return supplier.get(); } finally { redisTemplate.delete(lockKey); } } throw new BizException("请勿重复提交"); }

然后每次需要幂等保护的操作,前端在调用时传入一个唯一业务编号,后端用这个编号加锁:

@PostMapping("/create") public Result<String> createOrder(@RequestBody @Valid CreateOrderDTO dto) { String idempotencyKey = dto.getClientToken(); if (StrUtil.isBlank(idempotencyKey)) { throw new BizException("缺少幂等标识"); } return idempotencyService.executeWithIdempotency(idempotencyKey, 10, () -> { paymentService.createOrder(dto); return Result.success(); }); }

clientToken在前端由UUID生成,每次进入页面或者每次点击按钮时重新生成一个,一次请求用完就作废。这是我在实际项目里验证过最实用、最不折腾前端的一种方案。

3.5 数据看板模块的统计SQL优化

数据看板是很多毕设的加分项,但也是性能重灾区。我的看板包含:全校报修数量趋势、各类型报修占比、预约热门场地Top5、各学院使用活跃度排行。

第一版我直接在业务库上写统计SQL,结果每次打开看板,数据库CPU直接飙高,因为很多统计需要全表扫描。后来改成了汇总表的方案:

  • 建立一个statistics_daily表,按天粒度记录每个模块的核心指标
  • 每天凌晨用定时任务(Spring的@Scheduled或者XXL-Job)跑前一天的数据汇总
  • 看板接口只查询汇总表,不碰业务表
@Component public class StatisticsJob { @Scheduled(cron = "0 30 1 * * ?") // 每天凌晨1:30执行 public void generateDailyStatistics() { // 统计昨日报修数量,按类型分组 // 统计昨日预约数量,按场地分组 // 统计昨日新增用户数,按角色分组 // 写入 statistics_daily } }

这里有个小细节值得说:同一时刻不要同时跑多个任务,不然数据库压力还是很大。我配置了一个scheduler.lock的Redis分布式锁,保证集群部署时同一时刻只有一个实例在跑汇总任务。

4. 项目部署上线与文档沉淀

4.1 从开发到部署的全流程梳理

很多同学的SpringBoot项目在IDEA里跑得飞起,一上服务器就各种花式报错。这里我把我推荐的部署流程完整写出来,照抄就行。

第一步:本地打包

mvn clean package -DskipTests

如果用了多环境配置,打包时指定生产环境profile:

mvn clean package -DskipTests -Pprod

第二步:服务器环境准备

服务器的Java环境版本务必与本地保持一致,我用的JDK 1.8,阿里云或者腾讯云的CentOS 7/8都自带或者可以方便安装。

java -version

第三步:上传并启动

nohup java -jar campus-platform.jar \ --spring.profiles.active=prod \ --server.port=8080 \ > /data/logs/campus.log 2>&1 &

这里nohup和&是必须的,否则SSH断开进程就死了。

第四步:Nginx反向代理

前端打包后的dist目录放到/data/campus/dist,然后配置Nginx把/api路径转发到后端8080端口:

location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }

4.2 Docker部署方案(推荐)

如果服务器上装了Docker,部署会舒服很多。我的做法是直接一个Dockerfile搞定:

FROM openjdk:8-jre-alpine WORKDIR /app COPY target/campus-platform.jar app.jar EXPOSE 8080 ENV JAVA_OPTS="-Xms256m -Xmx512m -Dfile.encoding=UTF-8" ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar --spring.profiles.active=prod"]

加上docker-compose编排,MySQL、Redis、后端一次性拉起:

version: '3' services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: yourpassword MYSQL_DATABASE: campus_db volumes: - /data/mysql:/var/lib/mysql ports: - "3306:3306" redis: image: redis:6.2 ports: - "6379:6379" app: build: . depends_on: - mysql - redis ports: - "8080:8080" environment: DB_HOST: mysql DB_PASSWORD: yourpassword REDIS_HOST: redis

注意:Docker容器之间通信不能用localhost,要用depends_on里定义的service名。这个坑我第一次用Docker部署时踩了一晚上,一直报数据库连接失败。

4.3 lw论文和技术文档的写作框架

标题里带了“lw”,也就是论文。我这里简单说一下怎么结合代码把论文写扎实。一个SpringBoot项目的毕业论文,核心框架一般这样搭:

  • 第一章 绪论:研究背景、意义、国内外现状
  • 第二章 相关技术介绍:SpringBoot、MyBatis-Plus、JWT、Redis等,不要写口语,要写原理
  • 第三章 系统分析:可行性分析(技术、经济、操作)、需求分析(功能性+非功能性)、用例图
  • 第四章 系统设计:总体架构图、功能模块图、数据库E-R图、数据表设计、类图
  • 第五章 系统实现:按模块写关键功能的实现过程,配核心代码和截图
  • 第六章 系统测试:功能测试(每个模块的测试用例表格)、性能测试、兼容性测试

论文一定不要只贴代码,要把设计的思路写清楚。比如你为什么要用RBAC模型?JWT相比Session的优势在哪?MySQL的索引在哪个表上建了,为什么?这些才是答辩老师真正关心的。

另外,部署文档要写清楚每个步骤,尤其写清楚服务器环境要求、数据库初始化的SQL文件怎么导入、配置文件里的哪些参数需要改。不要默认读者什么都会,毕设文档的阅读对象是老师,不是你团队的人。

我的部署文档目录大概是这样的:

docs/ ├── 00_环境要求.md ├── 01_数据库初始化.md ├── 02_后端部署.md ├── 03_前端部署.md ├── 04_Nginx配置.md ├── 05_常见问题.md

4.4 答辩讲解时的核心逻辑

如果你需要给老师或者面试官讲解这个项目,我建议你不要按模块一个个讲,那样太散,讲完别人也抓不住重点。要按一条主线讲下来:用户登录 -> 携带JWT访问接口 -> 后端用Spring Security校验权限 -> 业务层处理 -> 返回统一格式。

主线中间穿插三个亮点:

  • 权限管理的精细度:RBAC + 数据权限下推SQL,不同角色看到不同范围的数据
  • 幂等性设计:防止重复缴费和重复报修
  • 缓存设计:Redis存储Token和热点数据,数据库压力大幅降低

这个逻辑讲完,整个项目的架构、业务流程、技术深度基本就全覆盖了。

5. 常见问题排查与避坑实录

5.1 我实际遇到的高频问题

做这个项目时,我自己记录了一个问题排查表,这里贴出来给大家参考。

现象可能原因解决方式
启动报Failed to configure a DataSource没有配置数据源或注解扫描路径错了检查application.yml,或检查启动类@SpringBootApplication扫描范围
请求接口报401Token缺失/过期,或Security放行配置不对检查前端是否携带Authorization头,检查SecurityConfig中放行的白名单
跨域请求被拦截未配置Cors跨域加@CrossOrigin或全局CorsFilter,注意allowedOriginPatterns别用*(新版Spring Security会拒绝)
文件上传报MaxUploadSizeExceededException上传大小超过限制检查spring.servlet.multipart.max-file-size配置
定时任务不执行忘了加@EnableScheduling启动类或配置类加注解
数据库连接被拒服务器防火墙没开端口,或MySQL bind-address设置不对firewall-cmd --add-port=3306/tcp,检查my.cnf中bind-address是否为0.0.0.0
Redis连接失败没设密码或开启保护模式设置requirepass,连接时带密码,生产环境改用专用Redis实例

除了表格里的问题,还有一个我每次都会强调的:数据库连接字符串一定要加时区和SSL参数,否则高版本MySQL驱动直接拒绝连接。

url: jdbc:mysql://localhost:3306/campus_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai

5.2 那些看不见但会坑你的细节

第一个坑是Spring Security的放行配置。登录接口本身是需要放行的,但是接口文档(Knife4j)也需要放行,否则前端连文档都看不了。另外就是/upload/**这些静态资源路径必须放行,不然图片全部403。我的SecurityConfig中放行列表是这样:

.antMatchers( "/auth/login", "/auth/refresh", "/doc.html", "/webjars/**", "/v3/api-docs/**", "/swagger-resources/**", "/upload/**" ).permitAll()

第二个坑是MyBatis-Plus的逻辑删除。我用的是全局逻辑删除配置,字段叫deleted,配置完成后,所有deleteById操作都会自动变成UPDATE ... SET deleted=1,查询也会自动带上deleted=0条件。但要注意:唯一索引和逻辑删除会产生冲突。比如用户名有唯一索引,用户删除后记录还在,重建同名用户就插不进去。解决方案是让deleted字段参与联合唯一索引,或者物理删除。

第三个坑是Jackson的时间格式化。默认情况下SpringBoot返回的Date是时间戳格式,前端不好解析。统一配置一下:

@Configuration public class JacksonConfig { @Bean public Jackson2ObjectMapperBuilderCustomizer customizer() { return builder -> { builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss"); builder.serializers(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); builder.deserializers(new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); }; } }

不配置的话,前端显示的“2025-03-12”就会变成“Mar 12, 2025 10:00:00 AM”,极其难看。

5.3 性能调优的几点经验

这个项目我做了压测,简单调优之后接口吞吐量提升了不少。经验不多,但都很管用。

第一,数据库连接池。默认的HikariCP参数可以微调:

spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000

连接池大小不是越大越好,公式大致是(核心数*2 + 磁盘数),搞太大反而增加开销。

第二,Redis缓存热点数据。我做了两个级别的缓存:Caffeine本地缓存(一级) + Redis分布式缓存(二级)。读取流程是:先查本地,没有再查Redis,还没有就查数据库并回填。这种设计能扛住非常大的读并发,而且代码也不复杂:

public CourseVO getCourseDetail(Long id) { // 一级缓存 CourseVO course = caffeineCache.getIfPresent("course:" + id); if (course != null) return course; // 二级缓存 Object obj = redisTemplate.opsForValue().get("course:" + id); if (obj != null) { course = JSONUtil.toBean(obj.toString(), CourseVO.class); caffeineCache.put("course:" + id, course); return course; } // 数据库 course = courseMapper.selectDetailById(id); redisTemplate.opsForValue().set("course:" + id, JSONUtil.toJsonStr(course), Duration.ofMinutes(30)); caffeineCache.put("course:" + id, course); return course; }

这里有个取舍:课程信息这种变化不频繁的数据适合缓存,但报修单状态这种实时性要求高的数据不要走缓存,否则用户提交了报修,列表里一直看不到,体验极差。

第三,SQL层面的慢查询优化。我给所有常用查询字段都加了索引,比如repair_order.create_by、repair_order.status、payment_order.user_id、course_schedule.term_id等。然后用EXPLAIN关键字检查执行计划,确保没有全表扫描。

EXPLAIN SELECT * FROM repair_order WHERE create_by = 10001 AND status = 1;

如果发现type是ALL而不是ref或者range,就说明索引建少了或者条件写法有问题,需要调整。

写在最后的一点体会

整个项目从头到尾做下来,我最大的感受是:一个看起来“普通”的智慧校园系统,真正把它做扎实,需要关注的细节实在太多了。从技术选型时的克制(不盲目上微服务),到数据库建模时面向业务的思考(状态机、权限下推、汇总表),再到部署时对环境和配置的敬畏,每一环都藏着大量经验积累。

我个人觉得最有价值的一个决定,就是在设计阶段把各模块的状态机和权限边界都画清楚了,才使得后面两个月开发基本没有返工。如果你也要做类似的项目,我的建议是:先花一周时间把架构设计文档写好,把每个模块的边界、状态流转、权限控制都写清楚,再动手写代码。真的,写代码永远不是最耗时的,最耗时的永远是“想清楚再做”。

最后分享一个跟部署文档相关的小技巧:你可以在项目里加一个docs/sql/manual_test.sql文件,把核心模块的测试SQL全部写进去。这样不管是答辩演示还是交给别人部署,都能快速验证系统功能是否正常,省去在界面上一个个点按钮的时间。这也是我在多次交付中摸索出的、非常实用的习惯。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 3:04:57

链表真的“已死”吗?CPU缓存与内存池下的数据结构真相

1. 先泼一盆冷水&#xff1a;链表不是死了&#xff0c;是退出了“新手村”我入行那年&#xff0c;面试官必问“链表和数组的区别”&#xff0c;背得滚瓜烂熟&#xff1a;数组连续内存、链表节点散落、插入删除O(1)、随机访问O(n)。那时候谁要是说“链表已死”&#xff0c;怕不是…

作者头像 李华
网站建设 2026/10/7 3:04:32

OpenClaw部署实战:Windows WSL2 + Ollama本地模型 + Skill开发完整指南

这两天在社区里看到不少人问OpenClaw&#xff08;老玩家还是习惯叫它Clawdbot&#xff09;的部署问题&#xff0c;特别是2026年之后项目架构调整过一轮&#xff0c;网上很多教程还是老写法&#xff0c;照着抄很容易卡在环境上。我自己前前后后在不同机器上部署了七八遍&#xf…

作者头像 李华
网站建设 2026/10/7 3:04:23

TeamCenter JavaAPI接入实战:从连不上到高并发稳定调用

简介&#xff1a;本资源是面向PLM系统开发工程师与Java集成开发者的TeamCenter Java API实战入门包&#xff0c;聚焦西门子TeamCenter平台的二次开发与系统集成场景。压缩包内含API开发文档、典型功能示例代码及核心库文件&#xff0c;覆盖用户管理、项目与BOM配置、变更流程控…

作者头像 李华
网站建设 2026/10/7 3:04:06

开源下载工具全攻略:用 aria2 与 qBittorrent 统一管理多设备下载

这几年我把主力下载工具从 IDM 和传统 BitTorrent 客户端&#xff0c;一点点换成了开源方案。先说我个人的结论&#xff1a;BitTorrent 协议并没有过时&#xff0c;过时的只是老工具那种“装一个软件只管一个协议”的思路&#xff1b;IDM 作为经典下载器也依旧能打&#xff0c;…

作者头像 李华
网站建设 2026/10/7 3:04:05

局域网与广域网技术全解析:从基础原理到工程实践

在计科专业里&#xff0c;计算机网络这门课有个很现实的问题&#xff1a;教材把网络分成了OSI七层、TCP/IP四层&#xff0c;但翻开第五章"局域网与广域网技术"时&#xff0c;很多同学会突然懵掉——前面刚把HTTP、TCP、IP这些"高层"概念过了一遍&#xff0…

作者头像 李华
网站建设 2026/10/7 3:02:38

基于SpringBoot的图书借阅平台:设计与部署要点全解析

这个项目我前前后后帮人调试过不下十次&#xff0c;从学生课设到毕业答辩都有&#xff0c;算是Java Web方向里最经典的一类管理系统。如果你是计算机相关专业的学生&#xff0c;大概率会在选题清单里看到“基于SpringBoot的在线图书借阅平台系统”这个名字&#xff0c;附带源码…

作者头像 李华