SpringBoot+微信小程序代驾系统实战:从登录态到实时位置同步
2026/9/17 13:54:34 网站建设 项目流程

简介:本资源是一份完整的本科毕业设计论文文档,面向计算机专业学生及Java全栈初学者,聚焦微信小程序代驾系统的工程化落地实践。论文基于SpringBoot后端框架与微信小程序前端,系统覆盖用户/司机双端功能、订单全流程管理、微信支付集成、实时定位对接及管理员后台等核心模块,并包含需求分析、技术选型(Java+MySQL+SpringBoot)、系统实现细节与性能安全优化方案。资源为单个Word文档(.doc格式),全文约5.94MB,结构完整,含中英文摘要、目录、五章正文(绪论、开发工具、系统分析、系统设计、系统实现)、参考文献及附录,内容详实,代码逻辑与业务流程描述清晰。目前已有144人学习下载,适合用于课程设计参考、毕设开题借鉴、SpringBoot实战复现及小程序服务类项目架构学习。

1. 为什么代驾系统必须用 SpringBoot + 微信小程序双端协同?不是技术炫技,而是业务刚性需求

一个真实的代驾订单,从用户在微信里点开小程序、定位出发地、输入目的地、选择车型、确认下单,到司机接单、实时位置共享、行程中状态更新、费用自动结算、服务评价闭环——整个链路涉及高并发定位上报、低延迟状态同步、强事务性支付回调、敏感地理位置脱敏、多角色权限隔离(用户/司机/客服/运营),以及微信生态内天然的用户身份体系复用。纯前端小程序无法承载订单状态机管理、支付验签、司机调度策略、风控规则引擎等核心逻辑;而传统 Java EE 单体架构又难以快速响应微信小程序“即用即走”带来的流量脉冲和灰度发布需求。SpringBoot 成为事实标准,不是因为它语法糖多,而是它用@SpringBootApplication一行注解就封装了嵌入式 Tomcat、自动配置、条件化 Bean 加载、Actuator 健康检查等生产级能力,让开发者能聚焦在「司机如何被合理分配」「超时未接单如何自动转单」「微信支付回调如何防重放」这些真实问题上。本文不讲论文写作套路,只拆解一个可上线、可监控、可横向扩展的代驾系统服务端骨架:从初始化一个 SpringBoot 框架的服务端代码开始,到微信小程序用 code 换取 token 的完整链路落地,再到代驾场景下最关键的实时位置同步与订单状态驱动机制。

2. 初始化一个 SpringBoot 框架的服务端代码:选型、结构与微信生态对接起点

代驾系统对服务端的稳定性、可观测性和微信生态兼容性要求极高。SpringBoot 版本选择不是越新越好——当前(2024 年主流实践)推荐使用SpringBoot 2.7.x3.1.x(若已全面升级 JDK 17+)。避开 3.0.x 初期版本,因其对部分 MyBatis-Plus 插件、旧版 Redis 客户端存在兼容性问题;也避开 2.6.x 之前版本,因其缺少对 Spring Security 5.7+ 的完整支持,而微信登录态校验需依赖 JWT 和 OAuth2 Resource Server 能力。项目结构必须清晰分离关注点:controller层仅做参数校验与微信签名验签;service层封装订单状态机、司机匹配算法、微信支付回调处理;mapper层严格使用 MyBatis-Plus 的LambdaQueryWrapper避免 SQL 注入;config包下必须包含微信配置类与全局异常处理器。

2.1 创建最小可运行 SpringBoot 工程并集成微信基础能力

