简介一套基于SpringbootSpringMVCMybatis小程序实现的图书捐赠管理系统毕业设计完整资源包面向计算机相关专业毕业生和正在学习SSM框架、小程序开发的读者适用于毕业设计、课程设计或项目实战参考。资源共包含856个文件压缩包约42.96MB其中Java源码与class文件构成后端核心XML、Properties、JSON为框架映射与配置JSP、WXML、WXSS、JS实现Web与小程序页面SQL脚本用于初始化图书、捐赠者、捐赠记录等数据表gif、png等素材则用于界面展示与流程演示整体目录结构完整方便直接导入开发工具运行与二次开发。当前已有143人学习下载适合作为同类管理系统的参考模板。通过研读该项目能够理解SpringBoot自动配置、SpringMVC请求分发、Mybatis持久化操作及小程序端交互的完整协作方式并能结合数据库表设计、业务模块划分与前后端联调思路快速搭建属于自己的毕业设计项目。1. 图书捐赠管理系统毕业设计一套能跑通的 SpringBoot小程序全家桶如果你也在为毕业设计选题目图书捐赠管理系统几乎是“性价比之王”业务闭环完整从用户注册、图书发布到捐赠领取涉及权限、状态流转和文件上传技术栈又正好卡在高校最常见的 SpringBootSpringMVCMyBatis 上前端再用微信小程序兜住“移动端”的亮点。但真正动手你会发现这个系统的业务逻辑一天就能写完真正让人翻车的是三个技术栈的边界——小程序登录态怎么和后端对上、图片传到哪、MyBatis 缓存什么时候把你坑了。这篇笔记就按我实际做完一版的路子把工程结构、表设计、小程序对接和排错步骤一次讲清适合拿去做课程设计、毕业设计或者想快速熟悉这套全家桶的读者。2. 后端工程拆分SpringBootSpringMVCMyBatis 三层怎么组织才不翻车2.1 为什么是这三个框架组合分工与边界先说清楚SpringBoot、SpringMVC、MyBatis 这一套很多新手会误以为它们是“三个并列的框架”其实它们是层层嵌套的关系。SpringBoot 负责自动配置和应用启动把 Tomcat、消息转换器、数据源这些基础设施一次性配好SpringMVC 是 Spring 家族自带的 Web 层框架专门管请求路由、参数绑定和方法拦截MyBatis 则只干持久层一件事——把 Java 方法和 SQL 映射起来。这套组合在毕设场景里几乎是默认答案。图书捐赠系统的查询条件多且组合不固定比如按书名模糊查、按分类精确查、按状态过滤MyBatis 的 XML 里写动态 SQL 比 JPA 直观得多而 SpringMVC 的拦截器又正好用来做小程序端登录态校验不用在 Controller 里重复写“从 Header 取 token”的模板代码。我一般会把分包按 controller / service / mapper / entity / config 来切不要引入复杂的 DDD 分层毕设系统业务量就一个读者一个管理员三层足够。还有一个常见误区有人会同时引入spring-boot-starter-web和mvc:annotation-driven之类的 SpringMVC XML 配置搞出双份配置。实际上spring-boot-starter-web内部已经嵌套了 SpringMVC 的自动配置你只需要在application.yml里调参数不要再手写DispatcherServlet配置否则会出现请求被处理两次或者静态资源 404 的玄学问题。2.2 最小可用工程pom.xml 与 SpringBoot 版本选择先说版本选择这是我最想强调的。Spring Boot 3.x 强制要求 JDK 17而很多毕设同学的开发环境还停留在 JDK 8学校的机器也未必装得上新版。更现实的问题是网上大量教程和现成代码都是 Spring Boot 2.x 的写法你复制一段回答里的配置可能直接就编译不过。我建议直接用 Spring Boot 2.7.18这是 2.x 系列的最后一个版本稳定性和生态兼容性都很好。对应的 MyBatis Starter 用mybatis-spring-boot-starter2.3.xJDK 1.8 就能跑。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies !-- Web 场景启动器里面自带 SpringMVC -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MyBatis Spring 整合 -- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.2/version /dependency !-- MySQL 驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency !-- Lombok简化实体类 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies注意几个参数。第一relativePath留空避免 Maven 向上递归找父工程第二MySQL 驱动在 Spring Boot 2.7 里不需要写版本号由父工程统一管理第三如果你用 JDK 8千万别手欠把 Spring Boot 升到 3.x否则启动会直接报UnsupportedClassVersionError。还有一个我踩过的坑新版 IDEA 生成工程时默认选 Spring Boot 3.x如果你没注意看右下角的版本号后面会多出大量兼容性修改时间。工程启动后在application.yml里加一行 MyBatis 日志打印这是排查 SQL 问题的关键。配置如下mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.donation.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case一定要开否则数据库里的create_time映射不到实体的createTime上查询结果全是 null。log-impl让 MyBatis 把每条 SQL 和参数打印到控制台你调试“查不出来”“查出来是空的”这类问题第一个动作就是看控制台的 SQL 和Parameters那行。2.3 写第一个捐赠图书的 Controller→Service→Mapper 链路一条完整的链路我习惯从 Mapper 往上写。先定义接口方法再在 XML 里写 SQL最后在 Service 里组织事务Controller 只做参数接收和结果返回。下面以“发布捐赠图书”为例给你一份可以直接抄的骨架。// BookMapper.java public interface BookMapper { int insert(Book book); } // BookService.java Service public class BookService { Resource private BookMapper bookMapper; Transactional public int publish(Book book, Long userId) { book.setOwnerId(userId); book.setStatus(0); // 0 待审核1 上架2 已领取3 下架 book.setCreateTime(new Date()); return bookMapper.insert(book); } } // BookController.java RestController RequestMapping(/api/book) public class BookController { Resource private BookService bookService; PostMapping(/publish) public ResultLong publish(RequestBody Book book, HttpServletRequest request) { Long userId (Long) request.getAttribute(userId); bookService.publish(book, userId); return Result.success(book.getId()); } }每个注解和参数都有讲究。Transactional必须放在 Service 层而不是 Controller 层因为 Spring 默认只对代理方法生效跨类调用放在 Controller 里事务管理范围会失控。request.getAttribute(userId)取自拦截器放入的用户信息避免了每个方法都去解析 token。RequestBody接收的是 JSON 字符串所以小程序端 wx.request 里要设置Content-Type: application/json这一点经常被忽略导致后端收到的 book 对象全是默认值。如果你在 Service 里调bookMapper.insert(book)之后想直接拿到自增主键不用额外配置MyBatis 的useGeneratedKeys默认行为是回填实体类的主键字段所以book.getId()在 Controller 里是有效的。2.4 SpringMVC 拦截器小程序端登录态校验放哪一层登录态校验我放在 SpringMVC 拦截器里而不是 Filter也不是 AOP。原因是 Filter 拿不到 HandlerMethod 的信息没法精确排除“登录接口”和“静态资源”AOP 写切入点表达式又太绕对毕设项目来说维护成本不划算。拦截器可以清晰地在预处理的preHandle方法里做三件事从 Header 取 token、解析用户、把 userId 放进 request attribute。Component public class LoginInterceptor implements HandlerInterceptor { Autowired private UserService userService; Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // 放行登录接口 if (request.getRequestURI().contains(/api/auth/)) { return true; } String token request.getHeader(Authorization); if (token null) { response.setStatus(401); return false; } Long userId userService.getUserIdByToken(token); if (userId null) { response.setStatus(401); return false; } request.setAttribute(userId, userId); return true; } }注意一个细节小程序端请求图片时不会带 Authorization Header如果你的图片也走了 Controller 接口拦截器会把图片请求也拦住。解决办法是注册拦截器时为/api/user/**、/api/upload/**这类路径单独设置排除规则或者干脆把上传的图片放在独立静态目录不走 Controller。注册拦截器用WebMvcConfigurerConfiguration public class WebConfig implements WebMvcConfigurer { Resource private LoginInterceptor loginInterceptor; Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .addPathPatterns(/api/**) .excludePathPatterns(/api/auth/**, /error); } }这里很容易踩坑的是Spring Boot 2.7 里实现WebMvcConfigurer不会影响 SpringMVC 自动配置但如果你继承了WebMvcConfigurationSupport自动配置会被直接关掉静态资源映射和消息转换器全要自己手动补回去。这是这个组合里最典型的误区之一后面避坑章会展开说。3. 图书捐赠的核心数据设计四张表撑起整个业务3.1 用户表与图书表状态字段与逻辑删除图书捐赠系统最怕把表设计得过于复杂。我见过有同学拆出“捐赠表、接收表、审核表、分类表、评论表”七八张表互相外键结果写 JOIN 查询写到自己崩溃。实际上四张表就够用用户表、图书表、捐赠记录表、领取记录表。用户和图书是主体捐赠记录和领取记录是行为流水分类字段直接冗余在图书表里用字符串存就好。用户表我习惯只放关键字段role 用 tinyint 区分管理员和普通用户密码用 MD5 加盐token 用 UUID 存到单独字段毕设不需要引入 Redis。图书表则是整个系统的核心几个状态字段的取值必须和前端约定死否则前后端各写一套状态码联调时全是乱码。以下是精简过的建表 SQLCREATE TABLE user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, openid VARCHAR(64) NOT NULL UNIQUE, nickname VARCHAR(32) DEFAULT , phone VARCHAR(20) DEFAULT , role TINYINT DEFAULT 0 COMMENT 0 普通用户1 管理员, token VARCHAR(64) DEFAULT NULL, deleted TINYINT DEFAULT 0 COMMENT 0 未删除1 已删除, create_time DATETIME DEFAULT NULL ); CREATE TABLE book ( id BIGINT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(64) NOT NULL, author VARCHAR(32) DEFAULT , publisher VARCHAR(64) DEFAULT , category VARCHAR(32) DEFAULT , cover_url VARCHAR(255) DEFAULT , owner_id BIGINT NOT NULL, status TINYINT DEFAULT 0 COMMENT 0 待审核1 上架中2 已领取3 已下架, deleted TINYINT DEFAULT 0, create_time DATETIME DEFAULT NULL, update_time DATETIME DEFAULT NULL );字段设计有几个约定要说明。第一所有表都加deleted字段做逻辑删除不要物理删除数据答辩时老师会问“为什么保留数据”你答“为了追溯捐赠记录”就能加分。第二状态字段用TINYINT不要用VARCHAR存“上架”“下架”这种中文枚举值在代码里用常量维护避免数据库里出现 “上架 ”和“上架”带空格这种脏数据。第三ISBN 这种字段不建议做唯一索引因为旧书或非正式出版物经常没有 ISBN一旦为空的字段加唯一索引第二次插入空值就会报错。3.2 捐赠记录表与领取记录表一个状态机串起来捐赠记录表记录的是“从发布到上架”的过程领取记录表记录的是“从申请到领取”的过程两者靠book_id关联。重点在于 state 状态要串成一个闭环不能用两个独立的字段各管各的。我设计的字段如下表名字段类型说明donate_recordidbigint主键donate_recordbook_idbigint关联图书donate_recorddonor_idbigint捐赠人donate_recordstatustinyint0 待审核1 通过2 驳回claim_recordidbigint主键claim_recordbook_idbigint关联图书claim_recordclaimer_idbigint领取人claim_recordstatustinyint0 申请中1 通过2 领取完成3 已取消claim_recordapply_timedatetime申请时间claim_recordfinish_timedatetime完成时间状态流转上图书表的 status 是主状态捐赠记录和领取记录的 status 是辅助状态。比如“领取完成”这一步需要同时更新claim_record.status2和book.status2这两条更新必须放在同一个事务里。如果只更新了 claim_record 没更新 book就会出现图书还挂在首页上、但已经被领走的脏数据。这一步是最容易被答辩老师追问的地方务必处理好事务边界。3.3 MyBatis 动态 SQL 处理多条件查询空串与缓存的坑图书列表页的“分类 书名 状态”三个条件组合用 MyBatis 动态 SQL 最舒服。下面这份 mapper 是列表页的核心注意每个if的判断细节。select idselectByCondition resultTypecom.example.donation.entity.Book SELECT * FROM book where if testtitle ! null and title ! AND title LIKE CONCAT(%, #{title}, %) /if if testcategory ! null and category ! AND category #{category} /if if teststatus ! null AND status #{status} /if AND deleted 0 /where ORDER BY create_time DESC LIMIT #{offset}, #{pageSize} /select三个参数细节要记住。第一where标签会自动去掉第一个AND你不需要自己拼WHERE 11那是老写法写进去反而显得代码不干净。第二页面端传category空字符串时category ! null是 truecategory ! 才是 false这个“参数等于空串”的判断漏写是查询结果被错误过滤的最常见原因。第三LIMIT的 offset 在 Controller 里计算offset (page - 1) * pageSize不要在 SQL 里直接传page - 1这种表达式MyBatis 传参时直接把表达式当字符串拼进去会报 SQL 语法错误。关于缓存我的建议是毕设系统全程不要开 MyBatis 二级缓存。二级缓存的作用范围是跨 session 的图书这类经常被更新的数据一旦开了缓存你改完状态后列表页很可能还显示旧数据而且调试时很难意识到是缓存问题。如果非要在某个字典表上开用注解cache开启后务必为每个写操作加上flushCachetrue否则数据不一致几乎是必然的。4. 小程序端对接从 wx.login 到发布捐赠4.1 小程序登录换取后端 token 的最小流程小程序的登录体系和网页完全不同核心思路是小程序通过微信提供的wx.login拿到一个一次性 code然后用这个 code 请求后端后端拿 code 向微信服务器换取 openid再用 openid 生成自己的登录态。记住一个原则openid 是微信给你的用户唯一标识绝对不要把它放在小程序前端存储里一切以你后端发的 token 为准。前端核心代码wx.login({ success: (res) { if (res.code) { wx.request({ url: http://127.0.0.1:8080/api/auth/login, method: POST, data: { code: res.code }, success: (loginRes) { const token loginRes.data.data.token; wx.setStorageSync(token, token); } }); } } });后端的处理逻辑是接收 code → 调用https://api.weixin.qq.com/sns/jscode2session换取 openid → 查数据库有没有这个 openid → 没有就自动注册新用户有就直接更新 token → 返回 token。这里有个毕设常见的烦恼微信登录获取手机号的功能要求小程序是企业主体且通过认证个人主体基本申请不了。我的替代方案是把手机号做成选填字段用户进个人中心后自己补不影响登录流程答辩时也能自圆其说。代码里的127.0.0.1是本地开发地址等部署到服务器换成你自己的域名。注意小程序在真机上不能访问localhost只能访问局域网 IP 或公网地址这是小程序加强了对不合法域名的限制所致。4.2 首页图书列表与分页加载触底加载怎么做首页列表用分页是最常规的做法。小程序端在onLoad加载第一页滚动到底部触发onReachBottom加载下一页后端接口返回{list, total}。这里不要一次性加载全部数据图书列表的数据量虽然不大但一次性渲染 500 条会明显卡顿而且分页写法在答辩时是一个加分项。Page({ data: { books: [], page: 1, pageSize: 10, hasMore: true }, onLoad() { this.loadBooks(); }, onReachBottom() { if (this.data.hasMore) { this.setData({ page: this.data.page 1 }); this.loadBooks(); } }, async loadBooks() { const res await wx.request({ url: http://127.0.0.1:8080/api/book/list, data: { page: this.data.page, pageSize: this.data.pageSize, status: 1 }, header: { Authorization: wx.getStorageSync(token) } }); const newList this.data.page 1 ? res.data.data.list : this.data.books.concat(res.data.data.list); this.setData({ books: newList, hasMore: this.data.books.length res.data.data.total }); } });两个细节容易忽略。第一请求 Header 里的 Authorization 必须从 storage 取如果登录 token 过期后端返回 401此时应该跳转到登录页而不是继续加载第二hasMore的判断条件写books.length total不是page * pageSize total因为最后一页的数据条数可能少于 pageSize按后者判断会多加载一个空页。分页加载后数据不会自动重置所以page 1时是刷新列表否则是追加数据这个判断逻辑要保留。4.3 发布捐赠表单提交与图片上传发布捐赠是小程序端最复杂的操作因为涉及图片上传。正确的流程是先调用wx.chooseMedia选择图片再通过wx.uploadFile上传到后端上传成功拿到图片 URL最后把 URL 和表单数据一起用 JSON 提交给发布接口。上传和发布是两步千万不要用wx.uploadFile的附加参数传整个表单那样后端收到的是文件流而不是 JSON。wx.chooseMedia({ count: 1, mediaType: [image], sourceType: [album, camera], success: (res) { const filePath res.tempFiles[0].tempFilePath; wx.uploadFile({ url: http://127.0.0.1:8080/api/upload/image, filePath: filePath, name: file, success: (resp) { const coverUrl JSON.parse(resp.data).data; this.setData({ coverUrl }); } }); } });后端接收图片用MultipartFile保存位置不要写到src/main/resources/static因为打包后这目录在 jar 包里你没法往里写文件。常见做法是保存到服务器上的绝对路径比如/data/upload/然后在WebMvcConfigurer里配置一个虚拟路径映射让/images/**指向/data/upload/。注意在application.yml里把上传大小限制调大一点默认只有 1MB 可能不够用spring: servlet: multipart: max-file-size: 10MB max-request-size: 20MB4.4 管理端审核小程序里怎么做“管理员入口”管理功能不用单独做一套后台页面直接在小程序里按角色显示入口就行。用户登录后后端在登录接口的返回值里带上role字段小程序端存到 storage再根据role动态渲染菜单。审核页面就是普通列表页加一个“通过 / 驳回”按钮两个按钮各调一个接口刷新当前列表。小程序端动态设置标题也是毕设里常被问到的点。进入不同页面时可以用wx.setNavigationBarTitle改变顶部导航文字比如普通用户看到“图书列表”管理员进入审核页看到“捐赠审核”这比所有页面都叫“首页”显得用心得多。onShow() { const role wx.getStorageSync(role); if (role 1) { wx.setNavigationBarTitle({ title: 捐赠审核 }); this.loadPendingBooks(); } else { wx.setNavigationBarTitle({ title: 图书列表 }); this.loadAllBooks(); } }5. 避坑图书捐赠系统里最常见的 5 个运行时问题5.1 小程序请求后端报 404 或 502域名与路径双重坑现象小程序开发者工具里请求http://127.0.0.1:8080/api/book/list提示not found或者request: fail换成局域网 IP 能通但真机上就是打不开。原因这个现象要拆开看。404 是后端路径确实不对检查 Controller 的RequestMapping前缀和方法路径是否完全匹配尤其是有拦截器时excludePathPatterns写错把请求挡了而request: fail则是小程序域名白名单的限制微信要求线上小程序必须使用 HTTPS 且配置合法域名本地开发要在开发者工具的“详情 - 本地设置”里勾选“不校验合法域名”。解决本地调试把“不校验合法域名”打开用127.0.0.1或局域网 IP 访问部署后换成备案且配好 HTTPS 的域名并在微信公众平台的服务器域名白名单里填上这个域名。后端拦截器的排除路径单独再核对一遍/api/auth/**这种写法在 Spring Boot 里是 Ant 风格路径分隔符必须一致。5.2 查询结果全是 null驼峰映射没开现象小程序端请求图书列表字段名全是title有值、createTime却一直是 undefined数据库里明明有create_time字段且非空。原因MyBatis 默认的数据库字段到 Java 属性的映射是严格按名字匹配的数据库里create_time不会自动映射到createTime。这是教科书上不会讲但一定会遇到的坑。解决application.yml里加mybatis.configuration.map-underscore-to-camel-case: true即可。如果你用的是 XML 文件手写映射还得在resultMap里给每个字段写column和property这是数据查出来却映射不上时最直接的排查方向。5.3 图书状态改完列表不刷新MyBatis 二级缓存惹的祸现象管理员将图书状态从“待审核”改为“上架中”小程序首页列表还是显示旧状态关掉小程序重新打开才正常。原因如果你在项目里开了 MyBatis 二级缓存SQL 执行结果被缓存到 Mapper namespace 级别同一条查询第二次不走数据库。图书表数据是实时变化的状态更新后缓存没有主动失效。解决毕设系统直接把二级缓存关掉不开启任何cache配置。如果已经开了在改状态的 Mapper XML 上补update flushCachetrue查询语句则加入useCachefalse属性。换库更新后注意观察控制台是否打印了 SQL如果第二次查询没有 SQL 输出一定是缓存命中了。5.4 Spring Boot 3.x JDK 8 不兼容版本对不上的一堆奇怪报错现象照着教程建项目启动时报UnsupportedClassVersionError或ClassNotFoundException: javax.servlet.Filter还有人遇到Failed to configure a DataSource但配置里明明写了数据库连接。原因Spring Boot 3.x 底层从javax.servlet换成了jakarta.servletJDK 最低要求 17。你如果环境是 JDK 8 却用了 3.x 的父工程连编译都过不了而Failed to configure a DataSource大概率是你没配数据库连接Spring Boot 启动时发现找不到数据源。两个现象混在一起最容易被误判成数据库问题。解决先把spring-boot-starter-parent版本降到 2.7.18JDK 保持 1.8 或 8 系列不变。编译过之后检查application.yml中的spring.datasource.url、username、password是否齐全。如果用的是 MySQL 8.x驱动类是com.mysql.cj.jdbc.DriverMySQL 5.x 才用com.mysql.jdbc.Driver版本不匹配也会有类似症状。5.5 上传的图片刷新后丢失路径写死在工程里现象本地开发上传图片成功浏览器里也能访问但项目重新打包部署到服务器后之前上传的图片全部 404。原因开发时把图片写到了src/main/resources/static/uploads但是 Spring Boot 启动运行的是打好的 jar 包resources 目录在 jar 内部属于只读。你上传时写进了开发目录部署后服务器上根本没有这个目录图片自然丢了。解决上传目录改成操作系统里的绝对路径比如 Linux 服务器用/data/upload/然后通过配置类加虚拟映射Configuration public class UploadConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/images/**) .addResourceLocations(file:/data/upload/); } }路径最后的斜杠不能省少了它 Spring 拼接资源路径时会把文件名和目录粘在一起。这个坑是小程序图片上传功能里最高频的线上问题建议一开始就按这个规范写。6. 从能跑到能答辩部署验证与论文里的技术写作技巧部署验证我的习惯分三步走。第一步后端本地mvn spring-boot:run启动后先用 Postman 把注册、登录、发布、审核、列表这五条接口全部过一遍确认返回结构和状态码符合你在纸上定的规范第二步小程序开发者工具里把“不校验合法域名”打开真机预览走完整流程重点测图片上传和登录态过期这些边缘场景第三步如果答辩现场用服务器演示先把后端打成 jar 包用宝塔或 Docker 部署起来MySQL 数据导到线上库再让小程序连公网域名。我见过太多人答辩当天还在用 IDEA 里的本地服务现场网络一出问题整个流程就垮掉了。论文写作方面技术含量不在于堆字而在于把你做的东西讲成方案。架构图必须画三层小程序端、SpringBoot 后端、MySQL 数据库中间标出 HTTP 接口和 JSON 数据流数据库设计部分把四张表的关系画成 E-R 图重点标注图书表的 status 四个状态和两个记录表的关联业务时序图要画发布捐赠到审核上架再到领取完成的完整流程。答辩老师常问的三个问题是“为什么用 MyBatis 不用 JPA”“为什么图书状态用数字不用字符串”“多个人同时领取一本书怎么避免重复领取”前两个在正文里找得到第三个在事务和乐观锁那里补一句就能应付。最后一个技巧是关于验证的把每个接口的请求参数、返回结果和异常情况整理成一张测试表格放在论文附录里不用写自动化测试手填十组数据就行但一定要真实——老师会随机挑一组让你现场演示。我自己的习惯是绕开那些花哨的复杂设计把状态机、事务边界和登录态这几件事讲透比堆一堆模糊的高并发方案更让人信服。这套思路不止适用于图书捐赠系统换成失物招领、二手交易、闲置交换都是同一套骨架。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?