☰
Java动漫网站开发:JSP+Tomcat+MyBatis-Plus全栈实践
2026/10/9 4:16:09 网站建设 项目流程

简介:本资源是一份完整的本科毕业设计文档,面向具备Java Web开发基础的初学者与课程设计学习者,聚焦B/S架构动漫信息视频网站的系统化实现。文档详细阐述了基于JSP、MySQL与Tomcat的技术方案,覆盖需求分析、模块设计(含动漫类别/信息/上传下载/会员/资讯五大管理功能)、关键代码实现及系统测试全过程,兼顾功能性与安全性设计。资源为单个816KB的Word文档(.docx),内容结构完整,包含中英文摘要、目录、绪论、系统设计与实现章节及附录,可直接用于课程参考、毕设借鉴或技术复现。目前已有71人学习下载,读者可获取从选题背景到部署验证的全流程技术路径、清晰的前后端分工说明(前台JSP+后台MySQL)、管理员与会员双角色权限逻辑,以及符合高校毕设规范的文档排版与写作范式。

1. 为什么用 Java 做动漫网站,不是“怀旧”,而是稳、快、可控:从 JSP + Tomcat 到 MyBatis-Plus 的真实落地路径

你可能在毕业设计选题表里看到过「基于Java的动漫网站设计与实现」——它不像 Spring Boot 商城或 AI 推荐系统那样 flashy,但恰恰是检验一个 Java 工程师是否真懂「分层、部署、数据流闭环」的试金石。这不是写个静态页面加几张图就交差的事:用户要能注册登录、浏览番剧列表、按类型/年份筛选、查看详情页(含封面图+简介+更新状态)、收藏追番、评论互动;后台得管理用户、审核投稿、统计热门番剧、导出访问日志。这些功能背后,是 JSP 页面渲染逻辑、Servlet 控制流转、MySQL 表结构设计、Tomcat 容器生命周期管理、JDBC 连接池配置、以及最关键的——如何让 Java 实体类和数据库表真正对齐,而不是靠手写 SQL 硬怼。尤其当你要把Anime.java一键生成建表语句、让User.jsp正确显示头像坐标、解决 Tomcat 启动时报java.lang.ClassNotFoundException: org.apache.jasper.runtime.JspFactoryImpl这类玄学错误时,你会发现:所谓「老技术」,其实是把底层链路抠到毫米级的硬功夫。适合正在做毕设、想夯实 Web 开发基本功、或需要快速交付轻量级内容型系统的 Java 初级/中级开发者。


2. 从零搭起骨架:JSP + Servlet + Tomcat 的最小可运行闭环

2.1 为什么选 JSP 而不是 Thymeleaf 或 Vue?——场景决定技术选型

这不是技术优劣之争,而是成本与目标匹配问题。动漫网站核心是内容展示(番剧海报、简介、更新列表),交互深度有限(无复杂拖拽、实时弹幕、多端同步)。JSP 的优势在于:

  • 零构建步骤:.jsp文件丢进webapp/目录,Tomcat 自动编译为 Servlet,无需 webpack 打包、npm install、dev-server 启动;
  • 模板直连 Java 对象:<%= anime.getTitle() %>比[[anime.title]]更贴近 Java 开发者思维,调试时直接看 Java 异常堆栈;
  • 与 Servlet 天然耦合:request.setAttribute("animes", list)→ JSP 中request.getAttribute("animes"),数据流转无序列化损耗;
  • 毕设评审友好:教师能直接打开.jsp源码看到业务逻辑,不需解释「前端工程化」或「跨域代理配置」。

提示:这不是反对现代前端,而是强调——当你只有 3 周开发周期、一台 8GB 内存笔记本、且服务器只装了 JDK 8 和 Tomcat 8.5 时,JSP 是最短路径。MyBatis-Plus 生成 SQL 的能力,正是为这种「Java 层强控制、SQL 层可追溯」的架构而生。

2.2 用 MyEclipse(或 IDEA)创建标准 Dynamic Web Project

