news 2026/10/23 3:14:44

基于SSM+JSP的研究生实验室管理系统实战解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于SSM+JSP的研究生实验室管理系统实战解析

简介:这是一套基于Java Web的研究生实验室管理系统完整项目源码,面向高校实验室导师与研究生,解决会议汇报、工作周报、项目代码共享与请假审批等日常管理问题。系统采用Struts、Spring、Hibernate分层架构,区分教师与学生双角色权限:教师可查看学生历次汇报、创建会议通知指定学生讨论、回复消息并处理请假;学生可参与进行中的会议、创建公开或私有项目、上传周报PPT并查看同学评价。资源共246个文件,以62个Java源码、62个class编译文件、54个jar依赖库及24个JSP页面为主体,配套XML配置和图片资源,压缩包约30.63MB,目录结构完整,便于导入IDE运行。已有589人学习下载,适合Java Web课程设计、毕业设计及实验室项目二次开发,对学习SSH整合流程有参考价值。

1. 实验室管理到底管什么:一个JSP项目背后的真实场景

研究生实验室管理最费劲的不是设备,是“会议”。组会开了,任务布置了,两周后没人记得当时答应过什么;设备谁借的、什么时候还,也全靠见面问。我见过某实验室用 Excel 加微信群撑了大半年,最后被一台无人认领的测试机逼到崩溃。后来换了一套 javaweb研究生实验室管理系统 把这些流程固化下来:SSM 做后台、JSP 做页面、MySQL 存数据,账号权限、实验室档案、设备借用、会议记录、成果归档都串在一条链路上。

这套系统最值钱的部分不在设备台账,而在会议闭环:会前建会议、写议题,会中记录参与人,会后把纪要和成果挂到具体学生名下。对带研究生的导师来说,它是组会管理的落地工具;对正在做课程设计或者准备毕设的人,它又是一份 Spring MVC + MyBatis + JSP 的完整学习样本,登录权限、CRUD、分页查询、文件上传一个不少,拿来做二次开发的底子很顺手。

2. 系统架构拆解:SSM + JSP 下的分层设计与表结构

拿到源码第一步不是急着跑,而是先把目录和数据库看明白。这项目属于典型的 JavaWeb 分层应用,没有微服务、没有中间件,Controller、Service、DAO 三层界限相当清楚,任何一个接手的人都能从包名猜到类大概负责什么。

2.1 技术选型:为什么是SSM不是Spring Boot

这套系统出现在不少课程设计和毕业设计题目里,核心原因不是技术老旧,而是 SSM 足够“稳”:Spring 负责对象管理和事务,Spring MVC 负责把 HTTP 请求分发到 Controller,MyBatis 负责把 Java 方法和 SQL 绑定。三层各管一段,问题也好定位,不会像 Spring Boot 那样把很多底层行为包成黑匣子,带学生做项目或者后期改需求反而更方便。

JSP 在这里也不是技术落后,而是刚好匹配服务端渲染。登录后跳转、表单提交、列表分页这些同步交互,用 JSP 加 EL 表达式就能写完,不需要强行上前后端分离再配一套 Vue。项目里所有 JSP 都放在 WEB-INF 目录下,浏览器不能直接访问,必须经过 Controller 跳转,这一点对权限控制非常重要,谁也不能绕过登录页直接打开某个 JSP。

2.2 目录结构:从一个war包看代码组织

项目的 Maven 结构是标准 war 包布局,核心代码全部集中在 src/main/java 下。

src/main/java ├── com.lab.controller │ ├── LoginController.java │ ├── UserController.java │ ├── LabController.java │ ├── DeviceController.java │ ├── MeetingController.java │ └── ResultController.java ├── com.lab.service │ ├── UserService.java │ ├── MeetingService.java │ └── DeviceService.java ├── com.lab.dao │ ├── UserMapper.java │ ├── MeetingMapper.java │ └── DeviceMapper.java ├── com.lab.entity │ ├── User.java │ ├── Lab.java │ ├── Device.java │ ├── Meeting.java │ └── Result.java └── com.lab.interceptor └── LoginInterceptor.java src/main/resources ├── jdbc.properties ├── mybatis-config.xml └── spring-mvc.xml src/main/webapp ├── WEB-INF │ ├── web.xml │ └── jsp │ ├── login.jsp │ ├── index.jsp │ ├── meeting │ │ ├── list.jsp │ │ ├── add.jsp │ │ └── detail.jsp │ └── device │ ├── list.jsp │ └── borrow.jsp └── static ├── css └── js

