ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SpringBoot+Vue多媒体素材管理系统实战:存储、视频预览与部署全解析

SpringBoot+Vue多媒体素材管理系统实战:存储、视频预览与部署全解析 做这套基于SpringBootVue的Web多媒体素材管理系统的时候我其实已经预感到会踩不少坑但真正动手之后才发现坑比预想的多而且大多数坑不在业务逻辑上而在文件存储、流媒体播放、打包部署这些边角料细节上。从最初接到需求——要给公司内部做一个统一管理图片、视频、音频、文档等素材的后台到最终交付可用版本前后大概花了三周。这篇文章把我从技术选型、数据库建模到前后端联调、部署上线的完整过程都梳理一遍重点讲清楚每一步为什么这么做希望能帮到正在做类似系统的朋友少走点弯路。这套系统本质上解决的问题很朴素素材散落在各个同事电脑、微信群里找素材靠问人版权来源也说不清。它核心的能力就是把素材统一收进来打上分类和标签支持在线预览和权限控制最后通过接口或前端页面快速检索下载。技术栈就是标题里那套经典的组合SpringBoot做后端服务、MySQL存元数据、MyBatis处理数据库访问、Vue做管理界面。对大部分中小团队来说这套方案够成熟、招聘成本低、出问题能查到大量踩坑资料属于最稳妥的选择没有之一。下面按我实际开发的顺序把从零到一的整个过程展开来说。考虑到篇幅关系我不会贴完整源码但关键的数据表结构、核心接口设计思路和坑点全部保留。1. 这个系统到底要做什么——需求拆分和边界划定拿到需求先别急着写代码。做管理系统最容易犯的错就是一开始就想做大而全结果每个模块都很浅。我这版需求经过反复压缩之后最终只保留了五个核心模块。1.1 素材管理系统的核心模块划分第一个是素材入库包括本地上传和批量导入。第二个是素材检索要支持按名称模糊搜索、按类型筛选、按分类和标签组合过滤。第三个是素材预览图片、视频、音频需要能在浏览器里直接看不能要求用户下载了才能确认内容。第四个是素材分发其实就是下载和对外分享链接的管理。第五个是基础权限管理不同角色看到不同敏感度素材操作记录有日志可查。这几个模块听起来简单但每个都有不少隐藏工作。比如素材入库不只是把文件丢到服务器还要提取文件的尺寸、时长、大小、格式这些元数据存到数据库里。检索也不只是SQL查一下素材数量上来之后分页、索引、多条件查询的性能问题会非常突出。预览就更麻烦视频需要做格式转换和流化播放图片要做缩略图否则动辄几百兆的视频文件直接拖进浏览器体验会很糟糕。1.2 为什么SpringBootVue这套组合最合适选SpringBoot的原因很简单它把Spring那套繁琐的XML配置几乎全部阉割掉了约定优于配置项目结构只要按照套路走基本不会出大错。配合Maven管理依赖启动一个Web服务只需要很少的样板代码。尤其适合这种以CRUD为核心、掺杂文件IO操作的管理系统。Vue这边的价值在于组件化开发和响应式数据流管理后台这种页面密度高、交互复杂的前端场景用Vue全家桶写起来效率确实很高。我这一版用的是Vue3 Vite Element PlusVite比Webpack启动快太多开发期几乎秒开。存MySQL也是成熟方案这种素材管理场景的数据量根本到不了需要上分布式数据库的程度单机MySQL加合理索引足够扛住几十万条记录。配合MyBatisSQL可以精细控制尤其适合需要写复杂动态查询的业务。提示选型不是越新越好。MyBatis-Plus虽然省事但如果你对原生MyBatis的SQL映射逻辑都不熟出了问题会很难排查。我建议先掌握原生写法再去用增强工具。2. 数据库设计——素材表的每一列都值得认真琢磨这一章是整个系统最重要的部分。素材管理系统的数据库设计直接决定了后面所有功能的实现难度。只要你把表结构设计得足够好后面写Mapper和前端页面都会很顺。2.1 五张核心表的字段设计与关联关系我设计的第一张表是素材主表material核心字段包括id主键、material_name原始文件名、stored_name物理存储名、file_type素材类型image/video/audio/document、format文件格式后缀、size文件大小、duration时长仅音视频、width和height分辨率、url访问地址、thumbnail_url缩略图地址、status状态位、uploader_id上传人ID、created_at上传时间。这个表要覆盖所有素材的公共属性不同类型素材的特有信息通过公共维度尽量下沉不单独拆表。第二张是分类表category字段就是id、parent_id、name、sort_order。支持树形结构是为了以后做多级目录。有些系统想省事不搞树形只做一层分类后面要调整就会很痛苦我建议一步到位。第三张是标签表tag表很简单id和name。标签和素材之间是多对多关系所以必须有第四张关联表material_tag_ref里面存material_id和tag_id再加一个联合唯一索引。第五张是用户权限相关的user表这版不做复杂的RBAC用一个role字段区分管理员、编辑、访客三种角色就够了。如果你要做更细的权限再扩展出角色表和用户角色关联表也不迟。核心建表SQL我贴其中一个素材主表的CREATE TABLE material ( id bigint NOT NULL AUTO_INCREMENT, material_name varchar(255) NOT NULL COMMENT 原始文件名, stored_name varchar(255) NOT NULL COMMENT 物理存储名, file_type varchar(16) NOT NULL COMMENT image/video/audio/document, format varchar(16) DEFAULT NULL COMMENT jpg/mp4/mp3/pdf..., size bigint DEFAULT NULL COMMENT 字节数, duration int DEFAULT NULL COMMENT 视频音频时长(秒), width int DEFAULT NULL, height int DEFAULT NULL, url varchar(512) DEFAULT NULL, thumbnail_url varchar(512) DEFAULT NULL, status tinyint NOT NULL DEFAULT 1 COMMENT 1正常 0禁用, uploader_id bigint DEFAULT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_type_status (file_type, status), KEY idx_created_at (created_at) ) ENGINE InnoDB DEFAULT CHARSET utf8mb4;这里有几个关键点material_name和stored_name必须分开。原始文件名是给用户看的物理存储名一定用UUID或哈希生成避免重名覆盖和中文文件名的编码问题。file_type这种枚举字段用字符串存不要用数字可读性差。字段长度给16个字符足够。状态位status一定要有做逻辑删除和上下架都要用到比物理删除安全得多。2.2 索引策略和素材检索的性能思考素材表最频繁的操作是列表查询通常的过滤条件是文件类型、分类、创建时间以及模糊搜索名称。对应到索引file_type status联合索引很关键因为查询永远是在正常状态的基础上按类型过滤。created_at单独索引用于按时间排序。模糊搜索LIKE %keyword%在数据量小时没问题但如果到了几十万条全表扫描会明显变慢。这个问题我当时没急着上Elasticsearch而是先用一个折中方案名称字段加前缀索引搜索时优先匹配前缀如果前缀结果太少再回退到全模糊匹配。实测在30万条数据下响应在200毫秒左右对内部系统完全够用。提醒MySQL里LIKE %关键词%会放弃索引走全表扫描这是必然的。如果你真的要做海量素材的全文搜索最靠谱的路线还是引入Elasticsearch。但对90%的企业内部系统在数据量到百万之前根本不需要。2.3 MyBatis的TypeHandler处理特殊字段素材表的url和thumbnail_url在某些业务里可能存多个地址比如同一视频有多套清晰度。这种情况如果只在字段里存一个字符串后续扩展就麻烦了。我后来是把多个清晰度地址用JSON格式存到url字段然后写一个自定义的TypeHandler来处理字符串到Java对象的转换。自定义TypeHandler的做法很简单继承BaseTypeHandler重写setNonNullParameter和getNullableResult方法。这里要注意MyBatis的TypeHandler是全局注册还是注解指定如果你只想作用于某个字段建议在Mapper XML里通过typeHandler属性指定不要全局注册否则容易影响其他字段的处理。MappedTypes(List.class) public class StringListTypeHandler extends BaseTypeHandlerListString { Override public void setNonNullParameter(PreparedStatement ps, int i, ListString parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, JSON.toJSONString(parameter)); } Override public ListString getNullableResult(ResultSet rs, String columnName) throws SQLException { String value rs.getString(columnName); return JSON.parseArray(value, String.class); } }这种做法的好处是数据库层面可读性依然很好应用层拿到的是一个字符串列表处理起来非常方便。3. 后端接口设计与文件上传——最容易出问题的部分后端接口的核心不只是CRUD文件上传才是这套系统里最容易踩坑的部分。多媒体的文件通常较大视频动不动就几百MB普通的表单上传根本扛不住。3.1 文件上传方案选型与分片上传的取舍我先说结论如果你们公司的素材通常不超过200MB直接使用单次上传加合理配置就够不要一上来就搞分片上传。分片上传带来的复杂度包括前端切片、后端合并、断点记录这些代码至少要占用两三天开发周期而且对于小文件是完全多余的。我当时定的策略是文件小于100MB走单次上传超过100MB走分片。前端用Vue组件做切片每个分片大小定为5MB后端提供三个接口初始化上传任务获取uploadId、上传单个分片、合并分片。后端SpringBoot里接收上传文件时要注意配置spring.servlet.multipart.max-file-size和max-request-size默认只有1MB和10MB不调大一定会报文件大小超出限制。这个是我线上踩过的第一个坑。spring: servlet: multipart: max-file-size: 1024MB max-request-size: 1024MB分片合并时要注意顺序问题不要依靠文件的自然顺序。每个分片上传时必须带上分片序号chunkIndex合并时按序号拼接文件流。合并完成之后还要校验最终文件的MD5和上传前计算的文件MD5是否一致。3.2 文件存储路径规范和静态资源映射文件物理存储路径我强烈建议做成按日期分目录的结构类似/data/material/2024/06/13/uuid.jpg。这样做的好处有三个目录文件数量可控避免一个文件夹放几万个小文件导致文件系统变慢按日期天然有归档效果排查问题时也容易定位。后端存储路径不能硬编码需要写到application.yml中配置。同时要做一个目录不存在时自动创建的初始化逻辑否则部署到新环境时第一件事就是报找不到目录。SpringBoot里映射本地文件目录到URL我用的办法是WebMvcConfigurer的addResourceHandlers方法。坑点在于这个映射的URL是虚拟路径如果前端访问不了先检查是不是路径拼接的问题。我这边当时就是多写了一个file:前缀导致了一整晚都在排错。Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { String uploadPath System.getProperty(user.dir) /upload/; registry.addResourceHandler(/files/**) .addResourceLocations(file: uploadPath); } }3.3 素材元数据自动提取的实现用户上传一个视频后台如果只把文件保存下来那这个系统就只是一个网盘不配叫素材管理系统。要真正做到管理必须在文件上传完成后自动提取素材的元数据。图片和视频的元数据提取我用的是Metadata Extractor这个库。音频和视频的时长、码率、分辨率这些信息用FFmpeg命令行工具处理最方便。注意FFmpeg在服务器上需要单独安装不在Java依赖范围里。我的做法是上传成功后把文件路径扔给一个异步线程池去处理用EnableAsync注解开启Spring的异步能力服务端先返回上传成功元数据提取完成后更新数据库记录并生成缩略图。这样用户上传一个视频不用等几秒钟去生成缩略图体验会好很多。FFmpeg提取时长的命令大概是这样ffprobe -v quiet -print_format json -show_format -show_streams input.mp4然后Java端解析输出JSON取出duration和width、height。这里有个坑要注意ffprobe对某些异常视频文件会直接卡住不返回需要在调用命令时加上超时控制我用的Process.waitFor(long time, TimeUnit unit)设置10秒超时超时就杀掉进程降级只存文件名不存元数据。4. Vue前端管理页面的实现——体验从流畅到卡顿的临界点后台前端如果只是把数据列表渲染出来其实没什么难度。真正的体验差异在于素材搜索是否够快、大图预览是否够流畅、视频播放是否不卡壳。这一章把这几个核心页面的实现思路和踩坑点过一遍。4.1 素材列表页的加载策略与搜索防抖素材列表展示的字段比较多包括缩略图、名称、类型、大小、上传人和时间。图片缩略图如果直接加载原图列表一多浏览器就崩了。我自己做的方案是上传时单独生成一套小尺寸缩略图列表页加载的是缩略图URL点击图片再加载原图。还有一个细节是图片懒加载Vue中给img标签设置v-lazy或者用loadinglazy属性让屏幕外的图片不加载。实测在素材达到千张以后懒加载的收益非常明显。搜索框必须做防抖。用户输入关键词每次键盘抬起都发一次请求后端扛得住但前端体验很不好而且连续快速输入会产生大量过期请求。我用的是简单的setTimeout加清理逻辑再配合一个请求序号只保留最后一次请求的结果。不然搜索结果的顺序会被网络延迟打乱出现显示旧结果的诡异问题。搜索还有一个需要注意的点回车触发和输入停顿触发是两种典型交互有些人习惯输完按回车有些人等着自动出结果。我两个都做了用户按回车直接搜索不按的话停顿600毫秒自动搜索。4.2 视频播放与M3U8方案的引入视频预览是这个系统里技术含量最高的部分。浏览器直接播放MP4格式是原生支持的但问题在于大多数用户上传的视频不是标准H.264编码而是各种奇怪格式浏览器根本不认。我这边收到的素材里有MOV、AVI、FLV甚至还有带旋转信息的手机视频。最初我想到的解决思路很直接视频上传后统一转码成MP4用传统的Video标签播放。但是实测遇到两个车型问题一是超大视频文件转码时间长二是浏览器加载MP4必须从头加载一部分才能播放拖动进度条时体验很差。后面我把方案升级为HLS流媒体方案。上传后由FFmpeg将视频转码为HLS分片文件生成M3U8索引文件和多个TS切片文件前端用video.js加videojs-contrib-hls插件播放。M3U8确实坑不少我当时还专门在Vue页面里调了跨域配置因为部分老的插件版本播放M3U8时会引来跨域问题。Vue里使用M3U8播放器的核心配置import videojs from video.js; import video.js/dist/video-js.css; const player videojs(this.$refs.videoPlayer, { autoplay: false, controls: true, sources: [{ src: this.videoUrl, type: application/x-mpegURL }] });M3U8切片还有个好处是支持不同清晰度切换这个我在后续版本里实现了基础版先不展开。4.3 Vue路由与打包进SpringBoot时的history模式问题前端开发有动态路由需求不同权限用户看到的菜单不同。这种需求我一开始就决定用动态路由实现登录后根据用户角色请求后端菜单接口再动态挂载到Vue Router上。要注意的是动态添加路由需要调用router.addRoute方法而且刷新页面后会失效必须配合vuex-persistedstate或者pinia-plugin-persistedstate把路由信息持久化或者在每次刷新都重新请求一次。但真正让我头疼的是另一个问题Vue打包之后放进SpringBoot的src/main/resources/static目录做单服务部署时路由模式必须从history改成hash。history模式在刷新页面时会向SpringBoot后端发请求后端没有对应的路由会直接404。改hash模式就完全没有这个问题而且部署架构简单很多。const router createRouter({ history: createWebHashHistory(), routes });提示如果你一定要用history模式就必须在SpringBoot里做一个统一转发Controller把非/api开头的请求全部转发到index.html。但这样做有个隐患某些静态资源请求也可能被误转发。作为一个成熟项目的做法我建议部署时用Nginx做前端静态资源服务SpringBoot只负责提供API。5. 权限控制和素材访问安全——内部系统也要有底线素材管理系统的素材往往是公司的核心资产权限控制马虎不得。这版我做了JWT认证、接口角色鉴权和文件访问控制三层每一层都不能少。5.1 JWT登录认证与接口鉴权实现登录流程就是经典的JWT流程用户提交用户名密码后端校验后签发一个JWT Token返回给前端。前端存储在localStorage每次请求在Header带上Authorization。后端用Spring Security还是自己写拦截器我一开始确实纠结了一下后来选择自己写拦截器。Spring Security功能强大但配置复杂对这种只有两三个角色的系统自己用HandlerInterceptor加注解实现反而更快更直观。具体做法是写一个AuthInterceptor在preHandle方法里解析Token并获取用户角色然后校验当前请求的接口是否在当前角色允许范围内。接口权限用自定义注解RequirePermission标记管理端接口只允许管理员调用。Component public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); // 解析token校验过期时间获取角色 // 如果注解要求admin角色但当前用户不是返回403 return true; } }JWT的密钥要放到配置文件中不要硬编码到代码里。换密钥时要注意已签发的Token会全部失效这既是好事也是坏事根据项目阶段灵活处理。5.2 文件访问的防盗链与权限控制策略文件URL如果直接暴露出来任何知道链接的人都能访问下载权限控制就形同虚设。我当时做了两步处理不在前端直接暴露真实文件路径而是通过一个/file/access/{materialId}接口去获取临时访问地址同时生成一个带过期时间的临时签名参数URL有效期只有5分钟。这里有个细节要注意/file/access/{materialId}接口本身必须要求登录状态否则用户绕过前端直接访问这个接口依然能达到未授权访问文件的目的。所以文件访问接口和普通接口一样需要鉴权不能因为是文件流就放松防护。素材的物理文件名用UUID的原因在这里也体现出来了就算别人猜到了存储路径也无法枚举出其他文件。UTF-8编码的中文文件名、特殊字符、空格全部被UUID屏蔽掉避免了非常多的麻烦。5.3 操作日志与素材上下架审核素材管理不只是存和取还包括管理动作的合规性。我加了一个operate_log表记录谁在什么时间上传、修改、删除、下载了哪个素材。这个日志表一开始设计得很简单就是user_id、action、target_id、created_at四个字段。实际运行中才发现这样做虽然是够用但查某个素材被谁改过这种逆向查询时必须拷target_id查效率不行。后面加了target_type和目标名称冗余字段查询效率才上来。上下架审核模块的设计原则是状态机尽量简单素材初始状态为待审核管理员审核通过后变为已发布发布后的素材可以被编辑和下载否则只能作者自己看到。中间如果需要驳回就加一个已驳回状态并携带驳回原因。状态枚举不要用自动递增的数字直接用字符串数据库可读性会好很多。6. 打包、部署与联调——三周项目里至少有五天耗在这前面所有功能写完项目依然不能算完成因为部署运行大概率会出问题。这一章把我在联调和部署阶段亲身踩过的坑系统总结一下。6.1 前后端联调时的跨域问题本地开发时前端跑在5173端口后端跑在8080端口跨域是必然发生的。最初我用Vite的proxy代理解决配置/api开头的请求全部代理到后端这个方案在开发环境最干净利落。// vite.config.js export default defineConfig({ server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } });但部署时如果前端静态资源和后端API在同一个域下面就不存在跨域。这种情况属于最简单的部署形态。如果你打算把前端放到CDN上后端单独一个域名那就必须在SpringBoot里开启CORS。CORS配置的坑主要在于预检请求OPTIONS。浏览器每次跨域提交JSON数据之前都会先发一个OPTIONS请求确认服务器是否允许跨域。如果这个请求没被正确处理就会看到前端报跨域错误但后端日志里明明收到过请求。SpringBoot的addCorsMappings应该写allowCredentials(true)配合allowedOriginPatterns(*)才可以使用allowedOrigins(*)会因W3C规范在携带用户凭证时无法生效。6.2 Maven打包与前端构建的版本兼容问题构建后端用的是Maven前端用的是npm单独执行都没问题。问题出在有些人会想能不能打包成一个独立的Jar文件双击就能跑。这个需求本身可以做但有坑。我的做法是先用npm run build构建前端到dist目录然后配置Maven的maven-resources-plugin把dist目录内容复制到classpath:/static下再整体打包成Jar。这个方案如果能顺利执行最终的产物就是一个包含前后端的Jar包运维成本极低。但我在实际执行中遇到的是版本兼容问题我本地的Maven版本是3.9.xSpringBoot版本如果是3.x需要依赖spring-boot-maven-plugin的最低版本号否则打包时报Unable to find main class。这个问题排查了大半天最后就是把Maven插件版本显式指定为跟SpringBoot一致版本才解决。提醒前端package.json里vite的版本和Node.js版本要配套。我用Node 18跑Vite 4没问题但如果换成更高的Vite版本最低Node版本要求会提升老服务器上直接npm install可能就报错了。部署前记住node -v先确认一下。6.3 MySQL安装配置与SSL连接报错MySQL我在这套系统里用的是8.0版本但网上不少教程还在教5.7的配置两者有些差异。最典型的就是SSL连接问题MySQL 8.0默认使用caching_sha2_password认证方式同时默认开启SSL连接。如果你在JDBC连接串里不显式指定一些参数经常会在连接时报SSL connection error: protocol version mismatch或者Public Key Retrieval is not allowed。我的JDBC连接配置如下这些参数请在application.yml里原样写上spring: datasource: url: jdbc:mysql://localhost:3306/material_db?useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/Shanghai username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver关于serverTimezone如果你不做配置线上服务器时区和本地不一致时所有时间字段都会差8个小时。Asia/Shanghai是免去时区换算烦恼的最省事选择。MySQL启动方式在Windows和Linux上也不同Linux下一般用systemctl start mysqldWindows下是注册成服务。如果是CentOS用rpm方式装的MySQL默认数据目录在/var/lib/mysql权限不对时MySQL会直接启动失败这个时候记得检查一下日志文件的位置通常路径是/var/log/mysqld.log。6.4 MyBatis缓存与数据一致性的坑MyBatis的缓存是个老生常谈的问题。一级缓存默认开启范围是SqlSession级别二级缓存默认关闭需要手动配置才开启。我在这套系统里遇到了一个因二级缓存导致的数据脏读问题。当时有一个素材分类编辑接口管理员把分类名称从A改成B下次用户刷新页面依然显示旧名字。排查到最后发现是Mapper里开启二级缓存导致的结果。两个Mapper接口在同一个命名空间下一级SQL查询结果缓存到二级缓存后更新接口没有正确刷新对应缓存。解决方案很简单删除XML里的cache/配置或者对更新操作的Mapper方法加上flushCachetrue。对于管理系统这种写操作频率不高的应用我建议干脆全局关闭二级缓存。性能瓶颈靠数据库索引和前端缓存解决不要靠MyBatis缓存否则很容易踩坑。6.5 SpringBoot整合其他组件的扩展考量如果你后续想给素材管理加更多智能化能力SpringBoot生态有很多现成的整合方向。比如素材入库后自动打标签可以用HanLP分词库对素材标题和描述做关键词提取然后自动写入标签表。这个整合其实不难在SpringBoot里把HanLP中分词器作为Bean注入然后上传接口处理链路中加一步关键词抽取。地理位置类的素材比如带有拍摄位置信息的图片前端做地图展示时可以考虑接入Mapbox后端把地理坐标存成经纬度字段Mapbox负责把素材在交互地图上渲染出来。这个功能我因为时间原因没做但在方案评审时已经在文档里预留了longitude和latitude字段。至于SpringBoot整合Flink这样偏流式处理的技术栈在本项目的早期阶段不建议引入。素材管理绝大多数是异步任务而不是高吞吐实时流处理。除非素材量每天几十万条并且有实时标签化处理需求否则引入Flink属于明显的过度设计。存储层面如果后续要接对象存储SpringBoot的Resource抽象也能帮你平滑过渡。你只需要写一个StorageService接口现有实现是本地磁盘以后换OSS或者S3只需要新增一个实现类就行。这也是我在这个项目里坚持存储路径永不硬编码、通过服务层调用的原因。7. 最后说几句心里话系统上线后运行了一段时间我最深的感受是这类管理系统的难点从来不是某个单一技术而是所有环节的衔接。文件上传、元数据提取、流媒体播放、权限控制、部署运维每一个点都有自己最适用的一套方案你要在做一个系统前就纵观全局规划好。如果你现在正要动手做类似的素材管理系统我的建议是先把存储方案和文件处理流程定下来这个决定了系统几十年的运维成本。其次是权限模型起步不要做太复杂不然一切都是空转。数据库表结构和前端路由这两件事也值得花一天时间推演好后面返工的代价远超你的想象。最后一个小技巧前端对接接口时统一做成一个request.js封装把Token注入、错误码提示、401自动跳转登录都在这一层处理。看起来是小事但在这种管理系统中接口少说也有三十个如果每个页面都自己写一遍请求逻辑维护起来会非常痛苦。这个封装建议在项目第一天就做好不要等写了一半再补。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进