From bc92cfb31f097342f7f09648d72527893f64a61d Mon Sep 17 00:00:00 2001 From: sangchengzhi <2305486879@qq.com> Date: Wed, 14 Jan 2026 17:15:54 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BD=93=E6=A3=80=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=EF=BC=8C=E6=96=B0=E5=A2=9E=E5=AD=97=E6=AE=B5=EF=BC=8C?= =?UTF-8?q?=E5=8F=8A=E4=BF=AE=E5=A4=8D=E9=83=A8=E5=88=86bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/guahao/GuahaoApplication.java | 2 +- .../api/task/mapper/UserReserve8Mapper.java | 2 +- .../common/listener/MysqlCleanupListener.java | 101 ++++++++++++++++++ .../java/com/guahao/common/util/XmlUtil.java | 25 +++-- .../h5/hsjc/controller/HsjcController.java | 8 +- .../guahao/h5/hsjc/service/HsjcService.java | 11 +- .../guahao/h5/hsjc/vo/ProgramDetailVO.java | 10 ++ .../impl/YbServiceImpl/YbServiceImpl.java | 19 ++-- src/main/resources/application.properties | 4 + 9 files changed, 155 insertions(+), 27 deletions(-) create mode 100644 src/main/java/com/guahao/common/listener/MysqlCleanupListener.java diff --git a/src/main/java/com/guahao/GuahaoApplication.java b/src/main/java/com/guahao/GuahaoApplication.java index 4920eb0..41d3cbc 100644 --- a/src/main/java/com/guahao/GuahaoApplication.java +++ b/src/main/java/com/guahao/GuahaoApplication.java @@ -15,7 +15,7 @@ import tk.mybatis.spring.annotation.MapperScan; @EnableAspectJAutoProxy(proxyTargetClass = true) @SpringBootApplication(scanBasePackages = "com") @MapperScan(basePackages = {"com.guahao.*.mapper"}) -@ServletComponentScan(basePackages = {"com.guahao.common.filter"}) +@ServletComponentScan(basePackages = {"com.guahao.common.filter", "com.guahao.common.listener"}) @ComponentScan(basePackages = {"com.guahao"}) @EnableScheduling public class GuahaoApplication extends SpringBootServletInitializer { diff --git a/src/main/java/com/guahao/api/task/mapper/UserReserve8Mapper.java b/src/main/java/com/guahao/api/task/mapper/UserReserve8Mapper.java index e2f45a8..6268b76 100644 --- a/src/main/java/com/guahao/api/task/mapper/UserReserve8Mapper.java +++ b/src/main/java/com/guahao/api/task/mapper/UserReserve8Mapper.java @@ -1,6 +1,6 @@ package com.guahao.api.task.mapper; -import com.guahao.api.task.model.UserMzjf; + import com.guahao.api.task.model.UserReserve8; import com.guahao.common.base.BaseMapper; import org.apache.ibatis.annotations.Mapper; diff --git a/src/main/java/com/guahao/common/listener/MysqlCleanupListener.java b/src/main/java/com/guahao/common/listener/MysqlCleanupListener.java new file mode 100644 index 0000000..7cd389f --- /dev/null +++ b/src/main/java/com/guahao/common/listener/MysqlCleanupListener.java @@ -0,0 +1,101 @@ +package com.guahao.common.listener; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextClosedEvent; +import org.springframework.stereotype.Component; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.annotation.WebListener; +import java.lang.reflect.Method; +import java.sql.DriverManager; +import java.sql.SQLException; + +/** + * MySQL连接清理监听器,用于在应用关闭时正确清理MySQL连接 + */ +@Component +@WebListener +public class MysqlCleanupListener implements ServletContextListener, ApplicationListener { + + private static final Logger logger = LoggerFactory.getLogger(MysqlCleanupListener.class); + + @Override + public void contextInitialized(ServletContextEvent sce) { + // 应用启动时无需特殊处理 + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + cleanup(); + } + + @Override + public void onApplicationEvent(ContextClosedEvent event) { + cleanup(); + } + + /** + * 清理MySQL连接和相关资源 + */ + private void cleanup() { + try { + // 注销驱动程序,防止内存泄漏 + deregisterJdbcDrivers(); + + // 尝试停止MySQL的AbandonedConnectionCleanupThread + stopMySqlAbandonedConnectionCleanupThread(); + + logger.info("MySQL连接及相关资源清理完成"); + } catch (Exception e) { + logger.error("清理MySQL连接资源时发生错误", e); + } + } + + /** + * 注销JDBC驱动程序 + */ + private void deregisterJdbcDrivers() { + try { + DriverManager.getDrivers(); + java.sql.Driver driver; + while (DriverManager.getDrivers().hasMoreElements()) { + driver = DriverManager.getDrivers().nextElement(); + if (driver.getClass().getName().startsWith("com.mysql")) { + DriverManager.deregisterDriver(driver); + logger.info("已注销MySQL驱动: {}", driver.getClass().getName()); + } + } + } catch (SQLException e) { + logger.error("注销JDBC驱动时发生错误", e); + } + } + + /** + * 停止MySQL的AbandonedConnectionCleanupThread线程 + */ + private void stopMySqlAbandonedConnectionCleanupThread() { + try { + Class clazz; + try { + clazz = Class.forName("com.mysql.jdbc.AbandonedConnectionCleanupThread"); + } catch (ClassNotFoundException e) { + // 对于MySQL 8.0+版本,类名可能不同 + try { + clazz = Class.forName("com.mysql.cj.jdbc.AbandonedConnectionCleanupThread"); + } catch (ClassNotFoundException ex) { + logger.debug("未找到MySQL连接清理线程类,可能使用的是不同版本的驱动"); + return; + } + } + + Method shutdownMethod = clazz.getMethod("shutdown"); + shutdownMethod.invoke(null); + logger.info("已停止MySQL AbandonedConnectionCleanupThread线程"); + } catch (Exception e) { + logger.warn("停止MySQL连接清理线程时发生错误", e); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/guahao/common/util/XmlUtil.java b/src/main/java/com/guahao/common/util/XmlUtil.java index 1d54b13..fb3d323 100644 --- a/src/main/java/com/guahao/common/util/XmlUtil.java +++ b/src/main/java/com/guahao/common/util/XmlUtil.java @@ -702,17 +702,20 @@ public class XmlUtil { str.append(vo.getPaynature()); str.append(""); // 当vo.getYbzhamount和ybtcamount都是0的时候,且zfamount>=0则此处用微信,其他情况用医保 - if ((vo.getYbzhamount() == null || vo.getYbzhamount().compareTo(BigDecimal.valueOf(0.00)) == 0) && - (vo.getYbtcamount() == null || vo.getYbtcamount().compareTo(BigDecimal.valueOf(0.00)) == 0) && - vo.getZfamount().compareTo(BigDecimal.valueOf(0.00)) >= 0) { - str.append(""); - str.append("微信"); - str.append(""); - } else { - str.append(""); - str.append("医保"); - str.append(""); - } + // if ((vo.getYbzhamount() == null || vo.getYbzhamount().compareTo(BigDecimal.valueOf(0.00)) == 0) && + // (vo.getYbtcamount() == null || vo.getYbtcamount().compareTo(BigDecimal.valueOf(0.00)) == 0) && + // vo.getZfamount().compareTo(BigDecimal.valueOf(0.00)) >= 0) { + // str.append(""); + // str.append("微信"); + // str.append(""); + // } else { + // str.append(""); + // str.append("医保"); + // str.append(""); + // } + str.append(""); + str.append("微信"); + str.append(""); str.append(""); str.append(vo.getPowertranid()); str.append(""); diff --git a/src/main/java/com/guahao/h5/hsjc/controller/HsjcController.java b/src/main/java/com/guahao/h5/hsjc/controller/HsjcController.java index 5f54d60..15f5c50 100644 --- a/src/main/java/com/guahao/h5/hsjc/controller/HsjcController.java +++ b/src/main/java/com/guahao/h5/hsjc/controller/HsjcController.java @@ -290,11 +290,11 @@ public class HsjcController { */ @RequestMapping("/opTiQuery") @WebLog(description = "opTiQuery",logResponse = false) - public Object opTiQuery(Integer userId, String token,String HospitalZone ,String startDate, String endDate) { + public Object opTiQuery(Integer userId, String token,String HospitalZone ,String startDate, String endDate,String Type) { try { int retToken = getUserToken(userId, token); if (retToken != 0) { - Map stringObjectMap = hsjcService.opTiQuery(HospitalZone, startDate, endDate); + Map stringObjectMap = hsjcService.opTiQuery(HospitalZone, startDate, endDate,Type); // log.info("后端返回数据为:"+stringObjectMap); return ResponseResult.success(stringObjectMap); } else { @@ -309,11 +309,11 @@ public class HsjcController { */ @RequestMapping("/getDetail") @WebLog(description = "getDetail") - public Object getDetail(Integer userId, String token, String programId,String bookindDate) { + public Object getDetail(Integer userId, String token, String programId,String bookindDate,String Type) { try { int retToken = getUserToken(userId, token); if (retToken != 0) { - Map stringObjectMap = hsjcService.getDetail(programId,bookindDate); + Map stringObjectMap = hsjcService.getDetail(programId,bookindDate,Type); // log.info("后端返回数据为:"+stringObjectMap); return ResponseResult.success(stringObjectMap); } else { diff --git a/src/main/java/com/guahao/h5/hsjc/service/HsjcService.java b/src/main/java/com/guahao/h5/hsjc/service/HsjcService.java index 44170af..972f205 100644 --- a/src/main/java/com/guahao/h5/hsjc/service/HsjcService.java +++ b/src/main/java/com/guahao/h5/hsjc/service/HsjcService.java @@ -151,7 +151,7 @@ public class HsjcService { return resSuc; } - public Map opTiQuery(String hospitalZone, String startDate, String endDate) { + public Map opTiQuery(String hospitalZone, String startDate, String endDate,String type) { Map result = new HashMap<>(); List> dataList = new ArrayList<>(); @@ -166,6 +166,9 @@ public class HsjcService { str.append("<EndDate>"); str.append(endDate); str.append("</EndDate>"); + str.append("<class>"); + str.append(type); + str.append("</class>"); String respXml = SoapUtil.soapMethod4(str.toString(),"mop_canreservationphysicalprogram"); // log.debug("【原始SOAP响应】:\n{}", respXml); @@ -240,7 +243,7 @@ public class HsjcService { return ""; } - public Map getDetail(String programId, String bookindDate) { + public Map getDetail(String programId, String bookindDate,String Type) { try { StringBuffer str = new StringBuffer(); str.append("<program_id>"); @@ -249,6 +252,9 @@ public class HsjcService { str.append("<booking_date>"); str.append(bookindDate); str.append("</booking_date>"); + str.append("<class>"); + str.append(Type); + str.append("</class>"); String respXml = SoapUtil.soapMethod4(str.toString(),"mop_queryphysicalprogramdetail"); @@ -297,6 +303,7 @@ public class HsjcService { str.append("<program_id>").append(programId).append("</program_id>"); str.append("<time_period>").append(detail.getTimePeriod()).append("</time_period>"); str.append("<time>").append(detail.getTime()).append("</time>"); + str.append("<class>").append(detail.getType()).append("</class>"); str.append("</program>"); // 拼接到本地存储的字符串:101,102,103 diff --git a/src/main/java/com/guahao/h5/hsjc/vo/ProgramDetailVO.java b/src/main/java/com/guahao/h5/hsjc/vo/ProgramDetailVO.java index aafb95a..1033db0 100644 --- a/src/main/java/com/guahao/h5/hsjc/vo/ProgramDetailVO.java +++ b/src/main/java/com/guahao/h5/hsjc/vo/ProgramDetailVO.java @@ -5,9 +5,19 @@ public class ProgramDetailVO { private String programId; // 项目ID(字符串或 Long,根据实际类型) private String time; // 具体时间段,如 "8:00-12:00" private String timePeriod; // 时段:AM / PM + private String type; // 项目类型:1 套餐/ 2 单项 // ======= Getters and Setters ======= + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + public String getProgramId() { return programId; } diff --git a/src/main/java/com/guahao/h5/yb/service/impl/YbServiceImpl/YbServiceImpl.java b/src/main/java/com/guahao/h5/yb/service/impl/YbServiceImpl/YbServiceImpl.java index 3ef52cc..731acae 100644 --- a/src/main/java/com/guahao/h5/yb/service/impl/YbServiceImpl/YbServiceImpl.java +++ b/src/main/java/com/guahao/h5/yb/service/impl/YbServiceImpl/YbServiceImpl.java @@ -131,8 +131,11 @@ public class YbServiceImpl implements YbService { return t; }); - // 用于跟踪正在执行延时任务的订单,防止重复执行 - private static final Set pendingDelayedTasks = ConcurrentHashMap.newKeySet(); + // 用于跟踪正在执行findStatus延时任务的订单,防止重复执行 + private static final Set pendingFindStatusTasks = ConcurrentHashMap.newKeySet(); + + // 用于跟踪正在执行关闭未支付订单延时任务的订单,防止重复执行 + private static final Set pendingCloseOrderTasks = ConcurrentHashMap.newKeySet(); @Autowired private WxPayConfig wxPayConfig; @@ -1024,7 +1027,7 @@ public class YbServiceImpl implements YbService { // 启动15分钟后检查订单状态的延时任务 String finalHospOutTradeNo = req.getHosp_out_trade_no(); // 使用防重复机制提交延时任务 - if (pendingDelayedTasks.add(finalHospOutTradeNo)) { + if (pendingCloseOrderTasks.add(finalHospOutTradeNo)) { scheduledExecutor.schedule(() -> { try { // 检查订单状态是否仍为2(未支付) @@ -1040,12 +1043,12 @@ public class YbServiceImpl implements YbService { log.error("检查订单状态异常", e); } finally { // 确保无论成功或失败都从待处理集合中移除 - pendingDelayedTasks.remove(finalHospOutTradeNo); + pendingCloseOrderTasks.remove(finalHospOutTradeNo); } }, 15, TimeUnit.MINUTES); // 15分钟后执行 log.info("已为订单 {} 安排15分钟后检查状态", req.getHosp_out_trade_no()); } else { - log.info("订单 {} 已存在待执行的延时任务,跳过重复提交", req.getHosp_out_trade_no()); + log.info("订单 {} 已存在待执行的关闭订单延时任务,跳过重复提交", req.getHosp_out_trade_no()); } resMap.put("appid", xml.get("appid")); @@ -1708,7 +1711,7 @@ public class YbServiceImpl implements YbService { // 使用独立调度线程池异步延时执行findStatus方法,等待系统状态同步 String hospOutTradeNo = wxResult.getHosp_out_trade_no(); // 使用防重复机制提交延时任务 - if (pendingDelayedTasks.add(hospOutTradeNo)) { + if (pendingFindStatusTasks.add(hospOutTradeNo)) { scheduledExecutor.schedule(() -> { try { log.info("用户退出流程,启用延时执行findStatus方法,写入His"); @@ -1717,11 +1720,11 @@ public class YbServiceImpl implements YbService { log.error("延时执行findStatus异常", e); } finally { // 确保无论成功或失败都从待处理集合中移除 - pendingDelayedTasks.remove(hospOutTradeNo); + pendingFindStatusTasks.remove(hospOutTradeNo); } }, 10, TimeUnit.SECONDS); } else { - log.info("订单 {} 已存在待执行的延时任务,跳过重复提交", hospOutTradeNo); + log.info("订单 {} 已存在待执行的findStatus延时任务,跳过重复提交", hospOutTradeNo); } } } else { diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 5613982..6f9751e 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -37,6 +37,10 @@ druid.testOnReturn=false druid.poolPreparedStatements=true druid.maxPoolPreparedStatementPerConnectionSize=20 druid.filters=stat +# 连接泄露检测配置 +druid.removeAbandoned=true +druid.removeAbandonedTimeout=1800 +druid.logAbandoned=true #数据库连接信息 #spring.datasource.dynamic.datasource.master.driver-class-name=com.mysql.jdbc.Driver #spring.datasource.dynamic.datasource.master.url=jdbc:mysql://localhost:3306/guahao?zeroDateTimeBehavior=convertToNull&serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai