资讯详情

SpringBoot+Vue+MySQL实现CRM系统:从设计到部署全解析

📅 2026/10/13 3:57:52 | 华诺云谱 👁 阅读
SpringBoot+Vue+MySQL实现CRM系统:从设计到部署全解析
1. 为什么毕设题库里永远少不了一套CRM系统每年带毕业设计和课程设计总有人来问老师Idea装好了数据库也装了但题目选什么我的回答一般很直接如果你Java基础一般、又想要一套拿得出手的完整系统客户关系管理系统是这个价位里性价比最高的选择之一。SpringBootVue.jsVue.jsMySQL这套组合做出来的CRM系统源码几乎出现在每一届学生的简历里也几乎被每一个需要凑工作量的课设题目清单收录。1.1 为什么偏偏是CRM而不是进销存或OA很多人第一反应是CRM听着不够高大上。但仔细看进销存系统核心就是单据、库存、往来账业务模型很固定除非你在报表上折腾出花否则很难做出差异化。再看OA系统审批流、考勤、公告功能点很杂但真正做起来容易流于表格堆砌答辩时评委一句这个流程是谁审批、怎么保证数据一致性就能问穿。CRM不一样。客户关系管理的核心逻辑天然适合教学演示它的业务主线非常完整从市场线索到客户建档从跟进记录到商机推进最后落到合同、回款整条链路是闭合的。每个环节都可以用增删改查来实现但又不只是增删改查因为中间掺着状态流转、权限划分、统计报表。这意味着你可以用一套标准的SpringBoot三层架构把后端搭完再用Vue把管理界面做出来整个过程不会涉及太多偏门技术又能把Java和前端的基础知识全部覆盖。另外一点非常实际CRM系统的业务场景大家都有生活经验不需要额外科普。就算答辩评委完全不懂代码他也能理解客户数据不能随便删销售看一眼仪表盘就知道这个月还差多少业绩。沟通成本低演示效果好这两点在答辩现场比什么都值钱。1.2 一套合格CRM系统应该覆盖哪些业务先给这套源码定个业务边界。完整的商业CRM可能还包括呼叫中心、客户画像、营销活动但作为毕设和课设做到下面几个模块就足够撑起一套系统了客户信息管理客户的基本资料、所属行业、客户等级、来源渠道支持新增、编辑、删除、条件查询和分页展示。跟进记录管理每个客户下可以写跟进历史记录沟通内容、下次联系时间形成时间线。商机管理把有购买意向的客户转化为商机设置预计成交金额、所处的销售阶段形成漏斗。合同管理商机成交后生成合同记录合同金额、签署日期、交付状态。回款管理合同对应的回款计划与实际回款记录用于统计应收和已收。数据看板首页展示客户总数、商机总额、本月新增客户、合同回款情况通常配几个柱状图和折线图。以这个范围作为源码的功能核心既不臃肿也能让每张数据库表都有自己的存在意义。后面我会讲到这套源码在表结构设计上还有一个非常值得参考的细节——客户和商机的关联方式以及状态字段的枚举处理。这些都是在实际开发中踩过坑才总结出来的。1.3 这套源码适合谁来用把它买回去或者下载下来的通常就三类人大三大四做毕设的学生、做课程设计的在校生、还有想系统性学习前后端分离项目的Java初学者。对第一类人最关心的是论文怎么写、答辩怎么讲、代码怎么跑通对第二类人最关心的是功能是否完整、能否在两周内改出自己的一点特色对第三类人最关心的其实是代码结构是否清晰能不能通过阅读这套工程把SpringBootVue的完整协作流程搞明白。这篇文章会尽量把三类需求都覆盖到。2. SpringBootVueMySQL这套组合到底帮你省了什么2.1 前后端分离的项目结构老式课设喜欢用JSPServlet或者Thymeleaf整个页面前端页面直接丢在后端项目里。好处是部署简单坏处是代码很粘前端和后端耦合在一起改个按钮样式都得重启服务。这套CRM源码用的是前后端分离后端单独跑在某个端口返回JSON数据前端Vue工程单独跑通过HTTP请求调用后端接口。这个结构本身就是加分项因为答辩时候你可以直接说这个项目采用了目前企业开发主流的分离式架构。实际打开工程的时候会看到两个独立目录crm-backend SpringBoot后端工程 ├── src/main/java │ ├── controller Controller层 │ ├── service Service层 │ ├── mapper MyBatis的Mapper接口 │ ├── entity 实体类 │ ├── dto 接收前端参数的载体 │ ├── common 统一返回、异常处理 │ └── config 配置类 └── src/main/resources ├── mapper XML映射文件 └── application.yml crm-frontend Vue前端工程 ├── src │ ├── api 封装请求接口 │ ├── router 前端路由 │ ├── views 页面组件 │ ├── components 通用组件 │ └── store Vuex状态管理 └── vue.config.js这种分层方式一眼看去就是标准的Controller-Service-Mapper三段式每一层干了什么清清楚楚。对初学者来说最大的帮助是以后看任何SpringBoot项目都能按这个套路去拆。2.2 后端选SpringBoot而不是SSH的理由早几年课设还有不少用SSH框架的现在不建议了。SpringBoot把繁琐的XML配置全部自动化了内嵌Tomcat打一个jar包就能跑这对毕设阶段的调试效率提升非常明显。你改完代码不用重启外部服务器项目本身的devtools还会帮你做热部署。更重要的是SpringBoot作为当前主流Java开发框架网上资料多、报错案例多遇到问题基本一搜就有答案。SpringBoot之外还需要配合两个基础设施级的组件一个是MyBatis负责数据库操作一个是Lombok用来减少实体类的getter/setter代码。MyBatis在这套源码里走的是XML映射方式SQL语句看得见、改起来方便。有些同学喜欢用Spring Data JPA但做这种管理类系统其实MyBatis更顺手因为业务报表相关的聚合查询用XML方式写SQL比JPA的派生方法更可控。数据源方面源码里用的Druid连接池。德鲁伊的意义不只是连接池它自带监控页面可以实时查看SQL执行情况。做毕设的时候论文里的技术选型一节可以多写这么一句使用Druid连接池便于对慢查询进行监控和优化这比单纯写HikariCP更有内容可挖。2.3 前端选Vue.js的实际收益很多课设团队选前端框架的时候犹豫到底是Vue还是React。Vue的学习曲线更平缓而且Vue 2版本的生态非常成熟Element UI组件库开箱即用表格、表单、弹窗、分页组件全都现成。这套CRM系统的页面如果不用组件库光客户列表的分页和搜索条件就得写几百行用Element UI之后核心工作就从写样式变成绑数据。前端工程里还有两个值得关注的点。第一是Vue Router的路由守卫通常在permission.js里判断登录状态未登录跳转登录页。这个机制说难不难但能把权限控制这个点从后端延伸到前端答辩时可以讲一讲。第二是Axios的请求拦截器在request.js里统一携带token后端通过这个token解析当前登录用户。别小看这套设计它把整个系统的用户身份串起来了后面改权限、加操作日志都靠它。2.4 MySQL 8.0的选择和表设计思路数据库用的MySQL版本建议用8.0以上。8.0默认的字符集是utf8mb4可以直接存生僻字和表情符号省去老版本一堆乱码问题。另外8.0支持窗口函数如果后续你想在统计查询里加排名、环比字段会比5.7方便很多。整套系统的核心表结构大概长这样表名作用关键字段sys_user系统用户登录、权限关联username, password, real_name, role_idsys_role角色表role_name, role_codecrm_customer客户信息customer_name, level, source, owner_idcrm_contact联系人信息customer_id, contact_name, phone, emailcrm_follow_record跟进记录customer_id, content, next_time, operatorcrm_business商机表customer_id, business_name, amount, stage_statecrm_contract合同表contract_no, customer_id, amount, sign_timecrm_receipt回款记录contract_id, receipt_amount, receipt_time这个设计里最核心的关联线是客户 - 商机 - 合同 - 回款。客户又不能直接跳到合同必须经过商机阶段业务逻辑上更严谨。同时crm_customer上的owner_id指向sys_user的主键代表该客户归谁所有这就是数据权限的基础。3. 功能模块拆解与数据库落地经验3.1 客户信息模块不只是简单的增删改查很多新手做客户管理就做一张CRUD的界面加载列表、填表单、提交、删除、查询。这套源码在基础之上加了两个细节很值得学第一个是逻辑删除。客户表里有一个deleted字段删除客户时执行的是UPDATE crm_customer SET deleted 1 WHERE id ?所有查询语句默认带AND deleted 0。这样做的原因是客户数据往往关联了跟进记录和商机物理删除会破坏历史关联而且答辩时候评委如果问你删了客户那个客户名下的商机怎么办逻辑删除可以很好地化解这个问题。实际开发里为了防止全表扫描时漏掉逻辑删除条件MyBatis的XML中最好把deleted 0条件专门抽出来或者直接在查询工具类中拼进去。第二个是客户去重校验。新增客户时根据客户名称判断库里是否已存在同名校验。这个校验不能只在前端做因为前端校验可以被绕过后端service层也必须查一次数据库。更好的做法是给customer_name加上唯一索引双保险。有些项目为了兼容不同销售人员可以各自维护同名客户会退而求其次用代码校验。这里建议在答辩时主动说明我在数据库层和业务层做了双重校验确保唯一性同时避免直接裸奔的唯一索引报错影响用户体验。3.2 跟进记录与日程提醒跟进记录模块看起来就是一个crm_follow_record表的增删改查但业务逻辑里有一个坑客户的状态和跟进记录的联动。比如一位客户最近一次跟进是三天前列表中应显示即将停滞的标记如果超过七天没有跟进该客户自动标记为需重点跟进。这套源码的做法是新增跟进记录的时候不仅插入follow_record表还会同步更新crm_customer表里的last_follow_time字段。客户列表页就能直接根据last_follow_time和当前时间计算出跟进状态不用跨表join去查最后一条记录。这种冗余最新值的设计在实际系统里非常常见属于用空间换时间的经典做法。日程提醒部分crm_follow_record里有个next_time字段代表下一次计划联系时间。首页的待办提醒逻辑很简单查出next_time在当天之前并且状态为未完成的所有记录。如果你想让系统更智能一点还可以加一个定时任务Spring的Scheduled每天跑一次把过期未跟进记录的消息推送给对应的销售用户这个点子写到论文的创新点里也不错。3.3 商机管理销售漏斗是怎么算出来的商机表crm_business里有几个字段必须设计合理stage_state表示销售阶段比如0-初步接触、1-需求确认、2-方案报价、3-商务谈判、4-赢单、5-输单amount表示预计成交金额。页面上的商机漏斗图本质就是统计每个阶段有多少商机金额总和大概代表这个阶段的潜在业绩。这里有个容易坑到新手的地方商机阶段状态变更需要记录日志。假设一个商机从初步接触推到需求确认如果直接改stage_state字段下次想追溯历史就没了证据。给商机加一张business_stage_log表是个很好的扩展方案记录每次变更前阶段、变更后阶段、操作人、操作时间。这样的设计虽然增大了代码量但答辩时候讲商机阶段可回溯说服力完全不一样。这套源码在商机列表上还有一个亮点商机的金额按阶段汇总并且用名额分配给自己的数据。因为每个销售打开商机列表应该只看到自己名下的商机而不是全公司的。这个通过SQL的WHERE owner_id #{currentUserId}实现。这里要注意如果项目后期引入部门层级这种简单的OWNER过滤就不够用了需要扩展成部门数据权限。3.4 合同与回款的关联查询合同表和回款表是一对多的关系一个合同可以对应多笔回款记录。合同总金额减去已回款金额就是待回款金额。我见过不少项目把已回款金额直接设计成合同表的一个字段每次回款就做UPDATE crm_contract SET received_amount received_amount #{amount}看起来简单但一旦需要删除一笔回款记录就得再做一次减法很容易出错。更稳妥的做法是crm_contract表不冗余已回款金额需要展示的时候通过SELECT IFNULL(SUM(receipt_amount), 0) FROM crm_receipt WHERE contract_id ?实时计算。数据量小的时候完全没性能问题还能保证数据绝对一致。这套源码用的就是这个思路值得参考。回款统计的图表也简单按月份分组求和前端用ECharts画折线图数据源就是一条带GROUP BY的SQL。4. 关键代码实现从接口到页面的串联4.1 统一返回结构与全局异常处理前后端分离项目里后端接口返回的数据格式一定要统一。这套源码会在common包里定义一个Result类大概长这样public class ResultT { private Integer code; // 200成功500失败 private String message; // 提示信息 private T data; // 业务数据 }所有Controller的接口统一返回Result前端Axios拦截器拿到结果后先判断codecode为200才继续处理data否则弹出错误消息。这么做带来的直接好处是前端逻辑不用每个接口单独判断成功失败代码整齐很多。如果你用的是RESTful风格还可以在ControllerAdvice里配合ExceptionHandler把业务异常统一包装成Result避免了每个方法写try-catch。4.2 后端Controller到Service的传递逻辑以客户列表查询为例整个链路是这样的Vue页面发起GET请求携带pageNum、pageSize、customerName、customerLevel等查询参数后端Controller接收后包装成CustomerQueryDTO交给Service。Service层主要负责两件事第一、处理查询条件的拼接逻辑比如customerName不为空就拼likecustomerLevel不为空就拼等于第二、调用Mapper查询列表和总条数返回PageResult结构里面包含list和total前端分页组件用total控制总页码。这里有一个新手容易踩的坑pageNum从1开始还是从0开始。如果前端Element UI的el-pagination组件current-page从1开始后端就应该把pageNum - 1传给MyBatis的PageHelper插件否则第一页数据会查不到。这个问题不解决列表页第一页永远是空的排查半天发现是分页起始值不对。// 分页参数转换示例 PageHelper.startPage(query.getPageNum(), query.getPageSize()); ListCustomer list customerMapper.selectCustomerList(query); PageInfoCustomer pageInfo new PageInfo(list);4.3 Vue前端页面的核心交互前端部分客户管理页面是功能最全的参考页。页面结构在src/views/customer/index.vue里主要分三块顶部搜索区域客户名称输入框、等级下拉框、查询按钮、重置按钮中间的增删改按钮和表格底部的分页组件。表格的el-table绑定后端返回的list分页组件的handleSizeChange和handleCurrentChange分别触发搜索方法重新请求接口。表单处理上新增和编辑共用同一个Dialog弹窗通过判断dialogType来区分是新增还是编辑。提交前先做表单校验客户名称必填、电话格式校验校验通过后调用customerApi.save方法。这里要特别注意编辑提交时一定要带上id字段否则后端分不清是新增还是更新。很多同学改完代码发现编辑之后新增了一条记录原因就是id没传。4.4 权限控制过程从登录到接口鉴权系统里的登录逻辑是这样的用户输入用户名密码后端用BCrypt加密校验成功则返回一个JWT Token前端存在localStorage里。后续每个请求通过Axios拦截器在header里加上Authorization: Bearer xxx。后端用一个拦截器或过滤器统一解析Token解析成功后把用户ID放入ThreadLocal当前请求的Service层就能通过UserContext.getUserId()拿到操作人。这个用户在客户数据上就是owner_id的来源。如果Token缺失或过期统一返回401前端路由守卫检测到401就跳回登录页。必须强调一点前端路由守卫只能是体验优化不能当安全手段。真正的权限判断一定在后端接口上。这套源码在角色权限上做了RequiresPermissions之类的注解切面虽然没有细到按钮级别但Controller层的方法都有对应的权限码答辩时可以把这个设计讲清楚。5. 本地跑通整套源码的完整步骤拿到任何SpringBootVue的源码不要急着改代码先把环境准备好。这里给一份可以直接照做的清单。5.1 本地环境准备JDK建议用1.8或者11两者都可以正常运行这套项目。Maven3.6以上配置过阿里云镜像不然下载依赖会慢到怀疑人生。Node.js14以上版本Vue 2项目在Node 16下一般都能跑但如果遇到node-sass报错建议换Node 14。MySQL8.0以上密码别带特殊字符和application.yml保持一致。开发工具后端用IntelliJ IDEA前端用VS Code各干各的活互不干扰。5.2 初始化数据库源码里一般会带sql目录里面放着建库建表脚本和初始数据。打开MySQL命令行或Navicat执行脚本文件。提醒一句执行前先改数据库名。脚本顶部的建库语句默认可能叫crm_db你自己建一个名叫crm_db的库再导入也行或者把文件里的库名统一替换保证连接配置里能对上。5.3 修改配置并启动后端打开src/main/resources/application.yml重点看三处server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/crm_db?useUnicodetruecharacterEncodingutf8mb4 username: root password: 你的数据库密码 mybatis: mapper-locations: classpath:mapper/*.xml如果端口8080被占用改一个不常用的端口比如8088。改完启动启动类看到SpringBoot的启动日志没有异常控制台出现Started ... Application后端就成功了。这时候可以先在浏览器访问http://localhost:8080/swagger-ui.html看看是否集成了Swagger如果源码里带说明接口文档也有了调试会轻松很多。5.4 安装依赖并启动前端前端目录下打开终端执行npm install。如果这个项目锁定的是yarn也可以用yarn。这一步容易卡住的是网络问题建议把npm registry设置成国内镜像npm config set registry https://registry.npmmirror.com依赖安装完成后执行npm run serve终端会输出一个本地访问地址通常是http://localhost:8081。浏览器打开输入初始账号一般脚本里预置了admin/123456能正常登录并看到首页看板说明前后端已联通。5.5 运行中常见的三个报错第一跨域报错。前端页面能打开但登录接口请求失败浏览器控制台出现CORS policy。解决办法是在后端增加一个跨域配置类允许来自前端开发服务器的请求。如果源码里已经配了检查一下allowOriginPatterns是否覆盖了你的前端地址。第二数据库连接失败。典型提示是Access denied for user rootlocalhost说明application.yml里的用户名密码和本地MySQL实际认证不匹配。还有一种情况是密码里有YAML解析时会出问题建议密码改为纯字母数字组合。第三前端接口404。打开前端页面后点击查询接口返回404很可能是后端端口改了但前端请求的baseURL没改。检查src/api/request.js里的baseURL配置让它指向实际的后端地址。6. 从能运行到答辩稳的进阶改造思路6.1 论文目录怎么搭最稳毕设论文要的不是炫技而是让评委觉得你的项目系统化、工程化、有深度。推荐一个稳妥的目录结构第一章 绪论写研究背景与意义、国内外研究现状、主要工作内容第二章 相关技术介绍把SpringBoot、Vue、MySQL、MyBatis逐个介绍一遍重点写选型理由第三章 系统分析可行性分析技术/经济/操作、需求分析角色分析、功能需求、数据需求第四章 系统设计总体架构设计、功能模块设计、数据库设计ER图、表结构第五章 系统实现每个核心模块的页面截图核心代码片段实现说明第六章 系统测试测试计划、功能测试用例、测试结果答辩评委最喜欢问的几个点集中在第四章为什么这样设计表为什么客户和商机分开状态字段为什么用int不用varchar你在论文里把这些逻辑提前写清楚答辩就已经赢了一半。6.2 哪些改进点能显著增加创新分大部分毕设CRM都是套壳标准功能想拿高分就要在功能边界上做一点差异化。性价比最高的几个扩展方向一是数据可视化增强。首页看板从天维度提升到周维度增加环比增长、成交率趋势、销售排行榜Top10。用ECharts做不需要太重的工作量论文里却可以写基于数据可视化技术实现销售运营监控。二是导入导出功能。客户数据支持Excel导入导出使用Apache POI或EasyExcel。这一个功能就能让你的系统从纯页面操作升级到满足真实办公场景很多评委对导入导出都有天然好感因为工作中确实用得上。三是轻量级消息提醒。基于SpringBoot的Scheduled定时任务每天早上批量检查当天需要跟进的客户用邮件或者系统内消息推送给对应销售用户。这个点综合了定时任务、消息推送、业务逻辑技术含量适中创新点很硬。四是多条件组合报表。商机报表不只要看总量还要支持按负责人、按客户等级、按时间段筛选。这些功能不用全做选一个做得精致写在论文的核心功能里比堆十个普通页面强得多。6.3 答辩现场必须准备的四个问题第一问你这个项目的角色权限是怎么实现的答后端采用JWT做身份认证Token中携带用户ID拦截器解析Token后得到当前用户权限上通过自定义注解和切面对接口进行角色校验前端配合路由守卫控制页面访问。第二问客户列表查询的数据量越来越大怎么办答目前用分页查询和条件索引控制如果数据量继续增长可以考虑在客户表中增加创建时间索引并将冷数据归档到历史表。第三问为什么选择逻辑删除而不是物理删除答客户删除后仍需要保留销售记录用于统计分析物理删除会破坏数据连贯性逻辑删除只改变数据状态不影响历史数据的引用。第四问你的表和表之间怎么保证数据一致性答通过外键逻辑关联不让数据库物理外键而是在业务层控制比如合同必须关联到已赢单的商机新增回款前校验合同状态未关闭核心操作使用事务保证一致性。6.4 实际写完这个项目我最想提醒你的几件事自己完整跑一遍并把功能改出花来之后有几个感受很想分享给后来的人。第一不要把大量时间花在调样式上Element UI默认样式已经足够干净把时间省下来写完整的SQL联调更值。第二客户模块一定要把owner_id这个字段吃透理解每个用户只能看到自己客户这条规则之后所有模块的数据范围就都通了。第三接口尽量都做成RESTful风格方法名统一用资源名而不是动词比如POST /customer、DELETE /customer/{id}这比那种/customer/deleteById的接口看起来专业太多。还有一个很多人容易忽略的细节答辩演示之前一定要把数据库重新初始化一遍。我自己当年写过一次系统演示现场点进去首页上全是测试时残留的垃圾数据非常尴尬。初始脚本里给的那几条演示数据足够展示功能就别把私下测试的售后数据留在库里了。如果你拿到这套源码建议先跑通再读代码最后动手改一个模块。把客户到商机到合同这条主链路完全打通之后你对SpringBootVue的工作方式基本上就有整体认知了。这个项目能带给你的也远不止一个毕设分数。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