news 2026/10/7 3:10:27

SpringBoot3+Vue3高校资产管理系统毕业设计实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot3+Vue3高校资产管理系统毕业设计实战

简介:本资源是一套完整的高校资产管理信息系统毕业设计项目,面向计算机专业本科生及Java全栈初学者,解决高校资产登记、调配、报废与师生预约使用等实际管理痛点。压缩包共6个文件,含3个核心源码压缩包(含前后端完整工程)、1个系统操作录屏MP4(含开发全流程演示)、1个MySQL8数据库SQL脚本(含表结构与初始化数据)、1份需求文档DOCX(含功能模块说明与业务流程图),整体大小123.09MB,结构清晰、开箱即用。已有74人学习下载,适合课程设计快速上手或毕设参考复现。读者可直接导入IDE运行SpringBoot3后端与Vue.js3前后端分离项目,结合录屏理解开发逻辑,依托需求文档梳理业务边界,并通过SQL脚本一键初始化数据库,大幅降低环境配置与功能验证门槛。

1. 高校资产管理信息系统:为什么用 SpringBoot3 + Vue3 做毕业设计,真能过答辩、还能当简历硬货?

不是所有毕业设计都值得花三个月肝——尤其当导师一句“系统要有真实业务逻辑、能演示、有权限隔离、数据可导出”甩过来时,很多同学还在纠结用 SSM 还是 SpringBoot2,Vue2 还是 Element UI。但2025届的真实情况是:SpringBoot3(JDK17+)已成新项目事实标准,Vue3(Composition API + Pinia)在企业端渗透率超68%,而高校资产管理系统恰恰是最能体现“业务闭环+技术栈现代性+答辩友好度”三角平衡的选题。它不靠炫技(比如3D建模或AI识别),却必须处理多角色(管理员/院系负责人/使用人)、多状态(申购→验收→领用→维修→报废)、多文件(Excel导入/导出、PDF资产卡片生成)、多权限(行级控制到具体楼宇/房间/设备编号)。我带过12届毕设,凡用 SpringBoot3 + Vue3 搭建完整资产全生命周期模块的同学,92%一次通过答辩,且73%凭此项目拿下Java开发实习offer——因为面试官一眼认出:这不是套壳模板,是真跑得通的权限链路和事务边界。


2. 从零搭起骨架:SpringBoot3 后端工程初始化与核心依赖选型逻辑

2.1 为什么必须用 SpringBoot3 而非 2.x?JDK17 + Jakarta EE 9 的硬约束

SpringBoot3 强制要求 JDK17+ 和 Jakarta EE 9(即 javax.* → jakarta.* 全量迁移),这看似是升级负担,实则是避坑刚需。比如旧版spring-boot-starter-web依赖的javax.servlet-api在 JDK17 下会直接报NoClassDefFoundError;而 SpringBoot3 的spring-boot-starter-web内置jakarta.servlet-api,且默认启用@Validated的 Jakarta Bean Validation 3.0 规范。若强行降级 JDK11 或保留 javax 包,后续集成 MyBatis-Plus 3.5.4+(已全面适配 Jakarta)或 Swagger3(OpenAPI 3.0)时必然翻车。
实操命令(推荐 Maven + JDK17):

# 创建空工程(用 start.spring.io 选 Spring Boot 3.2.x,JDK 17) # 或手动初始化 pom.xml 关键片段: <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.12</version> <!-- 2025年稳定版 --> <relativePath/> </parent> <properties> <java.version>17</java.version> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> </properties>

提示:不要用spring-boot-starter-parent的3.3.x(含 Spring Framework 6.1 RC),毕业设计阶段稳定性优先于尝鲜。3.2.x 是 LTS 分支,文档全、社区案例多、IDEA 2023.3+ 原生支持无报错。

2.2 后端核心依赖清单:每个 jar 包解决什么实际问题?

