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

基于SpringBoot+Vue3的多媒体素材管理系统设计与实现

基于SpringBoot+Vue3的多媒体素材管理系统设计与实现 ★ FEATURED ARTICLE
最早接到多媒体素材管理系统这类需求时我的第一反应是这又是一套“上传下载”的CRUD。真正动手之后才发现素材管理最难的从来不是接口怎么写而是当你手里躺着几千个图片、音频、视频混合素材时怎么让它们存得整齐、查得快、预览得顺。这套系统的技术栈是SpringBoot2 Vue3 MyBatis-Plus MySQL8.0后端负责接口和存储前端负责管理界面项目源码附带完整文档很适合拿来学习前后端分离开发理解一个管理后台从零到一的搭建过程也能直接作为毕业设计或中小型团队内部素材库的底座。我会把从需求拆解、技术栈选型、数据库设计、后端接口实现、Vue3前端交互、踩坑记录到部署流程的整条链路讲透。读完你不仅能跑起来还能知道每一层为什么这么设计。如果你最近正准备做一个类似的Java Web管理系统这篇应该能帮你省下不少试错时间。1. 素材管理到底管理什么需求拆解与功能边界很多人以为素材管理系统就是把文件传上去、列出来、再下载回来其实这只是表面。素材管理的难点在“素材”这两个字上图片、音频、视频、文档它们的文件格式、体积、预览方式都不一样属性字段也不完全通用。一套设计合理的系统必须先把这些差异抽象成统一模型再针对不同类型做差异化处理。1.1 多媒体素材的三大核心痛点第一是存储问题。一个小团队一个月可能上传几百个文件单文件能到几十甚至上百MB。如果直接存数据库里连接会变得很重备份和迁移也是灾难。常见做法是文件落磁盘或对象存储数据库只保存素材的元数据和访问路径。第二是检索问题。素材多了以后用户只记得大概文件名或某个分类。没有分类树、标签、类型筛选和关键字搜索列表翻页能翻到崩溃。第三是预览问题。上传的图片要能直接看图视频要能播PDF要能在线看。这要求后端不仅要能保存文件还要能把文件以静态资源方式暴露给前端并处理好路径安全。1.2 功能模块与业务闭环这套系统的核心模块可以分成四块。用户与登录模块提供基本的登录鉴权控制谁能上传和管理素材。素材管理模块素材上传、编辑、删除、批量操作、按名称和类型检索。分类管理模块维护素材分类支持父子层级方便前端做分类树。统计与展示模块后台首页展示素材总量、分类占比、最近上传等数据让管理员对库状态一目了然。业务闭环也很清晰登录后进入素材列表页左侧是分类树右侧是列表点击上传选择文件后写入服务器磁盘同时向数据库插入一条元数据记录上传完成后列表刷新新素材按照分类和类型出现在对应位置点击预览时前端直接请求后端静态资源映射的URL浏览器展示图片或播放视频。1.3 这个项目适合谁参考如果你是刚开始学SpringBoot和Vue的后端开发这套源码的代码结构比较清楚Controller层、Service层、Mapper层分得规范适合对照学习接口怎么写。如果你在准备毕业设计那更合适。多媒体素材管理系统是一个经典“有实际业务场景、有完整技术栈、有页面交互”的题目答辩时可以从数据表设计、上传流程、权限控制几个角度展开比纯CRUD系统好讲太多。如果是在公司内部需要一个简单的资源管理后台这套系统也能直接改一改部署起来。不过要注意如果素材量到了几十万规模检索和存储都需要重新设计那就要考虑ElasticSearch和对象存储了。2. 这次选型我没有盲目上最新版技术栈取舍技术选型是这类项目一开始就会遇到的问题。很多人一上来就想追最新版本但我在这套系统里刻意选择了比较稳妥的组合SpringBoot2、Vue3、MyBatis-Plus、MySQL8.0。不是最新但足够能打。2.1 后端为什么继续用SpringBoot2SpringBoot3确实发布了很长时间但SpringBoot2依然是目前国内企业项目和教程里存量最大的版本。选择SpringBoot2有几个实际原因。第一是生态兼容。很多老牌第三方库和代码生成器对SpringBoot3的Jakarta命名空间适配还不够完善尤其是一些生成代码插件和内部组件包。用SpringBoot2可以少踩很多莫名其妙的包冲突。第二是资料丰富。SpringBoot2的解决方案在社区沉淀了四五年遇到问题几乎都能搜到现成答案。对正在学Java Web的开发者这能显著降低卡壳时间。第三是资源占用。SpringBoot2配合内嵌Tomcat在2核4G的云服务器上跑得很轻松。如果只是做素材管理系统完全不需要微服务和云原生那套重架构。2.2 MyBatis-Plus省下的不只是SQL我主观上更喜欢JPA做中小型项目的快速开发但在这种带复杂查询的管理后台里MyBatis-Plus更顺手。最明显的好处是单表CRUD不用写SQLMapper接口继承BaseMapper后save、updateById、selectPage这些方法直接用。对于素材分类树这种需要递归查的场景MyBatis-Plus也保留了自定义SQL的能力不会像JPA那样在复杂查询上绕来绕去。再加上它的分页插件配置非常简单几分钟就能把Page对象和前端Table对接起来。2.3 前端为什么选Vue3而不是Vue2如果用Vue2也可以完成这个系统但Vue3的组合式APIComposition API在管理后台场景下优势很明显按功能组织逻辑而不是按选项分散逻辑。比如素材列表页的上传逻辑、加载逻辑、分页逻辑放到setup里可以各自抽成函数块代码读起来非常直观。配合Vite以后开发环境的启动速度比Webpack时代快了一个量级。素材管理后台页面并不复杂不需要重型状态管理用Vue3的reactive和ref就够了。2.4 MySQL8.0与字符集、排序规则的选择MySQL8.0的主要优势是稳定性和性能。在写数据库初始化脚本时要注意两点。数据库字符集用utf8mb4而不是utf8。因为utf8在MySQL里最多存3字节emoji和特殊字符会报错素材名称里完全可能出现emoji。排序规则用utf8mb4_general_ci还是utf8mb4_unicode_ci看需求。一般来说general_ci速度略快unicode_ci对多语言排序更准确。素材系统里中文内容多我用的是utf8mb4_general_ci名字检索时的精度完全够用。通过表格总结这套选型考虑会更直观技术组件版本/方案选择原因不选择项后端框架SpringBoot 2.7.x生态成熟、资料丰富、部署轻量SpringBoot3生态迁移成本高ORM框架MyBatis-Plus 3.5.x单表CRUD零SQL、分页插件好用JPA复杂查询绕、原生MyBatis开发慢前端框架Vue3 Vite组合式API逻辑清晰、启动快Vue2维护期、Angular过重数据库MySQL 8.0稳定、功能全、运维普遍PostgreSQL虽然也很好但团队不熟UI组件库Element Plus与Vue3匹配、后台常用组件齐全Ant Design Vue也很优秀看已熟悉程度3. 数据库设计先把素材信息存明白数据库设计决定了后面几乎所有功能的开发难度。一个素材管理系统的表结构如果没设计好后面加一个“标签”或“多级分类”都可能是灾难。这张系统的核心表其实不多务必先把它们的关系理清。3.1 核心表结构设计素材表是绝对的核心我建议字段包括id、素材名称、类型、分类id、文件原始名称、存储路径、URL路径、文件大小、扩展名、上传人、上传时间、更新时间、删除标记。下面是简化版的建表语句。CREATE TABLE material ( id bigint NOT NULL AUTO_INCREMENT, name varchar(128) NOT NULL COMMENT 素材名称, type varchar(16) NOT NULL COMMENT 类型image/video/audio/doc/other, category_id bigint DEFAULT NULL COMMENT 分类id, origin_name varchar(255) DEFAULT NULL COMMENT 文件原始名称, stored_path varchar(255) NOT NULL COMMENT 文件存储相对路径, url varchar(255) NOT NULL COMMENT 访问URL前缀, file_size bigint DEFAULT 0 COMMENT 文件大小字节, extension varchar(16) DEFAULT NULL COMMENT 扩展名, uploader_id bigint DEFAULT NULL COMMENT 上传用户id, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, deleted tinyint NOT NULL DEFAULT 0 COMMENT 逻辑删除, PRIMARY KEY (id), KEY idx_category (category_id), KEY idx_type (type), KEY idx_uploader (uploader_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT多媒体素材表;这里的设计重点是路径只存相对路径比如/upload/2024/05/12/xxxx.jpgURL也要能单独拼出来。不要直接存绝对路径否则以后服务器迁移、换磁盘路径会很痛苦。分类表相对简单id、父级id、分类名称、排序值、创建时间。如果要在前端做菜单树一次查出全部分类后在Java内存里组装树形结构即可。素材表通过category_id关联到分类大分类下可以一直往下挂子分类递归查询时注意别死循环。用户表也不复杂id、用户名、密码加密串、昵称、角色、状态。密码不要明文存储至少用BCrypt加密。素材管理后台一般是内部系统权限不需要像RBAC那么复杂做一个登录拦截和角色区分就够了。3.2 分类树与标签字段的取舍我见过不止一个项目在分类上过度设计搞了物化路径、多对多映射表结果查询一个列表要join好几张表。多媒体素材管理系统里分类做成“无限极父子结构”就够了不一定需要标签系统。如果确实需要标签可以在素材表中加一个tags字段存逗号分隔的字符串然后在Java层按逗号拆开渲染成Tag标签。不要为了“将来好扩展”一开始就建五张表除非你真的确定标签需要独立维护和统计。3.3 MyBatis-Plus的实体映射与自定义操作实体类建议用MyBatis-Plus的注解方式映射重点注意两个注解。TableLogic逻辑删除字段标注后所有内置删除方法会自动变成update语句查询时自动过滤已删除记录。TableField(fill FieldFill.INSERT)配合MetaObjectHandler实现create_time、update_time自动填充这样代码里就不用手动set当前时间了。自定义查询可以写在Mapper里用XML也可以用Select注解写在接口上。素材系统的检索条件主要是“名称like 类型eq 分类eq 时间范围”在Mapper里用一个resultMap搞定具体代码后面会展开。4. 后端核心接口实现上传、检索、预览、鉴权后端代码是整个系统的大脑所有功能最终都要体现为接口。这里我重点讲四个关键接口的实现思路和细节上传、检索、预览、登录鉴权。4.1 文件上传接口MultipartFile配合存储目录上传接口是素材管理项目最核心的Controller。使用SpringBoot的MultipartFile接收文件然后按年月分目录存储避免单个目录文件过多。PostMapping(/upload) public ResultMaterialVO upload(RequestParam(file) MultipartFile file, RequestParam(required false) Long categoryId, RequestParam(required false) String name) { if (file.isEmpty()) { throw new BizException(上传文件不能为空); } String originName file.getOriginalFilename(); String ext FilenameUtils.getExtension(originName); String yearMonth LocalDate.now().format(DateTimeFormatter.ofPattern(yyyy-MM)); String dirPath uploadPath / yearMonth; File dir new File(dirPath); if (!dir.exists()) { dir.mkdirs(); } String storedName UUID.randomUUID().toString().replace(-, ) . ext; File target new File(dir, storedName); file.transferTo(target.getAbsoluteFile()); Material material new Material(); material.setName(StringUtils.hasText(name) ? name : originName); material.setType(resolveType(ext)); material.setCategoryId(categoryId); material.setOriginName(originName); material.setStoredPath(/ yearMonth / storedName); material.setUrl(downlaodPrefix / yearMonth / storedName); material.setFileSize(file.getSize()); material.setExtension(ext); material.setUploaderId(currentUserId()); materialService.save(material); return Result.ok(materialToVO(material)); }这里面有几个细节值得说清楚。保存文件名用UUID避免用户上传的同名文件互相覆盖也避免中文文件名在浏览器URL下出现编码问题。文件真实路径和URL路径分开。真实路径用于磁盘读写和删除URL用于前端预览。两者通过公共前缀关联。resolveType根据后缀名判断文件类型比如jpg/png/gif/webp归为imagemp4/mov/avi归为videomp3/wav归为audiopdf/doc/txt归为doc其他归为other。上传接口写完一定记得在配置文件里设置SpringBoot的文件大小限制spring: servlet: multipart: max-file-size: 500MB max-request-size: 500MB这个限制不设大视频会直接被拒掉。如果你期望支持更大文件建议加断点续传和后端切片接口不过那是后续优化方向。4.2 多条件分页检索素材列表页一般会传三个参数当前页pageNum、每页条数pageSize、筛选条件keyword。代码里我用MyBatis-Plus的LambdaQueryWrapper实现动态条件拼接。Override public PageResultMaterialVO page(int pageNum, int pageSize, MaterialQuery query) { PageMaterial page new Page(pageNum, pageSize); LambdaQueryWrapperMaterial wrapper new LambdaQueryWrapper(); wrapper.eq(StringUtils.hasText(query.getType()), Material::getType, query.getType()) .eq(query.getCategoryId() ! null, Material::getCategoryId, query.getCategoryId()) .like(StringUtils.hasText(query.getKeyword()), Material::getName, query.getKeyword()) .orderByDesc(Material::getCreateTime); PageMaterial result materialMapper.selectPage(page, wrapper); return PageResult.from(result, MaterialVO::new); }注意一个细节eq、like这些方法只有第一个条件为true时才会把条件拼上。这正好适合搜索页面“传了就用没传就不管”的场景。分类筛选这里有个隐藏问题如果选了一个父分类要不要包含子分类的素材更好的设计是拿到父分类id后查出所有子分类id放进in条件里。这种查询在分类深度不深时性能完全扛得住别为了省事忽略。分页配置需要注册MyBatis-Plus的PaginationInnerInterceptorConfiguration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }这一步不配置分页查询会查出全量数据刚开始你可能发现不了等素材量大了接口就会明显变慢。4.3 静态资源映射与在线预览文件存到本地磁盘后浏览器要能直接访问。SpringBoot默认只映射classpath:/static/所以需要将本地上传目录注册为静态资源。Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { String uploadAbsolutePath System.getProperty(user.dir) /upload/; registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadAbsolutePath); } }上面的/upload/**是URL访问路径file:绝对路径是磁盘目录。图片和视频这样映射后前端把素材的url字段直接塞进img或video标签就能预览。文件删除操作记得同步删除磁盘文件否则会残留无主文件时间长了占满磁盘。4.4 登录鉴权的轻量方案中小型素材管理系统不必要上来就集成Spring Security和OAuth2。用拦截器加JWT或者更简单地用Session就能实现权限控制。这里我采用的是登录接口签发Token后续请求在Header里带上Token拦截器校验的轻量方案。public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token null || !JwtUtil.verify(token)) { response.setStatus(401); return false; } Long userId JwtUtil.parseUserId(token); UserContext.set(userId); return true; } Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { UserContext.clear(); } }拦截器注册时记得把登录接口、上传预览目录排除掉。预览目录如果也拦前端加载图片会返回401。这个位置特别容易踩坑我见过不少项目图片裂了排查半天发现是静态资源也被鉴权拦截了。5. Vue3前端实现组合式API下的后台页面前端在这个项目里不是附属品。素材管理后台用得舒不舒服完全取决于页面的交互细节。Vue3的组合式API让我能按业务逻辑组织代码素材上传、分类切换、列表分页、搜索重置这些逻辑各自成为独立函数比Vue2的Options API清晰太多。5.1 项目骨架搭建前端技术栈是Vite Vue3 Element Plus Axios。建议直接用Vite的vue模板初始化然后装依赖。npm create vitelatest material-admin -- --template vue cd material-admin npm install npm install element-plus axios项目目录上我建议按模块组织而不是按文件类型组织。只分views、components、api、utils四个大目录就够了。素材相关的组件放到components/material下素材相关的API请求统一放到api/material.js下面。这样页面组件的规模大了以后改动一个模块时所有相关文件都挨在一起。5.2 素材列表页与上传弹窗列表页用Element Plus的el-table展示素材每条数据带上缩略图。图片类型直接用el-image的preview功能预览大图视频类型展示一个播放按钮点击后抽屉播放。缩略图不需要额外生成直接复用素材url拼接缩略参数即可除非素材量极大一般不用接入图片处理服务。上传弹窗用el-dialog包el-upload设置好action、headers和数据回填。el-upload v-model:file-listfileList action/api/material/upload :headersuploadHeaders :data{ categoryId: currentCategoryId, name: uploadName } :limit5 :on-successhandleUploadSuccess list-typepicture-card 这里要注意headers里必须带上Authorization Token否则上传接口会被拦截器拦截。我最初漏了这一步上传时一直返回401排查了很久才意识到是el-upload默认不会自动带自定义请求头需要在uploadHeaders里从store手动取。5.3 分类树联动与分页关系左侧分类树用el-tree渲染点击节点时触发加载事件。素材列表和分类树的关系成一个回路点击分类树节点把categoryId写入查询参数重置pageNum为1重新请求列表上传素材时默认选择当前分类列表右上角显示当前筛选路径。代码逻辑可以整理成下面这段关键函数const loadMaterials async () { loading.value true; try { const resp await api.fetchMaterials({ pageNum: pageNum.value, pageSize: pageSize.value, type: filterType.value || , categoryId: selectedCategoryId.value || null, keyword: keyword.value.trim() || }); materialList.value resp.data.records; total.value resp.data.total; } finally { loading.value false; } };这里面有个交互细节分页组件切换页码时必须保持当前筛选条件。很多新人会单独维护一个currentPage变量导致翻页后条件被清空。我用reactive对象统一状态每次切换都重新调用loadMaterials筛选条件天然保持一致。5.4 API封装与响应拦截前端请求统一走一个axios实例然后在拦截器里做两件事一是自动附加Token二是统一处理业务错误。业务错误最好由后端定义统一返回结构前端拦截器里拿到code非200时直接Element Plus的ElMessage弹错误。service.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers.Authorization token; } return config; }); service.interceptors.response.use( response { const data response.data; if (data.code ! 200) { ElMessage.error(data.message || 请求失败); return Promise.reject(new Error(data.message)); } return data; }, error { if (error.response?.status 401) { router.push(/login); } else { ElMessage.error(error.message || 网络错误); } return Promise.reject(error); } );这里响应拦截器建议把data直接return出去这样业务页面调用时不用再resp.data.data两层展开。我在项目里就是先在拦截器统一展开页面里拿到的直接就是后端业务对象或不过多套一层的结构代码读起来更省心。6. 运行过程中真实踩过的坑与排查思路开发这套系统的过程中我遇到的坑不少。这里挑四个最有代表性的讲清现象、定位过程和最终的解决方式。这些坑如果你在实现类似项目时能提前避开至少能省一两天排查时间。6.1 MySQL8.0的时区与ONLY_FULL_GROUP_BY问题数据库连上后第一个坑是时区。MySQL8.0的JDBC连接串少配置一个serverTimezone查询时间字段就可能直接报错或者时间比北京时间慢8小时。解决方式是在jdbc连接串上显式设置jdbc:mysql://localhost:3306/material_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue第二个坑更隐蔽。如果你写了一个自定义统计SQL比如“按分类统计素材数量”数据一多就可能触发ONLY_FULL_GROUP_BY模式报错。因为MySQL8.0默认开启这个SQL模式会把select列中未被group by且不是聚合函数的字段直接报错。一种是改SQL让每个select列都加聚合函数另一种是修改sql_mode去掉ONLY_FULL_GROUP_BY。我建议优先改SQL因为改全局sql_mode容易影响其他项目。6.2 MyBatis-Plus自动填充没生效我给实体加了TableField(fill FieldFill.INSERT)也写了MetaObjectHandler但插入时create_time仍然是null。排查时发现是MetaObjectHandler上加的Component没被扫描到或者包路径不在SpringBoot启动类的扫描范围内。SpringBoot的默认包扫描从启动类所在包开始如果你把handler写在com.xxx.common.handler而启动类在com.xxx.system扫描不到Spring容器里根本没有这个bean。解决方式是把这个配置类放到启动类同包或子包下或者用ComponentScan显式指定扫描路径。这类问题有个通病报错信息不会直接告诉你“我没扫描到”需要检查Bean的加载情况。6.3 Vue3上传成功后列表不刷新在本地联调时上传接口返回成功但列表始终不出现新数据。问题出在调用顺序上。我的el-upload的on-success回调里类似这样的写法const handleUploadSuccess (response) { if (response.code 200) { loadMaterials(); } };看起来没问题但el-upload在on-success时已经把文件信息加入了fileList而fileList又被v-model绑定导致元素重新渲染时上传组件内部状态和列表请求并发。解决方案是把loadMaterials放进$nextTick或者直接改用手动上传方式在确认上传后自己调接口、自己管理fileList。我后来改成了手动上传可控性更高。还有一个很容易忽略的坑上传接口如果返回了业务对象但前端没有把新素材的id和路径加入列表数据结构只在原数组尾部push一个简易对象那么图片缩略图和下载链接都会是undefined。我建议统一在loadMaterials后重新拉一次数据而不是手动修改数组。6.4 前端跨域与Nginx映射位置本地开发时前端用Vite代理避免跨域在vite.config.js里配置server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true, rewrite: path path.replace(/^\/api/, ) } } }这里有个坑是后端接口路径本身带/api前缀那么代理就不应该rewrite去掉前缀。如果你前后端路径不一致会出现“前端能请求到但404”或者“404却在后端日志里看不到记录”的情况。部署到Nginx时需要把/upload的静态目录和/api的动态请求都正确指到后端location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /upload/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; }注意proxy_pass是否带尾斜杠很关键带不带尾斜杠会导致转发路径是否拼接原始URI配置错了接口地址会多一层或少一层路径。这类问题没有太多编译报错可以参考只能靠浏览器Network面板和Nginx的access.log对比。7. 从本地跑通到服务器部署的完整步骤最后说说怎么把项目从源码变成一个能在线访问的系统。很多人在本地开发很顺利一到部署就卡在环境配置上。我按顺序说清楚照着做就能跑起来。7.1 本地环境准备先确认本机环境满足版本要求JDK 8或11这里建议JDK8或JDK11SpringBoot2对应没毛病。Maven 3.6Node.js 16MySQL 8.0另外如果不想直接在宿主机上装MySQL用Docker起一个MySQL8.0非常方便docker run -d \ --name material-mysql \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDroot123456 \ -e MYSQL_DATABASEmaterial_db \ -v /data/mysql:/var/lib/mysql \ mysql:8.0启动后执行源码包里的sql脚本初始化数据表和演示数据。然后再启动后端服务。7.2 后端配置文件调整后端核心配置在application.yml里重点确认这几项server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/material_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: root123456 file: upload-dir: ./upload/启动后端后可以先用Postman或浏览器直接请求登录接口拿到Token后请求素材列表接口确认后端没问题再去启动前端。7.3 前端构建与Nginx部署前端在开发模式下通过vite运行部署时先构建静态文件npm run build构建产物在dist目录。把dist目录放到服务器上的Nginx站点目录然后把前面提到的location配置加到server块里。别忘了给upload目录设置可写权限否则上传文件时“Permission denied”会在日志里出现。如果你不想用Nginx也可以直接打成jar包用java -jar启动后端再单独配一个静态服务器给前端。不过我还是推荐用Nginx因为图片预览和静态资源缓存都在Nginx层处理效率明显更高。我在这类项目上反复体会到一件事文档写的“启动步骤”和实际部署之间永远差几步环境配置。所以上面每一步我都尽量写成了可以直接执行的状态目的就是让你少翻文档、少试错。如果你也打算拿这套系统做二次开发我额外建议三个方向一是给素材加上标签检索二是把存储目录改造成OSS对象存储三是增加角色权限细分。这些都是实际项目中一定会遇到的需求提前做一点整套系统的上限就不一样了。
阅读完成 · 觉得有帮助?
咨询建站