恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

SpringBoot+Vue3入校申报审批系统开发实践

  • 首页
  • 资讯中心
  • /
  • SpringBoot+Vue3入校申报审批系统开发实践

相关资讯

架构师必备:分布式锁方案选型(含代码实战) 2026/8/2 17:53:41
低温环境下压缩气体储能系统性能对比研究 2026/8/2 1:14:39
Agent 开发入门必懂的 10 个核心概念(附代码实战) 2026/8/2 17:53:42

最新资讯

数学建模思想与核心方法:从问题分析到模型验证的完整流程
基于POMDP的AI智能体分层验证:从信念、预测到策略的系统化工程实践
层次分析法(AHP)实战指南:从原理到应用,告别“拍脑袋”决策
Facter 3 到 Facter 4 迁移指南:自定义 Facts 与外部 Facts 兼容性完整清单
Just-Another-Android-App 指南:3 步配好应用快捷方式,再给快速设置面板加一块 TileService
BepInEx 6.0 终极指南:5步快速搭建Unity游戏插件框架并跑通第一个模组

今日推荐

OpenModScan:免费跨平台 Modbus 主站调试工具,让现场通讯验证一键搞定
WechatHook 终极指南:5大核心能力详解,3分钟看懂微信自动化
如何在ThinkPad X390上安装macOS:OpenCore EFI完整指南

本周热门

Nextcloud 桌面客户端:把同步交给它,你只管改文件
如何将 HTML 转成 Word 文档且格式不丢失?html-to-docx 使用教程
Anki 批量操作卡片完整指南:一次搞定上千张,不再逐张修改

本月精选

如何用DamaiHelper实现演唱会门票的智能自动化抢购:完整技术解决方案指南
第4篇:59 倍性能差距的索引瓶颈定位——一次教科书级的全表扫描调优
终极歌词批量下载神器:5分钟解决离线音乐库歌词同步难题

SpringBoot+Vue3入校申报审批系统开发实践

