尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

JSP毕业设计实战:书香羲园笔记展评系统从0到1

JSP毕业设计实战:书香羲园笔记展评系统从0到1 毕业设计答辩季又到了后台不少学弟学妹来问我“书香羲园”最美笔记展评管理系统该怎么做。说实话这个题目在JSP类毕业设计里属于“看着简单做起来绕”的类型。表面上是展示和评分的业务实际涉及用户角色权限、文件上传处理、评分并发一致性、榜单排序等多个环节任何一个地方没处理干净答辩时都会成为老师追问的火力点。这篇文章我会从零拆解这个项目的核心设计、数据库结构、关键代码实现以及我在实际开发中踩过的坑。内容按毕业设计的完整流程走从需求分析到代码实现再到答辩排查力求让你照着思路能搭出一个能跑、能讲、能过审的项目。1. 项目定位与需求拆解1.1 “书香羲园”到底要做什么先把这个题目翻译成人话。“书香羲园”是一个品牌壳子最美笔记展评是核心业务。也就是说这个系统是一个面向校内学生的笔记作品展示与评选平台学生上传自己的笔记照片或文档管理员审核后发布到展示区其他用户或评委登录后可以打分、写评语最终形成榜单。拆开来看这个系统必须回答三个核心问题笔记如何进得来——需要上传功能涵盖图片最常见的是用手机拍的手写笔记和PDF一般用于电子版讲义、思维导图导出件。笔记如何被看到——需要一个公开展示区按分类、按投票热度、按上传时间浏览。笔记如何被评出来——这是“展评”的重头戏。评分体系至少包含“评委打分”和“大众投票”两种形态评语不能少最终要能导出一个排行结果。从毕业设计的角度讲这个需求就有得写了用户系统是基础笔记管理是核心评分展评是亮点。三块叠起来工作量、技术点、演示效果都够。1.2 为什么选JSP而非其他技术栈近几年的毕业设计SpringBoot几乎是“标配”。但这个题目带了“jsp”这个关键词并且明确写的是JSP版本这其实是一个“反向红利”——你做的是一个用JSPServlet构建的经典JavaWeb项目反而能和那些满屏SpringBoot的设计区分开来。我个人理解这个选题的用意有两个一是让做设计的人回头扎一遍最原始的JavaWeb运行机制搞清楚HTTP请求如何到达ServletJSP如何被编译成ServletSession和Cookie到底是谁在维护。另一个原因是“展评系统”本质是信息展示型系统用JSP做服务端渲染页面直接动态输出在代码可读性、答辩讲解难度上都比前后端分离更省力。技术栈选型如下后端核心JSP Servlet JavaBean经典MVC模式数据库MySQL 5.7或8.0用JDBC连接可选配Druid连接池服务器Tomcat 8.5或9.0前端Bootstrap jQuery少量Ajax提升交互文件上传commons-fileupload组件图表展示ECharts用于评分趋势统计这套搭配的好处是跑起来轻量讲解时每一条链路都能对着代码说清楚。不同于SpringBoot的“自动配置黑盒”JSP项目的控制器、过滤器和DAO都是肉眼可见的老师问什么你都有话说。1.3 角色与核心功能边界系统角色划分成三类不需要再做更多角色做多了反而复杂管理员用户管理、笔记审核、评分规则设置、公告发布评委教师查看已审核笔记、打分、填写评语、参与终评学生用户注册登录、上传笔记、浏览展示区、投票或评分功能边界上我的建议是守住“一条主线”用户→上传笔记→管理员审核→展示笔记→评委评分→榜单发布。围绕这条线用户管理、审核管理、评分管理、榜单展示四大模块就够了。比如“评论社交”“私信”这类功能属于加分项但如果时间紧张宁可不做也不能做崩。2. 系统整体架构与数据库设计2.1 分层设计思路JSP项目的分层与其说是设计出来的不如说是“被逼”出来的。我最初写的时候把SQL直接写在JSP页面里结果页面改了需求SQL跟着改JSP又乱又容易错。后来老老实实按三层架构重排表示层JSP页面负责展示数据、收集表单参数不写任何业务逻辑和SQL。业务层Servlet控制器接收请求→调用Service→根据返回结果跳转或输出JSON。数据层DAO类通过JDBC操作MySQL数据库。再加一个基础设施包entity实体类对应数据库表、util工具类数据库连接、字符串处理、分页封装、filter过滤器登录校验、字符编码、非法字符拦截。日常开发中我习惯给Servlet加上一个公共父类BaseServlet把“根据action参数分发方法”的逻辑统一处理不然每个Servlet里都要写一坨switch。所有控制层的方法统一返回视图路径或JSON数据团队协作或后续扩展都会方便很多。一个请求从页面发起后的流转路径是浏览器发送HTTP请求 → Tomcat根据web.xml映射找到对应的Servlet → Servlet调用Service层处理业务 → Service调用DAO访问数据库 → 返回数据并set到request域 → forward到JSP渲染页面。这条链我要你背熟答辩必问。2.2 数据库表结构详细设计这是整个项目的骨架我按我的实际落地版给你列出来共6张表。每张表的字段都基于真实运行调试过你可以直接用。user表用户表字段名类型说明idint 主键自增用户IDusernamevarchar(50) 唯一登录用户名passwordvarchar(64)密码MD5加密存储real_namevarchar(50)真实姓名roletinyint1学生 2评委 3管理员avatarvarchar(255)头像路径collegevarchar(100)学院/班级create_timedatetime注册时间statustinyint1启用 0禁用note表笔记表字段名类型说明idint 主键自增笔记IDtitlevarchar(200)笔记标题descriptiontext笔记简介cover_imagevarchar(255)封面图片路径file_urlvarchar(255)笔记文件路径PDF等categoryvarchar(50)学科分类高数/英语/电路等user_idint上传者IDview_countint浏览量base_scoredecimal(5,1)基础评分评委平均分vote_countint投票数statustinyint0待审核 1已通过 2未通过create_timedatetime上传时间score表评委评分表字段名类型说明idint 主键自增评分IDnote_idint笔记IDuser_idint评分的评委或用户IDscoredecimal(3,1)打分值1-10分commentvarchar(500)评语create_timedatetime评分时间vote表投票表字段名类型说明idint 主键自增投票IDnote_idint笔记IDuser_idint投票用户IDcreate_timedatetime投票时间需要注意vote表和score表需要加上唯一约束比如( note_id, user_id )唯一防止同一用户重复投票或重复打分。category表分类表idnamesort。用来管理笔记分类比如高等数学、英语笔记、电路分析、编程语言等。notice表公告表idtitlecontentcreate_time。管理员发布评选通知前台显示在首页。2.3 评分机制与榜单规则展评系统的“灵魂”就是榜单规则这块讲不明白系统就只是一个图片陈列馆。我采用的方案是综合得分制综合得分 评委平均分 × 70% 大众投票数归一化值 × 30%这里有一个关键细节评委平均分要用“去掉一个最高分、去掉一个最低分”后的均值避免某一个评委给极端分影响公平。大众投票数由于数量级和分值不匹配不能直接参与计算要做归一化处理。归一化公式是该笔记票数 / 当前最高票数 × 10得到一个10分制下的投票分。我在代码里专门写了一个ScoreUtil工具类来计算最终得分并且在note表中用一个 total_score字段缓存结果这种方式比每次打开榜单都现算一遍性能要好得多。当有新的评分或投票发生时更新该字段。3. 核心功能模块实现要点3.1 用户注册登录与个人信息页用户模块最容易出现的问题是安全处理不到位。我见过不少同学习惯把明文密码存在数据库里答辩时老师看到基本都会发问。我在这个项目中使用的是MD5加盐哈希盐取用户名存的是“用户名密码”组合后的MD5值。从体验角度来说注册页要加前端校验密码长度、两次密码一致性、用户名唯一性控制层还要做一遍校验防止有人绕过页面直接构造HTTP请求提交非法数据。个人信息展示页面是题目相关热词里提到的一个点。很多人以为个人信息页就是显示username、real_name、avatar、college这几个字段其实我们可以做深一层展示当前用户上传了多少笔记、累计获得多少票数、平均评分是多少、历史评分记录。我在这块用了一个技巧在user表中的关联查询基础上通过ID返回包含统计指标的一次性视图数据。个人信息页的底部我还加入了“我上传的笔记列表”和“我的投票/评分记录”两块内容每一块独立分页用Ajax加载。这个设计在答辩时被老师专门表扬过认为是页面功能深度上花了心思。前端细节上用户上传头像我用的是canvas压缩后再提交把一张1MB的图片在前端压缩到200KB以内再上传不仅上传速度快也减小了服务器存储压力。这段代码不复杂核心是调用canvas的drawImage和toBlob方法。3.2 笔记上传与审核流程笔记上传是整个系统里最容易出Bug的环节。最典型的坑是表单用了multipart/form-data编码但接收端Servlet仍然是常规的request.getParameter(title)方式取参数结果所有表单字段取出来全是null。要解决这个问题必须使用commons-fileupload的ServletFileUpload类。使用过程中请求体里的普通字段和文件字段要分别处理普通字段用FileItem.isFormField()判断文件字段通过FileItem.getName()获取原始文件名再通过IOUtils和item.write()保存到服务器指定目录。目录路径不要写死成绝对路径更不要用“D:\upload\”这种在答辩时容易被挑刺的写法要使用ServletContext.getRealPath(/uploads)动态获取部署目录下的真实路径。另外需要防止上传同名文件覆盖我用的方法是UUID时间戳原始文件后缀重新生成文件名。管理员审核模块的处理方式我推荐将“待审核笔记数量”做成一个管理员登录后的红点提示点击进入待审列表。审核通过时设置状态为1不通过时填写原因并通过站内消息通知用户。这部分我用的是message表但我没有把站内消息做成一个复杂的东西就是一套简单的“通知”记录展示在用户消息中心即可。关于图片封面处理可以在上传封面时生成两张图一张原图一张经过等比缩放、最大宽度600px的缩略图。要注意的问题是内存溢出上传图片尺寸过大时使用BufferedImage进行处理很占堆内存。我当时处理方式是设定最大边缩放到800px如果原图本身不到800px则直接存储不处理这样既保证清晰度又控制了内存开销。3.3 评分与投票功能实现评分功能有两个核心问题要处理第一个是重复评分的幂等性。用户在评分页面提交表单走到Servlet判断当前用户是否已对该笔记评过分查score表是否存在user_id和note_id同时匹配的记录。已存在则给出提示。为了提升并发场景下的安全性最好在数据库层面加上唯一约束。这样即使两个请求同时到达数据库也能挡住重复插入。第二个是评分的可见性。评委打出的分数和评语默认前台的普通用户可以看到评语内容但不会直接公开单个评委的打分明细。这样做的好处是保护评委隐私让打分更客观。榜单页面展示的是综合得分和投票数而不是谁打了多少分。投票功能的交互细节也很重要。用户点击“投他一票”时需要判断三个前置条件当前用户是否已登录。是否已经投过这一份笔记。该笔记是否处于已审核通过状态。这三个判断都通过后事务性地执行“插入vote记录”和“更新note表vote_count”两个操作必须放在同一个事务里避免数据库里出现“有记录但计数没加”的不一致情况。这里我要提醒一个性能优化点大V榜页面如果每次都实时去LEFT JOIN两张表做统计在数据量变大后查询会很慢。我采取的方式是在note表中维护vote_count和total_score两个冗余字段展示时直接查冗余字段。这个“空间换时间”的设计在答辩环节也很有讲头可以向老师解释清楚。3.4 最美笔记榜单与页面加载优化榜单页面是整个项目要“输出”的核心成果页直接决定评委对系统的第一印象。我用两个标签来切换“人气榜”和“评分榜”。人气榜按投票数倒序排列评分榜按综合得分倒序排列。每张卡片展示封面、标题、上传者、票数、综合得分。点击进入详情页后展示大图预览、笔记PDF预览、评语列表和“去评分/投票”按钮。关于热词里面提到“jsp页面让加载完后刷新一次”这个问题我也专门处理过。这个需求的本质是JSP页面作为服务端渲染页面数据在服务端就已经输出完了但某些数据比如浏览量、投票数希望用户打开页面时能拿到最新值。我的处理方案是在页面底部加了一段JS在window.onload事件里用Ajax拉取统计数据再局部更新页面数字。注意千万不能在body加载完直接location.reload()那会造成死循环。我们真正要做的不是刷新整个页面而是异步刷新局部数据这才是更合理的方式。浏览量计数的实现我这里也和大家说一下我通过一个filter拦截详情页的请求计入redis或内存缓存来防刷同一IP在10分钟内重复访问只计一次。如果不想引入Redis简单做法是在ServletContext中维护一个Mapkey是noteId_IPvalue是时间戳每次请求先查Map再做计数更新。这个细节非常容易在答辩时加分。4. 实操过程与关键代码实现4.1 开发环境搭建细节我用的是Eclipse IDE for Java EE版本搭配Tomcat 9.0和MySQL 8.0JDK版本1.8。依赖管理方面因为JSP传统项目没有Maven我直接选择将jar包放到WEB-INF/lib目录下。需要准备的jar包包括jstl-1.2.jarJSP标准标签库mysql-connector-java-8.0.x.jarcommons-fileupload-1.4.jarcommons-io-2.11.0.jardruid-1.2.8.jar数据库连接池gson-2.8.9.jarJSON解析Ajax传输数据用在创建Dynamic Web Project时目标运行时选择Tomcat 9.0并勾选“Generate web.xml deployment descriptor”。Context root设置为“shuxiang”。4.2 数据库初始化语句CREATE DATABASE shuxiang DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE shuxiang; CREATE TABLE user ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(64) NOT NULL, real_name VARCHAR(50), role TINYINT DEFAULT 1, avatar VARCHAR(255), college VARCHAR(100), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, status TINYINT DEFAULT 1 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE note ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(200) NOT NULL, description TEXT, cover_image VARCHAR(255), file_url VARCHAR(255), category VARCHAR(50), user_id INT, view_count INT DEFAULT 0, base_score DECIMAL(5,1) DEFAULT 0, vote_count INT DEFAULT 0, total_score DECIMAL(5,1) DEFAULT 0, status TINYINT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_note_user FOREIGN KEY (user_id) REFERENCES user(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;注意创建表时一开始就把外键和唯一约束建好我在开发时吃过“表建好了忘加唯一约束后补很麻烦”的亏。score和vote表记得建UNIQUE KEYCREATE TABLE score ( id INT AUTO_INCREMENT PRIMARY KEY, note_id INT, user_id INT, score DECIMAL(3,1), comment VARCHAR(500), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uq_score (note_id, user_id), CONSTRAINT fk_score_note FOREIGN KEY (note_id) REFERENCES note(id), CONSTRAINT fk_score_user FOREIGN KEY (user_id) REFERENCES user(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;4.3 数据库连接工具类我用Druid连接池来管理数据库连接核心配置放在druid.properties中driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/shuxiang?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai usernameroot password你的密码 initialSize5 maxActive20 maxWait5000工具类封装时注意加载配置的代码要写在一个static代码块中public class DBUtil { private static DataSource dataSource; static { try { InputStream in DBUtil.class.getClassLoader() .getResourceAsStream(druid.properties); Properties props new Properties(); props.load(in); dataSource DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } public static void close(Connection conn, Statement stmt, ResultSet rs) { // 依次关闭注意先关rs再关stmt最后关conn } }这里不直接选择DriverManager.getConnection()是因为连接池在频繁开关连接时节约大量时间特别是详情页每次打开都要查询好几张表连接复用十分必要。4.4 笔记上传Servlet核心代码Servlet接收文件上传的核心逻辑如下我用Java语言标注的方式展示protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); User loginUser (User) request.getSession().getAttribute(loginUser); if (loginUser null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } Note note new Note(); try { DiskFileItemFactory factory new DiskFileItemFactory(); // 临时文件缓存目录超过2MB的文件先进这个目录 factory.setRepository(new File(D:/temp)); ServletFileUpload upload new ServletFileUpload(factory); upload.setHeaderEncoding(UTF-8); upload.setFileSizeMax(5 * 1024 * 1024); // 单个文件最大5MB upload.setSizeMax(20 * 1024 * 1024); // 一次请求总大小最大20MB ListFileItem items upload.parseRequest(request); String uploadPath getServletContext().getRealPath(/uploads); File dir new File(uploadPath); if (!dir.exists()) dir.mkdirs(); for (FileItem item : items) { if (item.isFormField()) { String fieldName item.getFieldName(); String value item.getString(UTF-8); if (title.equals(fieldName)) note.setTitle(value); if (description.equals(fieldName)) note.setDescription(value); if (category.equals(fieldName)) note.setCategory(value); } else { String fileName new File(item.getName()).getName(); // 扩展名白名单校验 String ext fileName.substring(fileName.lastIndexOf(.) 1).toLowerCase(); if (!(jpg.equals(ext) || png.equals(ext) || jpeg.equals(ext) || pdf.equals(ext))) { // 返回错误信息并停止 } String newName UUID.randomUUID().toString().replace(-, ) . ext; File saveFile new File(dir, newName); item.write(saveFile); if (cover.equals(item.getFieldName())) { note.setCoverImage(uploads/ newName); } else if (file.equals(item.getFieldName())) { note.setFileUrl(uploads/ newName); } } } note.setUserId(loginUser.getId()); note.setStatus(0); // 待审核 NoteDAO dao new NoteDAO(); dao.addNote(note); response.sendRedirect(request.getContextPath() /user/upload_success.jsp); } catch (Exception e) { e.printStackTrace(); response.sendRedirect(request.getContextPath() /user/upload_error.jsp); } }关于“上传目录放在了项目部署路径下”这个问题这里多说一句这样做在开发调试非常简单直接访问相对路径就能显示图片但项目重新部署时上传的文件会丢。如果希望加固可以将上传目录配置为服务器绝对路径并在Tomcat的server.xml中配置虚拟映射但毕业设计阶段能实现前者已经足够。老师通常只会问“你怎么防止路径泄露”你能答出使用UUID重命名且目录禁止执行脚本就够了。4.5 评委评分与榜单统计评分提交的Servlet中响应格式我设计为JSON前端用Ajax局部更新防误触ScoreVO vo new ScoreVO(); if (scoreDAO.isScored(noteId, userId)) { vo.setCode(1); vo.setMsg(您已评分不能重复评分); } else { Score s new Score(); s.setNoteId(noteId); s.setUserId(userId); s.setScore(Double.parseDouble(request.getParameter(score))); s.setComment(request.getParameter(comment)); boolean flag scoreDAO.addScore(s); // 内部开启事务更新note表 if (flag) { vo.setCode(0); vo.setMsg(评分成功); } else { vo.setCode(2); vo.setMsg(评分失败请重试); } } response.setContentType(application/json;charsetUTF-8); response.getWriter().write(new Gson().toJson(vo));榜单统计时需要根据综合得分排序。综合得分的SQL计算我一开始写成类似这样的SELECT n.*, (SELECT AVG(score) FROM score WHERE note_id n.id) AS avg_score FROM note n WHERE n.status 1 ORDER BY avg_score DESC;但这个写法在数据量变大后性能堪忧且没办法实现“去掉最高最低分”的逻辑。所以后来我改成在Service层计算逐份笔记计算评委均值后再更新到note表的base_score字段并在每次评分完成的事务中同步更新total_score。排序时直接ORDER BY total_score DESC即可。如果答辩时有老师问“为什么不用存储过程”你可以这样回应“项目规模不需要引入存储过程在Service层维护更能保留计算过程的业务可读性也方便SQL语句优化。”这个回答属于加分点因为展示了你对方案取舍的思考。5. 常见问题与排查技巧实录5.1 表单提交乱码页面出现问号这是JSP新手必踩的坑。出现乱码的根本原因是浏览器以UTF-8编码提交表单数据而Servlet默认按ISO-8859-1解码参数。处理办法是两层都要设置JSP页面顶部% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %Servlet的doPost最开头request.setCharacterEncoding(UTF-8)如果使用的是Tomcat 8.0以上版本GET请求默认已经是UTF-8解码但POST请求必须手动设置。对于文件上传表单由于使用multipart编码不能直接setCharacterEncoding解决需要在ServletFileUpload对象上setHeaderEncoding(UTF-8)。5.2 上传的图片不显示这种情况九成是路径问题。页面中img标签的src写成“uploads/xxx.jpg”但项目是放在“/shuxiang”这个根上下文下这时浏览器访问的是“http://localhost:8080/uploads/xxx.jpg”当然404。正确的写法是src前拼接上下文路径即img src${pageContext.request.contextPath}/uploads/${note.coverImage} /遇到这个问题时最直接的办法是浏览器按F12查看网络请求如果图片请求URL没有包含“/shuxiang”前缀那基本就是漏了上下文路径。5.3 数据库连接出错Public Key Retrieval is not allowed折磨了我好几个小时的问题。MySQL 8.0默认用caching_sha2_password认证连接时如果不加allowPublicKeyRetrievaltrueJDBC会直接报错。需要在url参数末尾加上useSSLfalseallowPublicKeyRetrievaltrue此外mysql-connector-java版本尽量和数据库版本对应如果继续沿用5.1.x老驱动连MySQL 8会出现SLL握手报错或无法加载类之类的新问题。我建议直接使用8.0.x驱动一劳永逸。5.4 评分时出现并发覆盖或票数对不上一个典型的业务异常是两个人同时给同一份笔记投票请求都通过了“是否已投票”校验导致数据库出现两条vote记录。解决办法是“乐观锁数据库约束”双保险在vote表上建立(note_id, user_id)唯一索引让数据库层拦截重复插入。更新note表的vote_count时使用原子操作UPDATE note SET vote_count vote_count 1 WHERE id ?不要先SELECT再UPDATE。这也是一个值得在文档的“系统亮点”部分特地说明的技术点因为体现出的并发设计意识很容易得分。5.5 页面加载后数据不刷新开头提过“JSP页面让加载完后刷新一次”这个需求。很多同学直接写成window.onload function() { window.location.reload(); }这样页面会陷入无限刷新循环。正确做法是在页面加载完成后用Ajax异步获取最新统计。以投票数为例window.onload function() { $.get(contextPath /api/stat?noteId noteId, function(res) { $(#voteCount).text(res.voteCount); $(#viewCount).text(res.viewCount); }); };这样做用户看到的数据就是最新的且不会打断当前页面的浏览状态。对于需要实时刷新的场景比如大屏展示榜单再单独通过setInterval定时拉取即可。5.6 JSP页面报错Unable to compile class for JSP这个错误绝大多数时候是jar包冲突。比如项目中同时存在了多个不同版本的servlet-api.jar或者jsp-api.jar放到了WEB-INF/lib目录下和Tomcat自带的类产生了冲突。解决办法很直接把lib目录下的servlet-api和jsp-api依赖全部移除因为Tomcat本身就会提供这些类。还有一种可能性是JDK版本过高Tomcat 9.0最高官方支持JDK 8和11如果你用JDK 17跑就会有兼容性问题。建议统一使用JDK 8环境稳定不容易出幺蛾子。6. 实操心得与个人建议6.1 开发顺序怎么排如果让我重新做一遍这个项目我会按照这个顺序推进先搭数据库和实体类再写登录注册打通整个MVC链路实现笔记上传最核心的流程实现审核与展示最后做评分、投票和榜单评分放最后有两个原因一是它依赖前面所有数据二是它是答辩时的“压轴亮点”当你前面积累稳定做最后一块时会顺手很多。6.2 答辩演示时的功能亮点演示时不要按用户操作的顺序机械走一遍。我建议按“核心亮点”来组织演示路径比如笔记上传走完一遍指出“文件重命名防冲突”和“类型白名单”展示页面演示指出“浏览量防刷设计”评分之后立刻看榜单排名变化指出“综合得分计算规则”最后切到管理员后台审核一条笔记和发布一条公告体现完整闭环6.3 一个容易被忽略的文档细节设计文档中的“系统实现”部分不要只是贴一堆代码截图。老师更想看到的是“遇到的问题和解决办法”。把这个项目里真实踩过的坑乱码、并发、上传大小限制、图片不显示写进文档里并配上你的解决思路整篇论文的实践性立刻上升一大截。6.4 写在最后这个题目的价值说实话“书香羲园”最美笔记展评管理系统的代码量并不大但它覆盖了JavaWeb开发中最重要的几个基础能力MVC分层设计、JDBC数据库操作、文件上传处理、并发控制策略和Ajax交互。做完这个项目你对JSP的运行机制和Web应用的请求处理流程都会有一个扎实的认知这是直接写SpringBoot界面操作所替代不了的。你在做的过程中如果遇到其他问题欢迎在下面评论区留言我会把常见的坑和解决办法继续补充进来。祝答辩顺利。
返回列表