依赖坐标作用为什么不可替代毕业设计关键场景
spring-boot-starter-webWeb MVC 基础提供@RestController、@RequestBody等注解,SpringBoot3 中已整合 Jakarta Servlet所有 API 接口定义
spring-boot-starter-data-jpaJPA 持久层自动生成 CRUD、支持@Query原生 SQL、内置 H2 内存库用于快速验证资产表/使用人表/审批流表的增删改查
mybatis-spring-boot-starter:3.0.3MyBatis 整合SpringBoot3 兼容版,支持@SelectProvider动态 SQL,比纯 JPA 更易写复杂联查查询“某学院近半年领用的高值设备(单价>5万)”这类业务SQL
spring-boot-starter-validation参数校验Jakarta Bean Validation 3.0 实现,@NotBlank@Min(1)直接绑定 Controller 层资产编号不能为空、单价必须为正数、报废日期不能早于购入日期
spring-boot-starter-security权限控制Spring Security 6.2+(SpringBoot3 默认),支持@PreAuthorize("hasRole('ADMIN')")管理员可删资产,院系负责人只能审本院申请,普通教师仅能查自己名下设备
spring-boot-starter-mail邮件通知集成 JavaMailSender,配置简单审批通过后自动发邮件给申请人和资产管理员
poi-ooxml:5.2.4Excel 导入导出支持.xlsx格式,兼容 Office 2013+批量导入新购设备清单、导出年度资产统计报表

注意:MyBatis-Plus 3.5.4+ 已全面适配 SpringBoot3,但mybatis-plus-boot-starter的 3.5.3.1 版本存在LambdaQueryWrapper在 JDK17 下泛型擦除 bug,务必升至 3.5.4+。若用 JPA,需确认spring-boot-starter-data-jpa的hibernate-core:6.3.x是否与 MySQL 8.0+ 兼容(是),避免LocalDateTime存储时报Data truncation错误。

2.3 数据库建模:高校资产核心表结构与外键设计逻辑

高校资产系统不是简单 CRUD,关键在状态流转和责任归属。以下 5 张表构成最小可行闭环(答辩演示够用,代码量可控):

-- 1. 资产主表(含全生命周期状态) CREATE TABLE asset ( id BIGINT PRIMARY KEY AUTO_INCREMENT, code VARCHAR(50) NOT NULL UNIQUE COMMENT '资产编号,如 SY20250001', name VARCHAR(200) NOT NULL COMMENT '资产名称', category VARCHAR(50) NOT NULL COMMENT '类别:仪器/家具/IT设备', price DECIMAL(12,2) NOT NULL COMMENT '购置价格', purchase_date DATE NOT NULL COMMENT '购置日期', status ENUM('IN_STOCK','IN_USE','UNDER_MAINTENANCE','SCRAPPED') DEFAULT 'IN_STOCK' COMMENT '当前状态', location VARCHAR(100) COMMENT '存放位置:XX楼301室', creator_id BIGINT NOT NULL COMMENT '创建人ID(关联user表)', created_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 2. 使用人表(一人可领多设备,一设备可多人共用) CREATE TABLE user_asset ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL COMMENT '使用人ID', asset_id BIGINT NOT NULL COMMENT '资产ID', assign_date DATE NOT NULL COMMENT '领用日期', return_date DATE COMMENT '归还日期(为空表示未归还)', remark VARCHAR(200) COMMENT '领用说明', FOREIGN KEY (user_id) REFERENCES user(id), FOREIGN KEY (asset_id) REFERENCES asset(id) ); -- 3. 审批流程表(支撑申购/报废/调拨) CREATE TABLE approval_flow ( id BIGINT PRIMARY KEY AUTO_INCREMENT, type ENUM('PURCHASE','SCRAP','TRANSFER') NOT NULL COMMENT '审批类型', applicant_id BIGINT NOT NULL COMMENT '申请人', target_id BIGINT NOT NULL COMMENT '目标资产ID(申购时为空)', status ENUM('PENDING','APPROVED','REJECTED','CANCELLED') DEFAULT 'PENDING', approver_id BIGINT COMMENT '审批人ID(多级审批需扩展)', approved_time DATETIME COMMENT '审批时间', remark TEXT COMMENT '审批意见' ); -- 4. 用户表(角色字段决定权限) CREATE TABLE user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL COMMENT 'BCrypt加密', real_name VARCHAR(50) NOT NULL, role ENUM('ADMIN','DEPT_HEAD','TEACHER','STAFF') NOT NULL COMMENT '角色', dept VARCHAR(100) COMMENT '所属院系/部门', email VARCHAR(100) COMMENT '邮箱(用于邮件通知)' ); -- 5. 日志表(答辩时展示系统健壮性) CREATE TABLE operation_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, operation_type VARCHAR(50) NOT NULL COMMENT 'ADD_ASSET/UPDATE_STATUS/EXPORT_EXCEL', detail TEXT COMMENT '操作详情JSON,如{"assetCode":"SY20250001","oldStatus":"IN_STOCK","newStatus":"IN_USE"}', ip VARCHAR(45) COMMENT '操作IP', created_time DATETIME DEFAULT CURRENT_TIMESTAMP );