使用 Spring Initializr(https://start.spring.io/)生成基础工程,勾选以下核心依赖:

  • Spring Web(必备,提供 RESTful 接口)
  • Spring Boot DevTools(开发期热部署)
  • Lombok(减少样板代码)
  • MySQL Driver(代驾数据持久化)
  • MyBatis-Plus(增强 ORM,支持分页、逻辑删除)
  • Spring Boot Starter Data Redis(缓存司机在线状态、订单锁)
  • Spring Boot Starter Validation(参数校验)
  • Spring Boot Starter Security(后续接入微信 OpenID 认证)

提示:不要勾选 Spring Boot Starter OAuth2 Client —— 微信小程序登录是Authorization Code 模式,但由小程序前端发起wx.login()获取code,后端再用该code向微信服务器换取openidsession_key,属于服务端主动调用微信接口,而非标准 OAuth2 授权码流程。因此只需RestTemplateWebClient即可,无需 OAuth2 Client 依赖。

生成后,在application.yml中配置微信基础参数:

# application.yml wechat: appid: wx1234567890abcdef # 小程序 AppID,从微信公众平台获取 secret: 1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p # 小程序 AppSecret auth-url: https://api.weixin.qq.com/sns/jscode2session # 微信登录凭证校验接口 mch-id: 1234567890 # 微信支付商户号(后续支付模块启用) api-v3-key: your-api-v3-key-here # 微信支付 APIv3 密钥(32位)

2.2 实现微信小程序 code 换 token 的核心 Controller

微信小程序调用wx.login()后得到临时登录凭证code,必须由服务端向微信服务器发起 HTTPS 请求换取openidsession_keyunionid(若绑定公众号)。此接口有调用频率限制(2000 次/天/IP),且code有效期仅 5 分钟,必须异步快速处理。

// src/main/java/com/example/daidai/controller/AuthController.java @RestController @RequestMapping("/api/v1/auth") @Slf4j public class AuthController { @Value("${wechat.appid}") private String appId; @Value("${wechat.secret}") private String secret; @Value("${wechat.auth-url}") private String authUrl; @Autowired private RestTemplate restTemplate; /** * 小程序前端传入 code,后端调用微信接口换取 openid 和 session_key * @param code 小程序 wx.login() 返回的临时登录凭证 * @return { "openid": "...", "session_key": "...", "unionid": "..." } */ @PostMapping("/jscode2session") public ResponseEntity<Map<String, String>> jsCode2Session(@RequestParam String code) { // 构建微信请求参数 String url = String.format("%s?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code", authUrl, appId, secret, code); try { // 使用 RestTemplate 发起 GET 请求 Map<String, String> response = restTemplate.getForObject(url, Map.class); if (response == null || response.containsKey("errcode")) { log.warn("微信 code 换 session 失败,code={}, response={}", code, response); return ResponseEntity.badRequest().body(Map.of("error", "微信验证失败")); } // 成功返回 openid 和 session_key,供后续生成自定义登录态 return ResponseEntity.ok(response); } catch (Exception e) { log.error("调用微信 jscode2session 接口异常", e); return ResponseEntity.status(500).body(Map.of("error", "服务内部错误")); } } }
参数说明与关键逻辑:
  • @RequestParam String code:强制要求前端以?code=xxx方式传参,避免 POST body 解析复杂度;
  • String.format(...)拼接 URL 是微信官方要求格式,grant_type=authorization_code不可省略;
  • restTemplate.getForObject(..., Map.class)直接解析微信返回的 JSON 为Map<String, String>,微信返回字段为openidsession_keyunionid(若用户已关注公众号)、errcode(失败时)、errmsg(失败时);
  • 日志记录code值用于排查(注意:生产环境需脱敏,如只记前 3 位);
  • 返回ResponseEntity显式控制 HTTP 状态码,便于小程序前端统一错误处理。

2.3 微信登录态的二次封装:从 session_key 到 JWT Token

微信返回的session_key不能直接暴露给前端,它是解密用户敏感数据(如手机号)的密钥。服务端应基于openid生成自己的 JWT Token,并存入 Redis 设置过期时间(建议 2 小时),同时将session_keyopenid绑定存储(用于后续解密手机号)。这是代驾系统安全基石。

// src/main/java/com/example/daidai/config/JwtConfig.java @Configuration public class JwtConfig { @Value("${jwt.secret:daidai-secret-key-2024}") private String jwtSecret; @Value("${jwt.expiration:7200}") // 2小时 private long jwtExpiration; @Bean public JwtParser jwtParser() { return Jwts.parserBuilder() .setSigningKey(Keys.hmacShaKeyFor(jwtSecret.getBytes(StandardCharsets.UTF_8))) .build(); } } // src/main/java/com/example/daidai/service/TokenService.java @Service @Slf4j public class TokenService { @Autowired private StringRedisTemplate redisTemplate; @Autowired private JwtParser jwtParser; @Value("${jwt.expiration:7200}") private long expiration; /** * 生成用户自定义 Token,绑定 openid,并缓存 session_key * @param openid 微信唯一标识 * @param sessionKey 微信返回的会话密钥 * @return JWT Token 字符串 */ public String generateToken(String openid, String sessionKey) { String token = Jwts.builder() .setSubject(openid) .claim("iat", System.currentTimeMillis()) .claim("exp", System.currentTimeMillis() + expiration * 1000) .signWith(Keys.hmacShaKeyFor("daidai-secret-key-2024".getBytes()), SignatureAlgorithm.HS256) .compact(); // 缓存 session_key,key 为 "session_key:" + openid,过期时间与 token 一致 redisTemplate.opsForValue().set( "session_key:" + openid, sessionKey, Duration.ofSeconds(expiration) ); return token; } /** * 校验 Token 并返回 openid */ public String validateToken(String token) { try { Jws<Claims> claimsJws = jwtParser.parseClaimsJws(token); return claimsJws.getBody().getSubject(); } catch (Exception e) { log.warn("JWT token 校验失败: {}", token.substring(0, Math.min(10, token.length()))); return null; } } }
关键设计点:
  • generateToken方法将openid作为 JWTsubject,便于后续所有接口通过@RequestHeader("Authorization")提取并校验;
  • redisTemplate.opsForValue().set(...)session_keyopenid绑定,设置与 JWT 相同的过期时间,保证两者生命周期一致;
  • validateToken抛弃异常不向上抛,只返回null,由全局异常处理器统一拦截 401;
  • jwtSecret必须配置为强随机字符串,不可硬编码在代码中(应通过环境变量或配置中心注入)。

3. 代驾核心业务落地:订单状态机驱动、司机匹配与实时位置同步

代驾系统区别于普通 O2O 的本质,在于其强状态驱动特性:一个订单从「待接单」→「司机已接单」→「司机到达」→「行程中」→「行程结束」→「待评价」,每个状态转换都触发不同动作(如发短信、更新地图标记、计算费用、释放司机资源)。SpringBoot 本身不提供状态机框架,但可借助spring-statemachine或更轻量的enum+@Transactional实现。此处采用后者,确保状态变更的原子性与可追溯性。

3.1 订单状态枚举与数据库表结构设计

订单主表order_info必须包含status字段,类型为TINYINT(节省空间),对应枚举值:

// src/main/java/com/example/daidai/enums/OrderStatus.java public enum OrderStatus { WAITING_DRIVER(0, "待接单"), DRIVER_ACCEPTED(1, "司机已接单"), DRIVER_ARRIVED(2, "司机已到达"), IN_PROGRESS(3, "行程中"), COMPLETED(4, "行程结束"), CANCELLED(5, "已取消"); private final int code; private final String desc; OrderStatus(int code, String desc) { this.code = code; this.desc = desc; } public int getCode() { return code; } public String getDesc() { return desc; } }

对应 MySQL 表结构(精简版):

CREATE TABLE `order_info` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', `order_no` varchar(32) NOT NULL COMMENT '订单号,全局唯一', `user_openid` varchar(64) NOT NULL COMMENT '用户 openid', `driver_openid` varchar(64) DEFAULT NULL COMMENT '司机 openid', `start_location` point NOT NULL COMMENT '起点坐标(POINT 类型)', `end_location` point NOT NULL COMMENT '终点坐标', `status` tinyint NOT NULL DEFAULT '0' COMMENT '订单状态,见 OrderStatus', `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`), SPATIAL KEY `idx_start_location` (`start_location`), KEY `idx_user_openid` (`user_openid`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='代驾订单主表';

注意:start_locationend_location使用 MySQL 的POINT类型,配合ST_Distance_Sphere函数可高效计算司机与用户的球面距离(单位:米),比传统lat/lon字段 + 复杂公式快 10 倍以上。

3.2 基于事务的订单状态变更 Service 方法

状态变更必须在一个数据库事务中完成,且需校验前置状态合法性,防止非法跳转(如从「待接单」直接到「行程结束」)。

// src/main/java/com/example/daidai/service/OrderService.java @Service @Transactional(rollbackFor = Exception.class) @Slf4j public class OrderService { @Autowired private OrderMapper orderMapper; @Autowired private RedisTemplate<String, Object> redisTemplate; /** * 更新订单状态,带前置状态校验 * @param orderId 订单 ID * @param fromStatus 允许的前置状态(可变参数,支持多个) * @param toStatus 目标状态 * @return 影响行数,1 表示成功 */ public int updateStatus(Long orderId, OrderStatus toStatus, OrderStatus... fromStatus) { // 查询当前订单 OrderInfo order = orderMapper.selectById(orderId); if (order == null) { log.warn("订单不存在,orderId={}", orderId); return 0; } // 校验前置状态 boolean validFrom = false; for (OrderStatus s : fromStatus) { if (s.getCode() == order.getStatus()) { validFrom = true; break; } } if (!validFrom) { log.warn("订单状态非法跳转,orderId={}, current={}, expected={}", orderId, order.getStatus(), Arrays.toString(fromStatus)); throw new IllegalArgumentException("订单状态不合法"); } // 执行更新 UpdateWrapper<OrderInfo> wrapper = new UpdateWrapper<>(); wrapper.eq("id", orderId) .eq("status", order.getStatus()) // 乐观锁:确保更新时状态未被其他线程修改 .set("status", toStatus.getCode()) .set("updated_at", LocalDateTime.now()); int rows = orderMapper.update(null, wrapper); if (rows == 1) { // 状态变更成功,发布事件(如推送消息、更新司机状态) publishOrderStatusEvent(orderId, order.getStatus(), toStatus.getCode()); } return rows; } private void publishOrderStatusEvent(Long orderId, int oldStatus, int newStatus) { // 这里可集成 RocketMQ/Kafka,或直接调用微信模板消息接口 // 示例:向司机 openid 发送「您有新订单」模板消息 log.info("订单 {} 状态从 {} 变更为 {}", orderId, oldStatus, newStatus); } }
关键保障机制:
  • @Transactional(rollbackFor = Exception.class)确保整个方法内所有 DB 操作原子性;
  • UpdateWrappereq("status", order.getStatus())是乐观锁实现,防止并发状态下两个线程同时读到「待接单」状态,然后都更新为「司机已接单」;
  • publishOrderStatusEvent是扩展点,实际项目中应解耦为事件驱动架构,此处留作占位;
  • 异常IllegalArgumentException会被全局异常处理器捕获,返回标准化错误码(如40001表示状态非法)。

3.3 司机匹配算法:基于 Redis Geo 的实时就近查找

代驾的核心体验是「秒级响应」。不能每次下单都查一次 MySQL,必须将在线司机坐标预热到 Redis。利用 Redis 的GEOADDGEORADIUS命令,可在毫秒级内找出 3 公里内的可用司机。

// src/main/java/com/example/daidai/service/DriverMatchService.java @Service @Slf4j public class DriverMatchService { @Autowired private StringRedisTemplate redisTemplate; /** * 根据用户坐标查找附近 3 公里内在线司机(按距离升序) * @param userLng 用户经度 * @param userLat 用户纬度 * @param count 最大返回数量 * @return 司机 openid 列表 */ public List<String> findNearbyDrivers(double userLng, double userLat, int count) { String driverGeoKey = "driver:online:geo"; // GEORADIUS 命令:在 driverGeoKey 中查找半径 3000 米内的司机,返回 member(即 openid)和距离 GeoRadiusCommandArgs args = GeoRadiusCommandArgs.newGeoRadiusArgs() .withDistance() .sortAscending() .count(count); GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate.opsForGeo() .radius(driverGeoKey, new Circle(new Point(userLng, userLat), new Distance(3000, Metrics.METERS)), args); return results.getContent().stream() .map(GeoResult::getContent) .map(GeoLocation::getName) .collect(Collectors.toList()); } /** * 司机上线时,将其坐标写入 Redis Geo * @param driverOpenid 司机 openid * @param lng 经度 * @param lat 纬度 */ public void driverOnline(String driverOpenid, double lng, double lat) { String driverGeoKey = "driver:online:geo"; redisTemplate.opsForGeo().add(driverGeoKey, new Point(lng, lat), driverOpenid); // 同时设置司机在线状态为 1,过期时间 30 分钟(心跳续期) redisTemplate.opsForValue().set("driver:status:" + driverOpenid, "1", Duration.ofMinutes(30)); } /** * 司机下线或超时,从 Geo 中移除 */ public void driverOffline(String driverOpenid) { String driverGeoKey = "driver:online:geo"; redisTemplate.opsForGeo().remove(driverGeoKey, driverOpenid); redisTemplate.delete("driver:status:" + driverOpenid); } }
Redis Geo 使用要点:
  • driver:online:geo是 Redis 中的 Geo 集合 key,每个元素是(经度, 纬度, openid)
  • GEORADIUS返回结果默认无序,必须加.sortAscending()才按距离由近到远排列;
  • withDistance()让结果包含距离字段,便于前端展示「距离您 1.2km」;
  • 司机在线状态需双重保障:Geo 数据 +driver:status:xxx字符串键,后者用于快速判断是否在线(避免每次 GEO 查询);
  • 心跳机制:司机 App 每 20 秒上报一次位置,调用driverOnline续期,超时未续则自动下线。

4. 微信小程序端对接细节:code 换 token、位置上报与 WebSocket 实时通信

小程序端是用户触达的第一入口,其与 SpringBoot 后端的协作质量直接决定体验。重点解决三个高频问题:code如何安全传递、用户实时位置如何高效上报、订单状态变更如何零延迟推送。

4.1 小程序前端获取 code 并调用后端接口的完整流程

微信小程序wx.login()返回的code是一次性凭证,必须在 5 分钟内使用。前端不应自行拼接微信 URL,而应调用后端/api/v1/auth/jscode2session接口。

// pages/login/login.js Page({ data: { userInfo: null, hasUserInfo: false }, // 用户点击「授权登录」按钮 bindGetUserInfo(e) { if (e.detail.userInfo) { this.setData({ userInfo: e.detail.userInfo, hasUserInfo: true }); this.login(); // 开始登录流程 } }, login() { wx.login({ success: (res) => { if (res.code) { // 将 code 发送给后端 wx.request({ url: 'https://your-server.com/api/v1/auth/jscode2session', method: 'POST', data: { code: res.code }, success: (resp) => { if (resp.data.openid) { // 后端返回 openid 和 session_key,生成自定义 token const customToken = this.generateCustomToken(resp.data); wx.setStorageSync('token', customToken); wx.switchTab({ url: '/pages/index/index' }); } else { wx.showToast({ title: '登录失败', icon: 'none' }); } }, fail: () => { wx.showToast({ title: '网络错误', icon: 'none' }); } }); } else { console.log('登录失败!' + res.errMsg); } } }); }, generateCustomToken(data) { // 此处应由后端生成 JWT,前端只存储和携带 // 实际项目中,后端 /jscode2session 接口应直接返回 { token: 'xxx', expires_in: 7200 } return data.token || ''; } });

注意:wx.login()必须在用户触发行为(如点击按钮)后调用,否则 iOS 会静默失败;wx.requesturl必须是 HTTPS,且域名已在小程序后台「request 合法域名」中配置。

4.2 用户位置上报:节制、精准、合规

微信小程序获取用户位置需用户授权,且wx.getLocation()在后台无法持续调用。代驾场景下,只需在「下单前」和「行程中」两个节点上报。

// utils/location.js const getLocation = () => { return new Promise((resolve, reject) => { wx.getLocation({ type: 'gcj02', // 国测局坐标系,与腾讯地图一致 success: (res) => { resolve({ latitude: res.latitude, longitude: res.longitude, accuracy: res.accuracy }); }, fail: (err) => { reject(err); } }); }); }; // pages/order/create.js Page({ data: { startLocation: null, endLocation: null }, // 下单前获取起点 async getStartLocation() { try { const loc = await getLocation(); this.setData({ startLocation: loc }); // 上报起点到后端,用于司机匹配 wx.request({ url: 'https://your-server.com/api/v1/order/start-location', method: 'POST', header: { 'Authorization': wx.getStorageSync('token') }, data: loc, success: () => console.log('起点上报成功') }); } catch (e) { wx.showToast({ title: '定位失败,请检查权限', icon: 'none' }); } } });
合规提醒:
  • 必须在app.json中声明requiredPrivateInfos: ["getLocation"]
  • 首次调用wx.getLocation()前,需用wx.openSetting()引导用户开启定位权限;
  • 上报的经纬度应为gcj02坐标系,与腾讯地图 SDK 一致,避免偏移。

4.3 订单状态实时推送:WebSocket 替代轮询

订单状态变更(如司机接单)必须秒级触达用户。轮询(每隔 3 秒查一次)浪费资源且延迟高。SpringBoot 内置spring-boot-starter-websocket,结合小程序wx.connectSocket,可建立长连接。

// src/main/java/com/example/daidai/config/WebSocketConfig.java @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(new OrderWebSocketHandler(), "/ws/order") .setAllowedOrigins("*"); // 小程序不校验 origin } } // src/main/java/com/example/daidai/handler/OrderWebSocketHandler.java @Component public class OrderWebSocketHandler extends TextWebSocketHandler { private static final Map<String, Session> USER_SESSIONS = new ConcurrentHashMap<>(); @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { // 从请求参数或 Header 中提取 openid(需前端在 URL 中带 token) String token = session.getUri().getQuery(); String openid = extractOpenidFromToken(token); // 实现略 USER_SESSIONS.put(openid, session); System.out.println("用户 " + openid + " 建立 WebSocket 连接"); } @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { // 可接收客户端心跳或指令 } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { USER_SESSIONS.values().remove(session); } // 对外调用方法:向指定用户推送订单状态 public static void sendOrderUpdate(String openid, String message) { Session session = USER_SESSIONS.get(openid); if (session != null && session.isOpen()) { try { session.sendMessage(new TextMessage(message)); } catch (IOException e) { USER_SESSIONS.remove(openid); } } } }

小程序端连接:

// pages/order/detail.js Page({ data: { socketOpen: false, orderStatus: 'WAITING_DRIVER' }, connectSocket() { const token = wx.getStorageSync('token'); wx.connectSocket({ url: 'wss://your-server.com/ws/order?token=' + token, success: () => console.log('WebSocket 连接成功') }); wx.onSocketOpen(() => { this.setData({ socketOpen: true }); }); wx.onSocketMessage((res) => { const data = JSON.parse(res.data); this.setData({ orderStatus: data.status }); if (data.status === 'DRIVER_ACCEPTED') { wx.showToast({ title: '司机已接单', icon: 'success' }); } }); } });
关键配置:
  • WebSocket 路径/ws/order必须与 Nginx 反向代理配置一致(若使用);
  • 小程序wx.connectSocketurl必须是wss://(HTTPS 的 WebSocket);
  • USER_SESSIONS使用ConcurrentHashMap保证线程安全,afterConnectionClosed中清理避免内存泄漏;
  • 实际项目中,应增加心跳保活(ping/pong)和断线重连逻辑。

5. 生产环境必调的 3 个 SpringBoot 参数:性能、安全与可观测性

本地跑通不等于线上可用。代驾系统面对真实用户时,必须调整 SpringBoot 默认参数,覆盖性能压测、安全加固、故障定位三方面。

5.1 JVM 与 Tomcat 线程池调优:应对订单创建高峰

微信小程序流量具有明显波峰(晚高峰 20:00–22:00),此时订单创建接口(POST /api/v1/order)QPS 可达 500+。默认 Tomcat 线程池(200 线程)和 JVM 堆内存(512MB)必然成为瓶颈。

# application-prod.yml server: tomcat: max-connections: 10000 # 最大连接数 accept-count: 1000 # 排队等待的最大连接数 max-threads: 500 # 最大工作线程数(根据 CPU 核数 * 2 ~ 4 设置) min-spare-threads: 50 # 最小空闲线程数,避免频繁创建销毁 spring: profiles: active: prod jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 # JVM 启动参数(添加到启动脚本) # -Xms2g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+HeapDumpOnOutOfMemoryError
调优依据:
  • max-threads: 500:假设服务器为 16 核 CPU,I/O 密集型应用(含数据库、Redis、HTTP 调用)线程数设为CPU * 3 = 48,但代驾订单创建涉及多次 DB 写入、Redis 操作、微信回调,故提升至 500 更稳妥;
  • accept-count: 1000:当所有线程忙时,新连接进入队列,避免直接拒绝;
  • JVM 参数中-XX:+UseG1GC启用 G1 垃圾收集器,-XX:MaxGCPauseMillis=200控制 GC 暂停时间不超过 200ms,保障接口响应稳定。

5.2 安全加固:禁用敏感端点与强制 HTTPS

SpringBoot Actuator 提供/actuator/env/actuator/beans等端点,泄露系统环境与 Bean 信息,是黑客扫描重点。生产环境必须关闭或加鉴权。

# application-prod.yml management: endpoints: web: exposure: include: health,info,metrics,prometheus,loggers # 只暴露必要端点 endpoint: health: show-details: when_authorized # 健康检查详情需认证 loggers: show-long-names: false security: roles: ACTUATOR # 访问 actuator 需 ACTUATOR 角色

同时,在SecurityConfig中强制 HTTPS:

@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .requiresChannel() .requestMatchers("/api/**").requiresSecure() // 所有 /api 接口强制 HTTPS .and() .authorizeHttpRequests(authz -> authz .requestMatchers("/actuator/**").hasRole("ACTUATOR") .requestMatchers("/api/v1/auth/**", "/api/v1/public/**").permitAll() .anyRequest().authenticated() ); return http.build(); } }

5.3 可观测性:集成 Prometheus + Grafana 监控订单核心指标

代驾系统最需监控的 3 个黄金指标:订单创建成功率、司机匹配平均耗时、WebSocket 连接数。SpringBoot Actuator + Micrometer 可无缝对接 Prometheus。

<!-- pom.xml --> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> </dependency>
// src/main/java/com/example/daidai/metrics/OrderMetrics.java @Component public class OrderMetrics { private final Timer orderCreateTimer; private final Counter orderCreateSuccessCounter; private final Gauge websocketConnectionGauge; public OrderMetrics(MeterRegistry registry) { this.orderCreateTimer = Timer.builder("order.create.duration") .description("订单创建耗时(毫秒)") .register(registry); this.orderCreateSuccessCounter = Counter.builder("order.create.success") .description("订单创建成功次数") .register(registry); this.websocketConnectionGauge = Gauge.builder("websocket.connections", () -> (double) OrderWebSocketHandler.USER_SESSIONS.size()) .description("当前 WebSocket 连接数") .register(registry); } public void recordCreateSuccess(long durationMs) { orderCreateTimer.record(durationMs, TimeUnit.MILLISECONDS); orderCreateSuccessCounter.increment(); } }

OrderService.createOrder()方法末尾调用orderMetrics.recordCreateSuccess(System.currentTimeMillis() - start)即可。Prometheus 抓取/actuator/prometheus端点,Grafana 配置看板,即可实时看到「过去 5 分钟订单创建成功率跌至 92%」,立刻排查数据库连接池是否耗尽。

提示:websocket.connections指标直接读取ConcurrentHashMap.size(),无锁、零开销,是监控长连接健康度的最轻量方式。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询