ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Java后端+原生前端:掌上阅读项目前后端分离设计与联调实战

Java后端+原生前端:掌上阅读项目前后端分离设计与联调实战 简介基于Java的掌上阅读后端设计源码整合HTML、CSS和JavaScript技术面向需要搭建阅读类应用后端及前端界面的Java开发者与前端学习者。压缩包共180个文件约72.91MB包含29个Java源文件、29个class编译文件、24个HTML页面、24个CSS样式、10个JavaScript脚本、13个XML配置、5个JAR依赖包、3个SQL数据库脚本及若干图标字体文件覆盖从服务端接口、数据持久化到前端交互展示的完整链路。Java类负责处理用户注册登录、书籍管理、阅读记录等核心业务逻辑HTML5与CSS3构建阅读界面与响应式布局异步通信则通过JavaScript实现。项目还内置SQL建表语句与yml配置文件便于导入数据库后快速运行调试。目前已有372人浏览学习适合希望结合前后端实践理解移动阅读平台设计的中级开发者通过研读源码可掌握分层架构、RESTful接口设计、数据表规划及前端动效实现等关键技能。 做了这么多年 Java 后端像“掌上阅读”这类带 HTMLCSSJS 前端的项目我前前后后见了不少。这标题乍看是“一个毕设源码”但真拆开看它其实是一个典型的“Java 后端 原生前端三件套”全栈小项目核心价值不在代码量而在前后端怎么把数据流程跑通。很多新手拿到这种源码容易懵后端接口一堆前端页面也一堆到底从哪儿看起联调的时候跨域、JSON 格式、鉴权逻辑到处是坑。这篇文章我就从后端工程师的视角把这个“基于 Java 的掌上阅读后端 原生前端”项目从架构、数据模型、接口设计到前端交互完整拆一遍。不光是讲代码更重要的是讲明白“为什么这么做”以及我实战中踩过的几个典型问题。不管你是准备拿它做课程设计、想入门前后端分离项目实战还是单纯想看看 Java 后端怎么给页面供数据这篇都适合你。1. 项目定位与技术选型拆解1.1 这个项目到底解决什么问题掌上阅读本质上就是一个移动端阅读场景的 Web 应用。它的核心需求其实很聚焦用户能注册登录能看到书籍列表能点进一本书看详情能开始阅读并记录阅读进度最好还能有个书架把想看的书收起来。就这么点事但“麻雀虽小五脏俱全”。它覆盖了一个业务系统最常见的闭环用户体系 内容展示 用户行为记录。这种项目最适合用来理解“前后端分离”到底怎么运作。你看标题里写的是“后端 HtmlCSSJavaScript 设计源码”意思就是后端用 Java 写接口前端不依赖 Vue、React 这些重框架直接用原生三件套写页面。这样做的最大好处是你不需要懂 Node 环境、不需要构建工具一个浏览器加一个 Tomcat 就能把整个项目跑起来对初学者极其友好。提示拿到任何一套源码不要先急着启动。先分清哪些是后端工程、哪些是前端静态资源再找接口文档或者看代码里请求的 URL这个项目的骨架就出来了。1.2 为什么是 Java 后端 原生三件套先聊后端。Java 在这个场景里属于“稳妥牌”。Spring Boot 是目前 Java Web 的绝对主流内嵌 Tomcat写几个RestController就能把接口暴露出去配合 MyBatis-Plus 或者 Spring Data JPA一张表对应一个实体类CRUD 基本就是模板代码。做阅读类这种业务逻辑不算复杂的后端Java 的强项是结构清晰、类型安全部署也简单打个 jar 包丢服务器上就能跑。再看前端。很多人觉得 2025 年了还用原生 JavaScript 写页面是不是太原始恰恰相反原生三件套在这种项目里是“恰到好处”。掌上阅读的核心交互无非就是列表、详情、翻页、进度保存用fetch调接口、用 DOM 操作渲染列表、用localStorage存一下阅读进度全部都能实现而且没有任何构建负担。更重要的是用原生 JS 能逼你理解 HTTP 请求的本质。用 Vue 的时候你可能是“照着模板写”但用原生三件套你必须自己拼 URL、自己处理fetch的 Promise、自己解析 JSON 渲染到页面上这一套流程跑通了以后上任何框架都是降维打击。所以选这套组合不是技术落后而是学习路径上的最优解。维度Java 后端方案原生前端方案技术栈Spring Boot MyBatis / JPAHTML CSS JavaScript启动方式打包 jar 运行或 IDE 直接跑静态页面部署到 Tomcat/nginx学习成本中等重点是接口思维低无需构建工具交互方式提供 RESTful JSON 接口fetch/Ajax 调用后端接口2. 核心功能模块与数据库设计思路2.1 阅读场景下的功能边界在动手看代码之前先理清楚这类项目通常包含哪些模块。掌上阅读不是电商系统不需要复杂的商品 SKU也不是社交软件不需要关注、私信。它的功能边界应该控制在“读”这条主线上用户模块注册、登录、个人信息查看。这个不用多说几乎所有系统都有。书籍模块书籍列表展示、书籍分类筛选、书籍详情页。数据来源一般是一张book表。书架模块用户把书籍加入书架、移出书架。这是用户和书籍的关联关系。阅读模块获取书籍章节内容、保存阅读进度、展示当前进度。这是阅读类项目的灵魂也是区别于普通 CMS 的关键功能。评论/评分可选不少项目会加一个书籍评分或者简短评论属于锦上添花。你拿到源码时可以对照一下基本八九不离十。新手容易犯的毛病是“一开始就想着做大而全的系统”但阅读类项目的核心体验就一条让用户点开一本书能接着上次的位置继续读。如果这个链路是通的项目就成功了八成。注意解析源码时先跑通“登录 → 书籍列表 → 书籍详情 → 阅读章节 → 保存进度”这条主链路比逐个类去抠代码高效得多。2.2 数据库模型从用户到书架数据库设计是这类项目最见功力的地方。我看过很多新手自己写的表喜欢一把梭把所有字段堆在一张表里结果后面改需求时痛不欲生。掌上阅读至少需要这么几张核心表t_user用户表字段一般是id主键、username、password存加密后的密文、nickname、create_time。t_book书籍表包含id、book_name、author、cover_url、category_id分类外键、description、word_count字数等。有些项目还有hot热度字段用来做推荐排序。t_book_category书籍分类表字段就id和category_name。注意如果把分类字段直接存成字符串放进t_book当然也能跑但后续维护会很难受规范做法是建一张分类表书籍表存外键。t_user_book或t_bookshelf书架关联表最关键的是用户 ID 和书籍 ID 的联合唯一索引防止同一本书被重复加入书架。t_book_content书籍内容表一般会存每个章节的文字内容字段有id、book_id、chapter_index章节序号、chapter_title、content长文本。t_user_progress阅读进度表记录用户读到某本书的哪个章节、什么位置。字段是id、user_id、book_id、chapter_index、progress_position可选、update_time。这套表设计是典型的“用户-书籍多对多 用户行为记录”模型。阅读进度单独建表非常重要书的内容是公共资源阅读进度是用户私有数据两者混在一起会出问题。你存进度时只要把user_id book_id作为查询条件拿到chapter_index后回到t_book_content里去取对应章节内容整个“接着读”的流程就闭环了。3. 后端接口设计与核心实现3.1 RESTful API 划分与返回格式约定后端接口怎么设计直接决定前端开发时爽不爽。一套规范的 RESTful 接口应该让人看一眼 URL 就知道在操作什么资源。掌上阅读项目的接口大致如下POST /api/user/register注册。接收username和password注册成功后直接返回用户信息或者 token。POST /api/user/login登录。校验用户名密码成功后返回 token可以是简单的 UUID 或 JWT前端存到localStorage里。GET /api/book/list书籍列表支持按categoryId筛选、按关键词搜索分页返回。GET /api/book/{id}书籍详情返回书籍基本信息分类名。GET /api/book/content?id{bookId}chapter{index}获取指定章节内容返回章节标题和正文。POST /api/bookshelf/add加入书架。DELETE /api/bookshelf/{bookId}移出书架。GET /api/bookshelf/list当前用户的书架列表注意这里要从 token 里解析出用户 ID。POST /api/progress/save保存阅读进度。GET /api/progress/get?bookId{bookId}获取某本书的阅读进度。接口格式这里有个关键约定所有接口的返回值建议统一用一个Result包装类。比如{ code: 200, message: success, data: {...} }code用来表示业务状态码data放真正的业务数据。这个格式定了之后前端fetch返回的 JSON 结构永远是一致的处理起来就非常统一。public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(success); result.setData(data); return result; } public static T ResultT error(Integer code, String message) { ResultT result new Result(); result.setCode(code); result.setMessage(message); return result; } }实操心得很多新手忽略统一返回格式的重要性每个接口返回的数据结构都不一样前端解析时分支判断写到怀疑人生。这个Result类建议直接复制到你的后端工程里一劳永逸。3.2 登录鉴权从 Session 到 Token登录鉴权是这类项目的重难点。早年 Java Web 常用HttpSession登录成功后把用户信息塞进 Session浏览器靠 Cookie 自动携带 SessionId。但在前后端分离场景下前端页面可能跑在 8080 端口后端接口跑在 9090 端口Cookie 跨域携带非常麻烦。所以建议项目直接用Token 机制登录成功就用用户 ID 生成一个 token简单点可以用UUID.randomUUID()正规项目用 JWT返回给前端存储前端每次请求都把这个 token 放在请求头Authorization里后端用一个拦截器统一校验。这里最容易被忽略的是“哪些接口需要鉴权”。书籍列表、书籍详情这是公开资源不需要登录也能看但书架列表、阅读进度保存这些必须登录因为数据跟用户强关联。方案是写一个 WebMvcConfigurer 注册拦截器在拦截器里排除掉登录注册接口和书籍查询接口其余接口统一从请求头里取 token 并解析用户信息。Component public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 前端请求头中携带 token String token request.getHeader(Authorization); if (token null || .equals(token)) { response.setStatus(401); return false; } // 这里根据你的 token 生成方式解析出 userId放到 request attribute 中方便后续使用 Integer userId TokenUtils.parseToken(token); if (userId null) { response.setStatus(401); return false; } request.setAttribute(userId, userId); return true; } }注意拦截器一定要在addPathPatterns里排除/api/user/login和/api/user/register以及/api/book/**的查询类接口否则前端还没登录就访问书籍列表直接被拦了排查起来也是一头雾水。3.3 搜索与阅读进度的细节处理搜索功能看似简单但 SQL 写法有讲究。简洁方案是WHERE book_name LIKE CONCAT(%, #{keyword}, %)不过需要注意关键词包含%或_时的转义处理这是一个隐藏的坑。如果系统数据量大LIKE全模糊匹配会全表扫描实际生产环境一般引入 Elasticsearch但这个项目用 MySQL 就够了不需要过度设计。阅读进度保存的时机也很讲究。如果用户每翻一页就调一次保存接口后端压力很大但如果只在用户退出时保存一次突然断电或者浏览器崩溃进度就丢了。折中方案是前端在用户阅读过程中每隔 5 秒做一次“节流保存”如果章节没有变化就不调接口后端保存时用INSERT ... ON DUPLICATE KEY UPDATE或先查后更避免频繁重复插入并且只更新chapter_index字段。这个“节流”思路在很多场景都通用线上项目也是这样干的。4. 前端结构与交互实现4.1 页面骨架一个入口一个容器前端部分用原生三件套页面的组织通常有两种方式。一种是每个页面一个独立 HTML 文件比如login.html、index.html、detail.html、reader.html页面间靠a标签跳转或者location.href跳转另一种是单页应用思路用 JS 根据 URL 的 hash 切换div的显示和隐藏。对于掌上阅读这种项目我推荐第一种结构简单、每个页面职责清晰。公共样式可以抽到一个css/common.css文件里头部导航栏、底部标签栏这种公共组件用 JS 动态渲染成公共 HTML 片段避免每个页面复制粘贴。举个例子底部导航栏包含“首页、书架、我的”三个 Tab每次切换就同步高亮样式这个逻辑如果复制粘贴到每个 HTML 里后期改一个图标要改三个文件非常痛苦。div idapp/div script function loadBookList(data) { const app document.getElementById(app); let html ; data.forEach(book { html div classbook-card onclickgoDetail(${book.id}) img src${book.coverUrl} alt${book.bookName} p classbook-name${book.bookName}/p p classbook-author${book.author}/p /div; }); app.innerHTML html; } /script实操心得原生 JS 拼 HTML 字符串时容易因为引号嵌套写错。ES6 的模板字符串反引号是神器编辑器对模板字符串会高亮拼接变量用${}也不会和 HTML 的引号冲突。这个习惯从现在养成以后写代码效率高很多。4.2 数据交互fetch 封装与跨域处理前端调后端接口的统一封装很有必要。你可以写一个request.js把fetch包一层每次请求自动带上Authorization头并统一处理 JSON 解析和错误码判断async function request(url, options {}) { const token localStorage.getItem(token); const headers { Content-Type: application/json, ...options.headers }; if (token) { headers[Authorization] token; } const response await fetch(/api url, { ...options, headers }); // 统一的 JSON 解析 const result await response.json(); if (result.code 401) { // token 失效跳转到登录页 window.location.href /login.html; } return result; }这一段代码就有很强的实战价值。很多新手写前端时每个页面都写fetch(http://localhost:9090/api/user/login)写死了完整的 URL。如果后端端口变了或者要换到线上域名所有页面的代码都要改一遍。用相对路径/api配合开发环境的代理配置Vite、webpack 的 proxy或者直接把前端页面也放到同一个 Tomcat 下就能把“跨域问题”挡在环境层面解决代码层面完全不用关心。注意如果你不配置任何代理直接用一个静态页面的file://协议打开 HTML 去请求http://localhost:9090浏览器一定会报跨域错误。最省事的方案是把前端静态资源直接扔进后端的src/main/resources/static目录这样前后端同源彻底绕开跨域。很多“设计源码”就是这么干的简单粗暴但有效。4.3 阅读器页面的样式细节阅读器页面是整个前端最花功夫的地方。阅读页的排版对 CSS 要求不低正文的字体大小、行高、页面边距需要让用户可调节背景色一般提供白色、米黄色、夜间黑三种主题模式长文本超出屏幕高度时要能滚动最好还显示“当前章节/总章节”的进度条。这部分实现有几个实用的 CSS 技巧。正文区域建议用max-width: 720px; margin: 0 auto;让内容在手机端和大屏上都不至于太宽文字颜色和背景色做成 CSS 变量切换主题时只需要改body上的>:root { --bg-color: #ffffff; --text-color: #333333; } body[data-themenight] { --bg-color: #1e1e1e; --text-color: #aaaaaa; } .reader-content { background-color: var(--bg-color); color: var(--text-color); }实操心得CSS 变量是原生 CSS 里被严重低估的功能处理多主题配色比用 JS 逐个改元素样式省太多事。这个方案的响应速度极快因为浏览器原生支持不需要重绘所有节点。5. 联调部署与常见问题排查5.1 联调时期一定会遇到的经典坑第一个坑是 JSON 格式不匹配。后端的 LocalDateTime 序列化出来的格式是2025-01-15T10:20:30前端想显示的是2025-01-15 10:20如果不做处理前端拿到的就是个带 T 的字符串。解决方式是在后端配置jackson的日期格式或者在实体类的日期字段上加JsonFormat(pattern yyyy-MM-dd HH:mm:ss)。第二个坑是 Long 类型精度丢失。数据库主键是bigint时如果值超过 JavaScript 的Number.MAX_SAFE_INTEGER前端拿到的主键后几位会被四舍五入变成 0导致后续请求传参传了错误 ID。这个问题的标准解法是后端把 Long 类型的主键序列化为字符串或者全局配置ToStringSerializer把 Long 转成字符串返回。新手项目很容易忽略这个等排查到的时候往往已经在线上崩了。第三个坑是跨域配置没生效。前端用fetch时如果带了Authorization请求头后端CorsFilter的allowedHeaders必须包含Authorization否则浏览器预检请求OPTIONS直接失败。常见错误具体表现排查方向日期格式带T字母页面显示2025-01-15T10:20:30检查后端 JSON 序列化日期格式长整型主键精度丢失点击详情跳转后 404检查后端是否把 Long 转为 String 返回跨域预检失败浏览器控制台报 CORS error检查后端是否允许Authorization请求头中文乱码页面显示为问号或乱码检查数据库连接是否配置characterEncodingutf-85.2 启动与部署从 IDEA 到服务器整个项目在本地跑起来的顺序很简单先用 IDEA 导入后端 Maven 工程等依赖下载完配置好application.yml里的数据库连接信息账号、密码、库名先执行项目里的sql脚本建表再启动 Spring Boot 应用如果前端在resources/static目录下直接访问http://localhost:8080/index.html就能看到登录页。如果前端是独立的文件夹那就用 VSCode 的 Live Server 插件起一个 5500 端口的静态服务也行但是记得处理跨域。等本地调试没问题要部署到服务器时标准流程是在后端机器上执行mvn clean package -DskipTests打包出 jar用nohup java -jar reader.jar app.log 21 后台启动前端静态文件如果用 Nginx 托管配一个location /api { proxy_pass http://127.0.0.1:8080; }把接口请求转发到后端服务。这套“前后端同域”的部署方式既不需要处理跨域又能把静态资源访问和接口转发统一到同一个入口线上运维非常省事。注意如果你同时管理前端静态资源和后端接口一定要给后端服务的启动脚本加上-Dfile.encodingutf-8否则服务器上中文很容易乱码。这个参数比大多数代码层面的编码设置都管用。5.3 这套源码后续还能怎么扩展掌上阅读这类项目最大的优点就是“留白合理”。它把基础业务做完了但没有把架构写死你可以顺着它延伸出不少实战功能后端加个Redis做书籍列表热点缓存顺便把登录 token 存在 Redis 里设置过期时间相当于理解了“缓存 分布式会话”的雏形。前端给阅读器加一个“字体大小调节”和“翻页动画”用原生 JS 实现浏览器本地存储同步对前端水平提升很有帮助。给书籍表和内容表加个全文搜索引入Elasticsearch或者先学MySQL FULLTEXT这是搜索工程师路线的一个小入口。把当前的单体结构拆成spring-cloud微服务用户服务、书籍服务、阅读服务理解服务拆分和 Feign 调用的关系但这属于进阶玩法建议先把单体摸透再动刀。从我自己的经验来看源码是死的人是活的。这个项目最好的使用姿势不是直接拿来交作业而是先把主链路跑通然后选一个你感兴趣的点去“破坏性修改”。比如把登录改成 JWT 拦截器把书籍列表改成 Redis 缓存把前端页面改成按加载滚动到底部分页。每改一处你对整个前后端协作机制的理解就会深一层。最后再分享一个小技巧排查线上问题时先在浏览器开发者工具的 Network 面板里看请求和响应。如果响应状态是 401那是鉴权问题是 404多半是 URL 拼错了是 500再看后端控制台的具体异常栈。这套排查习惯比记住任何框架 API 都值钱。本文还有配套的精品资源点击获取
返回列表