ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

SpringBoot+Vue+MySQL贸易行业CRM系统实战:从建表到部署

SpringBoot+Vue+MySQL贸易行业CRM系统实战:从建表到部署 1. 项目拆解与整体技术方案做了这么多年企业级管理系统我越来越觉得CRM这类系统是最适合拿来练手、也最适合用来承接实际业务的技术项目。这次要聊的贸易行业CRM系统算是把SpringBoot后端、Vue前端和MySQL数据库这三个最主流的技术栈全部串联起来的典型例子。标题里写“可直接运行”其实重点想传达的是这套源码不是半成品演示项目而是能真正跑起来、能往里填业务数据的完整系统。先说核心需求。普通CRM可能只需要管客户、管跟进、管销售漏斗但贸易行业的CRM往往多了好几层东西一要管理供应商和采购往来二要管理报价单、销售合同、采购合同这类贸易单据三要跟踪订单的发货、到货、对账状态四还要支撑多角色协作。所以开发这套系统时我并没有直接去套开源CRM的模板改造而是从贸易业务场景出发把客户管理、联系人、产品、询价报价、订单、合同、回款、发货这些模块整体设计了一遍。1.1 技术选型为什么是SpringBootVueMySQL现在Java后端的新框架层出不穷微服务、云原生、响应式编程都很火但SpringBoot依然是中小型管理系统最稳、最有效率的选择。原因很简单社区资料多遇到问题能快速查到解决方案自动配置机制让项目起步非常快生态足够成熟接权限框架、接工作流引擎、接消息队列都有现成方案。这套源码从一开始就定位于“可运行、可部署、可二次开发”SpringBoot天然适合。前端选择Vue更是顺理成章的事。Vue在国内的普及度极高组件化开发思路清晰配合Element UI或者Element Plus这类组件库做管理系统界面效率非常高。你可能在热搜词里也看到了“vue3”“vue路由参数”“vue安装及环境配置”这些词说明很多开发者正在入门前端这块。用Vue2还是Vue3的问题我的建议是如果团队熟悉Vue2且项目要求快速交付可以选Vue2加Vue CLI如果是从零开始学直接上Vue3加Vite更合适。这套源码兼容两条路线后续改造也不会太痛苦。数据库这块选了MySQL核心原因只有一个字——稳。MySQL对事务的支持、并发处理能力、运维生态都够成熟最重要的是它面向业务管理系统时的表现足够可靠。贸易行业CRM的数据量级单表百万条以内MySQL完全扛得住配合索引优化后的查询速度基本毫秒级返回完全没有必要为了“技术先进”去上分布式数据库或者NoSQL。1.2 项目结构设计思路这套源码的目录拆分是典型的前后端分离模式这也是现在企业级项目的主流做法。后端部分按SpringBoot分层架构来组织代码controller层负责接收前端请求、进行参数校验、返回统一响应格式service层承载核心业务逻辑事务控制全在这一层mapper层用MyBatis或MyBatis-Plus操作数据库SQL和业务代码分离entity包放数据库表对应的实体类dto包放接口交互的数据传输对象避免直接暴露实体类给前端前端部分Vue项目的核心结构包括views目录放页面组件比如客户列表、联系人管理、订单详情这些页面router目录配置前端路由管理页面跳转和权限控制api目录封装接口请求统一管理后端地址和axios实例store目录用Vuex或Pinia做全局状态管理存登录信息、权限标识前后端分离的好处是边界清晰后端人员不用关心页面长什么样前端人员可以独立开发调接口。这套源码切入点是“贸易行业”所以业务模块上多做了不少针对性的设计下面我会详细展开。2. 数据库建模贸易行业CRM的立身之本我不知道你拆过多少套管理系统源码反正我看一套系统第一件事就是打开数据库设计文档或者看建表SQL。数据库设计基本决定了系统的天花板——表设计烂代码写得再漂亮后期也没法救。2.1 核心表结构与业务关系贸易行业CRM的核心表我按业务主线来拆基本上可以分成五个板块客户资料板块、产品资料板块、单据流程板块、跟进记录板块、系统管理板块。客户资料板块包含客户表、联系人表和供应商表。客户表不只是存公司名称和联系方式还建议加上客户分类、所属行业、客户等级、来源渠道这些字段。等级字段直接决定销售对客户的跟进频率——A类客户可能要求每天早上查看最新动态C类客户一个月跟进一次就够了。字段设计是关键。客户表和联系人表是一对多关系一个客户公司下面通常有多个联系人——采购经理、技术负责人、财务、老板这些人有不同的沟通偏好和决策权重。给联系人表单独建库后期做客户移交、群发邮件、导入导出都会方便很多。产品资料板块是容易被忽略但极其重要的部分。贸易公司的产品往往有多个规格型号同一个产品面对不同客户时的报价可能还不一样所以除了产品基础表还要有价格策略表。我把价格策略设计成按客户等级或者按客户单独设置价格折扣比传统ERP那种统一价格更贴合贸易场景。单据流程板块是贸易行业CRM不同于普通CRM的核心。包括询价单、报价单、销售合同、采购订单、发货单、回款记录。这些单据不是孤立的它们构成一条完整的业务链客户询价-销售报价-客户确认-生成销售合同-关联采购订单-安排发货-记录回款。这套源码里通过业务流水号把这一链条贯穿起来方便随时追溯。2.2 表设计中的关键细节与索引策略建表时最容易踩的坑一是主键类型不合理二是时间字段类型不统一三是缺少必要的索引。这几个坑在贸易行业CRM里同样存在。主键我推荐用BIGINT自增而不是UUID字符串。道理很简单——UUID虽然是分布式环境的好方案但在单库单表的场景下自增主键的写入性能更好索引占用空间更小而且排序规则天然符合业务习惯。如果未来真的要考虑分库分表现在很多主流的ID生成策略比如雪花算法也能平滑过渡。时间字段统一用datetime注意Java实体里的LocalDateTime和MySQL的datetime完全对应不会出时区转换问题。我见过不少项目把时间存成String类型查询排序各种问题不断这种设计我建议直接淘汰不要再学了。索引策略这块要重点梳理一下查询场景。客户列表页的筛选条件是客户名称模糊查询、所属销售、客户等级、创建时间区间。所以我在客户表上建了销售ID和创建时间组合索引idx_sales_time页面的默认列表查询走的就是这条索引基本能保持在百毫秒内返回结果。单据表方面按业务流水号建唯一索引关联查询按客户ID建普通索引。还有一个值得单独说的地方状态字段。贸易业务的单据状态变化多比如订单可能有待审核、已确认、采购中、部分发货、已发货、已完成、已取消这些状态。状态字段的设计建议用int类型存状态码再用常量类统一管理状态码和状态名称的映射数据库里不要直接放中文。这样写代码时不容易手滑前端可以通过字典翻译显示。2.3 初始化数据的准备第一次运行这套系统的MySQL数据脚本除了建表语句之外我建议同时准备好一份初始化数据。包含一个管理员账号、基础的用户角色权限关系、贸易业务会用到的常用字典数据——比如客户来源展会、网络搜索、朋友介绍、老客户转介绍、订单状态、回款方式这些。还需要预设一些演示数据。不带任何演示数据的CRM系统跑起来空荡荡的你很难判断功能是否真的正常。我在初始化脚本里放了十几条模拟客户、几十条联系人、几条完整的询价到回款单据方便首次运行时直接看到业务效果。3. SpringBoot后端企业级功能落地的关键点后端是整套系统的中枢。SpringBoot自动装配帮我们屏蔽了很多底层配置的复杂度但要真正做好一个贸易行业CRM业务代码的编写仍然有不少细节要花心思。3.1 统一响应格式与全局异常处理我看过太多项目后端返回的数据格式五花八门。有的接口返回successtrue有的返回code200有的压根不包一层直接返回裸数据。前端对接时写了一大堆判断逻辑时间全花在联调上了。所以这套源码后端做了统一处理。一个标准API响应类包含三个字段code、message、data。不管请求成功还是失败控制器只负责执行业务逻辑并返回数据由全局异常处理器统一包装成这个格式返回。public class ApiResponseT { private Integer code; private String message; private T data; public static T ApiResponseT success(T data) { ApiResponseT response new ApiResponse(); response.setCode(200); response.setMessage(操作成功); response.setData(data); return response; } public static T ApiResponseT error(Integer code, String message) { ApiResponseT response new ApiResponse(); response.setCode(code); response.setMessage(message); return response; } }全局异常处理用RestControllerAdvice实现。业务异常、参数校验异常、数据库异常分别处理返回不同的提示信息。这里有个容易被忽视的细节——不要把内部异常信息直接抛给前端用户只需要知道“操作失败客户名称不能为空”就够了堆栈信息应该记录在服务端日志里方便排查问题。3.2 Shiro还是Sa-Token权限认证选型权限认证是CRM系统绕不开的功能。市场上有Shiro、Spring Security、Sa-Token等主流方案这套源码用的是Sa-Token。为什么不用Shiro倒不是Shiro不好主要是它是2010年左右的设计思路很多注解和API用起来不够直观。Spring Security虽然功能极强但配置学习成本高对中小型项目来说有点杀鸡用牛刀的感觉。Sa-Token轻量、上手快、同时支持session模式和各种主流集成方式我试过几次就爱不释手了。权限控制设计了三个层级用户表、角色表、权限表。角色和权限是多对多关系用户属于某个角色后自动继承角色的权限。在贸易公司常见场景里销售主管和普通销售看到的菜单和能执行的操作肯定不一样——销售主管可以查看全组跟进记录普通销售只看得到自己的数据。接口权限通过注解标注SaCheckPermission(customer:add) PostMapping(/customer) public ApiResponse? addCustomer(RequestBody CustomerDTO dto) { customerService.addCustomer(dto); return ApiResponse.success(null); }数据权限则是另一个维度的问题——用户A能操作哪些客户用户的上级能看到哪些下属的客户。数据权限这块需要单独写查询逻辑通常在SQL的条件里增加数据范围的过滤条件。这套源码的做法是在查询接口中根据当前登录用户角色自动拼接过滤条件。3.3 核心业务模块实现逻辑后端业务模块的划分跟数据库表结构一一对应这里重点讲两个贸易行业特有且重要的模块报价单模块和订单流转模块。贸易公司报价的业务流程可能是这样的销售收到客户询价之后在系统里填产品型号、数量、报价有效期系统自动关联客户等级对应的价格策略自动算出一个参考价格。销售可以在此基础上手动调整确认后生成报价单。系统在报价单有效期快到期时给出提醒这个功能在贸易场景里很实用——放在公用邮件里客户迟迟不回复你总得知道这个报价是不是快过期了好去跟进一下。订单模块的特点是状态机驱动。订单的每个状态都有对应的可执行操作和不允许操作。比如订单状态是“已完成”就不允许再添加发货记录状态是“已取消”就不允许再走回款流程。这里我用状态枚举加一个状态机Map来处理写起来清晰以后加状态也方便。public enum OrderStatus { PENDING(0, 待审核), CONFIRMED(1, 已确认), PROCURING(2, 采购中), PARTIAL_SHIPPED(3, 部分发货), SHIPPED(4, 已发货), COMPLETED(5, 已完成), CANCELLED(6, 已取消); private final Integer code; private final String description; // 构造函数和getter省略 }3.4 MyBatis-Plus的使用心得这套源码的持久层用了MyBatis-Plus它的优势是几乎不用写简单的单表CRUD的SQL。内置的BaseMapper已经提供了insert、deleteById、selectById、updateById等方法直接继承就能用。复杂查询通过LambdaQueryWrapper构建条件代码可读性比写XML的原始MyBatis好不少。举个例子客户列表的分页查询LambdaQueryWrapperCustomer wrapper new LambdaQueryWrapper(); wrapper.eq(StringUtils.hasText(dto.getLevel()), Customer::getLevel, dto.getLevel()) .like(StringUtils.hasText(dto.getName()), Customer::getName, dto.getName()) .eq(Customer::getSalesId, currentUserId) .orderByDesc(Customer::getCreateTime); PageCustomer page customerMapper.selectPage(new Page(dto.getPageNum(), dto.getPageSize()), wrapper);注意一个点列表页的模糊查询字段如果查询条件允许为空就别把它拼在SQL里。上面的写法用了StringUtils.hasText判断避免传空字符串时查询条件失效的问题。4. Vue前端业务页面的组织与开发前端的技术栈是Vue全家桶加Element组件库。这套源码的前端页面对接了一系列常规业务功能登录页、后台布局、客户管理、联系人管理、产品管理、单据管理、数据看板。我挑几个关键设计说一下。4.1 登录流程与权限控制的前端实现登录页面调后端login接口成功后拿token存到localStorage和Vuex/Pinia store里。axios实例设置请求拦截器每次请求自动在header带上token。// request.js import axios from axios import { ElMessage } from element-plus import router from /router const request axios.create({ baseURL: /api, timeout: 15000 }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] token } return config }) request.interceptors.response.use( response { const res response.data if (res.code ! 200) { ElMessage.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) } return res }, error { if (error.response error.response.status 401) { localStorage.removeItem(token) router.push(/login) } ElMessage.error(error.message || 网络异常) return Promise.reject(error) } )路由守卫是前端权限控制的关键一环。在router.beforeEach里面判断本地有没有token没有token就强制跳转到登录页。如果有token且要去登录页就放行到首页。菜单权限根据登录时后端返回的菜单列表动态生成没有权限的页面直接不渲染对应菜单项后端的注解校验作为兜底。4.2 客户管理页面的组件化设计客户管理页是我觉得这套前端代码里最有参考价值的部分。它不仅仅是一个列表页而是由搜索区、工具栏、数据表格、分页器四个主要组件组合而成。搜索区封装成一个对象绑定搜索条件字段。工具栏放“新增客户”“批量删除”“导入导出”等操作按钮。数据表格用el-table渲染操作列放“编辑”“详情”“跟进记录”“删除”等操作入口。分页器用el-pagination切换页码和修改每页条数时重新请求数据。新增和编辑客户我用了弹窗加表单控件的方式。表单控件里的字段校验规则用Element Plus的rules配置——比如客户名称是必填、手机号需要校验11位数字、邮箱需要校验邮箱格式。这样用户体验比较好输错了马上就能看到提示不用等提交后再报错。4.3 数据看板与图表展示贸易行业的CRM除了管业务数据还得帮助管理层做决策。我在这套源码里加了一个简易的数据看板模块通过ECharts展示客户增长趋势、订单月度统计、销售排行榜。这样管理层登录系统后的第一眼就能看到核心经营指标的变化。ECharts和Vue的集成不复杂直接封装一个图表组件props传入option配置对象组件内部初始化图表实例并在数据更新时调用setOption方法。我建议不要每个图表都单独写一个页面组件封装成一个公用ECharts组件配置通过props传进来这样维护成本低很多。4.4 Vue路由与页面的组织技巧页面多了之后路由管理也要讲究方法。我用的是动态路由和静态路由结合的方式登录页面、404页面是静态路由登录成功后根据后端返回的菜单权限动态添加业务路由。动态路由的好处是如果当前用户的角色没有“报表管理”权限前端直接不加载对应的路由组件就算你手输URL地址也访问不了对应的页面。后端的接口权限校验继续兜底双层防护下权限安全性基本没问题。页面目录的命名规范也需要统一。views目录下按业务模块建子目录customer、contact、product、order、contract、stats每个子目录内放该模块的列表页、编辑页、详情页组件。这样后期新增一个页面时能快速定位对应的文件在哪里。5. 本地运行、联调与部署上线标题里“可直接运行”这几个字非常重要。我拿到一套源码最讨厌的事情就是下载下来以后跑不起来缺这个依赖少那个配置环境折腾半天。所以这套源码在环境配置和运行文档上都做了优化尽量让第一次接触的人也能顺滑地跑起来。5.1 环境准备与初始化步骤先明确一下运行这套系统的必备软件JDK 8或者11、Maven 3.6以上、MySQL 5.7或者8.0、Node.js 14以上。这几个版本之间兼容性都很好不用刻意追求最新版稳定就行。后端启动步骤创建数据库crm将.sql文件导入注意MySQL8和5.7的驱动差异修改application.yml里的数据库账号密码mvn spring-boot:run或者直接启动主类前端启动步骤npm install安装依赖npm run serve启动开发服务器开发模式下前端默认端口是8080后端默认端口是8081通过Vite或Vue CLI的proxy配置把/api开头的请求代理到8081端口就解决了跨域问题。部署时再用Nginx把前后端统一到同一个入口业务上就不再有跨域限制。5.2 项目配置文件详解application.yml是后端配置的集中地。我一般把配置按环境拆分application-dev.yml用于本地开发application-prod.yml用于线上部署。数据库连接、Redis配置、服务端口、日志级别等在各自的文件中维护。本地开发时日志级别建议设为DEBUG方便看SQL和接口参数生产环境则设为INFO减少日志量。还有一个容易忽略的配置SpringBoot的Jackson序列化。默认情况下LocalDateTime序列化格式可能带T字母和纳秒部分前端拿到这个时间格式必须做转换比较麻烦。在application.yml中配置全局的JSON时间格式可以省掉一整套麻烦。spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8如果你用的是MyBatis-Plus还要注意它的一些全局配置。比如id-type设置成auto让数据库自增ID生效逻辑删除配置可以设置一个全局的deleted字段作为逻辑删除标记这样删除客户记录时其实执行的是update语句这样数据不会真正丢失在出现误删时能找回。5.3 前后端联调中的常见问题联调阶段最容易出现的问题我几乎每次都遇到这里提前说一下排查方案。第一个是跨域问题。前端的页面跑在8080端口后端的接口跑在8081端口如果不在前端配置代理或者后端加CORS允许浏览器就会拦截跨域请求。开发环境最简单的方案是在Vite的配置文件里加代理把/api路径转发到8081端口生产环境则是Nginx统一反向代理前端静态资源和后端接口走同一个域名。第二个是时间格式问题。后端的LocalDateTime默认序列化格式在没有配置date-format属性时前端拿到的格式是带T的ISO格式。统一配置成yyyy-MM-dd HH:mm:ss之后前端表格里的时间显示就正常了。第三个是HTTP状态码和业务码混淆的问题。有的开发者在后端直接返回401给前端表示Token失效同时业务失败也返回200状态码加失败码。我的建议是HTTP状态码只保留最基本的语义——200代表服务正常响应401代表未认证403代表无权限500代表服务端异常。业务层面的成功和失败统一在code字段里体现前端拦截器统一处理。5.4 打包发布从本地到服务器打包后端代码用Maven的package命令打成jar包后放到服务器上执行mvn clean package -DskipTests java -jar crm-system.jar --spring.profiles.activeprod前端代码用npm run build打成静态文件产物在dist目录下。Nginx中配置静态文件的根目录指向dist目录再配置一个反向代理把/api请求转发到Java服务。这样一个完整的发布流程就闭环了。服务器上还需要配置MySQL的远程访问权限。记得在安全组或者防火墙中只放行必要的端口数据库不要暴露公网IPNginx作为统一入口即可。6. 问题排查与实战心得做技术分享最忌讳只说顺利的部分把实际踩过的坑都藏起来。这里我整理了几个贸易行业CRM开发调试过程中的典型问题和解决方案算是这套源码里最值得看的“避坑指南”。6.1 数据库死锁与事务边界问题贸易行业的订单流转是一个典型的多表更新场景——修改订单状态、添加发货记录、更新客户最近成交时间、写入操作日志。如果这些操作放在同一个事务里但是更新表的顺序在不同方法间不一致就可能出现死锁。我在这套源码里遇到过一次死锁A操作先更新订单表再更新客户表B操作恰好先更新客户表再更新订单表两个事务互相等待对方释放锁数据库直接报死锁错误。解决办法是规定所有事务方法更新多个表的顺序必须一致比如统一先更新单据主表再更新关联表。另一个事务边界的问题是事务粒度过大。有些方法没有加事务注解却执行了多个insert和update操作一旦中间某一步失败前面的数据就已经写入造成脏数据。排查这类问题可以用一个技巧在事务里故意抛一个RuntimeException看数据是否回滚。不回滚就说明事务没生效——最常见的原因是方法被同类内部调用了Spring的事务代理没有生效。6.2 大数据量列表查询的性能瓶颈刚开始这套系统如果表里只有几十条数据查询速度你根本感知不到问题。等用了一两个月客户表积累了几万条数据订单流水表十几万条之后列表页开始变得卡顿。这时候就要考虑查询性能的优化。优先排查的点是索引。用EXPLAIN关键字查看执行计划看关键查询是否走了索引。常见的性能杀手是模糊查询写法——like %关键字%这种前置模糊查询索引会失效如果业务上确实需要这种查询方式可以考虑配合全文索引来优化。其次是N1查询问题。用MyBatis-Plus查出一个客户列表后循环遍历客户ID查联系人数量这种方式如果客户列表返回20条就要查20次联系人表。正确做法是一次性查出所有客户ID对应的联系人统计结果在内存中组装数据。6.3 前端常见的表格和状态刷新问题前端开发中我遇到最多的问题一个是修改数据后页面不刷新另一个是级联选择器回显不正常。第一个问题通常是因为没有重新调用列表接口或者调用了但没有清空旧数据。解决方案是在提交成功后的回调里重新请求列表接口并带上当前搜索条件和分页参数。第二个问题比较隐蔽。编辑客户时如果客户所属的行业分类是从字典表查出来的表单里展示的可能是字典key值而不是字典名称。回显时要根据key值找到对应的label再填进表单控件里。实现起来很容易但是前后端联调时不注意就会出问题。6.4 权限控制不生效的排查思路权限控制不生效最直接的排查顺序是先看后端日志确认当前登录用户的token有没有被正确传递再看Sa-Token配置确认接口注解是否写对了最后查数据库确认该用户角色对应的权限表里确实配了这条权限。我遇到过一个小坑开发环境中前端登录的是管理员账号但是后端权限配置里管理员的角色ID被误改了导致所有接口都返回403。这种问题非常隐蔽排查时要先确认角色和权限的关联关系有没有被动过。6.5 这套系统后续可以怎么扩展如果你打算拿这套CRM源码去做二次开发或者毕业设计我有几个建议方向。接入文件上传能力让客户证照、合同扫描件、物流单据能够上传到服务器统一管理。现在很多贸易公司在业务往来中都需要归档合同PDF和客户资质文件这个功能几乎必做。可以用MinIO或者阿里云OSS来存储文件后端提供统一的文件上传下载接口。增加跟进提醒和工作通知模块。贸易业务很看重时效性——报价单快过期了、客户好几天没联系、回款已经逾期这些都需要主动通知。消息可以通过WebSocket实时推送也可以接入企业微信通知。把数据看板做得更细。现在只是基础的ECharts统计展示可以进一步做销售漏斗分析、客户生命周期分析、产品利润分析。这些功能对管理层的价值很大也容易在汇报时展示效果。根据我个人搭建这套系统的经验来看做管理类项目最重要的不是炫技而是保证业务流程闭环、数据结构合理、代码可维护。SpringBootVueMySQL这套组合之所以经典就是因为它们在保证一定技术深度的同时把开发成本和维护难度控制在了合理范围。这套源码里沉淀的建表思路、事务设计、权限模型、前后端交互规范你直接拿去做其他企业管理系统——进销存、OA、售后工单——照样能复用。
返回列表