SpringBoot+Vue陕西民俗网管理系统开发实践

发布时间:2026/10/12 4:15:00

SpringBoot+Vue陕西民俗网管理系统开发实践 一套基于SpringBootVue的陕西民俗网管理系统用MyBatis做持久层、MySQL存数据前后端分离既能当毕业设计交差也能真正跑起来做民俗文化展示和后台管理。这篇文章我会把项目从技术选型、数据库设计、核心功能实现到部署配置和常见坑位一次讲透适合正在做类似管理系统、或者打算把民俗文化数字化落地的人参考。1. 项目定位与整体设计思路1.1 民俗文化数字化要解决的核心问题我做这类文化展示类管理系统感触很深的一点是多数民俗网站停留在“摆几张图片、贴几段文字”的静态阶段内容更新靠手工改HTML管理员想发布一条非遗资讯都要找开发人员帮忙。真正要做一个可用的民俗网管理系统至少得解决三件事民俗内容能不能结构化维护、前台展示能不能动态更新、后台操作能不能让非技术人员上手。陕西作为民俗文化大省可展示的内容类型非常丰富从传统技艺、地方戏曲到节庆活动、特色饮食每一类下面又有大量条目。如果只是堆页面内容一多就乱。所以这套系统从一开始就按“分类-条目”的树形结构来做后台管理人员录入一条民俗信息时只需要选分类、填标题、传图片、写正文前台页面自动刷新展示整个过程不碰代码。这就是管理系统的价值所在——把内容生产从开发手里交还给运营人员。1.2 为什么选SpringBoot Vue这套组合技术选型上我见过不少人纠结到底用单体模板渲染还是前后端分离。我的看法是如果你要的是一个“能长期维护、后续可能加小程序端或移动端”的系统前后端分离是更稳的起点。SpringBoot负责纯接口Vue负责页面交互两者通过JSON通信后期无论你要加微信小程序、安卓App还是大屏展示后端接口都可以直接复用。SpringBoot的优势不用多说约定大于配置内嵌Tomcat打一个jar包就能跑。MyBatis选择它是因为这类业务表的查询条件往往比较灵活——按分类筛选、按关键词搜索、按状态过滤MyBatis写动态SQL非常顺手比JPA那种全自动映射在复杂查询时更好控。MySQL则是这类中小型Web系统的稳妥选择部署简单、运维成本低配合Navicat或命令行工具管理数据都很方便。1.3 前后端模块怎么划分这套系统的功能边界我是这样划分的前端Vue项目负责页面展示和交互包括门户首页、民俗列表、民俗详情、资讯公告、后台管理界面。后端SpringBoot项目负责业务逻辑和数据持久化包括管理员认证、民俗内容管理、分类管理、资讯管理、文件上传等接口。数据库只做一件事——存储结构化数据业务规则尽量放在Service层处理不写复杂存储过程方便后续迁移和调试。这种划分在实际开发里最大的好处是前后端可以并行推进。我自己写的时候先定接口文档再同时写后端逻辑和前端页面效率比传统单体模式高不少。如果你是一个人做项目这种模式也不会增加太多工作量反而让代码结构更清晰。2. 数据库设计与功能模块拆解2.1 功能模块怎么拆陕西民俗网管理系统核心是“内容展示内容管理”。按使用角色来分前台游客可以浏览民俗分类、查看民俗详情、阅读资讯后台管理员可以维护分类树、发布民俗内容、管理资讯公告、处理图片上传。我自己整理常量时发现一个规律文化类管理系统的模块划分大体绕不开这几个内容分类模块、内容信息模块、内容资讯模块、管理员模块。分类模块解决内容组织结构问题信息模块是系统的数据核心资讯模块承担动态发布功能管理员模块负责后台登录认证。2.2 核心表结构设计下面直接给出这套系统的建表SQL字段命名和类型都是实际可跑的你可以根据自己的需求增删字段。-- 管理员表 CREATE TABLE admin_user ( id bigint(20) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 登录账号, password varchar(255) NOT NULL COMMENT 密码BCrypt加密, real_name varchar(50) DEFAULT NULL COMMENT 姓名, avatar varchar(255) DEFAULT NULL COMMENT 头像, role tinyint(4) DEFAULT 1 COMMENT 角色1超级管理员 2普通管理员, status tinyint(4) DEFAULT 1 COMMENT 状态1启用 0禁用, create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;-- 民俗分类表 CREATE TABLE folk_category ( id bigint(20) NOT NULL AUTO_INCREMENT, category_name varchar(100) NOT NULL COMMENT 分类名称, parent_id bigint(20) DEFAULT 0 COMMENT 父分类ID0表示顶级, sort_order int(11) DEFAULT 0 COMMENT 排序号, icon varchar(255) DEFAULT NULL COMMENT 分类图标, description varchar(500) DEFAULT NULL COMMENT 分类描述, status tinyint(4) DEFAULT 1 COMMENT 状态1启用 0禁用, create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;-- 民俗信息表 CREATE TABLE folk_info ( id bigint(20) NOT NULL AUTO_INCREMENT, category_id bigint(20) NOT NULL COMMENT 所属分类ID, title varchar(200) NOT NULL COMMENT 民俗标题, summary varchar(500) DEFAULT NULL COMMENT 摘要, cover_image varchar(255) DEFAULT NULL COMMENT 封面图, content longtext COMMENT 民俗详细介绍, region varchar(100) DEFAULT NULL COMMENT 所属地区如关中、陕北、陕南, source varchar(100) DEFAULT NULL COMMENT 资料来源, view_count int(11) DEFAULT 0 COMMENT 浏览量, is_hot tinyint(4) DEFAULT 0 COMMENT 是否热门推荐, status tinyint(4) DEFAULT 1 COMMENT 状态1已发布 0草稿, create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id), KEY idx_category (category_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT民俗信息表;-- 资讯公告表 CREATE TABLE folk_news ( id bigint(20) NOT NULL AUTO_INCREMENT, title varchar(200) NOT NULL COMMENT 资讯标题, content longtext COMMENT 资讯内容, cover_image varchar(255) DEFAULT NULL, news_type tinyint(4) DEFAULT 1 COMMENT 类型1公告 2新闻动态, publish_time datetime DEFAULT NULL, status tinyint(4) DEFAULT 1, create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;2.3 建表的一些细节经验第一点所有表的主键都用bigint自增不搞复杂的分布式ID方案单体管理系统没必要。第二点字符集一定要统一用utf8mb4虽然utf8也能存中文但生僻字和特殊符号会出问题。民俗内容里经常出现一些古籍引用、方言字、异体字utf8mb4能覆盖得更好。第三点我要特别强调status字段的设计。很多人做内容系统喜欢直接删记录但如果文章已经被前台引用物理删除会导致页面出现死链。我习惯加一个逻辑删除或上下架状态前台查询只查status1的数据管理后台可以看到全部记录。这样即使某条民俗内容审核不通过或者需要下架也只是切换状态不丢失数据。字段注释一定要写清楚这一点在团队协作和答辩演示时特别加分。你过两个月再回来看自己的SQL如果没有注释光靠字段名猜含义效率很低。3. 后端核心功能实现与实操细节3.1 项目初始化和统一响应封装SpringBoot项目初始化我用的是Maven作为构建工具打包成jar包部署。工程结构上按Controller、Service、Mapper分层分包命名建议用com.xxx.folklore这种形式清晰直观。后端接口最重要的一件事是先定一个统一的返回体。我在实际项目里深有体会如果没有统一返回结构不同接口返回格式五花八门前端对接的时候简直想摔键盘。我通常这样定义Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(操作成功); result.setData(data); return result; } public static T ResultT error(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } }有了这个类之后所有Controller返回值都统一用Result包一层前端拿到响应先判code是否为200再取data处理数据。这样可以省掉大量重复的错误处理代码。3.2 民俗信息分页查询接口民俗列表的查询是前台最核心的接口需要考虑分类筛选、关键词搜索、分页、排序这几个维度。用MyBatis写动态SQL来应对这种多条件组合查询最舒服不需要为每种查询条件组合单独写一个方法。GetMapping(/list) public ResultPageResultFolkInfoVO list( RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, RequestParam(required false) Long categoryId, RequestParam(required false) String keyword, RequestParam(required false) String region) { // 业务逻辑省略 }对应的XML文件核心部分这样写select idselectFolkInfoPage resultTypecom.xxx.folklore.vo.FolkInfoVO SELECT fi.id, fi.title, fi.summary, fi.cover_image, fi.region, fi.view_count, fi.create_time, fc.category_name FROM folk_info fi LEFT JOIN folk_category fc ON fi.category_id fc.id where fi.status 1 if testcategoryId ! null AND fi.category_id #{categoryId} /if if testkeyword ! null and keyword ! AND (fi.title LIKE CONCAT(%, #{keyword}, %) OR fi.summary LIKE CONCAT(%, #{keyword}, %)) /if if testregion ! null and region ! AND fi.region #{region} /if /where ORDER BY fi.is_hot DESC, fi.create_time DESC /select注意一个细节这里用LEFT JOIN把分类名称一起查出来避免前台页面拿到categoryId之后还要发二次请求去查分类名。在列表接口里多用一个JOIN字段比在页面里再做一次循环查询效率高得多请求次数也少了。很多新手容易忽视这个问题结果就是列表页每个条目都要单独查一次分类N1查询问题非常明显。3.3 文件上传与图片访问民俗内容里图片是刚需所以文件上传接口必须稳定。我在上传逻辑里做了三件事校验文件类型、按日期分目录存储、生成带时间戳的新文件名。PostMapping(/upload) public ResultString upload(RequestParam(file) MultipartFile file) { // 校验文件大小和类型 if (file.isEmpty() || file.getSize() 10 * 1024 * 1024) { return Result.error(文件不能为空且大小不能超过10MB); } String originName file.getOriginalFilename(); String ext originName.substring(originName.lastIndexOf(.)); ListString allowedExts Arrays.asList(.jpg, .jpeg, .png, .gif, .webp); if (!allowedExts.contains(ext.toLowerCase())) { return Result.error(不支持的图片格式); } // 新文件名时间戳 随机数 String newName System.currentTimeMillis() _ UUID.randomUUID().toString().substring(0, 8) ext; // 按日期分目录 String datePath new SimpleDateFormat(yyyyMMdd).format(new Date()); String dirPath uploadRootPath / datePath; File dir new File(dirPath); if (!dir.exists()) { dir.mkdirs(); } file.transferTo(new File(dir, newName)); // 返回可访问的URL String url /upload/ datePath / newName; return Result.success(url); }文件上传有两个隐蔽的坑。第一个是transferTo方法跨平台路径问题Windows和Linux的路径分隔符不一致最好用File.separator拼接。第二个是静态资源映射SpringBoot默认不映射本地磁盘的upload目录必须在配置里指定Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceHandler(file: uploadRootPath /); } }如果不配这个映射图片会一直显示404本地调试还看不出来部署到服务器上更容易踩坑。4. 前端Vue页面搭建与接口对接4.1 Vue工程结构和路由设计前端我用Vue2 Element UI组合。Vue3的生态当然更新但Element UI的组件成熟度在管理后台开发中依然有优势而且相关文档和踩坑文章最多遇到问题查起来效率高。工程目录按views、router、api、components拆分路由用vue-router配置。前台门户和后台管理分开const routes [ { path: /, component: Home, meta: { title: 陕西民俗网首页 } }, { path: /category/:id, component: CategoryDetail, meta: { title: 民俗分类 } }, { path: /detail/:id, component: FolkDetail, meta: { title: 民俗详情 } }, { path: /admin, component: AdminLayout, redirect: /admin/dashboard, children: [ { path: dashboard, component: Dashboard }, { path: category, component: CategoryManage }, { path: folk-list, component: FolkList }, { path: folk-edit, component: FolkEdit }, { path: news-list, component: NewsList } ] } ];这里有个路由设计上的心得前台详情页用动态路由传id比如/detail/:id进入页面后在mounted钩子里用this.$route.params.id请求详情接口。很多新手喜欢用query传参刷新页面后参数还在URL上看着不干净体验也一般。动态路由是更合理的方案。4.2 后台管理页面的表格与弹窗操作后台民俗列表页我用了Element UI的el-table el-dialog组合。表格展示标题、分类、地区、浏览量、状态、创建时间这几列每行提供编辑、删除按钮。新建和编辑共用一个el-dialog弹窗弹窗里放el-form表单通过dialog的title区分模式。关键点是编辑数据回显表单提交和编辑必须统一数据结构handleEdit(row) { this.dialogTitle 编辑民俗信息; this.dialogVisible true; // 深拷贝行数据避免直接修改表格显示 this.form JSON.parse(JSON.stringify(row)); }, handleSubmit() { this.$refs.form.validate(async (valid) { if (!valid) return; if (this.form.id) { await updateFolkInfo(this.form.id, this.form); this.$message.success(修改成功); } else { await addFolkInfo(this.form); this.$message.success(新增成功); } this.dialogVisible false; this.loadList(); }); }我特别提醒一个坑如果直接用this.form row来赋值表单里改了数据表格行里的数据也会跟着变因为对象是引用类型。用JSON.parse(JSON.stringify(row))深拷贝一份是最简单的规避方案。4.3 前端请求封装与跨域处理axios请求统一封装在utils/request.js里设置baseURL、超时时间、请求拦截器和响应拦截器。响应拦截器里统一处理code非200的情况避免每个页面重复写错误提示。跨域是前后端分离项目必遇到的问题。前端运行在8080端口后端接口在9090端口浏览器会拦截跨域请求。开发环境的解决方法是利用Vue CLI的proxy代理// vue.config.js module.exports { devServer: { port: 8080, proxy: { /api: { target: http://localhost:9090, changeOrigin: true, pathRewrite: { ^/api: } } } } };这样前端所有请求写成/api开头代理会自动转发到后端。这个方案比后端加CORS配置更干净生产环境里Nginx也有同样的代理能力开发和生产环境切换起来很顺畅。5. 系统部署配置与运行环境搭建5.1 本地运行需要准备什么运行这套系统需要先准备好环境JDK 1.8以上、Maven 3.6以上、Node.js 14以上、MySQL 5.7以上。如果你是刚接触这类项目建议按顺序来先启动MySQL并导入SQL脚本再启动后端SpringBoot工程最后启动前端Vue工程。一个常见问题是版本兼容。SpringBoot 2.x对应JDK 8和Java 8语法完全兼容Node版本太新可能导致node-sass编译失败。如果Node版本超过16建议优先选择Vue2.5对应版本的依赖或者直接用npm install --legacy-peer-deps来绕开依赖冲突。5.2 application.yml配置怎么看后端的核心配置全部在application.yml里几个关键项值得逐一说明server: port: 9090 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/folk_db?useUnicodetruecharacterEncodingutf8mb4useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: your_password servlet: multipart: max-file-size: 10MB max-request-size: 20MB mybatis: mapper-locations: classpath:mapper/**/*.xml type-aliases-package: com.xxx.folklore.entity configuration: map-underscore-to-camel-case: true第一MySQL驱动类要用com.mysql.cj.jdbc.Driver这是MySQL 8的驱动类名老版本的com.mysql.jdbc.Driver会报错。第二url里必须带serverTimezoneAsia/Shanghai否则连数据库直接报时区异常。第三map-underscore-to-camel-case这个配置一定要开数据库的下划线字段自动映射成Java驼峰属性省掉一堆resultMap。5.3 打包与Nginx部署后端打包时跳过测试用例能节省不少时间mvn clean package -DskipTests执行完会在target目录生成jar包上传到服务器上直接运行nohup java -jar folklore-admin.jar --spring.profiles.activeprod app.log 21 前端打包npm run build生成的dist目录部署到Nginx配置里做两件事一是location /指向dist目录二是/api开头的请求反向代理到后端的9090端口server { listen 80; server_name your_domain; root /home/www/folk_dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:9090/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }部署之后还有个缓存问题需要提醒你前端打包生成的文件名带hash值但index.html本身容易被缓存导致用户访问到旧版本。在Nginx里给index.html配置no-cache就可以了。6. 常见问题排查与避坑实录6.1 数据库中文乱码这个问题的概率最高。现象是页面显示中文全是问号或者乱码。排查方向按三步来第一步确认建库语句是否指定utf8mb4如果建库的时候没指定即使建表语句写了utf8mb4也可能被覆盖。第二步检查数据库连接URL是否带characterEncodingutf8。第三步检查前端页面meta标签是否声明UTF-8编码。大多数中文乱码都是连接URL少了编码参数加上characterEncodingutf8mb4重启即可解决。6.2 前端访问接口404这个坑和代理路径有关。如果前端axios的baseURL配置成/api而后端Controller的RequestMapping没有统一的context-path请求会转发到不存在的路径。解决方案是在application.yml里配置server: servlet: context-path: /api让后端所有接口统一带上/api前缀与前端代理保持一致。这个配置我强烈建议一开始就加上后续不管部署到哪路径前缀都清晰明了。6.3 MyBatis的XML映射文件不生效启动时报Invalid bound statement或找不到Mapper方法多半是mapper扫描配置问题。SpringBoot默认扫描不到src/main/java目录下的XML文件必须把mapper-locations指向classpath:mapper/**/*.xml同时确保XML文件放在src/main/resources/mapper目录下。还有一个更隐蔽的问题如果XML文件里namespace写错或id和Mapper接口方法名不一致启动时不会立刻报错运行时才提示找不到方法。遇到这种问题优先检查这两处。6.4 端口占用与启动失败启动SpringBoot时提示端口被占用用下面的命令查找进程并处理netstat -tlnp | grep 9090 kill -9 进程ID如果是前端8080端口冲突查出来的是Vue的dev-server进程。开发时这个弹窗直接换个端口也可以。6.5 图片上传成功但页面无法显示这个案例我遇到不下三次。上传接口返回了URL数据库也存了路径页面img标签的src就是那个路径但浏览器显示404。直接原因就是静态资源映射没有配置或者配置文件里的uploadRootPath和实际存储路径不一致。排查思路先用浏览器直接访问图片URL如果是404说明后端不会处理/upload开头的静态资源请求在WebConfig里补上addResourceHandlers配置。如果本机测试没问题、服务器上有问题多半是Linux目录权限或绝对路径写死了本机路径改成相对配置或统一配置环境变量即可。我实际整理这套系统的过程中最深的体会是技术本身都不复杂但把每个环节串起来、把每个常见的坑提前踩平才是这套源码真正的价值。如果你是拿来做课程设计建议重点讲清楚数据库设计的依据和前后端交互流程如果你是想真正部署上线建议先把配置规范和Nginx部署脚本准备好。如果后续想在这个项目上做扩展可以优先考虑加一个小程序端展示入口或者对接地图API做民俗文化的地理分布可视化这些都是加分项。
延伸阅读

更多相关文章

2026/10/12 4:15:00

兜里揣着千亿现金的苹果拟裁五千客服,机器接通后方案却被叫停

兜里揣着千亿现金的苹果拟裁五千客服,机器接通后方案却被叫停 如果你最近在美国或加拿大拨打过苹果的官方支持热线,接通那一刻,电话那头传来的很可能已经不是真人声音,而是一个能听懂日常口语、能分步骤教你排查手机故障的生成式人…

2026/10/12 4:10:00

100亿Token微调实战:从数据清洗到LoRA/QLoRA开源全流程

从立项到开源,这个项目整整花了我两个月时间,累计消耗的 Token 数超过了 100 亿。期间经历过数据质量不过关重来、训练中 loss 异常、评测结果不理想等多轮反复。这篇文章不打算只讲“我开源了一个模型”这样一个结果,而是把这套完整流程整理…

2026/10/12 5:20:02

携程酒店数据爬虫实战:从表结构设计到反爬避坑指南

简介:面向Java开发者的携程酒店数据爬虫项目完整工程包,适合需要采集酒店价格、房态等公开信息,或研究Java爬虫技术的开发者。项目依托Eclipse组织,核心抓取逻辑分布于23个Java源文件中,另附4个依赖jar包、属性配置、工…

2026/10/12 5:20:02

GitHub热榜深度拆解:从数据逻辑到高价值项目筛选与开源参与

每天早上的固定动作,我都是先泡杯咖啡,然后打开 GitHub 热榜页面的日榜。2026年10月8日这天照例不例外,榜单刷新之后还是熟悉的味道:新拿到首秀的库、连续挂榜两天的黑马、被社区重新挖出来的老项目,混在一个列表里。很…

2026/10/12 5:20:02

C语言整除判断:从取余运算到流程图与同余实战

提到“整除”这两个字,写代码的人脑子里通常先闪过的是C语言里的%取余符。说实话,这个运算符用起来简单,但真要拿它去判断“一个数能不能同时被3和5整除”,再画一张标准流程图,很多人会卡在细节上。前几天一个读者给我…

2026/10/12 5:20:02

近五十年的围墙

▲图注:这份采购文件里要修的东西,一样也不在机房的机柜里。华中某省的无线电台管理中心在2024年5月发布了一份竞争性磋商采购文件,为四个中波转播台做机房配套设施及围墙改造,总预算约300万元,分成四个包,…

2026/10/11 0:02:13

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/11 0:02:13

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/11 0:02:13

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/12 0:04:22

绝缘子缺陷检测数据集清洗与工业级训练实战指南

简介:本资源是面向电力AI研发人员、工业视觉工程师及智能巡检系统开发者的绝缘子缺陷检测专用YOLO格式数据集,解决无人机航拍场景下绝缘子破损、污闪、积雪等9类典型缺陷的精准识别与定位难题。数据集共2139张真实巡检图像(含训练/验证/测试集…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