毕业季收了一宿舍旧书扔了心疼卖了不值钱送给学弟学妹又找不到合适的渠道。这个场景每年都在校园里重复发生也是“校园旧书漂流交易系统”这一类项目一直有需求的原因。如果正在找 Java 全栈项目的练手题材或者毕业设计想做一个业务闭环完整、能演示、能部署、能写进简历的系统这个题目确实值得选。很多同学拿到这种需求后会直接开写代码结果越写越发现图书发布、漂流池、置换订单、用户管理、管理员审核这些模块看起来都不难但串在一起时总会出问题——要么数据库表建得不对要么后端接口和前端页面各写各的要么本地跑通了部署到服务器上却起不来。这篇文章从需求、表设计、后端实现、前端对接、环境部署、问题排查六个层面拆解校园旧书漂流交易系统的开发思路重点是让项目先能完整跑通再决定哪些点值得深入优化。1. 这类系统的真正难点不是写页面先说结论校园旧书漂流交易系统的难点不在 CRUD而在业务状态的流转设计和前后端数据契约的统一。“漂流”这个词听起来像是一个营销概念落到系统里它是一条明确的业务链路学生发布闲置旧书 → 图书进入全校可见的漂流池 → 其他学生浏览、联系、确定领取或换书 → 达成交易 → 图书状态变为已漂出。如果只做“发布列表详情”三个页面那不叫漂流交易系统只是一个图书展示网站。真正的交易系统必须考虑状态机、库存归属和操作的唯一性。还有一个容易被忽略的问题这个系统有“交易”属性但没有支付闭环。校园场景下最合理的模式是线上确认、线下交付。这意味着系统需要记录的是“谁发布”“谁希望领”“是否确认”“是否完成”而不是“钱打给谁”。很多初学者硬要往里面加微信支付、支付宝支付反而让项目变得不真实且复杂。所以适合这一题材的业务定位是面向高校学生的实名闲置旧书循环平台以“漂流池”为核心通过信息的透明流转降低旧书处置门槛。这类项目的典型功能边界可以这样划分用户端注册登录、个人中心、发布旧书、浏览漂流池、发起领取、查看漂流记录。管理端图书审核、用户管理、漂流记录追踪、类别维护。系统端图书唯一编号生成、状态变更记录、数据统计看板。从代码层面看把这张表设计好系统就成功了一半。2. 核心技术选型SpringBoot3 Vue3 MySQL 的组合逻辑校园项目的技术选型不能只看“流行”还要看是否匹配场景。Java 后端做这类信息管理型系统是典型的舒适区Spring Boot 的生态成熟大多数同学在课内都接触过 JPA 或 MyBatis。Vue.js 适合做后台管理系统和 C 端页面的理由也很直接组件化开发方式配合 Element Plus 这类组件库能在很短时间里搭出可用的后台页面。数据层使用 MySQL 是因为其稳定、通用、面试常问部署和学习资料都足够多。为什么强调 SpringBoot3SpringBoot3 是一个显式的分水岭它基于 Spring Framework 6 和 Java 17官方对旧版 Spring Boot 2.x 的维护力度已经逐步下降。如果要面向 2025 年之后的实际项目直接上手新版本能回避日后的迁移问题。需要注意SpringBoot3 的 Jakarta EE 命名空间变更会让很多旧版依赖从“javax.”迁移到“jakarta.”。如果参考了很多旧教程踩坑概率会明显偏高。技术栈组件清单可以参考层次技术选择说明后端框架Spring Boot 3.x提供依赖管理、自动配置、Web 服务持久层Spring Data JPA 或 MyBatis-PlusJPA 适合快速开发MyBatis-Plus 适合复杂查询前端框架Vue.js 3组合式 API 开发效率更高UI 组件库Element Plus管理端表单、表格、弹窗场景标配数据库MySQL 8.x生产环境常用版本构建工具Maven / npm后端和前端各自的依赖管理方式接口规范RESTful API前后端通过 JSON 交互选择 Spring Data JPA 还是 MyBatis 不必纠结。如果项目核心是业务状态流转、实体关系较多JPA 在开发效率上更高能省下大量实体映射文件如果后续要接手更复杂的 SQLMyBatis-Plus 又更直接。校园项目团队规模通常很小没有 SQL 优化的硬需求我更推荐 Spring Data JPA它能帮你把一个系统从零到一快速搭起来后面再针对慢查询做调整即可。3. 数据库设计先把“漂流”的字段设计清楚旧书漂流系统的表设计不用搞得太复杂核心表控制在 6-7 张以内可以让业务的每个环节都能被追溯。我建议的最小表集合是用户表学生用户和管理员共用一张表通过角色字段区分。图书类别表对书籍分类做筛选时能省不少事。图书表记录每本旧书的信息和当前状态。漂流记录表记录图书从发布到被领取、被确认的全过程。领取/申请记录表一个人发起领取后系统需要记录申请状态。校园信息表有些系统是全校通用有些是有校区或楼栋概念如果做预约自取这个表有必要。下面从用户表和图书表入手给出最核心的建表示例。CREATE TABLE user ( id bigint NOT NULL AUTO_INCREMENT COMMENT 用户ID, username varchar(50) NOT NULL COMMENT 学号/工号, password varchar(200) NOT NULL COMMENT 加密后的密码, nickname varchar(50) DEFAULT NULL COMMENT 昵称, role tinyint NOT NULL DEFAULT 0 COMMENT 角色 0-普通用户 1-管理员, phone varchar(20) DEFAULT NULL COMMENT 联系电话, school varchar(100) DEFAULT NULL COMMENT 学院/专业信息, created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, status tinyint NOT NULL DEFAULT 1 COMMENT 状态 1-正常 0-禁用, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; CREATE TABLE book ( id bigint NOT NULL AUTO_INCREMENT COMMENT 图书ID, book_no varchar(32) NOT NULL COMMENT 图书编号业务展示用, title varchar(100) NOT NULL COMMENT 书名, author varchar(100) DEFAULT NULL COMMENT 作者, publisher varchar(100) DEFAULT NULL COMMENT 出版社, category_id bigint DEFAULT NULL COMMENT 分类ID, owner_id bigint NOT NULL COMMENT 发布人ID, description text COMMENT 旧书描述、成色、笔记情况等, cover_url varchar(255) DEFAULT NULL COMMENT 封面图片地址, status tinyint NOT NULL DEFAULT 0 COMMENT 状态 0-待审核 1-漂流中 2-已被领取/已下架, created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_owner (owner_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT图书漂流表;漂流记录表的数据结构需要说明一下。它的意义不只是展示一个时间线更重要的是支撑“图书状态变化”的可追溯。比如一本书由“待审核”变为“漂流中”又由“漂流中”变为“领取确认中”每一次变化都应该留下记录。CREATE TABLE book_flow_record ( id bigint NOT NULL AUTO_INCREMENT, book_id bigint NOT NULL COMMENT 图书ID, from_user_id bigint DEFAULT NULL COMMENT 操作人ID, to_user_id bigint DEFAULT NULL COMMENT 领取人ID, action_type varchar(30) NOT NULL COMMENT 动作类型 PUBLISH / APPROVE / CLAIM / CONFIRM, remark varchar(255) DEFAULT NULL COMMENT 备注, created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_book (book_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT图书漂流记录表;这会让业务链路中每个环节都有据可查管理端展示“漂流轨迹”时直接查这张表即可。注意字段命名使用下划线风格还是驼峰风格需要在项目开始前确定。为了保证阅读通用性前后端交互统一使用驼峰命名 JSON 字段数据库统一使用下划线命名通过 MapStruct 或手动转换解决映射问题。4. 后端工程设计与代码落地后端工程结构建议按业务模块分包而不是按技术层次分包。很多课程设计里喜欢建一堆 entity / mapper / service / controller 的包但业务大了以后会很乱。更合理的结构是按业务模块分组。一个校园旧书漂流系统建议的结构是com.example.bookflow ├── BookFlowApplication.java ├── common │ ├── config │ ├── exception │ └── result ├── modules │ ├── user │ │ ├── UserController.java │ │ ├── UserService.java │ │ ├── UserServiceImpl.java │ │ ├── entity │ │ └── repository │ ├── book │ │ ├── BookController.java │ │ ├── BookService.java │ │ ├── BookServiceImpl.java │ │ ├── entity │ │ └── repository │ ├── flow │ │ ├── FlowRecordController.java │ │ ├── FlowRecordService.java │ │ └── repository │ └── admin └── ...先写统一返回对象。这是前后端协作的第一个关键约定。所有接口都返回一个固定结构前端就不用猜测接口成功还是失败。package com.example.bookflow.common.result; public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.code 200; result.message success; result.data data; return result; } public static T ResultT error(String message) { ResultT result new Result(); result.code 500; result.message message; return result; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code code; } public String getMessage() { return message; } public void setMessage(String message) { this.message message; } public T getData() { return data; } public void setData(T data) { this.data data; } }再以一个核心发布接口为例说明后端 Controller 和 Service 之间应该保持什么节奏。发布旧书的逻辑是接收用户输入 → 生成唯一图书编号 → 保存图书数据 → 写入漂流记录 → 返回已生成数据。发布阶段默认状态是待审核如果把它直接设置为漂流中会绕过管理端审核机制属于业务漏洞。package com.example.bookflow.modules.book; import com.example.bookflow.common.exception.BusinessException; import com.example.bookflow.common.result.Result; import org.springframework.web.bind.annotation.*; RestController RequestMapping(/api/books) public class BookController { private final BookService bookService; public BookController(BookService bookService) { this.bookService bookService; } PostMapping public ResultBookVO publish(RequestBody BookPublishRequest request) { return Result.success(bookService.publish(request)); } GetMapping(/{id}) public ResultBookVO detail(PathVariable Long id) { return Result.success(bookService.getDetail(id)); } PutMapping(/{id}/approve) public ResultVoid approve(PathVariable Long id, RequestParam Long adminId) { bookService.approve(id, adminId); return Result.success(null); } }Service 层承担业务规则控制。比如发布图书时校验分类、生成编号审核时校验管理员权限领取时使用事务保证同一本书不会被两个人同时申请成功。下面演示“发布”的方法逻辑其中设置图书编号可以通过简单的时间戳加随机数实现。package com.example.bookflow.modules.book; import com.example.bookflow.common.exception.BusinessException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.concurrent.ThreadLocalRandom; Service public class BookServiceImpl implements BookService { private final BookRepository bookRepository; private final FlowRecordRepository flowRecordRepository; public BookServiceImpl(BookRepository bookRepository, FlowRecordRepository flowRecordRepository) { this.bookRepository bookRepository; this.flowRecordRepository flowRecordRepository; } Override Transactional(rollbackFor Exception.class) public BookVO publish(BookPublishRequest request) { Book book new Book(); book.setTitle(request.getTitle()); book.setAuthor(request.getAuthor()); book.setPublisher(request.getPublisher()); book.setCategoryId(request.getCategoryId()); book.setOwnerId(request.getOwnerId()); book.setDescription(request.getDescription()); book.setCoverUrl(request.getCoverUrl()); book.setStatus(BookStatus.PENDING); book.setBookNo(generateBookNo()); Book saved bookRepository.save(book); FlowRecord record new FlowRecord(); record.setBookId(saved.getId()); record.setFromUserId(request.getOwnerId()); record.setActionType(FlowAction.PUBLISH.name()); record.setRemark(用户发布旧书); flowRecordRepository.save(record); return BookConverter.toVO(saved); } private String generateBookNo() { String time LocalDateTime.now() .format(DateTimeFormatter.ofPattern(yyyyMMddHHmmss)); int random ThreadLocalRandom.current().nextInt(100, 999); return B time random; } }这里用了 Transactional(rollbackFor Exception.class)有两个容易被低估的细节。第一Spring 声明式事务默认只回滚 RuntimeExceptionrollbackFor Exception.class 可以保证检查异常也能触发回滚。第二图书保存和漂流记录写入必须在一个事务里否则会出现“书创建了但没有记录”的数据割裂。对于领取旧书的动作还需要考虑并发问题。两台手机同时提交领取请求如果不在数据库层面做约束同本书就有两个申请记录。简单处理方式是在 book 表用状态位结合 update 的 where 条件实现乐观并发例如执行 update book set status 领取申请中 where id ? and status 漂流中返回受影响行数等于 1 才代表抢占成功。这个思路比单纯在代码里判断再更新更可靠。5. 前端 Vue.js3为什么不能把页面写成一堆 mixinVue 3 推荐组合式 API 的目的是让逻辑可以按功能聚合但在校园项目中我看到最多的问题是所有接口请求都直接写在页面组件里不同页面之间重复同样的分页逻辑、状态判断逻辑。正确的思路是拆成三部分API 请求模块、状态管理模块、页面展示组件。推荐前端目录结构src ├── api │ ├── book.js │ ├── user.js │ └── flow.js ├── assets ├── components │ ├── BookCard.vue │ └── FlowStatusTag.vue ├── router │ └── index.js ├── stores │ └── user.js ├── views │ ├── BookList.vue │ ├── BookDetail.vue │ ├── PublishBook.vue │ └── admin │ ├── BookAudit.vue │ └── FlowRecords.vue └── utils └── request.js请求封装这一步最为关键。如果每写一个接口都手动拼 axios.get 并解析 code团队协作时十有八九会出问题。可以集中维护import axios from axios import { ElMessage } from element-plus const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) request.interceptors.response.use( response { const res response.data if (res.code ! 200) { ElMessage.error(res.message || 请求失败) return Promise.reject(new Error(res.message || Error)) } return res.data }, error { ElMessage.error(error.message || 网络异常) return Promise.reject(error) } ) export default request在 Vue 3 页面中组合式 API 让“发布旧书页”的代码变得清晰template div classpublish-container el-form :modelform label-width80px el-form-item label书名 el-input v-modelform.title placeholder请输入书名/el-input /el-form-item el-form-item label作者 el-input v-modelform.author/el-input /el-form-item el-form-item label出版社 el-input v-modelform.publisher/el-input /el-form-item el-form-item label描述 el-input v-modelform.description typetextarea :rows4/el-input /el-form-item el-form-item el-button typeprimary :loadingloading clickhandlePublish 发布到漂流池 /el-button /el-form-item /el-form /div /template script setup import { ref } from vue import { publishBook } from /api/book import { ElMessage } from element-plus const form ref({ title: , author: , publisher: , description: , categoryId: 1 }) const loading ref(false) async function handlePublish() { if (!form.value.title) { ElMessage.warning(书名不能为空) return } loading.value true try { await publishBook(form.value) ElMessage.success(发布成功等待管理员审核) form.value { title: , author: , publisher: , description: , categoryId: 1 } } finally { loading.value false } } /script请注意 script setup 这种写法。很多人误以为 Vue3 就代表 script setup其实 Vue3 刚开始时仍然可以使用 Options APIscript setup 是后续才成为主流推荐的语法糖。它让组件顶层变量自动暴露给模板不需要 return逻辑也相对集中。前端对接有一个永远绕不开的问题——跨域。本地开发 Vue 在 5173 端口后端在 8080 端口直接请求会产生跨域错误。建议在 Vite 配置 dev server 代理而不是后端全部开启跨域。export default defineConfig({ plugins: [vue()], server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })这样前端请求 /api/books 时打包后由 Nginx 或网关将请求转发到后端服务生产环境中也不需要改动代码。6. 环境准备与本地部署实操本地运行这套项目需要准备三部分环境Java、MySQL、Node.js。Java 版本必须匹配 SpringBoot3。SpringBoot3 要求 JDK 17 及以上。如果还在使用 JDK 8运行时会直接报错提示版本不支持。建议直接安装 OpenJDK 17 或更高版本。MySQL 建议使用 8.x。虽然 MySQL 5.7 也能运行但 8.x 已经发布多年功能和性能更符合当下场景。安装后要确保 MySQL 服务已启动并创建好数据库。Node.js 建议使用 16.14 以上版本实际项目中采用 18 或 20 LTS 版本问题最少。后端配置文件 application.yml 需要关注几个重要配置项server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/book_flow_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: 你的数据库密码 driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update show-sql: true properties: hibernate: format_sql: true jwt: secret: your-secret-key-should-be-longer expire: 604800新手常踩的一个坑是 MySQL 8.x 的驱动类名。旧教程里通常写 com.mysql.jdbc.Driver在 SpringBoot3 和 MySQL 8.x 场景下应使用 com.mysql.cj.jdbc.Driver。同时 url 中需要带上 serverTimezone 参数否则会报时区错误。启动步骤可以总结为下面的顺序1. 启动 MySQL 服务确认 3306 端口可连接 2. 创建数据库 book_flow_db 3. 修改后端 application.yml 中的数据库账号密码 4. 在后端目录执行 mvn spring-boot:run 5. 在前端目录执行 npm install 然后 npm run dev 6. 浏览器访问 http://localhost:5173如果后端也能正常启动登录页能正确调取接口项目就算跑通了。这里建议先做一次最小验证——不登录直接调用一个无需鉴权的接口比如获取图书分类列表确认后端响应正常后再继续做登录注册联调这样能减少排查范围。7. 从课程设计到可部署项目的关键功能补充“能本地跑”和“能部署给老师/同学看”是两个层级。中间还有几项值得补充的功能设计和工程细节。第一鉴权不能只做前端路由拦截。很多校园项目把用户是否登录写在 Vue 路由守卫里这只能拦截小白用户。真正安全的做法是后端用 JWT 或 Session 对接口做统一鉴权前端在请求拦截器中携带 Token后端在 HandlerInterceptor 或 Filter 中校验 Token。以 JWT 为例用户登录成功后后端返回 Token前端把 Token 存入 localStorage 或 Pinia后续请求统一带上。第二管理端审核功能要写清审核动作。管理员可以将图书标记为通过或驳回如果驳回应填写原因用户端可以收到提示并修改后重新提交。这个流程是业务闭环里最容易被砍掉的部分但从真实项目角度看审核是不可省略的。第三文件上传与图片访问问题。书封面图片如果只保存 base64 字符串数据库会越来越胀。更常规的方式是后端接收 MultipartFile保存到配置的本地目录并使用 /upload 的静态资源映射对外提供访问。生产环境如果有多台机器通常会换到对象存储服务校园项目本地目录也够用。下面是一个简单的上传接口示例RestController RequestMapping(/api/file) public class FileController { private final String uploadDir /data/bookflow/uploads/; PostMapping(/upload) public ResultString upload(RequestParam(file) MultipartFile file) { if (file.isEmpty()) { return Result.error(上传文件为空); } String originalFilename file.getOriginalFilename(); String ext originalFilename ! null originalFilename.contains(.) ? originalFilename.substring(originalFilename.lastIndexOf(.)) : .jpg; String newFilename System.currentTimeMillis() _ UUID.randomUUID().toString().substring(0, 8) ext; try { File dir new File(uploadDir); if (!dir.exists()) { dir.mkdirs(); } File dest new File(uploadDir newFilename); file.transferTo(dest); return Result.success(/upload/ newFilename); } catch (IOException e) { return Result.error(上传失败); } } }然后在配置类中注册静态资源映射Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceHandler(file: uploadDir); } }第四接口幂等。用户点击“发布”按钮后网络卡顿再次点击会不会出现两条重复记录前端按钮置灰只是体验层手段后端还可以通过数据库唯一约束、幂等标识等方案处理。虽然课程设计里不一定要实现分布式锁但如果你能写出“按钮 loading 后端提交前查询相同时间碎片内是否已有同人同书记录”的处理方式在简历和面试中都会加分。8. 常见问题与排查思路下面的表格汇总了校园旧书漂流系统开发过程中出现频率较高的问题。问题现象可能原因排查方式解决方案Spring Boot 启动失败提示 Unable to start web server端口 8080 被占用或 Tomcat 启动失败查看异常堆栈具体位置执行 netstat -ano / lsof -i:8080 检查端口关闭占用进程或修改 server.portJava 程序报 java.lang.UnsupportedClassVersionError本地 JDK 版本过低与 SpringBoot3 要求的 JDK17 不匹配java -version 查看当前版本安装 JDK 17 并修改 IDE 项目 SDK 与 Maven 编译器版本数据库连接失败 Communications link failureMySQL 服务未启动、端口不对、账号密码错误或密码插件不兼容使用命令行 mysql -u root -p 测试本地连接确认 MySQL 服务已启动url 增加 allowPublicKeyRetrievaltrue启动时报 Unknown database book_flow_db数据库未创建查看 application.yml 中的数据库名用 CREATE DATABASE book_flow_db 创建数据库接口返回 403 或 401前端登录状态丢失JWT 过期、未携带 Token 或 Token 解析失败打开浏览器控制台查看请求头、响应状态清理旧 Token 重新登录检查后端拦截器放行路径跨域请求失败No Access-Control-Allow-Origin header前端直接请求后端地址且后端未配置跨域或代理配置不生效查看 Network 中请求 URL 是否走了代理改用 Vite proxy 代理或后端按需配置 CORS查询图书时日期显示为 GMT 而非北京时间未设置时区参数 serverTimezone查看数据库连接 urlurl 带 serverTimezoneAsia/Shanghai前端 npm run dev 报 Vite 版本不支持当前 Node 版本Node.js 版本过低或过高与 Vite 大版本不匹配node -v 查看当前版本查看 package.json 依赖范围安装 Node.js 18 或 20 LTS 后重新 npm install发布图书失败数据重复插入用户重复点击提交按钮且后端未做幂等处理查看数据库记录观察 id 自增间隔前端按钮加载态后端增加唯一校验同一本书被多个用户同时领取成功并发更新没有约束打开两个浏览器并行测试领取操作在数据层用条件更新状态比如 update ... where status 1出现问题时正确排错顺序应该从外到内先看浏览器 Network 面板里的接口实际返回再看后端控制台日志最后确认数据库数据是否符合预期。不要一上来就翻代码找逻辑那样很容易在错误假设里绕圈。9. 最佳实践与工程建议站在实际项目角度给这套校园旧书漂流系统提几条工程化建议。命名规范要统一。数据库表名建议使用单数单词之间用下划线分隔Java 类名使用大驼峰方法名和变量名使用小驼峰前端组件文件名与组件名保持一致。不要出现 bookService、bookservice、book_service 混用的情况。日志要留痕。管理端审核操作必须输出操作人、操作内容、被操作图书 ID。系统里的 publish、approve、claim 三个动作是数据敏感动作应该用 info 级别记录方便后期审计。配置与代码分离。数据库密码、JWT 密钥、文件上传目录不要硬编码到代码里。即使只是课程作业也应该把这些信息放在 application.yml 中生产环境通过环境变量覆盖。一个小习惯的差异会直接影响代码的专业观感。数据备份意识。校园项目的数据量一般不大但数据库仍然可能出现误删或误改。开发过程中建议定时导出 SQL 备份文件。管理员删除图书数据时采用逻辑删除加 deleted 字段而非物理删除能避免因误操作造成不可逆的影响。关于安全边界这里需要重点提醒管理端接口必须在后端做权限校验不能只靠前端隐藏按钮。普通用户 ID 为 1 的学生如果直接拼接管理端接口地址很可能看到审核列表接口。如果后端没有校验角色这就是一个越权漏洞。在对管理端新增、删除、审核等操作中都要先识别当前登录用户角色。10. 项目的扩展点与学习方向如果想在答辩或面试中展示出更高的思考水平可以从下面三个方向切入。第一个是数据可视化。在管理端增加“图书漂流统计”页面展示每类旧书的上架数量、热门书籍分类、活跃用户排行。如果这些数据是每次从数据库实时统计查询压力不小可以进一步使用定时任务或 Redis 缓存做汇总统计。这个功能虽然不大但能把“缓存”“定时任务”“数据聚合”三个知识点全部串起来。第二个是消息通知。当一本书审核通过或者被申请领取时发布者需要立刻知道。最简单的方式是站内消息表在业务动作发生后写入通知记录想更进一步可以对接邮件或企业微信机器人不过这已经超出项目常见需求建议按需扩展。第三个是履约记录。系统本身不执行线下取书但可以增加“用户确认收到书”的按钮。从 book_flow_record 表中关联出这条链路形成漂流痕迹。这会让项目更像是真正被使用过的系统。校园旧书漂流项目对学习的价值在于它不依赖复杂算法却要求开发者处理真实的业务状态流转和前后端协作。如果你正在做这个题目建议按照“建表 → 跑通后端接口 → 跑通前端页面 → 补充审核和鉴权 → 部署上线”的顺序推进。不要一上来就追求界面华丽先把一条数据从发布到被领取的完整生命周期打通你就已经掌握了这类信息管理系统 80% 的核心逻辑。
