简介:这个Java Web阅读系统包含完整的后端服务和适配手机、平板的前端界面。后端用Java开发,基于Spring框架,有29个业务类,支撑用户注册登录、图书分类管理、章节内容读取、书签保存等核心功能;前端由24个HTML页面、24份CSS样式文件和10个JavaScript脚本组成,实现响应式布局、翻页操作、关键词搜索、夜间模式切换等交互体验;配套13个XML配置文件完成Spring整合,3个SQL脚本用于建表和初始化测试数据,5个常用工具JAR包已内嵌;项目自带Maven Wrapper(mvnw)、pom.xml和wrapper目录,无需预装Maven即可一键编译运行。适合用来练习Java Web开发全流程,理解前后端分离基础结构,或快速搭建轻量级电子书阅读后台。
1. 项目概述:这不是一个“Demo”,而是一套可跑通、可调试、可扩展的阅读系统骨架
我带过不少刚学完Spring Boot的学生做毕业设计,也帮朋友公司快速搭过内部知识库前端。但绝大多数人卡在同一个地方:不是不会写Controller,而是不知道一个真实可用的阅读类Web应用,从数据库建表到用户点击“下一章”之间,到底要填多少坑、连多少线、配多少开关。这套“Java+HTML5掌上阅读系统”就是我反复打磨三轮后留下的“最小可行骨架”——它不追求炫酷动画或AI推荐,但把用户从打开首页到读完一章、加个书签、切个夜间模式、再用手机横屏看一眼的完整链路,全给你铺平了、标清楚了、注释好了。
关键词里提到的“Java阅读系统”“Spring后端”“HTML5前端”“移动端适配”“Maven一键构建”,不是宣传话术,而是五个必须亲手拧紧的螺丝。比如“移动端适配”,它不是简单加个<meta name="viewport">就完事;你得真正在iPhone SE、iPad Air和安卓千元机上测过字体缩放是否错位、触摸翻页区域是否够大、CSS Grid在旧版Android WebView里会不会塌陷。“Maven一键构建”也不是指mvn clean package能跑通就行——它意味着你双击mvnw.cmd(Windows)或执行./mvnw(Mac/Linux),就能自动下载JDK 11兼容的依赖、跳过被墙的中央仓库镜像、绕过本地Maven配置冲突,最终在target/下生成一个可直接用java -jar启动的jar包。这背后是wrapper目录里maven-wrapper.jar和maven-wrapper.properties的精准版本锁定,以及pom.xml中<repositories>节点对阿里云镜像的硬编码 fallback。
它适合谁?如果你正卡在“学完SSM却写不出完整登录流程”的阶段,这套代码就是你的调试沙盒:29个Java类,每个类名都直白如UserServiceImpl.java、ChapterController.java、BookmarkDao.java,没有花哨的泛型嵌套,没有过度抽象的策略模式,只有清晰的“谁查库、谁校验、谁返回JSON”。如果你是前端同学想理解后端怎么吐数据,24个HTML页面里,book_detail.html调用/api/chapter/content?bookId=123&chapterNo=5这个接口的AJAX写法,比任何文档都直观。它不教你怎么造轮子,但手把手告诉你,轮子装在车的哪个位置、用几颗螺栓、拧多大力矩才不会掉。
2. 整体架构与设计思路:为什么用XML配Spring,而不是纯注解?
很多人看到“13个XML配置文件”第一反应是:“都2024年了还写XML?是不是过时了?”——这恰恰是本项目最值得深挖的设计选择。它没用@Configuration类替代所有XML,也没上Spring Boot的application.yml,而是保留了spring-context.xml、spring-mvc.xml、spring-datasource.xml等核心XML,并在web.xml里明确声明ContextLoaderListener。这不是技术债,而是教学意图的刻意为之。
2.1 分层解耦:让每一层“看得见、摸得着”
XML配置的最大价值,在于强制你把关注点切开。比如spring-datasource.xml只干三件事:定义DataSource Bean(用的是HikariCP,不是老旧的DBCP)、配置SqlSessionFactoryBean、声明MapperScannerConfigurer扫描com.example.dao包。它不碰事务,不写拦截器,不定义Controller。而事务管理被单独放在spring-tx.xml里,用<tx:advice>绑定TransactionInterceptor,再通过<aop:config>把*Service.*方法织入。这样,当你想搞懂“为什么我的UserService.update()没回滚”,就不用在几百行@Configuration类里grep @Transactional,直接打开spring-tx.xml,看<tx:method>里rollback-for="Exception"是否漏写了RuntimeException。
再看spring-mvc.xml:它只管<mvc:annotation-driven/>、<context:component-scan>扫controller包、<bean>注册InternalResourceViewResolver。它不加载Service,不配数据源,不设拦截器。这种物理隔离,让初学者能逐个文件理解“MVC的M、V、C分别由谁负责”,而不是被一个巨无霸AppConfig.java淹没。
2.2 兼容性与可控性:为什么不用Spring Boot自动装配?
项目用的是Spring 5.3.x + MyBatis 3.4.x,而非Spring Boot 3.x。原因很实在:Boot的自动装配(Auto-configuration)会偷偷帮你配好DataSource、TransactionManager、甚至Jackson2ObjectMapperBuilder。这对生产环境是福音,但对学习者是陷阱——你改了application.yml里的spring.datasource.url,却不知道底层HikariDataSource的connection-timeout参数实际是多少,因为Boot用@ConditionalOnMissingBean覆盖了你的配置。而本项目中,spring-datasource.xml里明明白白写着:
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="connectionTimeout" value="30000"/>
<property name="maximumPoolSize" value="20"/>
</bean>
你改connectionTimeout,立刻生效;你删掉这行,启动直接报NoSuchBeanDefinitionException。这种“失败即教育”的机制,比任何文档都深刻。
2.3 前后端未完全分离:HTML直连后端,而非调API
注意关键词里写的是“HTML5前端”,而非“Vue/React单页应用”。24个HTML页面(如index.html、user_login.html)里,表单提交用的是<form action="/login" method="post">,不是fetch('/api/login')。这是因为项目定位是“轻量级阅读后台”,目标场景是内网部署、低并发、管理员少量使用。在这种场景下,服务端渲染(SSR)比前后端分离更省资源:用户访问/book/list,后端BookController查完数据库,把List<Book>塞进Model,book_list.jsp(或Thymeleaf模板)直接循环输出<li th:each="book : ${books}">,浏览器拿到的就是完整HTML,无需额外JS解析。这降低了前端复杂度(不用配Webpack、不用处理CORS),也让SEO友好——虽然阅读系统本身不需要SEO,但这种模式让你看清“请求-响应”最原始的链条。
当然,它也预留了API出口:ChapterController里有@ResponseBody方法返回JSON,供后续接入小程序或APP调用。这种“主干用SSR,分支留API”的混合架构,比非此即彼的教条更贴近真实项目权衡。
3. 核心模块拆解:29个Java类如何协作完成一次“翻页”?
我们以用户点击“下一章”按钮为例,追踪从HTTP请求发出到页面刷新的完整链路。这不是理论推演,而是基于源码的真实路径还原。
3.1 前端触发:HTML + JS如何发起请求
用户在book_detail.html页面点击“下一章”,触发的是nextChapter()函数(位于js/chapter_nav.js):
function nextChapter() {
const currentBookId = document.getElementById('bookId').value;
const currentChapterNo = parseInt(document.getElementById('chapterNo').value);
// 关键:这里不是发JSON,而是构造表单提交
const form = document.createElement('form');
form.method = 'GET';
form.action = '/chapter/next';
form.innerHTML = `
<input type="hidden" name="bookId" value="${currentBookId}">
<input type="hidden" name="chapterNo" value="${currentChapterNo}">
`;
document.body.appendChild(form);
form.submit();
}
注意两点:一是用GET而非POST,符合RESTful对“获取资源”的语义;二是没走AJAX,而是传统表单提交,确保低配手机也能稳定运行。chapter_nav.js里还处理了防抖(连续点击只生效一次)和禁用按钮(提交中显示“加载中…”),这些细节在24个CSS文件中的css/loading.css里有对应.loading-btn:disabled样式。
3.2 后端路由:DispatcherServlet如何找到ChapterController
请求/chapter/next到达Tomcat后,由web.xml中配置的DispatcherServlet接管:
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
</servlet>
spring-mvc.xml里启用了注解驱动:
<mvc:annotation-driven />
<context:component-scan base-package="com.example.controller" />
于是ChapterController.java被扫描到:
@Controller
@RequestMapping("/chapter")
public class ChapterController {
@Autowired
private ChapterService chapterService;
@GetMapping("/next")
public String nextChapter(@RequestParam Long bookId,
@RequestParam Integer chapterNo,
Model model) {
Chapter nextChapter = chapterService.getNextChapter(bookId, chapterNo);
model.addAttribute("chapter", nextChapter);
model.addAttribute("bookId", bookId);
model.addAttribute("chapterNo", nextChapter.getChapterNo());
return "chapter_content"; // 跳转到 chapter_content.jsp
}
}
这里@GetMapping("/next")和@RequestParam的绑定,依赖spring-mvc.xml中<mvc:annotation-driven/>启用的RequestMappingHandlerMapping和ServletModelAttributeMethodProcessor。如果XML里没这行,注解就失效——这就是为什么必须保留XML。
3.3 业务逻辑:29个类中的关键三角关系
ChapterService的实现(ChapterServiceImpl.java)是核心枢纽:
@Service
public class ChapterServiceImpl implements ChapterService {
@Autowired
private ChapterDao chapterDao; // 查库
@Autowired
private BookDao bookDao; // 查图书元数据
@Override
@Transactional(readOnly = true)
public Chapter getNextChapter(Long bookId, Integer currentNo) {
// 步骤1:查当前图书总章节数,防越界
Integer totalChapters = bookDao.getTotalChapters(bookId);
if (currentNo >= totalChapters) {
throw new IllegalArgumentException("已是最后一章");
}
// 步骤2:查下一章内容
return chapterDao.selectByBookIdAndNo(bookId, currentNo + 1);
}
}
这里体现了29个类的分工:ChapterDao(接口)定义selectByBookIdAndNo方法,ChapterDaoImpl.java(实现类)用MyBatis的SqlSessionTemplate执行SQL;BookDao同理。@Transactional注解生效,依赖spring-tx.xml中配置的DataSourceTransactionManager Bean。整个链路里,ChapterController(接收请求)、ChapterServiceImpl(编排逻辑)、ChapterDaoImpl(执行SQL)形成清晰的三层,每层只依赖下一层接口,不跨层调用——这是可测试性的基础:你可以Mock ChapterDao,单独测ChapterServiceImpl的越界判断逻辑,无需启动数据库。
3.4 数据持久:SQL脚本如何支撑业务
test.sql脚本创建了核心四张表:
CREATE TABLE `book` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(200) NOT NULL,
`author` varchar(100),
`total_chapters` int NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
);
CREATE TABLE `chapter` (
`id` bigint NOT NULL AUTO_INCREMENT,
`book_id` bigint NOT NULL,
`chapter_no` int NOT NULL,
`title` varchar(200) NOT NULL,
`content` text NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_book_chapter` (`book_id`,`chapter_no`)
);
注意chapter表的联合唯一索引uk_book_chapter——这是保证“同一本书不能有重复章节号”的数据库级约束,比Service层if (chapterNo > 0)校验更可靠。test.sql还插入了测试数据:
INSERT INTO `book` VALUES (1, '《三体》', '刘慈欣', 65);
INSERT INTO `chapter` VALUES
(1, 1, 1, '序章', '...汪淼觉得,幽灵倒计时的出现,与他最近接触的纳米材料研究有关...'),
(2, 1, 2, '第一章', '...科学边界组织的成员们聚集在良湘加速器控制中心...');
这些数据让ChapterDaoImpl.selectByBookIdAndNo(1L, 2)能真实查到记录,而不是空指针异常。这就是为什么3个SQL脚本(init.sql建表、test.sql插测试数据、demo.sql演示复杂查询)缺一不可。
4. 前端工程深度解析:24个HTML、24个CSS、10个JS如何协同工作
很多人以为前端就是写几个页面,但本项目的24个HTML、24个CSS、10个JS,是按设备能力分层设计的,不是简单复制粘贴。
4.1 HTML结构:语义化与渐进增强
book_detail.html的body结构是:
<main class="content-area">
<article class="chapter-article">
<header class="chapter-header">
<h1 class="chapter-title" id="chapterTitle">第一章</h1>
<div class="chapter-meta">
<span class="book-title">《三体》</span>
<span class="chapter-no">第1章</span>
</div>
</header>
<div class="chapter-content" id="chapterContent">
<!-- 此处由JS动态注入,避免服务端渲染长文本阻塞 -->
</div>
<nav class="chapter-nav">
<button onclick="prevChapter()" class="nav-btn prev-btn">上一章</button>
<button onclick="toggleNightMode()" class="nav-btn mode-btn">🌙 夜间模式</button>
<button onclick="nextChapter()" class="nav-btn next-btn">下一章</button>
</nav>
</article>
</main>
关键点在于<div class="chapter-content">为空,内容由js/chapter_load.js异步加载:
function loadChapterContent() {
const bookId = getQueryParam('bookId');
const chapterNo = getQueryParam('chapterNo');
fetch(`/api/chapter/content?bookId=${bookId}&chapterNo=${chapterNo}`)
.then(r => r.json())
.then(data => {
document.getElementById('chapterContent').innerHTML =
marked.parse(data.content); // 用marked.js渲染Markdown
});
}
这种“HTML骨架+JS填充内容”的方式,既保证了SEO基础(标题、元信息在HTML里),又提升了首屏速度(不用等长文本渲染完才显示导航栏)。marked.js被包含在lib/marked.min.js中,这是5个JAR包之外的前端依赖,项目已预置。
4.2 CSS分层:24个文件的职责划分
24个CSS文件不是随意命名,而是按功能域划分:
- base.css:重置默认样式、定义CSS变量(--primary-color: #2c3e50;)
- mobile.css:仅在max-width: 767px生效,设置font-size: 16px(防止iOS缩放)、touch-action: manipulation(优化触摸响应)
- night-mode.css:当<body class="night-mode">时激活,覆盖background-color、color、border-color
- print.css:为@media print定制,隐藏导航按钮,调整页边距
最精妙的是responsive-grid.css,它用CSS Grid实现真正的响应式布局:
.chapter-article {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
}
@media (min-width: 768px) {
.chapter-article {
grid-template-columns: 250px 1fr;
grid-template-areas:
"sidebar content"
"nav nav";
}
.chapter-header { grid-area: header; }
.chapter-content { grid-area: content; }
.chapter-nav { grid-area: nav; }
}
在平板上,章节标题固定在左侧250px侧边栏,正文在右侧自适应宽度;在手机上,自动切回单列流式布局。这比单纯用float或flex更简洁,且兼容性经caniuse.com验证,支持Chrome 66+、Safari 11.1+、Firefox 61+。
4.3 JavaScript交互:10个脚本的轻量级设计
10个JS脚本全部遵循“单一职责”原则:
- util.js:封装getQueryParam()、debounce()、throttle()等通用工具
- storage.js:封装localStorage操作,如saveBookmark(bookId, chapterNo, timestamp)
- night-mode.js:监听prefers-color-scheme系统偏好,并提供手动切换API
- search.js:实现客户端搜索(因数据量小,未调后端API),用indexOf()匹配章节标题和内容
特别值得提的是offline.js:
// 检测网络状态,离线时启用缓存
window.addEventListener('online', () => {
console.log('网络已恢复');
showNotification('网络已连接');
});
window.addEventListener('offline', () => {
console.log('网络已断开');
// 自动加载最近缓存的章节
const cached = localStorage.getItem('lastChapter');
if (cached) {
document.getElementById('chapterContent').innerHTML = JSON.parse(cached).content;
showNotification('已切换至离线模式');
}
});
它利用localStorage缓存最近阅读的章节,让用户在地铁等弱网环境仍能继续阅读。这种“降级体验”设计,正是掌上阅读系统的刚需,而非PWA的炫技。
5. 构建与部署实战:从mvnw到tomcat,一步不跳过
现在我们实操一把,从解压源码到浏览器看到首页。这不是理想化的教程,而是记录我真实遇到的三个典型问题及解法。
5.1 环境准备:JDK与IDE的最低要求
项目要求JDK 11(非17或21),因为pom.xml中指定了:
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
如果你用JDK 17,mvnw compile会报错Unsupported class file major version 61。解决方案只有两个:要么装JDK 11(推荐Adoptium Temurin 11),要么改pom.xml——但后者可能引发MyBatis 3.4.x兼容性问题,不建议。
IDE方面,IntelliJ IDEA Community版足够(无需Ultimate)。导入时选择“Maven project”,勾选“Create module groups”,确保src/main/java被识别为Sources Root。关键步骤:右键pom.xml → Maven → Reload project,否则@Autowired会标红,因为IDE没加载Spring依赖。
5.2 Maven构建:mvnw如何绕过网络限制
执行./mvnw clean package时,你可能会卡在Downloading from central: https://repo.maven.apache.org/maven2/...。这是因为国内访问中央仓库极慢。项目已内置解决方案:打开pom.xml,找到<repositories>节点:
<repositories>
<repository>
<id>aliyun</id>
<name>Aliyun Repository</name>
<url>https://maven.aliyun.com/repository/public</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>false</enabled></snapshots>
</repository>
</repositories>
这意味着mvnw会优先从阿里云镜像下载,速度提升10倍。如果仍失败,检查~/.m2/settings.xml是否误配了其他镜像——项目自带mvnw,就不要配全局settings.xml,避免冲突。
构建成功后,target/reading-system-1.0-SNAPSHOT.war生成。注意:这是WAR包,不是JAR,因为项目是传统Servlet容器部署(非Spring Boot内嵌Tomcat)。
5.3 Tomcat部署:配置server.xml与context.xml
将WAR包丢进Tomcat的webapps/目录后,启动bin/startup.sh(Linux/Mac)或bin/startup.bat(Windows)。首次访问http://localhost:8080/reading-system-1.0-SNAPSHOT/可能404,原因是项目上下文路径(Context Path)未配置。
解决方案:编辑conf/server.xml,在<Host>节点内添加:
<Context path="/reading" docBase="reading-system-1.0-SNAPSHOT" reloadable="true" />
然后访问http://localhost:8080/reading/。reloadable="true"允许热部署,修改JSP后无需重启Tomcat。
更关键的是数据库连接池配置。项目用HikariCP,但Tomcat需要context.xml声明JNDI资源。在conf/context.xml中添加:
<Resource name="jdbc/readingDB"
auth="Container"
type="javax.sql.DataSource"
factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
driverClassName="com.mysql.cj.jdbc.Driver"
url="jdbc:mysql://localhost:3306/reading_db?useSSL=false&serverTimezone=UTC"
username="root"
password="123456"
maxActive="20"
minIdle="5"/>
然后在spring-datasource.xml中引用:
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/readingDB"/>
</bean>
这样,数据库连接由Tomcat统一管理,比应用内建连接池更稳定。
5.4 首次运行必踩的坑与解法
| 问题现象 | 根本原因 | 解决方案 |
|---|---|---|
访问首页报HTTP Status 500 – Internal Server Error,日志显示ClassNotFoundException: org.springframework.web.servlet.DispatcherServlet | pom.xml中spring-webmvc依赖范围是provided,但Tomcat未提供该jar | 将<scope>provided</scope>改为<scope>compile</scope>,或在Tomcat的lib/目录放入spring-webmvc-5.3.31.jar |
登录后跳转/user/dashboard,但页面空白,控制台报Uncaught ReferenceError: $ is not defined | jquery.min.js未正确加载,因web.xml中<welcome-file-list>未包含index.html | 编辑web.xml,添加<welcome-file>index.html</welcome-file>,并确认js/jquery.min.js路径在HTML中为<script src="js/jquery.min.js"></script>(相对路径) |
点击“下一章”后URL变成/chapter/next?bookId=1&chapterNo=1,但页面显示404 | ChapterController的@GetMapping("/next")映射路径与web.xml中<servlet-mapping>的<url-pattern>不匹配 | 检查web.xml中<servlet-mapping>的<url-pattern>是否为/chapter/*(应为/或*.do),本项目用<url-pattern>/</url-pattern>,故/chapter/next可被DispatcherServlet捕获 |
这些问题在readme.txt里都有简要提示,但真实调试时,你需要看Tomcat的logs/catalina.out日志,而不是只盯着浏览器。这是我带学生时反复强调的:后端问题,永远先看服务端日志,而不是猜前端JS。
6. 扩展与二次开发指南:如何把它变成你的项目
这套代码不是终点,而是起点。以下是我在实际项目中验证过的三个扩展方向,附具体操作步骤。
6.1 接入MySQL 8.0:升级驱动与认证插件
原SQL脚本基于MySQL 5.7,若你用MySQL 8.0,会报错Client does not support authentication protocol requested by server。这是因为8.0默认用caching_sha2_password插件,而老驱动不支持。
操作步骤:
1. 修改pom.xml,升级MySQL驱动:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
- 在MySQL中执行:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '123456';
FLUSH PRIVILEGES;
- 更新
spring-datasource.xml中的JDBC URL:
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/reading_db?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true"/>
allowPublicKeyRetrieval=true是8.0驱动必需参数。
6.2 添加PDF阅读支持:集成PDF.js
用户常问“能不能直接读PDF?”答案是可以,但需前端改造。PDF.js是Mozilla开源的纯JS PDF渲染器,无需后端转换。
操作步骤:
1. 下载PDF.js预构建包(pdfjs-dist),放入webapp/lib/目录
2. 在book_detail.html中添加PDF查看器容器:
<div id="pdf-viewer" style="display:none;">
<canvas id="the-canvas"></canvas>
</div>
- 编写
js/pdf-reader.js:
function loadPDF(pdfUrl) {
pdfjsLib.getDocument(pdfUrl).promise.then(function(pdf) {
pdf.getPage(1).then(function(page) {
const viewport = page.getViewport({ scale: 1.5 });
const canvas = document.getElementById('the-canvas');
const context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
const renderContext = {
canvasContext: context,
viewport: viewport
};
page.render(renderContext);
});
});
}
- 在
ChapterController中新增API,返回PDF文件路径(非内容),由前端JS加载。这样避免后端内存溢出。
6.3 增加阅读进度同步:用WebSocket实现实时更新
当前书签是本地存储,若用户换设备就丢失。用WebSocket可实现多端进度同步。
操作步骤:
1. 在pom.xml添加WebSocket依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>5.3.31</version>
</dependency>
- 创建
WebSocketConfig.java(需在spring-websocket.xml中扫描):
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new ReadingProgressHandler(), "/ws/progress")
.setAllowedOrigins("*");
}
}
- 前端
js/progress-sync.js监听翻页事件,发送WebSocket消息:
const socket = new WebSocket('ws://localhost:8080/reading/ws/progress');
socket.onmessage = function(event) {
const data = JSON.parse(event.data);
if (data.userId === currentUser.id) {
updateProgressBar(data.progress); // 更新UI进度条
}
};
这样,用户在手机上读到第5章,平板上的进度条会实时跳转——这才是真正的“掌上阅读”。
最后分享一个小技巧:如果你想快速验证某个功能(比如夜间模式CSS是否生效),不必重启整个Tomcat。只需修改css/night-mode.css,然后在浏览器按Ctrl+F5强制刷新,CSS会立即生效。因为CSS是静态资源,Tomcat默认不缓存开发模式下的静态文件。而Java类修改,则必须重启——这就是为什么我把业务逻辑尽量抽到Service层,让CSS/JS的调试能脱离后端重启,大幅提升迭代效率。
简介:这个Java Web阅读系统包含完整的后端服务和适配手机、平板的前端界面。后端用Java开发,基于Spring框架,有29个业务类,支撑用户注册登录、图书分类管理、章节内容读取、书签保存等核心功能;前端由24个HTML页面、24份CSS样式文件和10个JavaScript脚本组成,实现响应式布局、翻页操作、关键词搜索、夜间模式切换等交互体验;配套13个XML配置文件完成Spring整合,3个SQL脚本用于建表和初始化测试数据,5个常用工具JAR包已内嵌;项目自带Maven Wrapper(mvnw)、pom.xml和wrapper目录,无需预装Maven即可一键编译运行。适合用来练习Java Web开发全流程,理解前后端分离基础结构,或快速搭建轻量级电子书阅读后台。
&spm=1001.2101.3001.5002&articleId=161763993&d=1&t=3&u=61db589d788540d0b5aa3895c6fb4c1c)
1038

被折叠的 条评论
为什么被折叠?



