☰
JavaEE+SSM影视创作论坛系统设计与实现
2026/11/2 14:26:12 网站建设 项目流程

简介:这份资源是面向计算机专业课程设计与Java EE入门者的影视创作论坛完整项目方案,包含程序源代码、数据库脚本与配套论文教程,可用于课程设计选题、毕业设计参考或Java Web综合实践练习。压缩包共142个文件、约54.95MB,主要由xml配置与页面描述文件、rels关系文件、png与jpeg界面截图及少量wdp格式图片组成,xml与rels支撑论坛页面结构与站点配置,截图则记录了论坛运行界面,便于读者对照理解页面效果与功能模块。目前已有487人学习下载,可作为同类课题的参考样本。资源围绕影视作品交流场景展开,涵盖影视资讯发布、幕后创作心得分享、观众观影体验交流与心得体会讨论等论坛核心板块,读者可据此梳理用户管理、帖子发布与回复、版块分类、数据库表结构设计等关键实现思路,并结合论文教程理解需求分析、系统设计与编码落地的完整流程,减少从零搭建的摸索成本。

1. 从「作品发出去没人看」倒推:这套 JavaEE 影视创作论坛要解决什么

课程设计选题里,「论坛」是被选烂的题目,但大部分做出来的是一个超大号留言板:所有人发所有内容,一个时间倒序列表从头刷到尾。影视创作场景不是这样——剧本、短片、混剪、影评、器材心得,五类内容的阅读姿势、附件形态、评审标准完全不同,混在一个流里,创作者的作品撑不过三天就被刷到看不见的地方。这套基于 JavaEE 的影视创作论坛的设计与实现,核心动作就四个:分区让内容归口,作品带封面和附件独立成页,审核决定能不能进首页推荐位,评论与点赞围绕作品沉淀讨论。它对应两拨人:一是正在找计算机专业课程设计题目、需要一份能跑能演示能答辩的完整系统的学生;二是要出一份系统开发交付方案、希望结构可复用的人。下面按需求、选型、库表、实现、避坑、验收的顺序,把这套东西拆开讲。

2. 需求与技术选型:SSM 三层结构下六个模块的职责边界

2.1 三类角色的动线与功能清单

论坛类系统最容易犯的错,是把「用户」当成一个角色,结果管理员和普通创作者共用一套页面,权限判断散落在 JSP 的 if 里。动手前先把角色切开:游客只能浏览已审核通过的作品和评论,检索、翻页、看详情;注册创作者能发作品、传附件、写评论、点赞收藏、收通知;管理员和版主负责板块维护、作品审核、举报处理、用户封禁。角色不同,动线不同,页面菜单也不同。

角色典型动线关键功能点数据可见范围
游客首页 → 板块 → 作品详情检索、排序、看评论仅审核通过内容
注册创作者登录 → 发布 → 我的作品 → 消息发帖、传附件、评论回复、点赞收藏自己的全部状态内容
版主/管理员后台 → 待审列表 → 审核 → 举报处理审核、下架、封禁、板块增删全量内容与用户

功能清单控制在六个模块内:账号模块(注册登录、资料、头像)、板块模块(分类维护、排序、是否开放发帖)、作品模块(发布、编辑、附件、封面、标签)、互动模块(评论盖楼、点赞、收藏)、检索模块(关键词、板块过滤、最新/最热排序)、后台模块(审核、举报、用户管理、操作日志)。模块数量看着不多,但每个都要有列表页、详情页、表单页和至少一个后台管理页,工作量刚好落在一个人能做完、又能讲出东西的区间。

2.2 技术选型:为什么是 SSM 而不是一把梭

JavaEE 体系下做课程设计,主流是 SSM(Spring + SpringMVC + MyBatis)配 JSP,打成 war 包丢进外置 Tomcat。这个组合不是最时髦的,但它有三个现实优势:分层清晰,答辩时老师问「你的业务逻辑写在哪」你能直接指到 Service;SQL 可见,MyBatis 的 XML 里每条语句都能拿出来讲;部署简单,单机 Tomcat + MySQL 就能演示,不用起 Node、不用配跨域。

如果学校不限制框架,Spring Boot 会更省事,内嵌 Tomcat 直接 java -jar 就能跑。这里按传统 war 包写,是因为不少课程设计大纲明确要求「基于 JavaEE 规范」,而且外置容器部署在答辩机房里更稳,不会因为端口被占或者 JDK 版本对不上当场翻车。