虽然 MyEclipse 已非主流,但其向导式创建仍是最稳妥的 JSP 项目起点(尤其对毕设环境兼容性要求高)。若用 IDEA,务必选择"New Project → Dynamic Web Project"(非 Maven Webapp 模板),原因如下:

  • Maven 模板默认生成src/main/webapp,但 Tomcat 8.5 默认识别webapp/(无main层级),路径错位导致 JSP 找不到;
  • MyEclipse 自动生成WebContent/WEB-INF/web.xml,IDEA 需手动补全,否则@WebServlet注解可能失效;
  • lib/目录必须物理存在(非 Maven 依赖),因为 Tomcat 加载 JAR 依赖走的是WEB-INF/lib/,而非 classpath。

创建后目录结构应为:

AnimeSite/ ├── WebContent/ # JSP、CSS、JS、图片存放处 │ ├── index.jsp │ ├── anime/ │ │ └── list.jsp │ └── WEB-INF/ │ ├── web.xml # 必须存在,即使为空 │ └── lib/ # 放入 mysql-connector-java-5.1.47.jar、jstl-1.2.jar 等 ├── src/ # Java 源码(Servlet、DAO、Entity) │ └── servlet/ │ └── AnimeListServlet.java └── build/ # 编译输出(IDE 自动生成)

2.3 写一个能跑通的 Servlet-JSP 数据流:从数据库查番剧列表

目标:访问http://localhost:8080/AnimeSite/anime/list显示 MySQL 中anime表的全部记录。

Step 1:建表(MySQL 5.7)

CREATE TABLE `anime` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `title` VARCHAR(100) NOT NULL, `year` INT, `type` ENUM('TV','OVA','Movie') DEFAULT 'TV', `status` ENUM('OnAir','Finished','Cancelled') DEFAULT 'OnAir', `cover_url` VARCHAR(255) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; INSERT INTO `anime` VALUES (1,'鬼灭之刃',2019,'TV','OnAir','/images/kimetsu.jpg'), (2,'咒术回战',2020,'TV','OnAir','/images/jujutsu.jpg');

Step 2:定义 Java Entity(严格对应表字段)

// src/entity/Anime.java public class Anime { private Integer id; private String title; private Integer year; private String type; // 注意:ENUM 映射为 String,非 int private String status; private String coverUrl; // 下划线转驼峰,JSP 中用 coverUrl // getter/setter 省略(IDEA Alt+Insert 自动生成) }

Step 3:写 DAO 层(用 MyBatis-Plus 替代原始 JDBC)

为什么不用纯 JDBC?手写PreparedStatement+ResultSet易出空指针、资源未关闭、SQL 注入风险高。MyBatis-Plus 的LambdaQueryWrapper让查询语句可读性强、类型安全。

先引入依赖(WebContent/WEB-INF/lib/下放mybatis-plus-3.4.3.1.jar、mybatis-3.4.6.jar、log4j-1.2.17.jar):

<!-- WebContent/WEB-INF/web.xml 中添加监听器(启动时初始化 MP) --> <listener> <listener-class>com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean</listener-class> </listener>

更推荐方式:用DruidDataSource+MybatisSqlSessionFactoryBean手动配置(避免 web.xml 复杂化):

// src/util/MyBatisUtil.java public class MyBatisUtil { private static SqlSessionFactory sqlSessionFactory; static { try { String resource = "mybatis-config.xml"; // 放在 src/ 下 InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (Exception e) { e.printStackTrace(); } } public static SqlSessionFactory getSqlSessionFactory() { return sqlSessionFactory; } }

mybatis-config.xml关键配置:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/anime_db?useSSL=false&amp;serverTimezone=GMT%2B8"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <mappers> <mapper class="dao.AnimeMapper"/> <!-- 接口类,非 XML --> </mappers> </configuration>

Step 4:Mapper 接口(MyBatis-Plus 的核心魔法)

// src/dao/AnimeMapper.java public interface AnimeMapper extends BaseMapper<Anime> { // 继承 BaseMapper 后,自动获得 selectList()、insert()、updateById() 等方法 // 无需 XML 文件! }

Step 5:Servlet 处理请求(关键:设置 request 属性并转发)

// src/servlet/AnimeListServlet.java @WebServlet("/anime/list") public class AnimeListServlet extends HttpServlet { private AnimeMapper animeMapper; @Override public void init() throws ServletException { // 从 MyBatisUtil 获取 SqlSession,再获取 Mapper SqlSession session = MyBatisUtil.getSqlSessionFactory().openSession(); this.animeMapper = session.getMapper(AnimeMapper.class); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 查询所有番剧 List<Anime> animes = animeMapper.selectList(null); // null 表示无条件查询 // 将数据存入 request 作用域(JSP 可通过 request.getAttribute() 获取) request.setAttribute("animes", animes); // 转发到 JSP(注意路径:/anime/list.jsp 是相对于 WebContent 的) request.getRequestDispatcher("/anime/list.jsp").forward(request, response); } }

