首页 / 资讯中心 / 文章详情

SpringBoot+Vue打造CRM客户管理系统:从数据库到部署全解析

SpringBoot+Vue打造CRM客户管理系统:从数据库到部署全解析 ★ FEATURED ARTICLE
这年头打开毕设选题列表客户关系管理系统CRM永远是那个最眼熟的选项。原因很直白它能把增删改查、分页搜索、登录鉴权、统计报表、前后端交互这些Java Web毕设的核心考核点全部装进一个项目里而且业务逻辑贴近真实场景答辩时讲出来不虚。SpringBoot Vue.js这套组合更是当前全栈开发的主流搭配后端用SpringBoot快速搭好接口服务前端用Vue.js配合Element UI把管理后台页面撑起来源码、SQL脚本、接口文档三件套齐备意味着你可以完整跑通、逐行读懂、二次改造。这篇文章我打算按一个实际项目复盘的方式来讲不铺概念只讲那些源码注释里不会写的东西数据库表为什么这么拆、接口文档到底要写到什么粒度、联调时最容易炸的坑、答辩时哪些点最加分。如果你手头正拿着这套项目准备跑起来或者打算拿它改成自己的毕设跟着往下看就行。1. 项目整体认知与方案设计1.1 这个CRM项目到底做了什么毕设场景下的CRM不是要做成Salesforce那种庞然大物而是把核心业务闭环走通。从模块划分上看这个项目包含几个固定板块客户管理负责客户信息的新增、编辑、删除和分页检索联系人管理把客户下面的人维护起来毕竟你跟进客户联系的是具体的人跟进记录是整个系统的灵魂每一次电话、拜访、聊天内容都沉淀在这里并且要管理下次跟进时间商机管理把有意向的客户转化为商机标注金额和阶段合同管理记录成单结果统计报表用图表展示客户来源、销售业绩等数据系统管理则负责用户、角色、菜单权限。我见过很多学生拿到项目后第一件事是跑起来截图然后发现不知道从哪开始讲。我建议你先把这个闭环在脑子里过一遍登录系统 - 新建客户 - 添加联系人 - 记录跟进 - 转化商机 - 签订合同 - 首页报表看到数据变化。这一步走通了整个项目的逻辑主线就掌握了答辩时按这条线讲比按页面讲高出好几个档次。1.2 为什么选这套技术栈选SpringBoot而不是SSH或者Servlet原生不是因为它新而是因为它是目前Java后端开发事实上的标准你毕业找工作写简历SpringBoot是基础项。它最大的优势是约定大于配置内嵌Tomcat一个mvn spring-boot:run就能起服务省掉一大堆XML配置这也让毕设项目的代码量保持在可控范围内。前端选Vue.js核心是组件化和数据驱动。一个客户管理页面拆成表格组件、表单弹窗、分页组件、搜索条件框各自独立互不干扰数据通过双向绑定自动更新视图。配合Element UI后台管理界面的常见组件基本都现成不用自己造轮子。持久层用MyBatis-Plus而不是纯MyBatis原因也很实际单表CRUD几十个方法不用手写SQLBaseMapper直接继承复杂查询再写XML。这套组合能在两三周内把项目做完把省下来的时间花在业务理解、报表和答辩准备上价值更高。1.3 项目目录结构速览项目按前后端分离组织后端是标准的SpringBoot分层结构controller层只做参数接收和结果返回service层处理业务逻辑mapper层管数据库操作entity对应数据库表config放各种配置类common放统一返回值、异常处理、工具类。前端用Vue CLI或Vite初始化src下面分views页面、components组件、router路由、store状态管理、api接口请求、utils工具封装。这套结构本身就是答辩加分项因为它是行业标准分层不是随便堆代码。2. 数据库设计与SQL脚本的落地2.1 表结构怎么设计关系怎么拆CRM系统的数据库设计核心是以客户表为中心向外辐射出联系人和跟进记录向上关联用户和权限体系。我在实际设计时习惯把表分成两大块系统管理区和业务数据区。系统管理区是RBAC权限模型一共四张表用户表sys_user、角色表sys_role、菜单表sys_menu以及用户角色关联表sys_user_role。如果做细一点还有角色菜单关联表sys_role_menu。这套模型讲的是用户属于什么角色角色拥有哪些菜单权限页面上的按钮权限可以基于菜单表的权限标识再控制。业务数据区里crm_customer是绝对核心。字段一般包含客户名称、联系方式、客户级别、所属行业、客户来源、跟进状态、负责人ID。这里有一个容易被忽略的设计点负责人ID关联的不是客户表而是sys_user表这决定了后续做数据权限隔离比如每个销售只能看到自己的客户时的实现成本。crm_contact联系人表通过customer_id关联客户表一个客户下面挂多个联系人。crm_follow跟进记录表记录每次沟通内容和时间同样用customer_id关联客户。crm_opportunity商机表除了客户关联还要有商机金额、所处的销售阶段。crm_contract合同表则记录客户最终成交的合同信息。表与表之间我用逻辑外键而不是物理外键因为物理外键在后期删数据、批量导入时经常造成麻烦而逻辑外键只要保证字段语义一致配合索引就能满足查询性能。2.2 SQL脚本里要埋哪些东西一套能直接跑起来的SQL脚本包含三部分内容建库建表、初始化数据、演示数据。建库语句注意一点字符集一定要指定CREATE DATABASE IF NOT EXISTS crm_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;mysql8的默认排序规则是utf8mb4_0900_ai_ci但如果你用的是MySQL 5.7甚至更早版本这个排序规则是不存在的会直接报错。用utf8mb4_general_ci兼容性最好。初始化数据一定要包含一个可用的管理员账号密码不能存明文用BCrypt加密后写入。BCrypt加密每次生成的密文不同你从别的项目复制密文时要确认加密算法一致否则登录时密码校验永远不通过。角色表和菜单表的数据要能对得上管理员角色至少要绑全部菜单否则登录进去页面是空的。演示数据是很多学生容易忽略的点。客户表里至少要造30到50条数据来源、级别、行业这些字段的取值要有区分度因为分页组件需要数据撑起页数统计图表需要有不同维度的数据才能画出有分析价值的图。跟进记录也要造几条时间线数据不然Vue的时间线组件渲染出来是空的演示效果大打折扣。2.3 MySQL执行SQL脚本的坑命令行执行脚本最常见的报错是文件路径问题。在Windows下用source命令导入路径最好不要带中文和空格否则MySQL解析路径会出各种幺蛾子。Linux下则要注意文件编码脚本文件必须保存为UTF-8否则导入后中文全是乱码。还有两个高频问题一个是执行脚本时提示表已存在这是因为脚本里写了DROP TABLE IF EXISTS如果当前数据库不是目标库这个操作可能把其他表删掉。所以执行前务必确认USE的是哪个库。另一个是外键导致导入顺序不对如果脚本里包含物理外键必须先建父表再建子表或者脚本开头加上SET FOREIGN_KEY_CHECKS0结尾再恢复。这套源码里的脚本一般会做顺序处理但你自己改表结构后重新导出时很容易踩到。3. 后端核心实现SpringBoot接口3.1 分层思想与代码规范SpringBoot后端的核心价值不是能用而是结构清晰、可维护。controller层只做三件事接收参数、调用service、返回结果。所有业务判断和事务处理都在service层。mapper层只做数据库操作不要在mapper里写复杂的业务逻辑。这个分层对应的是高内聚低耦合答辩时如果被问到代码架构照这个思路答基本稳。统一的返回结果是另一个不能省的设计。前端所有请求拿到的响应结构必须是固定的{ code: 200, msg: 操作成功, data: {} }用泛型定义一个Result对象code代表业务状态码204表示操作成功500表示系统异常401表示未登录。前端axios响应拦截器只需要对code统一判断不需要每个页面单独处理异常。如果你手里这套源码用的是这种方式说明封装得不错如果不是建议自己改一下省下的重复代码量非常可观。全局异常处理用RestControllerAdvice配合ExceptionHandler把业务异常、参数校验异常、系统异常分开处理。业务异常可以自定义一个BizException在service层直接throw全局处理器统一转成标准返回格式。这样做的好处是数据库报错、空指针异常不会被直接抛给前端前端看到的永远是一个结构化的错误提示而不是一串看不懂的堆栈。3.2 登录认证与权限控制的实现方式这套项目用的是JWT 拦截器的轻量方案没有引入Spring Security。JWT全称是JSON Web Token登录成功后由后端签发一段tokentoken里编码了用户ID、用户名、过期时间等声明信息并用密钥签名。客户端每次请求在请求头里携带Authorization: Bearer token后端拦截器校验签名、过期时间然后从token里解析出当前用户。相比Spring SecurityJWT方案的优点是轻量、原理直观、答辩好讲。你只需要实现一个拦截器拦截所有非login接口排除掉登录、验证码等白名单路径在preHandle方法里校验token即可。这里有一个你写的时候容易漏的点从token解析出来的用户信息要存到ThreadLocal或者request的attribute里后续的业务代码要用当前用户ID时直接取不用再查一次数据库。菜单权限的实现也不复杂。后端提供查询当前用户菜单列表的接口根据用户角色关联的角色ID去查菜单表前端拿到菜单树后动态渲染侧边栏。要注意菜单表的parent_id字段实现树形结构level字段区分目录和按钮按钮级别的权限标识用来控制页面里的操作按钮是否渲染。3.3 核心接口设计与接口清单接口文档是这个项目的标配接口设计遵循RESTful风格资源用名词复数动作用HTTP方法区分。核心接口大概这些接口地址请求方式功能说明关键参数/api/system/loginPOST用户登录username, password/api/system/user/infoGET获取当前用户信息及菜单token/api/customer/listGET客户分页搜索pageNum, pageSize, keyword, level/api/customerPOST新增客户客户信息JSON/api/customer/{id}PUT修改客户客户信息JSON/api/customer/{id}DELETE删除客户id/api/customer/{id}/followGET查询跟进记录id/api/followPOST新增跟进记录customerId, content, nextTime/api/opportunity/listGET商机列表pageNum, pageSize/api/contract/listGET合同列表pageNum, pageSize/api/report/customer-sourceGET客户来源统计无/api/report/sale-trendGET销售趋势统计时间范围客户分页接口是后端要格外注意的细节。MyBatis-Plus的分页和前端el-pagination组件的参数映射是新人重灾区。后端Page对象接收的pageNum从1开始前端el-pagination的current-page默认也是从1开始两边能对上。但如果用了PageHelper或者自己手写SQL分页LIMIT的起始位置计算方式是(pageNum - 1) * pageSize很容易在这个地方算出负值或漏数据。还有哪些事务问题新增客户时要同步初始化一条跟进记录两个操作必须放在同一个事务里。用Transactional注解时要注意自调用问题——同类内部方法调用事务注解不生效这是Spring AOP的经典坑项目里如果出现过明明加了事务但回滚没生效基本就是这个原因。4. 前端核心实现Vue.js页面组织4.1 前端路由与项目结构前端项目用Vue CLI或Vite创建我的建议是如果你闭着眼都能配webpack就直接Vite但如果你手头这套源码是Vue 2 Vue CLI Element UI那就别折腾升Vue 3毕设求稳不做无谓迁移。Vue 2生态还是Element UI最匹配Vue 3对应Element Plus两者的组件API有差异混用会出兼容问题。路由设计采用嵌套路由结构。登录页是独立路由登录成功后的所有页面都在一个Layout布局组件下Layout包含侧边栏菜单、顶部导航栏和内容区子路由根据菜单权限动态注册。这里有一个关键配置路由守卫。vue-router的全局前置守卫里判断当前路由是否需要登录需要登录且store里没有token时直接重定向到/login。这是整个前端权限链路的入口如果漏了刷新页面后会闪一下空白然后被后端401拦回来体验很差。4.2 请求封装与登录状态管理axios请求封装是必须做的基础工作。baseURL在开发环境指向后端服务地址生产环境指向接口网关或同源地址。请求拦截器统一在header里加token从localStorage读取如果token不存在则跳转登录页。响应拦截器是整个前端对后端的唯一出口后端返回code为401时清空本地存储并跳转登录code非200时用Element UI的Message组件弹出错误提示。这样页面代码里不需要每个请求都写错误处理代码量直接砍掉一半。登录状态管理用Vuex或Pinia取决于Vue版本。state里保存token和用户信息mutations处理赋值actions处理登录、退出登录的逻辑。token持久化到localStorage刷新页面后重新读取并还原到Vuex。用户信息里的菜单列表可以在登录后拉取一次然后动态注册路由这种方式叫动态路由是管理后台的惯用做法。4.3 核心页面组件拆解客户管理页面是前端四个经典组件的组合el-table展示数据、el-form做搜索和新增弹窗、el-dialog承载表单、el-pagination控制分页。用搜索按钮触发查询时重置查询条件后要手动将pageNum置为1否则会出现在第二页搜索却搜不到结果这种让客户疑惑的现象。表格里的客户级别、客户来源这些字段用el-tag组件渲染不同颜色视觉上比纯文本清晰得多实现成本也低。跟进记录页面建议用el-timeline做时间线把该客户的跟进记录按时间倒序排列每条记录显示内容、跟进人、跟进时间、下次跟进时间。新增跟进记录的表单里下次跟进时间用el-date-picker选择支持日期和时间的组合选择。这里要注意前端传给后端的日期格式是2024-06-20 14:30:00这种字符串后端接收的字段类型要对应LocalDateTime并且配置Jackson序列化格式否则前端收到的是带T的UTC格式字符串,timestamp一坨格式化要额外写代码。统计报表页面接ECharts。客户来源用饼图销售趋势用折线图核心思路是后端返回聚合好的数据前端只负责渲染不要在浏览器里做数据聚合。饼图的数据格式是一个数组每个元素是{name: 来源, value: 数量}后端直接用GROUP BY COUNT查出来转成这个结构返回。折线图的横轴日期在SQL里做DATE_FORMAT格式化按天或按月分组统计合同金额。5. 接口文档、联调与打包部署5.1 接口文档到底要写到什么粒度接口文档不是摆设它是你和前端同事或者答辩时老师检查你自己写的代码之间的契约。一份合格的接口文档每个接口必须包含接口地址、请求方式、请求头、请求参数表参数名、类型、是否必填、说明、响应示例、错误码说明。比如客户分页接口的文档应该长这样请求方式GET /api/customer/list请求头Authorization: Bearer {token}参数名类型必填说明pageNumint是页码从1开始pageSizeint是每页条数keywordstring否客户名称关键字levelstring否客户级别筛选响应示例{ code: 200, msg: 操作成功, data: { total: 56, records: [ { id: 1, customerName: 华为技术有限公司, level: A, source: 官网咨询, followStatus: 已跟进, createTime: 2024-05-10 15:30:00 } ] } }这套源码附带的接口文档建议你花一下午时间通读一遍把每个接口的前后端调用关系画出来这对理解整个项目比看代码快得多。如果想把文档升级成自动化管理可以在项目里集成knife4jSwagger的增强版启动后访问/doc.html就是在线文档答辩演示时直接现场调接口效果非常震撼。5.2 前后端联调的高频问题联调阶段是毕设项目里最耗时间的环节80%的问题集中在几个固定点。跨域问题首当其冲开发环境用webpack或Vite的proxy配置最省事生产环境部署在同一域名下则不存在跨域。如果你选择后端开放跨域用CorsFilter配置允许的来源、方法、请求头千万别用allowCredentials(true)配合allowOrigin(*)这种所有来源都行的写法浏览器会直接校验报错。Token过期和请求拦截是第二个高频坑。前端响应拦截器里对401的处理要完整清空localStorage、跳转登录页、防止无限循环跳转。如果调度器在401跳转和登录页之间来回跑刷新一下就崩了。解决办法是跳转之前判断当前路由不是/login再跳。时间格式不一致简直是联调重灾区。后端LocalDateTime默认序列化成数组或者带T的格式前端直接显示出来根本没法看。解决办法是全局配置Jacksonspring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai再配合JavaTimeModule处理LocalDateTime。如果这样还没生效就手动写一个Jackson的ObjectMapper配置类把LocalDateTimeSerializer注册进去。字段命名不匹配同样常出现。数据库字段是snake_casecustomer_nameJava字段是驼峰customerNameMyBatis-Plus默认开启了map-underscore-to-camel-case就没问题但如果你手写SQL映射了自定义resultMap要确认映射列的字段名写对了。5.3 打包部署毕设项目上线演示最稳的方式是前后端分离部署也可以把前端的dist目录复制到SpringBoot的static目录下打成单jar包跑同源部署这种方式只需要一台服务器一个端口部署成本最低。后端打包mvn clean package注意打包时跳过测试防止单元测试失败中断前端npm run build后把dist内容拷贝到后端src/main/resources/static目录。如果你买了服务器用宝塔面板部署会比较省心上传jar和静态资源配置一个Java项目应用装好MySQL执行SQL脚本改一下application.yml里的数据库连接信息即可。6. 从毕设源码到高分答辩的加分思路6.1 答辩前必须准备的几个点把项目跑通只是及格线高分答辩靠的是你能讲清楚为什么这么设计。我建议你花时间想清楚三个问题为什么用JWT不用Session为什么表要这么拆分为什么前端要用Vuex管理状态把这三个问题用口语化、层层递进的方式讲一遍比自己背代码强太多。另一个加分点是画出核心业务流程图。你不需要画复杂的UML一张简单的客户跟进链路图就够了客户进入系统 - 分配责任人 - 记录跟进 - 判断意向 - 转化商机 - 合同签约。这张图能撑起你答辩前五分钟的讲解节奏比你打开浏览器从登录页开始点一遍清晰很多。还有一个容易被忽视的点准备一份演示录屏。答辩现场网络抽风、服务器起不来、MySQL端口被占这类事故我见过很多次。提前用录屏工具把核心业务流程录制一段两分钟的视频关键时候拿出来救场展示的是你对项目的掌控力不是临时抱佛脚的慌张。6.2 项目还可以怎么扩展如果你想把项目做得更有区分度有三个方向投入产出比最高。第一个是定时任务用Spring Boot的Scheduled注解实现合同到期提醒和超期未跟进客户的自动提醒在业务上非常自然技术上又足够简单。第二个是数据权限现在的系统管理员能看到所有客户你可以加一层普通用户只能看到自己负责的客户部门主管看到本部门所有客户超级管理员看到全部在service层拼WHERE条件就能实现但讲出来很加分。第三个是文件上传给客户管理增加附件上传功能合同和资料都能传到服务器本地存储这是一个完整的独立功能点。这里有一套源码的原始版本没有覆盖你自己扩展时要注意扩展业务设计写清楚表结构和流程再动手不要边写边想后面返工成本更高。7. 常见问题与排查技巧实录7.1 SpringBoot版本太高引发的javax转jakarta问题这是近几年跑老项目的第一号拦路虎。网上大量教程和源码基于SpringBoot 2.x但你新建项目时可能拉到SpringBoot 3.x。SpringBoot 3把javax.servlet、javax.validation等一批包换成了jakarta.*前缀如果源码里用的是javax编译就会报包不存在。解决思路很直接锁SpringBoot版本到2.7.x对应JDK 8/11或者把源码里的javax全部改成jakarta。对2.7版本还有个配套问题MyBatis-Plus 3.5.1以前对SpringBoot 3支持不完善需要选对starter版本。版本锁定用Maven管理在pom.xml里指定版本号后重新加载依赖不要指望直接用最新版能自动兼容。7.2 npm install失败与Node版本冲突前端依赖安装失败大多和Node版本有关。Vue 2项目用node-sass时如果Node版本太高node-sass编译必挂。要么把node-sass替换成sassdart-sass要么用nvm安装旧版Node比如14或16再装依赖。还有一类情况是package-lock.json的registry源有问题可以先切换淘宝镜像源再重新安装。7.3 数据库连接与时区报错项目启动连不上MySQL先检查application.yml里的连接串。MySQL 8的驱动类是com.mysql.cj.jdbc.Driver连接串要带serverTimezoneurl: jdbc:mysql://localhost:3306/crm_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrueallowPublicKeyRetrievaltrue这个参数在MySQL 8下经常被忽略少了它会报Public Key Retrieval is not allowed。时区参数如果漏了时间字段会差8个小时查数据时感觉就像数据消失了。7.4 页面空白、接口404、刷新后404运行起来首页一片白优先开浏览器F12看Console报错如果是请求接口404检查后端Controller的RequestMapping路径是否和前端api文件里写的路径一致这种问题两个人写代码时尤其容易发生。如果是部署后刷新页面404是前端路由用了history模式刷新时服务器找不到对应路径解决方法是后端转发或改成hash模式。毕设演示场景用hash模式最省心不挑服务器。7.5 高频问题速查表报错现象大概率原因解决方案启动报ClassNotFound: jakarta.*SpringBoot 3与旧代码javax不兼容锁版本到2.7.x或改jakarta导入登录接口返回500BCrypt密文与算法不匹配用相同算法重新生成密码密文查询列表字段为空数据库字段与Java映射不一致检查map-underscore-to-camel-case配置时间显示成数组LocalDateTime未配置序列化配置Jackson日期格式跨域请求被拦截后端未开CORS或前端proxy未配置开发用proxy生产开CORS分页第一页正常第二页异常分页起始值计算错误统一pageNum从1开始Vue页面刷新后白屏路由history模式刷新404改hash模式或后端try_files这套项目的技术栈本身不复杂复杂的是第一次接触全栈项目时那条完整链路——数据库建表到后端接口到前端页面到最终部署每一步都有各自的暗坑。我当初自己动手改这个项目时前后折腾了大概一周才把全部流程捋顺回头来看最有价值的不是项目本身能不能跑而是通过它把前端发请求、后端查数据库、数据格式互相匹配这件事彻底想明白了。最后分享一个个人习惯拿到任何一套源码先通读SQL脚本再打开接口文档最后才看代码。表结构和接口清单是项目的骨架代码只是填充在骨架上的肉。把骨架理顺了代码怎么看都不会迷路。答辩前多画几遍客户跟进流程图把每个接口的参数和返回结构默写一遍比背代码有效率得多。祝你一次成功跑通答辩顺利。
阅读完成 · 觉得有帮助?
咨询建站