ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

大型商场应急预案管理系统:SpringBoot+Vue毕设实战指南

大型商场应急预案管理系统:SpringBoot+Vue毕设实战指南 简介这套大型商场应急预案管理系统源码是一份面向计算机专业毕业设计的完整项目采用Java语言及Spring Boot框架构建后端Vue作为前端展示MySQL作为数据存储整体为B/S模式。系统支持应急预案的维护、查询与执行管理通过模块化设计使商场应急工作从信息录入到处置反馈形成闭环帮助管理人员从繁琐的手工台账中解放出来实现无纸化办公显著提升应急管理效率。资源包共388个文件压缩后约9.25MB典型文件类型包括98个Java源文件、40个Vue组件文件、16个JavaScript文件、13个XML配置、SQL脚本、启动脚本.bat及项目说明文档等。其中Java文件承载核心业务逻辑Vue文件负责前端交互界面配置文件与SQL脚本便于环境部署和数据库初始化。当前已有97人学习浏览。包内附有说明文档与LW文档可指导读者理解源码结构、运行环境配置与二次开发思路适合作为毕业设计或课程设计的完整参考方案。1. 大型商场应急预案管理系统毕设选型到底在选什么很多Java初学者在选题时纠结的是“做什么系统”实际纠结的却是“用什么框架把系统做出来”。大型商场应急预案管理系统之所以常年被当成毕设题目恰好因为它是一个典型的SpringBootVueMySQL三件套载体商场按楼层和区域拆空间预案按类型和等级拆规则事件上报后有从待处置到归档的状态闭环处置记录还要按时间倒序查台账。这套业务不大不小刚好覆盖建表关联、条件分页、状态流转和角色权限做完后既能交毕设也能当成Java面试题练习册来复盘。适合想系统校一遍后端基础和Vue前后端联调的在校生也适合想拿完整项目补简历的初级Java工程师。2. 系统骨架六张核心表与后端项目的初始化先把系统拆开看这套应急预案管理系统最少要有六张表才能把流程讲圆区域表管“哪里发生了事”预案表管“这事按什么规则处理”预案步骤表管“规则具体分几步、每个步骤由谁做”事件上报表管“当前正在处理的突发事件”事件处理日志表管“每一步是谁在什么时间做了什么事”再加一张用户表区分管理员、预案编制人员和部门处置人员。2.1 从业务场景拆出数据模型表设计决定了后面写得痛不痛快先说区域表。大型商场通常不是一整块它有B1、B2停车场一楼奢侈品区二楼餐饮区三楼影院区。同一个火灾预案餐饮区因为用火用电多执行步骤和疏散通道跟影院区不一样所以预案必须绑定区域。如果图省事把区域字段直接塞在预案表里后面做“按楼层统计预案数量”还好做“某区域当前生效预案”就要循环处理SQL怎么写都别扭。事件表也有讲究。事件上报后处置是一个过程刚上报是待处置值班长接单后变处置中处置完成后需要部门负责人确认最后归档。这个状态不放在事件表主字段上加一个handle_status字段控制在0到4之间比用时间戳去推断状态靠谱得多因为可能出现“处置完成但忘了确认”这种边界情况。预案和预案步骤坚决分开。一张预案对应多条步骤每条步骤有step_no排序、执行部门、动作描述、预计用时和是否需要值班长确认。如果压缩成一张表存JSON数组CRUD是省事了答辩时被问“你要做部门维度统计怎么查”就会卡住拆分后一条GROUP BY就解决。2.2 建表SQL与字段参数预案、步骤、事件三张主表直接可抄下面是一套常见的建表SQL字段命名统一用下划线风格和MyBatis-Plus的驼峰映射默认配置正好对上。-- 商场区域表 CREATE TABLE mall_area ( id BIGINT PRIMARY KEY AUTO_INCREMENT, mall_name VARCHAR(50) NOT NULL COMMENT 商场名称, floor_no VARCHAR(20) NOT NULL COMMENT 楼层编号如 B1/F3, zone_name VARCHAR(50) COMMENT 区域名称如 餐饮区, manager VARCHAR(30) COMMENT 区域安全负责人, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT 商场区域表;区域表相对简单说明一个字段就够了manager冗余了负责人姓名而不是存用户ID是为了减少联表查询。如果区域负责人可能在用户表里不存在外键约束反而会成为导入数据的障碍。-- 预案主表 CREATE TABLE emergency_plan ( id BIGINT PRIMARY KEY AUTO_INCREMENT, plan_name VARCHAR(100) NOT NULL COMMENT 预案名称, plan_type VARCHAR(30) NOT NULL COMMENT 火灾/疏散/设备故障/治安事件, area_id BIGINT NOT NULL COMMENT 适用区域ID, severity_level VARCHAR(10) COMMENT I级/II级/III级, status TINYINT DEFAULT 0 COMMENT 0草稿 1已发布 2已失效, version VARCHAR(20) COMMENT 版本号如 V1.2, publish_time DATETIME COMMENT 发布时间, create_by BIGINT COMMENT 创建人ID, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_type_status (plan_type, status), KEY idx_area (area_id) ) COMMENT 应急预案主表;plan_type用VARCHAR而不是枚举原因是后续可能加新类型改动枚举要生成新迁移脚本用VARCHAR加上代码里校验反而灵活。status用TINYINT0、1、2三个取值直接对应草稿、发布、失效。复合索引idx_type_status很关键因为首页列表最常见筛选条件就是“按预案类型看已发布预案”没有这个索引表数据过万后查询会慢得明显。-- 预案步骤子表 CREATE TABLE plan_step ( id BIGINT PRIMARY KEY AUTO_INCREMENT, plan_id BIGINT NOT NULL COMMENT 预案ID, step_no INT NOT NULL COMMENT 步骤顺序从1开始, dept_name VARCHAR(50) NOT NULL COMMENT 执行部门名, action_content TEXT NOT NULL COMMENT 具体动作描述, expect_minutes INT DEFAULT 10 COMMENT 预计耗时分钟, need_confirm TINYINT DEFAULT 0 COMMENT 0无需确认 1需值班长确认, KEY idx_plan_id (plan_id) ) COMMENT 预案步骤子表;-- 事件上报表 CREATE TABLE emergency_event ( id BIGINT PRIMARY KEY AUTO_INCREMENT, plan_id BIGINT COMMENT 关联预案ID, area_id BIGINT NOT NULL, event_title VARCHAR(100) NOT NULL, severity VARCHAR(10) COMMENT 一般/较大/重大, handle_status TINYINT DEFAULT 0 COMMENT 0待处置 1处置中 2待确认 3已归档, reporter VARCHAR(30) COMMENT 上报人, report_time DATETIME DEFAULT CURRENT_TIMESTAMP, finish_time DATETIME COMMENT 归档时间 ) COMMENT 事件上报表;事件表里plan_id设计成可空因为实际商场运营中可能发生预案覆盖不到的情况此时只记录事件内容等值班长人工选预案补充关联。如果做成非空现场录入时想临时先进数据就会被迫编一个预案ID这种体验非常糟糕。2.3 后端初始化与关键配置SpringBoot版本和MyBatis-Plus的取舍后端项目创建时推荐用SpringBoot 2.7.x而不是3.x。不是3.x不好而是MyBatis-Plus的spring-boot-starter在3.x初期版本存在兼容问题网上搜“springboot版本太高”出现的帖子一大半都是在报Mapper注入失败。如果你是第一次搭环境2.7.x加MyBatis-Plus 3.5.x组合最省心等你熟练了再升3.x不迟。!-- pom.xml 核心依赖 -- parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.5/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependenciesspring-boot-starter-validation用于参数校验比如预案名称非空、事件严重等级必须在集合内。Lombok能省掉一堆getter和setter毕业设计 код量能少写很多答辩时也能说用了Lombok简化样板代码。# application.yml server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mall_emergency?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0连接串里serverTimezoneAsia/Shanghai不写的话MySQL 8以上驱动会拿默认时区去解析DATETIME你本地存的八点整查出来变成凌晨零点。Jackson的date-format统一了前后端时间的传输格式不然前端拿到的可能是时间戳数组。map-underscore-to-camel-casetrue可以让mall_area自动映射成mallArea这是MyBatis-Plus的默认值写出来是为了排查时确认配置没被改掉。如果表里加了deleted字段做逻辑删除logic-delete-field这里要配置。毕设阶段建议用物理删除简化逻辑真要做逻辑删除也只对预案主表做事件表必须保留全量流水不应删除。3. 后端落地预案CRUD与应急事件的Service状态流转后端最见功力的不是Controller层而是Service层的状态流转。暴力写法是把状态判断全塞进Controller接口一多就失控。我一般会拆三层Controller只做参数接收和权限注解标记Service负责业务判断和数据组装Mapper层就是最基础的增删改查加自定义SQL。3.1 Controller层分页查询与条件筛选的接口设计预案列表页最常见的筛选组合是“条件分页”按预案名称模糊搜、按plan_type精确过滤、按status过滤外加area_id下拉框。给出一个带接口参数讲解的写法。RestController RequestMapping(/api/plan) public class PlanController { Resource private PlanService planService; GetMapping(/page) public Result? page(RequestParam(defaultValue 1) Integer current, RequestParam(defaultValue 10) Integer size, RequestParam(required false) String planName, RequestParam(required false) String planType, RequestParam(required false) Integer status) { PageEmergencyPlanVO page planService.queryPlanPage(current, size, planName, planType, status); return Result.ok(page); } }current和size都给了默认值前端缺参时不会直接NPE。planName用String接收再在Service里拼模糊查询而不是在Controller拼SQL片段这样SQL对Controller完全不可见。返回值包一层Result是通用做法code、message、data三个字段前端统一按这个结构处理比直接裸返回Page要规范。3.2 Service层事件上报到处置闭环的状态机怎么写状态流转部分是一段值得认真写的逻辑。典型的生命周期是待处置 - 处置中 - 待确认 - 已归档其中禁止待处置直接跳已归档。如果是第一次写容易把状态流转散落在各个if里后面加需求会越改越乱。Service public class EmergencyEventServiceImpl implements EmergencyEventService { private static final MapInteger, ListInteger TRANSITIONS new HashMap(); static { TRANSITIONS.put(0, Collections.singletonList(1)); TRANSITIONS.put(1, Arrays.asList(2, 3)); TRANSITIONS.put(2, Collections.singletonList(3)); TRANSITIONS.put(3, Collections.emptyList()); } Override Transactional(rollbackFor Exception.class) public void changeStatus(Long eventId, Integer targetStatus, Long operatorId) { EmergencyEvent event this.getById(eventId); if (event null) { throw new BizException(事件不存在); } ListInteger allowed TRANSITIONS.get(event.getHandleStatus()); if (allowed null || !allowed.contains(targetStatus)) { throw new BizException(非法状态流转: event.getHandleStatus() - targetStatus); } event.setHandleStatus(targetStatus); if (targetStatus 3) { event.setFinishTime(LocalDateTime.now()); } this.updateById(event); // 写处置日志 EventHandleLog log new EventHandleLog(); log.setEventId(eventId); log.setOperatorId(operatorId); log.setFromStatus(event.getHandleStatus()); log.setToStatus(targetStatus); log.setOperateTime(LocalDateTime.now()); logMapper.insert(log); } }TRANSITIONS是一个有向图key是当前状态value是允许跳转到的目标状态集合。待处置只有1一个出口处置中能跳待确认或归档待确认只能归档归档后没有出口。这套写法的好处是后续新增“驳回”状态时只需要改这个Map加一条2到0的映射不用去翻if else。用Transactional包住状态更新和日志插入避免出现状态改了但日志没插入的数据不一致。rollbackFor设为Exception.class捕获所有异常就回滚。写日志的时机要放在状态更新之后因为日志里需要最新的handleStatus作为fromStatus先插日志再改状态日志里的源状态就会是旧的。3.3 角色权限校验一个注解控制管理员和部门用户毕设里不需要引入完整的Spring Security自研一个拦截器加自定义注解就够用。常见做法是这样定义一个RequireRole注解拦截器通过HandlerMethod读取方法上的注解比对当前登录用户的角色。Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface RequireRole { String[] value() default {}; }Component public class RoleInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { if (!(handler instanceof HandlerMethod)) { return true; } RequireRole requireRole ((HandlerMethod) handler).getMethodAnnotation(RequireRole.class); if (requireRole null) { return true; } // 从request attribute中取登录用户 Integer role (Integer) request.getAttribute(loginUserRole); for (String allow : requireRole.value()) { if (allow.equals(String.valueOf(role))) { return true; } } throw new NoPermissionException(当前角色无权执行此操作); } }登录用户的身份在登录接口里写到request attribute而不是ThreadLocal是为了避免线程池环境下ThreadLocal数据串包。拦截器只校验角色标识真正的用户数据查询放在登录拦截器里统一处理。注意这个方案能挡住非法的跨角色调用但挡不住接口被绕过登录直接访问所以WebMvcConfigurer里还要注册LoginInterceptor对所有/api路径生效。Configuration public class WebMvcConfig implements WebMvcConfigurer { Resource private RoleInterceptor roleInterceptor; Resource private LoginInterceptor loginInterceptor; Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .addPathPatterns(/api/**) .excludePathPatterns(/api/auth/login); } }注意无论Controller上写没写RequireRole登录拦截器对所有/api/**接口都会生效除了登录接口自己。这样就算漏写角色注解起码不会裸奔到放行匿名访问。4. 前端联调Vue3Element Plus从路由到页面打包前端在毕设项目里的角色不是“好看的壳”而是要完整承接后端接口的数据展示和交互。我推荐直接用Vue3加Element Plus加Pinia这套组合创建工程时用npm create vite比Vue CLI更快而且Vite的热更新对大项目也更友好。4.1 前端工程结构、路由守卫与Axios封装工程目录大致如下src下按api、router、views、store、utils分模块。api目录里每个文件对应一类后端接口views目录里一个文件夹对应一个页面模块这样可以保证多人协作时风格统一。// src/utils/request.js import axios from axios const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer token } return config }) request.interceptors.response.use( response response.data, error { if (error.response error.response.status 401) { window.location.href /login } return Promise.reject(error) } ) export default requestbaseURL写/api而不是完整地址目的是让开发环境代理和后端部署都走相对路径。响应拦截器直接返回response.data这样页面里拿到的就是后端Result对象不需要每次写两层点。401跳登录页是拦截器里必须做的因为token过期时接口会返回401没有统一跳转的话用户会以为按钮坏了。// src/router/index.js import { createRouter, createWebHashHistory } from vue-router const routes [ { path: /login, component: () import(/views/Login.vue) }, { path: /, component: () import(/layout/Layout.vue), redirect: /plan/list, children: [ { path: plan/list, component: () import(/views/plan/PlanList.vue) }, { path: event/list, component: () import(/views/event/EventList.vue) } ] } ] const router createRouter({ history: createWebHashHistory(), routes }) router.beforeEach((to, from, next) { if (to.path ! /login !localStorage.getItem(token)) { next(/login) } else { next() } }) export default routercreateWebHashHistory是毕设稳妥选择。用createWebHistory虽然URL更干净但打包进SpringBoot的static目录后你直接访问/plan/list会触发后端404还必须配置转发规则。用Hash模式刷新和直接访问都不会出问题省掉一个最常见的部署踩坑。4.2 预案管理页面表格、弹窗表单与状态切换列表页直接用Element Plus的el-table加el-dialog模板不做过度封装。核心交互是三件事条件查询、新增编辑预案、切换发布状态。template el-card el-form :inlinetrue :modelqueryForm el-form-item label预案名称 el-input v-modelqueryForm.planName placeholder输入预案名称 clearable / /el-form-item el-form-item label预案类型 el-select v-modelqueryForm.planType clearable el-option label火灾 valuefire / el-option label疏散 valueevacuation / el-option label设备故障 valueequipment / /el-select /el-form-item el-form-item el-button typeprimary clickhandleSearch查询/el-button /el-form-item /el-form el-table :datatableData v-loadingloading el-table-column propplanName label预案名称 min-width180 / el-table-column propplanType label类型 width100 / el-table-column propstatus label状态 width100 template #default{ row } el-tag :typerow.status 1 ? success : info {{ row.status 1 ? 已发布 : 草稿 }} /el-tag /template /el-table-column el-table-column label操作 width180 template #default{ row } el-button link typeprimary clickhandleEdit(row)编辑/el-button el-button link typewarning clickhandlePublish(row)发布/el-button /template /el-table-column /el-table el-pagination v-model:current-pagequeryForm.current v-model:page-sizequeryForm.size :totaltotal :page-sizes[10, 20, 50] layouttotal, sizes, prev, pager, next size-changeloadList current-changeloadList / /el-card /template关键点在于分页组件用了v-model:current-page和v-model:page-size双向绑定切换页码或每页条数时能自动把最新值提交给后端。如果你用单向绑定经常会出现一个隐患翻到第三页后改了筛选条件current还停留在3结果后端返回第一页数据。查询按钮里在重新请求前必须手动把current重置为1。4.3 联调代理与前端打包放进SpringBoot静态目录开发阶段前后端端口不同直接用后端8080时会遇到跨域问题。最省事的做法是打开Vite代理把/api前缀请求转发到本机8080避免在SpringBoot里写CorsFilter。// vite.config.js export default { server: { port: 3000, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }, build: { outDir: ../src/main/resources/static, assetsDir: static } }outDir直接指到SpringBoot的static目录这一步就回答了“vue打包放进springboot中”这个常见问题。执行npm run build后dist产物自动生成在后端resources/static下后端打成jar就能直接访问页面线上环境根本不存在跨域问题。需要注意如果后端用了RestController返回JSON同时前端静态资源也在同一个tomcat里后端接口的访问顺序要留意缓存问题。确认一下application.yml中没有单独配置mvc静态资源路径保持默认即可。开发环境里Vite的proxy与后端没关系生产环境里static目录就是前端两者路径互补。5. 毕设避坑指南从分页total到LW附件路径的五个翻车现场毕设答辩翻车往往不是系统逻辑跑不通而是边界情况没处理。下面这五类坑找工作时它们对应的是“java工程师最容易被考的故障排查题”在系统里是实打实能复现的故障。5.1 分页total永远不变或始终等于当前页条数现象列表翻到第三页底部total依旧显示第一页返回的10条。排查后端日志发现每页都返回了正确数据甚至total查出来没错但前端表格只渲染了当前页。原因MyBatis-Plus的Page对象在序列化时total字段的JSON名称是total没错但泛型对象如果是自定义VOProcessed数据时可能漏了total的setter。还有一种常见情况是后端查询时new Page(current, size)后直接把查询结果List塞进Record里却忘了把page.getTotal()赋值给返回对象的总数字段。解决统一用一个PageResult泛型对象构造时强制把page.getTotal()和page.getRecords()映射进去。打印后端返回值检查JSON里是否有total字段。记得给页码参数加校验current超过总页数时要重置为1。5.2 MySQL连接串不写时区时间全部差了八小时现象本地开发以为没问题部署到服务器后所有事件上报时间的“时分秒”都比真实时间晚八小时。有人第一反应改代码其实数据库查出来的时间就是错的。原因MySQL 8驱动默认基于服务器时区解析DATETIME如果连接串没指定serverTimezone服务器设置成UTC你在Asia/Shanghai时区读取出来当然偏移。解决连接串里固定写serverTimezoneAsia/Shanghaipom里mysql-connector-j版本要和MySQL服务器大版本匹配。改完清掉MyBatis-Plus的二级缓存再验证因为缓存里存的可能是旧时间。排查时直接先用Navicat查一遍表数据如果Navicat查出来正确那就是后端转换问题别一上来就改数据库时区配置。5.3 说明文档和LW附件上传后重启即丢现象上传预案附件或说明文档后文件显示成功服务一重启文件就404。打开文件存储目录看文件确实存在但后端用的绝对路径变了或者文件根本就没写到固定目录而是写进了临时目录。原因常见的写法是把文件路径存在数据库文件本体放在项目的临时目录例如/user/tomcat/temp里。服务器重启后临时目录被清理索引指向了不存在的路径。解决不要依赖项目的运行目录。在application.yml里配置一个外置file.upload-dir指向服务器固定目录如/opt/mall-uploadSpringBoot用Value读这个路径来创建文件。文件落盘后数据库存相对路径访问时通过一个FileController的映射接口拼接磁盘真实路径返回。数据库里永远不要存带有机器信息的绝对路径。5.4 前端隐藏了按钮后端接口照样能调现象页面里用v-if把“发布预案”按钮按角色隐藏了以为这样就是权限控制。答辩现场评委直接用Postman调发布接口状态照样被改成已发布当场翻车。原因前端隐藏按钮只是交互层策略后端接口完全没有权限校验。这种“越权”在java面试题里很常考属于典型的“缺少服务端鉴权”。解决后端方法上加RequireRole(ADMIN)角色拦截器在进入Controller前就拦截。注意拦截器要放在业务逻辑之前并且要对整个/api路径生效。只看代码写完不算完用Postman或curl手动构造一个非管理员用户调接口的请求确认返回的是403而不是业务数据。5.5 SpringBoot版本太高MyBatis-Plus Mapper注入失败现象新建项目选了SpringBoot 3.2启动时提示Mapper无法注入或循环依赖启动失败。搜“springboot版本太高”能看到大量同样问题的同学。原因SpringBoot 3.x基于Jakarta EE规范包名从javax迁移到jakartaMyBatis-Plus官方旧版starter只支持javax。问题出在包兼容而不在代码逻辑。解决主力开发环境用SpringBoot 2.7.18这是2.x的最终版本稳定性和MyBatis-Plus兼容性都验证过。若坚持3.x必须使用mybatis-plus-spring-boot3-starter并注意内置的分页插件配置方式也变了。判断这类兼容问题要看的不是报错头两行而是堆栈里第一个NoClassDefFoundError指向哪个包顺着包名找对应starter版本。6. 给答辩增色的三个进阶点演练闭环、缓存提速与数据验证如果基础功能全部完成还有富余时间优先做这三个方向每一个都能在答辩时展示“业务思维”。6.1 从预案到演练任务补齐业务闭环多数毕设做到预案CRUD和事件处置就停了业务上是残缺的——预案不发散、不演练、不复盘存储的只是一堆静态文档。我一般建议加一张演练任务表包含plan_id、预计演练时间、参与部门、演练结论、问题清单五个字段。预案发布的同时生成一条演练任务处置人员按步骤执行完毕后提交结论系统再自动把问题清单回写预案的版本更新说明里。这样预案不是面条演练数据也能反向证明预案可用性。6.2 Redis缓存预案列表给查询加一个提速点如果项目里引入了Redis可以在预案分页查询上加一层缓存。常见做法是把“plan_type status area_id”拼成缓存key命中缓存直接返回未命中查询数据库后写入缓存并给一个5分钟过期时间。注意发布接口要主动删除相关key否则会出现发布成功后前台依然是旧列表的“假延迟”。6.3 验证方法造一套真实数据走通完整流程答辩前务必准备一套能讲出逻辑的测试数据B1停车场设备故障、F3影院区治安事件、F2餐饮区火灾三件事对应三个不同等级的预案。测试路径要手动走完管理员登录创建并发布预案部门用户登录上报事件值班长接单转处置中处置完成后归档。全程截图存成文档和说明文档、LW放在一起答辩时按这条路径演示比临时开前端乱点要有说服力得多。我自己做毕设那会儿最后一天才发现分页total字段返回类型不对前端分页器永远显示不到真实总数。那时候深刻体会到写系统不能只追求页面能跑接口返回结构、边界参数和部署环境每个环节都要提前验证。希望这篇笔记能帮你把这套应急预案管理系统做得比自己预期更稳。本文还有配套的精品资源点击获取
返回列表