ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于SpringBoot固定资产管理系统的设计与实现(含源码)

基于SpringBoot固定资产管理系统的设计与实现(含源码) 简介本资源是一套完整的基于SpringBoot的固定资产管理系统毕业设计资料面向计算机专业本科生及Java初学者解决企事业单位资产登记、分类管理、审批流程与用户权限控制等核心管理需求。压缩包共245个文件包含44个Java源码文件涵盖UserController、AssetController、UserRealm等关键类、30个HTML页面模板、20个JS交互脚本、13个XML配置文件、12个CSS样式文件及1个建库SQL脚本完整支撑系统前后端开发与部署包体大小为36.6MB。已有513人学习下载资源内容与论文高度对应覆盖从需求分析、数据库设计含资产表、审批表、用户表等、六大功能模块实现登录、资产查询、分类管理、审批待办、个人资产、用户管理到系统测试的全流程附带可直接运行的项目源码与结构清晰的毕业论文文档便于快速理解MVC分层架构与企业级资产管理业务逻辑。 咱们这次聊的题目是“基于SpringBoot固定资产管理系统的设计与实现论文项目源码”一看就是典型的计算机类毕业设计选题。这个题在每年的毕设题目里出现频率极高不是没道理的既有业务逻辑可讲又能把SpringBoot、MyBatis、数据库设计、权限控制这些核心知识点全串起来还不用涉及太复杂的分布式场景很适合作为本科阶段的综合练手项目。从本质上说固定资产管理系统解决的是企业资产“从哪来、在谁手里、状态如何、用到什么时候”的问题。现实中大量中小企业还在用Excel台账管资产资产编号靠手写、领用人靠口头通知、报废审批靠线下签字盘点时几个人对着表格挨个核对效率和准确率都很感人。做成系统之后资产的全生命周期——从入库、领用、归还、调拨、维修到报废——都能在线上留痕审批有记录、数据可统计、责任能追溯这就是这个系统真正的价值所在。这套系统适合谁来参考第一类是正在准备毕业设计的在校生拿来捋清楚一个完整Web项目的结构第二类是刚入门SpringBoot、想找个“不太难又完整”的实战项目练手的朋友第三类是想给公司内部做个简单资产管理工具的后端开发。前两类读者占绝大多数所以下面的内容我尽量兼顾“论文怎么写”和“代码怎么落地”两条线既有逻辑拆解也有能直接抄的实操细节。1. 需求分析与整体设计思路拆解1.1 固定资产管理的真实痛点与需求边界为什么不建议直接“拍脑袋”写功能而是要先梳理痛点因为很多毕设项目的通病是功能堆砌——用户管理、部门管理、资产管理、报表管理全都有但每个模块都只是增删改查看不出设计思路。用真实场景驱动需求分析论文里才有东西写答辩时也才经得起追问。我接触过的小企业资产管理员日常工作大概是这样的每个月要更新一次Excel台账新买的电脑要手动编一个资产编号填进去有人离职时要查他名下有没有没归还的设备每隔半年要组织一次全公司盘点打印一大摞表格挨个房间核对。这些场景对应到系统里就是几个核心诉求资产信息要有统一的、不可重复的编码规则能快速查到某件资产的当前位置和责任人领用、归还、维修、报废这些操作必须走线上流程而且每一步都要留下操作记录管理层能随时看到资产分布情况比如哪个部门的固定资产最多、哪些设备即将到达报废年限权限要清晰普通员工只能查看和申请部门主管能审批资产管理员能做入库、处置系统管理员管用户和基础数据把这些痛点翻译成需求后功能边界就出来了。我建议不要做“大而全”的ERP式系统那是给自己挖坑。底线是四张核心主表加两张记录表资产分类表、资产信息表、部门表、用户表再加上领用归还记录表、维修记录表。围绕这些表展开的操作就是系统的全部核心功能。报表统计和资产预警可以做成加分项但不建议一开始就铺开。1.2 功能模块划分与优先级控制功能模块我建议按下表来划分同时标清楚优先级设计论文时也可以照着这个层次来写功能模块核心操作优先级说明登录与权限登录、退出、密码修改必做基于RBAC模型角色分管理员、资产管理员、普通用户资产分类管理分类的增删改查必做树形结构支持父分类比如办公设备、IT设备、家具资产信息管理资产录入、编辑、查询、删除、批量导入必做资产卡片为核心包含状态字段资产领用与归还申请、审批、领用登记、归还登记必做考虑轻量审批流程而非全套流程引擎资产维修管理维修申请、维修记录、维修完成推荐体现资产全生命周期资产报废管理报废申请、审批、处置推荐状态流转联动统计报表报表统计按分类/部门/状态统计到期预警加分项使用图表展示论文里可以写“可视化”消息提醒待审批通知、到期提醒加分项站内提醒即可不要碰短信、邮件这样划分有一个好处前三项保证系统“能用”中间三项保证“业务能闭环”最后两项是“亮点”。毕设答辩时评审最看重的是“闭环”——也就是一个业务的完整流程有没有走通。比如“领用申请→审批→出库→归还→归还确认”这条链路就是整个系统里最重要的业务闭环论文里最好单独拿出来讲。1.3 为什么选SpringBoot单体架构而不是微服务这个选择题几乎每次都会有同学困惑。我的建议很直接毕业设计和个人练手项目就选SpringBoot单体架构理由有三条。第一规模匹配。固定资产管理系统面向的是几百人到几千人的中小企业并发量很小单体架构完全扛得住硬上微服务反而徒增部署和运维成本。第二知识点匹配。本科阶段要求掌握的是Spring的IOC/AOP、MyBatis操作数据库、SpringMVC的请求处理链路、基本的安全控制这些在单体项目里全都能覆盖到。第三论文好写。微服务要写服务拆分原则、注册中心、网关、熔断降级每一块都需要大篇幅如果实际代码没体现出来答辩时一问就露馅。单体架构的优势在于一个应用包含所有模块部署时打一个Jar包就行开发调试时在IDE里一键启动没有跨服务调用的复杂性。这并不代表项目的代码可以随意堆在一个类里单体架构同样要求清晰的分层——Controller只做参数接收和响应封装Service层写业务逻辑Mapper层只做数据访问层与层之间用接口隔离。这一点在第二章会详细说。2. 技术选型与核心配置说明2.1 技术栈清单及各组件选型理由技术选型这部分在论文里一般是“系统开发环境”章节别小看这一段评委经常会问“为什么用这个不用那个”。把选型理由想透比堆一堆名词有用得多。后端SpringBoot。建议用2.7.x版本不是越新越好。3.x版本开始强制要求JDK17而很多学校的实验环境和教程还是基于JDK8遇到版本问题会卡住进度。SpringBoot 2.7.x是兼容JDK8的最后一个稳定大版本生态成熟网上资料也最多最适合这个题目。持久层框架MyBatis-Plus。这个东西属于“用了就回不去”的类型内置的单表CRUD不用写XML分页插件一行配置就搞定代码生成器还能直接生成实体类和Mapper接口。相比原生MyBatis少写大量重复代码相比Spring Data JPA又更容易理解SQL是怎么执行的。论文里可以提一句“基于MyBatis增强工具保留SQL灵活性的同时简化单表操作”这个说法十分稳妥。数据库MySQL 5.7或8.0都可以。8.0的窗口函数更强但对这个项目没什么影响如果你本机装的是5.7就继续用5.7没必要重新折腾。前端这是最容易纠结的地方。两个选择一是用Thymeleaf模板引擎做服务端渲染前后端代码在同一个工程里二是用Vue单独做前端通过接口与后端交互。我的建议如果你想省时间、把精力放在后端业务上选Thymeleaf如果论文想写“前后端分离架构”选Vue。但是要注意前后端分离意味着要处理跨域、Token校验、接口联调这些额外问题工作量至少多出30%。这个选择题没有标准答案关键是你在论文里的技术描述要和代码实际实现一致这是最基本的底线。权限认证JWTJSON Web Token或者Session都可以。JWT在现在的项目中更主流前后端分离场景下天然适配不需要在服务端维护会话状态。具体实现思路后面第四章会详细讲。其他组件文件上传用本地存储即可不推荐引入FastDFS或MinIO徒增部署复杂度Excel导入导出用EasyExcel或Hutool的工具类代码量极少报表图表用ECharts通过接口返回JSON数据前端渲染成饼图、柱状图。2.2 后端项目分层与包结构规划这一节的内容在毕设论文里对应“系统设计”章节代码里对应工程的包结构。实际开发时把包结构搭好后面写代码会很顺手不用到处乱找。我推荐的分层方式是经典的四层结构com.example.fixedasset ├── controller // 接口层接收参数、返回结果 ├── service // 业务层核心逻辑接口实现类 │ └── impl ├── mapper // 数据访问层继承BaseMapper ├── entity // 实体类对应数据库表 ├── dto // 数据传输对象定义接收参数和返回参数 ├── vo // 视图对象定义页面展示结构 ├── config // 配置类如MyBatis-Plus分页、Cors跨域 ├── common // 公共类如统一返回结果、异常处理、常量 ├── util // 工具类如JWT工具、Excel工具 └── interceptor // 拦截器如登录校验、权限校验每一层都只做自己的事不许越界。我见过不少同学在Controller里直接写SQL或用new HashMap拼参数这样写快了几天后面维护和写论文都会很难受。如果你想让代码更规范一些可以在Service里把“查询列表”“保存资产”“审批操作”这些动作在接口文档里描述清楚这对接下来的接口测试和论文测试章节都是素材。2.3 SpringBoot核心配置要点含application.yml示例配置这块是SpringBoot的“甜点”所在但也是很多新手第一次踩坑的地方。我给你一份可以直接改着用的核心配置server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/fixed_asset_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 hikari: minimum-idle: 5 maximum-pool-size: 20 connection-timeout: 30000 thymeleaf: cache: false servlet: multipart: max-file-size: 20MB max-request-size: 50MB mybatis-plus: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.fixedasset.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 jwt: secret: your-secret-key-please-change-in-production expire: 604800几个容易忽略的细节时间时区。serverTimezoneAsia/Shanghai必须加否则数据库连接时会报时区错误如果数据库存的时间比实际时间差8小时十有八九是这个参数的问题。逻辑删除。logic-delete-field: deleted表示所有表有一个deleted字段用于标记删除这样执行删除操作时其实是UPDATE数据不会真正消失以后想查历史记录还有机会。这个设计在论文“数据库设计”里可以写一条属于很小的加分项。驼峰映射。map-underscore-to-camel-case: true让数据库的asset_code自动映射到Java类的assetCode省一大串XML别名。3. 数据库设计与核心业务逻辑3.1 核心数据表设计与字段规划数据库设计是固定资产管理系统的“地基”也是论文里占篇幅最多、最容易被评委追问的部分。表设计得好后端的CRUD就是流水账表设计得烂写业务的时候到处要拼接条件。我先给你核心的几张表字段以实用为准然后逐个说明关键设计意图。资产分类表 asset_category字段名类型说明idbigint主键parent_idbigint父分类顶级为0category_namevarchar(64)分类名称category_codevarchar(32)分类编码sort_orderint排序deletedtinyint逻辑删除标记资产信息表 asset字段名类型说明idbigint主键asset_codevarchar(64)资产编号唯一asset_namevarchar(128)资产名称category_idbigint分类IDspec_modelvarchar(128)规格型号purchase_datedate购置日期original_valuedecimal(12,2)原值net_valuedecimal(12,2)净值use_statustinyint在库/领用/维修/报废/待处置current_user_idbigint当前使用人current_dept_idbigint当前使用部门locationvarchar(128)存放地点suppliervarchar(128)供应商warranty_enddate质保截止日期buy_user_idbigint经办人remarkvarchar(512)备注create_timedatetime创建时间update_timedatetime更新时间deletedtinyint逻辑删除标记领用归还记录表 asset_use_record字段名类型说明idbigint主键asset_idbigint资产IDasset_codevarchar(64)资产编号冗余use_user_idbigint领用人use_dept_idbigint领用部门apply_timedatetime申请时间approve_user_idbigint审批人approve_timedatetime审批时间approve_statustinyint待审批/通过/驳回approve_commentvarchar(255)审批意见use_start_timedatetime实际领用时间use_end_timedatetime预期归还时间actual_return_timedatetime实际归还时间return_statustinyint使用中/已归还维修记录表 asset_repair_record字段名类型说明idbigint主键asset_idbigint资产IDrepair_reasonvarchar(512)故障描述repair_statustinyint待维修/维修中/已修复/无法修复repair_companyvarchar(128)维修单位repair_costdecimal(12,2)维修费用repair_timedatetime维修时间finish_timedatetime完成时间operator_idbigint报修人表设计时有几点需要特别注意不要存冗余的字符串状态。比如use_status不要用“在库”“领用”这样的中文要用数字枚举在Java代码里定义常量或枚举类页面显示时再转成中文。这个习惯无论对代码可维护性还是论文的“数据字典”设计都有好处。记录表要冗余资产编号和名称。资产领用记录表里除了外键还冗余一个asset_code和asset_name这样即使以后资产被删了历史记录也能看出来当时领的是什么。这是很典型的数仓设计思维在业务系统里同样适用。日期字段的边界。use_start_time、actual_return_time这种字段不要用date类型只有年月日要用datetime因为领用和归还可能发生在同一天的不同时刻。只有purchase_date这种概念上只关心年月日的字段才用date。3.2 资产编码规则与状态机设计资产编码是整个系统的基石它相当于每件资产的“身份证号”。编码规则设计得好后续查询、盘点、流程关联都会很方便。我推荐一套简单实用的规则分类编码 购置年月 四位流水号。比如一台台式电脑分类编码是BG办公设备2024年6月购入当月第3台那资产编号就是BG2024060003。用Java实现时可以利用分类表的category_code字段加上当前年月再通过SQL统计同类资产当月的数量加一String categoryCode category.getCategoryCode(); String yearMonth new SimpleDateFormat(yyyyMM).format(new Date()); String prefix categoryCode yearMonth; QueryWrapperAsset wrapper new QueryWrapper(); wrapper.likeRight(asset_code, prefix); Long count assetMapper.selectCount(wrapper); String assetCode prefix String.format(%04d, count 1);这个方案有两点好处一是资产编号本身就能看出分类和购入时间不用查数据库二是不用单独维护一张流水号表直接在现有表里统计即可对数据量万级以内的系统完全够用。缺点是并发下可能会重复但对这个场景来说加个唯一索引偶发冲突时让用户重试一次就可以没必要引入分布式ID。状态机设计是资产系统里最重要的业务逻辑。资产的状态流转一定要集中管理不要在多个Service方法里各写各的if-else否则很容易出现资产状态和记录不一致的情况。建议单独写一个AssetStatusHandler所有状态变更的校验逻辑都往这里收敛public enum AssetStatusEnum { IN_STOCK(0, 在库), USED(1, 使用中), REPAIRING(2, 维修中), SCRAPPED(3, 已报废), DISPOSED(4, 已处置); private final Integer code; private final String desc; }状态流转的核心规则只有几条在库资产才能被领用使用中才能申请维修维修中不能重复申请报废必须从在库或使用中进入已报废不能再领用。把这些规则写在状态机的校验方法里所有服务调用之前先做校验就能避免脏数据。3.3 折旧计算逻辑与SQL实现技巧固定资产系统最容易被问到的业务点就是折旧——“你的系统怎么算折旧的”如果用最简单的直线折旧法逻辑并不复杂但你得把它讲清楚而且要在代码里实现出来。直线法公式月折旧额 资产原值 - 残值率×原值÷ 预计使用月份数。残值率一般取5%预计使用年限按资产类别区分电子设备3年、家具5年、建筑物20年。为了简化可以在资产分类表里加两个字段depreciation_years和residual_rate这样不同分类可以有不同参数。系统的实现思路是在资产卡片上记录原值、净值、使用起始月份每月初用定时任务或查询时即时计算当前净值。考虑到毕设场景不建议引入复杂的定时任务框架用查询时实时计算的方式更简单SELECT a.id, a.asset_code, a.asset_name, a.original_value, a.purchase_date, c.depreciation_years, c.residual_rate, ROUND(a.original_value * (1 - c.residual_rate) / (c.depreciation_years * 12), 2) AS month_depreciation, ROUND( a.original_value - a.original_value * (1 - c.residual_rate) / (c.depreciation_years * 12) * TIMESTAMPDIFF(MONTH, a.purchase_date, CURDATE()), 2 ) AS net_value FROM asset a JOIN asset_category c ON a.category_id c.id WHERE a.use_status ! 3这段SQL直接把每件资产的月折旧额、当前净值都算出来不用在Java代码里做循环性能也好。需要注意的点是TIMESTAMPDIFF是按整月计算的对月中购置的资产会少算当月折旧论文里可以写一句“按整月计提折旧不足整月的不计提”把规则定清楚就行。3.4 审批流程的轻量级实现思路很多同学一提到审批流程就想引入Flowable或Activiti工作流引擎这东西确实强大但对固定资产管理系统来说属于“杀鸡用牛刀”。审批引擎的学习成本高表结构复杂配置起来也麻烦单纯为了论文多写两章去引入它性价比太低。在这个系统里审批本质上就是一条状态链。以领用申请为例提交申请 - 部门主管审批 - 资产管理员确认出库 - 员工签收。这个链路用一张记录表加一个状态字段就能实现完全不需要工作流引擎。核心表结构就是上面看到的那样每次审批操作就是更新approve_status字段并记录审批意见。我建议把审批操作封装成一个通用方法避免每个模块重复写public void approve(UseRecord record, Integer approveStatus, String comment, Long approverId) { // 校验记录状态必须是待审批否则抛异常 if (!record.getApproveStatus().equals(ApproveStatusEnum.PENDING.getCode())) { throw new BizException(当前记录不是待审批状态无法审批); } record.setApproveStatus(approveStatus); record.setApproveComment(comment); record.setApproveUserId(approverId); record.setApproveTime(LocalDateTime.now()); useRecordMapper.updateById(record); // 审批通过后联动资产状态变更 if (approveStatus.equals(ApproveStatusEnum.APPROVED.getCode())) { assetService.changeStatus(record.getAssetId(), AssetStatusEnum.USED); } }用状态字段实现审批有几个好处表结构简单代码逻辑一目了然排查问题时看记录表就能完整还原流程。缺点是无法支持复杂的会签、或签、条件分支但对这个题目来说完全够用评委也不会因为你没用Flowable而扣分反而觉得你设计合理、复杂度控制得当。4. 核心功能实操与关键代码走读4.1 用户登录、密码加密与JWT鉴权登录鉴权是几乎所有系统的入口面试或答辩时也经常被单独拎出来问。固定资产管理系统的权限模型用最简单的RBAC基于角色的访问控制用户表、角色表、用户角色关联表。角色就三种——系统管理员、资产管理员、普通用户各自对应的菜单和接口权限不同。密码存储必须加密严禁明文。推荐用BCrypt算法Spring Security里自带的BCryptPasswordEncoder可以直接用没有引入Spring Security的项目也可以用hutool-crypto里的BCrypt实现。BCrypt的特点是每次加密结果都不同但校验时能匹配安全性远高于MD5加盐。JWT的实现思路是登录成功后服务端生成一个包含用户ID、用户名、角色信息的Token返回给前端前端每次请求在Header里带上Authorization: Bearer token后端通过拦截器校验Token是否有效、是否过期解析出用户信息后放入ThreadLocal方便后续业务方法获取当前用户。核心代码大致是这样Component public class JwtUtil { Value(${jwt.secret}) private String secret; Value(${jwt.expire}) private Long expire; 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() expire * 1000)) .signWith(SignatureAlgorithm.HS256, secret) .compact(); } public Claims parseToken(String token) { return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } }拦截器的注册别忘了放行登录接口和静态资源Configuration public class WebConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtInterceptor) .addPathPatterns(/**) .excludePathPatterns(/api/auth/login, /css/**, /js/**, /images/**); } }一个容易被忽视的坑是JWT是无状态的服务端无法主动让某个Token失效。在处理“用户修改密码后强制下线”这类需求时会有局限但毕设场景下不需要考虑论文里可以提一句“JWT适用于对过期时间要求不敏感的场景”稳住就行。4.2 资产CRUD与多条件组合查询资产列表是系统的核心页面查询条件通常包括资产名称模糊、资产编号模糊、分类下拉选择、使用状态多选或下拉、购入日期区间。用MyBatis-Plus的LambdaQueryWrapper就能优雅地完成。public PageResultAssetVO pageAsset(AssetQueryDTO dto) { PageAsset page new Page(dto.getPageNum(), dto.getPageSize()); LambdaQueryWrapperAsset wrapper Wrappers.lambdaQuery(); wrapper.like(StringUtils.hasText(dto.getAssetName()), Asset::getAssetName, dto.getAssetName()) .like(StringUtils.hasText(dto.getAssetCode()), Asset::getAssetCode, dto.getAssetCode()) .eq(dto.getCategoryId() ! null, Asset::getCategoryId, dto.getCategoryId()) .eq(dto.getUseStatus() ! null, Asset::getUseStatus, dto.getUseStatus()) .between(dto.getStartDate() ! null dto.getEndDate() ! null, Asset::getPurchaseDate, dto.getStartDate(), dto.getEndDate()) .orderByDesc(Asset::getCreateTime); PageAsset result assetMapper.selectPage(page, wrapper); // 将实体转为VO补充分类名称、使用人姓名等冗余信息 return convertToPageResult(result); }这个查询方式最大的好处是条件可空判断全部内聚在Wrapper里代码干净而且参数多了也不容易拼错。有一点要注意模糊查询最好只在资产名称、编号这种短字段上用like不要在备注这种长字段上做模糊搜索否则全表扫描会影响性能论文的性能测试部分如果做了大数据量测试会很尴尬。批量删除的时候要用事务。JDK11以下用Transactional注解就行但要注意事务回滚的边界——这个注解加在方法上只能对运行时异常生效。如果代码里手动捕获了异常记得TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()强制回滚否则删除一半的脏数据就产生了。4.3 报表统计与图表接口设计报表模块是答辩时的“讲故事”环节视觉效果好也容易讲清楚数据分析思路。ECharts的图无非是饼图、柱状图、折线图关键是把数据接口设计好。我建议提供三个核心统计接口按部门统计资产数量与资产原值柱状图按资产分类统计资产占比饼图按购置年份统计资产增长趋势折线图接口返回格式一般是{ categories: [研发部, 财务部, 行政部], values: [65, 42, 28] }写SQL时可以用分组聚合一次查出来避免在Java里循环再查数据库SELECT d.dept_name AS name, COUNT(a.id) AS value, SUM(a.original_value) AS total_value FROM asset a LEFT JOIN sys_dept d ON a.current_dept_id d.id WHERE a.deleted 0 AND a.use_status ! 3 GROUP BY d.dept_name ORDER BY value DESC除了统计图资产到期预警也很有用把质保即将到期、距报废年限不足一年的资产列出来让管理员提前关注。用一条SQL加简单的Java时间判断就能实现SELECT * FROM asset WHERE deleted 0 AND warranty_end IS NOT NULL AND warranty_end BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 90 DAY)这条SQL的含义是“质保期在未来90天内到期”。界面显示时可以用不同颜色标记“30天内到期”和“90天内到期”配合ECharts的警戒线效果会很好。4.4 Excel批量导入与导出落地Excel导入导出在固定资产系统里几乎是刚需因为企业现有的资产数据基本都是Excel格式的。EasyExcel是阿里的开源库内存占用比POI低很多代码也更简洁。导入的核心逻辑是前端上传Excel文件 - 后端接收并解析 - 校验每一行数据 - 校验通过的写入数据库 - 校验失败的错误信息返回给前端。public MapString, Object importAsset(MultipartFile file) { ListAssetImportDTO list EasyExcel.read(file.getInputStream()) .head(AssetImportDTO.class) .sheet() .doReadSync(); ListString errors new ArrayList(); int successCount 0; for (int i 0; i list.size(); i) { AssetImportDTO dto list.get(i); try { validate(dto); // 检查必填字段、分类是否存在、资产编号是否重复 assetService.save(convertToEntity(dto)); successCount; } catch (Exception e) { errors.add(第 (i 2) 行 e.getMessage()); } } return Map.of(successCount, successCount, errors, errors); }注意这里的行号是i 2因为表头占第一行用户实际看Excel时看到的就是第几行报错信息这样写才能对得上。这个细节虽然小但在答辩演示导入功能时很加分说明你真的在真实场景里用过。导出的思路反过来用EasyExcel把查询结果直接写到HttpServletResponse的输出流中设置好文件名和Content-Type就能触发浏览器下载。为了避免一次性导出全表数据导致内存溢出导出时也走分页查询每查出1000条就写一批写完用finish()收尾。5. 常见问题与排查技巧实录5.1 项目启动阶段的高频报错与解决方案我把实际开发中遇到最多的启动报错整理成一张速查表每一条都是真实踩过的坑报错信息原因解决方案Failed to configure a DataSource没有配置数据源或数据库没启动检查application.yml的url、账号、密码先确保能连上数据库Access denied for user rootlocalhost数据库密码错误用命令行试一下mysql -uroot -p确认密码Port 8080 was already in use端口被占用netstat -ano找到占用的PID结束进程或改server.portjava.lang.NoClassDefFoundError: javax/xml/bind/DatatypeFactoryJDK11缺少JAXB模块SpringBoot 2.x项目配置JDK11时引入jaxb-api依赖Caused by: java.sql.SQLException: The server time zone value数据库时区不识别在JDBC URL后加serverTimezoneAsia/ShanghaiPackage javax.servlet does not exist缺少servlet-api依赖引入spring-boot-starter-web后清理并重新导入依赖有一个很典型的坑值得展开说很多人下载的项目源码在自己电脑上死活启动不了报Invalid value type for attribute factoryBeanObjectType或者各种类找不到。这通常是依赖版本冲突。SpringBoot 3.x的源码不能直接换JDK8跑2.x的源码用JDK17跑虽然能启动但可能有CGLIB警告。最稳妥的办法是保持源码自带的SpringBoot版本不动装一个对应的JDK版本。5.2 MyBatis-Plus使用中的隐藏陷阱MyBatis-Plus虽然好用但它的“自动化”在某些时候也会坑人。第一个坑是自动填充不生效。create_time、update_time这种字段如果在插入时不赋值数据库里会是null。MP提供了MetaObjectHandler接口正确用法是写一个实现类并注册成Bean然后在实体字段上加上TableField(fill FieldFill.INSERT)注解。如果加了注解还是没生效检查一下是不是在实体类里把这些字段定义到了公共父类中而父类的包路径没被MP扫描到。第二个坑是分页失效。很多人以为引入了mybatis-plus-boot-starter就有分页了实际上还需要手动配置分页插件Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }没配置这个Bean的话selectPage返回的total永远是0但查出来的记录数还正常这种“半失效”最让人头大。排查方式很简单——SQL日志里如果没出现LIMIT关键字就是分页插件没生效。第三个坑是逻辑删除和唯一索引冲突。如果我给asset_code字段建了唯一索引逻辑删除后再次录入相同编号的资产就会报DuplicateEntry。解决办法是用“无效化编码”策略比如删除时把资产编号改成一串带时间戳的字符串或者像支付宝那样附加-DEL-123456后缀。这也是为什么很多生产系统里“删除”更像“作废”的原因。5.3 前后端联调时的跨域与日期序列化如果你选了前后端分离跨域问题几乎一定会遇到。浏览器的同源策略会拦截跨域请求后端需要配置CORS。Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }注意allowCredentials(true)时allowedOrigins(*)会冲突必须用allowedOriginPatterns(*)这是很多CORS配置不生效的隐藏原因。另外如果后端配置了JWT拦截器要记得对OPTIONS预检请求放行否则前端频繁看到“CORS error”。日期序列化是另一个几乎必踩的坑。Java后端的LocalDateTime默认序列化出来是2024-06-01T10:30:00这种带T的格式前端显示出来很丑。在配置类里统一加一个Jackson配置Bean public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() { return builder - { builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer( DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss))); builder.deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer( DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss))); }; }加上之后所有接口返回的日期格式就统一了前端接收时不用再写一堆dayjs的格式化逻辑。5.4 安全相关防SQL注入与防越权操作固定资产管理系统虽然不是高价值攻击目标但作为毕设安全这块是加分项而且这些知识在面试中也常被问。SQL注入防护。MyBatis的${}是字符串拼接有注入风险#{}是预编译安全。写SQL时原则是能用#{}的地方绝对不用${}。动态排序列名或表名时${}避不开这时候必须做白名单校验。比如前端传sortField时后端用一个Map映射允许排序的字段不在Map里的直接拒绝。越权访问防护。JWT解决的是“你是你”的问题解决不了“你能不能做这件事”的问题。如果只校验登录状态而不校验角色一个普通用户直接调用/api/asset/delete接口就能删资产这就是水平越权。合理的做法是在Mapper查询时加上“当前用户数据范围”条件比如普通用户只能查到自己名下的资产资产管理员能查全部门的资产系统管理员能查全部。用一个简单的数据权限注解配合拦截器解析是性价比最高的方案。XSS防护。输入型XSS的解决思路是全局过滤继承HttpServletRequestWrapper把请求参数里的script、javascript:等危险内容转义或剔除。Spring Boot里可以通过ControllerAdvice处理请求体中的JSON参数也可以用现成的过滤库。这里有一个很现实的坑如果在数据库中存的已经是被转义的lt;显示在页面上又会变成双重转义的奇怪内容。正确做法是“存原始内容输出时转义”或者在保存时统一去标签。要记住这个思路否则会在“到底在哪一层做过滤”上反复折腾很久。6. 论文写作要点、系统测试与答辩准备6.1 论文结构与技术路线图规划论文结构有标准模板不要自己乱发明。一篇合格的毕设论文通常包含摘要中英文、绪论、需求分析、系统设计、数据库设计、系统实现、系统测试、总结与展望。如果你们学校有模板严格按模板来没有模板的话这样的结构最保险。绪论写三节项目背景与意义、国内外研究现状、论文组织结构。背景部分不用写太宏大就把“中小企业固定资产管理依赖Excel台账、流程不透明、数据孤岛”这几个痛点讲清楚就行。研究现状要有参考文献支撑重点提一下固定资产管理系统从单机版C/S结构向B/S结构、云原生发展的趋势。需求分析是重中之重论文里最忌讳只贴几张截图然后说“这个功能很简单”。正确的写法是每个核心业务场景写一段文字描述配套绘制用例图和流程图。比如领用场景用文字描述“用户登录系统后发起领用申请填写申请数量和预期归还时间部门主管登录后看到待审批列表点击审批通过后资产管理员进行出库登记”再画一张领用业务流程图。这个过程把“用例描述、业务流程图、时序图”三件套做全需求分析基本就稳了。技术路线图建议画三张系统总体架构图B/S架构的浏览器、Web服务器、数据库服务器三层结构、系统功能结构图按模块树形展开、系统技术架构图体现SpringBoot、MyBatis-Plus、MySQL、前端技术栈之间的关系。这几张图不仅论文里需要答辩PPT里也是核心素材。6.2 系统测试用例与测试报告撰写很多同学写完代码后根本不测直接编测试章节这是非常危险的。答辩评委随机点开一个功能演示发现Bug整个测试章节的可信度就崩塌了。建议至少做一轮完整的手工回归测试把主要功能都点一遍并截图保存。测试用例表格式换成这样用例编号功能描述操作步骤预期结果实际结果是否通过TC001用户登录输入正确用户名和密码点击登录登录成功跳转系统首页登录成功跳转首页通过TC002用户登录输入错误密码提示用户名或密码错误提示用户名或密码错误通过TC003资产新增填写完整资产信息点击保存资产列表出现新资产编号自动生成列表出现新资产编号为BG2024060003通过TC004资产删除删除状态为“使用中”的资产提示不可删除提示该资产当前正在使用不可删除通过TC005领用流程普通用户提交领用申请记录状态为待审批资产状态不变待审批资产状态为在库通过TC006领用审批主管审批通过记录状态为已通过资产状态变为使用中已通过资产状态变为使用中通过性能测试可以不发“压测报告”这种大词简单做一个500条资产数据的分页查询耗时统计用System.currentTimeMillis()打印每次查询耗时记到表格即可。评委关心的是你有没有性能意识不是性能指标了不了解压测工具。当然如果你想更专业一点可以用JMeter压一个接口导出报告截图贴在论文里这是加分项。但要注意如果项目里没有做任何缓存、索引优化压测结果太漂亮反而不可信保持数据的真实性比什么都重要。测试章节写完后记得在论文最后附上“系统开发与运行环境”一节把JDK版本、MySQL版本、IDE版本、操作系统版本写清楚。这看似不起眼但能给评委一个“这个人确实把环境搭建起来并跑通了”的直观印象。6.3 答辩高频提问与应答思路答辩时评委问的问题通常围绕几个方向为什么这么设计、某个功能怎么实现、遇到什么困难、如果是更大规模场景怎么办。把以下问题提前准备充分基本就稳了“你为什么选SpringBoot”回答思路SpringBoot简化了Spring的配置内置服务器支持快速启动约定大于配置的机制提升了开发效率生态丰富适合快速构建中小型管理系统。这个回答比“因为大家都用”要专业得多。“你的系统权限是怎么实现的”回答思路RBAC模型用户关联角色角色关联权限码登录后发放JWT拦截器校验Token并解析角色Controller使用自定义注解做接口级鉴权。建议把职责链思路讲清楚评委就会觉得这不是背出来的。“你如何防止SQL注入”回答思路使用预编译SQL、禁止拼接字符串、使用MyBatis的#{}、动态排序列名做白名单校验。这几句话把原理和实战都覆盖了。“如果你的系统超过一万条数据哪些地方会变慢怎么优化”回答思路资产列表多条件查表会慢方案是给常用条件字段加索引、分页查询减少数据量、热点数据加Redis缓存报表统计慢方案是预聚合表或定时生成统计结果。这个问题不要只会说“加索引”要把场景和方案绑定起来。“你的系统相比Excel台账核心价值是什么”回答思路核心价值是把流程规范化资产全生命周期有迹可循权限分级责任明确统计报表实时可见。用这个回答收尾能给评委留下一个“真的做过需求理解”的印象。6.4 项目扩展方向与个人建议系统做完、论文写完如果你还有余力想让它看起来更“高级”有三条扩展方向。第一是引入Redis缓存把资产分类、部门列表这些不常变动的字典数据缓存起来减少数据库压力论文里能写“缓存策略设计”。第二是在报废审批中接入简单的消息通知用WebSocket或者轮询接口实现站内提醒让“待处理事项”主动推送给审批人。第三是给系统加一个简易移动端页面用H5适配手机浏览器哪怕只是一个扫码查看资产详情页也比没有强。但一定要记住扩展的前提是核心功能稳定、论文整体逻辑完整。不要因为加了新技术导致系统跑不起来那就得不偿失了。我个人在实际开发这类项目时最大的心得体会是写一个完整系统真正难的不是某一项技术而是把业务流程吃透、把状态流转设计正确、把异常情况考虑周全。固定资产管理系统的核心就是那张资产状态机的图只要状态流转清晰代码、数据库、接口设计都会顺理成章。你把这个系统完整做完后无论是对SpringBoot的理解、数据库设计能力还是排查问题的经验都会有一个质的提升。如果开发过程中遇到具体问题欢迎随时交流。本文还有配套的精品资源点击获取
返回列表