ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

SpringBoot+Vue+MyBatis+MySQL构建OA管理系统:从架构设计到部署实战

SpringBoot+Vue+MyBatis+MySQL构建OA管理系统:从架构设计到部署实战 接手过企业内部管理系统的人都有体会不管规模大小这类项目最麻烦的不是技术多高深而是业务逻辑绕、人员关系复杂、权限规则细碎。我这次做的这套OA管理系统选型就是SpringBootVueMyBatisMySQL前后端完全分离从零搭建到部署上线整个过程踩了不少坑也沉淀了一些复用的经验。这篇文章把这套系统的核心设计、数据库结构、认证授权链路、业务模块实现以及打包部署细节全部整理出来给正在做类似项目或准备上手前后端分离项目的朋友一个完整参考。1. 从一开始就想清楚这套OA系统为什么采用前后端分离1.1 传统单体结构开发OA日常哪里让人崩溃早期OA系统最常见的形态是JSPServlet或者SpringMVC直接渲染Thymeleaf模板。页面是服务端拼出来的前端代码和后端Java代码混在一个工程里。表面上开发起来省事一个请求从Controller直接对应到一个HTML页面但实际维护一段时间后问题就暴露了。最直观的痛点在于改页面要动后端。OA系统里审批单的样式、表单校验规则、列表展示字段这些调整是高频需求。今天人事说请假单要加一个紧急程度字段明天财务说报销单的金额要用大写显示每次都要去改Java代码里的ModelAndView然后重新编译、打包、重启Tomcat。整个流程走下来一次小改动花大半天很正常。另一个问题是分工混乱。前端页面和后端逻辑绑在一起前端工程师要迁就后端的模板语法后端工程师要处理一堆CSS和JavaScript两边都做得不舒服。尤其遇到页面白屏、脚本报错排查的时候要从前端HTML一路追到后端源码效率极低。还有部署上的麻烦。一套传统单体OA前端页面更新也要跟着整个WAR包一起发版。如果只是改了一个按钮的颜色也得走一次完整的发版流程风险高牵涉面大。1.2 这套架构重点解决的四个核心问题这次重做OA系统目标很明确就是要解决上面提到的痛点同时为后续迭代留出空间。前后端分离这个方向从一开始就确定了具体来说要解决四个问题。第一个是开发和维护效率。前后端分离之后前端工程和后端工程是独立的代码仓库可以并行开发。前端用Mock数据先行开发页面后端专注接口实现两边对上接口文档即可。改版时互不影响各自发版。第二个是权限控制的灵活性。OA系统的权限要求比普通网站严格得多不同角色的员工登录后看到的菜单、能点的按钮都不一样。前后端分离下后端通过接口返回菜单和权限标识前端根据权限动态渲染路由和按钮权限模型更加清晰可控。第三个是接口复用性。企业OA未来大概率要出移动端或者对接钉钉、企业微信这类办公平台。只要接口是标准的RESTful API移动端可以直接复用不用重新写一套后端逻辑。这个考量在选型时就定下了基调。第四个是多环境部署的灵活性。开发环境、测试环境、生产环境的数据库地址、文件存储路径、日志级别都不同。前后端分离后前端通过环境变量切换API地址后端通过Spring Profile切换配置互不干扰部署时只需要替换配置文件代码不用改。2. 技术选型复盘SpringBootVueMyBatisMySQL各自解决的问题2.1 SpringBoot版本选择以及版本太高带来的实际问题SpringBoot目前主流的稳定版本是2.7.x系列和3.x系列。很多新手在创建项目的时候会习惯性选择最新版本但这一步往往给后续部署埋下隐患。SpringBoot 3.0以上强制要求JDK 17而很多企业服务器的生产环境还停留在JDK 8。如果本地开发电脑用的是JDK 17代码跑得欢一放到服务器上就报UnsupportedClassVersionError折腾半天才反应过来是版本不匹配。这套OA系统我选用的是SpringBoot 2.7.18兼容JDK 8和JDK 11是目前企业项目里用的最多的版本序列。依赖相对稳定网上资料丰富遇到问题容易搜到解决方案。在选择SpringBoot版本时不要只看功能要结合目标部署环境来决定。如果服务器JDK是8就用2.7.x如果确定全链路都是JDK 17以上才考虑3.x。还有一个实际问题是SpringBoot版本对依赖版本的影响。SpringBoot 2.7默认管理的MyBatis Stater版本、MySQL Connector版本都是经过官方测试的直接继承父POM的依赖管理即可不需要手动指定版本。如果换成SpringBoot 3.x部分老版本的依赖会因为Javax命名空间改成Jakarta而无法兼容改起来很痛苦。2.2 Vue侧选型Vue3Element Plus还是Vue2Element UI前端框架的选择在这套OA系统里其实经历过摇摆。Vue2的生态成熟Element UI组件库全面网上案例多Vue3的响应式原理更高效组合式API写起来更清爽组件库方面Element Plus也在持续完善。最终确定Vue3 Element Plus原因有几个。第一OA系统里的表单和表格交互非常频繁Vue3的组合式API对逻辑复用很友好比如查询条件、分页逻辑、表单校验这些代码可以抽成可复用的函数减少重复代码。第二Element Plus对Vue3的支持是官方维护的组件覆盖了OA需要的几乎所有场景——表格、树形控件、上传、日期选择、对话框不用额外找第三方库。第三新项目用新框架社区在持续投入后续遇到问题更容易获得支持。不过如果你手里的项目要兼容IE浏览器或者团队里没人用过Vue3那退回到Vue2 Element UI也完全没问题。这套系统的接口设计是标准RESTful风格前端用什么框架都能对接不会锁死。2.3 MyBatis的价值以及和MyBatis-Plus的取舍持久层选MyBatis而不是JPA/Hibernate是考虑到OA系统的业务特点。OA里的查询条件非常灵活比如可以根据部门、时间范围、状态、关键字等多个维度筛选用户或单据这种动态多条件查询如果用JPA来写要么拼Specification要么用QueryDSL学习成本和维护成本都不低。MyBatis的XML里写动态SQL是最直观的if、where、foreach这些标签几乎是为这种场景量身定做的。另一个原因是SQL的可控性。OA系统里资金相关的报表SQL可能有几十行关联多张表、嵌套子查询用MyBatis可以在XML里直接优化SQL执行计划定位慢查询也方便。至于MyBatis-Plus它提供了内置的CRUD方法和分页插件确实能省不少基础代码。这套系统在部分基础模块使用了MyBatis-Plus的单表操作能力但在复杂查询场景依然保留XML手写SQL。如果团队对SQL掌控力一般用MyBatis-Plus做基础CRUD是很好的选择但不要完全依赖它复杂的多表关联还是得自己写SQL。2.4 MySQL安装版本以及连接串里的几个关键参数数据库选的MySQL版本是8.0。相比5.78.0的默认字符集是utf8mb4原生支持窗口函数和CTE性能也有提升。不过8.0默认的密码加密方式是caching_sha2_password一些老版本的客户端工具连接不上需要在创建用户时指定mysql_native_password或者升级客户端连接驱动。这个细节在部署教程里通常不会重点标注但真的很常见。数据库连接串里有几个参数值得注意。第一个是serverTimezoneAsia/Shanghai如果不设置JDBC驱动默认时区和服务器不一致日期查询会出现8小时偏差。第二个是useUnicodetruecharacterEncodingutf8mb4确保中文和特殊字符存储正常。第三个是useSSLfalse本地开发和内网环境不需要SSL加密省去证书配置的麻烦。spring.datasource.urljdbc:mysql://localhost:3306/oa_system?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/ShanghaiuseSSLfalse spring.datasource.usernameroot spring.datasource.passwordyour_password spring.datasource.driver-class-namecom.mysql.cj.jdbc.Driver3. 数据库设计与后端骨架搭建这些细节决定后续开发顺不顺畅3.1 一张表清单看懂OA系统的核心表结构OA系统的数据模型围绕用户—角色—权限—业务单据这条主线展开。下面是这套系统中几张核心表的结构设计基本覆盖了权限认证和审批流程的主链路。系统基础表表名用途关键字段sys_user用户表id, username, password, real_name, dept_id, statussys_dept部门表id, parent_id, dept_name, order_numsys_role角色表id, role_name, role_key, statussys_menu菜单/权限表id, parent_id, menu_name, path, perms, menu_typesys_user_role用户角色关联表user_id, role_idsys_role_menu角色菜单关联表role_id, menu_idsys_notice公告表id, title, content, status, create_time业务单据表表名用途关键字段oa_leave请假申请单id, user_id, leave_type, start_time, end_time, reason, status, approver_idoa_expense报销申请单id, user_id, amount, expense_type, detail, status, approver_idoa_approval审批记录表id, business_type, business_id, approver_id, approval_result, comment, create_time设计时要特别注意几点。第一用户表和部门表是多对一关系部门删除前要校验是否还有员工挂在这个部门下否则会产生孤儿数据。第二菜单表通过type字段区分目录、菜单和按钮按钮类型的权限用于前端按钮级控制。第三业务单据表里冗余一个approver_id作为当前审批人查询待办事项时直接根据approver_id和status就能查出当前用户需要处理的任务不用每次去关联审批流配置表。3.2 后端工程分包结构与启动流程后端项目按照常见的分层架构分包每个包职责清晰后续维护时能快速定位代码位置。com.oa.system ├── common // 通用工具类、常量、统一返回结果封装 ├── config // Spring配置类Redis、跨域、安全等配置 ├── controller // 接口层接收请求参数校验 ├── service // 业务逻辑层 │ └── impl ├── mapper // MyBatis接口 ├── entity // 数据库实体 ├── dto // 前端交互的数据传输对象 ├── vo // 视图对象用于返回给前端的组装数据 └── security // 认证授权相关JWT过滤器、登录处理启动流程里有一个非常容易忽略的细节数据库初始化。很多新手拿到项目后直接运行Application报了一堆表不存在的错误才发现忘了导入SQL脚本。建议在项目根目录放一个sql文件夹里面按顺序编号存放建库建表脚本和初始化数据脚本配合README说明其他人接手时直接按说明执行即可。3.3 MyBatis配置文件里最容易翻车的两个点第一个是mapper-locations路径问题。如果Mapper接口和XML文件没有放在同一个包下必须在application.yml里显式声明XML文件的位置否则启动时会报Invalid bound statement错误。常见做法是在resources目录下建和mapper接口相同包名路径的文件夹比如com/oa/system/mapper配置如下mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.oa.system.entity configuration: map-underscore-to-camel-case: true第二个就是那个经典的单字符比较问题。MyBatis的XML里如果用if teststatus 1这种写法框架会把1当成char类型和String类型的status比较会报ODI错误或者比较结果不正确。正确写法用双引号包裹if teststatus 1 AND status 1 /if这个坑在热词里也被大量搜索可见中招的人非常多。我最初也被这个坑卡了一下午后来养成习惯MyBatis动态SQL里只要有单字符比较一律用双引号包外层、单引号包内层或者反过来。这里整理的数据库连接、分包结构、MyBatis配置是搭建阶段最核心的三件事这三处做好了后面业务模块的编码效率会高很多。4. 登录认证与token处理前后端分离系统最核心的一环4.1 JWT认证流程从登录到接口放行的完整链路前后端分离系统的认证方案主流是JWT。相比Session方案JWT天然适合无状态接口后端不需要维护会话状态前端拿到token存在本地后续请求在Header里带上即可。这套OA系统的JWT认证流程如下用户提交用户名和密码到/api/login接口。后端查询用户表校验密码BCrypt加密后的密文比对。校验通过后用JWT工具类生成tokentoken里包含用户ID和用户名并设置过期时间一般2小时。后端返回token和一个refreshToken用于过期后刷新token。前端收到token后存入localStorage和Vuex/Pinia状态管理。后续每次请求前端在axios请求拦截器里把token放到Authorization请求头。后端拦截器拦截所有非白名单接口解析token解析成功则放行失败则返回401状态码。JWT工具类的核心代码大致长这样public String generateToken(Long userId, String username) { Date now new Date(); Date expiryDate new Date(now.getTime() EXPIRE_TIME); return Jwts.builder() .setSubject(username) .claim(userId, userId) .setIssuedAt(now) .setExpiration(expiryDate) .signWith(SignatureAlgorithm.HS512, SECRET_KEY) .compact(); }这里特别提醒一点JWT的SECRET_KEY一定不能硬编码在代码里也不要选取太简单的字符串。生产环境应该通过配置文件或者环境变量注入并且定期更换。如果密钥泄露攻击者可以直接伪造任意用户的token这比密码泄露还要可怕。还有一个实际设计经验不要把密码、手机号等敏感信息放进token的payload里。JWT的payload是Base64编码不是加密的任何人拿到token都能解码看到内容。4.2 Vue侧token处理axios拦截器和路由守卫的完整方案前端这块token处理是前后端分离项目的重中之重。热词里专门有vue前后端分离请求token处理这一条说明这个问题讨论度非常高。axios拦截器的封装我分成三步来做。第一步请求拦截器。在发送请求前从localStorage获取token如果有就设置到请求头的Authorization字段。注意判断是否存在token以及token是否过期。service.interceptors.request.use( config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config }, error Promise.reject(error) )第二步响应拦截器。在这里统一处理HTTP响应后端返回的数据一般会封装成{ code, message, data }结构拦截器里直接把data取出来页面代码不用每次解包。同时要处理一个关键情况token失效。后端返回401时说明token过期或非法此时应该清除本地token跳转到登录页面。这里要注意防止多个请求同时401时重复跳转得加个标记位控制。第三步创建共享的请求方法。按业务模块封装get/post/put/delete方法每个方法带Loading状态处理接口层面对前端组件完全透明。路由守卫主要负责页面访问控制。在Vue Router的beforeEach钩子里判断目标路由是否需要登录权限需要的话就检查是否有token。同时根据用户登录后缓存的菜单权限数据动态添加路由实现菜单级权限控制。router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path /login) { next() return } if (!token) { next(/login) return } if (to.matched.length 0) { next(/404) return } next() })4.3 权限控制的具体落地菜单权限和按钮权限OA系统的权限不是简单登录后就能访问所有模块不同角色的用户看到的菜单、能操作的按钮必须做区分。这套系统采用RBAC模型用户→角色→权限三层结构。后端在用户登录成功后根据用户ID查出所有角色再根据角色查出所有菜单权限返回给前端一个菜单树和权限字符集合。前端拿到菜单数据后动态生成侧边栏菜单权限字符集合存在状态管理里用于按钮级别的判断。菜单权限的实现方式有两种一种是前端写死全部路由登录后根据返回的菜单数据过滤出可见的菜单另一种是前端只写基础路由动态添加有权限的路由。按钮权限的实现主流是通过自定义指令比如v-permissionsys:user:add指令内部检查用户权限集合里是否有这个权限标识没有就移除该DOM。后端接口层面还要做二次校验防止有人绕过前端直接调用接口。比如删除用户接口在Service层先检查当前操作人是否有sys:user:delete权限没有就直接抛出权限不足异常。这里有一个容易被忽略的问题不要只根据登录接口返回的角色或权限做判断有些用户角色信息在登录到使用期间可能被管理员修改。稳妥的做法是每次进入某个模块时或者定期刷新用户权限信息。这套系统的做法是路由切换时如果路由配置里声明了需要的权限标识前端会从状态管理里检查同时后端接口对关键操作也会用自定义注解做权限校验双保险。5. OA核心业务模块的实现思路从审批流到公告管理5.1 审批流模块状态流转和待办事项的设计思路审批流是OA系统里最复杂、也最有代表性的模块。请假、报销、采购这些场景本质上都是提交申请→上级审批→结果通知的流程。这套系统没有引入专门的流程引擎而是用简单的状态机来支撑对中小企业的OA系统来说是性价比最高的方案。审批状态的定义状态值状态名称说明0草稿用户保存但未提交1待审批已提交等待审批人处理2已通过审批通过流程结束3已驳回审批被驳回可修改后重新提交4已撤回申请人撤回已提交的申请核心表就两张业务单据表请假、报销和审批记录表。业务单据表里有一个current_approver_id字段指向当前待处理的审批人。查询某人的待办事项就是查所有status1且current_approver_id当前用户ID的单据一条SQL就能搞定。审批动作的实现逻辑提交申请生成业务单据status1approver_id设置为审批人审批通过如果有多级审批approver_id指向下一级审批人如果已到终审status更新为2审批驳回status更新为3在审批记录表里写入驳回原因撤回审批只有在前一个审批人尚未处理的情况下才允许撤回否则撤回失败审批记录表的每条数据记录了谁在什么时间对哪张单据做了什么操作、审批意见是什么方便后续追溯和审计。如果以后要升级成复杂的会签、或签、条件分支等流程可以引入Flowable或Activiti但中小企业OA一般用不到那种复杂度。状态机方案的优势是简单、透明、可维护出问题容易排查。5.2 部门与用户管理树形结构怎么处理部门组织结构天然是树形结构这套系统用parent_id字段表示层级关系根部门的parent_id为0。后端查询时有两种方式。一种是递归查询每次根据parent_id查子部门优点是SQL简单缺点是数据库查询次数多。另一种是一次性查出所有部门在内存中组装成树一次查询搞定效率高缺点是当部门数据特别多时内存占用稍大。对一套OA系统来说部门数量一般不会超过几百个完全可以用第二种方式而且实现起来也不复杂。public ListDeptVO buildDeptTree(ListSysDept deptList) { MapLong, DeptVO deptMap new HashMap(); for (SysDept dept : deptList) { DeptVO vo new DeptVO(); BeanUtils.copyProperties(dept, vo); deptMap.put(dept.getId(), vo); } ListDeptVO tree new ArrayList(); for (DeptVO vo : deptMap.values()) { if (vo.getParentId() 0) { tree.add(vo); } else { DeptVO parent deptMap.get(vo.getParentId()); if (parent ! null) { parent.getChildren().add(vo); } } } return tree; }部门模块有个经验值得分享删除部门前除了检查有没有子部门还要检查有没有用户挂在这个部门下。很多OA系统上线第一周就出现员工找不到部门的情况不是数据丢了就是删部门的时候把关联用户给一起带崩了。用户管理模块查询条件一般包含关键字用户名、姓名、手机号、部门、状态。这里用MyBatis动态SQL做多条件分页查询配合PageHelper分页插件逻辑清晰接口响应快。5.3 公告与文件模块上传下载的实现细节公告模块的目的是发布公司通知、规章制度、内部动态。表结构里有title、content、status草稿/已发布、pinned是否置顶、create_time、publish_time。查询时置顶公告排前面其余按发布时间倒序。已发布的公告对所有人可见草稿公告只有管理员可查看。文件管理这块考虑到OA系统的实际使用场景文档上传、图片展示、下载是最基本的需求。存储方案有几种本地磁盘存储、FastDFS分布式存储、阿里云OSS。对中小企业的OA系统来说本地磁盘存储最简单部署时挂载一个专门的目录作为文件存储盘上传时把文件写到磁盘数据库保存文件访问的相对路径前端通过后端接口访问。上传接口的设计有一个关键点文件大小限制和类型校验。SpringBoot默认上传大小是1MB需要手动调到10MB甚至更大配置文件里这样设置spring: servlet: multipart: max-file-size: 20MB max-request-size: 20MB文件类型校验不能只判断扩展名要同时校验文件的Content-Type否则一个恶意用户改后缀名就能上传可执行文件这会成为安全隐患。文件访问权限也比较重要。OA系统里的文件不应该直接放在nginx静态目录下公开访问而是通过后端接口做权限校验后返回。前端下载文件时带上token请求文件接口后端校验无误再用流的方式返回文件内容。这样权限控制和文件访问是统一的。6. 从开发机到服务器打包部署全流程与坑点实录6.1 SpringBoot后端打JAR包的两个关键操作后端部署标准做法是用Maven打成可执行JAR包放到服务器上通过java -jar运行。打JAR包之前注意两个关键配置。第一个是跳过测试如果项目里写了单元测试但测试类依赖数据库连接打包时不跳过测试就会因为连不上测试库而失败。用下面这个命令mvn clean package -DskipTests第二个是生产环境配置文件要独立出来。项目里至少要有application-dev.yml、application-prod.yml或者通过application.yml配合spring.profiles.activeprod来切换环境。打包时不用改任何代码启动命令指定profile即可java -jar oa-system.jar --spring.profiles.activeprod --server.port8080JAR包启动还有一个细节内存参数。默认的JVM堆内存可能不够用尤其是同时运行前端构建工具和后端服务的时候。建议显式指定堆内存比如-Xms256m -Xmx512m根据服务器配置调整。6.2 Vue前端打包和部署路径的坑前端打包核心命令是npm run build完成后生成dist目录。但dist目录里的资源引用路径默认是绝对路径/。如果nginx把前端部署在域名根路径下没有问题如果部署在子路径下比如http://ip:8081/oa/直接访问就是白屏资源全部404。解决办法是修改vue.config.js里的publicPath改成相对路径或者绝对路径module.exports { publicPath: ./ }改成相对路径后dist目录里的js/css资源会使用相对路径加载不管部署在哪个子路径都能正常工作。这种做法在多数场景下是最省心的。还有一个常见的坑是打包后布局异常。热词里专门有vue 打包后 布局异常这条我实际也遇到一次。排查下来发现是Element Plus组件的样式没被完整引入或者是因为使用了按需引入但是有组件被遗漏。解决方案是检查main.js里的样式导入方式建议在开发阶段先全量引入Element Plus样式打包发布前再优化为按需引入避免样式缺失。6.3 Nginx反向代理配置模板前端打包后的dist目录通常用Nginx托管。Nginx的核心配置有两块静态文件服务以及API反向代理。静态文件服务的配置就是root指向dist目录index指定index.html。关键点是history路由模式下的重写规则。Vue Router如果用history模式刷新一个子页面时Nginx会去磁盘上找对应的路径找不到就返回404。解决办法是配置try_files让所有路径都回退到index.html由前端路由接管server { listen 8081; server_name localhost; root /data/www/oa-web; index index.html; location / { try_files $uri $uri/ /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; } }反向代理这一块实质上是把前端请求的/api开头的接口转发到后端SpringBoot服务。这样前后端的域名完全统一绕开了跨域问题。前端的axios请求baseURL配置为/api和Nginx的location规则对应即可。这里还有个细节代理配置里proxy_set_header带上Host和真实IP。后端Log里打印的是真实客户端IP地址方便排查问题。如果没有这段配置所有的日志IP都是127.0.0.1出了问题很难定位具体是哪个用户在操作。6.4 部署上线后客户反馈的真实问题部署完成后客户试用阶段暴露了很多在开发环境不会出现的问题这里挑几个典型的复盘。第一个是日期显示问题。客户反馈审批单上的时间比实际时间少了8小时。排查发现两个原因叠加前端没有做时区转换后端返回的时间是带时区的Date对象序列化后显示的是UTC时间。解决方法是后在配置里指定Jackson序列化格式spring: jackson: time-zone: Asia/Shanghai date-format: yyyy-MM-dd HH:mm:ss第二个是文件上传后中文文件名乱码。问题出在nginx代理没有设置header大小和编码。上传文件的接口返回的文件名如果是中文浏览器可能把UTF-8编码当成ISO-8859-1解读。解决方法是后端设置Content-Disposition时显式指定UTF-8编码。第三个是首次访问页面加载慢。OA首页要加载大量菜单权限数据、公告数据、待办数据多个接口串行请求导致白屏时间长。优化方案是首页改为并行请求并且后端对角色菜单数据做Redis缓存二次访问明显加速。第四个是客户在办公室内网访问系统图片加载正常但上传大文件时经常超时。排查下来是nginx默认的客户端请求体大小限制是1MB大文件直接返回413错误。调整nginx配置client_max_body_size 50m;部署上线后遇到的这些问题开发环境几乎无法提前暴露只有放到真实网络环境里才会触发。这也是为什么我一直强调部署前一定要在测试环境完整跑一遍尤其是文件上传、日期展示、路由刷新这些高频操作。这套OA系统从开发到上线前后用了差不多一个半月时间核心代码量不算夸张真正耗时的是各种边界情况的处理和联调。如果你也要做类似的企业管理系统架构上直接参考这套前后端分离的方案技术上把认证授权链路、审批状态流、部署配置这三块啃透基本就把握住了这类项目的骨架。实际动手时如果卡在哪个环节多看看日志多想想数据在前后端之间怎么流转很多问题其实自己就能定位出来。
返回列表