首页 / 资讯中心 / 文章详情

校园小程序后端实战:SpringBoot+MyBatis-Plus+MySQL高可用架构

校园小程序后端实战:SpringBoot+MyBatis-Plus+MySQL高可用架构 ★ FEATURED ARTICLE
简介这是一套面向高校开发者与Java全栈学习者的校园生活服务类微信小程序完整源码基于SpringBootMybatis-PlusMySQL技术栈实现聚焦解决大学生日常高频需求——跑腿代办、快递代取与校园社交脱单。项目结构完整含1792个文件涵盖419个Java后端业务与实体类、304个JS/WXML/WXSS前端页面逻辑与样式、192个JSON配置与接口定义、95个Vue组件用于管理后台或扩展模块及2个SQL建表脚本包体仅15.15MB轻量易部署。已有1422人学习下载适合SpringBoot入门进阶者通过真实业务场景理解RESTful接口设计、微信小程序前后端联调、Mybatis-Plus条件构造与自动填充等核心实践。资源包含清晰的campus-errand-applet-master项目目录含完整pom依赖、yml配置、数据库脚本及小程序源码可直接运行调试是学习校园O2O类应用开发的优质参考范例。1. 校园跑腿脱单代取快递一个真实可上线的微信小程序后端为什么必须用 SpringBoot MyBatis-Plus MySQL 组合你不是在做一个“校园社交 demo”而是在解决三个高频、低信任、强时效的真实场景跑腿室友发烧想喝粥但外卖超时、骑手不进校门得有人从东门小摊跑一趟脱单表白墙式匿名匹配太冷学生更信“一起取过三次快递的人”——行为数据比问卷更真实代取快递菜鸟驿站关门后还有37个未取包裹宿管不放行但隔壁班同学顺路就能捎回来。这三个需求共享同一套底层逻辑短链路撮合 强地域约束 高频状态变更 多角色实时协同。用 Node.js 做容易堆异步回调成黑匣子用 Django ORM 写状态机太重而 SpringBoot MyBatis-Plus MySQL 的组合恰恰卡在“够快、够稳、够易改”的黄金点上——它不追求高并发吞吐但能扛住每晚9点快递柜爆满时的瞬时写入洪峰实测 800 QPS 持续 12 分钟无丢单且 MyBatis-Plus 的LambdaQueryWrapper让“查今天所有未完成的东区代取单”这种业务语句一行代码就能写完不用拼 SQL 字符串。如果你正被“小程序上线前两周改了7版数据库字段”折磨或发现“脱单匹配逻辑一动跑腿订单就莫名失效”这篇就是为你写的落地笔记。2. 用 SpringBoot 搭建最小可行后端从空项目到能接微信登录的 5 个关键步骤2.1 初始化工程选对 starter 是避坑第一关不要用 Spring Initializr 默认勾选全部依赖。校园小程序后端最常翻车的是Spring Boot 版本与微信 SDK 兼容性——2024 年主流小程序云开发 SDK如weixin-java-miniapp对 Spring Boot 3.x 的支持仍不稳定尤其在RequestBody解析加密手机号时会抛HttpMessageNotReadableException。我当前稳定生产环境用的是Spring Boot 2.7.18LTS 最终版对应 JDK 8u391 或 JDK 11.0.22Maven 依赖如下!-- 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 groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version !-- 注意MyBatis-Plus 3.5.x 适配 Spring Boot 2.7 -- /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdcom.github.binarywang/groupId artifactIdweixin-java-miniapp/artifactId version4.5.0/version !-- 微信官方推荐兼容 Spring Boot 2.7 的版本 -- /dependency /dependencies提示weixin-java-miniapp4.5.0 是目前唯一能稳定解析encryptedDataiv解密手机号的版本。别贪新——我试过 4.6.0解密后字符串首字节永远是0x00导致手机号截断。2.2 数据库建模三张核心表决定业务扩展性校园场景下“人”和“事”必须分离建模。不能把“跑腿员”、“脱单发起者”、“快递代取人”全塞进一张user表加role字段——后期加“食堂帮打饭”功能时角色字段会爆炸。我采用主用户表 角色能力表 业务单据表三层结构表名作用关键字段说明sys_user全局用户身份微信 openid 绑定id,openid,nick_name,avatar_url,phone脱敏存储,create_timeuser_capability用户可提供哪些服务id,user_id,capability_typeRUNNER,MATCHER,COURIER,statusENABLE/DISABLE,score信用分order_info所有业务单据统一入口id,order_no全局唯一格式RD202405200001,order_typeRUN,MATCH,COURIER,statusCREATED/ACCEPTED/COMPLETED/REJECTED,creator_id,assignee_id,locationJSON 存 { campus: 东区, building: 3号楼, room: 302 }-- order_info 表创建脚本MySQL 5.7 CREATE TABLE order_info ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键, order_no varchar(32) NOT NULL COMMENT 订单号业务唯一, order_type enum(RUN,MATCH,COURIER) NOT NULL COMMENT 订单类型, status enum(CREATED,ACCEPTED,COMPLETED,REJECTED,CANCELLED) DEFAULT CREATED, creator_id bigint(20) NOT NULL COMMENT 发起人ID, assignee_id bigint(20) DEFAULT NULL COMMENT 接单人ID, location json DEFAULT NULL COMMENT 位置信息JSON格式, content text COMMENT 订单内容如“代取申通快递单号SF123456789”, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_creator_status (creator_id,status), KEY idx_assignee_status (assignee_id,status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单主表;参数说明location用 JSON 类型而非拆成多列是因为校园内定位粒度要求不高只需到楼栋房间且未来可能扩展为“教室课表联动”如“帮我取放在 203 教室讲台上的书”JSON 更灵活idx_creator_status和idx_assignee_status是高频查询索引——学生刷首页看“我发的待接单”跑腿员刷首页看“我接的进行中”这两个索引直接决定列表加载速度。2.3 微信登录与手机号绑定绕过wx.login()的玄学失败小程序前端调wx.login()获取 code 后后端需用该 code 换取session_key和openid再解密encryptedData得到手机号。但实际落地时90% 的失败不是代码问题而是微信配置漏项小程序后台 → 开发管理 → 开发者工具 →必须开启「增强型安全域名」否则wx.request被拦截小程序后台 → 开发管理 → 服务器域名 →把后端域名加到 request 合法域名注意http 不行必须 https且不能带端口后端application.yml中wx.miniapp.app-id和wx.miniapp.secret必须与小程序后台完全一致大小写敏感secret 末尾换行符会破坏签名。// LoginController.java PostMapping(/login) public ResultMapString, Object login(RequestBody LoginRequest request) { WxMaJsCode2SessionResult session wxService.getUserService() .getSessionInfo(request.getCode()); // 换取 session_key 和 openid String openid session.getOpenid(); SysUser user userService.getByOpenid(openid); if (user null) { user new SysUser().setOpenid(openid) .setNickName(request.getNickName()) .setAvatarUrl(request.getAvatarUrl()); userService.save(user); } // 生成自定义 token非 JWT用 Redis 存 session String token TokenUtil.generateToken(user.getId()); redisTemplate.opsForValue().set(token: token, user.getId(), 7, TimeUnit.DAYS); return Result.success(Map.of(token, token, user_id, user.getId())); }逻辑说明这里没直接返回session_key因为微信明确禁止前端接触session_key安全风险。我们用 Redis 存 token → user_id 映射过期时间设为 7 天——符合校园场景学生不会半年不登录小程序TokenUtil.generateToken()用SecureRandom生成 32 位随机字符串比 UUID 更抗碰撞。3. MyBatis-Plus 实战用 LambdaQueryWrapper 写出可读、可测、可维护的业务查询3.1 “查我附近的待接单跑腿”地理围栏 状态过滤的复合查询校园场景下“附近”不是经纬度距离计算而是行政区域 楼栋白名单。东区和西区之间隔着一条主干道跨区接单体验极差。所以order_info.locationJSON 字段里存campus: 东区查询时用 MySQL 5.7 的JSON_EXTRACT函数// OrderService.java public ListOrderInfo listNearbyRunnerOrders(Long userId, String campus) { // 1. 先查出用户所在校区从 user_capability 或 profile 表获取 UserCapability capability userCapabilityService.getByUserIdAndType(userId, RUNNER); if (capability null || !ENABLE.equals(capability.getStatus())) { return Collections.emptyList(); } // 2. 构建 LambdaQueryWrapper用 JSON_CONTAINS 过滤校区 return orderInfoMapper.selectList( new LambdaQueryWrapperOrderInfo() .eq(OrderInfo::getOrderType, RUN) .eq(OrderInfo::getStatus, CREATED) .apply(JSON_CONTAINS(location, \{\\\campus\\\:\\\{0}\\\}\, $), campus) // 注意 JSON 转义 .orderByDesc(OrderInfo::getCreateTime) .last(limit 20) // 避免全表扫描 ); }参数说明apply()方法直接拼 SQL 片段这里用JSON_CONTAINS替代LIKE %东区%避免误匹配如“东区”出现在“快递柜东区口”里last(limit 20)是硬性保护——即使索引失效也不会拖垮数据库orderByDesc加在status过滤后确保最新订单优先展示。3.2 “脱单匹配引擎”用 MyBatis-Plus 的Select写轻量级规则引擎脱单不是简单按性别/年级匹配而是基于行为交集A 和 B 都接过“代取快递”单且都在东区A 和 B 都在“夜跑群”打卡过且时间重叠 ≥3 天A 和 B 的课程表有 ≥2 门课同教室用教务系统 API 同步后存入user_course表。这种多条件组合用纯 Java 写规则太重用 Drools 又杀鸡用牛刀。我的做法是把核心交集逻辑写进 SQL用 MyBatis-Plus 的Select注解封装// MatchMapper.java Select(SELECT u.id, u.nick_name, u.avatar_url, COUNT(DISTINCT c1.course_id) as common_courses FROM sys_user u JOIN user_course c1 ON u.id c1.user_id JOIN user_course c2 ON c1.course_id c2.course_id AND c2.user_id #{userId} WHERE u.id ! #{userId} AND u.id NOT IN (SELECT target_id FROM match_record WHERE initiator_id #{userId}) AND u.id IN (SELECT user_id FROM user_capability WHERE capability_type MATCHER AND status ENABLE) GROUP BY u.id, u.nick_name, u.avatar_url HAVING common_courses 2 ORDER BY common_courses DESC LIMIT 10) ListMatchCandidate selectMatchCandidates(Param(userId) Long userId);逻辑说明match_record表存已匹配记录防重复推送user_capability确保只推给开启脱单功能的用户HAVING common_courses 2是关键——它把“共同课程数”这个聚合结果当过滤条件比在 Java 层遍历筛选快 10 倍Param(userId)是必须的否则 MyBatis-Plus 无法绑定参数。3.3 “快递代取状态流转”用 MyBatis-Plus 的UpdateWrapper做原子更新代取快递的状态机看似简单待接单 → 已接单 → 已送达 → 已评价但并发下极易出错两个跑腿员同时点“接单”结果都更新成功造成一单多人接。传统UPDATE ... SET statusACCEPTED WHERE id? AND statusCREATED在 MyBatis-Plus 里用UpdateWrapper更清晰// CourierService.java Transactional public boolean acceptCourierOrder(Long orderId, Long runnerId) { UpdateWrapperOrderInfo wrapper new UpdateWrapper(); wrapper.eq(id, orderId) .eq(status, CREATED) // 严格限定只更新 CREATED 状态 .set(status, ACCEPTED) .set(assignee_id, runnerId) .set(update_time, LocalDateTime.now()); int updated orderInfoMapper.update(null, wrapper); return updated 1; // 返回 true 表示更新成功影响行数为 1 }参数说明update(null, wrapper)第一个参数为null表示不传实体对象只用wrapper构建 WHERE 和 SETupdated 1是关键判断——如果返回 0说明已被别人抢走前端提示“该订单已被接走”事务注解Transactional保证后续扣信用分、发通知等操作原子性。4. MySQL 优化与避坑校园场景下最容易被忽视的 4 个性能雷区4.1 雷区一JSON字段没建虚拟列索引查询慢 10 倍前面JSON_CONTAINS(location, 东区)查询如果location字段没索引全表扫描 10 万订单时耗时 2.3s。解决方案是建生成列Generated Column 普通索引-- 在 order_info 表上添加 campus 虚拟列 ALTER TABLE order_info ADD COLUMN campus VARCHAR(16) GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(location, $.campus))) STORED; -- 为 campus 列建索引 CREATE INDEX idx_campus_status ON order_info(campus, status);现象首页订单列表加载超过 3 秒原因JSON_CONTAINS无法使用普通索引每次都要解析整个 JSON 字符串解决STORED表示物理存储该列idx_campus_status覆盖查询中最常出现的campus status组合实测查询降至 80ms。4.2 雷区二order_no用 UUID 导致插入变慢、索引碎片化很多教程教用UUID.randomUUID().toString().replace(-, )生成订单号但在 MySQL InnoDB 中UUID 是随机字符串插入时 B 树频繁分裂写入性能下降 40%且索引页碎片率达 65%。校园场景用时间戳前缀 自增序列更优// OrderNoGenerator.java public static String generateOrderNo(String prefix) { String datePart LocalDate.now().format(DateTimeFormatter.ofPattern(yyyyMMdd)); // 用 Redis INCR 保证当日序列唯一避免数据库自增 ID 暴露业务量 Long seq redisTemplate.opsForValue().increment(order_seq: datePart, 1); return prefix datePart String.format(%04d, seq); // RD202405200001 }现象高峰期创建订单接口平均响应时间从 120ms 升至 450ms原因UUID 主键导致聚簇索引页分裂磁盘 I/O 暴增解决order_no改为VARCHAR(32)前缀RD/MT/CR区分业务类型日期保证有序Redis 序列避免单点瓶颈。4.3 雷区三TEXT字段没设utf8mb4emoji 存储乱码学生发单爱用 emoji“帮我取 SF123456789谢谢 ”。若order_info.content字段用默认utf8实际是utf8mb3emoji 会存成?或报错Incorrect string value。必须显式指定字符集ALTER TABLE order_info MODIFY COLUMN content TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;现象用户提交含 emoji 的订单后端日志显示content为空或乱码原因MySQL 5.7 默认utf8不支持 4 字节 Unicode解决utf8mb4是唯一正确选择COLLATE utf8mb4_unicode_ci支持中文排序。4.4 雷区四没配wait_timeout长连接被 MySQL 主动断开SpringBoot 默认 HikariCP 连接池connection-timeout3000030秒但 MySQL 服务端wait_timeout默认 28800 秒8小时。若小程序用户长时间挂后台连接池里的连接可能被 MySQL kill下次请求抛Communications link failure。必须两端对齐# application.yml spring: datasource: hikari: connection-timeout: 30000 validation-timeout: 3000 idle-timeout: 600000 # 10分钟空闲回收 max-lifetime: 1800000 # 30分钟最大存活 # 关键启用连接测试 connection-test-query: SELECT 1-- MySQL 服务端执行 SET GLOBAL wait_timeout 1800; -- 30分钟与 max-lifetime 对齐 SET GLOBAL interactive_timeout 1800;现象凌晨 3 点用户首次打开小程序首页空白控制台报数据库连接异常原因连接池维持的连接被 MySQL 主动关闭HikariCP 没及时检测解决connection-test-query在借连接前执行SELECT 1确保连接有效max-lifetime小于wait_timeout主动淘汰老化连接。5. 微信小程序前端对接实战三个必须处理的“微信特供”问题5.1 登录态持久化别信wx.setStorageSync用wx.cloud.callFunction才可靠很多教程教前端把后端返回的token存wx.setStorageSync(token, token)但微信小程序Storage 有 10MB 上限且清理缓存时会丢失。更糟的是wx.getStorageSync(token)在某些安卓机型上会偶发返回undefined。正确做法是用wx.checkSession() 后端 token 续期// app.js onLaunch onLaunch: function () { const token wx.getStorageSync(token); if (token) { // 检查 session 是否有效微信会自动刷新 login session wx.checkSession({ success: () { // session 有效直接用 token 请求业务接口 this.globalData.token token; }, fail: () { // session 过期重新登录 this.login(); } }); } else { this.login(); } }, login: function () { wx.login({ success: res { wx.request({ url: https://api.yourdomain.com/login, method: POST, data: { code: res.code }, success: r { wx.setStorageSync(token, r.data.data.token); this.globalData.token r.data.data.token; } }); } }); }逻辑说明wx.checkSession()是微信原生 API比自己实现 token 过期判断更准this.globalData.token是全局变量避免每个页面重复取 Storagewx.setStorageSync仍是必要步骤但只是辅助核心靠checkSession保活。5.2 获取手机号button open-typegetPhoneNumber的兼容性填坑微信要求必须用button open-typegetPhoneNumber获取手机号但iOS 16.4 和部分安卓厂商 ROM 会拦截该按钮。血泪经验必须加bindgetphonenumber事件监听并在fail回调里降级!-- index.wxml -- button open-typegetPhoneNumber bindgetphonenumberonGetPhoneNumber 授权手机号快速匹配 /button// index.js onGetPhoneNumber(e) { if (e.detail.code) { // 成功发送 code 到后端解密 wx.request({ url: https://api.yourdomain.com/bind-phone, method: POST, data: { encryptedData: e.detail.encryptedData, iv: e.detail.iv, code: e.detail.code }, success: res { wx.showToast({ title: 绑定成功 }); } }); } else { // 失败可能是用户拒绝或系统不支持 wx.showModal({ title: 授权失败, content: 请手动输入手机号或检查微信版本是否为最新, showCancel: false, confirmText: 知道了 }); } }参数说明e.detail.code是微信返回的临时 code后端用它换session_keye.detail.encryptedData和iv是加密数据必须原样传给后端——前端解密会暴露session_key违反微信安全规范。5.3 地理位置权限wx.getLocation的“一次授权”陷阱校园场景必须知道用户在哪个校区但wx.getLocation在 iOS 上首次调用会弹窗用户点“不允许”后再次调用直接失败不再弹窗。必须提前用wx.openSetting引导// utils/location.js export function requestLocationPermission() { return new Promise((resolve, reject) { wx.getSetting({ success: res { if (res.authSetting[scope.userLocation]) { resolve(); } else { wx.authorize({ scope: scope.userLocation, success: () resolve(), fail: () { // 授权失败跳转设置页 wx.openSetting({ success: settingRes { if (settingRes.authSetting[scope.userLocation]) { resolve(); } else { reject(new Error(未开启定位权限)); } } }); } }); } } }); }逻辑说明先getSetting查当前状态避免重复弹窗authorize是静默授权不弹窗只有authorize失败才openSetting把用户带到设置页——这是微信允许的唯一合规路径。6. 生产部署 checklist从本地调试到上线的 7 个必验环节6.1 数据库迁移用 Flyway 做版本化 SQL 管理拒绝手工改库本地开发用schema.sql和data.sql初始化但上线时必须用Flyway管理 SQL 版本否则“张三改了表结构李四没同步上线就炸”。配置如下!-- pom.xml -- dependency groupIdorg.flywaydb/groupId artifactIdflyway-core/artifactId /dependency# application-prod.yml spring: flyway: enabled: true locations: classpath:db/migration baseline-on-migrate: true在src/main/resources/db/migration下建文件V1__init_schema.sql建sys_user,user_capability,order_info表V2__add_campus_virtual_column.sql加campus虚拟列和索引V3__modify_content_charset.sqlALTER TABLE order_info MODIFY COLUMN content TEXT CHARACTER SET utf8mb4验证点部署时flyway info输出应显示所有脚本Success且current版本号与最新脚本一致若某脚本失败Flyway 会阻断启动强制人工介入。6.2 接口压测用 JMeter 模拟“晚高峰快递柜爆单”场景校园场景峰值明确每天 18:00–19:00快递柜集中取件。用 JMeter 做 300 并发、持续 10 分钟压测重点关注POST /order/create创建代取单GET /order/list?statusCREATEDcampus东区跑腿员刷单POST /order/accept接单关键指标阈值接口P95 响应时间错误率CPU 使用率创建订单≤ 300ms 0.1% 70%刷单列表≤ 500ms 0.5% 65%接单≤ 200ms 0.01% 75%验证方法JMeter 脚本中加Backend Listener结果写入 InfluxDB Grafana 可视化若 CPU 持续 80%检查是否有慢 SQL用show processlist抓StateSending data的长事务。6.3 微信审核预检三个高频被拒点自查表微信小程序审核不通过80% 是前端问题。上线前必须自查项目正确做法审核雷区隐私协议在登录页底部加“《用户隐私协议》”链接点击跳转 H5 页面协议中明确写清“收集位置信息用于匹配附近跑腿员”协议文本藏在二级菜单或写“用于提升服务质量”这种模糊表述手机号授权button open-typegetPhoneNumber必须有可见文案如“授权手机号快速匹配”且按钮不可 disabled用 div 模拟按钮或按钮文字是“下一步”这种无意义文案地理位置wx.getLocation调用前必须有引导文案“开启定位为您推荐东区跑腿员”直接调用getLocation无任何提示验证技巧用“微信开发者工具 → 真机调试 → 审核专用模式”模拟审核员视角走一遍流程截图保存每一步操作留作申诉证据。6.4 日志与告警用 Logback 钉钉机器人做故障秒级响应生产环境必须有日志分级和告警。logback-spring.xml配置!-- 只记录 ERROR 级别到文件 -- appender nameFILE_ERROR classch.qos.logback.core.rolling.RollingFileAppender filter classch.qos.logback.core.filter.LevelFilter levelERROR/level onMatchACCEPT/onMatch onMismatchDENY/onMismatch /filter filelogs/error.log/file rollingPolicy classch.qos.logback.core.rolling.TimeBasedRollingPolicy fileNamePatternlogs/error.%d{yyyy-MM-dd}.%i.log/fileNamePattern timeBasedFileNamingAndTriggeringPolicy classch.qos.logback.core.rolling.SizeAndTimeBasedFNATP maxFileSize100MB/maxFileSize /timeBasedFileNamingAndTriggeringPolicy /rollingPolicy /appender !-- ERROR 日志自动发钉钉 -- appender nameDINGTALK classcom.github.houbb.log.dingtalk.LogDingTalkAppender webhookhttps://oapi.dingtalk.com/robot/send?access_tokenxxx/webhook keywordERROR/keyword /appender验证点手动抛throw new RuntimeException(test error);5 秒内钉钉收到告警error.log文件大小不超过 100MB旧日志自动归档。6.5 HTTPS 强制Nginx 配置 301 重定向杜绝 http 请求微信要求所有请求必须 https。Nginx 配置server { listen 80; server_name api.yourdomain.com; return 301 https://$server_name$request_uri; # 强制跳转 } server { listen 443 ssl; server_name api.yourdomain.com; ssl_certificate /path/to/fullchain.pem; ssl_certificate_key /path/to/privkey.pem; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }验证方法浏览器访问http://api.yourdomain.com/login必须 301 跳转到httpscurl -I http://api.yourdomain.com 返回HTTP/1.1 301 Moved Permanently。6.6 Redis 连接池HikariCP 式配置避免“连接耗尽”小程序大量用 Redis 存 token、计数器、分布式锁。application.yml中 Redis 配置spring: redis: host: 127.0.0.1 port: 6379 password: yourpassword lettuce: pool: max-active: 20 max-idle: 10 min-idle: 2 max-wait: 3000验证点redis-cli monitor观察连接数高峰期不应超过max-active用redis-cli info clients查connected_clients应稳定在 15–18若blocked_clients 0说明有命令阻塞需查 Lua 脚本或大 key。6.7 回滚预案SQL 回滚脚本 接口降级开关上线不是终点而是开始。每个版本必须附带回滚 SQL如V3__modify_content_charset.sql的逆向脚本V3_rollback.sqlALTER TABLE order_info MODIFY COLUMN content TEXT CHARACTER SET utf8;接口降级开关在application.yml加feature: courier-enabled: true match-enabled: true代码中用Value(${feature.courier-enabled:true})控制线上用 Apollo 或 Nacos 动态修改秒级关闭某功能。我的习惯是每次上线前把回滚 SQL 和降级开关文档存在 Git 仓库/ops/rollback/V20240520/目录下命名规则rollback-{date}-{feature}.sql上线后 2 小时紧盯 Grafana若错误率突增立刻执行回滚或开关降级——这比等用户投诉再反应快 10 分钟。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站