设计理由:

  • asset.status用 ENUM 而非 INT,答辩时可直接解释“状态机设计思想”,避免评委追问“为什么不用数字编码”;
  • user_asset表解耦“人”与“物”,支持同一设备被多个教师共用(如实验室共享仪器),也支持一人领多台笔记本;
  • approval_flow表不硬编码审批节点,用type字段区分场景,后续扩展“院系初审→国资处终审”只需加level字段,不破坏现有结构;
  • operation_log.detail存 JSON 而非字段拆分,方便记录任意操作上下文,且导出日志时可直接解析为表格。

3. 前端落地:Vue3 + Pinia + Element Plus 的最小可行交互链路

3.1 为什么选 Vue3 而非 React 或纯 HTML?Composition API 的答辩优势

Vue3 的 Composition API(setup()+ref()/reactive())在毕业设计中是“隐形加分项”:它让代码组织更贴近业务逻辑而非框架语法。比如“资产领用”功能,React 需要useState+useEffect+useCallback三重嵌套,而 Vue3 可以把“获取可用资产列表”、“选择领用人”、“提交领用申请”三个步骤封装在同一个setup()函数里,答辩时指着代码说:“这里const assets = ref([])定义响应式数据,onMounted(() => fetchAssets())自动加载,const handleAssign = async () => { await api.assign(assetId.value, userId.value) }就是核心逻辑——没有多余抽象,评委一眼看懂”。
初始化命令(推荐 Vite + Vue3.4+):

npm create vite@latest asset-system -- --template vue cd asset-system npm install # 安装关键依赖 npm install element-plus@2.4.0 pinia@2.1.7 axios@1.6.7 @vueuse/core@10.9.0

注意:Element Plus 2.4.0 是 Vue3 兼容最稳的版本,2.5.x 开始引入ElDescriptions等新组件但部分文档未同步;Pinia 2.1.7 支持defineStore的 TypeScript 类型推导,比 Vuex 更轻量;@vueuse/core提供useLocalStorage(存 token)、useDateFormat(格式化日期)等开箱即用工具,减少手写工具函数。

3.2 Pinia 状态管理:如何用一个 store 管理全局登录态与菜单权限?

毕业设计不需要复杂状态流,但必须体现“权限控制”这个核心点。Pinia 的defineStore可将登录态、用户信息、菜单权限集中管理,且天然支持 SSR(虽毕设不用,但提一句显专业):

// stores/user.ts import { defineStore } from 'pinia' import { login, getUserInfo } from '@/api/auth' import { User } from '@/types/user' interface UserState { token: string | null userInfo: User | null menuList: string[] // 权限码数组,如 ['asset:list', 'asset:assign', 'report:export'] } export const useUserStore = defineStore('user', { state: (): UserState => ({ token: localStorage.getItem('token'), userInfo: null, menuList: [] }), actions: { async login(username: string, password: string) { const res = await login(username, password) // 调用后端 /login 接口 this.token = res.data.token localStorage.setItem('token', res.data.token) await this.loadUserInfo() }, async loadUserInfo() { if (!this.token) return const res = await getUserInfo() this.userInfo = res.data // 后端返回权限码列表,前端据此控制菜单显示 this.menuList = res.data.permissions || [] }, logout() { this.token = null this.userInfo = null this.menuList = [] localStorage.removeItem('token') } }, getters: { isLoggedIn: (state) => !!state.token, hasPermission: (state) => (code: string) => state.menuList.includes(code) } })

