ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于JSP的高校固定资产管理系统:从数据库设计到毕业设计答辩全流程

基于JSP的高校固定资产管理系统:从数据库设计到毕业设计答辩全流程

简介:基于JSP的高校固定资产管理系统毕业设计完整资料包,面向计算机相关专业本科生及Web开发初学者,解决固定资产信息化管理系统的选题难、资料散、部署繁等问题。资源涵盖项目报告、答辩PPT、完整源代码、MySQL数据库脚本、系统运行截图及部署视频,覆盖需求分析、系统设计、编码实现与测试答辩全流程。压缩包整体约147.02MB,目录结构清晰便于检索学习,已有449人学习浏览。通过该资源,读者可掌握JSP+Servlet+MVC三层架构开发模式、MySQL第三范式数据库设计、SQL增删改查与报表生成等核心技能,并参照部署视频快速搭建本地运行环境,系统理解高校固定资产登记、报废、借用、盘点等模块的业务逻辑,对毕业设计与项目实战具有高价值的参考意义。

1. 需求背景:高校资产越管越乱,这套毕设方案要解决什么

某高校资产处的老师拿着一份Excel台账找到某开发者,说去年刚装的一批投影仪有三分之一对不上号——有的借出没登记,有的报废没销账,还有的编号已经重复。这不是个例,高校里资产“账实不符”基本是通病:资产数量大、流转频繁、经手人不固定,光靠办公软件根本堵不住漏洞。

《基于JSP的高校固定资产管理系统》就是冲着这个痛点来的。它要做的事很具体:把资产从入库、领用、借用、归还、报废、盘点这一整条生命周期管起来,让每一台设备在哪个办公室、哪个状态、谁经手,都能随时查出来。选择这个课题做毕业设计,是因为它恰好覆盖了JSP、Servlet、MySQL、软件工程这些知识点的完整链路,既能体现编程能力,又能在答辩时把“需求分析—系统设计—编码—测试—交付”讲成一条清晰故事线。这套完整产品级资料包含源码、数据库脚本、项目报告、答辩PPT、截图和部署视频,适合两类人:一类是正在做毕设或课设、想少走弯路的学生,另一类是要快速搭建后台资产台账的开发者,照着改就能用。

2. 总体架构设计:MVC三层、五个功能模块与核心数据表的职责划分

2.1 技术选型:JSP+Servlet+MySQL为什么是最稳妥的组合

很多同学一上来就问我:“现在都Spring Boot了,JSP这套老技术还能用吗?”这个问题在毕设答辩里几乎必被问到。我的建议是:如果你的课题要求是Web应用开发,JSP+Servlet+MySQL反而是最稳的选择。原因有三层:

第一,JSP本身就是Java的动态网页技术,JSP负责展示页面,Servlet负责接收请求和处理业务逻辑,两者天然契合MVC模式中“视图”和“控制器”的角色。构建前端的表单、列表、详情页很直观,评审老师翻开代码就能看懂结构,不像Spring Boot里一堆注解,容易陷入黑匣子。

第二,JSP+Servlet能逼你把Servlet生命周期、HttpServletRequest/Response、Session会话、请求转发与重定向这些底层机制讲清楚。答辩时老师常问“你Servlet是不是单例的”“Session和Cookie什么区别”,用这个技术栈你都有实打实的代码可以指。

第三,部署链路简单:一个Tomcat加一个MySQL就能跑。比起需要配置一堆中间件的微服务,这玩意儿在答辩现场换电脑演示也不容易翻车。

这套系统最终的架构分层如下:

  • 视图层:JSP页面,负责表单收集和数据显示;
  • 控制层:Servlet,负责接收请求、调用业务、分发跳转;
  • 数据访问层:DAO类配合JDBC操作MySQL;
  • 数据层:MySQL数据库,按第三范式设计表结构。

硬件上就是一台普通PC,开发工具用Eclipse或IDEA,数据库工具用Navicat,运行环境是JDK 8 + Tomcat 8.5。对于大多数高校的毕设硬件条件,这台配置完全够跑。

2.2 功能模块划分:资产台账、借还、报废、盘点与用户权限