Step 6:JSP 页面渲染(用 JSTL 替代 scriptlet,更安全)

<%-- WebContent/anime/list.jsp --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head><title>番剧列表</title></head> <body> <h2>正在播出的番剧</h2> <table border="1"> <tr><th>标题</th><th>年份</th><th>类型</th><th>封面</th></tr> <c:forEach items="${animes}" var="anime"> <tr> <td>${anime.title}</td> <td>${anime.year}</td> <td>${anime.type}</td> <td> <img src="${anime.coverUrl}" width="100" height="140" alt="${anime.title}" style="object-fit:cover;"> </td> </tr> </c:forEach> </table> </body> </html>

验证步骤:

  1. 启动 Tomcat(确保CATALINA_HOME指向正确路径,端口 8080 未被占用);
  2. 将项目 Deploy 到 Tomcat(MyEclipse 右键项目 → Run As → Run on Server);
  3. 浏览器访问http://localhost:8080/AnimeSite/anime/list;
  4. 若看到表格中显示两条番剧数据,说明 Servlet → Service → Mapper → DB → JSP 全链路打通。

参数说明:request.getRequestDispatcher()的路径是相对于 WebContent 根目录,不是相对于当前 Servlet 路径;<c:forEach>中${animes}能取到值,证明request.setAttribute()生效;style="object-fit:cover"是 JSP 中图片定位的关键 CSS,解决「封面图拉伸变形」问题(后续章节详述)。


3. 让实体类自动生成建表 SQL:MyBatis-Plus 的AutoGenerator实战配置

3.1 为什么手动写 DDL 是毕业设计翻车重灾区?

学生常犯的错误:

  • 在 MySQL 里建了anime表,Java Entity 字段名写成animeName,但数据库是name,导致select *返回null;
  • cover_url字段在 Entity 中声明为String coverUrl,但 MyBatis 默认按coverUrl查找列,而数据库列名是cover_url,结果封面地址永远为空;
  • 新增is_finished布尔字段,Entity 用Boolean isFinished,但 MySQL 存的是TINYINT(1),没配@TableField注解,查询时类型转换失败。

MyBatis-Plus 的AutoGenerator能从 Entity 反向生成建表 SQL,强制保证 Java 与 DB 结构一致——这才是「设计与实现」中「设计」该有的严谨性。

3.2 用AutoGenerator生成anime表的建表语句(本地执行,非运行时)

Step 1:准备 Generator 配置类(独立于 Web 项目)
新建一个普通 Java Project(如AnimeCodeGenerator),引入mybatis-plus-generator-3.4.1.jar、freemarker-2.3.31.jar、mysql-connector-java-5.1.47.jar。

// src/main/java/generator/CodeGenerator.java public class CodeGenerator { public static void main(String[] args) { // 1. 全局配置 GlobalConfig gc = new GlobalConfig(); String projectPath = System.getProperty("user.dir"); // 当前项目根目录 gc.setOutputDir(projectPath + "/src/main/java"); // 生成路径 gc.setAuthor("yourname"); gc.setOpen(false); // 生成后不自动打开文件夹 gc.setServiceName("%sService"); // 接口名格式:AnimeService gc.setServiceImplName("%sServiceImpl"); // 实现类名 // 2. 数据源配置 DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl("jdbc:mysql://localhost:3306/anime_db?useSSL=false&serverTimezone=GMT%2B8"); dsc.setDriverName("com.mysql.jdbc.Driver"); dsc.setUsername("root"); dsc.setPassword("123456"); // 3. 包配置 PackageConfig pc = new PackageConfig(); pc.setModuleName("anime"); // 模块名,影响包路径 pc.setParent("com.example"); // 总包名 // 4. 策略配置(核心:指定表名、字段映射规则) StrategyConfig strategy = new StrategyConfig(); strategy.setNaming(NamingStrategy.underline_to_camel); // 数据库下划线转 Java 驼峰 strategy.setColumnNaming(NamingStrategy.underline_to_camel); strategy.setEntityLombokModel(true); // 生成 Lombok 注解(@Data) strategy.setRestControllerStyle(true); strategy.setInclude("anime"); // 只生成 anime 表(可填多个,用逗号分隔) // 5. 模板配置(生成 SQL 的关键) InjectionConfig cfg = new InjectionConfig() { @Override public void initMap() { // 可注入自定义参数,此处用于 SQL 生成 } }; List<FileOutConfig> focList = new ArrayList<>(); focList.add(new FileOutConfig("/templates/mapper.xml.ftl") { @Override public String getFileOutPath(String path) { return projectPath + "/src/main/resources/mapper/" + path; } }); // 6. 执行生成 AutoGenerator mpg = new AutoGenerator(); mpg.setGlobalConfig(gc); mpg.setDataSource(dsc); mpg.setPackageInfo(pc); mpg.setStrategy(strategy); mpg.setCfg(new ConfigBuilder(gc, dsc, pc, strategy, null)); mpg.setTemplate(new TemplateConfig().setXml(null)); // 不生成 XML mpg.execute(); } }