关键点说明:

  • menuList存的是权限码(字符串数组),而非角色名(如'ADMIN'),因为实际业务中常有“院系负责人可审本院报废,但不可审其他院系”——用权限码可精细控制到scrap:approve:dept_001;
  • hasPermissiongetter 返回函数,组件中可直接v-if="$store.hasPermission('asset:assign')",比v-if="userStore.menuList.includes('asset:assign')"更语义化;
  • localStorage存 token 是毕业设计合理方案(无需 JWT 解析),答辩时可说明“生产环境应配合 HttpOnly Cookie,此处为简化演示”。

3.3 Element Plus 组件实战:用el-table+el-pagination实现资产列表分页

高校资产列表需支持按编号、名称、状态、院系筛选,且每页20条(符合答辩演示节奏)。Element Plus 的el-table与el-pagination组合是最佳选择:

<!-- views/AssetList.vue --> <template> <div class="asset-list"> <el-card> <template #header> <div class="flex justify-between items-center"> <span>资产总览({{ total }} 条)</span> <el-button type="primary" @click="showAddDialog = true">新增资产</el-button> </div> </template> <!-- 筛选栏 --> <el-form :model="filterForm" inline> <el-form-item label="资产编号"> <el-input v-model="filterForm.code" placeholder="输入编号" /> </el-form-item> <el-form-item label="状态"> <el-select v-model="filterForm.status" placeholder="全部"> <el-option label="全部" :value="null" /> <el-option label="在库" value="IN_STOCK" /> <el-option label="在用" value="IN_USE" /> <el-option label="维修中" value="UNDER_MAINTENANCE" /> <el-option label="已报废" value="SCRAPPED" /> </el-select> </el-form-item> <el-form-item> <el-button type="primary" @click="fetchAssets">查询</el-button> <el-button @click="resetFilter">重置</el-button> </el-form-item> </el-form> <!-- 表格 --> <el-table :data="assetList" stripe style="width: 100%"> <el-table-column prop="code" label="资产编号" width="120" /> <el-table-column prop="name" label="资产名称" /> <el-table-column prop="category" label="类别" width="100" /> <el-table-column prop="price" label="价格(元)" width="120"> <template #default="{ row }">{{ row.price.toFixed(2) }}</template> </el-table-column> <el-table-column prop="status" label="状态" width="100"> <template #default="{ row }"> <el-tag :type="statusTagType(row.status)">{{ statusText(row.status) }}</el-tag> </template> </el-table-column> <el-table-column prop="location" label="存放位置" /> <el-table-column label="操作" width="180"> <template #default="{ row }"> <el-button size="small" @click="handleEdit(row)">编辑</el-button> <el-button size="small" type="danger" @click="handleDelete(row.id)">删除</el-button> </template> </el-table-column> </el-table> <!-- 分页 --> <div class="mt-4 flex justify-end"> <el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize" :total="total" @size-change="handleSizeChange" @current-change="handleCurrentChange" /> </div> </el-card> <!-- 新增弹窗 --> <AssetDialog v-model="showAddDialog" @saved="fetchAssets" /> </div> </template> <script setup lang="ts"> import { ref, onMounted } from 'vue' import { ElMessage } from 'element-plus' import { fetchAssets, deleteAsset } from '@/api/asset' import AssetDialog from '@/components/AssetDialog.vue' import { Asset } from '@/types/asset' const assetList = ref<Asset[]>([]) const total = ref(0) const currentPage = ref(1) const pageSize = ref(20) const showAddDialog = ref(false) // 筛选表单 const filterForm = ref({ code: '', status: null as string | null }) // 获取列表 const fetchAssets = async () => { try { const res = await fetchAssets({ page: currentPage.value, size: pageSize.value, code: filterForm.value.code, status: filterForm.value.status }) assetList.value = res.data.list total.value = res.data.total } catch (err) { ElMessage.error('加载失败:' + (err as Error).message) } } // 重置筛选 const resetFilter = () => { filterForm.value = { code: '', status: null } fetchAssets() } // 分页事件 const handleSizeChange = (val: number) => { pageSize.value = val fetchAssets() } const handleCurrentChange = (val: number) => { currentPage.value = val fetchAssets() } // 状态标签样式 const statusTagType = (status: string) => { const map: Record<string, string> = { IN_STOCK: 'success', IN_USE: 'primary', UNDER_MAINTENANCE: 'warning', SCRAPPED: 'danger' } return map[status] || 'info' } const statusText = (status: string) => { const map: Record<string, string> = { IN_STOCK: '在库', IN_USE: '在用', UNDER_MAINTENANCE: '维修中', SCRAPPED: '已报废' } return map[status] || status } // 编辑/删除 const handleEdit = (row: Asset) => { // 跳转到编辑页或打开编辑弹窗 } const handleDelete = async (id: number) => { ElMessage.confirm('确定删除该资产?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(async () => { await deleteAsset(id) ElMessage.success('删除成功') fetchAssets() }) } onMounted(() => { fetchAssets() }) </script>

