ARTICLE DETAIL

资讯详情

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

SpringBoot+Vue校园网络设备报修系统开发实践

SpringBoot+Vue校园网络设备报修系统开发实践 1. 项目背景与需求分析高校校园网络设备报修管理一直是后勤信息化建设的痛点。传统报修方式主要存在以下几个问题流程繁琐师生需要通过电话、纸质表单或线下登记等方式提交报修请求信息传递效率低下响应滞后维修部门难以及时获取和分配报修任务导致响应时间长进度不透明报修人无法实时了解维修进度经常需要反复询问数据分散设备台账、维修记录等数据分散存储难以进行统计分析基于SpringBootVue的校园网络设备报修管理系统正是为解决这些问题而设计。系统采用前后端分离架构前端使用Vue.js构建用户友好的交互界面后端采用SpringBoot提供RESTful APINode.js作为中间层处理业务逻辑MySQL作为数据存储。2. 技术选型与架构设计2.1 技术栈组成前端技术栈Vue.js 3.x采用Composition API编写组件Element PlusUI组件库AxiosHTTP请求库Vue Router前端路由管理Pinia状态管理后端技术栈SpringBoot 2.7.x后端框架MyBatis-PlusORM框架Spring Security认证授权Redis缓存和会话管理中间层Node.js 16.x处理业务逻辑Express/KoaWeb框架数据库MySQL 8.0主数据库MongoDB可选存储非结构化数据如维修图片2.2 系统架构设计系统采用典型的三层架构用户层 → 表现层(Vue) → 业务层(Node.js) → 服务层(SpringBoot) → 数据层(MySQL)这种架构的优势在于前后端完全解耦便于独立开发和部署Node.js中间层可以处理高并发的I/O操作SpringBoot提供稳定的核心业务服务各层职责明确便于扩展和维护3. 核心功能模块实现3.1 用户认证与权限管理采用JWTSpring Security实现认证授权// Spring Security配置示例 Configuration EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers(/api/auth/**).permitAll() .antMatchers(/api/admin/**).hasRole(ADMIN) .antMatchers(/api/repair/**).hasAnyRole(ADMIN, MAINTENANCE) .anyRequest().authenticated() .and() .addFilter(new JwtAuthenticationFilter(authenticationManager())) .addFilter(new JwtAuthorizationFilter(authenticationManager())); } }Node.js中间层处理JWT验证// Node.js中间件验证JWT const jwt require(jsonwebtoken); const authenticateJWT (req, res, next) { const authHeader req.headers.authorization; if (authHeader) { const token authHeader.split( )[1]; jwt.verify(token, process.env.JWT_SECRET, (err, user) { if (err) { return res.sendStatus(403); } req.user user; next(); }); } else { res.sendStatus(401); } };3.2 报修流程设计完整的报修流程包括以下步骤报修提交用户填写设备信息、故障描述可上传图片工单分配系统自动或管理员手动分配维修人员维修处理维修人员接单、处理、反馈验收评价用户确认维修结果并评价Vue前端报修表单关键代码template el-form :modelrepairForm :rulesrules refrepairForm el-form-item label设备类型 propdeviceType el-select v-modelrepairForm.deviceType el-option v-foritem in deviceTypes :keyitem.value :labelitem.label :valueitem.value /el-option /el-select /el-form-item el-form-item label故障描述 propdescription el-input typetextarea v-modelrepairForm.description :rows4 /el-input /el-form-item el-form-item label上传图片 el-upload action/api/upload list-typepicture-card :on-successhandleUploadSuccess i classel-icon-plus/i /el-upload /el-form-item /el-form /template script export default { data() { return { repairForm: { deviceType: , description: }, rules: { deviceType: [ { required: true, message: 请选择设备类型, trigger: change } ], description: [ { required: true, message: 请输入故障描述, trigger: blur } ] } } }, methods: { handleUploadSuccess(response) { this.repairForm.images this.repairForm.images || []; this.repairForm.images.push(response.data.url); } } } /script3.3 维修进度追踪实现维修进度实时更新和通知// SpringBoot WebSocket配置 Configuration EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker(/topic); config.setApplicationDestinationPrefixes(/app); } Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint(/ws) .setAllowedOrigins(*) .withSockJS(); } }Vue前端订阅进度更新// Vue组件中连接WebSocket mounted() { this.connectWebSocket(); }, methods: { connectWebSocket() { const socket new SockJS(/ws); this.stompClient Stomp.over(socket); this.stompClient.connect({}, (frame) { this.stompClient.subscribe(/topic/repair/${this.repairId}, (message) { const update JSON.parse(message.body); this.updateRepairStatus(update); }); }); } }4. 数据库设计与优化4.1 核心表结构用户表(users)CREATE TABLE users ( id bigint NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL, password varchar(100) NOT NULL, real_name varchar(50) DEFAULT NULL, phone varchar(20) DEFAULT NULL, email varchar(100) DEFAULT NULL, role enum(ADMIN,MAINTENANCE,USER) NOT NULL DEFAULT USER, department_id bigint DEFAULT NULL, status tinyint NOT NULL DEFAULT 1, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY idx_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;设备表(devices)CREATE TABLE devices ( id bigint NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL, type varchar(50) NOT NULL, model varchar(100) DEFAULT NULL, location varchar(200) DEFAULT NULL, status enum(NORMAL,MAINTAINING,FAULT,SCRAPPED) NOT NULL DEFAULT NORMAL, purchase_date date DEFAULT NULL, warranty_period int DEFAULT NULL COMMENT 保修期(月), department_id bigint DEFAULT NULL, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;报修单表(repair_orders)CREATE TABLE repair_orders ( id bigint NOT NULL AUTO_INCREMENT, order_no varchar(20) NOT NULL COMMENT 报修单号, device_id bigint DEFAULT NULL, user_id bigint NOT NULL COMMENT 报修人, maintainer_id bigint DEFAULT NULL COMMENT 维修人, fault_type varchar(50) NOT NULL, description text, images text COMMENT 图片URL多个用逗号分隔, status enum(PENDING,ASSIGNED,PROCESSING,COMPLETED,CANCELLED) NOT NULL DEFAULT PENDING, priority enum(LOW,MEDIUM,HIGH,URGENT) NOT NULL DEFAULT MEDIUM, expected_finish_time datetime DEFAULT NULL, actual_finish_time datetime DEFAULT NULL, rating tinyint DEFAULT NULL COMMENT 评分1-5, feedback text COMMENT 用户反馈, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY idx_order_no (order_no), KEY idx_device_id (device_id), KEY idx_user_id (user_id), KEY idx_maintainer_id (maintainer_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;4.2 查询优化实践针对高频查询进行优化报修单分页查询-- 使用覆盖索引优化分页查询 SELECT ro.* FROM repair_orders ro WHERE ro.status PENDING ORDER BY ro.create_time DESC LIMIT 20 OFFSET 0;维修统计报表-- 使用物化视图提高统计查询性能 CREATE TABLE mv_repair_stats ( department_id bigint NOT NULL, year_month varchar(7) NOT NULL, total_count int NOT NULL, avg_duration decimal(10,2) NOT NULL, avg_rating decimal(3,2) NOT NULL, PRIMARY KEY (department_id, year_month) ) ENGINEInnoDB; -- 定期刷新物化视图 REPLACE INTO mv_repair_stats SELECT d.department_id, DATE_FORMAT(ro.create_time, %Y-%m) AS year_month, COUNT(*) AS total_count, AVG(TIMESTAMPDIFF(HOUR, ro.create_time, ro.actual_finish_time)) AS avg_duration, AVG(ro.rating) AS avg_rating FROM repair_orders ro JOIN devices d ON ro.device_id d.id WHERE ro.status COMPLETED GROUP BY d.department_id, DATE_FORMAT(ro.create_time, %Y-%m);5. 系统部署与性能调优5.1 容器化部署方案使用Docker Compose编排服务version: 3.8 services: mysql: image: mysql:8.0 container_name: repair-mysql environment: MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} MYSQL_DATABASE: repair_system MYSQL_USER: ${DB_USER} MYSQL_PASSWORD: ${DB_PASSWORD} volumes: - mysql_data:/var/lib/mysql ports: - 3306:3306 networks: - repair-network redis: image: redis:6.2 container_name: repair-redis ports: - 6379:6379 networks: - repair-network backend: build: context: ./backend dockerfile: Dockerfile container_name: repair-backend depends_on: - mysql - redis environment: SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/repair_system SPRING_DATASOURCE_USERNAME: ${DB_USER} SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD} SPRING_REDIS_HOST: redis ports: - 8080:8080 networks: - repair-network node: build: context: ./node dockerfile: Dockerfile container_name: repair-node depends_on: - backend environment: BACKEND_URL: http://backend:8080 ports: - 3000:3000 networks: - repair-network frontend: build: context: ./frontend dockerfile: Dockerfile container_name: repair-frontend ports: - 80:80 networks: - repair-network volumes: mysql_data: networks: repair-network: driver: bridge5.2 性能调优策略SpringBoot调优调整JVM参数java -jar -Xms512m -Xmx1024m -XX:MaxMetaspaceSize256m backend.jar启用GZIP压缩# application.properties server.compression.enabledtrue server.compression.mime-typestext/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json server.compression.min-response-size1024配置连接池spring.datasource.hikari.maximum-pool-size20 spring.datasource.hikari.minimum-idle5 spring.datasource.hikari.idle-timeout30000 spring.datasource.hikari.connection-timeout30000Node.js调优使用cluster模块充分利用多核CPUconst cluster require(cluster); const numCPUs require(os).cpus().length; if (cluster.isMaster) { for (let i 0; i numCPUs; i) { cluster.fork(); } } else { require(./app); }启用HTTP/2const http2 require(http2); const fs require(fs); const server http2.createSecureServer({ key: fs.readFileSync(server.key), cert: fs.readFileSync(server.crt) }); server.on(stream, (stream, headers) { stream.respond({ content-type: application/json, :status: 200 }); stream.end(JSON.stringify({ message: Hello HTTP/2! })); }); server.listen(3000);Vue前端优化路由懒加载const routes [ { path: /repair, component: () import(./views/Repair.vue) } ];启用Gzip压缩vue.config.jsconst CompressionPlugin require(compression-webpack-plugin); module.exports { configureWebpack: { plugins: [ new CompressionPlugin({ algorithm: gzip, test: /\.(js|css|html|svg)$/, threshold: 10240, minRatio: 0.8 }) ] } };6. 实际开发中的经验与坑点6.1 跨域问题解决方案开发过程中遇到的主要跨域问题及解决方案开发环境跨域// vue.config.js module.exports { devServer: { proxy: { /api: { target: http://localhost:3000, changeOrigin: true, pathRewrite: { ^/api: } } } } }生产环境跨域// SpringBoot配置CORS Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .exposedHeaders(Authorization) .maxAge(3600); } }6.2 文件上传优化处理文件上传时的注意事项前端分片上传// Vue组件中实现分片上传 async uploadFile(file) { const chunkSize 2 * 1024 * 1024; // 2MB const chunks Math.ceil(file.size / chunkSize); for (let i 0; i chunks; i) { const start i * chunkSize; const end Math.min(file.size, start chunkSize); const chunk file.slice(start, end); const formData new FormData(); formData.append(file, chunk); formData.append(chunkIndex, i); formData.append(totalChunks, chunks); formData.append(fileId, this.fileId); await axios.post(/api/upload/chunk, formData, { headers: { Content-Type: multipart/form-data } }); } // 通知服务器合并分片 await axios.post(/api/upload/merge, { fileId: this.fileId, fileName: file.name, totalChunks: chunks }); }后端处理大文件// SpringBoot处理分片上传 PostMapping(/upload/chunk) public ResponseEntity? uploadChunk( RequestParam(file) MultipartFile file, RequestParam(chunkIndex) int chunkIndex, RequestParam(totalChunks) int totalChunks, RequestParam(fileId) String fileId) { String tempDir System.getProperty(java.io.tmpdir) /uploads/ fileId; File dir new File(tempDir); if (!dir.exists()) { dir.mkdirs(); } File chunkFile new File(dir, chunkIndex .part); try { file.transferTo(chunkFile); return ResponseEntity.ok().build(); } catch (IOException e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } }6.3 性能监控与日志收集系统上线后的监控方案SpringBoot Actuator集成# application.properties management.endpoints.web.exposure.includehealth,info,metrics,prometheus management.metrics.export.prometheus.enabledtrue management.endpoint.health.show-detailsalwaysNode.js性能监控const promClient require(prom-client); const collectDefaultMetrics promClient.collectDefaultMetrics; // 收集默认指标 collectDefaultMetrics({ timeout: 5000 }); // 自定义指标 const httpRequestDurationMicroseconds new promClient.Histogram({ name: http_request_duration_ms, help: Duration of HTTP requests in ms, labelNames: [method, route, code], buckets: [0.1, 5, 15, 50, 100, 300, 500, 1000, 3000, 5000] }); // 中间件记录请求时间 app.use((req, res, next) { const end httpRequestDurationMicroseconds.startTimer(); res.on(finish, () { end({ method: req.method, route: req.route.path, code: res.statusCode }); }); next(); }); // 暴露指标端点 app.get(/metrics, async (req, res) { res.set(Content-Type, promClient.register.contentType); res.end(await promClient.register.metrics()); });前端性能监控// 使用web-vitals库监控前端性能 import { getCLS, getFID, getLCP } from web-vitals; function sendToAnalytics(metric) { const body JSON.stringify(metric); navigator.sendBeacon(/api/analytics, body); } getCLS(sendToAnalytics); getFID(sendToAnalytics); getLCP(sendToAnalytics);7. 系统扩展与未来优化方向7.1 移动端适配方案响应式设计/* 使用Flex和Grid布局实现响应式 */ .repair-card { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 1rem; } media (max-width: 768px) { .repair-card { grid-template-columns: 1fr; } }PWA支持// vue.config.js module.exports { pwa: { name: 校园报修系统, themeColor: #409EFF, msTileColor: #2d89ef, appleMobileWebAppCapable: yes, appleMobileWebAppStatusBarStyle: black, workboxPluginMode: InjectManifest, workboxOptions: { swSrc: ./src/service-worker.js, exclude: [/\.map$/, /_redirects/] } } }7.2 智能分配算法优化未来可实现的智能分配功能基于维修人员技能和位置的分配public class MaintainerAllocator { public Maintainer allocateMaintainer(RepairOrder order) { ListMaintainer candidates maintainerRepository.findBySkillsContaining(order.getFaultType()); return candidates.stream() .min(Comparator.comparing(m - calculateDistance(m.getLocation(), order.getDevice().getLocation()) )) .orElse(null); } private double calculateDistance(Location loc1, Location loc2) { // 实现距离计算逻辑 } }维修时间预测模型# 使用Python构建预测模型可集成到SpringBoot中 from sklearn.ensemble import RandomForestRegressor import pandas as pd import joblib # 加载历史数据 data pd.read_csv(repair_history.csv) # 特征工程 features data[[fault_type, device_type, priority, maintainer_exp]] target data[repair_duration] # 训练模型 model RandomForestRegressor(n_estimators100) model.fit(features, target) # 保存模型 joblib.dump(model, repair_time_predictor.joblib)7.3 数据分析与可视化使用ECharts实现维修数据可视化template div refchart stylewidth: 100%; height: 400px;/div /template script import * as echarts from echarts; export default { mounted() { this.initChart(); }, methods: { async initChart() { const response await this.$http.get(/api/repair/stats); const chart echarts.init(this.$refs.chart); const option { tooltip: { trigger: axis }, legend: { data: [报修量, 平均修复时间(h)] }, xAxis: { type: category, data: response.data.months }, yAxis: [ { type: value, name: 报修量 }, { type: value, name: 平均修复时间(h) } ], series: [ { name: 报修量, type: bar, data: response.data.counts }, { name: 平均修复时间(h), type: line, yAxisIndex: 1, data: response.data.durations } ] }; chart.setOption(option); } } } /script设备故障预测// 使用Java集成Python模型进行预测 public class FaultPredictor { public double predictFailureProbability(Device device) { ProcessBuilder pb new ProcessBuilder(python, predict.py, device.getType(), String.valueOf(device.getAge()), String.valueOf(device.getUsageHours())); try { Process p pb.start(); BufferedReader reader new BufferedReader( new InputStreamReader(p.getInputStream())); String line reader.readLine(); return Double.parseDouble(line); } catch (IOException e) { throw new RuntimeException(预测失败, e); } } }在实际开发过程中我们发现系统的响应速度和用户体验是决定项目成败的关键因素。通过引入Node.js中间层我们成功将前后端的耦合度降到最低同时利用其异步I/O特性提高了系统的并发处理能力。SpringBoot提供了稳定的核心业务服务而Vue的响应式特性则大大提升了前端开发效率和用户体验。
返回列表