ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot+Vue客户关系管理信息系统的设计与实现全解析

SpringBoot+Vue客户关系管理信息系统的设计与实现全解析 本文围绕“基于SpringBootVue公司客户关系管理信息系统的设计与实现”展开我将以一位实际动手做过这类项目的开发者的身份来分享。这类题目在现在的高校毕业设计和中小型公司内部信息化项目里太常见了我自己就帮人参谋过好几个类似的系统也落地过两个还跑在生产环境里的版本。网上关于这个题目的论文和资料很多但大多是教科书口吻真正能指导你把系统从零做出来、跑起来、不被答辩老师问住的实战内容反而少。这篇东西就按我实际开发的顺序来讲从架构选型、表结构设计、后端接口实现到前端页面整合再到权限模型和线上部署把我在真实开发中踩过的坑、验证过的方案一并写出来。1. 为什么这套技术栈成了客户管理系统的主流选择先聊点背景。现在如果去招聘网站或者毕设选题库里面看SpringBootVue的组合几乎占据了管理系统类项目的大半壁江山。这背后是有原因的而且是有技术逻辑的不是单纯因为教材都在写。从生态角度来看SpringBoot把Java后端开发的门槛降了一大截。以前做一个SSHStrutsSpringHibernate或者SSM项目光配置文件就要写一堆XML里各种bean定义、事务配置、扫描规则新手很容易被绕晕。SpringBoot的核心思想是约定优于配置它把大量的默认配置内嵌到了starter依赖里。你引入一个spring-boot-starter-web内嵌Tomcat就有了你引入spring-boot-starter-data-jpa或者MyBatis相关starter数据库访问层的基础设施也就齐了。这意味着你可以把精力从配置项目转移到写业务代码上这在做客户管理这种业务逻辑密集的信息系统时尤其重要。与之对应的Vue则解决了前端开发的状态管理和组件复用问题。做客户关系管理CRM系统页面上有非常多的表格、表单、弹窗、标签页。如果用传统的jQuery每一个交互都得手动操作DOM代码逻辑很容易乱成一团。Vue的响应式数据绑定和组件化开发能让你的页面代码保持清晰。你只需要定义好数据模型界面会自动跟随数据变化而更新逻辑上清爽很多。还有一点很实际这套技术栈的招聘需求量大出了学校也能直接用。企业里做管理信息系统SpringBoot后端加Vue前端是很多小团队和外包公司的标准配置。所以不管你是在做毕设还是公司内部要搭一套轻量级CRM选这套栈都是性价比很高的决定——资料多、社区活跃、遇到问题基本都能搜到解决方案。我的建议是如果项目时间有限优先保证核心模块的完整度而不是追求功能堆砌。CRM系统的核心永远在客户全生命周期数据链路的闭环后面我会重点讲这一点。2. 系统整体功能模块与数据库设计的核心思路功能模块的设计决定了整个系统的骨架。大多数公司客户关系管理信息系统其实都围绕一条主线获取线索 → 转化为客户 → 跟进商机 → 合同签约 → 回款服务。我把这个生命周期拆成几个核心模块这也是我做过几个项目后验证过的合理划分方式。2.1 功能模块怎么划分才不漏项我一般会分成六个核心模块系统管理用户管理、角色管理、菜单权限管理、操作日志。这属于基础底座没有它其他业务数据没有归属。客户信息管理客户档案的增删改查、导入导出、客户分类、联系人维护、客户池公海机制。商机/跟进管理销售跟进记录、商机阶段转换如初期沟通、需求确认、方案报价、谈判签约、跟进提醒。合同与回款管理合同台账、金额字段、回款计划与实收记录、合同附件上传。统计分析看板客户数量趋势、销售漏斗数据、跟进频次排行、回款率统计。个人工作台待办事项、我的客户、最近跟进、消息提醒。功能模块设计阶段最容易犯的错是把系统的目标用户搞混。内部使用的CRM和给多个租户使用的SaaS版CRM复杂度完全不同。我做的这个项目定位是单公司内部使用所以不需要做租户隔离用户都归一个公司组织。系统里的人员角色大概是三类销售专员管自己的客户、销售主管管自己团队的客户数据看板、系统管理员配置权限和系统参数。2.2 数据库表结构设计的几个关键决策表设计是CRM系统里最值得花时间打磨的部分。我先说结论核心表不要贪多重点是把关联关系理顺。我落地这套系统时设计的核心表有这些sys_user用户表存账号、密码BCrypt加密、姓名、手机号、部门、状态。sys_role角色表角色编码、角色名称、数据权限范围数据权限这个概念下面细说。sys_menu菜单/权限表菜单名称、路由地址、权限标识如customer:add、类型目录/菜单/按钮。sys_user_role和sys_role_menu用户与角色、角色与菜单的两张关联表标准的RBAC模型。customer客户表客户名称、行业类型、客户等级、来源渠道、所属销售ID、创建时间、更新时间。这里有一个关键点所属销售ID是逻辑外键不做数据库物理外键约束原因下面讲。customer_contact联系人表联系人姓名、电话、职位、微信、邮箱、是否为决策人。follow_record跟进记录表客户ID、跟进方式电话/拜访/微信、跟进内容、下次跟进时间、跟进人。business_opportunity商机表关联客户ID、商机名称、预计金额、当前阶段、预计成交日期。contract合同表合同编号、客户ID、商机ID、合同金额、签约日期、回款计划信息。receipt_record回款记录表关联合同ID、回款金额、回款日期、回款方式。这里有个我特别想提醒的细节为什么关联外键不做物理外键约束。很多课程设计里要求建立物理外键但在真实业务系统里物理外键会导致几个问题一是删除客户时如果存在关联记录会被外键约束拦住实际业务中我们往往是做逻辑删除用deleted字段标记而不是物理删除二是系统后期要做分库分表时物理外键会成为灾难三是性能方面高并发写入场景下外键检查会多出开销。所以我建议用逻辑外键加应用层校验的方案。客户表和联系人表的关系是一对多一个客户下面可以挂多个联系人典型场景是公司里有业务对接人和财务对接人两个不同的人。跟进记录表这张表是CRM里体量增长最快的表时间一长很容易到几十万条。所以在这张表上我建了(customer_id, follow_time)的联合索引。商机表与合同表是一对一或一对多一个商机可能产生多个合同比如分期合同我用business_opportunity_id做关联但允许为空因为有的合同不经过商机直接签约。2.3 客户池公海机制的表设计体现客户池是CRM系统里非常有业务价值的机制。简单说一个客户如果长时间没有被有效跟进它就应当释放回公海让其他销售可以领取。这个机制在表结构上如何体现我在customer表上加了三个字段owner_id、pool_time进入公海的时间、status0已分配1在公海。同时配了一个定时任务扫描超过N天没有新增跟进记录的客户自动把owner_id置空status置为1写入pool_time。这样实现非常轻量不需要再设计一张独立的公海记录表。3. 后端SpringBoot实现细节从分层结构到核心业务逻辑后端是整个系统的业务中枢。SpringBoot项目我采用经典的四层结构Controller层接收请求→ Service层业务逻辑→ Mapper层数据访问→ 实体类。下面把几个重要的实现细节展开说。3.1 项目分层与通用返回体的设计创建一个SpringBoot项目时我习惯把包结构设计成com.company.crm ├── controller ├── service │ └── impl ├── mapper ├── entity ├── dto ├── vo ├── config ├── common │ ├── Result.java │ ├── PageResult.java │ └── exception └── utils这里有一个很关键的习惯所有接口返回统一格式ResultT。不管成功还是失败结构永远是{ code: 200, message: 操作成功, data: {...} }这样前端axios拦截器里统一判断code不用每个接口单独写错误处理逻辑。如果接口返回code: 401前端自动跳转登录页code: 500就弹出后端返回的message。这个统一返回体是我强烈建议在项目一开始就定好的不然后期返工改起来烦死人。我见过不少项目有的接口直接返回实体对象有的返回Map有的返回boolean前端拿到数据后还要猜结构这是导致前后端联调效率低下的头号原因。3.2 认证与权限JWT 拦截器方案CRM系统最关心安全问题——客户数据是公司的核心资产权限绝对不能做成摆设。我的认证方案是JWTJSON Web Token 拦截器 注解。用户登录成功后后端生成一个JWT令牌过期时间设置为2小时把用户ID、用户名、角色编码这些信息放进去用签名字符串做HS256签名。前端拿到token后存到localStorage里每次请求在请求头加上Authorization: Bearer token。后端用一个拦截器统一解析token。写拦截器的时候有一个细节要特别注意放行路径和拦截路径的配置。登录接口、静态资源、验证码接口必须放行其他接口一律走token校验。我在WebMvcConfigurer里这样配置registry.addInterceptor(new JwtInterceptor()) .addPathPatterns(/**) .excludePathPatterns(/api/auth/login, /api/auth/captcha, /error);权限校验我用了自定义注解RequiresPermission(customer:add)结合Spring AOP实现。在需要控制权限的接口上加上注解AOP切面里检查当前用户是否拥有对应权限标识。比在拦截器里做更灵活因为它是方法级别的控制。角色与菜单权限的匹配关系都压在sys_role_menu表里菜单上带一个perms字段存customer:add这样的权限标识。3.3 客户归属权与数据权限的关键实现CRM系统里最复杂的业务规则之一是数据权限。简单说一个销售登录后只能看到自己名下的客户销售主管能看到自己团队的客户而管理员能看全部。我在设计时把数据权限分成了三个级别仅本人数据customer.owner_id 当前登录用户ID本部门数据customer.owner_id属于当前用户所在部门的所有用户全部数据不做数据行过滤这个在SQL层面怎么实现我的方案是在Service层根据当前用户的角色去动态拼接查询条件而不是每个Mapper方法都写死一条SQL。// 伪代码逻辑 if (数据权限级别 仅本人) { queryWrapper.eq(owner_id, currentUserId); } else if (数据权限级别 本部门) { ListLong userIds userMapper.selectUserIdsByDept(currentUser.getDeptId()); queryWrapper.in(owner_id, userIds); } // 全部数据则不加条件这样做能用一套代码适配多种权限需求。不要小看这个设计它直接影响你对系统的信心——数据权限如果出了漏洞客户资料倒挂这在真实公司里可就是事故了。3.4 跟进记录与客户池的定时任务实现前面提到了客户池机制我使用SpringBoot自带的Scheduled注解来实现这个定时扫描任务。在启动类上加上EnableScheduling然后在Service里写一个方法Scheduled(cron 0 0 2 * * ?) // 每天凌晨2点执行 public void autoReleaseExpiredCustomers() { LocalDateTime threshold LocalDateTime.now().minusDays(30); // 查出最后一次跟进时间早于threshold的已分配客户 ListCustomer expiredCustomers customerMapper.selectExpired(threshold); for (Customer customer : expiredCustomers) { customer.setOwnerId(null); customer.setStatus(1); customer.setPoolTime(LocalDateTime.now()); customerMapper.updateById(customer); } }这里有几个实际运行中发现的坑首先定时任务一定要做幂等和边界处理扫描条件必须判断status0仅处理已分配的客户不然已经被释放过的客户会被反复处理其次批量更新比循环单条更新性能好很多数据量大了之后要用update batch的方式第三定时任务尽量放在业务低峰期避免影响正常业务查询。3.5 大字段与文件上传合同附件的处理方案合同管理里通常要上传扫描件。后端用SpringBoot处理文件上传比较简单MultipartFile接参然后把文件存到本地磁盘的指定目录数据库中只存文件路径url字段。我在实际项目里是把文件存储在服务器/data/crm/upload/目录下文件名使用UUID重命名避免重名冲突。上传接口要注意几个问题限制文件大小在application.yml里配置spring.servlet.multipart.max-file-size: 10MBmax-request-size: 20MB。文件类型校验不能只靠前端限制后端必须检查文件扩展名和MIME类型合同附件一般允许pdf、jpg、png其他格式拒绝。反向代理问题前端通过Nginx访问上传的文件时要确保Nginx配置了静态文件映射的location指向那个上传目录。这个模块看着简单但恰恰是毕设答辩时老师最爱问的项目落地细节之一。4. 前端Vue设计与关键页面逻辑拆解前端我使用的是Vue 2也可以用Vue 3写法上稍有差别配合Element UI组件库搭建页面骨架。前后端通过RESTful API通信。下面挑几个关键实现点说。4.1 前端项目结构与路由设计我习惯用Vue CLI创建项目目录结构如下src ├── api // 接口请求封装 │ ├── customer.js │ ├── user.js │ └── auth.js ├── assets ├── components // 公共组件 │ └── Pagination.vue ├── router │ └── index.js // 路由配置 ├── store // Vuex状态管理 │ ├── modules │ │ └── user.js │ └── index.js ├── views // 页面 │ ├── dashboard.vue │ ├── customer │ │ ├── list.vue │ │ └── detail.vue │ ├── system │ │ ├── user.vue │ │ └── role.vue │ └── login.vue ├── utils │ └── request.js // axios封装 └── App.vue路由设计上我使用了动态路由的方式。简单说就是用户登录后后端返回该用户有权限访问的菜单列表和路由信息前端动态添加到路由表里。这样做的好处是无权限的页面压根就不会出现在路由表里即便手动输入URL也会被路由守卫拦截到404页。这里给一个实际的request.js封装的核心逻辑import axios from axios import { Message } from element-ui import router from /router const service axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 10000 }) // 请求拦截加token service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config }) // 响应拦截统一处理业务码 service.interceptors.response.use(response { const res response.data if (res.code 200) { return res } else if (res.code 401) { localStorage.removeItem(token) router.push(/login) return Promise.reject(new Error(登录已过期)) } else { Message.error(res.message || 系统异常) return Promise.reject(new Error(res.message)) } })4.2 客户列表页的查询分页数据权限联动逻辑客户列表页是最典型的一个页面它把CMR系统的核心交互集齐了条件查询、分页、权限数据过滤、操作按钮。页面加载时调用getCustomerList接口参数是pageNum、pageSize、keyword客户名称模糊查询、customerLevel客户等级、ownerId归属人。后端根据当前登录用户的角色自动拼数据权限SQL返回分页数据。前端表格用Element UI的el-table数据绑定customerList分页组件我封装了一个Pagination组件监听current-change和size-change事件重新拉数据。操作列常见的有查看详情、编辑、跟进、移入公海。这里**移入公海按钮需要权限控制**用v-permission自定义指令实现判断当前用户是否拥有customer:pool权限。没有权限的按钮直接不渲染这比后端校验更进一步提供了前端交互层面的控制。4.3 客户详情页的数据组织一个页面串起所有关联信息客户详情页是CRM系统里信息密度最高的页面。它要同时展示六个区块基本信息、联系人列表、跟进记录、商机列表、合同列表、回款记录。如果把这些信息放在六个不同页面里用户操作成本很高放在一个页面里又容易显得杂乱无章。我的方案是用el-tabs组件组织页面结构默认展示基本信息Tab用户可以自由切换。每个Tab是一个独立的Vue子组件页面数据分开加载el-tabs v-modelactiveTab el-tab-pane label基本信息 namebase customer-base-info :customer-idcustomerId / /el-tab-pane el-tab-pane label联系人 namecontact contact-list :customer-idcustomerId / /el-tab-pane el-tab-pane label跟进记录 namefollow follow-record :customer-idcustomerId / /el-tab-pane el-tab-pane label商机 nameopportunity opportunity-list :customer-idcustomerId / /el-tab-pane el-tab-pane label合同 namecontract contract-list :customer-idcustomerId / /el-tab-pane /el-tabs每个子组件在自己的created生命周期里拉取数据。这里要提一个我在真实开发中很注意的点二级联动数据的缓存。在下拉框中客户等级、行业类型、线索来源这些字典数据基本是固定不变的如果每次都从后端拉既慢又浪费。我在前端Vuex里维护了一个dicts状态接口返回首帧数据后缓存到内存后续页面直接从Vuex读取。这算是一个很实用的小优化。4.4 数据统计看板的实现思路看板页Dashboard是给管理层看的我选用了ECharts作为图表库。组件化封装后一个折线图组件大概长这样template div refchart stylewidth: 100%; height: 350px/div /template script import * as echarts from echarts export default { mounted() { this.chart echarts.init(this.$refs.chart) this.loadData() }, methods: { loadData() { getDashboardData().then(res { this.chart.setOption({ xAxis: { type: category, data: res.data.months }, yAxis: { type: value }, series: [{ type: line, data: res.data.counts }] }) }) } } } /script统计看板的数据源建议在后端聚合好了再返给前端不要让前端拿到明细数据自己聚合。因为聚合逻辑越靠后端数据一致性越有保障也便于后端做SQL优化。比如近半年客户增长趋势后端用一条SQL按月份GROUP BY查出结果返回即可。5. 权限模型落地的完整链路从数据库到前端的走向权限这块内容太重要了值得单独开一节。很多项目在权限设计上只做了登录拦截菜单显示也是靠前端写死我认为这是不够的。一个真正能用的CRM权限模型权限必须走完从数据库到后端的完整链路。5.1 RBAC基于角色的访问控制模型RBAC是这套权限系统的理论基础。核心思想是权限不直接给用户而是先给角色再把角色分配给用户。权限要调整时只需要修改角色下的菜单/按钮权限所有该角色的用户自动生效。在系统里我预置了三个角色超级管理员拥有全部菜单和按钮权限数据权限为全部。销售主管拥有客户查看本部门、跟进管理、商机、合同、数据看板权限数据权限为本部门。销售专员拥有客户查看仅本人、跟进管理、商机创建、合同查看权限数据权限为仅本人。每个角色的权限数据存在sys_role_menu表里。后端在用户登录成功后会根据用户ID查出所有关联角色以及角色对应的菜单权限标识列表返回给前端。前端根据这些标识动态生成侧边栏菜单。5.2 前端路由与后端权限的联动前端路由是动态生成的。我按模块划分好了静态的路由配置包含所有菜单对应的组件和路径但初始状态不挂载。登录成功后后端返回当前用户的可访问菜单列表按父级、子级组织好前端根据这个列表用router.addRoutes方法把匹配到的路由动态添加进去。这样处理后用户在浏览器地址栏直接输入一个没有权限的URLbeforeEach全局前置守卫会检查当前路由的meta.requiresAuth再查一下当前用户菜单里有没有这个路由的权限没有就直接redirect到403页。router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (!token to.path ! /login) { next(/login) } else { if (to.meta.requiresAuth !hasPermission(to.path)) { next(/403) } else { next() } } })5.3 权限模型验证与常见遗漏点做完权限模型一定要测试三个场景横向越权测试销售A登录后直接通过修改URL访问客户ID为某个他不拥有的客户详情。这里必须要靠后端数据权限过滤兜底不能只靠前端隐藏按钮。纵向越权测试销售专员直接调用主管才有的统计接口URL后端应该返回403。按钮级权限页面上隐藏了删除客户按钮但有心人用浏览器开发者工具手动调用删除接口。后端接口仍然必须校验操作权限。我见过很多系统前端菜单隐藏得漂亮后端接口却完全不设防这是很大的隐患。所以我在后端每个写操作增删改上都加了RequiresPermission注解确保接口层面也有防护。6. 部署上线与性能优化实践系统的代码写完只是第一步。真正让它跑起来、稳定运行需要经过部署和一系列调优。这个章节我把实际的运维方案和优化心得写出来。6.1 部署方案前后端分离部署后端打包成JAR包用Maven执行mvn clean package -Dmaven.test.skiptrue得到crm-server.jar然后放到服务器上执行nohup java -jar crm-server.jar --spring.profiles.activeprod /var/log/crm-server.log 21 生产环境配置文件application-prod.yml里数据库连接池、日志级别、文件上传路径都要单独调整。一个很容易被忽视的点数据库账号密码不要写在配置文件的明文里可以用环境变量引用比如spring.datasource.password${DB_PASSWORD}部署时在服务器的环境变量里配置。前端构建npm run build构建产物是dist目录把它部署到Nginx。Nginx配置要做两件事一是托管前端静态文件二是把/api开头的请求反向代理到后端服务的8080端口。这里给一段最简单的Nginx核心配置示例server { listen 80; server_name your-domain.com; root /var/www/crm/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location /upload/ { alias /data/crm/upload/; } }前后端分离部署后有一个必须注意的点跨域问题。因为前端页面在Nginx的80端口后端接口在8080端口直接从前端页面发请求到8080属于跨域请求浏览器的同源策略会拦截。解决办法有两种后端开启CORS配置CrossOrigin或者全局配置CorsFilter或者更推荐的方式是像上面那样通过Nginx反向代理来解决——前端只访问同源的/api路径由Nginx转发到后端。生产环境一定要用反向代理的方式一是规避了跨域二是隐藏了后端真实端口更安全。6.2 MySQL连接池与慢查询优化CRM系统虽然不是高并发系统但数据量积累几年后慢查询会开始出现。我实际遇到过的一个场景是客户列表页有一个关联客户联系人数据的统计功能早期直接在SQL里写子查询统计联系人个数客户量大之后这个接口的响应从200ms涨到了3秒以上。优化手段是给常用查询字段加索引。我在customer表的owner_id、level、status字段上建了联合索引在follow_record表的customer_id和follow_time上建了联合索引。加索引之后同样的查询降到了300ms以内。另外生产环境一定要开启MySQL的慢查询日志排查哪些SQL耗时异常SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 1;上线后观察一周慢查询日志把耗时超过1秒的SQL逐个分析优化这是性能调优最直接的方法。6.3 备份策略与日常维护客户数据是公司的资产备份不能马虎。我的习惯是每天凌晨3点用crontab执行MySQL全量备份0 3 * * * mysqldump -u username -ppassword crm_db /backup/crm_$(date \%Y\%m\%d).sql同时保留最近30天的备份文件超过30天的自动清理30 3 * * * find /backup -name crm_*.sql -mtime 30 -exec rm {} \;我今年就经历过一次教训有一个老项目因为三天没有做增量备份某天数据库文件意外损坏恢复数据时只能恢复到三天前的状态中间的业务数据全部丢失。从那以后我再也没有偷懒跳过备份。7. 项目开发中最容易忽略的几个细节最后一个章节分享一下我在开发这类系统过程中反复踩过的坑。这些细节在官方文档里基本不会写但是直接影响系统的可用性和开发效率。7.1 时间格式化陷阱前后端分离开发时最容易出问题的是时间类型。Java后端如果直接把LocalDateTime类型序列化返回给前端默认格式是2024-05-18T10:30:00前端显示起来不好看也不易读。我在后端统一做了全局配置把时间格式化为yyyy-MM-dd HH:mm:ss。Bean public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() { return builder - { builder.simpleDateFormat(yyyy-MM-dd HH:mm:ss); builder.serializers(new LocalDateTimeSerializer(formatter)); builder.deserializers(new LocalDateTimeDeserializer(formatter)); }; }但是这里有一个坑前端发起查询时某些参数日期格式是yyyy-MM-dd与后端LocalDateTime类型接收冲突。我的解决方案是查询时间段的参数统一用DateTimeFormat(pattern yyyy-MM-dd)注解处理用LocalDate类型接收与LocalDateTime分开处理。7.2 Excel导入导出的实现方案CRM系统里把线下Excel中的客户数据导入系统是非常高频的需求。我在项目中使用了Apache POI老牌Java处理Excel的库来做导入导出。导出的核心代码思路是先查数据再创建Workbook对象逐个单元格填值通过HttpServletResponse输出流写出。这里有两个容易被忽视的体验细节导出文件名的中文乱码问题需要对文件名做URL编码处理response.setHeader(Content-Disposition, attachment; filename*UTF-8 URLEncoder.encode(客户数据.xlsx, UTF-8))。导入时必须做数据模板合法性的校验比如手机号格式为空字段处理否则用户传一个格式错误的Excel直接程序报错体验很差。我在导入时把校验错误逐行收集生成错误原因列表返回给前端展示比如第3行手机号格式不正确第5行客户名称为空。7.3 操作日志要记录谁在什么时间对什么数据做了什么这个功能在管理系统里属于基础但容易被省略的模块。我在完成后端接口时用一个自定义注解OperationLog(module 客户管理, action 删除客户)标注需要记录日志的方法配合AOP切面在方法执行后把操作人、IP、操作内容插入sys_operation_log表。这个日志功能在前台页面没有任何存在感但它对内部管理意义极大。比如领导问上周谁删了一个重要客户运维人员查一下操作日志表就能快速定位。毕设答辩时评审老师也很吃这一套——体现了你考虑了系统的可追溯性和安全审计需求。7.4 系统上线前必须做的清理与配置调整项目从开发环境切到生产环境时有几个配置值得检查关闭Swagger接口文档或者加上访问权限避免接口信息裸奔。我在生产环境用springfox.production.enabledfalse来关闭。修改JWT密钥不要使用代码里写死的默认密钥。合理的方式是配置在环境变量中比如jwt.secret${JWT_SECRET}。数据库连接池参数调整初始大小、最大连接数根据机器配置来定比如initial-size: 5、max-active: 50。前端构建时关闭sourcemapproductionSourceMap: false避免源码泄露也减少版本包体积。写到这里这套系统的技术核心和我在实际开发中的经验大体说完了。从我自己的体会来说做一个客户关系管理信息系统难点并不在于某个单独的技术点——SpringBoot和Vue都是成熟的框架随便照着一本书敲都能跑通——真正拉开差距的地方在于你有没有把业务规则想清楚有没有把权限模型做成软编码可配置的系统有没有在细节体验上下够功夫。客户归属权的数据隔离做得不严谨上线后销售之间就能互看客户全区客户泄漏这在真实业务里是灾难Excel导入校验不做运营同事导一次数据就要来找你一次。这些点才是一个系统从能运行到好用的分水岭。另外如果你这个标题也是毕业设计选题我多说一句不要只把功能代码堆出来就交差。把表结构设计文档、接口说明文档、权限模型图理清楚。答辩时老师问你的通常是为什么这样设计表权限是怎么控制的这个字段为什么冗余你如果能把项目中每一个设计的取舍理由讲明白其实比功能数量多寡更能赢得认可。
返回列表