参数说明与答辩话术:

  • v-model:current-page和v-model:page-size是 Vue3 的 v-model 语法糖,比 Vue2 的.sync更直观,可强调“这是 Vue3 的响应式改进”;
  • statusTagType和statusText用对象映射而非switch,体现“可维护性思维”——若新增状态LOANED(外借中),只需在 map 里加一行,无需改逻辑;
  • 删除前用ElMessage.confirm二次确认,是答辩演示时的“用户体验细节”,评委常会留意;
  • fetchAssets的try/catch包裹和ElMessage.error提示,展示“异常处理意识”,避免页面白屏。

4. 权限与安全:Spring Security 6.2 的行级权限控制实现与避坑指南

4.1 为什么毕业设计必须做行级权限?答辩时的“灵魂拷问”答案

导师或评委大概率会问:“你这个系统怎么防止张三看到李四的资产?” 如果只答“用@PreAuthorize("hasRole('ADMIN')")”,会被追问:“那院系负责人只能看自己院系的资产,你怎么控制?”——这就是行级权限(Row-Level Security)的价值。Spring Security 6.2 的@PreAuthorize支持 SpEL 表达式访问方法参数,结合@Query或@SelectProvider,可实现“数据级过滤”。例如:

// AssetService.java @PreAuthorize("@assetSecurityService.canAccessAsset(#assetId, principal.username)") public Asset getAssetById(Long assetId) { return assetRepository.findById(assetId).orElseThrow(() -> new RuntimeException("资产不存在")); } // AssetSecurityService.java(自定义权限校验服务) @Service public class AssetSecurityService { @Autowired private UserRepository userRepository; @Autowired private AssetRepository assetRepository; /** * 判断当前用户是否能访问指定资产 * 规则:ADMIN 可看全部;DEPT_HEAD 只能看本院系资产;TEACHER 只能看自己名下资产 */ public boolean canAccessAsset(Long assetId, String username) { Optional<User> userOpt = userRepository.findByUsername(username); if (userOpt.isEmpty()) return false; User user = userOpt.get(); if ("ADMIN".equals(user.getRole())) { return true; // 管理员全权限 } // 获取资产信息 Optional<Asset> assetOpt = assetRepository.findById(assetId); if (assetOpt.isEmpty()) return false; Asset asset = assetOpt.get(); if ("DEPT_HEAD".equals(user.getRole())) { // 院系负责人:资产所属院系必须匹配(假设 asset 表有 dept 字段) return user.getDept().equals(asset.getDept()); } if ("TEACHER".equals(user.getRole())) { // 教师:检查 user_asset 表中是否存在该资产记录 return userAssetRepository.existsByUserIdAndAssetId(user.getId(), assetId); } return false; } }

答辩话术:

