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

从零构建企业内管系统:SpringBoot+Vue前后端分离实战

从零构建企业内管系统:SpringBoot+Vue前后端分离实战 ★ FEATURED ARTICLE
1. 为什么前后端分离是内管系统的最优解1.1 从实际需求出发我为什么要重构这个内管系统企业内管信息化系统这个词听起来有点大实际上落到日常就是一堆非常具体的痛点考勤统计要靠人力月底加班合同审批靠邮件来回转发资产台账在Excel里改得面目全非新员工入职权限要IT手工到各个系统去开。我之前接手过一个某公司的内管系统是个用旧技术栈写的单体应用页面嵌入在Java代码里业务逻辑和界面展示耦合得相当深加一个字段要改后端模板、改页面表单、改SQL脚本三处联动改完还要全量回归。最难受的是前端改动动不动就要动整个工程重新发布部署窗口被卡得很死。后来我下定决心把这套东西推倒重来核心思路就是做前后端分离。后端只负责暴露接口用SpringBoot做基础框架持久层用MyBatis配合MySQL前端独立成一个工程用Vue做页面框架通过HTTP接口跟后端交互。整个项目做下来最大的感受是前后端分离不只是把代码拆成两个目录而是把数据提供和数据展示两件事彻底解耦让两端的开发节奏、发布节奏、技术演进节奏都能独立进行。这篇博文就把我从零搭建这套企业内管信息化系统的过程、设计逻辑和最终部署流程完整复盘一遍包括关键代码、常规文档不会写的坑以及我自己的优化心得。如果你想自己搭一套类似的系统照着走一遍就能跑起来更重要的是能理解每一步为什么这么做。1.2 传统单体方案的痛点与前后端分离的收益先说说我为什么铁了心要分。很多企业内部系统的体量其实不大用户几百人并发也不高用单体架构完全跑得动这点我不否认。但内管系统的痛点从来不是性能而是需求变更频率和多方协作效率。传统JSP或Thymeleaf方案里浏览器拿到的HTML是在服务器端渲染好的页面上一个按钮的显示逻辑可能由后端Java代码决定。这就意味着两件事被绑死了后端开发要懂前端模板前端调整样式要等后端改完一起发布。更麻烦的是测试范围前端一个小改动整个应用要重新打包、重启、回归一次发布动辄半小时起步。我在旧系统上改过一个下拉框的默认选项前端页面、后端枚举、数据库字典三处都有逻辑改完忘了同步数据库字典线上直接出现了空选项这个经历让我下定决心要拆。前后端分离之后收益体现在几个维度发布解耦前端和后端各自独立部署前端改样式、改交互逻辑后端完全不需要动后端调整接口前端可以先用Mock数据继续开发两边的工期不再互相牵制。职责清晰后端只关心数据模型、业务规则、权限校验返回JSON前端只关心页面表现、交互状态、接口调用。出问题的时候排查边界很清楚不用在混合代码里翻来翻去。可复用性后端接口不绑定页面以后做移动端小程序、对接第三方系统直接复用同一套API。我在这套系统里就顺手给一个内部数据大屏做了几个汇总接口完全零成本。技术栈演进空间前端可以独立升级后端也可以独立调整技术组件不会因为互相依赖导致升级顾虑重重。当然前后端分离也不是没有代价接口文档的维护成本、跨域处理、权限控制要从前后端两侧分别考虑、部署链路变长。这些问题我在后面都会给出具体方案毕竟做工程不是做单选题重要的是知道成本在哪、怎么控制。1.3 技术栈选型逻辑技术选型这件事我见过太多人一上来就追求新项目还没影呢先堆了一堆中间件。我的原则是选团队最容易上手、生态最成熟、出了问题最好找解决方案的组合。这套内管系统我最终定了SpringBoot Vue MyBatis MySQL理由很直接组件选型核心理由后端框架SpringBoot生态成熟自动配置大大减少XML配置社区资料海量招人好招持久层MyBatis内管系统大量复杂的多表查询、动态条件筛选SQL可以直接掌控优化成本低前端框架Vue学习曲线平缓模板语法直观组件生态丰富做后台管理界面效率极高数据库MySQL稳定可靠运维门槛低内管系统的数据量级完全够用支持事务和复杂查询前端UI组件库Element Plus后台管理场景的开箱即用组件非常齐全表格、表单、弹窗、树形控件都很成熟构建工具Vite开发启动速度快热更新体验好比旧版Webpack在中小项目上明显省时间这套组合放在2024年的企业内管系统场景里属于稳得不能再稳的搭配。有人会问为什么不用MyBatis-Plus我个人的看法是内管系统里自定义SQL查询的场景非常多MyBatis原生能力加一个通用的BaseMapper就能覆盖大部分CRUD引入Plus反而多一层封装要学习、要排查。当然你用Plus也没问题这套架构里把它替换掉很轻松不影响整体设计。2. 系统功能边界与数据库设计2.1 内管系统的典型模块划分企业内管信息化系统这个内管每个公司定义不一样但骨架通常是相近的。我在设计这套系统时结合最常见的需求划分了这样几个核心模块组织架构管理公司、部门、岗位的管理树形结构展示支持部门新增、调整、合并、停用。这是整个系统的地基后续的审批流、数据权限都依赖它。用户账号管理员工账号的创建、禁用、重置密码、分配角色。这里需要处理好员工离职、转岗带来的账号状态变化。角色权限管理基于RBAC模型的角色定义、菜单权限分配、按钮权限控制。内管系统的敏感操作比如工资查看、合同审批、数据导出都要靠这一层守住。流程审批OA类请假、报销、用章申请等常见审批流核心是一个可配置的审批步骤定义配合每个节点的处理人和处理结果流转。通知公告管理内部通知的发布、编辑、归档以及已读未读统计。资产与物品管理固定资产台账、领用记录、维修记录。模块的边界划分有一个原则我特别想强调每个模块尽量是高内聚的模块之间只通过明确的接口或者数据关联来协作不要出现互相渗透的业务逻辑。比如资产模块要显示使用人它应该通过用户ID回查用户基本信息而不是把员工的姓名、部门直接冗余到资产表里否则用户改部门的时候资产表里的旧值就会变成脏数据。2.2 核心表结构与ER梳理数据库设计是整个系统里最需要花时间的部分。我不太赞成上来就画一大堆ER图更实际的做法是先把业务对象列清楚然后逐个设计表再去理表间关系。这套系统的核心表我列一下关键字段用户表sys_user字段类型说明idbigint主键usernamevarchar(50)登录账号唯一passwordvarchar(100)加密后的密码BCryptreal_namevarchar(50)真实姓名dept_idbigint所属部门IDstatustinyint1正常 0禁用create_timedatetime创建时间角色表sys_role字段类型说明idbigint主键role_namevarchar(50)角色名称role_codevarchar(50)角色编码如ADMIN、MANAGERremarkvarchar(200)备注用户角色关联表sys_user_roleuser_id role_id联合唯一索引。菜单权限表sys_menu核心字段包括parent_id父菜单、menu_name、menu_type目录/菜单/按钮、path路由地址、permission权限标识如system:user:add。部门表sys_deptparent_id、dept_name、ancestors字段存祖先链如0,1,5这招是从一些成熟框架学来的查某个部门下的所有子部门时效率极高不需要递归查询。审批表oa_leaveuser_id、leave_type、start_time、end_time、days、reason、status。审批记录表oa_leave_record存每个节点处理人、处理动作、意见和时间。表与表之间的关系核心就是用户-角色-菜单这种经典RBAC关系以及审批主表和审批记录表的一对多关系。建表的时候有几条我踩过坑之后的经验所有表都要带create_time和update_time而且让数据库自动维护DEFAULT CURRENT_TIMESTAMP和ON UPDATE。用户名、部门名这种字段加上唯一索引业务上不会出现重复必须在数据库层面兜底。外键我全部没用。逻辑外键足够物理外键在后期数据迁移、批量导入时只会添乱。2.3 RBAC权限模型落地细节RBAC基于角色的访问控制这套模型理论很好懂给用户分配若干角色每个角色绑定一堆权限用户通过角色间接获得权限。但落地的时候有两个细节比较容易出问题。第一个是菜单权限和按钮权限的区分。我的做法是菜单权限控制你能不能进入某个页面按钮权限控制你在页面里能不能点某个按钮。前端根据权限标识动态渲染按钮后端接口再用同一个权限标识做拦截。比如用户管理页的删除按钮前端用v-if判断是否有system:user:delete这个权限标识有才渲染后端在删除接口上标注这个权限要求没有权限的人直接调接口也会被拦截。两层都做前端体验好后端才真正安全。第二个是数据权限。这个比菜单权限复杂很多处理的是同一份数据谁能看哪些行。我在这套系统里做了一个简化的方案部门数据权限级别分三种——仅本人、本部门、全部。查询的时候根据当前用户的数据权限级别在SQL里自动拼接dept_id条件或者user_id条件。这个逻辑不能放在前端必须由后端在组装查询条件时完成否则等于没权限控制。权限相关表的经典查询就是根据用户ID取角色再根据角色取权限列表两者都是多表连接。MyBatis里写这种查询非常顺手下面这段SQL是权限树查询的核心给后端开发参考SELECT DISTINCT m.id, m.parent_id, m.menu_name, m.path, m.permission, m.menu_type FROM sys_menu m LEFT JOIN sys_role_menu rm ON rm.menu_id m.id LEFT JOIN sys_user_role ur ON ur.role_id rm.role_id WHERE ur.user_id #{userId} AND m.status 1 ORDER BY m.order_num3. SpringBoot后端核心实现3.1 项目分层与统一响应体设计后端工程结构我按照经典的分层来组织从 controller 到 service 到 mapper 三层加上一个独立的 entity 包放实体对象、dto 包放接口入参和出参对象、common 包放公共类。好的分层能让业务代码清晰但真正让项目规整起来的是规范了每层各干什么、不干什么Controller接收参数、做参数校验、调用Service、返回结果。不写任何业务逻辑。Service执行业务规则事务边界在这里一个方法就是一个完整业务操作。Mapper访问数据库一个方法对应一条SQL或者一个持久化操作不掺业务判断。实体类与表结构对应直接ORM映射尽量不扩展业务字段避免脏数据混入。接口返回格式我设计了一个统一响应体前端拿到永远是这个结构不管成功还是失败。这个结构的价值在后面的维护期尤其明显接口数量多了之后如果没有统一格式每个人的返回风格都不一样前端对接一个接口问一次。public class RT implements Serializable { private Integer code; // 业务状态码200表示成功 private String message; // 提示信息 private T data; // 业务数据可以是对象、列表、分页结果 public static T RT ok(T data) { RT r new R(); r.code 200; r.message success; r.data data; return r; } public static T RT fail(Integer code, String message) { RT r new R(); r.code code; r.message message; return r; } }业务状态码我做了简单约定200成功、400参数错误、401未认证、403无权限、500服务器异常。不要搞一长串业务码内管系统的调试成本主要在前端联调简单够用最重要。3.2 JWT认证与接口鉴权链路前后端分离之后因为页面和后端不再共享Session传统基于HttpSession的登录态维护方式就不好用了。我选用JWTJSON Web Token来做身份认证流程大概是用户登录成功后后端签发一个JWT令牌返回给前端前端把令牌存起来每次请求在请求头带上后端通过拦截器解析令牌确定用户身份。JWT为什么适合这个场景因为它是无状态的后端不需要为每个登录用户维护会话信息天然适合后端多实例部署和水平扩展。而且JWT本身自带过期时间登录状态过期后令牌自动失效。登录接口的核心逻辑是这样PostMapping(/login) public RLoginResult login(RequestBody Valid LoginDTO dto) { // 1. 按用户名查用户校验账号状态 SysUser user sysUserMapper.selectByUsername(dto.getUsername()); if (user null || !BCrypt.checkpw(dto.getPassword(), user.getPassword())) { return R.fail(400, 用户名或密码错误); } if (user.getStatus() ! 1) { return R.fail(403, 账号已被禁用); } // 2. 生成JWT令牌 String token JwtUtil.createToken(user.getId(), user.getUsername()); // 3. 查询权限标识列表前端用它控制按钮显示 SetString permissions sysMenuMapper.selectPermsByUserId(user.getId()); // 4. 返回登录结果 LoginResult result new LoginResult(); result.setToken(token); result.setUserInfo(user); result.setPermissions(permissions); return R.ok(result); }密码存储这块必须强调一点无论如何都不要明文存密码。我用的BCrypt加密它是自适应哈希算法每次加密结果不同验证时用checkpw比较。就算数据库被拖走密码也不能被直接反推出来。JWT令牌解析和用户态注入我通过一个Spring拦截器实现。拦截器在请求进入Controller之前做两件事取请求头里的token验证签名和过期时间把当前用户信息放入ThreadLocal后续业务代码里随时可以获取当前操作人。还有一类接口是登录后也不需要鉴权的比如验证码接口、登录接口本身所以拦截器里配了一个放行名单。权限校验我用的是注解方式。在需要权限的接口方法上标注权限标识PreAuthorize(hasPermission(system:user:delete)) DeleteMapping(/{id}) public RVoid deleteUser(PathVariable Long id) { userService.deleteUser(id); return R.ok(null); }这里用了一个自定义注解加AOP实现核心思路就是从上一小节设计的权限标识列表里去判断当前用户是否包含指定标识。为什么不用现成的Spring Security我在这个项目里特意控制了依赖复杂度内管系统的权限模型就是简单的RBAC加数据权限自定义一套成本很低反而排错更直接。如果你的系统后续要接OAuth2、单点登录再把Spring Security加进来也来得及接口层设计上留了空间。3.3 MyBatis动态SQL与多条件分页查询企业内管系统里最常见的页面就是列表页每个列表页都带着一堆筛选条件按部门查、按状态查、按时间范围查。这种查询用MyBatis的动态SQL写起来非常灵活核心就是where、if这些标签的组合框架会在运行时自动拼接SQL不需要写多个XML文件去穷举查询组合。拿用户列表查询举例Mapper XML里我这样写select idselectUserPage resultTypecom.example.entity.SysUser SELECT su.id, su.username, su.real_name, su.status, su.create_time, sd.dept_name FROM sys_user su LEFT JOIN sys_dept sd ON sd.id su.dept_id where if testusername ! null and username ! AND su.username LIKE CONCAT(%, #{username}, %) /if if testdeptId ! null AND su.dept_id #{deptId} /if if teststatus ! null AND su.status #{status} /if /where ORDER BY su.create_time DESC /select分页我用的PageHelper插件用起来就一行代码PageHelper.startPage(pageNum, pageSize)紧跟其后的查询会被自动拦截拼上LIMIT并且工厂自动生成总数查询。用这个插件有两点经验第一startPage必须紧跟查询方法中间不能有任何其他SQL操作否则分页会跑到别的查询上第二查询条件里的count查询是自动生成的如果有复杂JOIN可以手写count SQL去覆盖不然统计结果可能不对。动态SQL的使用也有一个反面教训。我见过有人把if条件写得特别多接口入参十几个字段全塞进去结果SQL难以阅读、难以优化。我的习惯是列表查询的筛选条件控制在六七个以内超过这个数就考虑拆接口或者做专门的高级查询页。XML里的SQL保持一眼能看懂在查什么比什么都重要。4. Vue前端工程与权限路由4.1 前端工程初始化与依赖前端这部分我用Vue 3加Vite来搭建。Vite在开发阶段的启动速度比老一代构建工具有质的提升热更新时间基本在毫秒级改完代码浏览器立刻刷新。工程依赖的选型也列一下Vue 3组合式API写业务逻辑更清晰setup语法让代码组织自由度高。Vue Router 4路由管理配合权限做动态路由。Pinia状态管理相比VuexAPI更简洁没有多余的样板代码跟组合式API搭配很顺手。Element PlusUI组件库后台管理系统最常用。AxiosHTTP请求库封装拦截器之后统一处理token和错误提示。Sass样式预处理写嵌套和变量方便。项目目录结构上我按功能模块划分目录src/views下按业务模块建目录system、oa、asset等src/api下对应每个模块建一个JS文件管理接口请求src/router里放路由配置src/store里放状态管理。这么划分之后新增一个业务模块就是加一个页面目录加一个对应的API文件注册一段路由思路非常顺畅。4.2 Axios封装与请求拦截器Axios如果不做封装每个页面都直接拿axios发请求代码里会出现大量重复的token注入、错误处理逻辑一旦后端调整返回结构全项目都要改动。封装的核心是把发请求这件事抽象成统一入口。我封装的请求模块包含这几层处理// 创建实例并设置基础配置 const service axios.create({ baseURL: /api, timeout: 15000 }); // 请求拦截器注入token service.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers[Authorization] Bearer token; } return config; }, error Promise.reject(error)); // 响应拦截器统一处理业务状态码 service.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 { // HTTP层错误处理 if (error.response error.response.status 401) { localStorage.removeItem(token); router.push(/login); ElMessage.warning(登录状态已过期请重新登录); } else { ElMessage.error(error.message || 网络异常); } return Promise.reject(error); });业务接口文件在request封装之上再定义一层每个接口函数只负责传参、调接口、返回数据。比如用户管理模块的接口文件import request from /utils/request; export function getUserPage(params) { return request({ url: /system/user/page, method: get, params }); } export function deleteUser(id) { return request({ url: /system/user/${id}, method: delete }); }这样的好处是页面里调用接口时完全不需要关心HTTP细节代码读起来跟读后端接口清单一样清晰。开发阶段后端接口还没就绪时我只要在这些函数里临时返回Mock数据前端可以独立跑起来调试页面这也是前后端分离协作模式很重要的一环。4.3 动态路由与按钮级权限登录之后前端需要根据当前用户的权限生成可访问的路由表。我的做法是登录接口返回权限标识列表时同时返回菜单路由元数据前端拿到之后把路由配置中需要权限的页面与菜单元数据做对比动态注册到路由器上没有权限的路由提前就过滤掉用户即使手动输入URL也无法访问。动态路由的核心代码简化后是这样// 从后端获取当前用户的菜单数据 const menuList await getMenuList(); // 将菜单数据转换为Vue Router的RouteRecord const routes generateRoutes(menuList); // 动态添加路由 for (let route of routes) { router.addRoute(route); }generateRoutes这个过程里有一个关键点后端菜单表里的path字段跟前端组件路径要做约定比如后端存的是system/user/index前端就用() import(/views/system/user/index.vue)来动态加载对应组件。这个约定两端的开发都要知道是动态路由能跑起来的前提。按钮级权限在页面里我用了一个自定义指令调用方式类似v-permissionsystem:user:delete这样比在每个按钮上写v-if更干净页面模板可读性好新增权限校验组件也不影响现有代码。指令内部就是判断当前用户权限列表里有没有这个标识没有就直接把DOM元素移除。状态管理这边登录后我把用户信息、权限标识、菜单列表都放到Pinia里。刷新页面时状态会丢失所以App启动时加一个处理如果本地有token就调用一个获取当前用户信息的接口来恢复用户态而不是要求用户重新登录。这个细节看起来小实际体验上差别很大不加的话用户一刷新就被踢回登录页非常影响使用。5. 完整部署流程与踩坑记录5.1 后端打包与运行配置后端打包环境我列一下JDK 1.8以上、Maven 3.6以上。第一次在服务器上部署时最容易漏的是数据库初始化脚本。我的做法是提供一份完整的init.sql包含所有建库建表语句和基础数据部署的时候直接导入。数据库连接信息放在application-prod.yml里用profiles来区分开发和生产环境生产配置里的密码不要写在代码仓库里部署时用环境变量或者外部配置文件覆盖。本地打包的命令mvn clean package -DskipTests打包产物是一个可执行的jar文件。在服务器上启动我建议用systemd托管而不是直接java -jar丢后台。好处是崩了会自动拉起、开机自启、日志统一管理。systemd的service文件配置大致这样[Unit] Descriptioninner-manage-system Afternetwork.target mysql.service [Service] Userdeploy WorkingDirectory/opt/inner-manage ExecStart/usr/local/java/bin/java -Xms512m -Xmx1024m -jar inner-manage.jar --spring.profiles.activeprod Restartalways RestartSec10 [Install] WantedBymulti-user.targetJVM内存参数方面内管系统一般并发不会特别高给个512M到1G的堆内存足够。不要动不动就-Xmx4g在服务器上浪费内存不说GC停顿反而更长。5.2 前端构建与Nginx反向代理前端构建npm install npm run build构建完成后dist目录就是要部署的静态资源。Nginx在这里承担两个职责一是托管前端静态文件二是把/api开头的请求反向代理到后端服务。之所以要用反向代理是为了解决前后端分离的跨域问题——浏览器访问的是Nginx的域名和端口Nginx转发请求到后端时浏览器感知不到跨域的存在从而规避了CORS配置的麻烦。一份经过线上验证可用的Nginx配置server { listen 80; server_name your-domain.com; # 前端静态资源 root /opt/inner-manage/dist; index index.html; # 后端API反向代理 location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # Vue Router history模式刷新404问题 location / { try_files $uri $uri/ /index.html; } }这里有两处需要特别说明。第一location /api/块里的proxy_pass后面带了这个/它的意思是把请求路径中的/api前缀剥掉再转发给后端。比如前端请求/api/system/user/page后端实际收到的是/system/user/page这个配置要和后端接口的实际路径约定好不然会出现404。第二try_files这行配置是为了处理Vue Router的history模式。正常点击页面内跳转没问题但如果用户在子路由页面刷新浏览器Nginx按实际路径找不到对应的静态文件就必须回退到index.html由前端路由接管。5.3 部署中遇到的三个典型问题部署这件事很多时候坑不在大工程上都是些意想不到的小细节。我把这次部署观察到的三个典型问题复盘一下每个都值得后来者提前避开。第一个是MySQL连接方式导致的性能问题。打包之前开发环境连的localhost数据库一切正常上了服务器之后发现接口响应偶尔很慢排查下来是JDBC连接串里没有加useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue导致每次连接都要做SSL握手和时区转换耗时明显。这个连接串在生产环境务必配好。第二个是静态文件启动权限问题。构建后的dist目录是用root用户拷到服务器上的我部署时用deploy用户启动服务结果Nginx读取静态资源时提示权限不足页面一直403。解决方式是最开始就用deploy用户创建和拷贝目录做好目录归属避免这种无谓的折腾。第三个问题最隐蔽前后端联调时登录功能偶发失败登录后下一次请求提示401。排查后发现是JWT工具类里用了随机salt生成秘钥后端服务重启一次之前签发的所有token全部失效。这个问题的根本解法是JWT的签名秘钥要固定放在配置里而不是每次启动随机生成。修复之后只要token没过期服务重启也不会影响在线用户。6. 性能优化与可扩展性思考6.1 慢SQL排查与索引优化内管系统的用户量不大但数据量会随着时间积累。我最先遇到的问题是审批记录表越滚越大查询我的待办越来越慢。这类问题的排查路径很固定打开MySQL慢查询日志定位执行时间长的SQL用EXPLAIN看执行计划然后补索引。举例说我的待办查询条件是user_id加status组合成一个联合索引idx_user_status (user_id, status)效果立竿见影。这里有个顺手经验联合索引的字段顺序有讲究等值条件放前面范围条件放后面像状态字段如果取值很少只有几个值选择性不高更适合放在联合索引的靠后位置。内管系统里最常见的索引陷阱是日期范围查询、模糊查询、LEFT JOIN的关联字段没索引。部门表、用户表这种关联频繁的表外键字段如dept_id、user_id一定要建索引。MySQL单表数据量到百万级之前这一层优化基本够用不必急着引入复杂的中间件。6.2 缓存与异步任务引入思路这套系统目前没有引入Redis因为大部分场景是实时查询和写操作MySQL直接扛得住。但有一个场景确实值得加缓存权限标识查询。这个接口每个请求都会被拦截器调用逻辑上要关联三四张表。我加了一个Caffeine本地缓存在用户权限变更时主动清除大幅度减少了权限查询的开销。异步任务方面内管系统里发通知写日志统计报表这类操作适合异步化。我把通知推送的逻辑从业务主流程里拆出来用Spring的Async注解实现调用者不再等待推送结果用户体验更顺滑。用Async要记得在启动类上开EnableAsync并且异步方法所在的Bean不能和调用方是同一个类内部调用否则注解不生效这是我实际踩过的坑。6.3 关于二次开发的几点建议这套前后端分离架构搭好之后后续扩展新业务模块的路径其实是非常清晰的。我自己梳理一下大概有这么几条经验可以复用新业务先画表结构再写后端接口最后做页面。表设计类比盖房子打地基后端接口就是水电管线页面是装修顺序反了容易出现返工。后端接口尽量做到幂等设计。内管系统里前端重复点击、重试请求很常见删除接口重复执行没关系但新增接口如果幂等性不好用户点两下就会插入两条重复数据。简单的做法是加唯一约束或者前端提交时做防抖。公共的逻辑抽出来下沉不要在每个模块里各写一份。比如导出Excel的功能我把它做成一个公共的工具类所有列表页统一复用新增模块导出功能完全不需要再开发。前端代码的命名和目录划分要提前约定好不然多人协作时同样功能的代码分布在各种不同的位置维护起来非常别扭。我自己的体会是做企业内管信息化系统真正难的不是某一个技术点而是把功能、权限、流程和数据之间的关系理顺同时把工程结构搭得足够清爽让后面接手的人不会想骂人。这套基于SpringBoot、Vue、MyBatis和MySQL的方案我在实际项目中跑了大半年无论是开发效率还是部署维护成本都达到了当初重构时预期的效果。如果你也正在纠结怎么从零搭建一套内管系统照这个思路走一遍应该能少走很多弯路。
阅读完成 · 觉得有帮助?
咨询建站