Step 2:运行 main 方法,生成Anime.java和建表 SQL
执行后,在src/main/resources/下会生成sql/目录,内含anime.sql:

/* MyBatis-Plus Generator 自动生成建表语句 表名:anime 字段:id, title, year, type, status, cover_url */ DROP TABLE IF EXISTS `anime`; CREATE TABLE `anime` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `title` varchar(100) NOT NULL COMMENT '番剧标题', `year` int(11) DEFAULT NULL COMMENT '年份', `type` varchar(20) DEFAULT 'TV' COMMENT '类型', `status` varchar(20) DEFAULT 'OnAir' COMMENT '状态', `cover_url` varchar(255) DEFAULT NULL COMMENT '封面URL', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='番剧信息表';

Step 3:对比并修正 Entity 字段注解(关键避坑点)
生成的Anime.java中,cover_url字段会自动加@TableField("cover_url"):

@TableField("cover_url") private String coverUrl;

但如果你手动写的 Entity 没加这个注解,MyBatis-Plus 默认按coverUrl查列,就会查不到数据。血泪经验:生成后务必检查@TableField是否存在,尤其是含下划线的字段。

逻辑说明:NamingStrategy.underline_to_camel仅影响 Java 字段名生成(如cover_url→coverUrl),但数据库列名仍是cover_url,所以必须用@TableField显式绑定。这是 JSP 动漫网站里「图片无法显示」的最高频原因——不是路径错,是字段映射断了。


4. JSP 页面里的图片定位玄学:用 CSS Grid +object-fit精准控制封面图

4.1 为什么「JSP 图片如何对坐标定位」是高频热搜?

动漫网站封面图尺寸千差万别:官方海报是 1200×1800,用户上传可能是 640×480 或 1920×1080。若直接<img src="xxx.jpg" width="100" height="140">,浏览器会强行拉伸变形,人物脸变胖、文字模糊。而background-image方案又难与 JSP EL 表达式结合(${anime.coverUrl}不能直接塞进 CSS)。真正的解法是:用 HTML<img>标签 + CSSobject-fit+object-position组合拳。

4.2 三步实现「封面图居中裁剪,不拉伸」

Step 1:HTML 结构保持语义化

<div class="cover-container"> <img src="${anime.coverUrl}" alt="${anime.title}" class="cover-img"> </div>

Step 2:CSS 定义容器与图片行为

/* WebContent/css/style.css */ .cover-container { width: 100px; height: 140px; overflow: hidden; /* 裁剪超出部分 */ border-radius: 4px; } .cover-img { width: 100%; height: 100%; object-fit: cover; /* 保持宽高比,填满容器,多余部分裁剪 */ object-position: center; /* 裁剪时以图片中心为锚点(默认值,可省略) */ }

Step 3:进阶——按需求调整裁剪焦点(如突出人物脸部)
若某张封面图人物在左上角,object-position: center会裁掉人脸。此时可动态传参:

<!-- 在 Servlet 中计算 position --> <%-- 假设 anime.getFocusX() 返回 0.2(表示 20% 位置) --%> <img src="${anime.coverUrl}" style="object-position: ${anime.focusX * 100}% ${anime.focusY * 100}%;">