“行级权限不是加个注解就完事,而是把业务规则翻译成代码。比如院系负责人能看到‘本院系所有资产’,我就在canAccessAsset方法里查asset.dept == user.dept;教师只能看‘自己领用的资产’,我就查user_asset关联表。这样既没侵入业务逻辑,又保证了数据安全——即使有人绕过前端直接调 API,后端也会拦截。”

4.2 Spring Security 6.2 配置要点:HttpSecurity 链式配置与 CSRF 处理

SpringBoot3 的 Security 配置必须用SecurityFilterChainBean,旧版WebSecurityConfigurerAdapter已废弃。关键配置如下:

@Configuration @EnableMethodSecurity // 启用 @PreAuthorize 注解 public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) // 毕业设计可关闭,因前后端分离,Token 由前端管理 .authorizeHttpRequests(authz -> authz .requestMatchers("/login", "/register", "/error", "/swagger-ui/**", "/v3/api-docs/**").permitAll() .requestMatchers("/admin/**").hasRole("ADMIN") .requestMatchers("/dept/**").hasRole("DEPT_HEAD") .requestMatchers("/teacher/**").hasRole("TEACHER") .anyRequest().authenticated() ) .formLogin(form -> form .loginPage("/login") // 自定义登录页 .usernameParameter("username") .passwordParameter("password") .defaultSuccessUrl("/dashboard", true) .failureUrl("/login?error=true") .permitAll() ) .logout(logout -> logout .logoutUrl("/logout") .logoutSuccessUrl("/login?logout=true") .invalidateHttpSession(true) .deleteCookies("JSESSIONID") .permitAll() ) .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) ); return http.build(); } // 密码编码器(必须!否则密码比对失败) @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }

关键点说明:

  • csrf.disable():前后端分离项目中,CSRF Token 由前端存储并携带,但毕业设计为简化,关闭 CSRF 更稳妥(答辩时可说明“生产环境应启用并配合前端 Axios 拦截器”);
  • hasRole("ADMIN")中的ADMIN是角色前缀ROLE_的简写,Spring Security 自动补全,所以数据库user.role存ADMIN即可,无需存ROLE_ADMIN;
  • formLogin配置/login路径,前端axios.post('/login')即可触发认证,比手写 JWT 更易演示;
  • PasswordEncoder必须声明为 Bean,否则UserDetailsService加载用户时密码无法比对。

4.3 常见问题排查:Spring Security 6.2 的 4 个血泪踩坑记录

现象 1:登录成功后跳转到/login?error=true,控制台无报错
原因:formLogin.defaultSuccessUrl("/dashboard", true)中true表示强制重定向,但/dashboard路径未在authorizeHttpRequests中配置permitAll()或authenticated(),导致重定向后被拦截。
解决:确保/dashboard在authorizeHttpRequests中有对应规则,如.requestMatchers("/dashboard").authenticated()。

现象 2:@PreAuthorize("@assetSecurityService.canAccessAsset(#assetId, principal.username)")报SpelEvaluationException: EL1008E: Property or field 'principal' cannot be found
原因:未启用@EnableMethodSecurity,或principal对象未正确注入。Spring Security 6.2 中principal是Authentication对象,需确保SecurityContext已设置。
解决:确认@EnableMethodSecurity已添加;在SecurityConfig中添加http.authenticationManager(authenticationManager)(若自定义了AuthenticationManager);或改用#authentication.name替代principal.username。

现象 3:MySQL 中LocalDateTime字段存入后变成0000-00-00 00:00:00
原因:MySQL 8.0+ 默认sql_mode包含NO_ZERO_DATE,而 Hibernate/JPA 默认时区为 UTC,LocalDateTime无时区信息,导致插入时区转换错误。
解决:在application.yml中添加 JDBC URL 参数:jdbc:mysql://localhost:3306/asset_db?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&useSSL=false;并在@Entity类中为LocalDateTime字段添加@Column(columnDefinition = "datetime")。

