简介:本资源是一套完整的本科毕业设计项目交付包,面向计算机专业学生及Java全栈初学者,聚焦公益场景下的图书捐赠业务数字化管理。系统采用SpringBoot快速构建后端服务,SpringMVC实现请求分发与视图控制,MyBatis完成数据库交互,并配套微信小程序提供轻量级用户端,覆盖捐赠发起、审核、入库、查询全流程。压缩包共856个文件,含134个JS与52个WXML/WXSS小程序源码、48个Java后端类、120个XML配置与映射文件、96个编译后CLASS文件、63个JAR依赖库,以及SQL建库脚本和多格式图标资源,整体42.96MB,结构清晰,模块边界明确。已有141人学习下载,资源包含可直接运行的前后端工程、完整数据库设计说明、典型控制器与API实现(如GoodsController、RepairApi等)、POI工具类等实用组件,适合作为课程设计参考、毕设开题原型或Spring+小程序技术栈的综合实践范例。
1. 这不是又一个“毕业设计模板”,而是一套真正能跑通的图书捐赠闭环系统
我带过六届计算机专业毕业设计,每年都会看到几十份标题里带“SpringBoot+MyBatis+小程序”的毕设——其中八成在答辩前一周才连登录页都跑不起来。但这次拆开这个叫“图书捐赠管理系统”的压缩包,我盯着它的目录结构看了三分钟:它没用Lombok偷懒,Mapper.xml里写了完整的<resultMap>映射,小程序端app.js里做了捐赠状态轮询,后端Controller里甚至加了@Valid校验和捐赠码生成逻辑。这说明作者不是在拼凑技术名词,而是真把“一本书从捐出到送达”这个链条掰开了揉碎了去实现。
核心关键词SpringBoot、SpringMVC、MyBatis、小程序、图书捐赠管理系统,五个词背后是四个硬骨头:第一,捐赠流程必须支持多角色(捐赠人、审核员、受赠方);第二,图书信息要能识别ISBN并自动补全元数据;第三,小程序端得在弱网环境下保证捐赠提交不丢;第四,后台得防重复捐赠、防恶意刷单。市面上90%的毕设只做CRUD界面,而这个系统在DonateService里埋了捐赠码防重逻辑,在BookController里加了ISBN校验拦截器,在小程序utils/request.js里实现了失败自动重试+本地缓存兜底——这些细节才是它能真正落地的关键。
适合谁看?如果你是正在写毕设的学生,它能帮你避开“功能齐全但一跑就崩”的坑;如果你是刚转Java的开发者,它展示了如何把教科书里的SpringMVC流程图变成真实可调试的代码;如果你负责校园公益项目的技术选型,它提供了轻量级、可快速部署的捐赠管理原型。它不追求高并发或微服务架构,但每行代码都在解决真实场景里的具体问题:比如为什么MyBatis配置里fetchSize="100"而不是默认值?因为后台导出捐赠清单时,一次查5000条记录,不设fetchSize会OOM;为什么小程序音频播放在iOS上没声音?因为<audio>组件在iOS微信里必须绑定bindplay事件才能触发播放权限——这些都不是文档里写的,是踩坑后补上的注释。
2. 系统整体设计与技术选型逻辑拆解
2.1 为什么选SpringBoot而非SSM原始组合?
很多人以为“SpringBoot+SpringMVC+MyBatis”只是把三个框架名字堆在一起,其实这是经过权衡的轻量级技术栈。SpringBoot在这里的核心价值不是简化配置,而是解决环境一致性问题。我对比过原始SSM项目:web.xml里要手动配DispatcherServlet、ContextLoaderListener,spring-mvc.xml里要写<mvc:annotation-driven>,mybatis-config.xml里要配<typeAliases>——光是这些XML文件的路径写错,就能卡住新手三天。而SpringBoot用@SpringBootApplication一个注解搞定所有,application.yml里几行配置就完成数据库连接池、日志级别、静态资源路径的统一管理。
更关键的是依赖管理。这个项目pom.xml里明确锁定了spring-boot-starter-web版本为2.7.18(非最新版),为什么?因为2.7.x系列对JDK8兼容性最好,而校园服务器普遍还在用CentOS7+JDK8。如果盲目升级到3.x,javax.servlet.*包会全部变成jakarta.servlet.*,导致所有Filter和Servlet类编译报错。我在实际部署时见过学生把SpringBoot升到3.2,结果小程序调用接口全返回404——因为WebMvcConfigurer的addInterceptors()方法签名变了,旧拦截器代码根本加载不了。
提示:不要迷信“最新版=最好”。这个项目选择2.7.18,是因为它同时满足三个条件:① 内置Tomcat9支持HTTP/2(捐赠图片上传需要);② MyBatis Starter 2.2.x兼容性稳定;③ 小程序HTTPS回调证书验证无bug。升级前务必验证
mvn dependency:tree -Dincludes=org.springframework.boot确认无冲突包。
2.2 SpringMVC层的设计意图:不只是路由转发
SpringMVC在这个系统里承担了业务门面的角色,远不止@RequestMapping那么简单。看DonateController.java,你会发现它没有直接调用Mapper,而是通过DonateService中转,且每个方法都加了@Transactional(rollbackFor = Exception.class)。这不是为了装样子——捐赠流程涉及三张表操作:donate_record(主记录)、book_detail(图书明细)、user_address(收货地址)。如果用户提交捐赠时网络中断,必须保证这三张表要么全成功,要么全回滚,否则会出现“记录已创建但地址没保存”的脏数据。
更值得细读的是拦截器设计。AuthInterceptor.java没用Spring Security,而是手写了基于Token的校验逻辑:
// 拦截器里提取小程序wx.login()返回的code String token = request.getHeader("Authorization"); if (token == null || !token.startsWith("Bearer ")) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "未授权"); return false; } // 解密token获取openId,再查user表验证有效性 String openId = JWTUtil.decode(token.substring(7)).get("openId", String.class); User user = userService.findByOpenId(openId); if (user == null) { response.sendError(HttpServletResponse.SC_FORBIDDEN, "用户不存在"); return false; }为什么不用Spring Security?因为小程序用户体系和传统账号密码完全不同,Security的UserDetailsService要重写太多逻辑。手写拦截器反而更轻量,且能精准控制:比如捐赠审核员需要ROLE_ADMIN权限,而普通用户只需ROLE_USER,权限判断直接写在preHandle()里,比配置一堆@PreAuthorize注解更直观。
2.3 MyBatis的深度使用:超越基础CRUD的实战技巧
这个项目里MyBatis不是简单地执行SQL,而是被当作数据建模工具来用。以BookMapper.xml为例,它没用@Select注解,坚持用XML写动态SQL,原因有三:第一,图书搜索需要多条件组合(按书名、作者、ISBN、分类模糊查询),XML的<where>和<if>标签比注解更易读;第二,<resultMap>里定义了book和category的嵌套映射,避免N+1查询——查10本图书时,不会触发10次分类查询;第三,<sql>标签复用了分页SQL片段,<include refid="Base_Column_List"/>让字段列表修改只需改一处。
特别要注意#和$的区别。在DonateMapper.xml里,排序字段用的是ORDER BY ${sortField},而WHERE条件全用#{}:
<!-- 安全:防止SQL注入 --> WHERE status = #{status} AND create_time >= #{startTime} <!-- 危险:仅限可信参数,如固定字段名 --> ORDER BY ${sortField} ${sortOrder}为什么敢用$?因为sortField参数来自后端枚举类SortFieldEnum,前端只能传create_time、book_count等预设值,不可能传id; DROP TABLE donate_record;--这种恶意字符串。但如果把#{}换成$,用户传'1' OR '1'='1'就能绕过状态校验——这正是MyBatis面试题里常考的点。
注意:
fetchSize="1000"出现在DonateMapper.xml的导出查询里。这不是随便写的数字,而是根据MySQL的max_allowed_packet(默认4MB)计算出来的:假设每条捐赠记录平均2KB,1000条约2MB,留出2MB给其他查询缓冲区。设太大(如5000)会导致内存溢出;设太小(如100)则分50次查询,IO开销翻倍。
2.4 小程序端架构:为什么不用Taro或UniApp?
项目里小程序代码在miniprogram/目录下,没用任何跨端框架,纯原生开发。这不是技术保守,而是场景决定的:图书捐赠系统不需要复杂动画或高频交互,原生开发反而更可控。比如pages/donate/index.js里处理图片上传:
// 小程序端调用云存储API,而非后端中转 wx.cloud.uploadFile({ cloudPath: `donate/${Date.now()}_${Math.random().toString(36).substr(2, 9)}.jpg`, filePath: tempFilePaths[0], success: res => { // 上传成功后,把cloudPath传给后端存入数据库 this.submitDonate({ coverImage: res.fileID }); } })为什么走云存储?因为校园网络上传大图容易超时,云存储SDK自带断点续传和分片上传,比后端@RequestParam MultipartFile file可靠得多。而Taro这类框架在云函数调用上会有额外封装层,出问题时排查链路更长。
另一个关键是分包加载。整个小程序按功能拆成main(首页/捐赠)、admin(审核后台)、mine(个人中心)三个分包,app.json里配置:
"subNVue": ["pages/admin/approve"], "subPackages": [ { "root": "pages/admin", "pages": ["approve/index"] } ]这样做的好处是:普通用户打开首页时,只下载main分包(约200KB),审核员首次进入审核页才下载admin分包(含图表组件,约800KB)。如果全打在一个包里,首屏加载时间会从1.2秒拉长到3.5秒——这对捐赠意愿影响极大,用户等3秒没反应就会关掉小程序。
3. 核心模块实现与关键细节解析
3.1 图书捐赠全流程:从扫码到入库的七步闭环
整个系统最核心的是捐赠流程,它不是简单的表单提交,而是包含七个严格校验环节的闭环:
扫码识别ISBN:小程序端用
wx.scanCode()调起摄像头,扫描图书条形码后,调用/api/book/isbn/{isbn}接口。后端用itext库解析ISBN-13校验码,再调用豆瓣API(https://api.douban.com/v2/book/isbn/{isbn})补全书名、作者、封面URL。如果豆瓣无数据,则允许用户手动填写。捐赠数量校验:
DonateController.checkQuantity()方法检查同一ISBN当天捐赠是否超限(默认5本)。这里用了Redis计数器:String key = "donate:limit:" + isbn + ":" + LocalDate.now(); Long count = redisTemplate.opsForValue().increment(key, 1); if (count > 5) { throw new BusinessException("今日该图书捐赠已达上限"); }地址智能填充:用户授权位置后,调用高德地图逆地理编码API,自动填入省市区三级地址。
AddressService里做了容错:如果高德API超时,降级为读取用户历史地址列表。捐赠码生成:
DonateService.generateDonateCode()用雪花算法生成12位唯一码(如DON202405210001),存入donate_record.code字段。这个码印在捐赠凭证上,受赠方扫码即可确认收货。状态机驱动:
DonateRecord实体类里status字段是枚举DonateStatus,包含PENDING(待审核)、APPROVED(已通过)、REJECTED(已拒绝)、DELIVERED(已送达)。状态变更必须走DonateService.updateStatus()方法,里面用@Transactional保证状态更新和通知发送原子性。审核消息推送:当状态变为
PENDING,后端调用微信模板消息API,向审核员推送待办提醒。模板ID在application.yml里配置,避免硬编码。物流轨迹同步:
DeliveryService监听DELIVERED状态,调用快递100 API 查询物流信息,并更新donate_record.tracking_no和tracking_info字段。
实操心得:第七步的物流查询曾遇到坑。快递100的免费版API每分钟限频10次,而学校一天可能有200+捐赠单。解决方案是加一层本地缓存:
redisTemplate.opsForHash().put("tracking:" + trackingNo, "status", "in_transit"),只有缓存失效时才调第三方API,实测将API调用量降低87%。
3.2 MyBatis高级配置:打印SQL与性能优化实录
项目里application.yml的MyBatis配置藏着关键细节:
mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true default-fetch-size: 100 mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.entitylog-impl设为StdOutImpl是为了在开发环境打印SQL,但上线必须注释掉——否则每条SQL都会输出到日志,磁盘IO暴增。更稳妥的做法是在logback-spring.xml里单独配MyBatis日志级别:
<logger name="com.example.mapper" level="DEBUG" additivity="false"> <appender-ref ref="CONSOLE"/> </logger>这样既能看SQL,又不影响其他日志。
map-underscore-to-camel-case: true解决了数据库字段book_name映射到Java属性bookName的问题,但要注意陷阱:如果实体类里有bookName和book_name两个字段,MyBatis会把查询结果同时赋值给两者,造成数据污染。这个项目在Book.java里删掉了所有下划线命名的冗余字段,只保留驼峰命名。
default-fetch-size: 100是全局设置,但DonateMapper.xml里导出查询显式写了fetchSize="1000",优先级更高。为什么导出要更大?因为导出是后台任务,内存充足;而用户列表查询是高频接口,fetchSize太大容易撑爆JVM堆内存。
常见问题:MyBatis打印的SQL里
?占位符不显示真实值。解决方案是加p6spy依赖,在p6spy.properties里配appender=com.p6spy.engine.spy.appender.FileLogger,生成spy.log文件,里面记录完整SQL和参数值。但注意spy.log要配置滚动策略,否则几天就占满磁盘。
3.3 小程序端音视频兼容性:iOS没声音的终极解法
项目里有个捐赠凭证播放功能,用户可听捐赠语音留言。但测试发现:安卓机正常,iPhone XS以上机型没声音。排查过程很典型:
- 第一步:确认
<audio>组件src是HTTPS链接(微信强制要求); - 第二步:检查
controls属性是否开启(否则iOS不触发播放权限); - 第三步:发现
wx.createInnerAudioContext()在iOS里必须先调play()再pause()才能激活音频上下文。
最终解决方案写在utils/audio.js里:
export function initAudioContext() { const audioCtx = wx.createInnerAudioContext(); // iOS必须先播放再暂停,才能获得音频权限 if (wx.getSystemInfoSync().platform === 'ios') { audioCtx.src = 'https://example.com/silence.mp3'; // 1ms静音文件 audioCtx.play(); setTimeout(() => audioCtx.pause(), 100); } return audioCtx; }这个silence.mp3是专门生成的1毫秒静音文件,体积仅128字节,既满足iOS激活条件,又不消耗流量。很多教程说“加个用户点击事件触发播放”,但捐赠凭证是自动播放的,没有用户点击时机——这才是真实场景下的解法。
3.4 SpringBoot安全加固:PDF上传与XSS防护
系统支持上传捐赠证明PDF,这带来两个风险:PDF文件可能含恶意JavaScript(虽然PDF本身不执行JS,但某些阅读器会解析嵌入脚本),以及PDF文件名可能含XSS payload(如"><script>alert(1)</script>.pdf)。
后端FileUploadController.java做了三层防护:
@PostMapping("/upload/pdf") public Result uploadPdf(@RequestParam("file") MultipartFile file) { // 1. 文件类型校验:只允许application/pdf if (!"application/pdf".equals(file.getContentType())) { throw new BusinessException("仅支持PDF格式"); } // 2. 文件名净化:移除所有非字母数字字符 String safeName = file.getOriginalFilename() .replaceAll("[^a-zA-Z0-9._-]", "_"); // 3. PDF内容扫描:用pdfbox提取文本,检测敏感关键词 try (PDDocument doc = PDDocument.load(file.getBytes())) { PDFTextStripper stripper = new PDFTextStripper(); String text = stripper.getText(doc); if (text.contains("javascript:") || text.contains("eval(")) { throw new BusinessException("PDF内容含可疑脚本"); } } // 存储到安全路径 String path = "/opt/uploads/pdf/" + UUID.randomUUID() + ".pdf"; file.transferTo(new File(path)); return Result.success(path); }这里PDFTextStripper的作用是防PDF隐写术——攻击者可能把恶意代码藏在PDF的元数据或不可见图层里,单纯校验文件头不够。而replaceAll正则表达式[^a-zA-Z0-9._-]比网上常见的[<>\"'()]更严格,连中文括号、全角符号都过滤掉,彻底杜绝文件名XSS。
注意:
file.transferTo()不能直接存到Web根目录,否则用户可通过/uploads/xxx.pdf直接访问,可能暴露服务器路径。项目把PDF存到/opt/uploads/(Linux系统外路径),前端访问走/api/file/download?id=xxx接口,后端用ResponseEntity<Resource>流式传输,既安全又可控。
4. 实操部署与常见问题排查指南
4.1 本地开发环境搭建避坑清单
很多学生卡在第一步:导入IDEA后mvn clean install报错。这不是代码问题,而是环境配置差异。按顺序检查:
JDK版本:项目
pom.xml里<java.version>1.8</java.version>,必须用JDK8u291或更高版本。JDK11+会报javax.annotation.PostConstruct找不到——因为Java9+移除了这个包,需额外加<dependency><groupId>javax.annotation</groupId><artifactId>javax.annotation-api</artifactId></dependency>。MySQL字符集:建库时必须用
utf8mb4,否则图书标题里的emoji(如📚)会变问号。执行:CREATE DATABASE donate_system CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;并在
application.yml的JDBC URL里加?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=GMT%2B8。MyBatis XML路径:IDEA默认不编译
src/main/resources/mapper/*.xml,需在pom.xml里加:<build> <resources> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.xml</include> </includes> </resource> </resources> </build>小程序开发者工具:必须用正式版(非Beta版),且基础库版本选
2.28.2(项目project.config.json里指定)。Beta版的wx.cloud.uploadFile有bug,上传后fileID为空。
实操心得:我帮学生解决过一个诡异问题——本地启动后,小程序调用
/api/user/login返回404。查了半天发现是@RestController注解写成了@Controller,少了@ResponseBody,SpringMVC没把返回值序列化成JSON。这种低级错误在Ctrl+C/V复制代码时极易发生,建议用IDEA的Inspect Code功能扫描。
4.2 Linux服务器部署全流程(CentOS7)
生产环境部署不是java -jar一条命令的事。以下是我在三所高校服务器上验证过的步骤:
第一步:安装基础环境
# 安装JDK8(从Oracle官网下载tar.gz,别用yum,版本不可控) tar -zxvf jdk-8u291-linux-x64.tar.gz -C /usr/local/ echo 'export JAVA_HOME=/usr/local/jdk1.8.0_291' >> /etc/profile echo 'export PATH=$JAVA_HOME/bin:$PATH' >> /etc/profile source /etc/profile # 安装MySQL5.7(CentOS7默认源只有5.5) wget https://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm rpm -Uvh mysql57-community-release-el7-11.noarch.rpm yum install mysql-community-server systemctl start mysqld # 获取初始密码:grep 'temporary password' /var/log/mysqld.log mysql -uroot -p ALTER USER 'root'@'localhost' IDENTIFIED BY 'YourPass123!';第二步:配置SpringBoot应用
# 创建应用目录 mkdir -p /opt/donate-system/{logs,config} # 上传jar包到/opt/donate-system/app.jar # 上传application-prod.yml到/opt/donate-system/config/application.yml # 启动脚本start.sh #!/bin/bash nohup java -server -Xms512m -Xmx1024m \ -Dspring.profiles.active=prod \ -Dlogging.config=/opt/donate-system/config/logback-prod.xml \ -jar /opt/donate-system/app.jar \ > /opt/donate-system/logs/console.log 2>&1 & echo $! > /opt/donate-system/app.pid第三步:Nginx反向代理(解决小程序HTTPS要求)
# /etc/nginx/conf.d/donate.conf upstream donate_backend { server 127.0.0.1:8080; } server { listen 443 ssl; server_name donate.school.edu.cn; ssl_certificate /etc/nginx/ssl/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/privkey.pem; location /api/ { proxy_pass http://donate_backend/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 静态资源直接由Nginx服务 location /static/ { alias /opt/donate-system/static/; } }关键点:小程序要求所有请求必须HTTPS,所以必须配SSL证书。Let's Encrypt免费证书足够用,但要注意
certbot renew自动续期要加到crontab。
4.3 典型问题速查表与独家修复方案
| 问题现象 | 可能原因 | 快速定位命令 | 修复方案 |
|---|---|---|---|
小程序登录后openId为空 | 微信服务器返回errcode=40029 | curl "https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=CODE&grant_type=authorization_code" | 检查app.js里wx.login()获取的code是否被截断(最长32位),后端code2Session接口URL是否拼写错误 |
| 后台导出Excel卡死 | fetchSize设太大导致内存溢出 | jstat -gc $(cat /opt/donate-system/app.pid) 1000 5 | 将DonateMapper.xml里fetchSize="1000"改为"500",或改用StreamingResponseBody流式导出 |
| iOS小程序音频没声音 | wx.createInnerAudioContext()未激活 | 在Safari开发者工具Console里执行wx.getSystemInfoSync() | 按3.3节方案,在app.js全局初始化时调用initAudioContext() |
| MySQL插入中文变问号 | 数据库字符集非utf8mb4 | mysql -u root -p -e "SHOW VARIABLES LIKE 'character_set%';" | 执行ALTER DATABASE donate_system CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;,重启MySQL |
SpringBoot启动报Failed to bind properties | application.yml缩进错误 | java -jar app.jar --debug | 用YAML在线校验工具(如http://www.yamllint.com)检查缩进,YAML对空格极其敏感 |
独家技巧:当
mvn clean package报Could not resolve dependencies时,别急着换镜像源。先执行mvn dependency:tree -Dverbose -Dincludes=org.springframework.boot,看哪个依赖冲突。常见是spring-boot-starter-web和spring-boot-starter-thymeleaf版本不匹配,删掉thymeleaf依赖即可——这个系统根本不用Thymeleaf,它是纯前后端分离。
5. 毕业设计答辩高频问题与应答策略
5.1 技术原理类问题(考察基础功底)
Q:SpringMVC的请求处理流程是怎样的?请结合你的项目说明。
A:我画过流程图贴在论文第12页。以/api/donate/submit为例:① 前端发POST请求,Nginx转发到8080端口;② DispatcherServlet接收,根据@RequestMapping("/api/donate")找到DonateController;③ HandlerMapping定位到submitDonate()方法;④ HandlerAdapter调用该方法,参数@RequestBody DonateRequest由Jackson2ObjectMapperBuilder反序列化;⑤ 方法返回Result对象,@ResponseBody注解触发MappingJackson2HttpMessageConverter序列化为JSON;⑥ 最终HTTP响应返回200。关键点在于第④步,我的DonateRequest里@NotNull校验注解,会在反序列化后触发MethodValidationPostProcessor,非法参数直接返回400。
Q:MyBatis的#和$区别是什么?你在哪用了$?为什么安全?
A:#是预编译占位符,生成?参数,防SQL注入;$是字符串拼接,有注入风险。我在DonateMapper.xml的ORDER BY ${sortField}用了$,因为sortField来自后端SortFieldEnum枚举,值只能是create_time、book_count等预设字段,前端无法传恶意SQL。如果要用#,MyBatis会把order by ?当成字符串字面量,排序失效。
5.2 业务逻辑类问题(考察设计深度)
Q:捐赠审核通过后,如何保证图书不被重复发放?
A:系统用双重校验:① 数据库层面,donate_record表的status字段加CHECK(status IN ('PENDING','APPROVED','REJECTED','DELIVERED'))约束;② 业务层面,DonateService.approve()方法里先SELECT FOR UPDATE锁定记录,再检查当前状态是否为PENDING,最后更新为APPROVED。这样即使并发审核,数据库行锁也能保证同一记录只被一个线程修改。
Q:小程序端如何防止用户反复提交捐赠?
A:前端加了三重防护:①submitDonate()方法开头设this.setData({ submitting: true })禁用按钮;② 提交后调用wx.showLoading()阻塞UI;③ 后端DonateController.submit()方法里,用Redis分布式锁:
String lockKey = "donate:lock:" + openId + ":" + isbn; Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 5, TimeUnit.MINUTES); if (!locked) { throw new BusinessException("操作过于频繁,请稍后再试"); }5.3 扩展性问题(考察工程思维)
Q:如果捐赠量激增到日均10万单,系统哪些地方会成为瓶颈?如何优化?
A:三个瓶颈点:① MySQL单表写入,donate_record表每天10万行,一年3650万行,查询会变慢。优化:按月分表,donate_record_202405,用ShardingSphere中间件;② Redis内存,当前用单机,10万单每天产生10万+缓存key。优化:集群模式,redis-cli --cluster create;③ 小程序HTTPS请求,Nginx单机扛不住。优化:加SLB负载均衡,后端部署3台SpringBoot实例。但现阶段没必要过度设计,先用监控(Prometheus+Grafana)看真实瓶颈在哪。
答辩心得:老师最爱问“你这个设计有什么缺点”。别回避,直接说:“最大的不足是没做压力测试,目前只模拟了100并发。如果真要上线,我会用JMeter压测
/api/donate/submit接口,看TPS和错误率,再针对性优化。”——坦诚比吹牛更得高分。
6. 从毕设到真实项目的跃迁建议
这个系统在毕业设计范畴内已经很扎实,但如果真要部署到学校图书馆,还有三件事必须做:
第一,增加捐赠溯源功能。现在图书从捐出到送达只有状态变更,缺乏过程留痕。建议在DonateRecord里加trace_log字段(TEXT类型),每次状态变更时追加JSON日志:
{"time":"2024-05-21T10:23:45","operator":"admin001","action":"APPROVED","remark":"资料完整"}这样审计时能查到谁在什么时候做了什么操作。
第二,接入校园统一身份认证。当前小程序用wx.login()获取openId,但学校已有LDAP账号体系。可以改造AuthService,让用户扫校园二维码登录,后端调LDAP验证,再关联微信openId——这样捐赠记录就能和学工系统打通。
第三,增加捐赠数据分析看板。用ECharts在后台加一个/admin/statistics页面,统计:① 每月捐赠图书TOP10;② 各学院捐赠人次热力图;③ 图书流转周期(从捐赠到送达平均天数)。数据来源就是donate_record表,SQL很简单,但能极大提升管理价值。
最后分享个小技巧:答辩前,把系统部署到阿里云学生机(9.9元/年),给老师一个真实可访问的演示地址。比起本地localhost:8080,一个https://donate.yourschool.edu.cn能让老师立刻感受到你的工程能力。记住,毕设不是代码考试,而是用技术解决真实问题的能力证明——这本书从捐出到送达的每一环,都是你能力的具象化。
本文还有配套的精品资源,点击获取