简介这份围绕个人微博应用展开的Java Web项目包面向具备前端与后端基础、希望借完整小项目串联知识的学习者可用于课程实验、毕业设计原型或自学练手重点覆盖会员管理、个人中心、消息发布管理等核心模块。压缩包共71个文件约3.33MB以22个Java源码、24个class文件、5个JSP页面和3个XML配置为主并配有CSS样式、JAR依赖、数据库文件及少量图片兼顾后端逻辑、页面展示与工程配置。已有224人学习。源码按Eclipse工程组织便于导入后梳理模块调用关系也可作为理解请求处理、权限控制、数据访问、接口设计及前端交互实现的参考。还可围绕注册登录、个人信息维护、微博发布与查询等功能对照代码从目录结构、数据库设计和前后端协作中积累可复用的开发思路对实验报告、功能扩展与排错也有帮助。1. 从“能发一条微博”倒推个人微博应用的需求边界课程设计里最容易被做成四不像的题目是“个人微博应用”。它看起来只是发文字、看列表但真正要回答的是Web原理及应用里的几个硬问题浏览器如何发请求、服务端如何保持登录态、数据库如何在分页和关注关系下取数以及前端Java Web页面如何把结果稳定渲染出来。这套方案面向需要交课程设计或轻量毕设的人范围收在“一个用户能注册登录、发微博、关注别人、在时间线看到关注内容、点赞评论、管理个人主页”。不做推荐算法不做分布式重点是把请求链路、MVC分层、会话与权限、数据库CRUD讲透。后端用Servlet/JSP打底前端用HTML/CSS/JS做渐进增强也方便后续替换成Spring Boot。2. 总体架构设计三层结构与六个模块的职责划分2.1 为什么用Servlet/JSP打底而不是直接上大框架这个题目的课程名是“Web原理及应用”选型首先要服务“能讲清原理”这个目标。直接上大框架当然写得快但答辩时被问到“登录态存在哪、过滤器顺序是什么、JSP什么时候编译、请求参数怎么到服务端”很容易答成“框架自动做的”。用Servlet/JSP打底HTTP请求、Session、Filter、Cookie、转发与重定向都在明面上代码量也不算大课程设计完全撑得住。我一般把技术栈定成Servlet负责控制器JSP负责视图JavaBean/Service负责业务JDBCPreparedStatement负责数据访问MySQL存业务数据前端用原生HTML/CSS/JavaScript加少量AJAX。连接池可以用常见的连接池组件也可以用课程里允许的简单JDBC工具类关键是不要把连接写死在Servlet里。下面这张表是选型时的取舍记录给答辩时讲“为什么不用某某框架”留依据。方案优点风险适用场景Servlet/JSP JDBC请求链路透明Web原理讲得清模板代码多需要自己封工具类课程设计、轻量毕设Servlet/JSP MyBatisSQL可控DAO层更干净多一层配置调试要会看映射想讲一点工程化前后端分离 大框架前端体验好开发快原理容易被框架遮住有一定基础、答辩要求偏工程真正落地时前端Java Web部分不要写成“JSP里塞满Java代码”。JSP只做展示和循环业务判断放到Servlet或ServiceJavaScript只负责表单校验、加载更多、点赞这类不刷新操作。这样做的好处是后面哪怕把JSP换成Thymeleaf或前端页面Controller层的请求处理逻辑还能保留大半。2.2 六个模块怎么拆职责边界与目录落点模块划分不要按“页面”拆而要按“业务能力”拆。个人微博应用通常拆成六块用户认证、微博内容、关注关系、互动点赞评论、时间线、公共支撑。每块只做自己那一摊事Servlet不直接拼SQLDAO不处理页面跳转。目录结构可以按下面这种方式落# 课程设计常用的Servlet/JSP工程目录示意 src/main/java/com/example/weibo/ controller/ # Servlet接收请求、校验参数、决定转发或重定向 service/ # 业务发布微博、关注、点赞等组合逻辑 dao/ # 数据访问只和数据库打交道 model/ # 实体User、Post、Comment 等 util/ # 工具数据库连接、密码处理、文件上传 src/main/webapp/ WEB-INF/views/ # JSP页面放在WEB-INF下防止直接访问 static/css/ # 样式 static/js/ # 前端脚本 upload/ # 课程设计可用的图片目录生产环境应外置WEB-INF/views这个位置是有意为之JSP不能通过浏览器地址直接访问只能由Servlet转发进去权限判断才有地方拦。upload放在webapp下是为了课程演示方便但要知道它有两个问题应用重新部署可能丢文件图片也可能被直接遍历。更稳妥的做法是把上传目录放到应用外部再用一个读取图片的Servlet做输出这个取舍在验收时能讲出理由就是加分项。六个模块的职责可以对应成下面这样用户认证注册、登录、退出、当前用户信息、登录过滤器。微博内容发布、删除、详情、个人主页列表。关注关系关注、取消关注、粉丝列表、关注列表。互动点赞、取消点赞、评论、评论列表。时间线关注流、个人流、分页加载。公共支撑编码过滤器、异常页、文件上传工具、SQL工具。这里最容易翻车的是“关注关系”和“时间线”混在一起写。关注是写操作时间线是读操作读写频率和索引设计都不一样。把关注放在关系模块把时间线查询放在内容模块后面排查“为什么关注了却看不到微博”时至少知道先看关系表有没有写进去再看时间线SQL的条件对不对。2.3 一次请求的完整链路从浏览器到JSP以“打开首页时间线”为例请求链路通常是浏览器发起GET /timeline?page1编码过滤器先设置请求和响应编码登录过滤器判断Session里有没有当前用户没有就重定向到登录页TimelineServlet取当前用户ID和页码调用ServiceService调用DAO查关注微博列表和总数Servlet把列表放进request作用域转发到/WEB-INF/views/timeline.jspJSP用JSTL循环输出页面底部再用JavaScript做“加载更多”。这条链路里有两个设计点要提前定。第一过滤器顺序要固定编码过滤器在前登录过滤器在后否则登录过滤器里输出中文提示会乱码。第二Servlet不要直接response.getWriter().write()拼HTML课程设计里这样写很快但后面加权限、加转义、加分页会越写越乱。用转发到JSP的方式视图和控制器边界清楚答辩时也能指着代码讲MVC。提示登录过滤器只拦需要登录的路径注册页、登录页、静态资源要放行放行清单写在过滤器初始化参数里不要散落在多个if里。3. 数据库与核心模型设计五张表撑起微博主链路3.1 表结构不是越全越好先保五张主表个人微博应用不需要一上来就设计十几张表。核心链路是“用户发微博、用户关注用户、用户对微博点赞评论”五张表就能撑住user、post、follow、comment、post_like。课程设计里可以加外键也可以只建索引由业务保证一致性但要把选择讲清楚。下面是一版可直接改字段的建表SQL-- 用户表密码只存哈希不存明文 CREATE TABLE user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32) NOT NULL UNIQUE, password_hash VARCHAR(128) NOT NULL, nickname VARCHAR(32) NOT NULL, avatar VARCHAR(255) DEFAULT NULL, bio VARCHAR(140) DEFAULT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); -- 微博表like_count、comment_count 是冗余计数减少列表页聚合查询 CREATE TABLE post ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, content VARCHAR(500) NOT NULL, image_url VARCHAR(255) DEFAULT NULL, like_count INT NOT NULL DEFAULT 0, comment_count INT NOT NULL DEFAULT 0, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_user_created (user_id, created_at) ); -- 关注表联合主键防止重复关注 CREATE TABLE follow ( follower_id BIGINT NOT NULL, followee_id BIGINT NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (follower_id, followee_id), KEY idx_followee (followee_id) ); -- 评论表post_id created_at 支撑详情页顺序展示 CREATE TABLE comment ( id BIGINT PRIMARY KEY AUTO_INCREMENT, post_id BIGINT NOT NULL, user_id BIGINT NOT NULL, content VARCHAR(300) NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_post_created (post_id, created_at) ); -- 点赞表联合主键保证一个用户对一条微博只能点一次 CREATE TABLE post_like ( post_id BIGINT NOT NULL, user_id BIGINT NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (post_id, user_id), KEY idx_user (user_id) );字段取舍上post表里的like_count和comment_count是故意冗余的。列表页如果每条微博都去count(*)一次十条微博就是十次聚合查询数据量一上来就明显。点赞和评论时用事务同时更新明细表和计数读列表时直接取计数字段。这个设计在课程设计里足够答辩时也能讲清“为什么允许冗余、怎么保证一致”。username和nickname分开是为了登录名稳定、展示名可改。密码字段叫password_hash而不是password提醒自己不要存明文。头像和微博图片只存URL或相对路径不把二进制塞数据库后面换存储方式时迁移成本低。3.2 时间线取数拉模式、推模式与混合模式的取舍时间线是这个项目最值得讲的查询。常见有三种做法拉模式用户打开首页时实时查“我关注的人发布的微博”推模式发布时把微博ID写进每个粉丝的收件箱混合模式大V走拉、普通用户走推。个人微博应用用户量小直接用拉模式最稳SQL也不复杂-- 关注流自己发布的 我关注的人发布的按时间倒序分页 SELECT p.id, p.content, p.image_url, p.like_count, p.comment_count, p.created_at, u.nickname, u.avatar FROM post p JOIN user u ON u.id p.user_id WHERE p.user_id ? OR p.user_id IN (SELECT followee_id FROM follow WHERE follower_id ?) ORDER BY p.created_at DESC, p.id DESC LIMIT ? OFFSET ?;第一个参数是当前用户ID第二个也是当前用户ID第三个是每页条数第四个是偏移量。ORDER BY里加p.id DESC是为了同一秒发布的两条微博也有稳定顺序否则分页时可能出现一条记录跨页重复。这个细节在演示时很不起眼但被问到“同一时间发的两条怎么保证不重不漏”时它就是答案。拉模式的缺点也明显关注很多人时子查询和排序成本会上来。课程设计的数据量通常没问题但要知道优化方向先查关注ID列表再用IN查询或者给follow表加覆盖索引再往上就是推模式或缓存。不要在方案里写“用了Redis就好了”先把SQL和索引讲清楚比堆组件更实在。3.3 索引与分页让翻页不随数据量崩掉索引不要等慢查询出现才加。这个项目里最常用的三条查询是按用户查微博、按关注关系查微博、按微博查评论。对应的索引分别是post(user_id, created_at)、follow(follower_id, followee_id)和comment(post_id, created_at)。建表时一起建后面用EXPLAIN看执行计划重点看有没有走索引、有没有临时表排序。分页参数也要有边界。page小于1按1处理size限制在1到20之间避免有人传size100000把服务端拖死。偏移量很大时LIMIT offset, size会越翻越慢课程设计可以接受但方案里要留一句优化路径用“上一页最后一条的created_at和id”做游标即WHERE (created_at, id) (?, ?)这样翻页不依赖偏移量。这个点不需要在课设里全部实现但答辩时能讲出来说明你知道分页不是只有LIMIT。4. 前后端核心模块实现登录、发微博与时间线4.1 登录鉴权Session与Filter的闭环登录模块的核心不是表单长什么样而是“密码怎么验、登录态放哪、后续请求怎么认人”。课程设计里用Session存当前用户ID最直接。密码不要明文比对注册时做哈希登录时用同一规则校验。下面是一个简化后的登录Servlet重点看参数校验、Session写入和错误回显WebServlet(/login) public class LoginServlet extends HttpServlet { private final UserService userService new UserService(); Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 统一编码避免用户名或错误提示乱码 req.setCharacterEncoding(UTF-8); String username req.getParameter(username); String password req.getParameter(password); if (username null || username.trim().isEmpty() || password null || password.isEmpty()) { req.setAttribute(error, 用户名和密码不能为空); req.getRequestDispatcher(/WEB-INF/views/login.jsp).forward(req, resp); return; } User user userService.login(username.trim(), password); if (user null) { req.setAttribute(error, 用户名或密码不正确); req.getRequestDispatcher(/WEB-INF/views/login.jsp).forward(req, resp); return; } // 登录成功后重建Session降低会话固定风险 req.getSession().invalidate(); HttpSession session req.getSession(true); session.setAttribute(loginUser, user); session.setMaxInactiveInterval(30 * 60); resp.sendRedirect(req.getContextPath() /timeline); } }userService.login内部先按用户名查用户再用同样的哈希规则比对密码查不到用户和密码错误返回同一个提示不告诉对方“用户名存在但密码错”。登录成功后先invalidate再新建Session是课程设计里成本很低的一个安全习惯。maxInactiveInterval设成30分钟演示时够用也不会让Session一直挂着。登录过滤器负责拦人。它从Session取loginUser为空就重定向到登录页并把原始访问地址作为参数带过去登录后能跳回来。过滤器只拦/timeline、/post/*、/follow/*、/like/*这类需要登录的路径登录页、注册页、静态资源放行。这里有一个常见错误过滤器里用sendRedirect之后又继续执行chain.doFilter导致响应提交后报错。正确做法是重定向后直接return。4.2 发微博表单、文件上传与事务边界发微博看起来是插入一条记录实际要处理四件事内容不能为空、长度不能超、图片可选、失败后要能回到发布页并保留提示。Servlet接收multipart/form-data表单时要给Servlet加MultipartConfig否则getPart拿不到文件。下面是一个可运行的最小实现WebServlet(/post/publish) MultipartConfig(maxFileSize 2 * 1024 * 1024, maxRequestSize 3 * 1024 * 1024) public class PublishPostServlet extends HttpServlet { private final PostService postService new PostService(); Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); User loginUser (User) req.getSession().getAttribute(loginUser); String content req.getParameter(content); if (content null || content.trim().isEmpty()) { req.setAttribute(error, 微博内容不能为空); req.getRequestDispatcher(/WEB-INF/views/publish.jsp).forward(req, resp); return; } if (content.length() 500) { req.setAttribute(error, 内容不能超过500字); req.getRequestDispatcher(/WEB-INF/views/publish.jsp).forward(req, resp); return; } String imageUrl null; Part imagePart req.getPart(image); if (imagePart ! null imagePart.getSize() 0) { // 只允许常见图片后缀文件名用UUID重写避免覆盖和路径穿越 String submitted imagePart.getSubmittedFileName(); String ext submitted.substring(submitted.lastIndexOf(.) 1).toLowerCase(); if (!ext.matches(jpg|jpeg|png|gif|webp)) { req.setAttribute(error, 只支持 jpg、png、gif、webp 图片); req.getRequestDispatcher(/WEB-INF/views/publish.jsp).forward(req, resp); return; } String fileName UUID.randomUUID() . ext; String uploadDir getServletContext().getRealPath(/upload); imagePart.write(uploadDir File.separator fileName); imageUrl req.getContextPath() /upload/ fileName; } postService.publish(loginUser.getId(), content.trim(), imageUrl); resp.sendRedirect(req.getContextPath() /timeline); } }参数说明maxFileSize限制单文件2MBmaxRequestSize限制整个请求3MB避免有人传超大文件getSubmittedFileName拿原始文件名但落盘时用UUID重写防覆盖也防../路径穿越后缀白名单只做第一层真实项目还要校验内容类型和图片头。postService.publish里只做一件事插入post表。如果后面加“发布成功给粉丝写收件箱”那才需要事务当前这条链路单表插入不要为了显得高级硬套事务。前端表单要设置enctypemultipart/form-data否则文件传不过去。内容输入框加maxlength500只是体验服务端必须再校验一次。提交按钮在提交后禁用防止连点造成重复发布这个细节在验收时很加分。4.3 时间线JSP首屏与AJAX加载更多时间线首屏用JSP渲染翻页用AJAX追加是课程设计里比较稳的组合。首屏由Servlet查第一页数据放进requestJSP用JSTL循环输出点击“加载更多”时JavaScript请求同一个Servlet返回JSON前端拼DOM。Servlet根据请求头或参数区分返回JSP还是JSON。下面先看JSP里的核心循环!-- timeline.jsp 片段JSTL输出c:out 做HTML转义 -- c:forEach items${postList} varpost article classpost img classavatar src${post.avatar} alt头像 a href${pageContext.request.contextPath}/user/${post.userId} c:out value${post.nickname}/ /a pc:out value${post.content}//p c:if test${not empty post.imageUrl} img classpost-image src${post.imageUrl} alt微博图片 /c:if span${post.likeCount} 赞/span span${post.commentCount} 评论/span /article /c:forEachc:out不是可选项。用户发布的内容里可能带script直接${post.content}会变成存储型XSS。昵称、评论、简介也一样处理。图片地址来自服务端生成风险低一些但也不要让用户直接控制src。加载更多的JavaScript用fetch请求/timeline?page2formatjson拿到数组后创建元素。页码由前端维护返回空数组就隐藏按钮。请求进行中要加锁避免用户连续点击发出两次同一个页码这是“时间线重复”最常见的来源之一。服务端返回的JSON字段名和JSP里的字段保持一致前端只做展示不在浏览器里拼SQL或做权限判断。5. 避坑与排查五个最容易翻车的点5.1 乱码、登录态与上传路径的排查顺序第一坑是中文乱码。现象是登录页提示正常提交后用户名变问号或者JSP输出中文变成乱码。原因通常不是数据库一家的问题而是请求、响应、数据库连接、JSP页面四处的编码不一致。排查顺序我一般固定为先看请求Content-Type有没有带charset再看编码过滤器是不是在最前面然后看response.setCharacterEncoding有没有在getWriter之前调用最后看数据库连接串和表字符集。解决方式是把编码过滤器映射到/*请求和响应都设UTF-8JSP页头声明pageEncodingUTF-8数据库连接串明确字符集。不要在东一处西一处写new String(name.getBytes(ISO-8859-1),UTF-8)那是把补丁当方案。第二坑是Session登录态丢失。现象是登录成功跳转首页后又回到登录页或者发布微博时取不到loginUser。原因可能是过滤器把登录页也拦了造成重定向循环也可能是sendRedirect之后没有return还可能是浏览器禁用了Cookie、Session超时、上传表单没带Session。排查时先看浏览器地址栏是不是在/login和/timeline之间来回跳再看过滤器放行清单最后看Session里到底有没有值。解决方式是过滤器放行登录、注册、静态资源重定向后立即返回上传表单所在页面确认已经登录演示前把Session超时调够但不要设成永不过期。第三坑是文件上传失败。现象是提交后内容发出去了但图片没上去或者直接报500。原因常见有四个表单没写enctypemultipart/form-dataServlet没加MultipartConfig上传目录不存在或没有写权限文件名重复导致覆盖。排查时先看请求是不是multipart再看getPart有没有抛异常然后看getRealPath(/upload)返回的目录在部署后到底在哪。解决方式是把上传目录做成可配置启动时检查并创建文件名用UUID限制大小和后缀失败时给用户明确提示不要把异常栈直接甩到页面上。5.2 时间线重复、SQL注入与XSS的排查顺序第四坑是时间线重复或漏条。现象是下拉加载后同一条第两次出现或者翻页时中间少了一条。原因通常有两个排序只按created_at同一秒的多条记录顺序不稳定前端“加载更多”没有防连点同一页码发了两次请求。解决方式是排序加唯一键ORDER BY created_at DESC, id DESC前端请求期间禁用按钮并用loading标记服务端对page和size做边界校验。如果用了冗余计数还要检查点赞和评论失败时计数有没有回滚否则会出现“显示3赞、点进去只有2条点赞记录”的不一致。第五坑是SQL注入和XSS。现象是搜索框输入 OR 11能把全表查出来或者微博内容里写一段脚本别人打开首页就弹窗。原因分别是DAO里用字符串拼接SQL以及JSP里直接用${content}输出。解决方式很明确所有带参数的SQL用PreparedStatement排序字段这类不能参数化的白名单校验JSP输出用户内容一律用c:out富文本如果必须支持走白名单过滤而不是简单替换script。这两个问题在课程设计里经常被忽略但答辩时只要被问一次“用户输入里带引号会怎样”答不上来就很被动。注意不要用前端校验代替服务端校验。前端maxlength、必填、图片后缀都能绕过服务端必须再验一遍。6. 验收与答辩用一条“发布—关注—回看”链路讲透方案验收时不要从“这是登录页、这是首页”开始念页面容易变成CRUD流水账。我一般让演示围绕一条完整链路走注册账号A并登录发布一条带图微博退出注册账号B并登录关注AB打开时间线看到A的微博B点赞并评论A重新登录在个人主页看到点赞数和评论最后A删除自己的微博确认时间线和详情页都不可见。这条链路把用户、内容、关系、互动、权限五块全串起来答辩时每走一步都能对应到一个模块和一张表。验收清单可以提前写成下面这样自己先跑一遍再让同学按清单点一遍验收项操作预期结果要留的证据注册登录新用户名注册后登录进入时间线Session中有当前用户截图数据库user表记录发布微博输入500字内内容并选图片列表出现新微博图片可访问截图post表记录关注关系B关注A后刷新时间线能看到A的微博follow表记录点赞评论B对A的微博点赞并评论计数增加评论列表出现post_like/comment表记录权限控制未登录直接访问发布接口跳转登录页浏览器地址变化异常输入空内容、超长内容、非图片文件页面提示错误不写库错误提示截图答辩时准备好三个“负向问题”比准备十个页面更有用同一秒发布两条微博怎么排序重复点击发布按钮会不会产生两条B能不能删除A的微博前两个对应排序和防重第三个对应权限校验。回答时不要只说“我加了判断”要指出判断在哪一层排序在SQL的ORDER BY防重在Servlet和前端按钮权限在Service里比对当前用户ID和微博作者ID。我以前演示时只走正常流程结果被问“同一秒发两条会怎样”时现场翻车后来发现SQL只按时间排序分页确实会重复。从那以后我每次验收都强制走一遍“空、重、越权”三条负向用例空内容提交、连续点两次发布、拿B的账号删A的微博。这三条过了方案才算能交付。需要对照完整工程时可以把这套表结构、目录和过滤器顺序直接拿去改配套的建表脚本和页面原型我已整理成同结构工程按需取用即可。希望帮到你。本文还有配套的精品资源点击获取