资产管理系统不是把Excel搬上网页那么简单,模块划分要贴合高校真实业务流程。我把系统拆成五个功能域,每个域对应项目报告里的一个章节,也对应答辩PPT里的一页功能架构。

功能域核心功能点涉及角色
用户登录与权限登录认证、密码修改、未登录拦截、角色区分管理员、普通教职工
资产分类与入库分类维护、资产登记、自动生成资产编号管理员
资产查询与台账多条件检索、列表分页、导出资产状态管理员
资产借还与借用借出登记、归还处理、借还历史记录管理员、教职工
资产报废与盘点报废申请、报废审核、盘点任务、盘盈盘亏管理员

其中“资产借还”和“资产盘点”是设计的重点,也是答辩时最有说头的地方。借还流程里涉及资产状态变更,盘点流程里涉及账存与实存对比,这两块都能体现数据一致性和事务处理能力。

2.3 数据库设计:第三范式下的资产表、借还表与盘点表

数据库设计是这套系统的地基。我一般采用第三范式设计:不搞冗余字段,每条资产信息只存一次,通过主外键关联。核心表分为四类:用户表、分类表、资产表、借还记录表,再加上辅助的报废记录表和盘点对比表。

资产表是关键中的关键,字段设计时要充分考虑查询和统计的需求。以下是我在实际项目中用了很多次的结构:

CREATE DATABASE asset_db DEFAULT CHARACTER SET utf8mb4; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, real_name VARCHAR(50), role TINYINT NOT NULL DEFAULT 0 -- 0普通 1管理员 ); CREATE TABLE t_category ( cate_id INT PRIMARY KEY AUTO_INCREMENT, cate_name VARCHAR(50) NOT NULL ); CREATE TABLE t_asset ( asset_id VARCHAR(32) PRIMARY KEY, -- 资产编号,如ZC20250001 cate_id INT NOT NULL, asset_name VARCHAR(100) NOT NULL, model VARCHAR(100), brand VARCHAR(50), purchase_date DATE, price DECIMAL(10,2), status TINYINT NOT NULL DEFAULT 0, -- 0在库 1借出 2维修 3报废 location VARCHAR(100), -- 存放位置 user_id INT, -- 当前责任人 create_time DATETIME, CONSTRAINT fk_cate FOREIGN KEY(cate_id) REFERENCES t_category(cate_id), CONSTRAINT fk_user FOREIGN KEY(user_id) REFERENCES t_user(id) ); CREATE TABLE t_borrow ( borrow_id INT PRIMARY KEY AUTO_INCREMENT, asset_id VARCHAR(32) NOT NULL, borrow_user VARCHAR(50), borrow_time DATETIME, expect_return_time DATETIME, actual_return_time DATETIME, status TINYINT DEFAULT 0, -- 0借出中 1已归还 FOREIGN KEY(asset_id) REFERENCES t_asset(asset_id) ); CREATE TABLE t_repair ( repair_id INT PRIMARY KEY AUTO_INCREMENT, asset_id VARCHAR(32) NOT NULL, reason VARCHAR(255), apply_time DATETIME, approved TINYINT DEFAULT 0, -- 0待审 1通过 2驳回 FOREIGN KEY(asset_id) REFERENCES t_asset(asset_id) );

这段SQL里值得注意三个设计点:

  • status字段用整型,而不是0/1布尔值,因为资产有“在库、借出、维修、报废”四种状态,后面扩展“盘点中”也方便;
  • 资产编号用字符串主键并采用IC卡式编码,例如ZC20250001,人工也能看懂归属年份和序号,查询时不需要关联额外表;
  • 借还表里同时保留borrow_time和actual_return_time,是方便生成逾期未归还的报表。

这样设计后,查询一台资产的完整履历只需要两次join:一次到借还表找借出记录,一次到报废表看状态变更,不需要在资产表里堆一长串变更记录。

2.4 项目分包与代码目录:让答辩时结构能讲得清

代码能跑只是及格,能让评审老师三分钟看懂项目结构才是加分项。我习惯把Java源码按职责分包,而不是全部堆在一个包下:

src ├─ com.demo.bean 实体类(User、Asset、Category、BorrowRecord) ├─ com.demo.dao 数据访问类(直接操作JDBC SQL) ├─ com.demo.servlet 控制器(接收请求、调用DAO、跳转JSP) ├─ com.demo.filter 登录拦截过滤器 ├─ com.demo.util 数据库连接工具类 web ├─ static CSS/JS/图片资源 ├─ WEB-INF web.xml 及 lib ├─ login.jsp ├─ asset_list.jsp ├─ asset_add.jsp ├─ borrow.jsp ├─ repair.jsp └─ inventory.jsp

这个结构本身就是MVC的具象化。bean是Model,JSP是View,Servlet是Controller,filter是横切关注点。被问“分层为什么这么分”的时候,直接指向目录说“每一层的依赖单向向下”比背概念更有说服力。

3. 核心实现:登录鉴权到借还报废的业务代码落地

3.1 登录与权限拦截:用Filter给所有页面加一道统一闸门

做一个管理系统,第一件事不是写增删改查,而是把登录拦截做掉。不然系统刚上线,别人直接在URL里输入asset_list.jsp就能绕过登录页看到数据,这在答辩演示时是最尴尬的翻车现场。

我采用一个自定义Filter,拦截所有请求,统一判断Session里有没有登录用户。这样每个页面不需要单独写权限校验,逻辑高度集中:

@WebFilter("/*") public class LoginFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; String uri = req.getRequestURI(); // 放行登录相关请求、静态资源 if (uri.endsWith("login.jsp") || uri.endsWith("/login") || uri.contains("/static/")) { chain.doFilter(request, response); return; } HttpSession session = req.getSession(false); Object loginUser = (session == null) ? null : session.getAttribute("loginUser"); if (loginUser == null) { resp.sendRedirect(req.getContextPath() + "/login.jsp"); } else { chain.doFilter(request, response); } } }

这段代码的核心在于用了req.getSession(false)而不是req.getSession()。getSession(false)表示如果当前没有Session就返回null,不会为每个请求都强行创建一个新会话,避免静态资源和攻击探测把内存撑爆。放行规则里只放行login.jsp、/login和/static/,其他业务页面一旦没登录就会被重定向到登录页。

登录成功后,Servlet里需要把用户放入Session:

User loginUser = userDao.findByUsernameAndPassword(username, password); if (loginUser != null) { request.getSession().setAttribute("loginUser", loginUser); response.sendRedirect("asset_list.jsp"); }

这里不要用请求转发到asset_list.jsp,而是重定向。因为如果直接转发,浏览器地址栏仍然是/login,用户刷新一次页面就会把登录请求重复提交,导致表单单标、重复操作一系列莫名其妙的问题。重定向是让浏览器重新请求真正的列表页,地址栏也变成目标地址,刷新就没副作用。

3.2 资产登记:从JSP表单到Servlet再到DAO的完整请求链路

资产入库是业务起点,也是最基本的功能。我把完整链路拆成三块:JSP表单负责收集信息,Servlet负责接收和校验,DAO负责写入数据库。三块都要各司其职,才好在答辩时讲清楚MVC是怎么分工的。

<form action="AssetAddServlet" method="post"> 资产编号:<input type="text" name="assetId" value="ZC2025" required/><br/> 资产名称:<input type="text" name="assetName" required/><br/> 规格型号:<input type="text" name="model"/><br/> 分类:<select name="cateId"> <option value="1">电子设备</option> <option value="2">办公家具</option> </select><br/> 价格:<input type="number" step="0.01" name="price"/><br/> 存放位置:<input type="text" name="location"/><br/> <input type="submit" value="登记入库"/> </form>
@WebServlet("/AssetAddServlet") public class AssetAddServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); String assetId = request.getParameter("assetId"); String assetName = request.getParameter("assetName"); String model = request.getParameter("model"); String location = request.getParameter("location"); int cateId = Integer.parseInt(request.getParameter("cateId")); BigDecimal price = new BigDecimal(request.getParameter("price")); Asset asset = new Asset(); asset.setAssetId(assetId); asset.setAssetName(assetName); asset.setModel(model); asset.setCateId(cateId); asset.setPrice(price); asset.setLocation(location); asset.setStatus(0); AssetDao dao = new AssetDao(); boolean ok = dao.addAsset(asset); if (ok) { response.sendRedirect("AssetListServlet"); } else { request.setAttribute("msg", "添加失败,编号可能已存在"); request.getRequestDispatcher("asset_add.jsp").forward(request, response); } } }