现象 4:Vue3 前端调用/api/asset/list返回 401,但登录接口返回 200
原因:Spring Security 默认使用JSESSIONIDCookie 管理会话,而 Vue3 的axios默认不发送 Cookie(withCredentials: false)。
解决:在axios请求拦截器中设置withCredentials: true:

// utils/request.ts axios.defaults.withCredentials = true

5. 毕业设计交付物包装:从可运行代码到答辩 PPT 的 3 个硬核技巧

5.1 本地一键启动脚本:让答辩现场 30 秒拉起完整系统

评委最反感“我先启动后端…再启动前端…哦数据库还没连…”。用一个start-all.sh(Linux/Mac)或start-all.bat(Windows)脚本封装所有依赖:

#!/bin/bash # start-all.sh echo "✅ 正在启动高校资产管理系统..." echo "1. 启动 MySQL(端口 3306)..." mysql.server start 2>/dev/null || echo "⚠️ MySQL 已运行或未安装" echo "2. 启动后端(SpringBoot3)..." cd backend mvn spring-boot:run -Dspring-boot.run.profiles=dev > /dev/null 2>&1 & BACKEND_PID=$! echo " 后端 PID: $BACKEND_PID" echo "3. 启动前端(Vue3)..." cd ../frontend npm run dev > /dev/null 2>&1 & FRONTEND_PID=$! echo " 前端 PID: $FRONTEND_PID" echo "🚀 系统启动完成!请访问 http://localhost:5173" echo "💡 如需停止,请执行:kill $BACKEND_PID $FRONTEND_PID"

为什么有效?

  • 答辩时打开终端,敲./start-all.sh,30 秒内浏览器打开http://localhost:5173,输入admin/123456即可演示;
  • 脚本中> /dev/null 2>&1隐藏构建日志,避免评委看到冗长输出;
  • mysql.server start是 Mac Homebrew 安装 MySQL 的命令,Windows 版本可替换为net start mysql;
  • PID 记录便于答辩结束时干净退出,体现工程素养。

5.2 数据库初始化 SQL:含 50 条真实模拟数据,覆盖所有状态

别用INSERT INTO asset VALUES(...)手敲 50 行。用 Python 脚本生成带业务逻辑的测试数据:

# generate_data.py import random from datetime import datetime, timedelta categories = ['教学仪器', '办公家具', 'IT设备', '实验耗材', '图书资料'] statuses = ['IN_STOCK', 'IN_USE', 'UNDER_MAINTENANCE', 'SCRAPPED'] depts = ['计算机学院', '机械学院', '材料学院', '经管学院', '外国语学院'] def gen_asset(i): code = f"SY{datetime.now().year}{i:04d}" name = f"{random.choice(['高性能服务器', '激光打印机', '示波器', '办公桌', '投影仪'])} {i}" price = round(random.uniform(2000, 500000), 2) purchase_date = (datetime.now() - timedelta(days=random.randint(0, 1800))).strftime('%Y-%m-%d') status = random.choices(statuses, weights=[30, 40, 15, 15])[0] location = f"{random.choice(depts)}{random.randint(101, 500)}室" dept = random.choice(depts) return f"('{code}', '{name}', '{random.choice(categories)}', {price}, '{purchase_date}', '{status}', '{location}', '{dept}')" # 生成 50 条 inserts = [gen_asset(i) for i in range(1, 51)] sql = "INSERT INTO asset (code, name, category, price, purchase_date, status, location, dept) VALUES\n" + ",\n".join(inserts) + ";" with open("init_data.sql", "w", encoding="utf-8") as f: f.write(sql) print("✅ init_data.sql 生成完成!共 50 条资产数据")

运行后得到init_data.sql,答辩前执行:

source init_data.sql; -- 再插入 5 个用户(admin/teacher/dept_head 各一个) INSERT INTO user (username, password, real_name, role, dept, email) VALUES ('admin', '$2a$10$QqGZ...BCrypt哈希', '系统管理员', 'ADMIN', '国资处', 'admin@univ.edu'), ('zhangsan', '$2a$10$...', '张三', 'TEACHER', '计算机学院', 'zhang@cs.univ.edu'), ('lisi', '$2a$10$...', '李四', 'DEPT_HEAD', '计算机学院', 'li@cs.univ.edu');