发布时间:2026/8/24 17:08:01
SpringBoot+Vue3入校申报审批系统开发实践 1. 项目概述入校申报审批系统的技术架构与价值这个基于SpringBoot2Vue3MyBatis-PlusMySQL8.0的入校申报审批系统是典型的现代化校园管理解决方案。我在实际开发中发现这类系统能有效解决传统纸质审批流程中存在的效率低下、数据孤岛和追溯困难等问题。整套技术栈的选择充分考虑了当前企业级应用开发的主流趋势前后端分离架构让系统具备良好的扩展性和维护性。系统核心功能包括在线申报表单、多级审批工作流、实时状态跟踪、数据统计分析等模块。特别值得一提的是我们采用了Vue3的Composition API来实现动态表单渲染这使得不同类别的入校申请如访客、供应商、临时工能够自动适配不同的字段要求。后端审批逻辑通过SpringBoot的异步处理机制确保高并发场景下的系统稳定性。2. 技术栈深度解析与选型考量2.1 SpringBoot2的后端基石作用选择SpringBoot2而非更新的3.0版本主要基于企业环境的JDK兼容性考虑。在实际部署中发现很多学校的服务器仍运行JDK8环境而SpringBoot3.x强制要求JDK17。我们通过自定义starter模块封装了以下核心功能统一异常处理ControllerAdvice参数验证Hibernate Validator接口文档Knife4j集成权限拦截JWT自定义注解特别要注意的是在pom.xml中需要精确控制spring-boot-starter-parent的版本号我们使用2.7.18避免自动升级带来的兼容性问题。以下是关键依赖示例dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version0.9.1/version /dependency2.2 Vue3前端架构设计采用Vue3的组合式API相比Options API更适合复杂审批流程的实现。我们在项目中建立了以下核心结构src/ ├── api/ # Axios封装 ├── composables/ # 组合式函数 │ ├── useFormDynamic.js # 动态表单逻辑 │ └── useWorkflow.js # 审批流程控制 ├── stores/ # Pinia状态管理 └── views/ ├── application/ # 申报模块 └── approval/ # 审批模块一个典型的动态表单组合函数实现// useFormDynamic.js export function useFormDynamic(formType) { const fields ref([]) const loadTemplate async () { const { data } await getFormConfig(formType) fields.value data.fields.map(f ({ ...f, model: field_${f.id}, rules: f.required ? [{ required: true }] : [] })) } return { fields, loadTemplate } }2.3 MyBatis-Plus的高效数据操作MyBatis-Plus的Lambda查询方式大幅简化了审批查询逻辑。我们特别定制了分页插件解决审批记录分页时的性能问题Configuration public class MyBatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); // 分页插件优化count查询 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL) { Override public void optimizeCount(IPage? page, String countSql) { // 审批列表不需要精确count if (page.getCurrent() 10) { page.setTotal(10000L); return; } super.optimizeCount(page, countSql); } }); return interceptor; } }2.4 MySQL8.0特性应用利用MySQL8.0的窗口函数优化审批统计报表查询。例如计算各部门审批时效SELECT department, AVG(TIMESTAMPDIFF(HOUR, create_time, approve_time)) OVER() AS avg_all, AVG(TIMESTAMPDIFF(HOUR, create_time, approve_time)) OVER(PARTITION BY department) AS avg_dept FROM approval_records WHERE status APPROVED同时使用了JSON字段存储动态表单数据平衡了关系型数据库的规范性和灵活性需求。3. 核心功能实现细节3.1 动态表单引擎设计申报表单需要适应不同场景访客/施工/活动我们设计了基于JSON Schema的配置方案// 访客申请表单配置 { type: object, properties: { visitorType: { type: string, title: 访客类型, enum: [家长, 合作方, 其他], widget: select }, idNumber: { type: string, title: 身份证号, pattern: ^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[0-9Xx]$, required: true } } }后端使用Jackson的JsonNode类型接收动态表单数据通过MyBatis-Plus的TableField(typeHandler JacksonTypeHandler.class)实现JSON字段映射。3.2 审批工作流实现基于状态模式设计审批流程控制器public interface ApprovalState { void submit(ApprovalContext context); void approve(ApprovalContext context); void reject(ApprovalContext context); } // 示例状态实现 Component Scope(prototype) public class DepartmentReviewState implements ApprovalState { Override public void approve(ApprovalContext context) { ApprovalRecord record context.getRecord(); if (record.getApproverLevel() 3) { record.setStatus(NEXT_LEVEL); record.setApproverLevel(record.getApproverLevel() 1); } else { record.setStatus(COMPLETED); } record.setUpdateTime(LocalDateTime.now()); approvalMapper.updateById(record); } }3.3 实时通知机制结合WebSocket和数据库事件表实现可靠通知// 事件发布器 public class ApprovalEventPublisher { private final SimpMessagingTemplate messagingTemplate; private final ApplicationEventPublisher eventPublisher; public void publishEvent(ApprovalEvent event) { // 数据库持久化 eventMapper.insert(event); // WebSocket实时推送 messagingTemplate.convertAndSendToUser( event.getTargetUserId(), /queue/approval, event ); // 应用内事件 eventPublisher.publishEvent(event); } }前端使用SockJS实现断线重连const setupWebSocket (userId) { const socket new SockJS(/ws-endpoint) const client Stomp.over(socket) client.connect({}, () { client.subscribe(/user/${userId}/queue/approval, (message) { const event JSON.parse(message.body) showNotification(event) }) }, (error) { setTimeout(() setupWebSocket(userId), 5000) }) }4. 部署与性能优化实践4.1 多环境配置方案使用SpringBoot的Profile机制实现环境隔离# application-dev.yml spring: datasource: url: jdbc:mysql://dev-db:3306/approval?useSSLfalse username: devuser password: dev123 # application-prod.yml spring: datasource: url: jdbc:mysql://prod-cluster:3306/approval?useSSLtrueallowPublicKeyRetrievaltrue username: ${DB_USER} password: ${DB_PASSWORD} hikari: maximum-pool-size: 20 connection-timeout: 300004.2 前端性能优化通过Vite的代码分割和懒加载优化首屏性能// 路由配置 const routes [ { path: /approval, component: () import(/views/approval/Index.vue), children: [ { path: list, component: () import(/views/approval/List.vue) } ] } ]同时配置了Gzip压缩和CDN加速// vite.config.js export default defineConfig({ build: { rollupOptions: { output: { manualChunks: { vue: [vue, vue-router, pinia], element: [element-plus] } } } } })4.3 数据库优化实践针对审批列表查询建立了复合索引CREATE INDEX idx_approval_multi ON approval_records (applicant_id, status, create_time DESC);使用EXPLAIN分析慢查询时发现范围查询后排序会导致filesort于是调整了索引顺序-- 优化前 SELECT * FROM approval_records WHERE status PENDING AND create_time 2023-01-01 ORDER BY priority DESC; -- 优化后添加索引 CREATE INDEX idx_status_priority ON approval_records (status, priority DESC, create_time);5. 典型问题排查实录5.1 MyBatis-Plus更新失效问题遇到updateById执行后数据库未更新的情况排查发现是实体类中使用了不规范的Lombok注解// 错误示例 Data public class ApprovalRecord { private Long id; TableField(update now()) private LocalDateTime updateTime; // 不会自动更新 } // 正确写法 Data public class ApprovalRecord { private Long id; TableField(fill FieldFill.UPDATE) private LocalDateTime updateTime; }还需要在配置类中添加自动填充处理器Component public class MyMetaObjectHandler implements MetaObjectHandler { Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, updateTime, LocalDateTime::now, LocalDateTime.class); } }5.2 Vue3路由缓存失效三级嵌套路由下keep-alive失效的解决方案!-- 父路由组件 -- router-view v-slot{ Component } keep-alive :includecachedViews component :isComponent / /keep-alive /router-view // 路由配置 { path: /detail/:id, component: () import(./Detail.vue), children: [ { path: basic, component: () import(./BasicInfo.vue), meta: { cacheKey: basic } } ] }在pinia store中维护缓存keyexport const useRouteStore defineStore(route, { state: () ({ cachedViews: new Set() }), actions: { addCachedView(key) { this.cachedViews.add(key) } } })5.3 大文件上传内存溢出处理入校申请中的附件上传时出现OOM通过以下方案解决// 前端分片上传 const uploadFile async (file) { const chunkSize 2 * 1024 * 1024 // 2MB const chunks Math.ceil(file.size / chunkSize) for (let i 0; i chunks; i) { const chunk file.slice(i * chunkSize, (i 1) * chunkSize) await axios.post(/upload, chunk, { headers: { Content-Range: bytes ${i * chunkSize}-${Math.min((i 1) * chunkSize - 1, file.size - 1)}/${file.size} } }) } }后端使用临时文件存储PostMapping(/upload) public ResponseEntity? upload( RequestParam(file) MultipartFile file, RequestHeader(Content-Range) String range) { String tempDir System.getProperty(java.io.tmpdir); Path tempFile Paths.get(tempDir, upload_ range.hashCode()); Files.write(tempFile, file.getBytes(), StandardOpenOption.CREATE); return ResponseEntity.ok().build(); }6. 安全防护方案6.1 接口防刷策略针对申报接口添加Guava RateLimiter限流RestController RequestMapping(/api/application) public class ApplicationController { private final RateLimiter rateLimiter RateLimiter.create(10.0); // 10 QPS PostMapping public ResponseEntity? createApplication(RequestBody ApplicationDTO dto) { if (!rateLimiter.tryAcquire()) { throw new BusinessException(操作过于频繁请稍后再试); } // 正常处理逻辑 } }6.2 SQL注入防护虽然MyBatis-Plus默认使用预编译但对于动态排序仍需注意// 不安全的写法 String orderBy request.getParameter(orderBy); queryWrapper.orderBy(true, asc.equals(orderDir), orderBy); // 安全的白名单方式 private static final SetString ALLOWED_ORDER_FIELDS Set.of( create_time, update_time, status ); public void applyOrder(QueryWrapper? wrapper, String field, String dir) { if (ALLOWED_ORDER_FIELDS.contains(field)) { wrapper.orderBy(true, asc.equalsIgnoreCase(dir), field); } }6.3 XSS防护前端使用DOMPurify净化富文本内容import DOMPurify from dompurify const cleanHtml DOMPurify.sanitize(dirtyHtml, { ALLOWED_TAGS: [p, br, strong, em], ALLOWED_ATTR: [style] })后端同时进行内容校验public class XssFilter implements Filter { private static final Pattern[] PATTERNS new Pattern[]{ Pattern.compile(script(.*?)/script, Pattern.CASE_INSENSITIVE), Pattern.compile(javascript:, Pattern.CASE_INSENSITIVE) }; Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { HttpServletRequest httpRequest (HttpServletRequest) request; XssRequestWrapper wrappedRequest new XssRequestWrapper(httpRequest); chain.doFilter(wrappedRequest, response); } }7. 项目文档体系7.1 接口文档规范使用Knife4j增强Swagger展示Configuration EnableSwagger2 Import(BeanValidatorPluginsConfiguration.class) public class SwaggerConfig { Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage(com.school.approval)) .paths(PathSelectors.any()) .build() .securitySchemes(Collections.singletonList(apiKey())); } private ApiKey apiKey() { return new ApiKey(Authorization, Authorization, header); } }7.2 数据库设计文档采用PDManer生成ER图并导出Markdown文档包含以下关键表用户表sys_user角色表sys_role申报表application_form审批记录表approval_record通知表sys_notification7.3 部署手册要点容器化部署方案示例# 前端Dockerfile FROM nginx:alpine COPY dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf # 后端Dockerfile FROM openjdk:8-jdk-alpine ARG JAR_FILEtarget/*.jar COPY ${JAR_FILE} app.jar ENTRYPOINT [java,-jar,/app.jar]使用docker-compose编排version: 3 services: frontend: build: ./frontend ports: - 80:80 backend: build: ./backend environment: - SPRING_PROFILES_ACTIVEprod depends_on: - mysql mysql: image: mysql:8.0 environment: - MYSQL_ROOT_PASSWORDroot123 - MYSQL_DATABASEapproval volumes: - mysql_data:/var/lib/mysql volumes: mysql_data:8. 扩展功能建议8.1 微信小程序集成通过uni-app改造前端复用现有API// 微信登录集成 uni.login({ provider: weixin, success: (res) { axios.post(/api/auth/wechat, { code: res.code }) .then(response { store.commit(SET_TOKEN, response.data.token) }) } })8.2 审批数据大屏使用ECharts实现审批数据可视化// 审批时效统计图 const renderChart () { const chart echarts.init(document.getElementById(chart)) chart.setOption({ tooltip: { trigger: axis }, xAxis: { data: departments }, yAxis: { type: value }, series: [{ name: 平均审批时效(h), type: bar, data: avgHours }] }) }8.3 自动化测试方案基于Testcontainers的集成测试Testcontainers SpringBootTest class ApprovalServiceIT { Container static MySQLContainer? mysql new MySQLContainer(mysql:8.0); DynamicPropertySource static void configureProperties(DynamicPropertyRegistry registry) { registry.add(spring.datasource.url, mysql::getJdbcUrl); registry.add(spring.datasource.username, mysql::getUsername); registry.add(spring.datasource.password, mysql::getPassword); } Test void testApprovalWorkflow() { // 测试完整审批流程 } }在实际项目中我们发现使用H2内存数据库进行测试时某些MySQL特有语法如窗口函数无法执行这正是选择Testcontainers的重要原因。通过真实数据库测试可以提前发现环境差异导致的问题。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号