Controller 包按业务资源拆分,不是按技术动作拆分,这一点我比较喜欢,因为新增一个模块时直接复制一个 Controller 改一改就能用。Service 层只暴露业务方法,DAO 层与 resources 下的 Mapper XML 一一对应,Entity 里全是与数据库字段对应的 POJO,没有任何业务逻辑。配置文件集中在 src/main/resources,数据库连接信息单独抽到 jdbc.properties,避免每次部署都要翻 XML。

2.3 数据库设计:六张表如何支撑会议与设备闭环

数据库是这个项目真正的骨架,一共六张核心表:user 用户表、lab 实验室表、device 设备表、meeting 会议表、meeting_record 会议记录表、result 成果表。其中 user 通过 lab_id 归属到实验室,device 通过 lab_id 挂在实验室下,meeting 和 result 则把“开会”和“产出”绑定到具体的人身上。

CREATE TABLE `user` ( `id` int NOT NULL AUTO_INCREMENT, `username` varchar(32) NOT NULL, `password` varchar(64) NOT NULL, `real_name` varchar(32) NOT NULL, `role` tinyint NOT NULL DEFAULT 3 COMMENT '1管理员 2导师 3学生', `lab_id` int DEFAULT NULL, `phone` varchar(16) DEFAULT NULL, `email` varchar(64) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `device` ( `id` int NOT NULL AUTO_INCREMENT, `lab_id` int NOT NULL, `name` varchar(64) NOT NULL, `device_type` varchar(32) DEFAULT NULL, `status` tinyint NOT NULL DEFAULT 0 COMMENT '0空闲 1借用 2维修', `borrow_user_id` int DEFAULT NULL, `borrow_time` datetime DEFAULT NULL, `return_time` datetime DEFAULT NULL, `note` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `meeting` ( `id` int NOT NULL AUTO_INCREMENT, `lab_id` int NOT NULL, `title` varchar(64) NOT NULL, `content` text, `meeting_time` datetime NOT NULL, `address` varchar(64) DEFAULT NULL, `creator_id` int NOT NULL, `status` tinyint NOT NULL DEFAULT 0 COMMENT '0待开始 1进行中 2已结束', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

注意几个字段的设计意图:user 表用 role 区分管理员、导师、学生三种角色,而不是建三张表,这样登录后只需一次查询就能拿到身份,权限拦截器直接比对 session 里的 role 即可。device 表把借用状态直接冗余在设备行上,borrow_user_id 和 borrow_time 共同记录“谁在什么时候借走”,归还时再把状态改回 0。meeting 表用 creator_id 记录会议发起人,status 状态机控制会议阶段。

meeting_record 表承担的是会议纪要锚点,每条记录对应一位参会者在某次会议中的发言或认领的任务,result 表则通过 meeting_id 把论文、专利、实验报告这些成果挂到会议下。这个设计的巧妙之处在于:成果不是孤立存在的,当用户打开某次会议详情页时,能看到这次会议产生了哪些实际产出,组会汇报时不用再到各个文件夹里翻找。

3. 核心模块实战:从登录校验到会议管理的一整条链路

这一章直接抄代码。我拆项目最关注的就是登录和业务主链路,因为登录决定权限边界,业务链路决定这个系统真正能干什么。会议模块是整个项目的灵魂,设备模块则是典型的 CRUD 加状态流转,两者合在一起就能看到一套 JavaWeb 管理系统的完整写法。

3.1 登录与权限拦截:Filter还是Interceptor

SSM 项目里做登录拦截有两种选择,Filter 或者 Spring MVC 的 HandlerInterceptor。这里用 Interceptor,因为可以拿到 handler 对象精确判断是不是 Controller 方法,而 Filter 只能对 URL 做粗粒度过滤。核心逻辑是先放行登录请求和静态资源,再检查 session。

@Component public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object loginUser = request.getSession().getAttribute("loginUser"); String uri = request.getRequestURI(); // 登录请求、静态资源直接放行,避免死循环 if (uri.contains("/login") || uri.startsWith("/static/")) { return true; } if (loginUser == null) { // 未登录统一重定向回登录页 response.sendRedirect(request.getContextPath() + "/login"); return false; } return true; } }

这段代码的价值在“双保险”:既在拦截器代码里判断 URI,又在 spring-mvc.xml 里用 exclude-mapping 排除路径。我第一次写这种拦截器时只配了 XML,结果登录接口也被拦了,未登录访问任何页面都会重定向死循环,最后就是靠这段代码救回来的。session 里存的是整个 User 对象而不是 userId,避免每次请求都查一次数据库。

XML 注册是和 Java 配置配合的,两个地方都要看:

<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <mvc:exclude-mapping path="/login"/> <mvc:exclude-mapping path="/static/**"/> <bean class="com.lab.interceptor.LoginInterceptor"/> </mvc:interceptor> </mvc:interceptors>

mapping 的 path 是拦截范围,exclude-mapping 是白名单。这里 login 不光是登录页面的 GET 请求,也包括表单提交的 POST 路径,所以只写一个 /login 就能同时覆盖两种场景。如果在项目里看到某个 Controller 的路径叫 /user/login,记得把这里改成对应值。

3.2 会议管理:从前端表单到数据库插入的完整链路

会议模块符合我对“教学级代码”的预期:Controller 很薄,只做参数绑定和页面跳转,真正的业务组装放在 Service 里。前端表单的 name 属性与实体类属性同名,Spring MVC 会自动封装成 Meeting 对象,不用手动一个个 set。

@Controller @RequestMapping("/meeting") public class MeetingController { @Autowired private MeetingService meetingService; @RequestMapping("/add") public String add(Meeting meeting, HttpSession session) { User user = (User) session.getAttribute("loginUser"); meeting.setCreatorId(user.getId()); meeting.setStatus(0); meetingService.insert(meeting); return "redirect:/meeting/list"; } @RequestMapping("/list") public String list(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, Model model) { PageInfo<Meeting> pageInfo = meetingService.findPage(pageNum, pageSize); model.addAttribute("pageInfo", pageInfo); return "meeting/list"; } }

这里有两个常用的设计:第一,creatorId 不从表单取,而是从 session 里的登录用户拿,防止有人恶意伪造发起人;第二,status 初始化成 0,也就是“待开始”,这部分业务状态不应该信任前端传值。list 方法用了 PageHelper 的分页插件,pageNum 默认 1、pageSize 默认 10,对应的 Service 返回 PageInfo 对象,里面封装了总条数、当前页数据、总页数,JSP 页面直接遍历就行。

Service 层的核心逻辑是会议和参会记录的联动写入:

@Override @Transactional(rollbackFor = Exception.class) public void insert(Meeting meeting) { meetingMapper.insert(meeting); // 会议创建后自动把创建者写入参会记录,作为第一份纪要 meetingRecordMapper.insertDefaultRecord(meeting.getId(), meeting.getCreatorId()); }

@Transactional 注解保证了 insert 操作要么同时成功要么同时失败,避免出现会议建好了但参会记录没写进去的脏数据。rollbackFor 指定任何异常都回滚,而不是只回滚 RuntimeException。演进时要注意事务方法不能同类内调用,否则 Spring 的代理失效,这是我踩过一次的坑。

对应的表单页面需要注意日期格式的传递:

<form action="${pageContext.request.contextPath}/meeting/add" method="post"> <input name="title" placeholder="会议主题" required/> <input name="meetingTime" type="datetime-local" required/> <input name="address" placeholder="会议室位置"/> <textarea name="content" placeholder="本次议题和背景说明"></textarea> <button type="submit">创建会议</button> </form>

datetime-local 是 HTML5 原生日期选择器,提交到后台的格式是“2025-06-10T14:30”,刚好能直接被 MySQL 的 datetime 字段接收。如果浏览器是旧内核不支持这个控件,常见做法是在 JSP 里写一个日期插件,或者后台用 String 接参再转 Date。这里不推荐前台传时间戳,因为项目里没有配置全局时间转换器,直接传字符串最省事。

3.3 设备借用与归还:事务处理里的关键判断

设备模块是另一个重要场景,它看起来简单,但隐藏着一个并发问题。借用方法的直觉写法是“查出设备、判断状态、更新状态”,但并发情况下两个用户同时查到了状态为 0,就会把同一台设备借给两个人。

@Override @Transactional(rollbackFor = Exception.class) public boolean borrow(Integer deviceId, Integer userId) { Device device = deviceMapper.selectById(deviceId); // 状态校验:只有空闲设备才能被借走 if (device.getStatus() != 0) { return false; } device.setStatus(1); device.setBorrowUserId(userId); device.setBorrowTime(new Date()); deviceMapper.update(device); return true; }

简化版代码在单用户系统里没问题,但实验室管理是个多人系统,我建议改成“条件更新”方案,把状态校验下沉到 SQL:

int rows = deviceMapper.borrowByStatus(deviceId, userId, 0); // 返回值是受影响行数,0 表示状态已被别人改了 if (rows == 0) { return false; }

对应的 Mapper XML 长这样:

<update id="borrowByStatus"> UPDATE device SET status = 1, borrow_user_id = #{userId}, borrow_time = NOW() WHERE id = #{deviceId} AND status = #{expectStatus} </update>

update 语句执行时会把 status=0 作为条件写进 where 子句,数据库行锁保证同一时刻只有一个请求能更新成功,第二个请求 affected rows 为 0,直接在代码里返回 false。这种写法把乐观锁的思想用在了状态字段上,比查再改的 check-then-act 安全得多,而且不用额外引入版本号字段。归还逻辑刚好相反,把 status 改回 0、清空借用人信息即可,注意归还时也要校验当前借用人确实是操作者本人,否则会出现 A 借的设备被 B 归还的情况。

4. 本地运行指南:从导入到Tomcat启动的完整过程

很多人拿到源码后卡在第一步:项目导进去了,Tomcat 也加了,页面就是打不开。这章的每一个步骤我都按实际踩坑顺序整理,跟着做基本能一次跑通。先说环境:这个项目基于 Java 8 和 Maven 构建,运行时依赖 Tomcat 8.5 以上和 MySQL 5.7 以上,IDE 用什么顺手都行。

4.1 准备工作:JDK、MySQL、IDEA配置

JDK 直接装 1.8 版本,不要装太高,因为老项目里如果写了 JDBC 驱动兼容性代码,高版本 JDK 会把模块访问权限限制得更严。MySQL 建议 5.7 或 8.0,安装时注意选择 utf8mb4 字符集,否则后面建库建表容易出现中文乱码。IDE 里打开项目后,先等 Maven 把依赖拉完,看到 BUILD SUCCESS 再继续往下走。

pom.xml 里最关键的依赖是 spring-webmvc、mybatis、mybatis-spring、mysql-connector-java 和 jstl。如果你发现某个 jar 报红,先检查本地 Maven 仓库是否下载完整,常见做法是把 .m2/repository 下对应的文件删掉重新刷新。Tomcat 方面我用 8.5 居多,JDK 1.8 搭配 Tomcat 8.5 是最稳的组合,Tomcat 10 以上把 javax 包换成了 jakarta,老项目多半跑不起来。

4.2 导入项目与配置文件修改

数据库连接配置集中在 src/main/resources/jdbc.properties,这是整个项目能不能连上数据库的关键:

jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/lab_manager?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=123456

url 里三个参数一个都不能省:useUnicode=true 开启 Unicode 支持,characterEncoding=utf8 让中文能正确写入,serverTimezone=Asia/Shanghai 解决 MySQL 8.0 的时区报错。如果你本机 MySQL 密码不是 123456,直接改最后一行。driver 这里用的是 com.mysql.cj.jdbc.Driver,这是 MySQL 8.x 驱动的写法,如果用 5.x 驱动,第一行要换成 com.mysql.jdbc.Driver,老项目这个坑出现频率很高。

4.3 建库建表与初始化数据

源码里通常会附带一个 sql 文件,如果没有,可以直接执行第二章的建表语句。顺序很重要:先建库,再建表,最后插初始账号。

CREATE DATABASE IF NOT EXISTS lab_manager DEFAULT CHARSET utf8mb4; INSERT INTO `user`(`username`,`password`,`real_name`,`role`) VALUES ('admin','admin123','管理员',1);

注意初始管理员密码这里用的是明文,演示项目常见做法就是这样,方便新手登录。如果拿到的是真实部署环境,我一般会先把密码改成 MD5 加盐或者 BCrypt 存储,同时把默认的管理员账号改掉。插入完成后可以用 SELECT 验证一下,确认 user 表有一条 role=1 的记录。

4.4 启动与验收:三步确认系统跑通

配置改完不要急着开浏览器,按顺序确认三件事:第一,Tomcat 启动日志里没有 Mapper 绑定异常,如果看到 BindingException 说明 Mapper XML 路径配错了;第二,访问 http://localhost:8080/项目路径/login 能正常显示登录页;第三,用 admin/admin123 登录后新建一条会议、借一台设备,然后打开 MySQL 客户端确认 meeting 和 device 表都多了数据。

如果打开页面报 404,先看是不是 web.xml 里的 servlet-mapping 问题,或者项目的 application context 路径没写对。如果报 500,切到 IDEA 控制台看异常堆栈第一行,百分之八十是数据库连接或 Mapper SQL 写错。这里有个小技巧,MyBatis 项目可以在 mybatis-config.xml 里打开日志输出,把 SQL 打到控制台,排查时能直接看到 SQL 与参数。

5. 避坑记录:实验室管理系统最常见的五个翻车现场

拆过几十个 SSM 老项目之后,你会发现报错就那几类。这里挑出五个最容易让新手卡死的问题,每条我都按“现象、原因、解决”的顺序写清楚,照着排查基本都能救回来。

5.1 页面空白:JSP的EL表达式失效

现象:登录成功后跳转到首页,页面是空白的,或者 ${loginUser.realName} 这种表达式原样显示在页面上。这不是数据问题,因为你直接在浏览器地址栏访问同一个 JSP 路径,能看到静态内容。

原因:web.xml 的根节点声明版本太老。老项目的 web.xml 如果写的是 2.3 版本,容器默认关闭 EL 表达式支持,所有 ${} 都不会被解析。我的判断依据很简单,先看页面源码里是不是留着原样的 ${},是就说明容器根本没解析。

解决:把 web.xml 的根声明换成 3.0 以上版本,推荐直接上 4.0:

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0">

改完之后重新构建部署,如果还是不行,可以在 JSP 页面头部加一句 <%@ page isELIgnored="false" %> 强制开启。

5.2 数据库中文乱码:连接URL和存储引擎一起看

现象:往数据库里插入中文,查询出来全是问号。很多人只改 jdbc.properties,结果发现没用,因为问题可能出在数据库或表本身的字符集上。

原因:两个地方都要检查。一是连接 URL 没带 characterEncoding=utf8;二是建表时没指定 CHARSET=utf8mb4,MySQL 默认用了 latin1。字符集是链路式的,客户端、连接、数据库、表、字段任何一个环节断了,中文都会变成乱码。

解决:先按 2.3 节的建表语句重新建表;如果表已经建好了,执行 ALTER TABLE user CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 把整张表转换过去。然后确认 jdbc.properties 的 URL 里带着 characterEncoding=utf8,重启 Tomcat 再插入中文验证。经验是“先改表、再改 URL、最后重启”,这个顺序能帮你定位到底哪一环出了问题。

5.3 Mapper映射报错:接口和XML的对位关系

现象:Tomcat 启动时报 BindingException,提示 Invalid bound statement (not found),或者找不到名为 insert 的方法。这种问题在 MyBatis 项目里出现频率极高,核心原因是 Mapper 接口和 Mapper XML 没配对。

原因:常见情况有两种。一是 XML 文件没有放在 resources 下与接口相同的包路径;二是 Mapper XML 的 namespace 没有写接口的全限定名。我在第三章的设备模块里写过 Mapper XML,namespace 一定得是 com.lab.dao.DeviceMapper 这种完整路径。

解决:打开 spring-mybatis.xml 或者 mybatis-config.xml,检查 mapperLocations 配置。常见做法是写成 classpath:com/lab/dao/*.xml,让 MyBatis 去 DAO 包下找 XML 文件。然后检查 XML 的 namespace 和每个语句的 id 是否与接口方法名一致。注意接口里不能出现两个同名方法,MyBatis 靠 id 区分,重载在这里行不通。

5.4 文件导入失败:Tomcat请求体大小限制

现象:上传一张几 MB 的实验图片或者 Excel 表格,页面直接返回 400 错误,控制台报 IllegalStateException 或 Request size limit exceeded。小文件能传,大文件就挂,这是典型的 Tomcat 请求体限制。

原因:Tomcat 默认 maxPostSize 只有 2MB,超过这个大小的表单请求会被直接拒绝。SSM 项目常用的 MultipartFile 上传组件本身没有大小限制,真正卡人的是应用服务器这一层。

解决:修改 Tomcat 安装目录下 conf/server.xml,在 Connector 节点里增加两个属性:

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" maxPostSize="0" maxSwallowSize="10485760" />

maxPostSize 设为 0 表示不限制,maxSwallowSize 控制异常时最多吞掉多少数据。如果项目里同时用了 Spring 的 multipart 配置,也要检查 MultipartResolver 的 maxUploadSize 是不是被那个限制卡住了。线上环境不建议直接把限制放开到无限大,应该根据自己的业务文件尺寸定一个合理值。

5.5 会话失效跳转死循环:登录拦截器的一个隐藏坑

现象:一个用户长时间没操作后点某个链接,页面一直在登录页和跳转页之间反复横跳,甚至直接报 “Too many redirects”,浏览器都看不下去。

原因:拦截器把登录页也拦截了。未登录访问 /meeting/list,被拦截器重定向到 /login;如果拦截器没有放行 /login,这个登录请求又会被重定向回 /login,形成死循环。还有一种情况是重定向地址写成了绝对路径 localhost:8080/login,丢失了 application context 名称,导致 Tomcat 找不到对应应用。

解决:按第三章 3.1 节的写法,在 XML 里用 exclude-mapping 放行 /login,同时在拦截器代码里保留 URI 判断作为第二层保险。重定向时一定用 request.getContextPath() + "/login" 来拼路径,这样部署到任何 context 下都不会出错。最后在会话失效场景下测试一遍:手动清掉 cookie 或者等 session 超时,再点页面链接,确认能跳回登录页且登录后能继续操作。

6. 二次开发改造:给系统增加真正的“研究生”属性

如果你拿这套代码当课程设计,跑通就够了。但如果你想让它真正配得上“研究生实验室管理系统”这个名字,我建议从会议模块下手做三个方向的改造,每个都能在现有表结构上平滑扩展。

第一个改造是给会议增加任务追踪能力。现在的 meeting_record 只记录“说了什么”,但组会最核心的是“谁在什么时候交出什么”。这里我不建议新建复杂表,最简单的做法是给 meeting_record 加两个字段:task_content 和 task_deadline,再把 status 从 0 改成 2 表示已完成。这样开会时导师现场布置任务,学生完成任务后更新状态,组会上直接查“逾期未完成”列表,比任何项目看板都贴合实验室场景。

ALTER TABLE meeting_record ADD COLUMN task_content varchar(255) DEFAULT NULL, ADD COLUMN task_deadline datetime DEFAULT NULL, ADD COLUMN status tinyint NOT NULL DEFAULT 0 COMMENT '0待办 1完成';

第二个改造是页面导出。实验室管理最常用的不是花哨图表,而是“把某个月的会议记录和成果列表导出成 Excel”,交给导师签字。常见做法是引入 Apache POI,在 Controller 里加一个 export 方法,把查询结果循环写入 Workbook,然后设置响应头让浏览器直接下载。这一步要注意内存问题,我曾在导出大批量数据时遇到过 OOM,后来强制先查 count 再决定是否分页导出。

第三个改造是权限细分。原来只有管理员、导师、学生三个角色,实际场景里管理员可能是负责设备的助理。我一般会把 device 表的管理权独立出来,给 User 加一个 perms 字段,或者在拦截器里针对 /device/** 和 /meeting/** 做角色判断。注意改权限模型时要同步改登录页和 JSP 的菜单展示逻辑,否则会出现“学生看到了设备删除按钮,点下去却报 403”的尴尬。

我个人的习惯是,拿到这类老项目先不急着加功能,而是把数据库表关系画一遍,标出哪些表之间存在状态关联、哪些字段冗余了哪些值。然后从最小的需求开始改,比如先给 meeting 表加一个 meeting_type 字段区分组会和一对一交流,再谈权限细化。因为基于支付场景做事务设计时,最怕的就是改到一半发现状态机对不上。从那以后,我每回拆 SSM 老项目,都强制自己先走一遍“登录 → 新建主数据 → 状态流转”的完整链路再动手改代码,省下的返工时间比写代码时间还多。希望帮到你。

本文还有配套的精品资源,点击获取

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

AI向实:从炫技到算账,智能经济如何落地业务场景

这两年我频繁参与各行业的AI项目评审和落地复盘&#xff0c;一个特别直观的感受是&#xff1a;大家聊AI的方式彻底变了。前两年立项书里写的是"我们要打造一个行业大模型""构建多模态能力底座"&#xff0c;听完一头雾水&#xff1b;现在立项书里写的是&quo…

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

ST7305/ST7306超低功耗驱动方案:1Hz时序与反射式LCD实战

简介&#xff1a;本资源是一份由Sitronix原厂发布的ST7305/ST7306超低功耗TFT显示驱动IC技术介绍PDF文档&#xff0c;面向嵌入式硬件工程师、IoT设备开发者及电子价签、可穿戴设备等低功耗显示方案设计人员。文档系统阐述了两款芯片的架构差异、ULP a-TFT驱动原理&#xff08;含…

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

进程与线程的区别:从原理到实战选型指南

面试过几轮技术岗的人&#xff0c;十有八九被问过一个问题&#xff1a;进程和线程的区别。我当年第一反应是背教科书&#xff0c;把“进程是资源分配的最小单位&#xff0c;线程是CPU调度的最小单位”这种标准答案甩出去。直到有阵子我半夜处理线上故障&#xff0c;盯着Dump文件…

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

银河麒麟系统清空回收站后文件恢复:原理、命令与实战

先别慌&#xff0c;这不是第一次发生&#xff0c;也大概率不是最后一次。用过银河麒麟系统&#xff08;Kylin OS&#xff09;的人&#xff0c;多多少少都经历过“回收站里一堆文件&#xff0c;手一快点了清空&#xff0c;发现里面还有重要文档/照片/压缩包”的瞬间。更常见的情…

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

结构化车牌数据集:覆盖新能源绿牌与多光照场景

简介&#xff1a;本资源是面向计算机视觉初学者与算法工程师的车牌检测识别专用训练数据集&#xff0c;聚焦交通监控、智能停车及自动驾驶等实际场景中的车牌定位与字符识别任务。数据集覆盖蓝牌、黄牌、新能源绿牌三大主流类型&#xff0c;并包含少量白牌与黑牌&#xff0c;兼…

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

如何为CSDN技术教程提供完整素材?

当前输入不完整&#xff0c;无法生成指定的 CSDN 技术教程博文。请您按以下格式补充素材后&#xff0c;我会立即按教程结构输出完整正文&#xff1a;项目标题: [技术主题] 项目正文: [零散描述、代码片段、踩坑记录等] 关键词: [关键词1, 关键词2, ...] 摘要描述: [一句话说明文…

作者头像 李华