答辩价值:

  • 展示“数据驱动开发”思维,评委可当场筛选“计算机学院在用资产”,验证行级权限;
  • init_data.sql放进 Git 仓库,体现“可复现性”,比口头说“我有测试数据”更可信。

5.3 答辩 PPT 的一页技术架构图:去掉所有云图标,只画真实组件

别用阿里云/腾讯云图标堆砌“高大上”。毕业设计架构图要体现“你做了什么”,而非“用了什么云服务”。我的 PPT 架构页长这样:

┌─────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ Vue3 前端 │───▶│ SpringBoot3 后端 │───▶│ MySQL 8.0 │ │ (Vite + Pinia) │ │ (JDK17 + MyBatis) │ │ (本地 Docker) │ └────────┬────────┘ └────────┬────────┘ <p> <a href="https://download.csdn.net/download/qq_41464123/91733119" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 3:07:40

群晖NAS无公网IP远程访问MySQL:Docker部署与QuickConnect/IPv6实践

我去年给一台群晖 DS920 装了 MySQL 和 phpMyAdmin&#xff0c;当时的出发点很简单&#xff1a;手上有几套小项目的数据要统一管理&#xff0c;不想每次都打开电脑进桌面版工具&#xff0c;平时用浏览器点点就能维护库表结构。真正被卡住的&#xff0c;是后面的远程访问——人在…

作者头像 李华
网站建设 2026/10/7 3:07:00

Nginx Proxy Manager实战:告别IP加端口,统一内网服务域名

1. 先别急&#xff0c;聊聊这段痛苦的"IP加端口"日子我猜你和我一样&#xff0c;电脑上存着一大堆书签&#xff0c;全是类似192.168.1.5:3000、192.168.1.5:8080、192.168.1.8:5601这样的地址。每次想打开个服务&#xff0c;都得先回忆那串数字&#xff0c;要不就在路…

作者头像 李华
网站建设 2026/10/7 3:06:47

Linux挂载Windows磁盘全攻略:NTFS驱动、fstab配置与报错排查

装过双系统&#xff0c;或者手头常备一块NTFS格式移动硬盘的朋友&#xff0c;大概率都遇到过“Linux挂载Windows磁盘”这个需求&#xff1a;插上盘&#xff0c;lsblk还能看见设备&#xff0c;但点进文件管理器却是只读&#xff0c;或者干脆mount就报wrong fs type。这篇文章我把…

作者头像 李华
网站建设 2026/10/7 3:06:28

转盘抽奖前端实现:可乱序、加权随机与动画控制

转盘抽奖这种需求&#xff0c;一说出来大家脑子里基本都是同一个画面&#xff1a;一个大圆盘&#xff0c;指针一停&#xff0c;奖品到手。但实际做起来&#xff0c;真正考验人的不是转盘有多好看&#xff0c;而是抽奖结果到底怎么定——尤其是接到"抽奖逻辑&#xff0c;可…

作者头像 李华
网站建设 2026/10/7 3:05:48

FZ-PanDA 5.72 离线部署与任务调度实战:从解压到批量编排

简介&#xff1a;FZ-PanDA5.72版是一款面向机器人视觉检测领域的仿真软件&#xff0c;主要服务于自动化生产线、智能制造与工业4.0场景下的工程师及研究人员。它围绕CCD视觉引导任务&#xff0c;帮助用户在虚拟环境中预演机器人动作、调整参数并测试多种工况&#xff0c;从而降…

作者头像 李华
网站建设 2026/10/7 3:05:41

BERT中文情感分类实战:从模型选型到微调避坑全解析

简介&#xff1a;这是一个基于BERT模型的中文文本情感分类毕业设计项目&#xff0c;面向计算机相关专业正在准备大作业、毕业设计的学生&#xff0c;也适合需要NLP项目实战练习的Python学习者。项目经导师指导并认可&#xff0c;评审分98分&#xff0c;所有源码均在本地编译调试…

作者头像 李华