层次选型版本建议选它的理由
容器Tomcat8.5 / 9.0war 部署,机房环境兼容性好
框架Spring + SpringMVC5.3.x事务与 MVC 分离,教学资料多
持久层MyBatis3.5.xSQL 可控,便于讲索引与优化
数据库MySQL5.7 / 8.0唯一索引、联合索引都能用上
视图JSP + JSTL2.3服务端渲染,无跨域问题
构建Maven3.6+依赖统一,换机器不用手拷 jar

依赖部分只需要引这些核心包,剩下的按需加:

<!-- pom.xml 关键依赖,版本统一在 properties 里管理 --> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.30</version> </dependency> <!-- SpringMVC 与 Servlet API,scope 必须是 provided,否则和 Tomcat 冲突 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.30</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <!-- MyBatis 与 Spring 整合包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.1.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> <!-- 文件上传解析器依赖,传统 SSM 用它接 MultipartFile --> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.5</version> </dependency> </dependencies>

provided这个 scope 是最容易忽略的一行:Servlet API 必须由容器提供,打进 war 包里会和 Tomcat 自带的版本打架,启动时报ClassCastException或者NoSuchMethodError,排查半天最后发现是打了两个 servlet-api。

2.3 工程目录与分层约定

分层不是摆样子,得有硬约定:Controller 只做参数校验和视图跳转,不写 SQL;Service 承担事务边界和业务规则;Mapper 只做数据访问;实体类不直接返给页面,列表页用 VO 裁剪字段。这一条约定能挡掉后面一半的性能问题。

src/main/java/com/example/forum ├── controller # 只收参数、调 Service、返回视图名或 JSON ├── service # 业务规则与事务边界,接口 + impl 两层 ├── mapper # MyBatis 接口,SQL 写在 resources/mapper 下 ├── entity # 与表一一对应的实体 ├── vo # 列表页/详情页的展示对象,裁掉大字段 ├── dto # 表单提交对象,带校验注解 ├── interceptor # 登录拦截、权限拦截、操作日志 ├── util # 文件上传、敏感词、分页工具 └── config # Spring 与 MyBatis 的配置类 src/main/resources ├── mapper # 每个 Mapper 接口对应的 XML ├── spring # applicationContext、spring-mvc 配置 └── db # 建表脚本与演示数据

一条典型调用链长这样:PostController.publish()接住表单和MultipartFile,转成PostDTO做非空与长度校验,调PostService.publish(),Service 里先落附件、再插帖子、最后写一条审核通知,整段包在@Transactional里,任何一步抛异常全部回滚。Controller 里看不到一行 JDBC 代码,答辩时把这条链路讲一遍,比背框架概念管用得多。

3. 数据库设计:十张表把论坛的读写主链路撑起来

3.1 实体关系与表清单

论坛的数据模型天然是一棵「用户—板块—作品—评论」的树,加上附件、点赞、通知、举报、日志这些旁支。表不用多,但每张都要有明确归属,别出现「一个表存三种东西」的设计。下面十张表覆盖了全部功能点,量级预估按课程设计演示数据给,实际跑几千条没有压力。

表名用途关键字段预估量级
sys_user用户与角色username、password、role、status千级
forum_board板块分类name、sort、allow_post十级
forum_post作品主表board_id、user_id、status、cover万级
forum_comment评论盖楼post_id、root_id、parent_id、floor十万级
forum_like点赞记录target_type、target_id、user_id十万级
forum_collect收藏记录post_id、user_id万级
forum_attachment附件post_id、file_path、file_type万级
forum_notice站内通知user_id、type、is_read万级
forum_report举报target_id、reason、handle_status百级
sys_log后台操作日志admin_id、action、target千级

sys_user里用单个role字段区分USER、MODERATOR、ADMIN三档就够了,课程设计上完整的 RBAC 五张表反而增加讲解成本。真要扩展,把 role 拆成角色表和关联表即可,字段名不用改。

3.2 关键表结构与索引取舍

帖子表是整个系统的读写热点,字段设计要一次到位。正文用LONGTEXT,但列表页绝对不查它;封面单独存相对路径;计数字段(浏览、点赞、评论)冗余在帖子上,避免列表页每行都去 count。