注意几个细节:request.setCharacterEncoding("UTF-8")必须放在所有getParameter之前,否则POST表单里的中文在Tomcat默认编码(ISO-8859-1)下直接乱码。这是后端乱码问题最常见的原因。

再来是DAO层的方法:

public boolean addAsset(Asset asset) { String sql = "INSERT INTO t_asset(asset_id, cate_id, asset_name, model, price, status, location, user_id, create_time) VALUES(?,?,?,?,?,?,?,?,NOW())"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, asset.getAssetId()); ps.setInt(2, asset.getCateId()); ps.setString(3, asset.getAssetName()); ps.setString(4, asset.getModel()); ps.setBigDecimal(5, asset.getPrice()); ps.setInt(6, asset.getStatus()); ps.setString(7, asset.getLocation()); ps.setInt(8, asset.getUserId()); return ps.executeUpdate() > 0; } catch (SQLException e) { e.printStackTrace(); return false; } }

必须用PreparedStatement而不是Statement加字符串拼接。一方面防止SQL注入,另一方面也能让MySQL缓存SQL执行计划,批量插入资产时性能不至于太难看。字段里设置了user_id作为责任人,登记时如果当前登录用户是普通教职工,就直接默认该用户为资产责任人,省去单独填的环节。

3.3 资产借用与归还:用事务保证借出记录和资产状态一致

资产借出是整系统里最大的“坑”所在。很多同学实现借出功能时,先往t_borrow表插一条记录,再改t_asset表的status。结果代码写完后一测试:借出记录插成功了,资产状态更新失败,或者反过来。这就是没有事务控制的典型翻车。

我一般这么改造:将两步数据库操作放在同一个Connection里,先关闭自动提交,再一起执行,一起提交,任何一步失败就整体回滚。

