JavaWeb课程设计实战:手写Servlet+JSP打造个人主页与家乡档案系统

发布时间:2026/10/17 10:41:25
JavaWeb课程设计实战:手写Servlet+JSP打造个人主页与家乡档案系统 1. 从课程设计到个人名片这个项目到底在做什么很多同学拿到“JavaWeb课程设计”这个题目时第一反应是去网上找一套现成的后台管理系统模板改改标题就交差。但“展示和管理自己和家乡的信息”这个题目其实比想象中更有发挥空间——它本质上是一个个人主页与家乡档案的结合体既要能对外展示又要能对内管理。说白了就是给自己和家乡做一个“数字名片”别人打开浏览器就能看到你的基本信息、家乡的风土人情而你自己登录后台就能随时修改这些内容。这个项目适合谁如果你是刚学完Servlet、JSP、JDBC这一套JavaWeb基础正愁没有一个完整项目练手的学生那这个题目再合适不过。它不像电商系统那样复杂但该有的东西一样不少前端页面展示、后端数据管理、数据库增删改查、用户登录验证、文件上传比如家乡照片。麻雀虽小五脏俱全。做完这一套你对JavaWeb的整个请求响应流程、MVC分层思想、数据库连接池这些概念会有完全不一样的理解。我见过太多同学把课程设计做成了“一次性代码”——能跑就行答辩完就扔。但如果你愿意多花两天时间把这个项目打磨成一个真正能用的个人站点它完全可以成为你简历上的一个亮点。面试官看到你有一个自己部署上线的个人主页比看到十个“仿某某商城”要有兴趣得多。所以这篇文章我会从零开始把整个项目的设计思路、技术选型、核心代码、踩坑经验全部摊开来讲你跟着做最后拿到的不只是一个课程设计而是一个真正属于你自己的作品。2. 技术选型为什么不用Spring Boot偏要手写Servlet2.1 课程设计的“限制”反而是优势如果你去问一个已经工作两年的Java开发让他做一个个人主页他大概率会直接上Spring Boot半小时搭完框架剩下的时间全在写业务。但课程设计不一样很多学校的评分标准里明确要求“不能使用高级框架”必须用原生Servlet和JSP。这不是老师故意为难你而是因为框架帮你屏蔽了太多细节而课程设计的目的恰恰是让你理解这些细节。我举个很简单的例子在Spring Boot里你写一个RestController加一个GetMapping浏览器就能访问到数据。但你知道这背后发生了什么吗请求是怎么从浏览器到TomcatTomcat怎么找到你的ServletServlet怎么把数据写回响应这一整套流程如果你只用过Spring Boot大概率是说不清楚的。而手写Servlet你就必须自己配置web.xml或者用注解自己处理doGet和doPost自己设置response.setContentType。这些看似繁琐的步骤恰恰是JavaWeb的核心。所以我的建议很明确课程设计阶段老老实实用Servlet JSP JDBC。等你把这套东西吃透了再去学Spring Boot你会发现一切都顺理成章。反过来如果你直接上框架遇到问题你连从哪里排查都不知道。2.2 具体技术栈与工具版本下面是我实际用的技术栈都是稳定且兼容性好的版本你可以直接抄作业技术/工具版本说明JDK1.8 或 111.8最稳学校机房大概率是这个Tomcat8.5 或 9.0不要用Tomcat 10包名从javax变成jakarta很多老代码跑不起来MySQL5.7 或 8.08.0需要改驱动类名和时区配置下面会讲Servlet3.1用注解方式配置不用web.xmlJSP2.3配合JSTL使用JDBC原生不用ORM框架自己写DAO前端HTML CSS JS BootstrapBootstrap用CDN引入省事IDEIntelliJ IDEA 或 EclipseIDEA对JavaWeb支持更好但Eclipse更轻量数据库连接池Druid 或 HikariCP不用也行但用了更专业注意如果你用的是MySQL 8.0JDBC URL必须加时区参数否则会报The server time zone value ???ú±ê×??±?? is unrecognized这个经典错误。具体写法在后面的数据库章节会详细说。2.3 项目分层结构别把所有代码塞进一个Servlet很多同学写Servlet喜欢在一个doGet方法里从数据库查数据、拼HTML、写响应一个方法两三百行。这样写能跑但后期改一个字段你就要翻半天。正确的做法是分层哪怕是小项目也要分层。我的分层是这样的entity层放实体类比如PersonalInfo、HometownInfo、User每个类对应数据库一张表字段一一对应。dao层放数据库操作类比如PersonalInfoDao、HometownInfoDao里面写增删改查方法只负责和数据库打交道。service层放业务逻辑比如PersonalInfoService调用dao层的方法做一些参数校验、事务控制。小项目里这一层可以很薄但一定要有。servlet层放Servlet类负责接收请求、调用service、把结果存到request域、转发到JSP页面。util层放工具类比如DBUtil数据库连接、FileUploadUtil文件上传。这样分层之后你的代码结构会非常清晰。答辩的时候老师问你“你的项目怎么分层的”你能说出来印象分直接拉满。3. 数据库设计两张核心表加一张用户表就够了3.1 表结构设计思路这个项目的数据模型其实很简单核心就是“谁的信息”和“哪里的信息”。我设计了三个表第一张表user用户表这张表用来做后台登录验证。课程设计不需要太复杂的权限系统一个管理员账号就够了。CREATE TABLE user ( id int(11) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL, password varchar(100) NOT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;密码我建议存MD5或者SHA-256的哈希值不要存明文。虽然课程设计没人会攻击你但这是一个习惯问题。你可以在UserDao里写一个md5()方法注册和登录的时候都转一下。第二张表personal_info个人信息表这张表存你自己的信息因为只有一个人所以实际上只会有一条记录。CREATE TABLE personal_info ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(50) DEFAULT NULL, age int(11) DEFAULT NULL, gender varchar(10) DEFAULT NULL, major varchar(100) DEFAULT NULL, school varchar(100) DEFAULT NULL, hobby varchar(255) DEFAULT NULL, intro text, avatar varchar(255) DEFAULT NULL, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;注意avatar字段存的是图片的路径不是图片本身。图片文件存在服务器磁盘上数据库只存路径这是标准做法。第三张表hometown_info家乡信息表这张表存家乡的信息可以有多条比如家乡的简介、特产、景点、美食每条一个分类。CREATE TABLE hometown_info ( id int(11) NOT NULL AUTO_INCREMENT, category varchar(50) DEFAULT NULL, title varchar(100) DEFAULT NULL, content text, image varchar(255) DEFAULT NULL, sort_order int(11) DEFAULT 0, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;category字段用来区分是“特产”还是“景点”还是“美食”前端展示的时候可以按分类筛选。sort_order用来控制显示顺序数字越小越靠前。3.2 数据库连接工具类一个坑点一个坑点地填数据库连接是JavaWeb里最容易出问题的地方。我见过太多同学卡在ClassNotFoundException: com.mysql.jdbc.Driver这个错误上。下面是我写的DBUtil类把常见的坑都处理了public class DBUtil { private static final String DRIVER com.mysql.cj.jdbc.Driver; private static final String URL jdbc:mysql://localhost:3306/personal_site?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue; private static final String USERNAME root; private static final String PASSWORD 你的密码; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USERNAME, PASSWORD); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { try { if (rs ! null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (ps ! null) ps.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }这里有几个关键点驱动类名MySQL 5.x用com.mysql.jdbc.DriverMySQL 8.x用com.mysql.cj.jdbc.Driver。写错了直接报ClassNotFoundException。URL参数useSSLfalse关掉SSL警告serverTimezoneAsia/Shanghai解决时区问题allowPublicKeyRetrievaltrue解决MySQL 8的公共密钥检索问题。这三个参数缺一不可。字符编码characterEncodingutf8保证中文不乱码。数据库建表的时候也要用utf8mb4。提示如果你不想每次都手动关闭连接可以用try-with-resources语法Java 7以上都支持。但课程设计里为了展示你对资源关闭的理解手动关闭反而更好。4. 后端核心功能实现从登录到增删改查4.1 登录功能Session和Filter的配合登录功能看起来简单但要做好也不容易。我的实现思路是用户访问login.jsp输入用户名和密码。表单提交到LoginServletServlet调用UserService.login()方法。Service调用UserDao.findByUsername()查出用户比对密码哈希。如果匹配成功把用户对象存到session.setAttribute(user, user)然后重定向到后台首页。如果失败把错误信息存到request域转发回login.jsp显示。关键点在于密码比对。不要直接比明文而是把用户输入的密码也做一次哈希再和数据库里的哈希值比。这样即使数据库泄露攻击者也拿不到原始密码。// LoginServlet核心代码 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username request.getParameter(username); String password request.getParameter(password); User user userService.login(username, password); if (user ! null) { request.getSession().setAttribute(user, user); response.sendRedirect(request.getContextPath() /admin/index.jsp); } else { request.setAttribute(msg, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } }光有登录还不够还要防止未登录用户直接访问后台页面。这时候就需要一个登录拦截FilterWebFilter(/admin/*) public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; Object user request.getSession().getAttribute(user); if (user null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }这个Filter拦截所有/admin/下的请求检查Session里有没有user对象。没有就踢回登录页。这样你就不用在每个后台Servlet里都写一遍登录检查了。4.2 个人信息管理单条记录的更新逻辑个人信息因为只有一条记录所以逻辑比较特殊。我的做法是页面加载时先查数据库里有没有记录。如果有就显示出来供编辑如果没有就显示空表单。提交时判断是新增还是更新。如果数据库里已经有记录就执行UPDATE如果没有就执行INSERT。这里有一个容易忽略的点如果用户只改了部分字段其他字段怎么办比如用户只改了爱好没改名字。如果你用UPDATE personal_info SET hobby? WHERE id?那名字字段不会被覆盖没问题。但如果你用UPDATE personal_info SET name?, age?, ... WHERE id?而表单里没传name那name就会被更新成null。所以我的建议是表单里所有字段都要有对应的input即使不修改也要提交原值。或者用动态SQL只更新非空字段。课程设计里用第一种方案更简单。// PersonalInfoServlet核心代码 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); PersonalInfo info new PersonalInfo(); info.setName(request.getParameter(name)); info.setAge(Integer.parseInt(request.getParameter(age))); info.setGender(request.getParameter(gender)); info.setMajor(request.getParameter(major)); info.setSchool(request.getParameter(school)); info.setHobby(request.getParameter(hobby)); info.setIntro(request.getParameter(intro)); // 处理头像上传 Part part request.getPart(avatar); if (part ! null part.getSize() 0) { String fileName System.currentTimeMillis() _ part.getSubmittedFileName(); String savePath request.getServletContext().getRealPath(/uploads/); part.write(savePath File.separator fileName); info.setAvatar(uploads/ fileName); } personalInfoService.saveOrUpdate(info); response.sendRedirect(personalInfo?actionedit); }文件上传这里用的是Servlet 3.0的Part接口需要在Servlet上加MultipartConfig注解。注意getRealPath在打包成war后路径会变生产环境建议用外部目录但课程设计里用getRealPath没问题。4.3 家乡信息管理一对多的增删改查家乡信息是一对多的关系一个人可以有多个家乡条目。所以后台需要一个列表页显示所有条目每条后面有编辑和删除按钮。列表页的实现很简单HometownDao.findAll()返回一个ListServlet把它存到request域JSP用JSTL的c:forEach遍历显示。新增和编辑共用一个表单页面。如果是新增表单action指向hometown?actionadd如果是编辑action指向hometown?actionupdateidxxx。编辑的时候要先根据id查出原数据回填到表单里。删除操作要注意级联删除。如果家乡条目有对应的图片文件删除记录的时候也要把图片文件删掉否则服务器上会堆积一堆没用的图片。这个逻辑可以放在Service层public void delete(int id) { HometownInfo info hometownDao.findById(id); if (info ! null info.getImage() ! null) { File file new File(uploadPath info.getImage()); if (file.exists()) { file.delete(); } } hometownDao.deleteById(id); }4.4 前台展示页面JSP JSTL的经典组合前台页面是给访客看的所以不需要登录。我的设计是index.jsp首页展示个人信息概览和家乡信息的前几条。personal.jsp个人详细信息页。hometown.jsp家乡信息列表页按分类展示。detail.jsp家乡信息详情页。这些页面都用JSP JSTL来渲染数据。比如在hometown.jsp里c:forEach items${hometownList} varitem div classcard img src${pageContext.request.contextPath}/${item.image} alt${item.title} h3${item.title}/h3 p${item.content}/p a hrefhometown?actiondetailid${item.id}查看详情/a /div /c:forEach注意${pageContext.request.contextPath}这个写法它保证你的路径在项目部署到不同context下都能正确。很多同学直接写/uploads/xxx.jpg结果项目名不是ROOT的时候就404了。5. 前端页面不用太花哨但要干净整洁5.1 页面布局的取舍课程设计的前端不需要多炫酷但一定要干净、整齐、能看。我见过有的同学用了一堆花哨的动画和渐变结果文字都看不清老师打开第一眼就皱眉。我的建议是用Bootstrap 5的CDN直接引入不用自己写太多CSS。主色调选一个舒服的颜色比如深蓝或墨绿不要大红大紫。字体用系统默认的不要引入奇怪的中文字体加载慢还容易乱码。图片统一尺寸不要一张大一张小看起来像拼凑的。首页的布局我推荐“上中下”结构顶部导航栏中间个人信息卡片下面家乡信息网格。导航栏放“首页”“我的家乡”“后台管理”三个链接。个人信息卡片左边放头像右边放姓名、专业、学校、爱好。家乡信息用卡片网格展示每行三个响应式布局。5.2 后台管理页面的交互细节后台页面是给自己用的所以效率优先。我的做法是左侧固定一个侧边栏放“个人信息管理”“家乡信息管理”“退出登录”三个菜单。右侧是内容区根据菜单切换显示不同的管理面板。表格列表页加一个“新增”按钮放在右上角。每条记录后面放“编辑”和“删除”两个按钮删除按钮加一个onclick确认框。function confirmDelete(id) { if (confirm(确定要删除这条记录吗删除后不可恢复。)) { window.location.href hometown?actiondeleteid id; } }这个确认框很重要。我测试的时候手滑点了一次删除直接把一条数据删了还好是测试数据。如果没有确认框正式数据被误删就麻烦了。5.3 中文乱码问题的终极解决方案中文乱码是JavaWeb的经典问题我把它拆成三个层面来解决第一层数据库层面。建库建表的时候用utf8mb4字符集连接URL里加characterEncodingutf8。第二层请求层面。在Servlet里doPost方法第一行写request.setCharacterEncoding(UTF-8)。如果是doGet请求Tomcat 8以后默认是UTF-8不用额外处理。但如果你用的是Tomcat 7需要在server.xml的Connector里加URIEncodingUTF-8。第三层响应层面。在Servlet里写response.setContentType(text/html;charsetUTF-8)。如果是JSP页面在页面顶部写% page contentTypetext/html;charsetUTF-8 languagejava %。这三层都设置好中文乱码基本就不会出现了。如果还有问题检查一下IDE的文件编码设置确保所有文件都是UTF-8。6. 部署与测试从本地跑通到上线访问6.1 本地Tomcat部署的注意事项在IDEA里配置Tomcat运行项目有几个地方容易出问题Artifact配置确保把项目的编译输出和依赖库都加到Artifact里否则运行时会报ClassNotFoundException。Application context建议设置成/personal这样访问路径就是http://localhost:8080/personal/比默认的/更清晰。端口冲突如果8080被占用改成8081或别的端口。改完之后记得同步改URL。热部署IDEA默认是热部署改了JSP不用重启但改了Java代码需要重启。如果发现改了代码没生效先检查是不是没重启。6.2 常见报错与排查思路下面是我在开发过程中实际遇到过的报错以及排查过程报错信息原因解决方案ClassNotFoundException: com.mysql.jdbc.Driver驱动类名写错或驱动包没导入MySQL 8用com.mysql.cj.jdbc.Driver检查lib目录下有没有mysql-connector-java.jarAccess denied for user rootlocalhost数据库密码错误检查DBUtil里的密码确认MySQL服务已启动The server time zone value ... is unrecognizedMySQL 8时区问题URL加serverTimezoneAsia/Shanghai404 Not Found路径写错或Servlet没配置检查WebServlet注解的路径检查JSP里的链接500 Internal Server ErrorJava代码抛异常看Tomcat日志定位到具体行号中文乱码编码没设置按上面三层方案逐一检查图片上传后不显示路径错误检查getRealPath返回的路径检查JSP里的${pageContext.request.contextPath}排查报错的核心思路是先看日志再看代码最后看配置。Tomcat的日志在logs/catalina.out里IDEA的控制台也会输出。不要凭感觉猜日志里写得清清楚楚。6.3 项目打包与部署到服务器如果你想让别人也能访问你的个人主页可以把项目打包成war部署到一台有公网IP的服务器上。步骤是在IDEA里用Maven的package命令打包生成personal.war。把war上传到服务器的Tomcat的webapps目录。启动Tomcatwar会自动解压。浏览器访问http://服务器IP:8080/personal/。注意服务器上的MySQL要单独安装和配置数据库要手动导入。另外服务器防火墙要开放8080端口否则外网访问不了。提示如果只是课程设计答辩本地跑通就够了不需要真的上线。但如果你想让这个项目成为简历亮点上线是值得的。7. 我在这个项目里踩过的坑和总结的经验7.1 文件上传的路径陷阱文件上传是我踩坑最多的地方。最开始我用request.getRealPath(/uploads)获取上传目录本地跑没问题。但后来我把项目打包成war部署到另一台机器上发现上传的图片全都不见了。原因是war包每次重启都会重新解压之前上传的文件被覆盖了。解决方案是把上传目录设在项目外部比如/data/uploads/然后在Tomcat里配置一个虚拟路径映射到这个目录。这样重启项目不会丢文件。课程设计里如果只在本地跑用getRealPath没问题但你要知道这个坑的存在。7.2 Session过期导致的“莫名其妙退出登录”Tomcat默认的Session过期时间是30分钟。如果你在后台编辑一条很长的信息超过30分钟才提交Session已经过期了提交的时候就会被Filter踢回登录页之前编辑的内容全丢了。这个体验非常糟糕。我的解决方案是在web.xml里把Session过期时间设长一点比如60分钟。同时在后台页面加一个定时器每25分钟发一个AJAX请求刷新Session。这样只要页面开着Session就不会过期。session-config session-timeout60/session-timeout /session-config7.3 数据库连接未关闭导致的“连接数耗尽”这个问题在课程设计里不容易发现因为访问量小。但如果你反复刷新页面每次请求都创建一个新连接而不关闭很快MySQL就会报Too many connections。我一开始写DAO的时候只在方法最后关闭了ResultSet和Statement忘了关Connection。后来加了一个finally块统一关闭问题就解决了。所以我的经验是只要用了Connection就必须在finally里关闭。不管有没有抛异常都要关。这是铁律。7.4 关于代码注释和文档最后说一个容易被忽略的点注释和文档。课程设计答辩的时候老师很可能会翻你的代码。如果你的代码里一个注释都没有变量名全是a、b、c老师的第一印象就会很差。我的建议是每个类头部写一段注释说明这个类是干什么的。每个方法上面写注释说明参数和返回值。关键逻辑行写行内注释解释为什么这么写。项目根目录放一个README.md写清楚项目怎么部署、数据库怎么导入、默认账号密码是什么。这些花不了多少时间但能让你的项目看起来专业很多。我见过一个同学代码写得一般但注释和文档非常完整最后拿了优秀。另一个同学代码功能更多但注释几乎没有只拿了中等。这个差距就是这么来的。7.5 后续可以扩展的方向如果你做完基础功能还有时间可以考虑加这几个扩展富文本编辑器把家乡介绍的textarea换成富文本编辑器支持加粗、插入图片、调整格式。评论功能访客可以在家乡信息下面留言你可以在后台审核和回复。数据统计用ECharts做一个简单的访问量统计图表展示每天有多少人访问了你的主页。多用户支持把个人信息表加上user_id字段支持多个用户各自管理自己的信息。这些扩展不需要全部做完选一两个你觉得有意思的加进去就能让你的项目在答辩时脱颖而出。但前提是基础功能要先跑通不要本末倒置。我在实际做这个项目的过程中最大的体会是不要怕麻烦该写的代码一行都不要省。很多同学为了省事把该分层的代码全塞在一起该关的连接不关该做的校验不做。结果就是项目能跑但经不起细看答辩的时候被老师一问就露馅。JavaWeb这套东西虽然老但它是理解Web开发本质的最好途径。你把这个项目认真做一遍以后学任何框架都会觉得轻松很多。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询