灾情救助系统毕设:微信小程序+Java后端全链路实战
发布时间:2026/9/28 16:09:21来源:尧图网络
简介基于微信小程序与 Java 后端的灾情救助系统毕业设计资料包面向毕业设计、课程设计或项目实战人群完整覆盖管理员与会员两端业务。系统以微信开发者工具构建小程序前端、JavaMySQL 支撑后端包含会员管理、灾情公告、信息咨询、求助救助、志愿者管理、轮播图维护等模块会员可在线注册、浏览灾情视频与公告、提交救助申请材料并发布求助信息管理员负责审核与前台内容管理。压缩包共 1250 个文件约 124.54MB以 232 个 png 图片、177 个 js 脚本、134 个 vue 组件、120 个 java 后台类、94 个 json、86 个 wxss、84 个 wxml 为主同时附带 2 个 sql 数据库脚本、mp4 演示录像及 docx 说明文档。资源内置 install/run/build 三个 bat 环境脚本与多个 bak 备份文件便于快速还原项目、梳理前后端目录结构并复现完整业务流程。已有 224 人学习下载适合需要直接参考真实源码、答辩说明和演示录像来完成相似课题的开发者。1. 灾情救助系统毕设微信小程序Java后端这条链路值不值得投入先说结论这个题目拿高分的关键不在“灾情”选题本身而在于它把微信小程序、Java后端、数据库、说明文档和演示录像串成了一条完整链路。很多毕设死在“功能看着多数据却是写死的”而这个课题天然要求你让用户在手机端填报、让后端接住数据、让数据库真正落库光是这个闭环就比一半的“管理系统”扎实。适合谁正在做Java课程设计或本科毕设的人尤其是想走前后端分离项目实战方向但没独立跑通过完整项目的人。所谓“源码数据库说明演示录像”本质是四类交付物源码证明你能写数据库证明你会设计说明文档证明你讲得清演示录像证明它真能跑。下文就按这套交付物把灾情救助系统从数据库到小程序端逐个拆开包括参数怎么定、接口怎么写、坑在哪里。2. 系统架构与数据库设计一次灾情上报如何流转到物资发放2.1 前后端分离与数据流小程序、后端、数据库各管哪一段在动手写代码前先把数据流画在脑子里用户在微信小程序里填写灾情表单点击提交后wx.request把 JSON 数据发到 Java 后端后端 Controller 接收请求Service 层做业务校验和状态流转Mapper 层把结果写进 MySQL数据库返回自增 ID 后后端再把统一格式的 JSON 响应回传给小程序小程序收到后提示“上报成功”。这中间有个常见认知偏差小程序端发请求并不存在浏览器跨域问题。浏览器的同源策略只约束网页环境小程序的运行环境是微信客户端它关心的只有两件事——请求域名是否在后台配置过、后端接口是否能正确处理请求。所以毕设里“后端配了 CORS 依然报跨域”的排查方向基本是错的真正要查的是域名白名单和本地调试开关这一点在第 5 章会展开。后端内部的分层也建议按教科书写法拆开Controller 只做参数接收和响应包装Service 只做业务逻辑Mapper 只管 SQL。灾情上报这种几十个字段的业务如果全堆在 Controller 里后面加一个“派单”功能就得重写一大片。前后端分离项目实战里最忌讳的就是分层混乱因为小程序端只认接口返回的 JSON 结构后端一旦把状态码和业务数据混在同一个字段里前端每个页面都要跟着改。2.2 核心表设计六张表撑起救助闭环我一般会设计七张表其中六张是核心用户表、灾情上报表、救助求助表、救援任务表、物资表、物资流水表再加一张字典表存灾害类型和紧急程度。下面是各表的职责和关键字段表名作用关键字段user微信用户与管理员openid、nickname、roledisaster_report用户上报的灾情信息user_id、type、level、longitude、latitude、images、statusrescue_request受灾用户的物资/人力求助report_id、material_type、quantity、statusrescue_task管理员或救援队的派单记录report_id、assignee、task_statusmaterial救援物资库存name、stock、unitmaterial_log物资出入库流水material_id、change_type、change_num、operator_id灾情上报表是整个系统的数据入口用户在小程序端的每一步操作几乎都会落到它身上救援任务表则把“有人报灾”推进到“有人去救”这也是演示录像里最能体现业务完整度的一段。注意 disaster_report 和 rescue_request 不要合并成一张表一次火灾可能同时需要消防和帐篷报灾信息是稳定的求助需求却会变化拆开以后状态流转才清晰。2.3 建表 SQLuser 与 disaster_report 的字段细节下面给出核心两表的建表语句字符集必须用 utf8mb4否则用户填的生僻字和表情符号在存储时会变问号。CREATE DATABASE IF NOT EXISTS disaster_aid DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE disaster_aid; -- 微信用户表 CREATE TABLE user ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键, openid VARCHAR(64) NOT NULL COMMENT 微信openid同一小程序下唯一, nickname VARCHAR(64) DEFAULT COMMENT 昵称, avatar VARCHAR(500) DEFAULT COMMENT 头像地址, phone VARCHAR(20) DEFAULT COMMENT 联系电话, role TINYINT NOT NULL DEFAULT 1 COMMENT 角色0-管理员1-普通用户, deleted TINYINT NOT NULL DEFAULT 0 COMMENT 逻辑删除0-正常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 uk_openid (openid) ) ENGINE InnoDB COMMENT 微信用户表; -- 灾情上报表 CREATE TABLE disaster_report ( id BIGINT NOT NULL AUTO_INCREMENT, user_id BIGINT NOT NULL COMMENT 上报人 user.id, type TINYINT NOT NULL COMMENT 灾害类型1火灾 2水灾 3塌方 4被困, level TINYINT NOT NULL DEFAULT 2 COMMENT 紧急程度1一般 2紧急 3特急, title VARCHAR(100) DEFAULT COMMENT 灾情标题, description TEXT COMMENT 灾情描述, location_name VARCHAR(255) DEFAULT COMMENT 位置文字描述, longitude DECIMAL(10,6) COMMENT GCJ-02经度, latitude DECIMAL(10,6) COMMENT GCJ-02纬度, images TEXT COMMENT 图片地址多张用逗号分隔, status TINYINT NOT NULL DEFAULT 0 COMMENT 状态0待审核 1待救援 2救援中 3已完成, deleted TINYINT NOT NULL DEFAULT 0, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_status_create (status, create_time), KEY idx_user_id (user_id) ) ENGINE InnoDB COMMENT 灾情上报表;字段注释一定要写全因为毕设说明文档里的“数据库设计”章节可以直接复用这些注释。DECIMAL(10,6)是经纬度最常用的精度10 位总长度里小数点后占 6 位误差约 0.1 米足够定位到楼栋。images用逗号分隔存储多图是毕设里性价比最高的做法不需要额外建附件表小程序端拿到后split(,)就能循环渲染如果以后要做图片按条删除再拆子表不迟。2.4 物资表与流水表为什么库存和流水必须分开物资扣减是答辩时最容易深挖的点设计上要把“库存”和“变动记录”分开。物资表只存当前剩余量物资流水表存每一次出入库的痕迹两张表配合才能回答“这批帐篷去哪了”的追问。-- 救援物资表 CREATE TABLE material ( id BIGINT NOT NULL AUTO_INCREMENT, name VARCHAR(100) NOT NULL COMMENT 物资名称, stock INT NOT NULL DEFAULT 0 COMMENT 当前库存, unit VARCHAR(10) DEFAULT 件 COMMENT 单位, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINE InnoDB COMMENT 救援物资表; -- 物资出入库流水表 CREATE TABLE material_log ( id BIGINT NOT NULL AUTO_INCREMENT, material_id BIGINT NOT NULL COMMENT 物资 material.id, change_type TINYINT NOT NULL COMMENT 1入库 2出库, change_num INT NOT NULL COMMENT 变动数量正数表示增减, operator_id BIGINT DEFAULT NULL COMMENT 操作人 user.id, remark VARCHAR(255) DEFAULT COMMENT 备注关联救援任务ID, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_material_id (material_id) ) ENGINE InnoDB COMMENT 物资出入库流水表;出库时先判断库存是否足够再写流水表两个操作必须放在同一个事务里。如果先改库存但流水没写进去演示时就会看到“库存少了但查不到谁领的”这种逻辑漏洞在答辩追问下基本没法圆场。后面第 3 章会专门讲并发扣减的正确写法。2.5 初始化数据管理员账号与字典表建完表之后要写一份init_data.sql否则后端启动后没有任何数据可测。管理员密码不要用明文Java 后端常用 BCrypt 加密初始化 SQL 里可以直接放一段 BCrypt 密文登录时用BCrypt.matches()校验。INSERT INTO user (openid, nickname, role, deleted) VALUES (admin_init, 系统管理员, 0, 0); INSERT INTO dict (type, code, label, sort) VALUES (disaster_type, 1, 火灾, 1), (disaster_type, 2, 水灾, 2), (disaster_type, 3, 塌方, 3), (disaster_type, 4, 被困, 4), (level, 1, 一般, 1), (level, 2, 紧急, 2), (level, 3, 特急, 3);字典表的好处是前端单选框的选项列表可以从后端接口拉而不是写死在小程序代码里。答辩时如果被问“灾害类型以后要加一种怎么办”直接说“往 dict 表插一条记录小程序端下拉框自动多一个选项”比“去改代码重新发版”高级得多。3. Spring Boot 后端接口实现登录、上报、派单与物资扣减3.1 技术选型与基础配置Spring Boot MyBatis-Plus 的理由Java 后端我建议直接用 Spring Boot 2.7.x MyBatis-Plus MySQL这是毕设圈最稳的组合。Spring Boot 负责把 Web 服务、数据源、事务管理自动化MyBatis-Plus 让单表 CRUD 不用写 XML省出来的时间可以补业务逻辑。dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdcom.auth0/groupId artifactIdjava-jwt/artifactId version3.19.2/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependenciesmybatis-plus-boot-starter的版本建议固定一个已知稳定的不要随手写最新版MyBatis-Plus 3.5.x 配 Spring Boot 2.7 的兼容性最好。JWT 用的是java-jwt比jjwt的 API 直观生成 token 只要三行代码。Lombok 可以省掉 getter/setter但注意说明文档里的实体类代码最好保留完整写法否则评阅老师如果不用 Lombok读代码会卡住。3.2 微信登录code2Session 换 openid签发自己的 JWT小程序端wx.login()拿到的 code 是一次性的必须交给后端去微信服务器换 openid。后端拿到 openid 后查 user 表没记录就自动注册然后签发自己的 JWT 返回给小程序。这里的关键是不要把appid和secret写在小程序代码里微信服务器接口必须由后端调用否则 anyone 抓包就能看到密钥。RestController RequestMapping(/api/auth) public class WxAuthController { PostMapping(/login) public Result login(RequestBody LoginRequest req) { // 1. 用 code 向微信服务器换 openid String url https://api.weixin.qq.com/sns/jscode2session ?appid wxConfig.getAppid() secret wxConfig.getSecret() js_code req.getCode() grant_typeauthorization_code; String resp restTemplate.getForObject(url, String.class); JSONObject obj JSON.parseObject(resp); String openid obj.getString(openid); // 2. 查库或注册 User user userMapper.selectOne( new LambdaQueryWrapperUser().eq(User::getOpenid, openid)); if (user null) { user new User(); user.setOpenid(openid); user.setNickname(微信用户 openid.substring(openid.length() - 6)); user.setRole(1); userMapper.insert(user); } // 3. 签发 JWT有效期 7 天 String token JWT.create() .withClaim(userId, user.getId()) .withClaim(role, user.getRole()) .withExpiresAt(new Date(System.currentTimeMillis() 7 * 24 * 3600 * 1000)) .sign(Algorithm.HMAC256(wxConfig.getJwtSecret())); return Result.ok(new LoginVO(token, user.getRole())); } }代码里jscode2session的返回如果带errcode说明 code 已过期或 appid 不匹配一定要记日志而不是直接抛异常。新注册的用户建议默认密码为空只允许微信登录管理员账号走独立的账号密码登录接口用 BCrypt 校验。JWT 的有效期设 7 天小程序端请求拦截器里如果收到 401就自动调wx.login()重新静默登录用户无感知。3.3 灾情上报接口图片上传与后缀白名单上报接口要处理两件事先把图片传到后端存储再把灾情数据写库。图片上传最容易出现的就是只校验了Content-Type攻击者把文件名改成1.jpg但内容是一段 JSP 脚本如果后端把文件存到了可执行目录这就是一个上传漏洞。毕设里必须做三件事校验真实后缀、限制大小、重命名文件。Service public class DisasterReportServiceImpl implements DisasterReportService { Override public Long saveReport(ReportReq req, MultipartFile[] files) { // 1. 校验必填项 if (req.getLongitude() null || req.getLatitude() null) { throw new BizException(请选择上报位置); } // 2. 上传图片只允许 jpg/png/webp ListString urls new ArrayList(); if (files ! null) { for (MultipartFile file : files) { String ext StringUtils.getFilenameExtension( file.getOriginalFilename()); if (!Arrays.asList(jpg, jpeg, png, webp).contains(ext)) { throw new BizException(图片格式不支持); } if (file.getSize() 5 * 1024 * 1024) { throw new BizException(单张图片不能超过5MB); } // 重命名时间戳 随机串避免文件名攻击 String newName System.currentTimeMillis() _ UUID.randomUUID().toString().replace(-, ) . ext; file.transferTo(new File(uploadDir, newName)); urls.add(/upload/ newName); } } // 3. 写库 DisasterReport report new DisasterReport(); report.setUserId(req.getUserId()); report.setType(req.getType()); report.setLevel(req.getLevel()); report.setLocationName(req.getLocationName()); report.setLongitude(req.getLongitude()); report.setLatitude(req.getLatitude()); report.setImages(String.join(,, urls)); report.setStatus(0); reportMapper.insert(report); return report.getId(); } }StringUtils.getFilenameExtension从原始文件名抠后缀比originalFilename.endsWith(.jpg)严谨能防1.jpg.jsp这种双重后缀绕过。图片上传后的 URL 用相对路径存库小程序端渲染时拼上服务器地址如果直接存全路径以后换服务器 IP历史图片全部失效。坐标这里存的是 GCJ-02 坐标也就是微信wx.chooseLocation返回的坐标系后端不需要二次转换直接入库如果以后要接地形图服务再单独做转换模块。3.4 派单与状态流转从“待审核”到“救援中”管理员审核上报后创建救援任务核心是把disaster_report.status从 0 推到 1再插入一条rescue_task。这里我建议用一张状态机辅助表来表达流转规则而不是在代码里散落十几个 if 判断。Transactional(rollbackFor Exception.class) public Long assignTask(Long reportId, Long assigneeId) { DisasterReport report reportMapper.selectById(reportId); if (report null || report.getDeleted() 1) { throw new BizException(灾情不存在); } // 只允许从“待审核”流转到“待救援” if (report.getStatus() ! 0) { throw new BizException(当前状态不能派单); } RescueTask task new RescueTask(); task.setReportId(reportId); task.setAssigneeId(assigneeId); task.setTaskStatus(0); // 0未出发 1救援中 2已完成 taskMapper.insert(task); report.setStatus(1); reportMapper.updateById(report); return task.getId(); }方法的Transactional必须指定rollbackFor Exception.class因为 Spring 默认只回滚运行时异常如果业务代码抛的是受检异常事务不会回滚就会出现“任务表插进去了上报状态还是待审核”的脏数据。派单的状态校验看起来是多此一举但演示时如果重复点击“派单”按钮没有这层保护就会生成两条任务。3.5 物资扣减的并发问题别用“先查再改”物资扣减是答辩时最容易被追问的考点。很多人第一版代码是这样的先select stock判断stock need再update stock stock - need。单用户跑没问题两个救援队同时领同一批帐篷时两个线程都读到库存 10都判断够用都执行扣减最后库存变成 -2。正确做法是把判断写进 UPDATE 的 WHERE 条件里Override Transactional(rollbackFor Exception.class) public void deductStock(Long materialId, int num, String remark) { if (num 0) { throw new BizException(扣减数量必须大于0); } // 关键库存条件放在 UPDATE 里数据库行锁保证只扣一次 int rows materialMapper.deductStock(materialId, num); if (rows 0) { throw new BizException(库存不足扣减失败); } MaterialLog log new MaterialLog(); log.setMaterialId(materialId); log.setChangeType(2); log.setChangeNum(num); log.setRemark(remark); logMapper.insert(log); }对应的 Mapper 方法UPDATE material SET stock stock - #{num}, update_time NOW() WHERE id #{id} AND stock #{num}这里的原理是UPDATE执行时会对命中的行加行级锁两个并发请求到达数据库后会排队执行。第一个请求执行成功stock变成 0第二个请求执行时stock #{num}不成立影响行数为 0代码里判断rows 0就直接报“库存不足”。这样就避免了先查再改的竞态。事务里先扣库存再写流水流水也不要放在 UPDATE 之前否则库存扣减失败但流水已经落库对账就会对不上。4. 微信小程序端请求封装、上报表单、地图选点与分页列表4.1 原生小程序还是 uni-app毕设场景怎么选做毕设我建议用原生小程序而不是 uni-app。理由有三个第一微信开发者工具对原生项目的调试体验最好console里的报错能直接定位到具体页面文件第二毕设答辩一般只要求跑通微信端uni-app 的跨端能力在答辩中展示不出来反而增加一层编译问题第三网上搜“微信小程序项目实例”找得到的代码片段九成是原生写法遇到问题抄起来更快。如果你以后想进跨端开发方向再单独学 uni-app 也不迟但不要把毕设当成技术试验田。4.2 请求封装token 注入、401 处理与加载态小程序端所有请求应该走同一个封装函数而不是每个页面各自写wx.request。统一封装以后要改请求头、要处理登录失效都只改一个文件。// utils/request.js const BASE_URL http://127.0.0.1:8080; function request(path, method GET, data {}) { return new Promise((resolve, reject) { wx.showLoading({ title: 加载中 }); wx.request({ url: BASE_URL path, method: method, data: data, header: { Content-Type: application/json, // 从本地缓存取 token每次请求自动带上 Authorization: wx.getStorageSync(token) }, success(res) { if (res.statusCode 200 res.data.code 0) { resolve(res.data.data); } else if (res.statusCode 401) { // token 过期清缓存并重新登录 wx.removeStorageSync(token); wx.removeStorageSync(userInfo); wx.showToast({ title: 登录已过期, icon: none }); // 跳回登录页 wx.reLaunch({ url: /pages/login/login }); reject(res.data); } else { wx.showToast({ title: res.data.msg || 请求失败, icon: none }); reject(res.data); } }, fail(err) { // 常见原因本机调试时后端没启动、域名没配 wx.showToast({ title: 网络异常, icon: none }); reject(err); }, complete() { wx.hideLoading(); } }); }); } module.exports { request, BASE_URL };代码里约定后端返回结构是{ code: 0, data: ..., msg: ... }code为 0 表示成功。小程序端只认这一种结构后端所有接口都要保持统一否则一个接口返回{ success: true }另一个返回{ code: 200 }前端就得写两份判断。注意BASE_URL里的127.0.0.1只对开发者工具有效真机预览时要把 IP 换成电脑在局域网里的地址比如http://192.168.1.100:8080。4.3 上报页面单选框、地图选点与图片上传上报页面是整张小程序的交互核心包含三块灾害类型单选框、位置选择、多图上传。灾害类型用微信原生radio-group后端配了字典表前端对应的选项值必须和后端dict.code保持一致。!-- pages/report/report.wxml -- view classform-item text classlabel灾害类型/text radio-group bindchangeonTypeChange label wx:for{{typeList}} wx:keycode radio value{{item.code}} checked{{item.code form.type}} / text{{item.label}}/text /label /radio-group /view view classform-item text classlabel上报位置/text input value{{form.locationName}} placeholder点击选择位置 bindtaponChooseLocation disabled / /view view classform-item text classlabel现场照片/text view classimage-list image wx:for{{imageList}} wx:key*this src{{item}} / view bindtaponChooseImage 添加/view /view /view位置选择调用wx.chooseLocation这个接口会拉起微信自带的地图选点返回的longitude和latitude就是 GCJ-02 坐标正好和后端字段对应。onChooseLocation() { wx.chooseLocation({ success: (res) { this.setData({ form.locationName: res.name || res.address, form.longitude: res.longitude, form.latitude: res.latitude }); } }); }, onChooseImage() { wx.chooseMedia({ count: 3, mediaType: [image], sourceType: [camera, album], sizeType: [compressed], success: (res) { const files res.tempFiles; this.setData({ imageList: files.map(f f.tempFilePath) }); } }); }, async onSubmit() { // 多图逐个上传全部成功后再提交表单数据 const uploadTasks this.data.imageList.map((filePath, index) { return wx.uploadFile({ url: BASE_URL /api/upload, filePath: filePath, name: file, header: { Authorization: wx.getStorageSync(token) } }); }); const results await Promise.all(uploadTasks); const urls results.map(r JSON.parse(r.data).data); // urls 拼进 form.images 后调后端接口 }count: 3限制最多选三张sizeType: [compressed]让微信自动压缩图片不然原图可能五六兆上传体验很差。多图上传用Promise.all并发执行比串行快但如果学校机房网络不好并发三个大文件也可能超时稳妥做法是reduce串行上传每张等上一个完成再传。后端收到的name: file要和 Spring Boot 接口的MultipartFile参数名对应。4.4 列表分页onReachBottom 与后端 page/limit 配合灾情列表不能一次把所有数据查出来毕设数据量虽然不大但分页逻辑是必问考点。小程序端监听页面滚动到底部累加页码重新请求。// pages/list/list.js Page({ data: { list: [], page: 1, limit: 10, hasMore: true, loading: false }, async loadList(reset false) { if (this.data.loading) return; if (!reset !this.data.hasMore) return; this.setData({ loading: true }); if (reset) { this.setData({ page: 1, hasMore: true }); } const res await request(/api/report/list, GET, { page: this.data.page, limit: this.data.limit }); const rows res.rows || []; this.setData({ list: reset ? rows : this.data.list.concat(rows), page: this.data.page 1, hasMore: this.data.page res.totalPage, loading: false }); }, onPullDownRefresh() { this.loadList(true).finally(() wx.stopPullDownRefresh()); }, onReachBottom() { this.loadList(false); } });后端返回{ rows: [], totalPage: 2 }这种结构前端只要判断page totalPage就能决定能不能继续加载。这里有个小坑onPullDownRefresh和onReachBottom必须在页面 json 里开启对应的窗口配置否则这两个事件永远不触发。loadList里加loading锁是为了防止滚动过快时连续触发多次请求导致重复数据。4.5 自定义导航栏顶部高度适配如果项目里用了自定义导航栏会发现同一个navigationStyle: custom在 iPhone 和安卓上的顶栏高度不一样。不要自己写死 64 或 80 像素用wx.getMenuButtonBoundingClientRect拿胶囊按钮的位置动态计算导航栏高度。const menuRect wx.getMenuButtonBoundingClientRect(); const systemInfo wx.getSystemInfoSync(); // 导航栏高度 状态栏高度 (胶囊顶部到状态栏底部的距离) * 2 胶囊高度 const navBarHeight systemInfo.statusBarHeight (menuRect.top - systemInfo.statusBarHeight) * 2 menuRect.height;把这个计算结果存到全局变量或页面 data 里样式中用padding-top: {{navBarHeight}}px撑开内容区。这个适配公式是抄不来的经验网上搜“微信小程序顶部导航栏高度”能搜到很多方案但只有这种动态计算能覆盖所有机型包括带灵动岛的机型。5. 避坑与排查五条血泪经验照着查能省一个通宵5.1 wx.request 一直报“不在合法域名列表”现象开发者工具里请求本机后端console报url not in domain list。第一次遇到的人会以为是后端跨域配置问题于是去加 CORS 注解加完发现照样报错。原因微信小程序为了安全要求wx.request的域名必须在微信公众平台配置为合法业务域名正式版尤其严格。本机调试用的http://127.0.0.1或http://192.168.x.x显然不在任何人的合法域名列表里。解决开发者工具右上角“详情 - 本地设置”勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”。这是开发阶段的标准操作。真机预览时手机和电脑连同一个 Wi-Fi把源码里的BASE_URL改成电脑的局域网 IP 就能调通。注意体验版和正式版仍然要求 HTTPS 合法域名如果毕设只演示开发版这个坑就不用管。5.2 getUserProfile 拿不到头像昵称现象用了wx.getUserProfile点击授权后res.userInfo里nickName是一串“微信用户”头像也是灰色默认图。原因微信在 2022 年之后收紧了用户信息接口wx.getUserProfile不再返回真实头像昵称官方推荐用头像昵称填写能力。网上大量旧教程还在教getUserProfile照着抄必然翻车。解决头像用button open-typechooseAvatar bindchooseavataronChooseAvatar昵称用input typenickname /。用户主动点一次头像、填一次昵称才能拿到真实信息。如果演示时不想让用户手动填可以在用户首次登录后生成默认昵称把主要精力放在灾情上报链路上。5.3 图片上传失败或文件名被绕过现象上传图片返回“文件上传失败”后端日志没有异常但文件没落盘或者上传一个改名成1.jpg的脚本居然成功了。原因前者多半是 Nginx 或 Spring 配置文件上传大小上限太低spring.servlet.multipart.max-file-size默认只有 1MB微信压缩后的图片也可能超过。后者是后端只校验了Content-Type攻击者抓包改成image/jpeg就能绕过更危险的是只判断fileName.contains(.jpg)后缀拼在中间也可以绕过白名单。解决在application.yml里把上传上限调到 10MB并配合第 3 章的“真实后缀 白名单 随机重命名”三段式校验。检查是否生效看后端日志里有没有MultipartException或FileUploadBase.SizeLimitExceededException。随机重命名不只是为了防冲突更重要的是让文件名脱离用户控制从根上断掉上传漏洞的路径。5.4 并发扣库存后出现负数现象两个用户同时提交物资领取最终库存变成 -1但流水表里只有一条出库记录。原因典型的先查再改竞态。两个线程同时读到库存stock 1都通过了if (stock 1)的判断然后各自执行UPDATE material SET stock stock - 1数据库里最终结果取决于谁后执行但两个请求都认为自己是成功的那一个。解决按第 3 章 3.5 的方式把stock #{num}写进UPDATE的WHERE条件里用数据库行锁保证同一行库存的扣减串行化。检验办法用 Postman 或 JMeter 并发发 20 个扣减请求预期只有 10 个成功库存最终为 0。如果毕设答辩被问到“高并发下怎么做”能说出这一条就能看到评委点头。5.5 MySQL 中文乱码与时间差 8 小时现象小程序端提交的中文灾情描述在数据库里变成???后端返回的时间字段比本地时间早了 8 小时。原因中文乱码是数据库连接串没指定字符集或者建库时用了默认的latin1。时间差是 JDBC 连接时区默认用了服务器时区而 MySQL 8 默认时区是 UTC东八区就差了 8 个小时。解决连接串固定加?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai建库语句用第 2 章的DEFAULT CHARACTER SET utf8mb4。时间字段建议让 MySQL 自己维护create_time用DEFAULT CURRENT_TIMESTAMP就行后端实体类里用LocalDateTime接收不要自己在 Java 代码里new Date()塞进去。排查时先SHOW CREATE TABLE看表字符集再SHOW VARIABLES LIKE character_set%看连接字符集两步能定位九成乱码问题。6. 从“能运行”到“能答辩”演示录像、接口日志与验收顺序演示录像的录制顺序比很多人想的更重要。不要打开系统后东点一下西点一下我建议严格按“用户报灾 → 管理员派单 → 救援队更新状态 → 物资扣减”这条主线录小程序端录入位置、上传图片、选择火灾类型提交后切到管理端看到新上报点击派单救援队接单后把状态改成救援中最后领物资时录到库存数量变化。这段录像要能在十分钟内讲完对应的说明文档章节顺序和它保持一致不要录像里演示了派单文档却先讲数据库设计评阅老师对不上会很扣分。想证明后端接口真实跑通给 Service 层加一个日志切面答辩时切到控制台展示实时日志比盯着页面口头解释有说服力得多。用 Spring AOP 十几行就能搞定核心逻辑是记录每个接口的入参、耗时和返回状态。一个小技巧报灾接口故意打一条“上报成功IDxxx耗时 32ms”的日志然后顺着这个 ID 去数据库里SELECT功能闭环立刻立住。检查方向按“项目能启动、主流程能闭环、极端输入不崩”的顺序来保证必现问题在上台前全部处理掉整段演示不要出现第二次冷启动或重新登录。这个项目里最容易被追问的就是并发扣减和状态流转我当年答辩时被问“两个救援队同时扣同一批物资怎么办”直接愣住后来把乐观锁的 SQL 反复背熟才补上这个短板。所以第 3 章和第 5 章的内容建议你也按“能讲清原理、能写出 SQL、能说出报错现象”的标准准备一遍。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网