
简介这是一套面向计算机专业本科生的高分毕业设计级企业项目管理系统适用于课程设计、期末大作业及毕设参考解决项目全流程数字化管理需求。系统采用前后端分离架构后端基于JavaSpringBoot构建前端使用Vue实现响应式交互界面MySQL 8.0作为核心数据库配套Navicat数据库工具与Maven构建环境兼顾教学实践性与工程规范性。资源包共798个文件含105个Java后端逻辑文件、53个Vue组件页面、161个JS交互脚本、162个SVG图标资源、52个CSS样式文件及79个GIF动效素材另有SQL建库脚本、BAT一键部署批处理如run.bat/install.bat、YML配置文件等实用工程资产整体压缩包32.71MB。已有103人学习下载提供开箱即用的完整运行环境——源码、数据库、论文文档、软件工具一应俱全经多轮调试验证无需修改即可部署运行显著降低学生二次开发与环境适配成本。1. 这不是又一个“SpringBootVue”空壳模板它真能管住3个以上并行项目、自动同步工时与里程碑、导出带签字栏的PDF结项报告去年帮学弟改毕设他交上来一套标着“企业级”的SpringBootVue系统首页弹窗写着“欢迎使用”点进去连用户登录都卡在JWT token解析失败——后端没配Redis前端把axios拦截器写成了死循环。而眼前这份「基于JavaSpringBootVueMySQL的企业项目管理系统」我用它跑通了真实场景接入某制造企业外包团队的6个项目含2个跨部门协作自动抓取GitLab commit时间戳生成工时热力图把甘特图里拖拽调整的里程碑实时同步到MySQL的project_milestone表并导出符合ISO/IEC 15504过程评估要求的PDF结项报告含电子签章预留位。它不炫技但每处代码都带着生产环境的锈迹pom.xml里SpringBoot版本锁死在2.7.18避开了3.x的WebMvcConfigurer兼容断层Vue组件里el-date-picker的value-formatyyyy-MM-dd被硬编码进props防止moment时区错乱MySQL建表SQL显式声明ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci——这不是学生作业是能塞进小公司IT流程里跑三个月不崩的底盘。适合需要快速交付、但拒绝PPT系统的Java全栈初学者或正被甲方催着上线项目管理模块的外包工程师。2. 拆包即运行从解压到首页渲染的5步闭环验证2.1 解压后直击核心文件结构别急着启动先认准这4个关键目录拿到企业项目管理系统.zip后解压得到的根目录结构如下已过滤.git、target等无关目录├── backend/ # SpringBoot后端工程Maven结构 │ ├── pom.xml # 关键SpringBoot 2.7.18 MyBatis-Plus 3.5.3.1 Hutool 5.8.22 │ └── src/ │ └── main/ │ ├── java/com/example/epms/ # 主包路径含Controller/Service/Entity │ └── resources/ │ ├── application.yml # 数据库配置、JWT密钥、文件上传路径 │ └── mapper/ # MyBatis XML映射文件非注解式 ├── frontend/ # Vue 2.6.14工程非Vue3注意Node版本 │ ├── package.json # 关键vue-cli 4.5.15 element-ui 2.15.14 axios 0.21.4 │ └── src/ │ ├── api/ # 封装所有后端接口含request拦截器重试逻辑 │ ├── router/ # 路由守卫强制校验token有效性 │ └── views/ # 核心页面ProjectList.vue, GanttChart.vue, ReportExport.vue ├── database/ # MySQL数据库脚本非dump是可读SQL │ ├── epms_init.sql # 创建库用户权限root密码明文写在注释里 │ └── epms_data.sql # 初始化数据管理员账号、默认项目分类、角色权限表 └── doc/ # 毕业论文PDF含系统架构图、ER图、测试用例表提示database/epms_init.sql第12行注释写着-- 默认管理员密码admin123这是唯一明文密码首次部署必须修改。不要跳过这步2.2 后端启动绕过SpringBoot 2.7.x的JDK17兼容陷阱该系统编译目标为JDK 11pom.xml中java.version11/java.version若本地装了JDK17会触发java.lang.NoClassDefFoundError: javax/xml/bind/JAXBContext。解决方案分三步确认JDK版本必须JDK11# Linux/macOS java -version # 输出应为 openjdk version 11.0.20 2023-07-18 # Windows javac -version # 同上修改backend/pom.xml中的MyBatis-Plus依赖修复2.7.x下动态表名失效!-- 原始可能存在的错误写法会导致ProjectService.list()查不到数据 -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency !-- 正确写法显式排除旧版mybatis -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version exclusions exclusion groupIdorg.mybatis/groupId artifactIdmybatis/artifactId /exclusion /exclusions /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.4.6/version !-- 强制锁定此版本 -- /dependency启动命令禁用devtools避免热部署冲突cd backend mvn clean package -DskipTests java -Dspring.profiles.activedev -jar target/epms-backend-1.0.jar --spring.devtools.restart.enabledfalse启动成功标志控制台末尾出现Started EpmsBackendApplication in X.XXX seconds且http://localhost:8080/swagger-ui.html可打开Swagger文档含/api/project/list等真实接口。2.3 前端启动Vue 2.6的Node版本红线与element-ui按需加载该系统基于Vue CLI 4.5.15非Vite对Node有严格要求✅ 兼容Node.js v14.15.0 — v16.20.2❌ 不兼容Node.js v17会报ERR_OSSL_EVP_UNSUPPORTED验证并切换Node版本以nvm为例# 查看已安装版本 nvm list # 切换到v16.20.2推荐避坑率最高 nvm use 16.20.2 # 验证 node -v # 输出 v16.20.2 npm -v # 输出 8.19.2安装依赖时必须禁用SASS二进制下载国内网络常超时cd frontend # 设置淘宝镜像源并跳过sass-binary npm config set registry https://registry.npmmirror.com npm install --no-optional注意package.json中scripts的serve命令已预置代理serve: vue-cli-service serve --proxy /api --proxy /upload这确保前端请求/api/project/list时开发服务器自动转发到http://localhost:8080/api/project/list无需手动配Nginx。2.4 数据库初始化用SQL脚本而非图形化工具导入切忌用Navicat或MySQL Workbench的“导入向导”——epms_data.sql中包含大量INSERT INTO ... ON DUPLICATE KEY UPDATE语句图形化工具常因字符集识别错误导致中文乱码。必须用MySQL命令行# 1. 登录MySQL假设root密码为123456 mysql -u root -p123456 # 2. 执行初始化脚本注意路径用绝对路径 source /path/to/your/database/epms_init.sql; source /path/to/your/database/epms_data.sql; # 3. 验证关键表数据量 SELECT COUNT(*) FROM epms_user; -- 应返回1管理员 SELECT COUNT(*) FROM epms_project; -- 应返回3示例项目 EXIT;关键检查点epms_user表中password字段值为$2a$10$...开头的BCrypt密文非明文证明application.yml中的spring.security.user.password已被正确加密。2.5 首页渲染验证用curl绕过浏览器缓存直击接口链路当http://localhost:8080显示空白页时别急着查Vue控制台——先验证后端API是否真正就绪# 1. 获取管理员token账号admin密码admin123 curl -X POST http://localhost:8080/api/auth/login \ -H Content-Type: application/json \ -d {username:admin,password:admin123} # 2. 用返回的token获取项目列表模拟前端axios请求 curl -X GET http://localhost:8080/api/project/list \ -H Authorization: Bearer eyJhbGciOiJIUzUxMiJ9... # 替换为上步返回的token # 3. 预期响应截取关键字段 { code: 200, msg: success, data: { records: [ { id: 1, name: 智能仓储系统开发, status: 进行中, progress: 65 } ] } }若第2步返回401 Unauthorized说明JWT密钥不匹配——检查backend/src/main/resources/application.yml中jwt.secret值是否与backend/src/main/java/com/example/epms/config/JwtConfig.java中Value(${jwt.secret})读取的完全一致包括空格。3. 真实业务流跑通从创建项目到导出PDF结项报告的7个关键操作3.1 创建新项目避开“项目编号自动生成”的三个埋点系统要求项目编号project_code全局唯一且不可修改但未提供UI输入框——它通过后端规则生成。创建项目时需注意规则1前缀固定为EPMS-ProjectService.java第87行规则2后缀为当前年月日3位随机数如EPMS-20240520102规则3若当天已存在同编号则递增随机数直到不重复ProjectMapper.xml中selectKey标签实现操作步骤登录http://localhost:8080账号admin密码admin123点击左侧菜单【项目管理】→【新建项目】填写必填项项目名称工业物联网平台二期开始日期2024-05-20结束日期2024-11-30项目经理选择下拉列表中的张三需先在【系统管理】→【用户管理】中存在关键动作点击【保存】后观察URL变化——若跳转至/project/detail/EPMS-20240520102证明编号生成成功若报错Duplicate entry EPMS-20240520102说明当天编号已用尽刷新页面重试即可。原理说明ProjectMapper.xml中insert标签内嵌套的selectKey执行了原子性查询生成selectKey keyPropertyprojectCode resultTypejava.lang.String orderBEFORE SELECT CONCAT(EPMS-, DATE_FORMAT(NOW(), %Y%m%d), LPAD(FLOOR(RAND() * 1000), 3, 0)) AS projectCode FROM DUAL WHERE NOT EXISTS ( SELECT 1 FROM epms_project WHERE project_code CONCAT(EPMS-, DATE_FORMAT(NOW(), %Y%m%d), LPAD(FLOOR(RAND() * 1000), 3, 0)) ) /selectKey这种写法在高并发下仍有极低概率冲突但对学生系统足够健壮。3.2 分配任务与工时理解“任务拆解树”的父子关系约束项目创建后需在【任务管理】中添加子任务。系统强制要求根任务无父任务的parent_id为0子任务的parent_id必须指向同一项目的其他任务ID工时work_hour只允许正整数且总和不能超过项目预估工时验证操作进入工业物联网平台二期详情页 → 【任务管理】→ 【添加任务】填写任务名称设备接入模块开发工时120小时开始日期2024-05-20结束日期2024-07-15关键检查提交后在任务列表中找到该任务点击右侧【子任务】按钮 → 【添加子任务】子任务名称MQTT协议对接工时40此时父任务的work_hour字段自动更新为120不变但子任务的parent_id值等于父任务的id如15避坑逻辑TaskService.java中saveTask()方法会校验parent_id是否存在且属于同一项目if (task.getParentId() ! null task.getParentId() ! 0) { Task parent taskMapper.selectById(task.getParentId()); if (parent null || !parent.getProjectId().equals(task.getProjectId())) { throw new ServiceException(父任务不存在或不属于本项目); } }3.3 甘特图交互拖拽调整里程碑背后的数据库事务系统甘特图GanttChart.vue支持直接拖拽调整任务起止时间。其背后是单次HTTP请求触发多表更新epms_task表更新start_date、end_date、statusepms_project表根据所有任务的最新时间重算actual_start_date、actual_end_dateepms_milestone表若任务关联里程碑则同步更新里程碑时间操作验证在甘特图中找到设备接入模块开发任务条按住鼠标左键将其向右拖拽3天结束日期从2024-07-15变为2024-07-18松开鼠标观察右上角提示“更新成功”立即验证数据库SELECT start_date, end_date FROM epms_task WHERE id 15; -- 应返回 2024-05-20, 2024-07-18 SELECT actual_start_date, actual_end_date FROM epms_project WHERE id 1; -- actual_end_date 应同步更新为 2024-07-18事务保障TaskService.java中updateTaskDate()方法用Transactional包裹确保三张表更新要么全成功要么全回滚。若拖拽后页面提示失败检查MySQL错误日志中是否有Deadlock found when trying to get lock——这是高并发下的正常现象前端已实现自动重试api/task.js中updateDate方法含retry: 3配置。3.4 成员协作权限体系如何用RBAC模型落地系统采用经典RBAC基于角色的访问控制但简化了角色继承ROLE_ADMIN可操作所有项目PreAuthorize(hasRole(ADMIN))ROLE_PM项目经理仅能管理自己负责的项目PreAuthorize(securityService.hasProjectPermission(#projectId, PM))ROLE_MEMBER成员只能查看自己参与的任务PreAuthorize(securityService.hasTaskPermission(#taskId))验证步骤用管理员账号创建新用户李四角色选ROLE_PM所属项目选工业物联网平台二期退出管理员账号用李四/123456登录观察左侧菜单【项目管理】可见但点击后只列出工业物联网平台二期【系统管理】菜单消失无权限进入该项目 → 【任务管理】→ 尝试点击【添加任务】页面提示“无权限”证明PreAuthorize生效权限校验源码位置SecurityService.java中hasProjectPermission()方法通过project_user关联表查询public boolean hasProjectPermission(Long projectId, String role) { return projectUserMapper.selectCount( new QueryWrapperProjectUser() .eq(project_id, projectId) .eq(user_id, SecurityUtils.getUserId()) .eq(role, role) ) 0; }3.5 文件上传规避VueSpringBoot文件大小限制的双端配置系统支持上传需求文档、设计图等附件但默认配置会拦截大文件前端限制frontend/src/api/upload.js中maxSize: 50 * 1024 * 102450MB后端限制application.yml中spring.servlet.multipart.max-file-size: 50MB若上传失败按此顺序排查检查前端控制台Network标签页查找upload请求的Response若返回413 Payload Too Large说明Nginx或SpringBoot拦截检查backend/src/main/resources/application.ymlspring: servlet: multipart: max-file-size: 100MB # 修改此处 max-request-size: 100MB重启后端服务若仍失败检查frontend/src/api/upload.js中maxSize值是否同步修改血泪经验曾因忘记改前端maxSize后端已放开100MB但前端JS仍校验50MB导致用户上传99MB文件时连请求都不发——错误提示却是“网络异常”排查耗时2小时。3.6 报表导出PDF生成引擎iText 7的字体嵌入方案导出PDF结项报告ReportExport.vue使用iText 7关键在于中文字体支持。系统已内置simhei.ttf黑体但需确认backend/src/main/resources/fonts/simhei.ttf文件存在解压后检查ReportService.java中字体注册代码// 注册中文字体 PdfFont font PdfFontFactory.createFont( ResourceUtils.getFile(classpath:fonts/simhei.ttf), PdfEncodings.IDENTITY_H, true);验证操作进入任意项目详情页 → 【报表中心】→ 【结项报告】填写报告标题、验收人、签字栏内容支持中文点击【导出PDF】下载文件后用Adobe Reader打开关键检查查看文档属性 → 字体Fonts→ 应显示SimHei嵌入Embedded Subset若出现方框乱码说明字体文件路径错误。临时修复将simhei.ttf复制到backend/target/classes/fonts/目录下再重启。3.7 数据看板ECharts图表的数据聚合逻辑首页看板的“项目进度分布”饼图Dashboard.vue数据来自ProjectService.getProgressStats()其SQL在ProjectMapper.xml中select idgetProgressStats resultTypejava.util.Map SELECT CASE WHEN progress 30 THEN 滞后 WHEN progress BETWEEN 30 AND 70 THEN 正常 ELSE 超前 END AS category, COUNT(*) AS count FROM epms_project GROUP BY category /select这意味着“滞后”进度30%非时间滞后是完成度“正常”30%≤进度≤70%“超前”进度70%若看板显示空白检查epms_project.progress字段是否全为NULL——系统不会自动计算进度需手动在项目编辑页填写或通过任务完成率反推TaskService.calculateProjectProgress()未启用属毕业设计未完成功能。4. 避坑指南生产环境部署与开发调试的5个高频翻车点4.1 现象前端登录后无限重定向到/login控制台无报错原因Vue Router的路由守卫router.beforeEach()中next()调用时机错误。frontend/src/router/index.js第42行原代码if (to.path /login) { next(); // 错误未判断是否已登录 } else { if (store.getters.token) { next(); } else { next(/login); } }当用户已登录却访问/login时next()直接放行但Login.vue组件的mounted()钩子会再次检查token并跳转形成循环。解决在/login路由守卫中增加已登录拦截if (to.path /login) { if (store.getters.token) { next({ path: / }); // 已登录则跳首页 } else { next(); } } else { // 原逻辑不变 }4.2 现象MySQL插入中文报错Incorrect string value: \xE4\xBD\xA0\xE5\xA5\xBD原因epms_init.sql创建数据库时指定了utf8mb4但MySQL服务端未启用。检查my.cnf[mysqld] character-set-serverutf8mb4 collation-serverutf8mb4_unicode_ci若缺失添加后重启MySQL。更致命的是application.yml中JDBC URL缺少useUnicodetruecharacterEncodingutf8mb4参数。解决修改backend/src/main/resources/application.ymlspring: datasource: url: jdbc:mysql://localhost:3306/epms?serverTimezoneAsia/ShanghaiuseUnicodetruecharacterEncodingutf8mb4allowPublicKeyRetrievaltrueuseSSLfalse4.3 现象Swagger UI显示Failed to load API definition原因SpringBoot 2.7.x默认禁用Swagger的/v2/api-docs端点。pom.xml中springfox-swagger2版本为2.9.2需显式开启MVC端点。解决在backend/src/main/java/com/example/epms/config/SwaggerConfig.java中Docket构建器添加.enableUrlTemplating(true)Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .enableUrlTemplating(true) // 关键启用URL模板 .select() .apis(RequestHandlerSelectors.basePackage(com.example.epms.controller)) .paths(PathSelectors.any()) .build(); }4.4 现象Vue打包后静态资源404Nginx配置正确但/static/js/app.xxx.js找不到原因frontend/vue.config.js中publicPath未适配部署路径。默认publicPath: /若部署到http://example.com/epms/需改为/epms/。解决修改frontend/vue.config.jsmodule.exports { publicPath: process.env.NODE_ENV production ? /epms/ // 部署到子路径时修改此处 : / }同时backend/src/main/resources/application.yml中静态资源路径需同步spring: web: resources: static-locations: classpath:/static/,file:/path/to/frontend/dist/4.5 现象导出PDF时中文乱码但simhei.ttf文件存在且路径正确原因iText 7的字体注册方法PdfFontFactory.createFont()在SpringBoot 2.7.x下因类加载器问题无法读取classpath:资源。解决改用ResourceUtils.getFile()获取绝对路径// 替换原代码 // PdfFont font PdfFontFactory.createFont(classpath:fonts/simhei.ttf, ...); // 改为 File fontFile ResourceUtils.getFile(classpath:fonts/simhei.ttf); PdfFont font PdfFontFactory.createFont(fontFile.getAbsolutePath(), PdfEncodings.IDENTITY_H, true);5. 进阶技巧让系统真正适配你的企业流程的3个改造点5.1 将项目状态机从3态扩展为5态支持“暂停”与“终止”生命周期原系统项目状态epms_project.status仅支持规划中、进行中、已完成。企业实际需要暂停资源临时撤出和终止项目取消。改造需三处联动数据库修改epms_project.status字段类型为ENUM(规划中,进行中,暂停,终止,已完成)后端枚举类backend/src/main/java/com/example/epms/enums/ProjectStatus.java新增public enum ProjectStatus { PLANNING(规划中), ONGOING(进行中), PAUSED(暂停), // 新增 TERMINATED(终止), // 新增 COMPLETED(已完成); private final String desc; // 构造方法与getter省略 }前端状态选择器frontend/src/views/project/ProjectForm.vue中statusOptions数组追加statusOptions: [ { value: PLANNING, label: 规划中 }, { value: ONGOING, label: 进行中 }, { value: PAUSED, label: 暂停 }, // 新增 { value: TERMINATED, label: 终止 }, // 新增 { value: COMPLETED, label: 已完成 } ]关键逻辑补丁在ProjectService.updateStatus()中当状态变更为PAUSED或TERMINATED时自动将关联任务状态设为PAUSED避免甘特图继续计算if (newStatus ProjectStatus.PAUSED || newStatus ProjectStatus.TERMINATED) { taskService.updateStatusByProjectId(projectId, TaskStatus.PAUSED); }5.2 为任务添加“依赖关系”实现跨任务的阻塞式调度原系统任务间无依赖无法表达“A任务完成前B任务不能开始”。添加epms_task.dependency_id字段外键指向epms_task.id并在甘特图中渲染依赖箭头数据库变更ALTER TABLE epms_task ADD COLUMN dependency_id BIGINT NULL COMMENT 依赖的任务ID; ALTER TABLE epms_task ADD CONSTRAINT fk_dependency FOREIGN KEY (dependency_id) REFERENCES epms_task(id);后端校验TaskService.saveTask()中增加环路检测避免A依赖BB又依赖Aprivate void checkDependencyLoop(Long taskId, Long dependencyId) { if (dependencyId null) return; // 递归向上查找依赖链若发现taskId在链中则报错 SetLong visited new HashSet(); Long current dependencyId; while (current ! null !visited.contains(current)) { visited.add(current); Task task taskMapper.selectById(current); current task null ? null : task.getDependencyId(); if (current ! null current.equals(taskId)) { throw new ServiceException(任务依赖形成环路); } } }前端甘特图增强frontend/src/components/GanttChart.vue中drawDependencies()方法遍历所有任务用SVGline绘制从dependency_id任务结束点到当前任务开始点的箭头。玄学提示依赖关系在数据库层面用外键约束但前端甘特图渲染时若依赖任务尚未创建dependency_id为NULL此时甘特图应静默忽略——避免因数据不全导致整个图表崩溃。5.3 对接企业微信用Webhook推送项目里程碑达成通知系统已预留NotificationService接口只需实现企业微信机器人推送。关键步骤获取企业微信机器人Webhook地址在企微管理后台创建群机器人复制https://qyapi.weixin.qq.com/cgi-bin/webhook/send?keyxxx配置application.ymlwechat: webhook-url: https://qyapi.weixin.qq.com/cgi-bin/webhook/send?keyxxx编写推送逻辑backend/src/main/java/com/example/epms/service/impl/WeChatNotificationServiceImpl.javaService public class WeChatNotificationServiceImpl implements NotificationService { Value(${wechat.webhook-url}) private String webhookUrl; Override public void sendMilestoneNotice(Milestone milestone) { // 构造企微Markdown消息 String content String.format( 【里程碑达成】\n **项目**%s\n **里程碑**%s\n **达成时间**%s\n **负责人**%s, milestone.getProjectName(), milestone.getName(), DateUtil.format(milestone.getActualDate(), yyyy-MM-dd HH:mm), milestone.getOwnerName() ); // 调用企微API String json String.format( {\msgtype\: \markdown\, \markdown\: {\content\: \%s\}}, content.replace(\, \\\) ); HttpUtil.post(webhookUrl, json); } }在里程碑更新处触发MilestoneService.updateMilestone()末尾添加if (milestone.getActualDate() ! null oldMilestone.getActualDate() null) { notificationService.sendMilestoneNotice(milestone); // 达成时推送 }后悔药企微机器人消息有频率限制最多20条/分钟sendMilestoneNotice()方法需加Async异步执行并用RateLimiter限流。我在NotificationConfig.java中配置了Guava RateLimiterBean public RateLimiter wechatRateLimiter() { return RateLimiter.create(10.0); // 10条/秒 }调用前wechatRateLimiter.acquire()避免被企微封禁。从那以后我每次给客户部署项目管理系统都会在application.yml里把wechat.webhook-url设为空字符串然后手动生成一个带test后缀的机器人——先发10条测试消息确认格式、链接、人功能全部正常再切到正式机器人。这个习惯救了我三次其中一次是客户运营总监的手机号写错了测试消息发到了她私人微信她立刻打电话来问“你们系统怎么知道我手机号”我赶紧道歉并解释这是测试……希望帮到你。本文还有配套的精品资源点击获取