校园失物招领系统:SpringBoot+Vue前后端分离实战 简介这是一套面向高校计算机专业毕业生的高分毕业设计项目源码基于Spring Boot与Vue.js实现前后端分离架构专为解决校园场景下失物信息登记、检索、认领与管理等实际问题而开发。资源包含完整可运行系统涵盖后端Java核心模块69个.java文件、MyBatis映射配置13个.xml、前端静态资源9张图片、数据库脚本1个.sql及基础配置文件2个.yml共97个文件压缩包仅973KB轻量易部署。已有5203人学习下载说明其结构清晰、代码规范、实用性广受认可。读者可直接导入IDE与数据库运行获得含用户角色权限、失物发布审核、图文详情展示、搜索与状态跟踪等功能的完整闭环系统同时便于理解主流Java全栈开发流程与校园信息化系统设计逻辑。1. 项目概述为什么一个校园失物招领系统值得花三个月打磨我带过六届毕业设计每年都会遇到学生问“老师做个XX系统能过吗”——“XX”里出现频率最高的就是“校园失物招领”。听起来简单但真把它做成一个能跑通、能演示、能答辩、还能被辅导员拿去试用的系统远比写个“用户登录物品发布列表展示”的三页Demo难得多。它不是功能堆砌而是一次对工程落地能力的真实压力测试前端要响应快、交互顺、适配手机后端要稳、要安全、要可扩展数据库不能只建几张表就完事得考虑并发查询、图片存储、状态流转、数据一致性部署不能只在本地localhost:8080跑起来就算成功得能在学校那台老服务器上扛住几十人同时刷页面。这个标题里的关键词——SpringBoot Vue 前后端分离 源码 数据库——每一个都不是装饰词。SpringBoot不是为了凑热门框架而是因为它能让你在三天内搭好一套具备日志、监控、配置中心雏形的后端骨架省下两周手动配Tomcat、MyBatis、Logback的时间Vue不是为了赶时髦而是因为失物招领这种高频操作拍照上传、实时刷新、分类筛选必须依赖响应式视图和组件化开发jQuery写出来的页面在iPhone上点三次才触发一次事件答辩时绝对翻车前后端分离不是概念炫技是硬性要求——你得让前端同学独立开发调试后端同学专注接口逻辑和数据库优化两人不用为“为什么我改了JS他那边报404”扯皮一整天源码和数据库意味着它必须是可运行、可验证、可追溯的完整体不是截图拼凑的PPT不是导出SQL却缺外键约束的半成品。我去年指导的一个学生最初只想做个“发帖看帖”的静态页面结果在第三周卡在“失物认领后如何确保只有失主和管理员能修改状态且修改记录可查”这个问题上。他查了三天文档最后发现光靠前端按钮禁用根本没用必须后端做状态机校验操作审计日志。这件事让我意识到校园场景看似简单实则暗藏大量业务规则细节——捡到饭卡和捡到笔记本的处理流程不同教学楼A栋和宿舍3号楼的失物领取点不同毕业生离校前的集中招领和日常零散招领的权限策略也不同。这些才是毕业设计真正该训练的能力把模糊的现实需求翻译成精确的代码逻辑和数据结构。所以这篇内容不讲“怎么装Node.js”也不列“SpringBoot十大注解”而是带你从真实校园痛点出发拆解这个系统每一层的设计决策、踩过的坑、以及为什么非得这么写。2. 整体架构设计与技术选型逻辑2.1 为什么必须是前后端分离单体架构在这里行不通很多学生第一反应是“用JSPServlet写个传统Web应用”毕竟课程设计里这么干过。但放到失物招领这个场景单体架构会立刻暴露出三个致命短板开发协作效率低前端同学想加个“按楼层筛选”的下拉框得等后端把JSP页面传给他改完再传回去编译。而前后端分离后前端直接调用/api/lost-items?buildingA后端只需保证这个接口返回JSON双方并行开发互不阻塞。用户体验断层传统页面每次筛选都要整页刷新用户刚点开一张高清失物照片切到“已认领”标签页照片就没了得重新加载。Vue的路由懒加载组件缓存keep-alive能让用户在“待认领”和“历史记录”间秒切图片、筛选条件全保留这才是移动端友好的体验。部署与维护僵化学校信息中心只给一台Linux服务器但要求“前端静态资源走CDN加速后端API走内网”。单体应用打包成WAR包所有东西捆在一起根本没法拆。而分离架构下Vue打包生成的dist文件夹直接扔到Nginx静态目录SpringBoot JAR包单独运行运维同学可以分别升级、回滚、扩缩容。提示答辩时老师如果问“为什么不用Thymeleaf”你可以直接回答“因为Thymeleaf渲染在服务端无法实现SPA级别的局部刷新和复杂状态管理而失物招领需要高频交互如实时点赞、地图定位、多图轮播这是用户体验的硬性门槛。”2.2 SpringBoot版本选择2.7.x是当前最稳妥的平衡点网络热词里有“springboot版本太高”、“springboot 4 源码”这恰恰说明版本陷阱是真实存在的。我实测过多个版本SpringBoot 3.x基于Java 17强制使用Jakarta EE 9包名从javax.*变成jakarta.*但学校机房的JDK普遍还是8或11强行升级会导致Tomcat兼容问题且MyBatis-Plus 3.5.x对Jakarta支持不完善连基础CRUD都报错。SpringBoot 2.6.x默认开启spring-boot-starter-validation的严格模式导致NotBlank校验在空字符串时抛异常而非返回友好提示学生调试三天找不到原因。SpringBoot 2.7.x推荐稳定支持Java 8/11MyBatis-Plus 3.5.3完美兼容内置HikariCP连接池默认配置合理maximumPoolSize20对MySQL 5.7/8.0双兼容且Actuator健康检查接口路径统一/actuator/health方便后续加监控。关键参数配置示例application.ymlspring: datasource: url: jdbc:mysql://localhost:3306/lost_found?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver # HikariCP连接池核心调优针对校园小并发场景 hikari: maximum-pool-size: 15 # 学校服务器内存有限设为15比默认20更稳 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000注意allowPublicKeyRetrievaltrue是MySQL 8.0必需参数否则连接报错“Public Key Retrieval is not allowed”。这个细节90%的学生会在部署时栽跟头。2.3 Vue选型Vue 2.7是毕业设计的“黄金版本”热词里有“vue入门”、“vue安装及环境配置”但实际选型不能只看教程多。Vue 3 Composition API虽新但生态插件尤其PDF导出、地图组件成熟度不如Vue 2。我们最终锁定Vue 2.7.14最后一个Vue 2版本兼容Options API且支持部分Composition特性理由很实在Element UI生态成熟el-table的exportExcel方法、el-upload的before-upload钩子、el-date-picker的范围选择都是失物招领刚需Vue 3的Element Plus在2023年仍存在v-model绑定bug。构建工具链稳定Vue CLI 4.5.x webpack 4比Vue CLI 5webpack 5对老旧Windows电脑兼容性更好学生用i5笔记本编译node_modules不会卡死。学习成本可控学生已有HTML/CSS/JS基础直接上Vue 2的data()、methods、computed概念比理解ref()、reactive()、setup()组合式API快得多。项目脚手架命令确保全局安装Vue CLI 4# 全局安装指定版本CLI避免自动升级到v5 npm install -g vue/cli4.5.15 # 创建项目选择Manually select features勾选Babel、Router、Vuex、CSS Pre-processors vue create lost-found-vue cd lost-found-vue # 安装Element UI注意Vue 2必须用element-ui不是element-plus npm install element-ui2.15.14 # 安装axios统一管理API请求 npm install axios0.21.42.4 数据库设计不是建表而是建“业务状态流”热词里反复出现“数据库增删改查”、“数据库课程设计”但失物招领的数据库难点不在CRUD而在状态驱动的数据关系。我们没用ER图工具画一堆虚线箭头而是先梳理业务主干失物发布→lost_item表含status字段0-待审核1-待认领2-已认领3-已归还认领申请→claim_apply表关联lost_item_id和user_id状态0-待处理1-已同意2-已拒绝归还确认→return_record表由管理员操作记录归还时间、领取人、经办人关键设计决策物理删除 vs 逻辑删除lost_item表加is_deleted字段TINYINT软删除。因为失物可能被重复发布同一张饭卡被不同人捡到硬删除会导致历史认领记录关联失效。图片存储方案不存二进制大字段lost_item表只存image_urlsJSON字符串如[/upload/20231001/abc.jpg,/upload/20231001/def.png]文件实际存服务器/var/www/lost-found/upload/目录。这样数据库轻量备份快且便于Nginx直接代理图片请求。索引优化重点lost_item(status, created_time)联合索引——首页按状态筛选如只查“待认领”再按时间倒序避免全表扫描。-- 失物主表精简核心字段 CREATE TABLE lost_item ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键ID, title varchar(100) NOT NULL COMMENT 物品标题, description text COMMENT 详细描述, status tinyint NOT NULL DEFAULT 0 COMMENT 状态0-待审核1-待认领2-已认领3-已归还, image_urls json DEFAULT NULL COMMENT 图片URL数组, building varchar(20) DEFAULT NULL COMMENT 所在楼宇, floor varchar(10) DEFAULT NULL COMMENT 所在楼层, created_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, updated_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, is_deleted tinyint NOT NULL DEFAULT 0 COMMENT 逻辑删除0-未删1-已删, PRIMARY KEY (id), KEY idx_status_time (status,created_time) USING BTREE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT失物信息表;3. 核心模块实现与关键代码解析3.1 后端状态机驱动的业务逻辑以“认领申请”为例很多学生把“用户点击认领按钮”直接写成update lost_item set status2 where id?这是典型错误。真实流程是用户申请→管理员审核→状态变更→通知双方。必须用事务状态校验兜底。SpringBoot Controller层LostItemController.javaPostMapping(/claim/{itemId}) public Result claimItem(PathVariable Long itemId, RequestBody ClaimApplyDTO dto) { // 1. 参数校验非空、用户登录态 if (dto.getUserId() null || dto.getUserId() 0) { return Result.fail(用户ID不能为空); } // 2. 业务校验只能对待认领状态的物品申请 LostItem item lostItemService.getById(itemId); if (item null || item.getStatus() ! 1 || item.getIsDeleted() 1) { return Result.fail(物品不存在或不可认领); } // 3. 调用Service层处理含事务 boolean success claimApplyService.applyForClaim(itemId, dto.getUserId(), dto.getContact()); return success ? Result.success(申请已提交) : Result.fail(申请失败请重试); }Service层核心逻辑ClaimApplyService.javaTransactional(rollbackFor Exception.class) public boolean applyForClaim(Long itemId, Long userId, String contact) { // 查询当前物品状态防止并发下状态被其他请求修改 LostItem item lostItemMapper.selectById(itemId); if (item.getStatus() ! 1) { throw new BusinessException(物品状态已变更无法申请); } // 插入认领申请记录 ClaimApply apply new ClaimApply(); apply.setLostItemId(itemId); apply.setUserId(userId); apply.setContact(contact); apply.setStatus(0); // 待处理 apply.setCreateTime(new Date()); claimApplyMapper.insert(apply); // 更新物品状态为已申请注意不是直接变2而是进入中间态 // 这里用乐观锁避免ABA问题version字段1 LostItem updateItem new LostItem(); updateItem.setId(itemId); updateItem.setStatus(1); // 保持1表示有申请待审核 updateItem.setVersion(item.getVersion() 1); int rows lostItemMapper.updateById(updateItem); if (rows 0) { throw new BusinessException(更新失败请稍后重试); } return true; }实操心得Transactional必须加在Service方法上Controller层只做参数校验和结果包装。我见过太多学生把Transactional加在Controller上导致事务不生效——因为Spring AOP代理的是Bean对象Controller默认是单例代理失效。3.2 前端Vue组件化实现“动态表单图片上传”失物发布页是用户第一触点体验差直接劝退。Vue组件LostPublish.vue需解决三个痛点多图上传预览用el-upload的on-preview和on-remove实现点击放大、拖拽排序、即时删除。楼宇楼层联动选择“教学楼”后“楼层”下拉框动态加载1-5层选择“宿舍楼”后加载1-12层。用watch监听building变化template el-form :modelform label-width80px el-form-item label所在楼宇 el-select v-modelform.building placeholder请选择 el-option label教学楼A栋 valueA/el-option el-option label宿舍3号楼 value3/el-option /el-select /el-form-item el-form-item label所在楼层 el-select v-modelform.floor placeholder请选择 el-option v-forfloor in floorOptions :keyfloor :labelfloor 层 :valuefloor /el-option /el-select /el-form-item /el-form /template script export default { data() { return { form: { building: , floor: }, floorOptions: [] // 动态楼层选项 } }, watch: { form.building: { handler(newVal) { // 根据楼宇类型设置楼层选项 if (newVal A) { this.floorOptions [1, 2, 3, 4, 5] } else if (newVal 3) { this.floorOptions Array.from({length: 12}, (_, i) (i 1).toString()) } else { this.floorOptions [] } }, immediate: true // 组件初始化时立即执行 } } } /script富文本描述用vue-quill-editorVue 2兼容版但必须处理图片上传——编辑器默认把图片转base64超大图直接撑爆内存。解决方案重写handlers.image调用后端上传接口// 在mounted钩子中 this.$nextTick(() { const quill this.$refs.quillEditor.quill // 替换图片上传handler quill.getModule(toolbar).addHandler(image, () { const input document.createElement(input) input.setAttribute(type, file) input.setAttribute(accept, image/*) input.click() input.onchange async () { const file input.files[0] const formData new FormData() formData.append(file, file) try { const res await this.$http.post(/api/upload/image, formData, { headers: { Content-Type: multipart/form-data } }) const url res.data.url // 后端返回的图片访问路径 const range quill.getSelection() quill.insertEmbed(range.index, image, url) } catch (e) { this.$message.error(图片上传失败) } } }) })3.3 前后端联调跨域与接口规范的实战避坑热词里有“前后端分离项目实战”但90%的联调失败源于两个隐形地雷跨域配置位置错误学生常把CrossOrigin加在Controller类上但生产环境Nginx反向代理后此注解失效。正确做法是在SpringBoot中配置全局CORSConfiguration public class WebConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOrigins(http://localhost:8080, http://192.168.1.100:8080) // 开发环境允许的前端地址 .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowCredentials(true) // 允许携带cookie登录态 .maxAge(3600); } }接口返回格式不统一前端Axios期望{code: 200, data: {}, msg: success}而后端有人直接return list有人return ResponseEntity.ok(list)导致前端到处写res.data.data或res.data混乱不堪。必须定义统一响应体Data AllArgsConstructor NoArgsConstructor public class ResultT { private Integer code; // 200成功500失败 private String msg; private T data; public static T ResultT success(T data) { return new Result(200, success, data); } public static T ResultT fail(String msg) { return new Result(500, msg, null); } }Controller层强制返回ResultGetMapping(/list) public ResultListLostItem listItems(RequestParam Integer status) { ListLostItem list lostItemService.listByStatus(status); return Result.success(list); }注意CrossOrigin和WebMvcConfigurer二选一即可混用会导致CORS头重复浏览器报错。我建议用WebMvcConfigurer因为它是全局配置且可动态读取配置文件中的allowedOrigins。3.4 PDF详情导出前后端分离下的安全实现热词里有“前后端分离详情导出pdf实现步骤”、“springboot解决pdf xss攻击”这需求很真实——辅导员要打印失物清单存档。但直接用jsPDF在前端生成PDF中文乱码、样式错乱用后端iText生成又面临XSS风险用户输入的HTML描述直接渲染进PDF。我们的方案前端生成HTML模板 → 后端渲染为PDF → 浏览器下载全程规避XSS前端用html2canvas截取详情页DOM已过滤危险标签import html2canvas from html2canvas import { saveAs } from file-saver async downloadPDF() { const element this.$refs.detailContainer // 指向详情页DOM const canvas await html2canvas(element, { useCORS: true, // 允许跨域图片 scale: 2 // 提高清晰度 }) const imgData canvas.toDataURL(image/png) // 发送图片base64到后端 const res await this.$http.post(/api/export/pdf, { imageBase64: imgData }) const blob new Blob([res.data], { type: application/pdf }) saveAs(blob, 失物详情_${Date.now()}.pdf) }后端用Flying SaucerXHTMLCSS渲染生成PDF对输入做严格白名单过滤PostMapping(/export/pdf) public void exportPdf(RequestBody MapString, String payload, HttpServletResponse response) throws Exception { String imageBase64 payload.get(imageBase64); // 白名单校验只允许data:image/png;base64开头 if (!imageBase64.startsWith(data:image/png;base64,)) { throw new IllegalArgumentException(非法图片格式); } // 构建安全HTML模板不拼接用户输入只插入已校验的base64 String html htmlbody stylefont-family:SimSun h2失物招领详情/h2 img src imageBase64 stylemax-width:100%/ /body/html; // 渲染PDF ITextRenderer renderer new ITextRenderer(); renderer.setDocumentFromString(html); renderer.layout(); response.setContentType(application/pdf); response.setHeader(Content-Disposition, attachment; filenamedetail.pdf); renderer.createPDF(response.getOutputStream()); }关键安全点绝不将用户输入的description字段直接写入HTML模板而是用Jsoup.clean()过滤后存入数据库PDF只展示已清洗的纯文本。这是答辩时老师最爱问的安全问题。4. 部署与运维从本地开发到校园服务器上线4.1 前端部署Nginx静态资源托管非Node.js热词里有“前后端分离项目部署”但学生常误以为Vue必须用Node.js跑。其实生产环境最佳实践是Nginx托管静态文件 反向代理API。Nginx配置/etc/nginx/conf.d/lost-found.confserver { listen 80; server_name lostfound.school.edu.cn; # 域名若无域名用IP # 前端静态资源 location / { root /var/www/lost-found/dist; # Vue build后的dist目录 try_files $uri $uri/ /index.html; # 支持Vue Router history模式 index index.html; } # API反向代理指向SpringBoot location /api/ { proxy_pass http://127.0.0.1:8080/; # SpringBoot默认端口 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # 图片直传绕过SpringBoot提升性能 location /upload/ { alias /var/www/lost-found/upload/; expires 7d; } }重启Nginxsudo nginx -t sudo systemctl reload nginx注意try_files $uri $uri/ /index.html是关键否则Vue Router的/detail/123路径刷新会404。这个配置必须写在location /块里写在server根节点会失效。4.2 后端部署JAR包systemd服务化SpringBoot打成JAR包后不能直接java -jar app.jar 后台运行必须用systemd管理进程、日志、开机自启。创建服务文件/etc/systemd/system/lost-found.service[Unit] DescriptionLost and Found System Afternetwork.target [Service] Typesimple Userwww-data WorkingDirectory/var/www/lost-found ExecStart/usr/bin/java -Xms256m -Xmx512m -jar /var/www/lost-found/lost-found-backend.jar Restartalways RestartSec10 StandardOutputjournal StandardErrorjournal SyslogIdentifierlost-found [Install] WantedBymulti-user.target启用服务sudo systemctl daemon-reload sudo systemctl enable lost-found.service sudo systemctl start lost-found.service sudo systemctl status lost-found.service # 查看运行状态日志查看替代tail -fsudo journalctl -u lost-found.service -f # 实时日志 sudo journalctl -u lost-found.service --since 2023-10-01 # 查历史日志实操心得-Xms256m -Xmx512m是关键学校服务器内存通常只有2G不设堆内存上限JVM会吃光内存导致MySQL崩掉。Restartalways确保崩溃后自动拉起比nohup可靠十倍。4.3 数据库同步与备份用mysqldump做增量备份热词里有“数据库同步软件”、“数据库同步工具”但校园场景无需复杂同步。我们用最朴素的mysqldump每日备份编写备份脚本/opt/backup/backup_lost_found.sh#!/bin/bash DATE$(date %Y%m%d_%H%M%S) BACKUP_DIR/opt/backup/lost-found DB_NAMElost_found DB_USERroot DB_PASS123456 mkdir -p $BACKUP_DIR mysqldump -u$DB_USER -p$DB_PASS --single-transaction --routines --triggers $DB_NAME $BACKUP_DIR/${DB_NAME}_${DATE}.sql # 保留最近7天备份 find $BACKUP_DIR -name ${DB_NAME}_*.sql -mtime 7 -delete添加定时任务每天凌晨2点# 编辑crontab sudo crontab -e # 添加一行 0 2 * * * /opt/backup/backup_lost_found.sh注意--single-transaction参数对InnoDB表有效能保证备份时数据一致性避免锁表。--routines和--triggers确保存储过程和触发器也被导出这是课程设计答辩加分项。5. 常见问题与排查技巧实录5.1 “页面空白/404”问题速查表这是部署后最高频问题按优先级排查现象可能原因排查命令解决方案Nginx打开首页空白控制台报Failed to load resource: net::ERR_CONNECTION_REFUSEDSpringBoot服务未启动或端口被占sudo systemctl status lost-found.servicesudo lsof -i :8080sudo systemctl restart lost-found.service若端口被占sudo kill -9 PIDNginx打开首页显示404 Not FoundNginx配置的root路径错误或dist目录无index.htmlls -l /var/www/lost-found/dist/sudo nginx -t确认Vue已执行npm run build生成文件在正确路径检查Nginx配置语法点击按钮跳转/detail/123后刷新404Vue Router history模式未配置Nginxcurl http://your-server/detail/123确保Nginxlocation /块中有try_files $uri $uri/ /index.html;图片不显示URL返回404图片路径配置错误或Nginx未配置/upload/别名ls -l /var/www/lost-found/upload/curl http://your-server/upload/abc.jpg检查后端返回的图片URL是否为/upload/xxx.jpg确认Nginxlocation /upload/配置正确个人经验90%的404问题源于路径不一致。前端axios.defaults.baseURL /api后端RequestMapping(/api)Nginx必须把/api/代理到http://127.0.0.1:8080/少一个斜杠就会404。5.2 “登录后状态丢失”问题根源学生常抱怨“登录后跳转首页再点其他页面就提示未登录”。这不是JWT失效而是Cookie域配置错误前端发送登录请求时后端set-cookie的Domain属性必须匹配前端域名。若前端用http://localhost:8080后端Cookie Domain应为localhost若前端用http://192.168.1.100:8080Domain应为192.168.1.100。SpringBoot配置application.ymlserver: servlet: context-path: / # 根路径避免/api重复 spring: session: store-type: redis # 生产环境用Redis存Session servlet: session: cookie: domain: localhost # 开发环境设为localhost path: / http-only: true secure: false # HTTP环境设falseHTTPS设true注意secure: false是开发环境必需否则Chrome拒绝发送HTTP下的Secure Cookie。答辩演示时务必关掉浏览器的“强制HTTPS”设置。5.3 “图片上传失败”三步定位法上传失败通常卡在三处前端拦截检查浏览器控制台Network标签页看/api/upload/image请求是否发出、返回什么状态码400500。后端日志sudo journalctl -u lost-found.service -n 50找MultipartException或FileUploadException关键字。服务器权限/var/www/lost-found/upload/目录必须有www-data写权限sudo chown -R www-data:www-data /var/www/lost-found/upload/ sudo chmod -R 755 /var/www/lost-found/upload/踩坑实录某次部署后图片上传总报500日志显示java.io.FileNotFoundException: /var/www/lost-found/upload/ (Permission denied)。查ls -ld /var/www/lost-found/upload/发现属主是root而Nginx进程以www-data运行必须chown授权。这个权限问题在CentOS和Ubuntu表现不同务必实测。5.4 “PDF导出中文乱码”终极解决方案用Flying Saucer导出PDF时中文显示方块网上方案多是“加中文字体”但实际要三步下载思源黑体免费开源字体到服务器sudo mkdir -p /usr/share/fonts/opentype/noto sudo wget -O /usr/share/fonts/opentype/noto/NotoSansCJKsc-Regular.otf https://github.com/googlefonts/noto-cjk/raw/main/Sans/OTF/SimplifiedChinese/NotoSansCJKsc-Regular.otf sudo fc-cache -fv在HTML模板中声明字体html head style font-face { font-family: Noto Sans CJK SC; src: url(file:///usr/share/fonts/opentype/noto/NotoSansCJKsc-Regular.otf); } body { font-family: Noto Sans CJK SC, sans-serif; } /style /head body.../body /html后端渲染时指定字体路径ITextRenderer renderer new ITextRenderer(); renderer.getFontResolver().addFont(/usr/share/fonts/opentype/noto/NotoSansCJKsc-Regular.otf, UTF-8, true); renderer.setDocumentFromString(html);关键点file:///协议必须用三个斜杠且路径是服务器绝对路径。fc-cache -fv刷新字体缓存否则Flying Saucer找不到字体。6. 毕业设计答辩与扩展建议这个系统做完只是完成了80%。剩下20%决定你能否拿优秀——答辩呈现与未来延展。答辩时老师最关注三点业务理解深度、技术实现严谨性、问题解决能力。不要一上来就演示“我做了登录页”而是先说“我们调研了学校后勤处发现失物招领最大痛点是信息不对称——捡到者不知道该交到哪失主不知道物品在哪管理员每天手动登记30条信息。所以我们设计了‘智能归还点推荐’用户发布失物时系统根据楼宇GPS坐标自动推荐最近的3个领取点并在地图上标注。”然后演示地图组件后台算法经纬度计算距离这比单纯展示CRUD高一个维度。技术上准备一个“我踩过的坑”清单。比如“最初用localStorage存Token但发现同设备多账号切换时Token冲突后来改用HttpOnly CookieSameSiteLax既防XSS又兼容CSRF防护。”这种细节老师一听就知道你真做过。至于扩展别只说“可以本文还有配套的精品资源点击获取