ARTICLE DETAIL

资讯详情

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

基于Spring Boot和微信小程序的灾情救助系统设计与实现

基于Spring Boot和微信小程序的灾情救助系统设计与实现 简介基于微信小程序与 Java 后端的灾情救助系统毕业设计项目包面向高校毕业设计、课程设计与项目实战练习。系统分管理员与会员两类角色管理员可维护会员、发布灾情公告与救助政策、管理论坛咨询及轮播图会员可查看灾情视频公告、在线申请救助并上传材料、发布求助信息。包内共 1250 个文件压缩包约 124.54MB主要文件类型涵盖 png 图片资源、js/vue/java 前后端代码、json 配置、wxss/wxml 小程序样式与页面结构并附带 sql 数据库脚本、mp4 演示录像、docx 说明文档和 bat 快捷运行脚本。已有 224 人学习/下载。借助完整源码、数据库及演示录像可系统理解小程序端与 Java 后端、MySQL 数据库的交互逻辑掌握救助申请、资讯发布、后台管理等模块的设计思路便于二次开发和论文撰写。1. 灾情救助系统的技术选型逻辑与适用边界灾害发生后的最初几个小时求助信息往往散落在微信群里接龙消息被新信息冲掉电话热线占线纸质登记表无法同步给后台审核人员。把灾民的救助申请、材料提交、管理员审核、志愿者响应放进同一个闭环需要的不只是一张在线表单而是一套能承载角色权限、状态流转和数据留痕的系统。基于微信小程序加 Java 后端的灾情救助系统解决的就是这个问题小程序端提供灾民侧的轻量入口Java 后端承载数据校验、审核流转和管理端操作MySQL 把申请记录、公告内容和用户信息固化成可追溯的结构化数据。这套系统的典型场景是计算机类毕业设计和数据库课程设计也可以作为应急管理信息化改造的参考原型。前后端分离的接口对接、状态字段的流转控制、文件上传的存储策略都能在源码里找到对应的实现位置。后面按数据建模、后端接口、小程序端、验证改造四层推进。2. MySQL核心表结构灾民信息到救助申请的数据建模2.1 核心表职责划分与关联关系系统里「会员」对应的就是灾区群众也是灾民。管理员在后台查看会员的基本信息、地址、手机和邮箱时实际读取的就是 member 表。字段设计时要把「身份信息」和「联系方式」分开因为救助申请的材料审核引用的是身份部分求助信息的分发依赖的是联系方式部分两类字段的更新频率和敏感级别不同。业务主表是 member救助申请表 relief_apply 和求助信息表 help_info 都通过 user_id 逻辑关联到它。admin_user 管理员表独立存在不参与业务外键因为管理员账号是初始化写入的与灾民注册没有业务交集。轮播图表 carousel 和灾情公告表 disaster_notice 属于内容类表由管理员在后台维护小程序端只读。核心表的职责边界如下表名业务职责核心字段member灾民注册信息与联系方式real_name, phone, address, id_cardadmin_user管理员账号与角色username, password, rolerelief_apply救助申请与审核材料user_id, apply_type, statushelp_info求助信息与响应状态user_id, content, statusdisaster_notice灾情公告与自救政策title, content, create_timecarousel首页轮播图配置image_url, sort_order, status外键约束在课程设计阶段建议保留但要注意一个边界当管理员删除某个灾民账号时救助申请记录不能被级联删除否则审核留痕就断了。常见做法是表结构里保留 user_id 字段但不建物理外键删除前先检查该用户是否已有申请记录有记录就把账号标记为禁用而不是物理删除。拿到压缩包后先执行 sql 目录下的建表脚本再改后端 application.yml 里的数据库连接和密码最后用微信开发者工具导入小程序端源码才能保证三端跑的是同一套数据。2.2 救助申请表的状态机字段与审核留痕救助申请表是系统里状态流转最复杂的表。灾民提交申请、管理员审核、灾民查看结果每一步都要留下记录。字段设计上状态用 TINYINT 存状态码而不是字符串原因有两个一是列表页按状态过滤时整数索引扫描更快二是避免中文状态在不同客户端出现编码差异导致查询匹配不上。CREATE TABLE relief_apply ( apply_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 申请ID, user_id INT NOT NULL COMMENT 灾民ID关联member表, apply_type VARCHAR(32) NOT NULL DEFAULT 物资 COMMENT 救助类型物资/安置/医疗, materials_url VARCHAR(512) DEFAULT NULL COMMENT 申请材料附件地址, status TINYINT NOT NULL DEFAULT 0 COMMENT 状态0待审核1通过2驳回, audit_remark VARCHAR(255) DEFAULT NULL COMMENT 审核意见, audit_admin_id INT DEFAULT NULL COMMENT 审核管理员ID, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 提交时间, audit_time DATETIME DEFAULT NULL COMMENT 审核时间, INDEX idx_user (user_id), INDEX idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT救助申请表;status 的注释里已经把状态值定死后续所有代码都按 0、1、2 三个数字判断不允许在业务代码里出现三个数字之外的取值。audit_remark 和 audit_time 就是审核留痕缺了这两个字段灾民被驳回时不知道原因管理员也无法追踪是谁在什么时间做的审核。课程设计的答辩环节这两列的设计意图基本必问。materials_url 只存文件路径不存文件内容。小程序端上传材料时后端先接收 MultipartFile 保存到本地 upload 目录或者对象存储再把访问地址返回给前端前端随申请表单一起提交后端写入这条记录。这么做让表体积可控以后要切换 MinIO 或 OSS 也不用改表结构。2.3 求助信息表与轮播图的初始化数据要点求助信息表虽然和救助申请表同属灾民操作状态语义却完全不同。求助信息发布后进入公开列表其他用户看到后响应并联系发布者状态从「待响应」变成「已响应」发布者确认得到帮助后变成「已解决」。这里要特别注意联系方式保护列表页接口返回的 phone 应该是脱敏后的完整号码只能通过详情接口按需获取。CREATE TABLE help_info ( help_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 求助ID, user_id INT NOT NULL COMMENT 发布者ID, title VARCHAR(100) NOT NULL COMMENT 求助标题, content VARCHAR(1000) NOT NULL COMMENT 求助内容, phone VARCHAR(20) NOT NULL COMMENT 联系电话, status TINYINT DEFAULT 0 COMMENT 0待响应 1已响应 2已解决, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 发布时间, INDEX idx_status_time (status, create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT求助信息表;这里给 status 和 create_time 建了联合索引因为列表页最常见的查询是「当前待响应的求助按发布时间倒序」联合索引能同时过滤状态和排序避免文件排序。初始化轮播图数据时image_url 必须和项目里 upload 目录下的示例图片对应否则首页渲染时图片 404。公告初始化数据可以放一条防灾自救指南登录后直接能看到前台展示效果。admin_user 表初始化时写入管理员账号密码字段存散列值明文只出现在说明文档里。3. Java后端救助申请审核与求助分发的接口实现3.1 Controller-Service-Mapper 分层与接口路径约定后端基于 Spring Boot接口层按业务域拆成 MemberController 和 AdminController 两个入口分别对应小程序端和后台管理端。路径统一以 /api 开头/api/member/** 带用户 token 访问/api/admin/** 带管理员 token 访问拦截器按路径前缀区分角色权限。三层结构里Controller 层只做参数接收和结果封装Service 层处理业务状态流转Mapper 层负责 SQL 操作。以救助申请提交接口为例完整链路是小程序 POST 参数到 ControllerController 把 DTO 转成实体Service 里补上 create_time 和初始 status再调 Mapper 写入 relief_apply 表。把 SQL 写在 Controller 里的做法在课程设计里很常见但会拖累后续加审核逻辑时的可维护性。RestController RequestMapping(/api/member) public class MemberController { Autowired private ReliefApplyService reliefApplyService; PostMapping(/apply/submit) public Result submitApply(RequestBody Valid ApplySubmitDTO dto, RequestHeader(token) String token) { Long userId tokenService.getUserId(token); if (userId null) { return Result.error(401, 登录状态已失效); } return reliefApplyService.submit(userId, dto); } }Valid 触发 DTO 里的字段校验注解比如 materialsUrl 不能为空、applyType 长度不超过 32校验失败由全局异常处理器统一返回提示不用在每个接口里写 if else。token 参数从请求头取小程序端在 request 封装里已经塞进 header后端拦截器解析出 userId 和角色。DTO 里不要直接放数据库实体类否则前端多传一个 status 字段就能伪造审核状态。3.2 救助申请审核的状态校验与幂等更新管理员点击通过或驳回时后端要做的不只是 UPDATE 状态还要确认当前状态允许流转以及审核操作只能执行一次。否则两个管理员同时打开审核列表、先后提交审核操作后一个会覆盖前一个的审核意见。演示环境里出现一次覆盖整套系统的可信度就崩了。Transactional public Result audit(AuditRequest req) { ReliefApply apply reliefApplyMapper.selectById(req.getApplyId()); if (apply null) { return Result.error(申请记录不存在); } if (apply.getStatus() ! 0) { return Result.error(当前状态不可审核请刷新列表); } apply.setStatus(req.getAuditResult()); apply.setAuditRemark(req.getRemark()); apply.setAuditAdminId(req.getAdminId()); apply.setAuditTime(new Date()); reliefApplyMapper.updateById(apply); return Result.success(); }逻辑顺序是先查出来看状态再更新。这里有一个并发边界两次审核请求同时进来都读到 status0都会执行更新。课程设计层面可以接受想做得严谨就在 UPDATE 语句里带上 status0 条件用数据库行锁兜底UPDATE relief_apply SET status #{newStatus}, audit_time NOW() WHERE apply_id #{applyId} AND status 0;受影响行数为 0 说明状态已被其他管理员更新事务回滚即可。注意 Transactional 必须加在业务方法上同时 Service 层调用 Mapper 的 update 方法时不能把返回结果丢掉否则无法根据行数判断是否冲突。auditResult 的取值在 DTO 里用注解限定只能是 1 或 20 和 null 都会在进入方法前被拦截。3.3 求助信息列表的脱敏查询与分页参数求助信息列表是高频读接口首页、发现页、搜索都会调用。分页参数 pageNo 从 1 开始pageSize 固定 10后端用 PageHelper 或者手写 LIMIT 都可以。列表数据里的手机号必须脱敏完整号码只能在用户点击「提供帮助」后通过详情接口按需返回。SELECT help_id, title, content, CONCAT(LEFT(phone, 3), ****, RIGHT(phone, 4)) AS masked_phone, create_time FROM help_info WHERE status 1 ORDER BY create_time DESC LIMIT #{offset}, #{pageSize};offset 由后端通过 (pageNo-1) * pageSize 计算前端不要直接传 offset否则页码和条数不一致时会出现重复或漏页。status1 表示当前可响应的求助已解决的求助不再出现在列表里避免堆满历史数据。脱敏在 SQL 里做不会影响查询性能因为 phone 没有参与 WHERE 条件只在 SELECT 投影列里用函数处理。搜索场景可以加 keyword 参数WHERE 条件改成 title LIKE CONCAT(%, #{keyword}, %)。演示数据几百条没问题真实数据量大时 LIKE 前导通配符会让索引失效需要换成全文索引或者 Elasticsearch这一点到第五章的生产化改造里展开。4. 微信小程序端轮播图加载、公告渲染与材料上传联动4.1 app.js 全局配置与 request 请求封装小程序端用微信开发者工具打开项目后先看 app.js 里的 globalData。baseURL 决定所有接口的请求地址本地联调时是 localhost真机预览必须改成电脑的局域网 IP否则手机访问不到 Java 后端。token 在登录成功后写入 globalData同时通过 wx.setStorageSync 持久化一份冷启动时从 Storage 恢复。// app.js App({ globalData: { baseURL: http://localhost:8080/api, token: }, request(path, method, data) { return new Promise((resolve, reject) { wx.request({ url: this.globalData.baseURL path, method: method || GET, data: data || {}, header: { Content-Type: application/json, token: this.globalData.token }, success: res resolve(res.data), fail: err reject(err) }); }); } });request 封装把 wx.request 的细节收敛到一个函数里页面里调用 getApp().request(/notice/latest) 就能拿到 Promise 结果。header 里的 token 是身份凭证后端拦截器靠它区分管理员和会员。注意 wx.request 的 Content-Type 只影响请求体的序列化方式上传文件要换成 wx.uploadFile 走 multipart/form-data没法直接复用这个 request 函数。4.2 首页轮播图和公告列表的并行加载首页是小程序的门面轮播图来自 carousel 表按 sort_order 升序展示公告列表来自 disaster_notice 表按时间倒序取最新五条。两个接口相互独立用两个 Promise 并行请求不要串行 await否则一次网络往返变成两次弱网环境下感官差异明显。onLoad() { const app getApp(); app.request(/carousel/list, GET).then(res { if (res.code 200) { this.setData({ banners: res.data }); } }); app.request(/notice/latest?limit5, GET).then(res { if (res.code 200) { this.setData({ notices: res.data }); } }); }分两次 setData 是有意的banners 和 notices 的数据来源和更新时机不同合并成一个对象反而让后续局部刷新变得困难。注意首页如果有 tab 页签onLoad 只在首次进入时触发一次切后台再回前台要用 onShow 重新拉取公告列表否则公告更新后首页还显示旧数据。模板里 swiper 组件的 autoplay 控制自动播放interval 建议 3000 到 5000 毫秒太短用户来不及阅读。image 组件的 mode 设成 aspectFill 防止图片被拉伸变形加载失败时要在 binderror 事件里替换成默认兜底图。首页也是项目答辩时第一眼看到的页面轮播图空数据或者破图会直接影响评委印象。4.3 救助申请材料上传与表单提交申请救助页是交互最重的页面。用户先选材料图片再填申请类型最后提交。上传动作发生在表单提交之前而不是随表单一起提交这样用户上传失败时可以单独重试不用重新填写整个表单。uploadMaterial() { const app getApp(); wx.chooseMedia({ count: 3, mediaType: [image], success: (res) { const filePath res.tempFiles[0].tempFilePath; wx.uploadFile({ url: app.globalData.baseURL /upload, filePath: filePath, name: file, formData: { token: app.globalData.token }, success: (uploadRes) { const data JSON.parse(uploadRes.data); this.setData({ materialUrl: data.url }); } }); } }); }count 限制 3 张对应后端接口的单次接收数量材料太多会拖慢审核效率。name 字段值 file 必须和后端 RequestParam(file) 的参数名一致不一致时直接报 400。token 通过 formData 传递而不是 header是因为 wx.uploadFile 的 header 只能放字符串且无法自定义 Content-Typemultipart 表单里 token 放 formData 最稳。上传成功拿到 materialUrl 后表单提交时把它一并 POST 到 /api/member/apply/submit。提交按钮要加 disabled 状态materialUrl 为空时按钮置灰防止用户不传材料就提交后后端再返回「材料不能为空」的提示。前端先拦截比后端报错再回显体验好得多。5. 权限边界、审核闭环与系统改造验证5.1 接口越权拦截与会话保持小程序端登录后拿到的 token 只代表「当前用户是会员」不代表能访问所有接口。后端拦截器按路径前缀做角色判断是最直接的方式/api/admin/** 必须有管理员角色/api/member/** 只要有有效 token 即可。容易漏掉的是 /api/upload 文件上传接口如果放在 admin 路径之外会员可以上传任意文件攻击面就打开了。文件类型校验不能只在前端做后端要按扩展名和文件头双重校验只认 jpg、png、pdf。5.2 全链路验证的三个关键节点拿到源码后先按演示录像跑通完整流程重点验证三个节点。第一小程序端注册新账号后member 表新增记录且密码是密文。第二申请救助时上传材料upload 目录出现文件relief_apply 表写入记录且 status 为 0。第三管理员后台审核通过后小程序端个人中心能看到状态变成已通过。提示演示录像里的登录账号和数据库初始化脚本里的 admin 密码是对应的改密码之前先确认登录页的说明文档。验证时直接查库SELECT apply_id, user_id, status, audit_time FROM relief_apply ORDER BY apply_id DESC;如果审核操作之后 audit_time 还是 NULL说明 UPDATE 没执行成功优先检查事务是否回滚、updateById 是否把 null 字段也写进去了。MyBatis-Plus 默认只更新非空字段audit_time 必须显式 set 再更新否则这条记录永远没有审核时间。5.3 生产化改造的优先级建议三处改造按性价比排序。文件存储换成 MinIO 或 OSS解决本地 upload 目录在服务器重启后文件丢失的问题实现上只改上传接口的存储逻辑数据库不用动。公告和轮播图加 Redis 缓存key 用 notice:latest 和 carousel:index失效时间 5 分钟灾情公告是低频变更数据缓存命中率接近 100%。求助状态变更加订阅消息通知审核通过时主动触达灾民不用用户反复刷新页面。这三个方向分别对应存储、缓存和消息任何一个做完都可以写进论文的创新点里而且不改变现有表结构和接口路径改造风险可控。本文还有配套的精品资源点击获取
返回列表