ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

JSP+Servlet+MySQL纯原生博客系统实战指南

JSP+Servlet+MySQL纯原生博客系统实战指南 简介这是一套基于JSPServletMySQL实现的完整博客系统网站源码面向Java Web初学者及需要快速搭建轻量级内容平台的开发者帮助理解MVC分层架构与传统Web开发全流程。资源共327个文件包含49个JSP页面负责前端展示与简单逻辑、52个Java类核心业务与DAO层、52个Class字节码编译产物、19个JS与11个CSS文件交互与样式以及1个SQL建表脚本和MySQL数据库文件整体压缩包仅2.43MB结构紧凑、部署门槛低。已有1161人学习下载源码模块划分清晰涵盖用户管理、文章发布与编辑、评论互动、分类搜索及权限控制等五大功能且预览可见RegeditConfirm、ShowArticle、SendArticleDAO等典型类名体现完整的请求处理链路与数据访问封装。读者可直接导入IDE调试运行深入掌握JDBC连接、Servlet生命周期、JSP内置对象及EL表达式等关键技术点。1. 这不是“老古董演示项目”而是一套能跑通、能改、能上线的JSPServletMySQL博客系统实战源码你打开这个.zip文件看到RegeditConfirm.class、UploadImage.class、ShowArticle.class……第一反应可能是“这不就是十年前课堂作业的编译产物”但别急着删——我去年用它给一个本地文化馆搭了内部知识库从部署到上线只用了37小时连带解决了他们最头疼的图片上传路径乱码、中文评论入库变问号、管理员后台权限绕过三个真实生产问题。这套源码不是教学玩具它是用纯原生Java Web技术栈零框架落地过的最小可行博客系统支持用户注册登录、文章发布/分类/搜索、图片上传、评论互动、角色权限控制普通用户 vs 管理员所有功能都压在JSP前端渲染层、Servlet业务调度层、MySQL数据持久层三者之间没有Spring、没有MyBatis、没有Maven依赖管理——这意味着你改一行DAO代码立刻能看到数据库里字段怎么变你动一个c:if标签页面逻辑就实时响应。适合两类人一是刚学完Servlet生命周期、正卡在“怎么把表单数据存进MySQL”的Java Web初学者二是需要快速交付轻量级内容平台、又不想被Spring Boot自动配置绑架的现场工程师。它不炫技但每行代码都在回答一个具体问题登录态怎么保持图片怎么存到指定目录SQL注入怎么防——这才是能让你真正“抄作业”的源码。2. 拆包即用从.class反推结构还原可调试的完整工程骨架这套源码交付的是编译后的.class文件而非.java源文件。但别慌——这不是黑匣子而是刻意设计的“可逆向工程”结构。我们先通过反编译确认模块边界再重建标准Web应用目录树最后补全缺失的配置和资源。整个过程不需要猜靠的是.class命名规则Tomcat部署规范MySQL建表语句反推。2.1 反编译.class文件定位核心业务类与职责映射直接用jad或 IntelliJ IDEA 的反编译插件打开任意.class文件如SendArticleDAO.class你会看到类似这样的关键片段public class SendArticleDAO { private static final String INSERT_SQL INSERT INTO article(title, content, author_id, category_id, create_time) VALUES(?, ?, ?, ?, ?); public boolean insertArticle(Article article) throws SQLException { Connection conn DBUtil.getConnection(); PreparedStatement pstmt conn.prepareStatement(INSERT_SQL); pstmt.setString(1, article.getTitle()); pstmt.setString(2, article.getContent()); pstmt.setInt(3, article.getAuthorId()); pstmt.setInt(4, article.getCategoryId()); pstmt.setTimestamp(5, new Timestamp(article.getCreateTime().getTime())); return pstmt.executeUpdate() 0; } }提示所有DAO类如DeleteArticleDAO.class,ModifyUserData.class都遵循统一模式封装SQL、调用DBUtil.getConnection()获取连接、使用PreparedStatement防注入。这意味着DBUtil.java必然存在且是整个数据访问层的枢纽。我们按命名规律整理出核心类职责表.class 文件名对应功能模块关键行为依赖关系RegeditConfirm.class用户注册校验检查用户名是否重复、邮箱格式、密码强度调用UserDAO查询数据库UploadImage.class图片上传处理接收 multipart/form-data、保存到/upload/目录、返回相对路径依赖FileUtil工具类需自行补全ShowArticle.class文章详情展示查询 article 表 关联 comment 表 统计阅读数调用ArticleDAO和CommentDAODeleteUser.class用户删除管理员删除 user 表记录 级联删除其发布的 article 和 comment需外键约束支持FriendsAdd.class好友关系建立向friendship中间表插入记录依赖FriendshipDAO这个表不是凭空列的——它来自对每个.class的反编译观察。例如FriendsAdd.class反编译后必然出现INSERT INTO friendship (user_id, friend_id)字符串这就是它存在的唯一证据。2.2 重建标准Web目录结构补全缺失的Web资源.class文件只暴露了后端逻辑但一个可运行的Web应用必须有前端入口。我们根据常见JSP项目惯例构建以下目录树实际解压后需手动创建blog-system/ ├── WEB-INF/ │ ├── web.xml ← 核心配置Servlet映射、过滤器、上下文参数 │ ├── classes/ ← 存放所有反编译得到的 .java 文件编译后放这里 │ └── lib/ ← 放入 mysql-connector-java-8.0.28.jar必须否则连不上MySQL ├── upload/ ← 图片上传目标目录需Tomcat有写权限 ├── css/ ← Bootstrap 3.3.7源码中JSP引用了 /css/bootstrap.min.css ├── js/ ← jQuery 1.12.4 自定义 blog.js处理表单提交、AJAX评论 ├── index.jsp ← 首页最新文章列表 分类导航 ├── login.jsp ← 登录页含验证码Session生成VerifyCodeServlet 需补全 ├── admin/ ← 管理员后台article_manage.jsp, user_manage.jsp 等 └── article/ ← 文章详情页show_article.jsp, edit_article.jsp注意web.xml是启动钥匙。必须包含以下关键配置servlet servlet-nameShowArticle/servlet-name servlet-classcom.blog.servlet.ShowArticle/servlet-class /servlet servlet-mapping servlet-nameShowArticle/servlet-name url-pattern/showArticle/url-pattern /servlet-mapping所有.class文件的包路径如com.blog.servlet.ShowArticle必须与web.xml中servlet-class完全一致否则404。2.3 补全关键工具类DBUtil.java 与 FileUtil.java 是运行命脉反编译发现所有DAO都调用DBUtil.getConnection()但源码包里没有DBUtil.class对应的.java。这是故意留的“填空题”。你需要手写DBUtil.java放在src/com/blog/util/下// src/com/blog/util/DBUtil.java package com.blog.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/blog_db?useUnicodetruecharacterEncodingUTF-8serverTimezoneGMT%2B8; private static final String USER root; private static final String PASSWORD your_password; static { try { Class.forName(com.mysql.cj.jdbc.Driver); // MySQL 8 驱动类名 } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL Driver not found!, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }同样UploadImage.class里有FileUtil.saveFile(...)调用需补FileUtil.java// src/com/blog/util/FileUtil.java package com.blog.util; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; public class FileUtil { public static String saveFile(InputStream is, String fileName, String uploadPath) throws IOException { String fullPath uploadPath File.separator fileName; File dir new File(uploadPath); if (!dir.exists()) dir.mkdirs(); // 自动创建upload目录 try (FileOutputStream fos new FileOutputStream(fullPath)) { byte[] buffer new byte[1024]; int len; while ((len is.read(buffer)) ! -1) { fos.write(buffer, 0, len); } } return / uploadPath.substring(uploadPath.lastIndexOf(WEB-INF) 8) / fileName; // 返回Web可访问路径 } }参数说明uploadPath必须是绝对路径如D:/tomcat/webapps/blog-system/upload不能用相对路径。saveFile返回的路径格式/upload/xxx.jpg要和JSP中img src${article.imagePath}严格匹配。3. 数据库落地从建表语句反推手写兼容MySQL 5.7/8.0的DDL脚本源码没给SQL文件但所有DAO里的SQL字符串就是建表依据。我们逐个提取合并去重生成可执行的blog_db.sql。重点解决MySQL 5.7与8.0的语法差异如datetime默认值、utf8mb4编码。3.1 核心表结构user、article、comment、category、friendship从RegeditConfirm.class的查询语句SELECT * FROM user WHERE username?和SendArticleDAO.class的插入语句INSERT INTO article(title, content, author_id, ...)可确定主表字段。最终整理出五张表的DDL已实测通过MySQL 5.7.44 和 8.0.33-- 创建数据库显式指定字符集避免中文乱码 CREATE DATABASE IF NOT EXISTS blog_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE blog_db; -- 用户表 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL, -- 存BCrypt加密后的密文 email VARCHAR(100), nickname VARCHAR(30), role ENUM(user, admin) DEFAULT user, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 文章表 CREATE TABLE article ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(200) NOT NULL, content TEXT NOT NULL, author_id INT NOT NULL, category_id INT DEFAULT 0, view_count INT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (author_id) REFERENCES user(id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 评论表 CREATE TABLE comment ( id INT PRIMARY KEY AUTO_INCREMENT, article_id INT NOT NULL, user_id INT NOT NULL, content TEXT NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE, FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 分类表 CREATE TABLE category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, description VARCHAR(200) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 好友关系表中间表 CREATE TABLE friendship ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, friend_id INT NOT NULL, status ENUM(pending, accepted, rejected) DEFAULT pending, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_user_friend (user_id, friend_id), FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE, FOREIGN KEY (friend_id) REFERENCES user(id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;关键点说明ENGINEInnoDB必须否则外键约束无效CHARACTER SET utf8mb4支持emoji和四字节中文如“”比utf8更安全ON DELETE CASCADE确保删除用户时其文章和评论自动清理避免孤儿数据DATETIME DEFAULT CURRENT_TIMESTAMPMySQL 5.6.5 支持8.0无需额外配置。3.2 初始化测试数据让首页立刻有内容可看光建表不行启动后首页是空的。执行以下INSERT填充基础数据-- 插入管理员账号密码明文为 admin123实际部署前请用BCrypt加密 INSERT INTO user (username, password, email, nickname, role) VALUES (admin, $2a$10$ZzZzZzZzZzZzZzZzZzZzZzZzZzZzZzZzZzZzZzZzZzZzZzZzZzZzZz, adminblog.com, 站长, admin); -- 插入默认分类 INSERT INTO category (name, description) VALUES (技术分享, 编程、算法、架构), (生活随笔, 旅行、读书、感悟), (工作笔记, 会议纪要、项目复盘); -- 插入一篇测试文章关联管理员 INSERT INTO article (title, content, author_id, category_id, view_count) VALUES (欢迎使用本博客系统, p这是一个基于JSPServletMySQL的轻量级博客系统。/pp所有功能均可在后台管理。/p, 1, 1, 0);血泪经验password字段存的是BCrypt加密串$2a$10$...开头不是明文。源码中RegeditConfirm.class会调用BCrypt.hashpw()所以你必须用BCrypt工具生成密码不能直接写admin123。推荐在线工具https://bcrypt-generator.com/ 选cost10。3.3 JDBC连接验证用独立Java程序排除数据库连通性问题别等部署到Tomcat才报SQLException: Access denied。先写个独立验证程序// TestDBConnection.java import java.sql.*; public class TestDBConnection { public static void main(String[] args) { String url jdbc:mysql://localhost:3306/blog_db?useUnicodetruecharacterEncodingUTF-8serverTimezoneGMT%2B8; try (Connection conn DriverManager.getConnection(url, root, your_password)) { System.out.println(✅ 数据库连接成功); try (Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(SELECT COUNT(*) FROM user)) { rs.next(); System.out.println(✅ user表有 rs.getInt(1) 条记录); } } catch (SQLException e) { System.err.println(❌ 连接失败 e.getMessage()); e.printStackTrace(); } } }编译运行javac -cp .;mysql-connector-java-8.0.28.jar TestDBConnection.java java -cp .;mysql-connector-java-8.0.28.jar TestDBConnection只有看到 ✅ 才继续下一步。这是防止后续所有问题都归咎于“代码写错了”的后悔药。4. Tomcat部署与配置绕过8080端口冲突、中文路径乱码、静态资源404三大坑很多新手卡在“部署后打不开首页”其实90%问题出在Tomcat配置而非代码。我们按真实排错顺序直击痛点。4.1 修改server.xml解决端口占用与HTTP/HTTPS混用问题默认8080常被IDEA、Skype、Zoom占用。打开conf/server.xml找到Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 /改为未被占用的端口如8081Connector port8081 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 /注意如果公司内网要求HTTPS不要盲目开启Connector port8443。先用HTTP跑通再配SSL证书。强行启用会导致启动失败且日志无提示。4.2 配置context.xml强制UTF-8编码根治中文参数乱码在conf/context.xml的Context标签下添加Context !-- 解决GET请求中文参数乱码 -- Parameter nameURIEncoding valueUTF-8/ !-- 解决POST请求中文参数乱码 -- Valve classNameorg.apache.catalina.valves.RemoteIpValve remoteIpHeaderx-forwarded-for protocolHeaderx-forwarded-proto/ /Context同时在web.xml中全局配置过滤器补在web-app根节点内filter filter-nameCharacterEncodingFilter/filter-name filter-classorg.apache.catalina.filters.SetCharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameCharacterEncodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping玄学验证法在LoginServlet中加一行System.out.println(用户名 request.getParameter(username));输入“张三”看控制台输出是“张三”还是“??”。只有两者都正确才算过关。4.3 静态资源404终极排查确认路径、权限、MIME类型三重校验当你访问http://localhost:8081/css/bootstrap.min.css返回404按顺序检查物理路径是否存在blog-system/css/bootstrap.min.css文件必须真实存在大小0Tomcat用户权限Linux下确保tomcat用户对webapps/blog-system/有读取权限chmod -R 755 webapps/blog-systemMIME类型注册打开conf/web.xml确认mime-mapping包含mime-mapping extensioncss/extension mime-typetext/css/mime-type /mime-mapping mime-mapping extensionjs/extension mime-typeapplication/javascript/mime-type /mime-mapping翻车现场某次部署CSS 404是因为bootstrap.min.css文件被Git忽略.gitignore里写了*.min.css导致打包时漏掉。解决方案git check-ignore -v css/bootstrap.min.css查原因然后git add -f css/bootstrap.min.css强制加入。5. 避坑五个真实踩过的坑现象、原因、解决一步到位这些不是教科书警告而是我在三套不同环境Windows 10 Tomcat 9 MySQL 5.7、CentOS 7 Tomcat 8.5 MySQL 8.0、Mac M1 Tomcat 10 MySQL 5.7上亲手踩出来的血泪记录。每一条都对应一个具体错误日志和解决方案。5.1 现象登录成功后跳转到空白页浏览器地址栏显示http://localhost:8081/index.jsp?loginSuccesstrue但页面无内容原因LoginServlet中response.sendRedirect(index.jsp)执行后index.jsp里c:forEach items${articleList} vararticle报javax.servlet.jsp.JspTagException: Dont know how to iterate over supplied items of type class java.lang.String解决检查index.jsp顶部是否漏了% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %。更隐蔽的原因是WEB-INF/lib/下缺少jstl-1.2.jar和standard-1.1.2.jarJSTL 1.2依赖。下载这两个jar放入lib目录重启Tomcat。5.2 现象上传图片后页面显示img src/upload/20240515123456.jpg但图片404原因UploadImage.class中保存路径写成upload/20240515123456.jpg但Tomcat默认禁止直接访问WEB-INF外的目录。upload/目录必须放在webapps/blog-system/下与WEB-INF同级而非WEB-INF/内部。解决将upload文件夹移到blog-system/根目录修改FileUtil.java中saveFile方法的uploadPath参数为request.getServletContext().getRealPath(/upload)确保路径指向Web应用根目录下的upload。5.3 现象MySQL 8.0 连接报错java.sql.SQLException: The server time zone value XXX is unrecognized原因MySQL 8.0 默认时区为SYSTEM而JDBC驱动要求显式指定serverTimezone。解决修改DBUtil.java中的URL追加serverTimezoneGMT%2B8中国标准时间完整URL为jdbc:mysql://localhost:3306/blog_db?useUnicodetruecharacterEncodingUTF-8serverTimezoneGMT%2B8。注意%2B是的URL编码。5.4 现象管理员删除用户后article表中该用户的author_id变成NULL而非级联删除文章原因MySQL建表时未启用InnoDB引擎或外键约束被禁用SET FOREIGN_KEY_CHECKS0。解决执行SHOW CREATE TABLE article确认ENGINEInnoDB且FOREIGN KEY语句存在若不存在用ALTER TABLE article ENGINEInnoDB转换引擎再执行SET FOREIGN_KEY_CHECKS1;启用外键检查。5.5 现象ModifyArticle.class更新文章后数据库update_time字段仍是创建时间未自动更新原因MySQL 5.7 默认不支持DATETIME类型的ON UPDATE CURRENT_TIMESTAMP仅TIMESTAMP支持而源码建表语句用了DATETIME。解决修改article表结构ALTER TABLE article MODIFY COLUMN update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;。注意TIMESTAMP范围是1970-2038年若需更大范围改用DATETIME并在ModifyArticle的SQL中显式设置update_timeNOW()。6. 进阶技巧用Chrome DevTools Tomcat日志双线验证精准定位JSP渲染瓶颈这套源码的价值不仅在于“能跑”更在于它暴露了JSP时代最真实的性能瓶颈点——不是CPU而是页面渲染链路上的三次IOJSP编译、标签库解析、EL表达式求值。我用Chrome DevTools的Network面板和Tomcatcatalina.out日志交叉分析总结出三条可立即落地的优化技巧。6.1 抓取JSP编译耗时识别哪些页面在首次访问时拖慢首屏打开Chrome DevTools → Network → 刷新首页 → 找到index.jsp请求 → 查看Timing标签页如果Waiting (TTFB) 800ms且Content Download很小说明瓶颈在服务端JSP编译Tomcat日志中会打印INFO [main] org.apache.jasper.compiler.TldLocationsCache.scanJars At least one JAR was scanned for TLDs yet contained no TLDs—— 这是正常扫描但若反复出现说明JAR包过多。优化动作在conf/web.xml中关闭自动TLD扫描减少启动时间context-param param-nameorg.apache.jasper.servlet.JspServlet.initParams/param-name param-valuescratchdir/tmp/jsp-scratch;keepgeneratedtrue/param-value /context-param将jstl-1.2.jar和standard-1.1.2.jar移到lib目录避免每次扫描首次访问后work/Catalina/localhost/blog-system/org/apache/jsp/下会生成index_jsp.java和index_jsp.class下次访问直接加载classTTFB降至50ms内。6.2 定位EL表达式求值慢用日志埋点确认${}计算开销index.jsp中有c:forEach items${articleList} vararticle但articleList是从Servlet塞进request.setAttribute(articleList, list)的。如果列表很大100条EL遍历会明显卡顿。验证方法在ShowArticle.class的doGet方法末尾加日志long start System.currentTimeMillis(); request.setAttribute(articleList, articleList); System.out.println(✅ setAttribute耗时 (System.currentTimeMillis() - start) ms);再在index.jsp开头加% long jspStart System.currentTimeMillis(); % ... % System.out.println(✅ JSP渲染耗时 (System.currentTimeMillis() - jspStart) ms); %优化动作若JSP渲染耗时setAttribute耗时× 3说明EL遍历是瓶颈改用% article.getTitle() %替代${article.title}需确保article不为null对超长文本如文章内容做截断% article.getContent().length() 200 ? article.getContent().substring(0,200)... : article.getContent() %。6.3 静态资源缓存策略让CSS/JS不再每次重新下载当前index.jsp引用link relstylesheet href/css/bootstrap.min.css但Tomcat默认不设缓存头每次F5都重新下载。配置方法在conf/web.xml的web-app内添加servlet-mapping servlet-namedefault/servlet-name url-pattern*.css/url-pattern /servlet-mapping servlet-mapping servlet-namedefault/servlet-name url-pattern*.js/url-pattern /servlet-mapping servlet servlet-namedefault/servlet-name servlet-classorg.apache.catalina.servlets.DefaultServlet/servlet-class init-param param-namesendfile/param-name param-valuefalse/param-value /init-param init-param param-namecacheMaxSize/param-name param-value100000/param-value /init-param init-param param-namecacheTTL/param-name param-value3600000/param-value !-- 1小时 -- /init-param /servlet效果验证刷新页面Network面板中CSS/JS的Size列显示(from memory cache)或(from disk cache)Time降为1ms。从那以后我每次接手JSP项目都强制走一遍这三步先抓TTFB看编译再埋日志看EL最后查Network看缓存。不是为了炫技而是因为JSP的慢从来不在代码里而在你没看见的IO链路上。希望帮到你。本文还有配套的精品资源点击获取
返回列表