简介:在软件开发领域,B/S架构是构建Web应用的主流模式,其核心在于通过浏览器与服务器分离,实现业务逻辑与用户界面的解耦。SpringBoot作为Java生态中广受欢迎的框架,通过约定大于配置的理念,极大地简化了企业级应用的开发、配置和部署流程,是快速构建RESTful API服务的利器。结合微信小程序这一轻量级前端平台,开发者能够打造出用户体验流畅、即用即走的移动端应用。这种技术组合的价值在于,它能高效地将传统业务流程数字化,特别适用于图书管理这类需要线上线下结合、数据实时交互的场景。本文以图书管理系统为例,深入剖析如何运用SpringBoot框架与微信小程序,从数据库设计、核心业务实现到安全认证与性能优化,构建一个功能完整、可扩展的毕业设计项目,为初学者提供一个从理论到实践的完整学习路径。
1. 项目概述与核心价值
最近在整理硬盘,翻出来一个几年前带学生做的毕业设计项目,一个基于JAVA技术栈的微信小程序图书管理系统。当时这个项目拿了高分,学生也顺利毕业了。今天把它拿出来拆解一下,一方面是给正在做类似毕设的同学们一个完整的、可以直接“抄作业”的参考,另一方面,也是想从一个过来人的角度,聊聊怎么把一个常见的“图书管理系统”做出亮点,让它不仅仅是一个CRUD的作业,而是能真正体现你技术思考和工程能力的作品。
这个项目本质上是一个典型的B/S架构应用,后端采用SpringBoot框架快速搭建RESTful API服务,数据持久化层使用MySQL,前端则是轻量级的微信小程序。听起来技术栈很常规,对吧?但常规不代表简单。一个能拿高分的毕设,关键在于如何在常规需求上做出深度和细节。比如,如何设计一个既满足三范式又兼顾查询性能的数据库?如何利用SpringBoot的生态(如Spring Security, MyBatis-Plus)来优雅地处理权限和复杂查询?微信小程序端如何与后端进行安全、高效的数据交互?这些才是评委老师真正会看的地方。
这个系统主要解决了小型图书馆或图书角的管理痛点:手工记录效率低、易出错、查询统计困难。通过小程序,管理员可以扫码录入新书、办理借阅归还;普通用户(学生/读者)可以随时随地查询馆藏、查看个人借阅记录、进行图书预约。它适合计算机相关专业,特别是软件工程、信息管理专业的同学作为毕业设计或课程设计,也适合刚入门JAVA全栈开发,想找一个完整项目练手的朋友。接下来,我会从设计思路、技术细节到避坑经验,毫无保留地分享一遍。
2. 整体架构设计与技术选型考量
2.1 为什么是SpringBoot + 微信小程序这个组合?
很多同学选型时可能会纠结,为什么不用更传统的JSP/Servlet,或者更新潮的Vue+SpringCloud?对于毕设项目,我的核心建议是:在满足功能需求的前提下,选择生态成熟、资料丰富、能集中体现你核心学习成果的技术栈。
首先,SpringBoot是JAVA企业级开发的事实标准。它通过“约定大于配置”的理念,极大地简化了SSM(Spring+SpringMVC+MyBatis)框架的整合与配置。对于毕设来说,这意味着你可以把宝贵的时间从繁琐的XML配置中解放出来,专注于业务逻辑的实现。同时,SpringBoot内嵌Tomcat,一键启动,非常适合演示和部署。评委老师看到你用了SpringBoot,会默认你掌握了现代JAVA Web开发的主流框架。
其次,前端选择微信小程序而非Web页面,有几个现实优势。第一,微信小程序无需安装、即用即走,用户体验更轻便,特别适合图书查询、个人中心这类低频但需要随时访问的场景。第二,小程序开发框架(如WXML/WXSS)学习曲线相对平缓,且与后端通过HTTPS API交互,架构清晰,符合前后端分离的现代开发模式。第三,在答辩演示时,直接用手机打开小程序进行操作,比打开电脑浏览器访问一个本地localhost:8080的网页,显得更完整、更“像”一个真实的产品。
最后,MySQL作为关系型数据库,在数据一致性、复杂查询(如多表关联查询借阅历史)方面有天然优势,并且与SpringBoot通过MyBatis或JPA可以无缝集成。这个组合技术栈成熟、社区活跃,你在开发中遇到的几乎任何问题,都能在网上找到解决方案,这对于限时完成的毕设项目至关重要。
2.2 核心业务模块与数据库设计思路
一个图书管理系统的核心业务无外乎“书”、“人”、“借还”这三件事。但设计的好坏,直接决定了代码的复杂度和系统的扩展性。
核心实体关系分析:
- 图书(
book):核心实体。除了书名、作者、出版社、ISBN这些基本信息,还需要考虑“在馆状态”(可借、已借出、遗失等)、“馆藏位置”(书架号)以及“分类号”。分类号建议采用《中国图书馆分类法》,这是一个加分项,体现了你对专业领域的了解。 - 用户(
user):这里用户分为两类。一类是读者(reader),对应微信小程序端的普通用户,属性包括学号/工号、姓名、所属院系、可借阅数量、已借数量等。另一类是管理员(admin),负责后台管理,通常我们将其与读者分开设计,权限控制更清晰。 - 借阅记录(
borrow_record):这是连接“书”和“人”的纽带。每条记录需要包含:记录ID、图书ID、读者ID、借出时间、应还时间、实际归还时间、续借次数、超期天数、状态(借出、已还、超期)等。这里的设计关键点在于“应还时间”的计算,它不能简单写死为“借出时间+30天”,而应该考虑闭馆日、节假日等因素。一个简单的实现是在系统设置里维护一个“借阅周期”字段(如30天),然后由后端计算。
数据库设计中的几个“坑”与技巧:
- 避免循环依赖:不要在
book表里直接存“当前借阅人ID”,也不要在reader表里存“当前借阅图书ID列表”。这种设计会导致更新异常。正确的做法是通过borrow_record表关联,查询当前借阅情况时,联表查询状态为“借出”的记录即可。 - 索引优化:为
book表的isbn、book_name字段,borrow_record表的reader_id、book_id、status字段建立索引,能极大提升查询速度。特别是用户在小程序上搜书,以及查询个人借阅记录时。 - 状态字段枚举化:像图书状态、借阅记录状态,建议使用
TINYINT类型存储,在代码中用枚举类(Enum)管理。例如,book_status: 0-可借,1-已借出,2-整理中,3-遗失。这样比存字符串“可借”更节省空间,查询效率也更高。
3. 后端核心实现与SpringBoot实战要点
3.1 项目结构与分层设计
一个清晰的MVC(或更精确地说是Controller-Service-Dao)分层是代码可维护性的基础。典型的SpringBoot项目结构如下:
src/main/java/com/yourdomain/library/ ├── controller/ # 控制层,接收小程序请求,返回JSON ├── service/ # 业务逻辑层,核心 │ ├── impl/ # 服务实现类 ├── dao/ # 数据访问层(或叫mapper),使用MyBatis ├── entity/ # 实体类,对应数据库表 ├── dto/ # 数据传输对象,用于前后端交互 ├── vo/ # 视图对象,用于封装返回给前端的数据 ├── config/ # 配置类(如跨域、Swagger、安全) └── utils/ # 工具类(如日期处理、字符串处理)关键技巧:在entity中,使用JPA注解或MyBatis-Plus的@TableName、@TableField来映射表字段。在service层,接口和实现分离,便于未来扩展和单元测试。controller层的方法应尽量简洁,只负责参数校验、调用服务和返回结果,复杂的业务判断全部放到service中。
3.2 使用MyBatis-Plus极大提升开发效率
对于毕设项目,我强烈推荐使用MyBatis-Plus(MP),它是MyBatis的增强工具。它提供的通用Mapper和Service,能让你免去编写大量简单SQL的烦恼。
例如,对于Book实体,你只需要:
- 让
BookMapper接口继承BaseMapper<Book>。 - 让
BookService接口继承IService<Book>,让BookServiceImpl继承ServiceImpl<BookMapper, Book>并实现BookService。
然后,你就可以直接使用诸如bookService.save(book),bookService.getById(id),bookService.page(pageQuery)这样的方法,完成90%的单表CRUD操作。对于复杂的多表查询,你依然可以使用MP提供的QueryWrapper进行条件构造,或者直接编写XML映射文件。
一个分页查询图书列表的典型Service方法:
@Service public class BookServiceImpl extends ServiceImpl<BookMapper, Book> implements BookService { @Override public Page<BookVO> getBookPage(PageQuery query, String keyword) { Page<Book> page = new Page<>(query.getPageNum(), query.getPageSize()); LambdaQueryWrapper<Book> wrapper = new LambdaQueryWrapper<>(); if (StringUtils.isNotBlank(keyword)) { wrapper.like(Book::getBookName, keyword) .or().like(Book::getAuthor, keyword) .or().like(Book::getIsbn, keyword); } // 假设只查询在馆可借的图书 wrapper.eq(Book::getStatus, BookStatus.AVAILABLE.getCode()); wrapper.orderByDesc(Book::getCreateTime); Page<Book> bookPage = this.page(page, wrapper); // 将Page<Book>转换为Page<BookVO>返回给前端 return convertToBookVOPage(bookPage); } }3.3 用户认证与API安全设计
微信小程序端与后端的通信必须是安全的。我们采用经典的JWT(JSON Web Token)方案,而不是传统的Session。因为小程序是跨端的,JWT的无状态特性更合适。
流程如下:
- 用户在小程序端输入学号和密码(初始密码可能为学号后六位或身份证后六位)登录。
- 后端验证通过后,使用一个密钥(存储在
application.yml中)生成一个JWT Token,其中包含用户ID、角色等非敏感信息,并设置一个过期时间(如2小时)。 - 后端将Token返回给小程序。
- 小程序后续请求任何需要认证的API时,都在HTTP请求头
Authorization中携带这个Token(格式:Bearer your_token_string)。 - 后端通过一个自定义的拦截器(Interceptor)或过滤器(Filter)来校验每个请求的Token是否有效、是否过期。校验通过,则从Token中解析出用户信息,放入当前请求的上下文中(如
SecurityContextHolder或ThreadLocal),供后续业务逻辑使用。
关键实现细节:
- 密码存储必须加密!绝对不要明文存密码。使用Spring Security的
BCryptPasswordEncoder进行哈希加密。 - Token的刷新机制:可以在Token快过期时,引导用户重新登录,或者设计一个refresh token机制。对于毕设,前者更简单可靠。
- 权限控制:可以在拦截器中,根据Token解析出的用户角色(
ROLE_READER,ROLE_ADMIN),来校验其是否有权访问某个API(如删除图书的API只允许ROLE_ADMIN访问)。更细粒度可以使用Spring Security的@PreAuthorize注解。
4. 微信小程序前端开发关键点
4.1 项目初始化与基础配置
使用微信开发者工具创建小程序项目。项目结构清晰是关键:
miniprogram/ ├── pages/ # 页面文件 │ ├── index/ # 首页(图书检索/展示) │ ├── my/ # 个人中心(我的借阅) │ ├── login/ # 登录页 │ └── ... ├── components/ # 自定义组件(如图书卡片、搜索框) ├── utils/ # 工具类(如request封装、常量) ├── app.js # 小程序入口 ├── app.json # 全局配置(页面路径、窗口样式) ├── app.wxss # 全局样式 └── project.config.json在app.js的onLaunch生命周期中,可以尝试从本地存储(wx.getStorageSync)中读取之前登录成功的Token,并校验其有效性(例如调用一个简单的验证接口),实现静默登录。
4.2 网络请求封装与状态管理
小程序发起网络请求使用wx.request,但直接使用很原始。务必进行封装,统一处理URL前缀、请求头、加载状态、错误提示等。
一个简单的request.js封装示例:
// utils/request.js const BASE_URL = 'https://your-api-domain.com/api'; // 后端API地址 const request = (options) => { // 显示加载中 wx.showLoading({ title: '加载中...', mask: true }); // 从本地获取token const token = wx.getStorageSync('token'); return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + options.url, method: options.method || 'GET', data: options.data || {}, header: { 'Content-Type': 'application/json', 'Authorization': token ? `Bearer ${token}` : '' // 携带Token }, success: (res) => { wx.hideLoading(); if (res.statusCode === 200) { // 假设后端统一返回格式 { code: 200, data: {}, msg: 'success' } if (res.data.code === 200) { resolve(res.data.data); } else if (res.data.code === 401) { // Token过期或无效,跳转到登录页 wx.removeStorageSync('token'); wx.redirectTo({ url: '/pages/login/login' }); wx.showToast({ title: '登录已过期', icon: 'none' }); reject(res.data); } else { // 其他业务错误 wx.showToast({ title: res.data.msg || '请求失败', icon: 'none' }); reject(res.data); } } else { // HTTP状态码错误 wx.showToast({ title: `网络错误: ${res.statusCode}`, icon: 'none' }); reject(res); } }, fail: (err) => { wx.hideLoading(); wx.showToast({ title: '网络连接失败', icon: 'none' }); reject(err); } }); }); }; // 导出get, post等方法 module.exports = { get: (url, data) => request({ url, method: 'GET', data }), post: (url, data) => request({ url, method: 'POST', data }), // ... put, delete 等方法 };4.3 核心页面交互逻辑实现
首页图书列表与搜索:
- 页面
onLoad时,调用封装的request.get('/book/list')获取第一页数据。 - 使用小程序原生的
scroll-view组件实现上拉加载更多。监听bindscrolltolower事件,加载下一页数据,并与旧数据合并。 - 搜索框使用
input组件绑定bindinput事件,但为了性能,不要每次输入都请求。可以设置一个防抖(debounce)函数,在用户停止输入300毫秒后再发起搜索请求。
个人借阅记录页:
- 进入页面时,根据当前登录用户的ID,请求
/borrow/my-record接口。 - 数据返回后,在页面上按状态(“借阅中”、“已归还”、“超期中”)进行分组或标签筛选展示,用户体验更好。
- 对于“借阅中”的图书,提供一个“续借”按钮。点击后调用续借接口,后端需要校验该书是否可续借(如未超期、续借次数未达上限),然后更新
borrow_record表中的应还时间。
扫码借还书功能(管理员端): 这是体现项目完整性的亮点功能。利用小程序的wx.scanCodeAPI。
- 管理员点击“扫码借书”按钮,调用
wx.scanCode,扫描图书ISBN码或我们自定义的图书二维码(内容可以是图书ID)。 - 扫码成功后,获取到ISBN或图书ID,再调用后端
/book/detail-by-isbn接口获取图书详情,并自动填充表单。 - 管理员再输入或扫描读者证号(学号),调用
/borrow/lend接口,完成借书操作。后端需要在一个事务内完成:检查图书状态、检查读者借阅资格、创建借阅记录、更新图书状态、更新读者已借数量。
5. 数据库详细设计与SQL优化
5.1 核心表结构定义
以下是几个核心表的简化版DDL,包含了关键字段和注释:
-- 图书表 CREATE TABLE `book` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `isbn` varchar(20) NOT NULL COMMENT '国际标准书号,唯一标识', `book_name` varchar(100) NOT NULL COMMENT '书名', `author` varchar(50) NOT NULL COMMENT '作者', `publisher` varchar(50) DEFAULT NULL COMMENT '出版社', `publish_date` date DEFAULT NULL COMMENT '出版日期', `price` decimal(10,2) DEFAULT NULL COMMENT '价格', `category_id` int(11) DEFAULT NULL COMMENT '分类ID,关联分类表', `bookshelf_no` varchar(20) DEFAULT NULL COMMENT '书架号', `total_copies` int(11) NOT NULL DEFAULT '1' COMMENT '总册数', `available_copies` int(11) NOT NULL DEFAULT '1' COMMENT '可借册数', `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '状态:0-可借,1-已借出,2-整理中,3-遗失', `cover_image` varchar(255) DEFAULT NULL COMMENT '封面图片URL', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入库时间', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_isbn` (`isbn`), KEY `idx_book_name` (`book_name`), KEY `idx_author` (`author`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='图书信息表'; -- 读者表 CREATE TABLE `reader` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `student_id` varchar(20) NOT NULL COMMENT '学号/工号,唯一', `name` varchar(20) NOT NULL COMMENT '姓名', `password` varchar(100) NOT NULL COMMENT '加密后的密码', `college` varchar(50) DEFAULT NULL COMMENT '学院', `major` varchar(50) DEFAULT NULL COMMENT '专业', `max_borrow` int(11) NOT NULL DEFAULT '5' COMMENT '最大可借数量', `current_borrowed` int(11) NOT NULL DEFAULT '0' COMMENT '当前已借数量', `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '状态:0-正常,1-挂失,2-冻结', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_student_id` (`student_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='读者信息表'; -- 借阅记录表(核心事务表) CREATE TABLE `borrow_record` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '记录ID', `book_id` bigint(20) NOT NULL COMMENT '图书ID', `reader_id` bigint(20) NOT NULL COMMENT '读者ID', `borrow_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '借出时间', `due_time` datetime NOT NULL COMMENT '应还时间', `return_time` datetime DEFAULT NULL COMMENT '实际归还时间', `renew_count` int(11) NOT NULL DEFAULT '0' COMMENT '续借次数', `overdue_days` int(11) NOT NULL DEFAULT '0' COMMENT '超期天数', `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '状态:0-借出,1-已还,2-超期', `operator_id` bigint(20) DEFAULT NULL COMMENT '操作员ID(管理员)', PRIMARY KEY (`id`), KEY `idx_book_id` (`book_id`), KEY `idx_reader_id` (`reader_id`), KEY `idx_status_due_time` (`status`,`due_time`), -- 复合索引,用于查询超期记录 CONSTRAINT `fk_borrow_book` FOREIGN KEY (`book_id`) REFERENCES `book` (`id`), CONSTRAINT `fk_borrow_reader` FOREIGN KEY (`reader_id`) REFERENCES `reader` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='图书借阅记录表';5.2 关键查询SQL与性能考量
1. 查询某读者当前借阅的图书及详情: 这是一个典型的多表关联查询。使用INNER JOIN效率较高。
SELECT b.book_name, b.author, b.cover_image, br.borrow_time, br.due_time, br.renew_count FROM borrow_record br INNER JOIN book b ON br.book_id = b.id WHERE br.reader_id = #{readerId} AND br.status = 0 -- 状态为“借出” ORDER BY br.borrow_time DESC;注意:确保borrow_record表的reader_id和status字段有索引。
2. 统计各类别图书的借阅排行榜(热门图书): 这个查询涉及分组和排序,数据量大时可能较慢。
SELECT c.category_name, b.book_name, COUNT(br.id) as borrow_count FROM borrow_record br INNER JOIN book b ON br.book_id = b.id INNER JOIN category c ON b.category_id = c.id WHERE br.borrow_time >= DATE_SUB(NOW(), INTERVAL 1 YEAR) -- 统计近一年 GROUP BY b.id, c.category_name ORDER BY borrow_count DESC LIMIT 10;优化建议:对于这种复杂的统计查询,如果实时性要求不高,可以考虑使用定时任务(如Spring的@Scheduled)在每天凌晨计算一次,将结果存入一张book_popularity统计表,前端直接查这张表,性能会好很多。
3. 查找所有超期未还的记录(用于定时任务发送提醒):
SELECT r.name, r.student_id, b.book_name, b.isbn, br.due_time, DATEDIFF(NOW(), br.due_time) as overdue_days FROM borrow_record br INNER JOIN reader r ON br.reader_id = r.id INNER JOIN book b ON br.book_id = b.id WHERE br.status = 0 -- 借出状态 AND br.due_time < NOW() -- 应还时间已过 AND br.return_time IS NULL; -- 尚未归还这个查询需要频繁执行,在borrow_record表上建立(status, due_time)的复合索引能极大提升效率。
6. 部署上线与运维注意事项
6.1 后端SpringBoot应用部署
对于毕设演示,最简单的部署方式是打成可执行的JAR包。
- 在
pom.xml中确保打包方式为jar,并引入了spring-boot-maven-plugin。 - 在项目根目录下执行Maven命令:
mvn clean package -DskipTests。打包成功后,会在target目录下生成一个your-project-0.0.1-SNAPSHOT.jar文件。 - 将JAR包上传到你的云服务器(如学生优惠的腾讯云/阿里云轻量应用服务器)。
- 在服务器上,使用
nohup命令在后台运行:nohup java -jar your-project-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod > app.log 2>&1 &。这里的--spring.profiles.active=prod会激活application-prod.yml配置文件,用于配置生产环境的数据库连接、日志级别等。 - 使用
ps -ef | grep java查看进程,tail -f app.log查看日志,确认应用启动成功。
生产环境关键配置(application-prod.yml):
server: port: 8080 servlet: context-path: /api # 统一API前缀 spring: datasource: url: jdbc:mysql://your-mysql-ip:3306/library_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: prod_user password: strong_password # 务必使用强密码,不要用默认的root/123456 hikari: maximum-pool-size: 10 # 连接池大小,根据服务器配置调整 # Redis配置(如果用了缓存或Session共享) # redis: # host: localhost # port: 6379 # JWT配置 jwt: secret: your_super_strong_jwt_secret_key_here # 务必更换,且足够长足够复杂 expiration: 7200 # token过期时间,单位秒 # 日志配置 logging: level: com.yourdomain.library: DEBUG # 自己项目的包设为DEBUG org.springframework: WARN # 框架日志设为WARN,避免日志过多 file: name: /var/log/library/app.log # 指定日志文件路径6.2 微信小程序发布与配置
- 域名与备案:微信小程序要求后端API必须使用HTTPS协议,且域名必须经过ICP备案。你需要为你的云服务器域名申请SSL证书(很多云服务商提供免费证书),并在Nginx或Apache中配置好HTTPS代理,将请求转发到后端SpringBoot应用的8080端口。
- 小程序后台配置:在微信公众平台的小程序管理后台,“开发”->“开发设置”中,将你的HTTPS域名添加到“服务器域名”的
request合法域名列表中。 - 代码上传与审核:在微信开发者工具中点击“上传”,填写版本号。然后在管理后台提交审核。注意:审核可能需要几天时间,务必提前规划。为了演示,你也可以直接使用开发者工具的“预览”或“真机调试”功能,扫描二维码在手机上体验测试版。
- 内容安全:如果你的小程序有用户评论或上传功能,务必接入微信提供的内容安全API进行检测,避免违规内容。
6.3 常见问题与排查实录
问题1:小程序真机预览时,网络请求失败(报错request:fail url not in domain list)
- 原因:没有在小程序后台配置服务器域名,或者配置的域名与请求的域名不完全一致(包括协议、端口)。
- 解决:检查并确保
utils/request.js中的BASE_URL与小程序后台配置的request合法域名完全一致。开发阶段可以在“详情”->“本地设置”中勾选“不校验合法域名...”,但上线前必须配置正确。
问题2:后端服务本地运行正常,部署到服务器后连接数据库失败
- 原因:服务器防火墙未开放3306端口,或MySQL未允许远程连接。
- 解决:
- 检查云服务器安全组规则,放行3306端口(生产环境建议限制访问IP)。
- 登录服务器MySQL,执行:
GRANT ALL PRIVILEGES ON library_db.* TO 'prod_user'@'%' IDENTIFIED BY 'strong_password' WITH GRANT OPTION;然后FLUSH PRIVILEGES;。注意@'%'表示允许所有主机远程连接,生产环境应改为具体的服务器IP。 - 检查MySQL配置文件
my.cnf,确保bind-address = 0.0.0.0(允许所有IP绑定)或注释掉这一行。
问题3:扫码借书时,提示“该图书已借出”,但数据库查询状态是可借
- 原因:典型的并发问题。两个管理员几乎同时扫描了同一本仅剩一本的图书。
- 解决:在借书的业务逻辑中,必须使用数据库事务和乐观锁/悲观锁。例如,在Service方法上添加
@Transactional注解。在更新图书available_copies时,使用条件更新:UPDATE book SET available_copies = available_copies - 1, status = ... WHERE id = ? AND available_copies > 0。通过判断更新影响的行数是否为0,可以知道是否更新成功,从而避免超借。
问题4:小程序页面列表数据渲染很慢,尤其是图片多的时候
- 原因:网络请求慢或图片过大。
- 解决:
- 后端分页:确保列表接口一定实现了分页,不要一次性返回所有数据。
- 图片优化:图书封面图使用CDN加速,并对图片进行压缩和裁剪,生成适合小程序显示的缩略图。
- 小程序端懒加载:使用小程序
image组件的lazy-load属性。对于长列表,可以使用wx.createIntersectionObserverAPI监听图片是否进入视口,再加载图片。 - 数据缓存:对于不常变的数据(如图书分类),可以在小程序端使用
wx.setStorage进行本地缓存,设置合理的过期时间。
问题5:JWT Token过期后,用户操作突然中断,体验差
- 原因:Token过期机制是固定的,用户无感知。
- 解决:实现Token自动刷新。有两种思路:1)在拦截器中,当发现Token过期(返回401)时,尝试使用一个专门的refresh token(有效期更长,且仅用于刷新)去请求新token,无感刷新。2)更简单的方案:在Token过期前几分钟(如过期前15分钟),在用户下一次请求的响应头中告知前端“Token即将过期”,前端弹窗提示用户“登录即将过期,是否续期?”,用户确认后调用刷新接口。对于毕设,第二种方案实现起来更清晰可控。
本文还有配套的精品资源,点击获取