public boolean borrowAsset(String assetId, String borrowUser, Date expectTime) { Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 第一步:插入借出记录 String insertSql = "INSERT INTO t_borrow(asset_id, borrow_user, borrow_time, expect_return_time, status) VALUES(?,?,NOW(),?,0)"; PreparedStatement ps1 = conn.prepareStatement(insertSql); ps1.setString(1, assetId); ps1.setString(2, borrowUser); ps1.setTimestamp(3, new Timestamp(expectTime.getTime())); ps1.executeUpdate(); // 第二步:变更资产状态,同时校验资产仍然是在库状态 String updateSql = "UPDATE t_asset SET status=1, user_id=(SELECT id FROM t_user WHERE real_name=?) WHERE asset_id=? AND status=0"; PreparedStatement ps2 = conn.prepareStatement(updateSql); ps2.setString(1, borrowUser); ps2.setString(2, assetId); int rows = ps2.executeUpdate(); if (rows == 0) { throw new SQLException("资产不在库,可能已被借出"); } conn.commit(); return true; } catch (Exception e) { if (conn != null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } finally { if (conn != null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }

这段代码里有个巧妙的点:UPDATE ... WHERE asset_id=? AND status=0。这个status=0条件相当于一次乐观锁,即使两个用户同时点“借出”同一台资产,数据库层面也只会有一个UPDATE语句能命中,另一个rows=0,直接抛出异常回滚。这比先SELECT再判断状态再UPDATE更可靠,因为SELECT和UPDATE之间别人可能已经改了状态,而把这判断合并进UPDATE,就把并发竞争在数据库层面拦住了。

归还的逻辑对称:先更新资产status为0,再更新借还记录status为1并写入actual_return_time。顺序其实无所谓,反正都在一个事务里。不过我习惯先改资产表再改借还表,因为资产状态是对外可见的核心数据,借还记录更多是审计用途,核心数据先落更符合直觉。

3.4 报废与盘点:状态流转和批量更新背后的规则

资产报废不是管理员一言堂。正常流程是:有人提出报废申请,写明原因,管理员审核通过后,资产状态才变为“报废”。如果直接删资产记录,就丢了固定资产的历史价值。

报废申请功能核心就是一个带多项参数的INSERT加UPDATE:

-- 申请报废:插入报废申请记录 INSERT INTO t_repair(asset_id, reason, apply_time) VALUES (?, ?, NOW()); -- 管理员审核通过 UPDATE t_repair SET approved=1 WHERE repair_id=?; -- 变更资产状态为报废 UPDATE t_asset SET status=3 WHERE asset_id=?;

同样,这三条SQL也应该放在一个事务里执行。尤其是审核通过和状态变更,必须保证要么都成功,要么都失败,否则会出现“申请单已经通过但资产还在流转”的脏数据。

盘点模块是这套系统里最能体现软件工程价值的功能。盘点通常的做法是:先导出一份账存清单,打印成表格去现场逐项核对,再把实际结果录入系统。因此在系统里我设计了一个盘点对比方案:

-- 盘点结果表:存放实盘结果 CREATE TABLE t_inventory ( id INT PRIMARY KEY AUTO_INCREMENT, asset_id VARCHAR(32) NOT NULL, actual_status TINYINT, actual_location VARCHAR(100), inventory_time DATETIME, remark VARCHAR(255) ); -- 查询盘盈盘亏差异 SELECT a.asset_id, a.asset_name, CASE WHEN i.id IS NULL THEN '盘亏' ELSE '一致' END AS diff_type FROM t_asset a LEFT JOIN t_inventory i ON a.asset_id = i.asset_id;

左连接查询的作用是找出“账上有但盘点表里没有”的资产,这部分就是盘亏资产。反之,如果盘点表里有资产编号,但t_asset里不存在,就是盘盈。用一条LEFT JOIN就能把两个方向都覆盖到。我通常会把这个差异查询结果做成一张报表页面,并在页面底部标注“本次盘亏数:X,盘盈数:Y”,方便答辩时直接展示系统解决了账实相符这个核心痛点。

4. 避坑指南:JSP+Servlet项目最容易翻车的五个环节

4.1 中文乱码:从页面到数据库层层排查

现象:表单提交的中文名称保存到数据库后变成“???”,或者页面显示乱码。

原因:乱码有三个作案地点,第一是JSP页面本身没写pageEncoding,第二是Servlet没有写request.setCharacterEncoding("UTF-8"),第三是数据库连接URL没带编码参数,Tomcat默认使用ISO-8859-1处理POST参数。

解决:三个地方都要堵住。JSP页面在文件头写<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>;Servlet在接收参数之前调用request.setCharacterEncoding("UTF-8");JDBC连接URL后面加上useUnicode=true&characterEncoding=utf8。还有一个隐藏坑:如果MySQL建表时没指定字符集,字段默认是latin1,即使应用层全对了,存进去还是乱。建表语句必须带上DEFAULT CHARSET=utf8mb4。

4.2 数据库连接驱动与MySQL时区导致启动报错

现象:Tomcat一启动,应用访问数据库时报ClassNotFoundException: com.mysql.jdbc.Driver,或者The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized。

原因:MySQL驱动版本和数据库版本不匹配。MySQL 8.0之后驱动类名变成了com.mysql.cj.jdbc.Driver,很多人还在用老版com.mysql.jdbc.Driver,要么没把驱动jar包放到WEB-INF/lib下。

解决:驱动jar必须放在WEB-INF/lib目录,用MySQL 8就用com.mysql.cj.jdbc.Driver,JDBC URL加serverTimezone=Asia/Shanghai。如果你不想和版本纠缠,直接使用MySQL 5.7配老驱动,操作路径更简单。不过现在新电脑装MySQL 8很常见,所以驱动类名和时区参数这两个坑必须知道。

4.3 表单提交后404:Servlet映射和前端URL对不上

现象:点击“保存”按钮,页面跳到404,控制台没有任何异常日志。

原因:前端action写的是AssetAddServlet,但Servlet的@WebServlet注解里写的地址是/assetAdd,或者没有加/;又或者项目部署后上下文路径是/asset_system,而表单里写了相对路径/AssetAddServlet,导致请求发去了根域名下的AssetAddServlet,实际路径找不到。

解决:Servlet注解统一写成@WebServlet("/AssetAddServlet"),JSP里写action="AssetAddServlet",并且表单在asset_add.jsp中,提交时会以当前目录为基准。更稳的做法是前端写${pageContext.request.contextPath}/AssetAddServlet,无论项目改什么名字都不会错。每次改完Servlet映射后,重启Tomcat再测一次,不要冷启动后又用旧Class缓存骗自己。

4.4 部署后CSS和JS加载失败:上下文路径的锅

现象:本地IDE预览一切正常,部署到Tomcat后登录页变成没有任何样式的纯HTML。

原因:页面里引用了href="/static/css/style.css",这个绝对路径默认到http://localhost:8080/static/css/style.css,但你的项目部署在http://localhost:8080/asset_system/下,CSS实际地址是http://localhost:8080/asset_system/static/css/style.css,差了一个项目名。

解决:所有静态资源引用统一用${pageContext.request.contextPath}拼前缀:

<link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/style.css">

同样,重定向和跳转页面时也尽量用getContextPath()拼,否则演示时换了部署名就满屏样式崩坏。顺手可以在登录Filter放行规则里把/static/放行,不然登录页本身都会被拦成死循环。

4.5 项目报告和代码脱节:演示时被一眼识破

现象:项目报告里写的功能模块、截图、数据库表和实际代码不一致,答辩演示时老师翻报告发现找不到对应页面。

原因:写报告时参考了别人的模板,把框架抄得很漂亮,但代码是自己简化实现的,功能覆盖不全。

解决:把这套系统的项目报告结构固定成五段——需求分析、总体设计、数据库设计、核心功能实现、测试。每写一个功能点,就去跑一遍对应的页面,截图直接贴到报告对应位置。尤其是核心业务流程,比如借还和盘点,报告里的时序描述必须和代码里的SQL一一对应。我一般会在报告初稿完成后做一次“报告对照代码检查”,逐项打钩,这项动作能让答辩的信任度提升一个档次。

5. 让方案打包完整:项目报告、答辩PPT、截图与部署视频的组织方式

5.1 项目报告:让需求、设计、测试形成闭环

项目报告是毕业设计的重量级交付物,评审老师读报告的速度很快,通常只看三样东西:目录结构、核心截图、测试结论。所以报告不要按源码堆流水账,要按软件工程的自然流程组织。

我推荐的报告章程如下。这份结构对应了系统从无到有的生命周期,答辩时也照这个顺序讲。

章节内容要点篇幅建议
1. 绪论选题背景、国内外现状、研究意义4~6页
2. 需求分析业务流程图、功能需求表、用例说明6~8页
3. 总体设计系统架构、模块划分、技术选型4~6页
4. 数据库设计E-R图、核心表SQL、字段说明6~10页
5. 详细设计与实现每个模块的截图、关键代码、流程说明12~18页
6. 系统测试测试用例表、测试结果、缺陷修复记录4~6页
7. 结论与展望项目收获、不足与下一步方向2页

需要特别提醒的是,第5章一定要结合截图写,每一张截图下面写清楚“这张图完成了哪个需求点”,不要放一堆代码然后不做解释。老师看代码的速度比你想象中快,他看不懂你写什么不是因为他不会,而是因为你没讲清楚它在业务里承担什么。

5.2 答辩PPT:十页讲完整个项目,留足时间给提问

答辩PPT控制在10到12页最合适。页数太少显得项目没有工作量,页数太多讲不完,而且老师翻页会烦。每页的职责要单一,别把数据库设计和功能模块塞在一页里。

我常用的PPT页序如下:

  • 第1页:项目背景与痛点,用数据强调“账实不符”;
  • 第2页:需求分析,列出核心业务流程图;
  • 第3页:总体架构图,画出三层结构;
  • 第4页:功能模块图,展示五大功能域;
  • 第5页:数据库设计,放E-R图和核心表样例;
  • 第6页:核心功能实现,挑登录拦截和资产借出两个点,贴精简代码片段;
  • 第7页:运行效果截图,登录页、列表页、盘点报表页;
  • 第8页:系统测试,给出测试用例和结果;
  • 第9页:项目总结,讲遇到的问题和怎么解决;
  • 第10页:Q&A,什么都不写,留白等提问。

演讲时间控制在8到10分钟,重点放在背景、架构、核心功能和测试结果上。代码不要逐行讲,只讲最关键的一两处,比如借出事务那段UPDATE ... WHERE status=0,这个代码一经抛出,老师就能看出你确实理解并发控制。

5.3 截图与部署视频:交付物怎么录才能让评审一次看懂

截图看似简单,其实很见功力。系统截图不要只截页面本身,要截出带浏览器地址栏的完整窗口,确保地址栏可见,这样老师才知道你运行的是真实部署后的页面,而不是用代码模拟器画出来的假图。

建议每个模块至少截两张:一张是录入或操作前页面,一张是操作后结果页面。比如资产借出,第一张是借出前资产状态为“在库”的列表页,第二张是借出后状态变成“借出中”的列表页。前后状态对比越明显,越能证明系统真的工作。

部署视频的录制我是这么做的:开录之前先写一个脚本,列出录像步骤清单。视频不必追求“一镜到底”的高超剪辑,反而要慢,要露出每一步操作。

很多人在录部署视频时只录“打开Tomcat,输入网址,点几下页面”就完事,这忽略了一个关键信息:数据库是怎么导入的。正确的步骤应该是:

  1. 打开数据库工具,创建数据库,导入asset_db.sql;
  2. 启动Tomcat,确认控制台没有异常输出;
  3. 在浏览器访问登录地址,完成一次完整业务操作;
  4. 最后登录管理员账号,进入盘点页面,展示一次差异查询。

部署视频的时长控制在8到15分钟。答辩现场如果设备出问题,老师甚至会直接要求播放这段视频,所以视频里的操作路径要能脱离现场讲解独立看懂。

6. 验收与答辩:从操作演示到提问风暴的过关技巧

验收这条线,我会用一张清单来控制,每一步不过就不算通过。

验证项操作方式与预期结果
登录拦截不登录直接访问列表页,应被重定向到登录页
资产登记新增一条资产,列表页能立刻出现且状态为在库
重复编号校验再次提交同一编号,系统提示失败且不产生脏数据
借出并发两个入口同时借同一资产,只有一个成功
归还后状态归还后资产状态恢复在库,借还记录出现归还时间
报废审核申请报废后资产仍在列表但状态为待处理,审核后变为报废
盘点差异人为在盘点表少录一个编号,差异查询显示盘亏

演示时我会这样安排顺序:先登录,登记一台新资产,把它借出,再归还,然后报废,最后打开盘点页看结果。这六个步骤恰好覆盖全部核心模块,中间没有任何多余动作,总时长三分钟。别从“用户管理”这种边缘功能开场,那是把黄金时间浪费在最没技术含量的地方。

关于提问环节,我把老师常问的问题和应对思路整理成一个对应关系,你可以直接当排练脚本:

  • 问“为什么用JSP不用Spring Boot”:答课题目标是掌握Servlet底层原理,Spring Boot是封装之上的简化,用JSP能更清楚展示请求生命周期。
  • 问“数据库怎么保证一致性”:答借出操作放在事务里,并用UPDATE ... WHERE status=0做条件校验,保证并发下不会同借一物。
  • 问“系统有哪些安全措施”:答密码采用加盐哈希存储,SQL全部用PreparedStatement防注入,Filter统一做登录鉴权。
  • 问“数据量大了怎么办”:答索引已经建在资产编号和状态字段上,后续可以加Redis做缓存,属于可扩展点。

其中“数据量大了怎么办”是很多同学最怕的问题。不要慌,不用真的去写缓存代码,只要说出“第一步会在查询频繁的字段上加索引,当前表已经在asset_id上建了主键索引,接下来会给status和location加组合索引;如果再做一次升级,可以把资产列表热点数据放到Redis”就足够展示你思考过扩展性。

我最后说一个自己踩过的坑:当初答辩前,我自认为系统已经跑得很顺,结果临时换了一台演示电脑,Tomcat装了不同版本,项目部署名带上了加路径,CSS全部丢失,登录接口也404,现场只能硬着头皮口述功能。从那以后,每次演示前我都会强制走一遍从零部署流程:重建数据库、启动Tomcat、清缓存、试一次登录,确认驱动、编码、路径都没问题再上台。希望帮到你,别在细节上丢掉该拿的分数。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进