CREATE TABLE forum_post ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, board_id INT UNSIGNED NOT NULL COMMENT '所属板块', user_id BIGINT UNSIGNED NOT NULL COMMENT '作者', title VARCHAR(120) NOT NULL, summary VARCHAR(255) DEFAULT '' COMMENT '列表页摘要,发布时截取', content LONGTEXT NOT NULL COMMENT '正文,列表页不查', cover_path VARCHAR(255) DEFAULT '' COMMENT '封面相对路径', tags VARCHAR(200) DEFAULT '' COMMENT '逗号分隔,最多5个', status TINYINT NOT NULL DEFAULT 0 COMMENT '0草稿 1待审 2通过 3驳回 4下架', is_top TINYINT NOT NULL DEFAULT 0, view_count INT UNSIGNED NOT NULL DEFAULT 0, like_count INT UNSIGNED NOT NULL DEFAULT 0, comment_count INT UNSIGNED NOT NULL DEFAULT 0, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_board_status_time (board_id, status, create_time), KEY idx_user_status_time (user_id, status, create_time), KEY idx_status_hot (status, like_count, comment_count) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='影视创作作品主表';

三个索引对应三种首页查询:按板块刷最新、进「我的作品」看自己全部状态、按热度排推荐位。idx_board_status_time的顺序不能换,board_id等值在前、status等值居中、create_time范围在后,这个顺序才用得上索引;反过来写成(create_time, board_id)就只能全表扫。

评论表用「两级盖楼」而不是无限嵌套,root_id指向顶层评论,parent_id指向被回复的那条,floor是顶层楼层号。这样查详情页只需要一条按root_id排序的 SQL,不用递归,JSP 里两层循环就能渲染。

CREATE TABLE forum_comment ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, post_id BIGINT UNSIGNED NOT NULL, user_id BIGINT UNSIGNED NOT NULL, root_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '0表示顶层评论', parent_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '被回复的评论id', floor INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '仅顶层评论有楼层号', content VARCHAR(1000) NOT NULL, status TINYINT NOT NULL DEFAULT 1 COMMENT '1正常 0已删除', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_post_root_floor (post_id, root_id, floor), KEY idx_user_time (user_id, create_time) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='作品评论表';

点赞表的灵魂是唯一索引,不是代码里的 if 判断:

CREATE TABLE forum_like ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, target_type TINYINT NOT NULL COMMENT '1帖子 2评论', target_id BIGINT UNSIGNED NOT NULL, user_id BIGINT UNSIGNED NOT NULL, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_target_user (target_type, target_id, user_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='点赞记录,唯一索引防重复';

并发下「先查再插」一定会漏,两个请求同时查到没点过,然后都插进去,计数字段多加一次。唯一索引把这件事交给数据库,业务层只需要处理插入结果。

3.3 冗余计数字段与一致性补偿

like_count、comment_count这类冗余字段是为了列表页不用 join 聚合,代价是要维护一致。做法是:所有增减都和业务写在同一个事务里,用UPDATE ... SET x = x + 1的原子写法,不要先 select 再 set,否则并发下会丢更新。

// PostMapper.xml 中对应的语句:原子自增,避免读改写丢更新 // UPDATE forum_post SET like_count = like_count + #{delta} WHERE id = #{postId} @Transactional(rollbackFor = Exception.class) public void like(Long postId, Long userId) { // 1. 唯一索引兜底,重复点赞影响行数为 0 int inserted = likeMapper.insertIgnore(1, postId, userId); if (inserted == 0) { return; // 已经点过,直接返回,不报错也不加计数 } // 2. 计数原子自增,与上一步同一事务 postMapper.addLikeCount(postId, 1); // 3. 给自己之外的人发通知,自己点赞自己不通知 Long authorId = postMapper.selectAuthorId(postId); if (!authorId.equals(userId)) { noticeService.push(authorId, "LIKE", postId); } }

参数说明:insertIgnore对应INSERT IGNORE,重复时返回 0 而不是抛异常,比 catchDuplicateKeyException干净;delta传 +1 或 -1,取消点赞走同一个方法。事务里做了三件事,通知失败会整体回滚,这一点要在答辩时主动讲——很多同学只在点赞表写了记录,计数没跟上,演示时数字对不上,很难圆。

4. 核心链路实现:发帖审核、评论盖楼与点赞幂等

4.1 作品发布:从表单到附件落盘

发布页是整个系统里表单最长的页面:标题、板块、标签、封面、正文、可选附件(剧本 PDF、短片 MP4)。后端接的时候分两步走,先落文件再落库,文件名统一用 UUID 重命名,数据库只存相对路径。这样换部署目录、换域名都不用改数据。

@PostMapping("/post/publish") public String publish(@Valid PostDTO dto, BindingResult br, @RequestParam("cover") MultipartFile cover, HttpSession session, Model model) { if (br.hasErrors()) { model.addAttribute("errors", br.getAllErrors()); return "post/edit"; // 校验失败回填表单,不丢用户输入 } Long userId = (Long) session.getAttribute("uid"); // 文件校验:后缀白名单 + 大小上限,别信前端 accept String coverPath = fileUtil.save(cover, "cover", Arrays.asList("jpg", "png", "webp"), 2 * 1024 * 1024L); dto.setCoverPath(coverPath); postService.publish(dto, userId); return "redirect:/post/my?status=1"; // 跳我的作品,提示待审核 }

fileUtil.save内部做四件事:取原始后缀并转小写、白名单比对、UUID 生成新文件名、按yyyyMM建子目录后写入磁盘。返回的相对路径形如cover/202506/9f3c....jpg,JSP 里拼上配置的访问前缀即可。大小上限放在工具方法里而不是只依赖spring-mvc.xml的maxUploadSize,双层限制,前者给出友好提示,后者防止请求体过大直接 500。

Service 层负责补全业务字段并落库:

@Transactional(rollbackFor = Exception.class) public void publish(PostDTO dto, Long userId) { Post post = new Post(); BeanUtils.copyProperties(dto, post); post.setUserId(userId); post.setStatus(PostStatus.PENDING.getCode()); // 新发布一律待审 post.setSummary(TextUtil.truncate(dto.getContent(), 120)); // 列表页摘要,去标签后截断 postMapper.insert(post); // 附件逐条落库,file_path 已在 Controller 层存好 for (AttachmentDTO a : dto.getAttachments()) { attachmentMapper.insert(post.getId(), a); } // 给所有管理员推一条待审提醒,后台角标能实时看到 noticeService.pushToAdmins("AUDIT", post.getId()); }

摘要字段在发布时算好存下来,是列表页性能的关键。如果每次列表查询都用SUBSTRING(content, 1, 120),LONGTEXT字段会被读进内存再截断,几十条数据就能把首页拖慢一倍。

4.2 审核状态机与敏感词过滤

状态字段最怕写成「到处 set」。正确做法是定义枚举 + 一张允许流转的表,任何非法跳转直接抛业务异常。这套东西在答辩时是加分项,因为大部分同学的作品只是把 status 当布尔用。

public enum PostStatus { DRAFT(0), PENDING(1), PASSED(2), REJECTED(3), OFFLINE(4); private final int code; PostStatus(int code) { this.code = code; } public int getCode() { return code; } // 允许的流转:草稿→待审→通过/驳回;通过→下架;驳回→待审(作者改完重提) private static final Map<PostStatus, Set<PostStatus>> FLOW = new HashMap<>(); static { FLOW.put(DRAFT, EnumSet.of(PENDING)); FLOW.put(PENDING, EnumSet.of(PASSED, REJECTED)); FLOW.put(REJECTED, EnumSet.of(PENDING)); FLOW.put(PASSED, EnumSet.of(OFFLINE)); FLOW.put(OFFLINE, EnumSet.of(PENDING)); } public static void check(PostStatus from, PostStatus to) { if (!FLOW.getOrDefault(from, Collections.emptySet()).contains(to)) { throw new BizException("非法的审核流转:" + from + " -> " + to); } } }

审核接口只做三件事:校验当前状态、按流转表切状态、写审核意见与操作日志。作者重新提交时走REJECTED -> PENDING,历史审核意见保留在日志表里,不覆盖。

敏感词放在发布入口,用前缀树(DFA)而不是String.contains循环。词库几百条时两者差别不大,但contains是 O(词数 × 文本长度),词库上千就会明显卡;前缀树一次扫描文本,复杂度只和文本长度相关。实现上把词库存进Set,构建成Map<Character, Object>嵌套结构,命中后替换成等长星号,保证正文长度不变、摘要截断位置不乱。

提示:敏感词过滤只做「发布时拦截 + 命中转人工复核」两层,不要做成正则全量替换,否则正文里正常的影视术语也会被误伤,演示时很容易尴尬。

4.3 评论盖楼与点赞幂等

评论详情页的查询是一条 SQL 搞定的,按root_id和floor排序,顶层评论root_id = 0排在最前,回复跟在各自楼层后面。JSP 里先按root_id分组再渲染,前端不用发第二次请求。

-- 详情页评论列表:一次查出两级结构,按楼层聚合 SELECT c.id, c.root_id, c.parent_id, c.floor, c.content, c.create_time, u.id AS uid, u.nickname, u.avatar FROM forum_comment c JOIN sys_user u ON u.id = c.user_id WHERE c.post_id = #{postId} AND c.status = 1 ORDER BY IF(c.root_id = 0, c.id, c.root_id) ASC, c.floor ASC LIMIT #{offset}, #{size}

ORDER BY里那个IF是为了让顶层评论按自身 id 排、回复按所属楼层排,避免回复跑到别的楼层下面。楼层号在插入时用SELECT MAX(floor) + 1 FROM forum_comment WHERE post_id = ? AND root_id = 0取,同一事务内加行锁(FOR UPDATE)防并发重号,课程设计量级下这点锁开销可以忽略。

点赞前面已经讲过唯一索引兜底,这里补一个取消点赞的写法,两个接口共用一套计数逻辑:

@Transactional(rollbackFor = Exception.class) public void unlike(Long postId, Long userId) { int deleted = likeMapper.delete(1, postId, userId); // DELETE ... WHERE 三字段 if (deleted > 0) { postMapper.addLikeCount(postId, -1); // 只有真删掉了才减计数 } }

deleted > 0这个判断不能省。用户连点两次取消,第二次删除影响 0 行,如果照样减计数,数字就会变成负数,演示时一眼就能看出来。

4.4 列表分页与首页查询优化

分页两种写法:手写LIMIT offset, size加一条COUNT(*),或者用 PageHelper。课程设计推荐手写,因为 SQL 全在自己手里,索引有没有走上一看执行计划就知道。PageHelper 也能用,但有个经典坑:PageHelper.startPage()之后必须紧跟那条要分页的查询,中间插任何一条别的查询,分页参数就会被错误消费,表现为「第一页正常,第二页数据重复」。

首页列表查询只取列表需要的字段,正文和附件都不碰:

<!-- PostMapper.xml:列表页 VO 查询,显式列字段,禁止 select * --> <select id="selectPageByBoard" resultType="PostListVO"> SELECT p.id, p.title, p.summary, p.cover_path, p.tags, p.like_count, p.comment_count, p.view_count, p.create_time, u.nickname, b.name AS boardName FROM forum_post p JOIN sys_user u ON u.id = p.user_id JOIN forum_board b ON b.id = p.board_id WHERE p.status = 2 <if test="boardId != null"> AND p.board_id = #{boardId} </if> <if test="keyword != null and keyword != ''"> AND (p.title LIKE CONCAT('%', #{keyword}, '%') OR p.tags LIKE CONCAT('%', #{keyword}, '%')) </if> ORDER BY p.is_top DESC, p.create_time DESC LIMIT #{offset}, #{size} </select>

参数说明:status = 2写死在 SQL 里而不是传参,防止前端传个 0 把草稿捞出来;keyword用LIKE前缀模糊匹配,数据量上万后可以换成FULLTEXT索引或者交给前端做二次过滤;offset由页码换算,size固定 10 或 15。浏览数 +1 不要放在列表页,放详情页并且用UPDATE ... SET view_count = view_count + 1的原子写法,列表页每刷一次就加一遍会让热度排序失真。

5. 避坑与排查:交付前最容易翻车的五处

5.1 中文乱码与时间差八小时

现象:作品标题存进库里变成问号,评论时间比实际早或晚 8 小时。原因通常是三处没统一:数据库连接串没写characterEncoding=utf8、Tomcat 的URIEncoding没配、JDBC 时区参数没写。解决是把三处一次配齐,连接串写成jdbc:mysql://localhost:3306/forum?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false,建表和建库都用utf8mb4,Tomcat 的server.xml里 Connector 加URIEncoding="UTF-8"。时间字段统一用DATETIME而不是TIMESTAMP,后者会受时区影响,答辩换台机器就可能整体偏移。

5.2 上传大文件直接 500

现象:传 20MB 的短片附件,页面报 500,后台日志里是SizeLimitExceededException或者MaxUploadSizeExceededException。原因是框架默认上传上限只有 1MB 到 10MB,且异常没有被业务层接住。解决分两层:spring-mvc.xml里把CommonsMultipartResolver的maxUploadSize调到业务需要的大小(比如 200MB),同时配一个@ControllerAdvice捕获上传超限异常,返回「文件过大,请压缩后重试」的友好提示,而不是把 500 堆栈扔给用户。还要注意临时目录,Tomcat 默认临时目录在小分区上会写满。

5.3 删除帖子后评论变孤儿

现象:后台删了一条作品,详情页 404,但评论表里那一堆记录还在,按用户查「我的评论」时点进去全是空白页。原因是只删了主表,没用外键也没做级联。课程设计里不建议上物理外键(导入数据顺序容易出错),做法是在 Service 里做软删除:帖子status改成下架,评论列表接口先查帖子状态,不存在或已下架就统一提示「内容已删除」。真要物理删除,就在同一事务里按post_id把评论、点赞、附件、收藏一起清掉,顺序是先子后主。

5.4 N+1 查询把首页拖到三秒

现象:本地测试首页很快,导入两千条演示数据后打开要三秒以上。原因是列表循环里每条都去查一次作者名或者附件数,一页 15 条就是 15 次以上查询。解决办法是把关联查询写进同一条 SQL(上面的列表 VO 查询就是干这个的),或者在 Service 里先收集本页所有user_id,一次IN查询拿到用户 Map,再在内存里拼。判断方法很简单:把 MyBatis 的 SQL 日志打开,看一次列表请求打了几条Preparing,超过 5 条就该合并。

5.5 权限只在前端藏按钮

现象:普通用户把地址栏里的/admin/post/audit一敲,照样能进后台页面,甚至能提交审核。原因是权限判断只写在 JSP 的<c:if>里,后端接口裸奔。解决是加一个登录拦截器和一个角色拦截器,后者匹配/admin/**路径,从 session 取角色,不是管理员直接跳 403 页面。前端藏按钮只是体验优化,真正的门禁必须在 Controller 之前。这一条在答辩时被问到的概率极高,主动讲出来比被问出来好。

6. 验收与答辩:用一份演示脚本把系统讲成方案

验收不是把系统跑起来点两下就完事,得有一条能走完的演示路径。我一般会提前写好脚本,按「游客浏览 → 注册登录 → 发布作品 → 管理员审核 → 回到前台看审核结果 → 评论点赞 → 后台看日志」这个顺序走一遍,全程不超过五分钟,每一步都能对应到一个功能点。演示前把数据库重置成演示数据,清掉测试过程中的脏数据,避免首页全是「test123」这种标题。

检查项通过标准常见失分点
注册登录密码加密存储,错误提示不泄露账号是否存在明文存密码
发布作品封面与附件都能传,列表页显示摘要正文整段塞进列表
审核流转待审→通过/驳回可走通,非法流转被拒status 随便 set
评论盖楼回复挂在正确楼层下,楼层号连续回复跑到别的楼层
点赞重复点赞不重复计数,取消后数字正确计数变负数
权限非管理员访问后台被拦截只藏前端按钮
分页翻页不重复不丢数据PageHelper 位置写错
检索关键词命中标题和标签只查标题

答辩提问集中在三个方向。第一问「你的分层怎么划分的」,答 Controller 校验、Service 事务、Mapper 数据访问,再举一条发帖链路。第二问「并发下点赞怎么防重」,答唯一索引加INSERT IGNORE,再补一句计数用原子自增。第三问「数据量大了哪里会慢」,答列表页不查正文、关联查询合并成一条、按板块加联合索引,能说出执行计划最好。三个问题答稳,基本就过了。

从那以后我每次交付前都强制走一遍这份清单:先重置数据,再按脚本点一遍,最后打开 SQL 日志数一下每页请求打了几条语句。翻车最惨的一次是演示前一天改了 Mapper,列表页多了二十条查询,现场打开首页转了五秒,从那以后这个习惯就没断过。整套工程包含建表脚本、演示数据和部署说明,按模块分好了目录,拿过去改个包名就能跑,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询