简介:面向计算机专业课程设计与JavaEE入门进阶学习者的影视创作论坛完整项目包,围绕影视资讯、幕后创作心得、观众观影体验与交流讨论等模块展开,可用于课程设计选题、毕业设计参考或JavaEE综合实践练习。压缩包共142个文件、约54.95MB,以xml配置与rels关系文件为主体,配合png、jpeg界面截图与wdp素材图,覆盖工程配置、页面资源与图文素材等层面。另含源代码、数据库脚本与论文教程,便于按模块对照理解系统结构与实现流程。目前已有488人学习下载。读者可据此获得一套较完整的论坛系统实现方案:从需求分析、数据库表设计到前后端交互与页面呈现,配合论文文档梳理设计思路与关键技术点,适合需要快速搭建项目骨架、补足文档材料或对照学习JavaEE开发流程的同学参考。
1. 解压之后先别急着打开 IDE
解压一个课程设计压缩包,最怕看到的是满屏 JSP 和 Servlet,没有 README,连数据库有几张表都得从代码里反推。这份基于 JavaEE 的影视创作论坛就属于这一类:没有花哨的微服务,是一套典型的 Java Web 系统开发骨架——注册登录、按板块发帖、作品展示、评论回复、后台审核,功能闭环是完整的。它最适合两类人:正在做计算机专业课程设计、需要一个能跑通又改得动的参考工程的同学,以及刚学完 Servlet 想把 MVC 串一遍的初学者。下面按我拆包的顺序,从选型、表结构、核心链路,一路讲到部署和二次开发。
2. 技术栈拆解:Servlet + JSP + JDBC 这套“老三样”为什么够用
拿到一个陌生工程,我第一件事不是运行,而是看它用了什么、为什么这么用。影视创作论坛这类内容型站点,核心动作只有几个:读列表、看详情、写内容、管内容。请求量小、业务规则简单、数据关系清楚,这三条决定了它不需要上 Spring Cloud 那一套,Servlet 容器加一层薄薄的 MVC 就足够。
2.1 选型判断:为什么是 JSP 而不是前后端分离
这类课程设计工程的典型组合是 JDK 8 + Servlet 3.1 + JSP 2.3 + JSTL 1.2 + JDBC + MySQL 5.7 + Tomcat 8.5/9。前端多半是 Bootstrap 3 加 jQuery,页面跳转靠服务端渲染,表单同步提交。
这么选的理由很实在:JSP 可以直接在页面上写${post.title}和<c:forEach>,不用额外起一个 Node 服务,也不用配跨域;Tomcat 一个进程就把静态资源和接口全包了,答辩时插上 U 盘、拷个 war 包就能演示。代价是页面和 Java 代码耦合度高,改样式要动 JSP,改逻辑要动 Servlet。对课程设计来说,这个代价可以接受。
真正需要留意的分界点在“内容编辑器”上。影视创作论坛发帖经常要贴剧本、贴分镜说明,纯 textarea 体验很差。常见做法是引一个轻量富文本编辑器(如 wangEditor 早期版本),把 HTML 内容存进content字段,展示时用<c:out value="${post.content}" escapeXml="false"/>输出。这一步会顺带引出一个 XSS 问题,后面避坑章节会讲。
2.2 四个包的分层约定:entity / dao / service / servlet
工程里的 Java 源码一般按职责分包,看清包名基本就看懂了架构:
| 包名 | 职责 | 典型类 |
|---|---|---|
com.forum.entity | 与表一一对应的 POJO | User、Post、Reply、Board |
com.forum.dao | 只做 SQL,返回实体或集合 | UserDao、PostDao |
com.forum.service | 业务规则、事务边界、参数校验 | UserService、PostService |
com.forum.servlet | 收参数、调 service、转发或重定向 | LoginServlet、PostListServlet |
com.forum.filter | 编码、登录态、权限 | EncodingFilter、AuthFilter |
com.forum.util | 连接池、分页对象、字符串工具 | DBUtil、PageBean |
需要说明的是,很多课程设计工程会把 service 层省掉,Servlet 里直接 new 一个 Dao 就开始查库。这种写法短期省事,一旦要加“发帖同时给用户加积分”这类跨表操作,事务就没地方放了。我在拆这类项目时,倾向于至少在帖子发布、回复、删除这三处保留 service 层,其余查询可以让 Servlet 直连 Dao,不必为了分层而分层。
2.3 依赖与环境:把 jar 包和版本先对齐
工程一般不是 Maven 结构,WEB-INF/lib下直接躺着 jar。要跑起来,先确认这几样:
- JDK 1.8(Servlet 3.1 与 Tomcat 9 的组合最稳,JDK 17 会因为模块化限制在编译期报
javax找不到) - Tomcat 8.5 或 9.0(不要用 Tomcat 10,它的包名从
javax.servlet改成了jakarta.servlet,整份代码会全红) - MySQL 5.7(5.8/8.0 也能用,但驱动类名和连接串要改)
mysql-connector-java-5.1.x.jar、jstl-1.2.jar、standard-1.1.2.jar,这三个缺一个页面就会抛 500
数据源不引连接池也能跑,但每次请求都DriverManager.getConnection在演示时明显卡顿。我一般会写一个静态工具类,把驱动加载和连接获取封起来:
package com.forum.util; import java.io.InputStream; import java.sql.*; import java.util.Properties; public class DBUtil { private static String url; private static String username; private static String password; // 类加载时读一次配置,避免每次取连接都读文件 static { try (InputStream in = DBUtil.class.getClassLoader() .getResourceAsStream("jdbc.properties")) { Properties p = new Properties(); p.load(in); Class.forName(p.getProperty("jdbc.driver")); url = p.getProperty("jdbc.url"); username = p.getProperty("jdbc.username"); password = p.getProperty("jdbc.password"); } catch (Exception e) { // 静态块里抛异常,必须包成 Error,否则会被吞掉 throw new ExceptionInInitializerError("数据源初始化失败: " + e.getMessage()); } } public static Connection getConn() throws SQLException { return DriverManager.getConnection(url, username, password); } public static void close(ResultSet rs, Statement st, Connection conn) { // 关闭顺序与打开顺序相反,且每个都要单独 try,前一个抛异常不能影响后面的 try { if (rs != null) rs.close(); } catch (SQLException ignored) {} try { if (st != null) st.close(); } catch (SQLException ignored) {} try { if (conn != null) conn.close(); } catch (SQLException ignored) {} } }配套的jdbc.properties放在src根目录,编译后会自动落到WEB-INF/classes下:
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/film_forum?useUnicode=true&characterEncoding=utf8&useSSL=false jdbc.username=root jdbc.password=123456这里有两个参数值得单独说。useUnicode=true&characterEncoding=utf8少了,中文标题入库就是问号;useSSL=false少了,MySQL 5.7 以上会在控制台刷一堆警告,虽然不影响功能,但演示时不好看。驱动类名在 MySQL 8 的驱动里要改成com.mysql.cj.jdbc.Driver,连接串再加serverTimezone=Asia/Shanghai,否则时间字段会差 8 小时——这是我从一个“发帖时间总是显示凌晨”的 bug 里换来的经验。
3. 工程结构与数据表:5 张表 + 4 个包撑起整个论坛
搞清楚分层之后,下一步是把“代码长在哪”和“数据存成什么样”对齐。论坛类系统的表设计有个共同点:实体少、关系清楚、读多写少。这份工程的核心就是五张表。
3.1 目录结构:从 webapp 往下看
典型的 Eclipse 动态 Web 工程结构如下,如果你拿到的是 IDEA 工程,只是.idea和src/main的差异,业务代码位置一致:
| 路径 | 内容 |
|---|---|
src/com/forum/entity | User.java、Board.java、Post.java、Reply.java |
src/com/forum/dao | UserDao.java、PostDao.java、ReplyDao.java |
src/com/forum/service | UserService.java、PostService.java |
src/com/forum/servlet | LoginServlet.java、RegisterServlet.java、PostListServlet.java、PostDetailServlet.java、PostAddServlet.java |
src/com/forum/filter | EncodingFilter.java、AuthFilter.java |
WebContent/jsp | index.jsp、login.jsp、post_list.jsp、post_detail.jsp、post_add.jsp |
WebContent/admin | board_manage.jsp、post_audit.jsp |
WebContent/static | css/、js/、images/(含用户上传的作品封面) |
WebContent/WEB-INF | web.xml、lib/、classes/ |
web.xml里除了 welcome-file,主要就是 Filter 和 Servlet 的映射。如果工程用的是 Servlet 3.0 的@WebServlet注解,web.xml会很干净,只留一个EncodingFilter;两种写法都常见,看web.xml的metadata-complete是不是true就能判断。
3.2 五张表的关系与建表语句
用户发帖、帖子归属板块、帖子下面挂回复、管理员改状态,这四条关系决定了表结构。下面是我按这类工程常见字段整理的建表脚本,字段名与实体类的属性一一对应:
CREATE DATABASE IF NOT EXISTS film_forum DEFAULT CHARSET utf8mb4; USE film_forum; -- 用户表:role 区分普通会员(0)和管理员(1),status 用于封禁 CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, -- 存 MD5 摘要,不存明文 nickname VARCHAR(32), email VARCHAR(64), avatar VARCHAR(255), role TINYINT DEFAULT 0, status TINYINT DEFAULT 1, reg_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 板块表:影视创作论坛按内容分区,如剧本、短片、影评、器材 CREATE TABLE t_board ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(32) NOT NULL, description VARCHAR(255), sort INT DEFAULT 0, post_count INT DEFAULT 0 ); -- 帖子表:is_top 置顶、is_essence 加精、status 控制是否已审核 CREATE TABLE t_post ( id INT PRIMARY KEY AUTO_INCREMENT, board_id INT NOT NULL, user_id INT NOT NULL, title VARCHAR(128) NOT NULL, content TEXT, cover VARCHAR(255), -- 作品封面图路径 views INT DEFAULT 0, reply_count INT DEFAULT 0, is_top TINYINT DEFAULT 0, is_essence TINYINT DEFAULT 0, status TINYINT DEFAULT 1, -- 0 待审核 1 已发布 2 已删除 create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_board_time (board_id, create_time) ); -- 回复表:parent_id 支持楼中楼,不需要就把该字段留空 CREATE TABLE t_reply ( id INT PRIMARY KEY AUTO_INCREMENT, post_id INT NOT NULL, user_id INT NOT NULL, content VARCHAR(1000) NOT NULL, parent_id INT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_post (post_id, create_time) );几点设计上的取舍值得说清楚。t_post上的idx_board_time联合索引是为板块列表页准备的,列表查询永远是where board_id=? order by is_top desc, create_time desc,走这个索引能避免全表扫描。reply_count和post_count是冗余字段,每次回复时update ... set reply_count = reply_count + 1,读列表时就不用count(*)子查询了——这是论坛类系统最划算的一处反范式。
status字段是审核功能的关键。影视创作论坛里用户上传的是原创剧本、短片链接,管理员多半要过一眼再放出来。默认值设成 0(待审核)还是 1(直接发布),取决于课程设计要不要演示审核流程。如果答辩时需要展示“后台审核”这个功能点,就把它设成 0,前台列表查询统一加and status = 1。
3.3 初始化数据与首次导入
脚本一般随工程一起放在db/film_forum.sql,用命令行导入最快:
# 登录 MySQL 并执行脚本,注意路径换成你解压后的实际位置 mysql -uroot -p123456 --default-character-set=utf8mb4 < db/film_forum.sql # 验证:应该能看到 5 张表和至少一条管理员账号 mysql -uroot -p123456 -e "use film_forum; show tables; select id,username,role from t_user;"初始化数据至少要有一个管理员账号(role=1)和四个板块(剧本创作、短片分享、影评杂谈、器材交流)。如果脚本里没写,手工补一条:
INSERT INTO t_user(username, password, nickname, role) VALUES ('admin', MD5('123456'), '管理员', 1);密码用MD5()函数直接生成,登录时 Java 侧对用户输入做同样的摘要再比对。注意:MD5 在今天不算安全方案,但课程设计里够用;如果这是要真正上线的项目,换成 BCrypt 并加盐,改动点只在UserService.login()一处。
4. 核心链路:一个帖子列表请求从 Filter 走到 JSP 的 5 个节点
功能跑通与否,看一条完整链路就够。我选“打开某个板块的帖子列表”这条路径,因为它串起了编码过滤、登录校验、分页查询、JSP 渲染四个环节,几乎覆盖了这个工程的全部技术要点。
4.1 第一站:EncodingFilter 解决中文乱码
所有请求先进编码过滤器。这一步放在最前面,是因为 POST 表单里的中文一旦在getParameter之前没设编码,后面怎么补救都没用:
package com.forum.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException; @WebFilter("/*") public class EncodingFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { // 请求体编码,只对 POST 有效;GET 的编码由 Tomcat 的 URIEncoding 决定 req.setCharacterEncoding("UTF-8"); resp.setContentType("text/html;charset=UTF-8"); chain.doFilter(req, resp); } }req.setCharacterEncoding只影响请求体,GET 请求的参数在 Tomcat 8 之后默认按 UTF-8 解析,但如果拿到的是 Tomcat 7,需要在server.xml的 Connector 上加URIEncoding="UTF-8"。这就是为什么同样的代码在别人机器上好好的,在你这里搜索关键词全是问号。
4.2 第二站:AuthFilter 判断哪些路径要登录
论坛的规则是:列表和详情页游客可看,发帖、回复、后台必须登录。这个判断写在一个 Filter 里,比在每个 Servlet 里写if (session.getAttribute("user") == null)要干净得多:
@WebFilter("/*") public class AuthFilter implements Filter { // 白名单:静态资源、登录注册、帖子浏览不拦 private static final String[] WHITE = { "/login", "/register", "/post/list", "/post/detail", "/static/", "/jsp/login.jsp", "/jsp/register.jsp" }; @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; String uri = request.getRequestURI().substring(request.getContextPath().length()); for (String w : WHITE) { if (uri.startsWith(w)) { chain.doFilter(req, resp); return; } } // 后台路径额外校验角色,普通会员访问 /admin/ 一律打回首页 Object user = request.getSession().getAttribute("loginUser"); if (user == null) { response.sendRedirect(request.getContextPath() + "/jsp/login.jsp"); return; } if (uri.startsWith("/admin/") && ((com.forum.entity.User) user).getRole() != 1) { response.sendRedirect(request.getContextPath() + "/post/list"); return; } chain.doFilter(req, resp); } }两个 Filter 的执行顺序由web.xml里<filter-mapping>的声明顺序,或注解的类名字母序决定。注意:如果AuthFilter排在EncodingFilter前面,登录失败跳转时写入 session 的提示语会是乱码。稳妥做法是在web.xml里显式声明,把编码过滤器放第一个。
4.3 第三站:PostListServlet 收参数
列表页要处理三个参数:板块 id、页码、可选的关键词。Servlet 的职责很薄——解析参数、调 service、把结果塞进 request、转发 JSP:
@WebServlet("/post/list") public class PostListServlet extends HttpServlet { private final PostService postService = new PostService(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 参数缺失时给默认值,避免 NumberFormatException 直接把页面打崩 int boardId = parseInt(req.getParameter("boardId"), 0); int pageNo = parseInt(req.getParameter("page"), 1); String kw = req.getParameter("kw"); PageBean<Post> page = postService.listByBoard(boardId, kw, pageNo, 10); req.setAttribute("page", page); req.setAttribute("boards", postService.listAllBoards()); req.getRequestDispatcher("/jsp/post_list.jsp").forward(req, resp); } private int parseInt(String s, int def) { try { return Integer.parseInt(s); } catch (Exception e) { return def; } } }parseInt这个私有方法看着不起眼,却是很多工程翻车的地方——地址栏被人手改成?page=abc,服务端直接 500,答辩现场很尴尬。给默认值是最省事的防御。
4.4 第四站:分页查询与 PageBean
分页是论坛列表的核心。两条 SQL:一条count拿总数算总页数,一条limit拿当前页数据。封装成一个通用PageBean后,所有列表页都能复用:
public class PageBean<T> { private int pageNo; // 当前页,从 1 开始 private int pageSize; // 每页条数 private int total; // 总记录数 private List<T> list; // 当前页数据 public int getTotalPage() { return total % pageSize == 0 ? total / pageSize : total / pageSize + 1; } public boolean isHasPrev() { return pageNo > 1; } public boolean isHasNext() { return pageNo < getTotalPage(); } // getter / setter 省略 }对应的 DAO 方法,注意limit的两个参数是偏移量和条数,偏移量从 0 开始:
public PageBean<Post> listByBoard(int boardId, String kw, int pageNo, int pageSize) { PageBean<Post> page = new PageBean<>(); page.setPageNo(pageNo); page.setPageSize(pageSize); StringBuilder where = new StringBuilder(" where status = 1 "); List<Object> args = new ArrayList<>(); if (boardId > 0) { where.append(" and board_id = ? "); args.add(boardId); } if (kw != null && !kw.trim().isEmpty()) { where.append(" and title like ? "); args.add("%" + kw.trim() + "%"); } String countSql = "select count(*) from t_post " + where; String dataSql = "select * from t_post " + where + " order by is_top desc, create_time desc limit ?, ?"; // ... 执行 countSql 得到 total,再补两个分页参数执行 dataSql,映射成 List<Post> return page; }这里拼接的是where条件串,参数一律走?占位符。不要把kw直接拼进 SQL——' or 1=1 --这种输入会让整个帖子库裸奔,是课程设计里最常见的安全扣分项。另外order by is_top desc, create_time desc的顺序不能反,否则置顶帖会被新帖淹没。
4.5 第五站:JSP 渲染与分页条
数据到了 JSP,用 JSTL 遍历即可,不需要写 Java 脚本片段:
<c:forEach items="${page.list}" var="post"> <div class="post-item"> <a href="${pageContext.request.contextPath}/post/detail?id=${post.id}">${post.title}</a> <span>${post.nickname} · <fmt:formatDate value="${post.createTime}" pattern="yyyy-MM-dd HH:mm"/></span> <span>浏览 ${post.views} / 回复 ${post.replyCount}</span> </div> </c:forEach> <%-- 分页条:上一页/下一页用 PageBean 的布尔方法控制,避免页码越界 --%> <c:if test="${page.hasPrev}"> <a href="?boardId=${param.boardId}&page=${page.pageNo - 1}">上一页</a> </c:if> <c:if test="${page.hasNext}"> <a href="?boardId=${param.boardId}&page=${page.pageNo + 1}">下一页</a> </c:if>fmt:formatDate需要页面顶部<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>,少这一行,时间会直接打印成Tue Mar 12 10:20:31 CST 2024这种 Java 默认格式。列表页做完,详情页、发帖页、回复页基本是同一套模式复制,理解了这条链路,其余页面看一眼就懂。
5. 部署与避坑:从解压到首页打开要过的六道关
代码看懂不等于能跑起来。这一章是我每次拿到类似工程必定会走的部署顺序,以及几个最容易翻车的地方。
5.1 六步部署流程
# 1. 建库导数据(脚本在 db 目录下) mysql -uroot -p --default-character-set=utf8mb4 < db/film_forum.sql # 2. 改 jdbc.properties 里的账号密码,务必和本机一致 # 3. 确认 WEB-INF/lib 下有 mysql-connector、jstl、standard 三个 jar # 4. IDE 中把工程加入 Tomcat,Deployment 里确认 context path(如 /forum) # 5. 启动 Tomcat,先看控制台有没有 ClassNotFoundException # 6. 浏览器访问 http://localhost:8080/forum/post/list 验证列表页先访问列表页而不是首页,是有讲究的:列表页依赖数据库连通性,能打开就说明连接串、驱动、建表三件事全对;首页往往只有静态内容,打开了也证明不了什么。
5.2 六个高频翻车点
现象一:页面报 404,控制台无异常。原因多半是@WebServlet("/post/list")这类注解没生效,工程web.xml的metadata-complete="true"会关闭注解扫描。解决办法是把该属性删掉或改成false,或者改在web.xml里显式配置<servlet-mapping>。
现象二:所有中文变问号或乱码。三个地方要逐个排查:连接串有没有characterEncoding=utf8、Filter 有没有在getParameter之前执行、数据库和表的字符集是不是utf8mb4。我遇到过一次建库时用了默认的 latin1,改连接串怎么都没用,最后alter database film_forum character set utf8mb4才解决。
现象三:登录成功但一刷新就掉线。通常是 JSP 页面里写死了session="false",或者 Filter 里用request.getSession()时又 new 了一个 session,和登录时存的不是同一个。检查登录 Servlet 里存的是request.getSession().setAttribute("loginUser", user),取的时候也用request.getSession(),不要用getSession(true)重复创建。
现象四:图片上传成功但页面显示裂图。上传目录如果放在工程根目录下的upload/,重新部署 war 包时整个目录会被覆盖清空。稳妥做法是把上传路径配到工程外部(如D:/forum_upload/),再在 Tomcat 里配一个虚拟目录映射;或者接受“演示前重新传一次”的妥协。
现象五:SQL 报Unknown column 'xxx' in 'field list'。实体类加了字段、数据库没加列,或者反过来的情况在二次开发时特别常见。这类错误的排查顺序是:先看报错里的列名,去表结构里desc t_post确认,再回实体类对属性名。
现象六:发帖提交后浏览器提示重复提交表单。刷新页面会重复插数据,这是转发和重定向没分清。写操作(发帖、回复、删除)处理完必须response.sendRedirect(),不能forward()。改一行就能解决,但很多工程里就是错的。
5.3 出问题先看哪三处
第一看 Tomcat 控制台的catalina.out或 IDE 控制台,异常栈的第一行Caused by通常就是根因;第二看浏览器 F12 的 Network,请求状态码是 404 还是 500 决定了排查方向完全不同;第三看 SQL 日志,如果工程没配 log4j,最土的办法是在 DAO 方法里临时System.out.println(sql)打一下拼好的语句,复制到 MySQL 客户端跑一遍,问题立刻现形。这套三板斧用完,九成问题都能定位。
6. 二次开发:在现有骨架上加“作品集 + 审核流”的两处改法
这份工程最值钱的地方不是功能多,而是骨架完整、改动面小。我在它上面做过两次扩展,一次是加“个人作品集”,一次是把审核流从“管理员手动放行”改成“积分自动放行 + 抽检”,两次改动都控制在半天以内。
第一处,作品集。思路是复用t_post而不是新建表,给t_user加works_count冗余字段,个人主页直接查select * from t_post where user_id=? and status=1 order by create_time desc,前台加一个/user/works的 Servlet 即可。真正要动的是权限:作品集页面的删除按钮必须校验session里的用户 id 与帖子的user_id一致,否则改个 URL 参数就能删别人的帖子。这段校验我建议写在 service 里,别放在 JSP 用c:if隐藏按钮——隐藏不等于拦截。
第二处,审核流。原逻辑是status从 0 到 1 由管理员逐条点。改成自动放行的关键是在PostService.add()里加一个分支:
public int add(Post post, User user) { // 老用户(发帖数 >= 3 且未被封禁)直接发布,新用户进待审核队列 boolean trusted = user.getStatus() == 1 && postDao.countByUser(user.getId()) >= 3; post.setStatus(trusted ? 1 : 0); int id = postDao.insert(post); if (trusted) { // 只有真正发布的帖子才更新板块计数,避免待审核内容把统计撑虚 boardDao.incrPostCount(post.getBoardId()); } return id; }改动只有三行,但它把“谁能直接发、谁要等审核”这条业务规则从散落在各处的 if 收敛到了一处。验证方法是准备两个账号,一个发满三条、一个刚注册,分别发帖后查t_post.status,能稳定得到 1 和 0 就说明分支正确。
还有一处容易被忽略的改造点:帖子详情页的浏览量。update t_post set views = views + 1 where id = ?放在每次详情查询里,压力不大时没问题,但同一个人反复刷新会刷出虚高数字。我一般的做法是用 session 记一个已访问帖子 id 的 Set,同一个会话内只加一次,代码不到十行,演示时数据好看很多。
顺便给个改造顺序的建议,按依赖关系来最省事:
| 顺序 | 改动 | 影响文件 |
|---|---|---|
| 1 | 加数据库字段 | film_forum.sql、对应实体类 |
| 2 | 改 service 业务规则 | PostService、UserService |
| 3 | 加或改 Servlet 路由 | web.xml或注解 |
| 4 | 最后动 JSP | 页面展示与表单 |
从后往前改必翻车——先改 JSP 再改表结构,中间那段时间页面永远报列不存在,改一次错一次。我头一回拆这类课程设计,图省事直接从 JSP 下手,结果字段名对不上、分页参数丢、编码又乱了一遍,折腾到凌晨才发现是改的顺序反了。从那以后我每次动这类 JavaEE 老工程,都强制先跑通列表页、再动数据库、最后碰页面,一步一验证。希望帮到你。
本文还有配套的精品资源,点击获取