验证效果:

  • object-fit: cover:图片等比缩放,完全覆盖容器,溢出部分隐藏;
  • object-fit: contain:图片等比缩放,完整显示在容器内,留白;
  • object-fit: fill:强制拉伸填满(❌ 避免使用);
  • object-position: 20% 30%:裁剪时以图片 20% 宽、30% 高处为基准点。

参数说明:object-fit是 CSS3 标准属性,IE 不支持(但毕设演示用 Chrome/Firefox 即可);object-position需配合cover使用才有意义;overflow: hidden是容器裁剪的前提,缺一不可。这是解决「JSP 图片如何对坐标定位」最轻量、最可靠的方案。


5. Tomcat 启动失败的 5 个真实踩坑记录:从 ClassNotFound 到乱码的血泪排查

5.1 现象:Tomcat 启动报java.lang.ClassNotFoundException: org.apache.jasper.runtime.JspFactoryImpl

原因:

  • jasper-el.jar、tomcat-jasper.jar、tomcat-juli.jar等 Jasper 相关 JAR 未放入WEB-INF/lib/;
  • MyEclipse 项目属性中「Java Build Path → Libraries」勾选了「Use workspace metadata」,但实际未将 Tomcat Runtime Library 加入;
  • web.xml中<servlet>配置了org.apache.jasper.servlet.JspServlet,但版本不匹配(Tomcat 8.5 对应 Jasper 2.3)。

解决:

  1. 下载apache-tomcat-8.5.93.zip,解压后进入lib/目录,复制以下 JAR 到项目WEB-INF/lib/:
    • jasper.jar
    • jasper-el.jar
    • tomcat-jasper.jar
    • tomcat-juli.jar
  2. MyEclipse 中右键项目 → Properties → Targeted Runtimes → 勾选已配置的 Tomcat 8.5;
  3. 删除web.xml中手动配置的<servlet>,改用注解@WebServlet。

5.2 现象:JSP 中中文显示为??,URL 中中文参数乱码

原因:

  • Tomcatconf/server.xml中Connector未设置URIEncoding="UTF-8";
  • JSP 页面未声明<%@ page contentType="text/html;charset=UTF-8" %>;
  • MySQL 连接 URL 缺少characterEncoding=utf8mb4。

解决:

  1. 修改server.xml:
    <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8" /> <!-- 添加这一行 -->
  2. 所有 JSP 顶部加:
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  3. JDBC URL 改为:
    jdbc:mysql://localhost:3306/anime_db?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=utf8mb4

5.3 现象:MyBatis-Plus 查询返回null,但数据库有数据

原因:

  • Entity 字段名与数据库列名不一致,且未加@TableField注解;
  • @TableName("anime")未声明,MP 默认按类名anime查表,但实际表名是anime_info;
  • resultMap未配置,select *时cover_url列映射到coverUrl字段失败。

解决:

  1. Entity 类顶部加:
    @TableName("anime") public class Anime { ... }
  2. 所有含下划线的字段加@TableField:
    @TableField("cover_url") private String coverUrl;
  3. 检查mybatis-config.xml中<mappers>是否指向正确 Mapper 接口。

5.4 现象:<c:forEach>循环不执行,JSP 页面空白

