尧图精选

SpringBoot+微信小程序图书管理系统实战:登录、借阅与部署全解析

🕒 发布时间:2026/9/27 4:16:37 📁 来源:尧图网络
简介这是一份基于Java、SpringBoot、MySQL及微信小程序开发的图书管理系统毕业设计项目面向高校计算机相关专业学生适用于毕业设计、课程设计或期末大作业。资源包内共包含1231个文件覆盖Java后端源码、Vue前端页面、小程序WXML/WXSS、JavaScript逻辑文件、png/jpg界面图片、数据库SQL脚本、论文docx文档及运行bat脚本等压缩包整体大小约20.76MB目录层次清晰方便按模块查阅。系统实现用户管理、图书管理、借阅管理、搜索查询等核心功能后端基于SpringBoot搭建接口配合MySQL5.7以上数据库存储数据并利用Maven管理依赖、Navicat操作数据库前端通过微信开发者工具编译运行整体架构完整且经过严格调试导入环境后可直接复用。已有37人学习下载适合需要可运行项目并理解前后端分离开发流程的学习者使用。1. 基于JAVASpringBootMySQL微信小程序的图书管理系统这套毕设源码包值得吃透的三层东西把「基于JAVASpringBootMySQL微信小程序的图书管理系统」这个标题拆开看它其实是毕设项目里最典型的一套前后端分离架构微信小程序做读者端SpringBoot 提供后端接口MySQL 存业务数据。标题里的「源码数据库论文」三件套对应了你要交付的全部成果。这个系统能解决的实际问题很明确读者在小程序里搜书、借书、续借、查记录管理员在后台维护图书和维护借还状态。适合毕设、Java 课程设计或者想快速搭一个校园/社区图书流转 demo 的从业者。这类项目真正让人翻车的从来不是 CRUD而是借阅状态机的边界条件和微信登录态的处理这两块我会在后面重点展开。2. SpringBoot 后端把小程序请求接进 MySQL 的数据管道2.1 后端工程结构先弄懂包路径再动手改代码拿到一个 SpringBoot 源码包第一步不是急着点运行而是先看包结构。常见做法是标准的四层分包controller接收小程序请求service写业务逻辑mapper负责和 MySQL 交互entity放图书、用户、借阅记录这些实体类。此外还会有config放拦截器和跨域配置common放统一返回结果Result类和异常处理utils放 JWT 工具类、日期工具类。工程基于 Maven 管理依赖核心依赖很少但每一个都有明确用途parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies !-- Web 工程必备内置 Tomcat打 jar 后直接 java -jar 启动 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MyBatis-Plus比手写 MyBatis 省掉大量 XML 映射 -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.2/version /dependency !-- MySQL 驱动SpringBoot 2.x 用这个坐标3.x 改成了 com.mysql:mysql-connector-j -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency !-- Lombok用 Data 省略 getter/setter代码量少一半 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies这套依赖组合是 SpringBoot 项目里最主流的搭配。spring-boot-starter-parent锁定了 SpringBoot 2.7.18 的版本2.7 这个版本号在 2024 年之前是大多数课程设计和毕设项目的首选——它稳定、资料多、遇到 bug 网上基本都有答案。MyBatis-Plus 3.5.3.2 提供了分页插件、条件构造器LambdaQueryWrapper、雪花 ID 生成这些能力写一个图书管理系统绰绰有余。2.2 最小可运行配置application.yml 里的参数一个都不能删配置文件是整个后端能不能连上 MySQL 的关键。很多新手拿着源码在自己电脑上启动报错九成问题出在数据库连接参数上。下面这份配置是从毕设包里最常见的写法里整理出来的server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/library?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: assign_idserverTimezoneAsia/Shanghai这一项是 MySQL 8.0 必须加的不加会报时间差八小时或者直接连接失败。map-underscore-to-camel-case让数据库里的create_time自动映射到 Java 实体里的createTime省掉大量手写映射。log-impl配置成StdOutImpl后MyBatis-Plus 会在控制台打印每条 SQL 和参数debug 阶段不要关等系统稳定了再换成Slf4jImpl。password 要改成你自己 MySQL 的密码。这里注意源码包里写的密码大概率不是你的本地密码跑不起来很正常不是代码错了是配置没改。2.3 第一个接口图书检索背后的分页查询图书检索是这套系统最核心的接口。小程序首页搜书、分类页列书、管理员后管维护图书全都走这个模式。SpringBoot 里最简洁的写法是用 MyBatis-Plus 的分页能力RestController RequestMapping(/api/book) public class BookController { Autowired private BookService bookService; GetMapping(/search) public ResultIPageBook search( RequestParam(required false) String keyword, RequestParam(defaultValue 1) long page, RequestParam(defaultValue 10) long size) { LambdaQueryWrapperBook wrapper Wrappers.lambdaQuery(); if (keyword ! null !keyword.trim().isEmpty()) { wrapper.like(Book::getTitle, keyword) .or().like(Book::getIsbn, keyword) .or().like(Book::getAuthor, keyword); } return Result.ok(bookService.page(new Page(page, size), wrapper)); } }这段代码的逻辑是keyword为空就查全部分页数据不为空就在书名、ISBN、作者三个字段上做模糊匹配。Page对象接收页码和每页条数IPage里除了记录列表还带了total总数、pages总页数这些分页元数据小程序端拿data.records渲染列表拿data.total控制是否还有下一页。参数上注意page和size都给了默认值前端不传也不会报错。Result.ok()是统一返回结构正常返回码是 0业务异常返回非 0这个约定要和前端request.js里的拦截逻辑对上。2.4 微信登录态code2session 与 token 签发微信小程序的登录和传统账号密码登录完全不同。小程序端用wx.login()拿到一个临时code这个 code 有效期只有几分钟后端拿它向微信服务端换openid。openid 是用户在该小程序下的唯一标识但注意这个换取的请求必须由后端发起绝不能在小程序前端直接调因为微信接口需要 appSecret一旦放进小程序代码里就相当于泄露了。Service public class WxAuthService { Value(${wx.appid}) private String appId; Value(${wx.secret}) private String appSecret; private final RestTemplate restTemplate new RestTemplate(); public String wxLogin(String code) { // 用 code 向微信服务端换 openid这一步必须发生在后端 String url https://api.weixin.qq.com/sns/jscode2session ?appid appId secret appSecret js_code code grant_typeauthorization_code; Map?, ? result restTemplate.getForObject(url, Map.class); String openid String.valueOf(result.get(openid)); // session_key 不要返回前端它只用于解密手机号等敏感数据 User user userMapper.selectOne( Wrappers.UserlambdaQuery().eq(User::getOpenid, openid)); if (user null) { // 新用户自动注册不需要在小程序里填账号密码 user new User(); user.setOpenid(openid); user.setNickname(微信用户); user.setRole(1); // 1 读者2 管理员 userMapper.insert(user); } // 签发 JWT后续请求带这个 token 即可识别身份 return JwtUtil.createToken(user.getId(), user.getRole()); } }两个关键点第一appid和secret要配置在application.yml里不要硬编码在代码中第二session_key只用于微信手机号解密这类高级能力图书管理系统用不到千万不要拼在 token 里返回给前端这是安全底线。这套「code 换 openid、openid 换 token」的链路是微信小程序后端开发面试必问的基本功。3. MySQL 数据库设计让借阅记录经得起对账的六张表与状态机3.1 六张核心表分类、图书、用户、借阅、预约、操作日志图书管理系统的数据库设计不建议做成一张大表把所有字段塞进去。常见做法是拆成六张表分类表category、图书表book、用户表user、借阅记录表borrow_record、预约表reserve_record、操作日志表operation_log。每张表的职责单一后面写统计 SQL 才不会痛苦。建库建表脚本是毕设包里「数据库」这部分的核心文件结构如下CREATE DATABASE IF NOT EXISTS library DEFAULT CHARACTER SET utf8mb4; USE library; CREATE TABLE category ( id BIGINT NOT NULL COMMENT 分类ID, name VARCHAR(50) NOT NULL COMMENT 分类名, sort_order INT DEFAULT 0 COMMENT 排序权重, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT图书分类表; CREATE TABLE book ( id BIGINT NOT NULL COMMENT 雪花主键, isbn VARCHAR(20) NOT NULL COMMENT ISBN, title VARCHAR(200) NOT NULL COMMENT 书名, author VARCHAR(100) DEFAULT NULL COMMENT 作者, category_id BIGINT DEFAULT NULL COMMENT 分类ID, publisher VARCHAR(200) DEFAULT NULL COMMENT 出版社, publish_date DATE DEFAULT NULL COMMENT 出版日期, total_count INT DEFAULT 1 COMMENT 馆藏总册数, status TINYINT DEFAULT 1 COMMENT 1可借 2借出 3下架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_isbn (isbn), KEY idx_title (title) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT图书表; CREATE TABLE user ( id BIGINT NOT NULL COMMENT 用户ID, openid VARCHAR(64) NOT NULL COMMENT 微信openid, nickname VARCHAR(50) DEFAULT NULL, phone VARCHAR(20) DEFAULT NULL, role TINYINT DEFAULT 1 COMMENT 1读者 2管理员, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT小程序用户表; CREATE TABLE borrow_record ( id BIGINT NOT NULL COMMENT 记录ID, user_id BIGINT NOT NULL COMMENT 借阅人, book_id BIGINT NOT NULL COMMENT 图书ID, borrow_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 借出时间, due_time DATETIME DEFAULT NULL COMMENT 应还时间, return_time DATETIME DEFAULT NULL COMMENT 实际归还时间, status TINYINT DEFAULT 1 COMMENT 1借出中 2已归还 3逾期归还, PRIMARY KEY (id), KEY idx_user_status (user_id, status), KEY idx_book_status (book_id, status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT借阅记录表; CREATE TABLE reserve_record ( id BIGINT NOT NULL, user_id BIGINT NOT NULL, book_id BIGINT NOT NULL, reserve_time DATETIME DEFAULT CURRENT_TIMESTAMP, status TINYINT DEFAULT 1 COMMENT 1预约中 2已取消 3已借出, PRIMARY KEY (id), KEY idx_book_user (book_id, user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT预约记录表; CREATE TABLE operation_log ( id BIGINT NOT NULL, operator_id BIGINT NOT NULL COMMENT 操作人, action VARCHAR(50) NOT NULL COMMENT 借书/还书/续借/上下架, target_id BIGINT DEFAULT NULL COMMENT 目标图书或记录ID, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT操作日志表;几个设计取舍值得说明。book.status是当前状态快照borrow_record.status是单次借阅的状态二者配合能回答两个不同的问题「这本书现在能不能借」看book.status「某个用户有没有逾期记录」看borrow_record。isbn设置唯一索引很关键——同一本书的不同副本共用 ISBN用total_count记录馆藏数量而不是一条记录只对应一本书。3.2 借阅状态机从「可借」到「已还」怎么流转才不出错图书管理最难的不是建表而是借还的并发控制。想象一个场景这本书只剩最后一本可借两个用户同时点了「借阅」如果代码先查book.status再插入借阅记录两个人都会查到「可借」最后超借。解决这个问题有一个非常经典的做法——条件更新Transactional(rollbackFor Exception.class) public boolean borrowBook(Long bookId, Long userId) { // 关键更新时带 status1 条件影响行数为 0 说明书已被借走 int rows bookMapper.update(null, new LambdaUpdateWrapperBook() .eq(Book::getId, bookId) .eq(Book::getStatus, 1) .set(Book::getStatus, 2)); if (rows 0) { return false; // 没抢到不插入借阅记录 } // 占位成功后才登记借阅记录 BorrowRecord record new BorrowRecord(); record.setUserId(userId); record.setBookId(bookId); record.setDueTime(LocalDateTime.now().plusDays(30)); borrowRecordMapper.insert(record); return true; }注意Transactional不能省book.status的更新和borrow_record的插入是两个写操作第二个失败时第一个必须回滚否则会出现书标记为借出但没借阅记录的脏数据。条件更新SET status2 WHERE id? AND status1是数据库层面的原子操作InnoDB 的行锁会保证同一时间只有一个事务能改这一行这就是并发借书的正确解法。归还流程相对简单把borrow_record.status置为已归还写入return_time再把book.status置回可借。续借不要做成新借一条而是在这条记录上把due_time往后推 30 天同时校验当前没有逾期否则允许无限续借会破坏规则。3.3 数据库脚本在毕设包里的落地不是打开就万事大吉毕设包里「数据库」部分的library.sql文件导入时有一个常见的执行顺序问题。建议先用命令行或 Navicat 建库再执行表结构的导入。命令行方式在任何装有 MySQL 的机器上都能跑mysql -uroot -p library.sql执行前检查脚本最前面有没有CREATE DATABASE IF NOT EXISTS和USE library如果没有直接执行会在系统默认库里建表甚至报「No database selected」。MySQL 8.0 环境下执行后可以用SHOW TABLES;验证六张表是否都在。另一个高频问题MySQL 8.0 和 5.7 对时间类型的默认行为有差异。8.0 的DEFAULT CURRENT_TIMESTAMP是合法的但如果你用的是 5.6 老版本部分表结构会出现语法兼容问题。建议整个项目统一用 MySQL 8.0 系列标题里的 MySQL 也指代当前主流版本别在 5.7 上纠结兼容性白白浪费时间。4. 微信小程序前端从登录态到借还书按钮的页面串联4.1 原生小程序还是 uni-app这个包通常用原生写法拿到源码包后小程序端大概率是原生微信小程序工程而不是 uni-app。这两种写法都能跑但原生工程的结构更贴近标题里的「微信小程序」这个关键词。原生小程序用 WXML 写页面结构WXSS 写样式JS 写逻辑和 uni-app 那种 Vue 单文件组件的写法差别很大改代码前先打开app.json确认是哪种不然会用错语法。原生工程里常见的目录是pages页面、utils工具、components组件、静态资源目录。app.json登记所有页面路径和窗口样式app.js里通常放全局的登录初始化逻辑。改页面入口时记得同步改app.json只新建页面文件不注册路径真机预览会一直白屏。4.2 request.js 封装统一带 token、统一拦错误小程序端所有后端请求都应该走同一个封装否则每个页面各自写wx.request拦截器逻辑会散落一地。常见的做法是在utils/request.js里封装一层 Promiseconst BASE_URL http://localhost:8080/api; function request(url, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL url, method: method, data: data, header: { Content-Type: application/json, Authorization: wx.getStorageSync(token) || }, success(res) { // 后端统一返回 { code, data, msg }code 为 0 表示正常 if (res.data.code 0) { resolve(res.data.data); } else if (res.data.code 401) { wx.removeStorageSync(token); wx.redirectTo({ url: /pages/login/login }); } else { wx.showToast({ title: res.data.msg || 请求失败, icon: none }); reject(res.data); } }, fail(err) { wx.showToast({ title: 网络异常, icon: none }); reject(err); } }); }); } module.exports { request, BASE_URL };这里的Authorization头是前后端约定的 JWT 携带方式后端拦截器从请求头里取 token 并解析出用户 ID 和角色。code 401对应 token 过期或无效前端统一清缓存跳登录页比每个页面自己判断优雅得多。BASE_URL在本地联调时是局域网 IP 或 localhost上线前要改成 HTTPS 合法域名。4.3 首页检索与顶部导航栏高度两个别出问题的细节搜索页面通常用输入框加防抖实现「边输入边搜」。防抖的时间一般设 300ms——用户停止输入 300ms 后才真正发起请求避免每个字符都打一次后端let timer null; const onSearchInput function(e) { const keyword e.detail.value; if (timer) clearTimeout(timer); timer setTimeout(() { request(/book/search?keyword encodeURIComponent(keyword)) .then(data { this.setData({ books: data.records }); }); }, 300); };encodeURIComponent一定要加中文关键词不编码会产生非法 URL。setData是小程序特有的数据绑定方式和 Vue 的this.data xxx不一样忘记调用setData页面不会刷新。另外一个小程序里常被新手忽略的点自定义导航栏时顶部高度不要写死。不同手机的胶囊按钮位置不同正确做法是用wx.getMenuButtonBoundingClientRect()拿到胶囊的位置动态计算。写死 64px 在 iPhone 上正常在部分安卓机上会被状态栏压缩变形。4.4 借书、续借、还书三个按钮对应三个后端操作图书详情页根据book.status决定显示「借阅」还是「已借出」。点击借阅按钮时把图书 ID 传进 POST 请求即可const borrow function(e) { const bookId e.currentTarget.dataset.id; request(/borrow/add, POST, { bookId }) .then(() { wx.showToast({ title: 借阅成功 }); this.loadDetail(bookId); // 刷新详情状态从可借变成已借出 }) .catch(() { wx.showToast({ title: 已被借走, icon: none }); }); };注意e.currentTarget.dataset.id和e.target.dataset.id的区别在列表页用currentTarget才能拿到绑定事件的那个元素的数据。借阅成功后一定要重新拉详情否则页面还停留在「可借」状态用户连点两下就产生两条借阅请求。还书和续借是同构操作换接口路径即可但续借前后端会校验是否有逾期前端不用重复判断。4.5 管理员端权限控制不能只靠前端藏按钮毕设包里通常还会有一个管理员页面入口用于上架下架图书、处理还书、查看所有借阅记录。但权限判断必须以后端拦截器为准——前端把按钮藏起来只是用户体验优化不是安全边界。后端在拦截器里解析出用户角色管理员接口校验role 2不满足直接返回 403这样才能挡住直接拿浏览器工具调接口的路径。常见的做法是写一个AdminInterceptor继承HandlerInterceptor注册到 SpringMVC 配置里只拦截/api/admin/**前缀的路由。5. 部署联调常见问题排查五个必踩的坑记录5.1 真机预览请求失败域名白名单和本地联调现象开发者工具里接口正常返回一放到真机预览就报request:fail。原因微信小程序对wx.request的 URL 有严格限制正式版要求域名必须是已经配置到微信公众平台的 HTTPS 合法域名且不能带端口。本地开发时后端跑在http://localhost:8080开发者工具默认勾选了「不校验合法域名」所以能通真机环境没有这个豁免。解决开发阶段最容易的方案是保持开发者工具的「不校验合法域名」勾选同时把BASE_URL改成电脑的局域网 IP比如http://192.168.1.100:8080/api手机和电脑连同一个 Wi-Fi 就能联调。注意 Windows 防火墙可能拦截 8080 端口需要在入站规则里放行。如果是要上线演示就得把后端部署到有公网 IP 的服务器把域名备案并配置到微信公众平台的 request 合法域名里。补一句微信公众平台配置域名时只填域名本身不用加http://但 HTTPS 证书是必须的否则真机仍然不通。5.2 图书 ID 传到小程序后末尾几位变 0现象后端返回的图书 ID 在小程序端显示成1502000001234560000这种末尾带一串 0 的数字用这个 ID 查详情一直查不到。原因MyBatis-Plus 默认的雪花 ID 是Long类型长度 19 位。JavaScript 的 Number 精度只有 53 位超过 9007199254740991 的数会丢失精度后面几位被抹成 0。这是 Java 后端 小程序前端联调里最常见的数据类型坑之一。解决在后端做 Jackson 序列化配置把Long统一转成String返回给前端Configuration public class JacksonConfig { Bean public Jackson2ObjectMapperBuilderCustomizer longToStringCustomizer() { return builder - { builder.serializerByType(Long.class, ToStringSerializer.instance); builder.serializerByType(Long.TYPE, ToStringSerializer.instance); }; } }加了这段配置后前端拿到的是字符串形式的 ID不会丢精度。数据库里的主键和业务表borrow_record.book_id也要统一处理凡是涉及雪花 ID 的字段都转 String到处漏配就会出「详情页查不到」的灵异问题。5.3 两个人同时借一本书两个人都借成功了现象压测或两个账号同时操作同一本书后端返回两个「借阅成功」但书只有一本。原因典型的 check-then-act 竞态条件。代码先SELECT status FROM book WHERE id?查出来「可借」然后插入借阅记录。两个请求同时完成查询都能通过判断各自插入记录超借。解决把检查与更新合并成一个原子操作也就是第 3 章那个条件更新UPDATE book SET status2 WHERE id? AND status1数据库行锁保证只有一个事务更新成功影响行数为 0 的前端直接弹「已被借走」。这个写法比 Java 层加synchronized可靠因为应用多实例部署时synchronized只锁单机进程数据库行锁是天然全局互斥的。5.4 MySQL 8.0 启动报连接错误或时间差 8 小时现象SpringBoot 启动时控制台报Communications link failure或者插入的数据时间比本地时间晚了 8 小时。原因MySQL 8.0 的驱动类名从 5.x 时代的com.mysql.jdbc.Driver改成了com.mysql.cj.jdbc.Driver同时连接 URL 需要显式指定时区。URL 不写serverTimezone时驱动默认用 UTC 时间而中国在东八区自然差 8 小时。解决驱动类名换成com.mysql.cj.jdbc.DriverURL 末尾加上serverTimezoneAsia/Shanghai。如果 Linux 服务器上的 MySQL 系统时区本身是 UTC建议在 my.cnf 里把default-time-zone设为08:00这样连数据库工具也不会出现时间错乱。5.5 导入数据库脚本时反复报「table already exists」现象把library.sql用 Navicat 执行第一次成功第二次执行报Table book already exists。原因脚本开头虽然有CREATE DATABASE IF NOT EXISTS但表结构语句没写DROP TABLE IF EXISTS。重复执行时数据库已存在建表语句冲突。解决安全做法是先手动删除旧库再导入mysql -uroot -p -e DROP DATABASE IF EXISTS library; mysql -uroot -p library.sql别小看这一步毕设答辩现场最容易出的状况就是评委让你重新演示一遍你重新导库里还残留着上一轮数据界面上出现一些自己都没见过的借阅记录场面非常尴尬。6. 验收旧项目包的四个快速检查项拿到源码别急着跑拿到这套源码包我建议按下面的顺序做一遍验收能省下后面数不清的排错时间。第一先看数据库脚本能不能干净导入。用命令行执行mysql -uroot -p library.sql然后SHOW TABLES确认六张表齐全重点检查book表里带不带初始数据。有些精简版脚本把book表留成空表小程序首页打开一片空白,你还以为是接口问题实际上是没给你造测试数据。第二再启动后端工程。IDE 里运行启动类看到Tomcat started on port 8080之后用浏览器直接访问http://localhost:8080/api/book/search能返回 JSON 就说明数据库连接没问题。第三用小程序的开发者工具打开前端工程勾选不校验域名把BASE_URL指到本机先跑通登录——控制台出现code2session相关日志说明链路正常。第四跑一遍完整闭环搜索到一本书点击借阅去「我的借阅」里看到记录再点还书回到详情页确认状态变回可借。一个实用的技巧毕设论文里的系统截图别用模拟器截图用真机预览截图。模拟器里字体和间距跟真机有明显差异截图放论文里显得很假答辩老师一眼就能看出来。论文里的统计图也不需要后端生成直接用 ECharts 画好导出图片贴进去即可别折腾 Java 后端图表库。这类项目做多了以后我的习惯是先看「还书」这个接口的逻辑再回看「借书」。还书接口如果没做book.status的恢复更新那这个系统的核心链路就是断的界面做得再漂亮也不能要。源码包拿回来先跑通这条链路再谈扩展。讲解了这个顺序希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →