基于Vue+SpringBoot的图书借阅管理系统设计与实战解析 又是一年毕业设计季每年这时候总有人问我“想做个管理系统不知道该选什么题目又怕技术栈太老被导师嫌弃。”如果你正好在纠结那我直接说结论图书借阅管理系统用 Vue SpringBoot 来做是一个性价比很高的选择。它不像电商、秒杀那种项目需要处理高并发和复杂的分布式事务也不像纯算法项目那样对数学基础有硬性要求但它足够完整——涵盖了登录鉴权、CRUD、借还书业务流程、数据统计、权限控制这些企业级项目里最常见的模块。换句话说你把这个项目吃透前端 Vue 的路由、组件通信、状态管理你会了后端 SpringBoot 的 RESTful 设计、MyBatis-Plus 操作、JWT 鉴权你也通了数据库设计的范式与反范式取舍你也能摸到门道。这套东西放到真实工作中就是一个中小型管理后台的完整缩影。这篇文章我不打算只给你堆一堆代码片段而是把整个系统的设计思路、核心模块怎么拆、借还书流程的状态机怎么画、哪些地方容易踩坑一条线讲清楚。无论你是打算直接用来当毕业设计还是想自己动手写一遍练手这份拆解都能让你少走不少弯路。1. 项目整体拆解从选题到方案落地的完整思路1.1 为什么图书借阅管理系统是毕业设计的常青树我见过太多人选题目时犯的一个毛病——眼高手低。上来就要做高并发秒杀系统、要做基于深度学习的推荐系统结果数据库都没摸熟Redis 集群也没搭过最后死在性能优化和模型调参上。毕业设计的核心目标只有一个在有限时间内完整展示你掌握了从需求分析、系统设计、编码实现到部署上线的全过程。图书借阅系统恰恰是“麻雀虽小五脏俱全”的典型。选这个题目有几个非常现实的好处业务逻辑清晰不依赖外部第三方接口。不需要接支付、不需要调地图 SDK所有数据自己闭环自己造数据自己消费。天然包含多角色权限。管理员管图书、管用户、管借还普通用户查书、借书、续借、还书、看历史记录。这不就是 RBAC基于角色的访问控制最简单的落地场景吗有一个可以深挖的业务主链路借书 → 还书 → 逾期处理。这条链路能做出状态流转能引出事务、并发、时间计算等进阶话题答辩时非常有话讲。前端展示有数据可依。图书封面、分类统计、借阅排行榜这些图表和卡片有真实数据支撑页面不会空视觉效果也好看。1.2 技术选型为什么是 Vue SpringBoot 这件黄金搭档现在的毕业设计技术栈选择上大家基本默认一套组合前端 Vue后端 SpringBoot数据库 MySQLORM 用 MyBatis-Plus。这个组合为什么能成为默认项是有实际原因支撑的。先看 Vue。Vue 在国内开发者中的普及率一直很高生态成熟Element Plus 这类组件库开箱即用能做表格、弹窗、表单校验、分页省掉大量手写 DOM 的时间。你用 Vue 写图书管理页面核心就是在组件和状态之间倒腾数据这个思路本身就贴近现在主流前端框架的设计理念。而且 Vue 的单文件组件SFC把模板、脚本、样式放一起结构清晰导师看起来也舒服。再看 SpringBoot。SpringBoot 的约定大于配置大幅降低了搭建门槛内置 Tomcat打成一个 jar 包就能跑。配合 MyBatis-Plus连 SQL 大部分都不用手写了BaseMapper 里的 selectPage、updateById 直接帮你把 CRUD 搞定。你只需要把精力集中在业务逻辑层Service和控制器层Controller的设计上。这种“框架替你做脏活累活”的思路让你有空间把业务代码写得更规范——比如事务加在哪里、参数校验怎么做、异常怎么统一处理。有人说这套技术栈太“普通”没有亮点。这种说法我不太认同。毕业设计考核的是工程能力不是 novelty新颖性。你把一套标准技术栈用得规范、代码结构干净、业务逻辑严谨比堆一堆你其实没搞懂的新技术更有说服力。如果你实在想加点分可以在角色权限上引入 Spring Security JWT在数据统计上接入 ECharts这些后面我会详细说。2. 后端SpringBoot 核心模块设计与接口实现2.1 数据表设计先把业务边界划清楚很多人上来就写代码结果写到一半发现表结构缺字段、关系理不清再回头改表牵一发动全身。我的习惯是动手写任何一行代码之前先把 MySQL 的表设计出来并且花至少半天时间把它打磨清楚。图书借阅管理系统核心表我建议这样划表名核心字段说明userid, username, password, real_name, phone, role, status用户表role 区分管理员/普通用户status 控制是否禁用bookid, isbn, title, author, publisher, category_id, cover, stock_total, stock_available图书表stock_available 是关键后面借书逻辑要盯着它categoryid, name, description图书分类表简化图书管理维度borrow_recordid, user_id, book_id, borrow_time, due_time, return_time, status, fine_amount借阅记录表整个系统业务核心fine_recordid, borrow_record_id, user_id, amount, paid_status逾期罚款记录可选但有了它业务闭环更完整这里有几个设计上特别想提醒你的点第一图书表为什么要拆出 stock_total 和 stock_available 两个可用数量字段。因为“馆藏总量”和“当前可借数量”是两条信息总量不随借还改变可用数量必须在每次借还时更新。如果你只用一个库存字段还书时加一、借书时减一逻辑也能跑但你想看“这本书一共买了几本”就得靠日志去推查起来很费劲。拆开之后统计和管理都清爽。第二借阅记录表里 status 字段不要用数字 0、1、2 去裸存建议有明确的枚举含义。0 表示借出、1 表示已还、2 表示逾期已还代码里定义常量或枚举类。硬编码数字的坏处是过一个月你自己回来看代码都得重新猜数字含义更别提答辩时导师问了。第三外键的问题。实际开发中我很少在数据库层面加物理外键而是靠 Service 层逻辑保证一致性。理由是物理外键在插入、更新时会有额外的锁和校验开销而且一旦业务要拆分表或做分库外键会变成灾难。你们做毕设数据库层面不用建外键只需要在实体类里保留关联字段比如 borrow_record 里的 user_id、book_id然后用代码控制逻辑。这个做法在答辩时也可以说是“为了保证性能和灵活扩展”。2.2 核心接口登录鉴权与图书 CRUD登录鉴权我推荐直接上 JWTJSON Web Token而不是传统的 Session。原因是前端是 Vue 独立部署的后端接口是纯 API 风格天然就是前后端分离架构Session 在这种结构下需要处理跨域携带 Cookie 的问题麻烦且不好解释。JWT 的思路理解起来也不难用户登录成功后后端签发一个带有用户信息和过期时间的 Token前端每次请求在请求头里带上后端用一个拦截器校验 Token 的合法性和有效性。用 Java 写 JWT 工具类核心就三步// 1. 生成 token登录成功时调用 public String generateToken(Long userId, String username, String role) { return Jwts.builder() .setSubject(username) .claim(userId, userId) .claim(role, role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() 1000 * 60 * 60 * 8)) .signWith(SignatureAlgorithm.HS512, SECRET_KEY) .compact(); } // 2. 解析 token拦截器中调用 public Claims parseToken(String token) { return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody(); } // 3. 校验是否过期 public boolean isTokenExpired(String token) { return parseToken(token).getExpiration().before(new Date()); }SECRET_KEY 不要硬编码在代码里配置到 application.yml 中答辩时你说一句“密钥放配置文件生产环境会用环境变量注入避免源码泄露”这个细节马上显得专业。图书 CRUD 接口设计上建议严格按照 RESTful 风格来GET /api/book/page 分页查询图书支持按书名模糊查询、按分类筛选GET /api/book/{id} 查询图书详情POST /api/book 新增图书管理员权限PUT /api/book 更新图书信息管理员权限DELETE /api/book/{id} 删除图书管理员权限分页这块MyBatis-Plus 有个现成的分页插件配置一下就可以用 IPage 直接拿数据。特别注意分页查询返回的数据不要只返回一个 List把 total 也带上前端分页组件才能算出总页数。我用过一个统一返回体结构大概是这样public class ResultT { private Integer code; // 200 成功500 失败401 未登录 private String message; private T data; }然后分页返回 data 是一个 PageVO 对象里面有 records当前页数据和 total总条数。所有接口统一返回 Result 包装前端处理数据会非常省心。2.3 借阅业务的状态机设计图书馆借阅系统里最考验逻辑能力的是借书、还书、续借这几个核心动作。很多人的代码写着写着就乱是因为没有先把状态流转想清楚。我建议你画出这样的状态机借书用户发起借书 → 校验用户状态正常 → 校验图书可借数量大于 0 → 创建借阅记录status0borrow_timenowdue_timenow30天→ 图书 stock_available 减一。还书用户还书 → 找到借阅记录 → 判断当前时间是否超过 due_time → 未逾期status1return_timenow已逾期status2在 status 里直接记为逾期已还同时计算逾期天数、生成罚款记录 → 图书 stock_available 加一。续借用户在未逾期且借出状态下可以发起 → 将 due_time 向后顺延 30 天 → 限定只能续借一次需要一个字段记录续借次数否则可以无限续逻辑就破了。这里最容易被忽略的是数据库事务。借书这个动作既要插入借阅记录又要扣减图书可用数量两步必须保证同时成功或同时失败。SpringBoot 里一个 Transactional 注解就能搞定。但注意事务只在你跨多个 Mapper 操作时才有意义很多人只调用一个 Mapper 方法时也习惯性加事务这没毛病但没实际意义。借书的核心代码大致长这样Transactional(rollbackFor Exception.class) public Result borrowBook(BorrowRequest request) { // 1. 校验用户是否存在且状态正常 User user userMapper.selectById(request.getUserId()); if (user null || user.getStatus() ! 1) { return Result.error(用户不存在或已被禁用); } // 2. 校验图书存在且可借数量 0 Book book bookMapper.selectById(request.getBookId()); if (book null || book.getStockAvailable() 0) { return Result.error(图书不存在或无库存可借); } // 3. 检查该用户是否已有未归还的相同图书记录 // where user_id ? and book_id ? and status 0 // 如果有直接拒绝防止重复借同一本 // 4. 创建借阅记录 BorrowRecord record new BorrowRecord(); record.setUserId(user.getId()); record.setBookId(book.getId()); record.setBorrowTime(new Date()); record.setDueTime(DateUtil.offsetDay(new Date(), 30)); record.setStatus(0); borrowRecordMapper.insert(record); // 5. 扣减库存 book.setStockAvailable(book.getStockAvailable() - 1); bookMapper.updateById(book); return Result.success(借书成功); }以上代码里我特意加了一个很多初学者想不到的步骤——校验用户是否已经借了同一本书且未归还。不加这个判断用户可以把同一本书连续借十次库存很快耗尽但事实是一个读者通常只能持有一本同一本书。这种“业务规则先行”的思路是答辩时加分的关键点。3. 前端Vue3 Element Plus 从零搭建页面3.1 项目初始化与权限控制前端我建议直接用 Vite 来初始化 Vue3 项目。Vite 启动速度快热更新流畅而且是目前官方推荐的构建工具用起来比老一代的 Vue CLI 舒服很多。初始化方式官网有标准命令跑完之后装几个依赖npm install vue-router4 npm install pinia npm install element-plus npm install axios npm install echarts状态管理用 Pinia 而不是 Vuex因为 Pinia 是 Vue3 官方推荐的API 更简洁没有 Vuex 里 mutation 那一套啰嗦的概念。你就把它理解成一个全局的“数据总仓库”比如登录后的用户信息、Token放进去任何组件都能取。路由设计上建议用动态路由来控制权限。什么意思就是用户登录后根据角色不同添加不同的路由规则。管理员可以看到“用户管理”“图书管理”“借阅管理”这些菜单普通用户只看到“图书检索”“我的借阅”。这种做法比单纯在前端用 v-if 判断按钮显隐要更安全——因为前端只能控制“看不看得到”真正的权限校验还是得靠后端接口但路由级别的控制能让体验更干净。一个简化版的动态路由逻辑// 登录后根据 role 动态注册路由 if (userInfo.role ADMIN) { router.addRoute({ path: /admin, name: Admin, component: () import(/views/admin/AdminLayout.vue), children: userInfo.permissions }); } else { router.addRoute({ path: /my, name: MyBorrow, component: () import(/views/user/MyBorrow.vue) }); }路由守卫里记得处理一个细节用户刷新页面时状态管理里的 userInfo 会丢失因为它是存在内存里的。所以需要把 Token 和用户基础信息存到 localStorage 或 sessionStorage。刷新时如果发现有 Token 但没有 userInfo可以先调一个接口更稳妥地处理例如后端提供的获取当前用户信息接口GET /api/user/info因为 Token 本身是被后端持久化的。不要让接口因为信息丢失导致用户被强行退出登录这个体验是真实项目里会被反复吐槽的。3.2 核心页面实现图书管理表格与借书弹窗图书管理页面核心是一个带搜索条件的表格加上新增、编辑的弹窗。Element Plus 的 el-table、el-form、el-dialog、el-pagination 这四个组件基本承包了日常后端管理页面的所有需求。图书表格的列我建议这样安排列说明图书封面el-image 展示加个默认占位图ISBN / 书名 / 作者 / 出版社基本静态信息分类来自分类表库存总量 / 可借数量合计行可不汇总单列展示状态可借数量 0 显示“可借”否则“不可借”用 el-tag 做颜色区分操作编辑、删除、借书管理员可以代用户借书新增图书的表单里ISBN 建议加一个格式校验正则13位数字因为这是图书维一的业务标识避免录错。分类字段用 el-select 下拉选择选项从 /api/category/list 动态加载不要写死。封面上传如果不想引入复杂对象存储服务最简单方案是后端提供一个上传接口存到本地目录/upload/再用一个映射路径把静态资源暴露出来。生产上这做法不行但毕设场景完全够用也确实能砍掉很多麻烦。借书弹窗是交互比较重的部分。点击借书弹窗后需要选择用户。管理员场景是“代客借书”所以需要先选一个用户。我这里推荐的是一个用户下拉加远程搜索输入手机号关键字向后端发请求查匹配的用户选一个再提交。这个交互比一次性拉全量用户列表要合理而且答辩时说“支持按关键字远程搜索用户适应大量用户场景”也是一个很好的细节。3.3 Axios 封装与前后端联调细节Axios 封装这块建议所有请求都走一个统一实例别在页面里直接裸调 axios.get。封装的核心干三件事BaseURL 统一、Token 自动携带、错误统一处理。const service axios.create({ baseURL: /api, timeout: 10000 }); // 请求拦截器自动带 token service.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers.Authorization Bearer ${token}; } return config; }); // 响应拦截器统一处理业务错误 service.interceptors.response.use( response { const res response.data; if (res.code 200) { return res; // 业务成功直接返回数据体 } // 401 特殊处理跳转登录页 if (res.code 401) { localStorage.clear(); router.push(/login); } return Promise.reject(new Error(res.message)); }, error { // 网络/HTTP级错误给个统一提示 return Promise.reject(error); } );前后端联调时最容易卡住的一个问题是跨域。我在开发环境解决跨域的手段是 Vite 的 proxy 配置而不是在后端写 CrossOrigin。这个取舍很关键因为 CrossOrigin 是后端允许某个来源跨域调用调试时比较灵活但生产环境前端和后端可能部署在不同地址依赖这个注解就等于把安全策略交给了每一个接口去控制一旦写漏一个又出问题。Vite proxy 的思路是开发环境前端跑在 5173 端口代理把/api开头的请求转发到后端的 8080 端口浏览器看所有请求都是同源的根本不存在跨域问题。生产环境部署时候选方案是用 Nginx 反代理同样把/api/转发到后端服务这才是标准做法。Vite proxy 配置示例export default defineConfig({ server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } });4. 把“借书-还书-续借”跑通的完整流程4.1 前端交互闭环从点击到成功提示用户视角的借书流程是这样的在图书列表页发现一本想借的书点击“借书”按钮弹出确认框如果该用户当前没有未还的相同图书且库存充足后端返回成功前端弹出成功提示表格自动刷新可借数量减一。整个闭环只在书列表页完成不需要跳转详情页交互成本低。如果你是普通用户角色登录那“借书”按钮只出现在你有“可借”状态的图书上。这个细节前端不要偷懒直接在模板里 v-if 判断建议用一个计算属性或者方法function canBorrow(book) { return book.stockAvailable 0; }后端也必须校验不能只靠前端隐藏按钮。还书流程建议单独放在“借阅管理”页面。管理员能看到所有未还的借阅记录支持按用户、按图书、按借出日期筛选。列表里每一条记录超过应还时间还没还的应还日期红色高亮并显示逾期天数。点击“还书”按钮之前弹窗里先展示“这本书已经逾期 X 天将产生罚款 Y 元确认还书吗”这个确认步骤能避免很多误操作也能防止用户在不知情的情况下被罚款。4.2 逾期计算与罚款这个细节值多少分逾期计算是借阅系统最容易让程序“差一点正确”的环节。我见过不少人是这样写的拿当前时间减应还时间得到毫秒差除以一天毫秒数得到逾期天数。这个逻辑大方向对但有一个问题——它忽略了“当天还书算不算逾期”这种业务规则。不同学校或图书馆的规定不一样有的当天还就是逾期有的只要在当天 24 点前还算正常。你要在代码里明确这个口径并在答辩时能讲清你的规则。我的实现建议是逾期状态计算单独写一个方法放在工具类里不要散落在 Controller 里。public static long calcOverdueDays(Date dueTime, Date returnTime) { if (returnTime null) { returnTime new Date(); } // 阈值设置为天数级别比较日期大小更可靠 long diffMillis returnTime.getTime() - dueTime.getTime(); if (diffMillis 0) { return 0; } // 1 还是不加看业务规则此例定义超过应还日 24:00:00 才算逾期 1 天 return (long) Math.ceil(diffMillis / (1000 * 60 * 60 * 24)); }罚款金额我建议简单计算金额 逾期天数 * 每日罚款金额每日罚款金额放到系统配置表里而不是写死在代码里。这样你答辩的时候能说“罚款标准是可配置的运营人员能改”体现你考虑了可维护性。4.3 并发场景下防超借的可行方案图书可借数量只有 5 本但两个用户同时提交借书请求会不会借出去 6 本在纯单机部署、并发量不高的毕设场景下这个问题不常发生但如果你这么写了并在答辩时主动提起这题就答得漂亮了。防超借有三种常见手段我按推荐顺序给你排一下第一数据库层面用乐观锁。给 book 表加一个 version 字段更新库存时带着版本号UPDATE book SET stock_available stock_available - 1, version version 1 WHERE id #{id} AND version #{version} AND stock_available 0;受影响行数为 0说明版本不匹配或库存不足事务回滚即可。这个方案成本低理解简单是中小型并发控制的首选。第二悲观锁SELECT ... FOR UPDATE把图书行锁住再执行更新串行化操作。能解决问题但并发性能差适合极低并发下无脑可控的场景。第三Redis 分布式锁对 key 为book:borrow:{bookId}设置过期锁。能力强但在毕设里有点杀鸡用牛刀而且如果你们不熟悉 Redis引入后又在部署环境踩坑反而得不偿失。我的建议是代码里实现乐观锁方案并在文档里写明“未来如果并发量上来可以考虑引入 Redis 分布式锁”既展示了思考深度又不会给自己增加工作负担。5. 常见问题与排查实录5.1 跨域问题的三种表现与排查思路跨域是前后端分离项目第一个拦路虎而且它的错误表现非常有迷惑性。常见症状是前端控制台报错“CORS policy: No ‘Access-Control-Allow-Origin’ header”或者请求状态码是 200 但浏览器拦截了响应还有的情况是发起了 OPTIONS 预检请求然后 403。排查思路我建议按下面的顺序来先确认后端接口地址是不是真的能被浏览器直接访问。直接在地址栏敲接口 URL如果能返回 JSON说明后端活着问题大概率出在跨域配置。检查是不是走的本项目代理。开发环境用了 Vite proxy就不需要后端允许跨域也不要给 axios baseURL 写死成http://localhost:8080这会让代理失效。baseURL 写/api就好让代理层帮你在转发前加上目标地址。如果生产环境还是报跨域优先检查 Nginx 反代理配置而不是去给后端逐接口加 CrossOrigin。5.2 时间字段的时区与格式化问题Java 后端时间字段如果你用Date或LocalDateTime直接向前端返回默认序列化出来是一长串数字时间戳或者带 T 的 ISO 格式前端拿到还得自己处理。我建议统一在配置里指定全局的时间格式用 Jackson 配置spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8同时也要注意时区问题。如果后端服务器时区不是东八区默认部署环境的时区往往是 UTC那“借书时间”“应还时间”显示出来会差 8 个小时。SpringBoot 配置文件里指定的 time-zone 要用GMT8不要写Asia/Shanghai因为有些基础镜像里没有完整的时区库反而识别不了。这是我以前亲手踩过的坑。前端展示时我自己的习惯是 new Date() 之后调用一个 dayjs 来做格式化。Element Plus 的 date-picker 和 dayjs 配合得比较好不要重复造轮子。5.3 数据库常见问题时区、编码、时区之外的连接坑MySQL 5.7 及以上版本在 URL 里默认带了serverTimezone的要求。如果你的连接串写jdbc:mysql://localhost:3306/library?serverTimezoneAsia/Shanghai在不同版本的 MySQL 驱动下可能出现 “Malformed database URL” 或无法识别时区的问题。我建议统一用jdbc:mysql://localhost:3306/library?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneGMT%2B8另外图书管理系统的表格里难免有中文数据数据库和表的字符集建议建库时直接指定utf8mb4而不是utf8因为 utf8mb4 能存 Emoji 和更多特殊字符是一种更安全的兜底。5.4 前端常见问题表格数据刷新与表单重置一个高频 bug新增图书成功后弹窗关了但表单里的数据还在下一次打开还是上一次的内容。原因是 el-form 绑定的响应式对象没有重置。正确做法是调用表单的 resetFields 方法// 打开弹窗时 dialogVisible.value true; nextTick(() { formRef.value?.resetFields(); // 重置表单 // 或者手动赋值空对象 });还有一类问题是删除图书后列表没有更新。别只在提示成功后就完事务必重新调用分页查询接口让表格重新拉取数据。如果删除后数据没回到第一页你还要考虑当前页是否已经没数据了要往前翻一页。这种“交互闭环”的细节是很多初手容易忽略的地方。5.5 依赖版本冲突一次真实排查经历说一个具体的案例给你参考。我之前帮一个同学搭项目SpringBoot 用的是 2.7.x但 MyBatis-Plus 用的还是 3.4.x 老版本结果一启动就报找不到某方法排查了半天才发现是 MyBatis-Plus 版本不兼容 SpringBoot 2.7 的新特性。后来把 MyBatis-Plus 升到 3.5.x 就好了。这个教训非常通用现在很多教程写于两三年前里面的版本号不一定适配你当前初始化的新项目。如果你照着老教程复制依赖版本跑不起来不要先怀疑代码先看看版本要求。再比如 SpringBoot 3.x 用的是jakarta.*包名而 2.x 用javax.*网上很多老代码贴的是 javax你若直接复用到 3.x 上编译直接不通过。我的建议是如果你没有非用 3.x 不可的理由毕设就老老实实用 SpringBoot 2.7.x因为绝大多数教程、博客、已有的开源代码都是基于这套生态的你遇到问题更容易搜到答案。6. 写在最后的一些实在话我可以把源码发你一份但我更希望你做一件事情先看文档和 README把项目跑起来然后做一次“重构”——不要大改架构而是只改其中一个小模块。比如我把借阅状态从“数字枚举”改成“字符串枚举”或者你把分页查询里新增一个按出版社筛选的接口。这种小步动手比通读源码有效得多。为什么因为看懂别人的代码是一种被动学习只有真正动手改过、加过功能你才会发现哪些地方设计得好、哪些地方你想吐槽这个过程才是你自己的收获。另外答辩之前强烈建议你自己列一个“为什么清单”为什么用 JWT 而不是 Session为什么库存不直接用数据库的减一操作而是带 version 的乐观锁为什么分类和数据字典要单独建表为什么图书删除是假删除而不是物理删除这些问题你如果能对答如流那这份毕设真就过关了。我当年做完项目后把导师可能问的每一个点都写在纸上反复演练到能脱口而出。答辩时导师问的角度往往就是这些“看似基础、其实见功底”的问题。最后祝你一次通过顺利搞定。