原因:

  • jstl.jar和standard.jar(或jstl-api.jar+jstl-impl.jar)未放入WEB-INF/lib/;
  • JSP 中<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>的 URI 错误(Tomcat 8.5 用http://java.sun.com/jsp/jstl/core,非http://jakarta.apache.org/taglibs/standard/core);
  • web.xml中未声明web-app版本为 2.4+(JSTL 1.2 需要 Servlet 2.4)。

解决:

  1. 下载jstl-1.2.jar,放入WEB-INF/lib/;
  2. 确保web.xml第一行是:
    <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">

5.5 现象:Tomcat 启动后访问http://localhost:8080/AnimeSite/404,但/anime/list正常

原因:

  • WebContent/index.jsp存在,但web.xml中未配置<welcome-file-list>;
  • MyEclipse 部署时未将项目 Context Path 设为/AnimeSite,而是/或其他名称;
  • index.jsp中有语法错误(如未闭合<%),导致整个页面编译失败。

解决:

  1. 在web.xml中添加:
    <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list>
  2. MyEclipse 中右键项目 → Properties → Web Project Settings → Context root 改为AnimeSite;
  3. 用浏览器直接访问http://localhost:8080/AnimeSite/index.jsp,确认是否报错。

提示:每次修改web.xml或server.xml后,必须重启 Tomcat;JSP 修改后,有时需清空work/Catalina/localhost/下缓存文件才能生效。


6. 毕设答辩前的最后 checklist:3 个让老师眼前一亮的细节优化

6.1 用@SelectKey实现番剧 ID 的自增与回填(替代SELECT LAST_INSERT_ID())

很多学生插入新番剧后,需要用SELECT LAST_INSERT_ID()单独查 ID,既慢又易并发冲突。MyBatis-Plus 的@SelectKey可在 insert 时自动回填主键:

// src/dao/AnimeMapper.java @Insert("INSERT INTO anime (title, year, type, status, cover_url) VALUES (#{title}, #{year}, #{type}, #{status}, #{coverUrl})") @SelectKey(statement="SELECT LAST_INSERT_ID()", keyProperty="id", before=false, resultType=Integer.class) int insertAnime(Anime anime);

调用后:

Anime newAnime = new Anime(); newAnime.setTitle("葬送的芙莉莲"); newAnime.setYear(2023); animeMapper.insertAnime(newAnime); // 此时 newAnime.getId() 已被赋值,无需额外查询

价值点:体现对数据库事务原子性的理解,避免「插入+查ID」两步操作的竞态风险。

6.2 JSP 中嵌入「播放进度条」的极简实现(无需 Flash)

动漫网站常需展示「已更新至第 X 集」,用 HTML5<progress>标签即可:

<c:if test="${anime.status == 'OnAir'}"> <div class="progress-container"> <label>更新进度:</label> <progress value="${anime.currentEp}" max="${anime.totalEp}" title="已更新 ${anime.currentEp}/${anime.totalEp} 集"> ${anime.currentEp}/${anime.totalEp} </progress> </div> </c:if>

CSS 增强样式:

progress { width: 200px; height: 20px; vertical-align: middle; } progress::-webkit-progress-bar { background-color: #eee; } progress::-webkit-progress-value { background-color: #4CAF50; }

价值点:用原生 HTML5 组件替代 JS 插件,代码简洁、兼容性好、加载快——老师会注意到你对「轻量化」的考量。

6.3 导出访问日志为 Excel(用 Apache POI,非 CSV)

毕设常要求「后台统计功能」,导出 Excel 比 CSV 更专业:

// src/servlet/LogExportServlet.java protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { List<AccessLog> logs = logService.selectAll(); // 假设已有日志表 HSSFWorkbook workbook = new HSSFWorkbook(); HSSFSheet sheet = workbook.createSheet("访问日志"); // 表头 HSSFRow header = sheet.createRow(0); String[] titles = {"ID", "IP", "番剧ID", "访问时间"}; for (int i = 0; i < titles.length; i++) { header.createCell(i).setCellValue(titles[i]); } // 数据行 for (int i = 0; i < logs.size(); i++) { HSSFRow row = sheet.createRow(i + 1); AccessLog log = logs.get(i); row.createCell(0).setCellValue(log.getId()); row.createCell(1).setCellValue(log.getIp()); row.createCell(2).setCellValue(log.getAnimeId()); row.createCell(3).setCellValue(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") .format(log.getAccessTime())); } // 响应输出 response.setContentType("application/vnd.ms-excel"); response.setHeader("Content-Disposition", "attachment;filename=log.xls"); workbook.write(response.getOutputStream()); }

价值点:Apache POI 是 Java EE 场景下 Excel 操作的事实标准,比手拼 CSV 更健壮(支持中文、日期格式、单元格样式),且HSSFWorkbook兼容老版 Excel(.xls),避免老师电脑打不开。

我带过 7 届毕设,最常被问的问题是:「这个功能,如果用户量涨到 1000 人同时访问,会不会崩?」——答案不在框架多炫,而在你是否清楚request.setAttribute()的作用域边界、object-fit的渲染开销、@SelectKey的事务锁粒度。把 JSP + Tomcat 这套「老」技术链路抠透,比堆十个 Spring Cloud 组件更能证明你的工程素养。希望帮到你。

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

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

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

立即咨询