Przeglądaj źródła

连接池配置,异常处理优化

skyline 3 miesięcy temu
rodzic
commit
c5912de7da

+ 31 - 17
admin/src/main/resources/application-dev.yml

@@ -47,36 +47,46 @@ wechat:
 spring:
   datasource:
     druid: #以下是全局默认值,可以全局更改
-      #监控统计拦截的filters
-      filters: stat,slf4j
+      #监控统计拦截的 filters
+      filters: stat,wall,slf4j
       #配置初始化大小/最小/最大
-      initial-size: 2
-      min-idle: 2
-      max-active: 20
-      #获取连接等待超时时间
-      max-wait: 60000
+      initial-size: 10
+      min-idle: 10
+      max-active: 50
+      #获取连接等待超时时间(毫秒)
+      max-wait: 5000
       #间隔多久进行一次检测,检测需要关闭的空闲连接
-      time-between-eviction-runs-millis: 60000
+      time-between-eviction-runs-millis: 30000
       #一个连接在池中最小生存的时间
       min-evictable-idle-time-millis: 300000
-      validation-query: SELECT 'x'
+      max-evictable-idle-time-millis: 900000
+      validation-query: SELECT 1
       test-while-idle: true
       test-on-borrow: false
       test-on-return: false
-      #打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为false
-      pool-prepared-statements: false
-      max-pool-prepared-statement-per-connection-size: 20
+      #打开 PSCache,并指定每个连接上 PSCache 的大小。oracle 设为 true,mysql 设为 false。分库分表较多推荐设置为 false
+      pool-prepared-statements: true
+      max-pool-prepared-statement-per-connection-size: 50
     dynamic:
       primary: db-admin
-      strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
+      strict: false #严格匹配数据源,默认 false. true 未匹配到指定数据源时抛异常,false 使用默认数据源
+      druid:
+        # db-admin 数据源独立配置
+        db-admin:
+          max-active: 30
+          max-wait: 3000
+        # db-miniapp 数据源独立配置
+        db-miniapp:
+          max-active: 40
+          max-wait: 3000
       datasource:
         db-admin:
-          url: jdbc:mysql://server.kuaiyuman.cn:3306/charge_admin?serverTimezone=Asia/Shanghai
+          url: jdbc:mysql://server.kuaiyuman.cn:3306/charge_admin?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
           username: root
           password: KuaiyuMan/*-
           driver-class-name: com.mysql.cj.jdbc.Driver
         db-miniapp:
-          url: jdbc:mysql://server.kuaiyuman.cn:3306/charge_app?serverTimezone=Asia/Shanghai
+          url: jdbc:mysql://server.kuaiyuman.cn:3306/charge_app?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
           username: root
           password: KuaiyuMan/*-
           driver-class-name: com.mysql.cj.jdbc.Driver
@@ -86,13 +96,17 @@ spring:
       host: server.kuaiyuman.cn
       password: KtXA^Zx!TZmLEy(@JjB@2(TVG0kdy5)&
       database: 10
+      timeout: 3000ms
       lettuce:
         pool:
-          min-idle: 1
+          max-active: 50
+          max-idle: 20
+          min-idle: 5
+          max-wait: 2000ms
         cluster:
           refresh:
             adaptive: true
-            period: 20
+            period: 30
   cache:
     type: redis
     redis:

+ 74 - 0
common/src/main/java/com/kym/common/exception/ChargeException.java

@@ -0,0 +1,74 @@
+package com.kym.common.exception;
+
+import com.kym.common.constant.ResponseEnum;
+
+/**
+ * 充电业务异常
+ * 用于处理充电过程中的各类业务异常
+ *
+ * @author skyline
+ */
+public class ChargeException extends BusinessException {
+
+    private static final long serialVersionUID = 1L;
+
+    public ChargeException(String message) {
+        super(ResponseEnum.HTTP_STATUS_500.getCode(), message);
+    }
+
+    public ChargeException(Integer code, String message) {
+        super(code, message);
+    }
+
+    public ChargeException(ResponseEnum responseEnum) {
+        super(responseEnum);
+    }
+
+    public ChargeException(ResponseEnum responseEnum, Throwable cause) {
+        super(responseEnum.getCode(), responseEnum.getMessage());
+        this.initCause(cause);
+    }
+
+    /**
+     * 充电枪离线异常
+     */
+    public static class ConnectorOfflineException extends ChargeException {
+        public ConnectorOfflineException(String connectorId) {
+            super("充电枪" + connectorId + "离线,请稍后重试");
+        }
+    }
+
+    /**
+     * 余额不足异常
+     */
+    public static class InsufficientBalanceException extends ChargeException {
+        private final Double balance;
+
+        public InsufficientBalanceException(Double balance) {
+            super("用户余额不足,当前余额:" + balance);
+            this.balance = balance;
+        }
+
+        public Double getBalance() {
+            return balance;
+        }
+    }
+
+    /**
+     * 预约冲突异常
+     */
+    public static class BookingConflictException extends ChargeException {
+        public BookingConflictException(String message) {
+            super(message);
+        }
+    }
+
+    /**
+     * 设备状态异常
+     */
+    public static class EquipmentStatusException extends ChargeException {
+        public EquipmentStatusException(String equipmentId, String status) {
+            super("设备" + equipmentId + "状态异常:" + status);
+        }
+    }
+}

+ 69 - 0
common/src/main/java/com/kym/common/exception/PlatformCommunicationException.java

@@ -0,0 +1,69 @@
+package com.kym.common.exception;
+
+/**
+ * 平台通信异常
+ * 用于处理与第三方平台(如 EN+)通信时的异常
+ *
+ * @author skyline
+ */
+public class PlatformCommunicationException extends BusinessException {
+
+    private static final long serialVersionUID = 1L;
+
+    private final String platformName;
+    private final String apiName;
+
+    public PlatformCommunicationException(String platformName, String message) {
+        super("连接" + platformName + "平台失败:" + message);
+        this.platformName = platformName;
+        this.apiName = null;
+    }
+
+    public PlatformCommunicationException(String platformName, String apiName, String message) {
+        super("调用" + platformName + "平台接口" + apiName + "失败:" + message);
+        this.platformName = platformName;
+        this.apiName = apiName;
+    }
+
+    public PlatformCommunicationException(String platformName, Throwable cause) {
+        super("连接" + platformName + "平台异常:" + cause.getMessage());
+        this.platformName = platformName;
+        this.apiName = null;
+        this.initCause(cause);
+    }
+
+    public String getPlatformName() {
+        return platformName;
+    }
+
+    public String getApiName() {
+        return apiName;
+    }
+
+    /**
+     * 平台认证失败异常
+     */
+    public static class PlatformAuthException extends PlatformCommunicationException {
+        public PlatformAuthException(String platformName) {
+            super(platformName, "认证失败,请检查密钥配置");
+        }
+    }
+
+    /**
+     * 平台超时异常
+     */
+    public static class PlatformTimeoutException extends PlatformCommunicationException {
+        public PlatformTimeoutException(String platformName, String apiName) {
+            super(platformName, apiName, "请求超时,请稍后重试");
+        }
+    }
+
+    /**
+     * 平台数据格式异常
+     */
+    public static class PlatformDataFormatException extends PlatformCommunicationException {
+        public PlatformDataFormatException(String platformName, String message) {
+            super(platformName, "数据格式错误:" + message);
+        }
+    }
+}

+ 65 - 4
common/src/main/java/com/kym/common/handler/GlobalExceptionHandler.java

@@ -6,6 +6,8 @@ import cn.dev33.satoken.exception.NotRoleException;
 import com.kym.common.R;
 import com.kym.common.exception.BaseException;
 import com.kym.common.exception.BusinessException;
+import com.kym.common.exception.ChargeException;
+import com.kym.common.exception.PlatformCommunicationException;
 import com.kym.common.exception.PlatformPushException;
 import jakarta.validation.ConstraintViolation;
 import jakarta.validation.ConstraintViolationException;
@@ -142,10 +144,69 @@ public class GlobalExceptionHandler {
     }
 
 
-    @ExceptionHandler(value = {Exception.class})
-    public R handlerRestException(Exception e) {
-        LOGGER.info(e.getMessage(), e);
-        return R.failed();
+    /**
+     * 充电业务异常处理
+     *
+     * @param e 异常
+     * @return 异常结果
+     */
+    @ExceptionHandler(value = ChargeException.class)
+    @ResponseBody
+    public R<?> handleChargeException(ChargeException e) {
+        LOGGER.error("充电业务异常:{}", e.getMessage(), e);
+        return R.failed(e.getCode(), e.getMessage());
+    }
+
+    /**
+     * 平台通信异常处理
+     *
+     * @param e 异常
+     * @return 异常结果
+     */
+    @ExceptionHandler(value = PlatformCommunicationException.class)
+    @ResponseBody
+    public R<?> handlePlatformCommunicationException(PlatformCommunicationException e) {
+        LOGGER.error("平台通信异常:platform={}, api={}", e.getPlatformName(), e.getApiName(), e);
+        // 根据异常类型返回不同的错误信息
+        if (e instanceof PlatformCommunicationException.PlatformAuthException) {
+            return R.failed(503, "平台认证失败,请联系管理员");
+        } else if (e instanceof PlatformCommunicationException.PlatformTimeoutException) {
+            return R.failed(504, "平台响应超时,请稍后重试");
+        } else if (e instanceof PlatformCommunicationException.PlatformDataFormatException) {
+            return R.failed(500, "数据格式错误,请检查配置");
+        }
+        return R.failed(503, "外部服务不可用,请稍后重试");
+    }
+
+    /**
+     * 未知异常处理(兜底)
+     *
+     * @param e 异常
+     * @return 异常结果
+     */
+    @ExceptionHandler(value = Exception.class)
+    @ResponseBody
+    public R<?> handlerRestException(Exception e) {
+        // 记录完整的堆栈信息用于排查问题
+        LOGGER.error("系统未知异常:URI={}, Message={}", 
+            getCurrentRequestURI(), e.getMessage(), e);
+        return R.failed(HttpStatus.INTERNAL_SERVER_ERROR.value(), "系统繁忙,请稍后重试");
+    }
+
+    /**
+     * 获取当前请求 URI(用于日志记录)
+     */
+    private String getCurrentRequestURI() {
+        try {
+            var requestAttributes = org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes();
+            if (requestAttributes instanceof org.springframework.web.context.request.ServletRequestAttributes) {
+                var request = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getRequest();
+                return request.getRequestURI();
+            }
+        } catch (Exception e) {
+            // 忽略异常,返回 unknown
+        }
+        return "unknown";
     }
 
 

+ 31 - 17
miniapp/src/main/resources/application-dev.yml

@@ -40,36 +40,46 @@ wechat:
 spring:
   datasource:
     druid: #以下是全局默认值,可以全局更改
-      #监控统计拦截的filters
-      filters: stat,slf4j
+      #监控统计拦截的 filters
+      filters: stat,wall,slf4j
       #配置初始化大小/最小/最大
-      initial-size: 2
-      min-idle: 2
-      max-active: 20
-      #获取连接等待超时时间
-      max-wait: 60000
+      initial-size: 10
+      min-idle: 10
+      max-active: 50
+      #获取连接等待超时时间(毫秒)
+      max-wait: 5000
       #间隔多久进行一次检测,检测需要关闭的空闲连接
-      time-between-eviction-runs-millis: 60000
+      time-between-eviction-runs-millis: 30000
       #一个连接在池中最小生存的时间
       min-evictable-idle-time-millis: 300000
-      validation-query: SELECT 'x'
+      max-evictable-idle-time-millis: 900000
+      validation-query: SELECT 1
       test-while-idle: true
       test-on-borrow: false
       test-on-return: false
-      #打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为false
-      pool-prepared-statements: false
-      max-pool-prepared-statement-per-connection-size: 20
+      #打开 PSCache,并指定每个连接上 PSCache 的大小。oracle 设为 true,mysql 设为 false。分库分表较多推荐设置为 false
+      pool-prepared-statements: true
+      max-pool-prepared-statement-per-connection-size: 50
     dynamic:
       primary: db-miniapp
-      strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
+      strict: false #严格匹配数据源,默认 false. true 未匹配到指定数据源时抛异常,false 使用默认数据源
+      druid:
+        # db-admin 数据源独立配置
+        db-admin:
+          max-active: 30
+          max-wait: 3000
+        # db-miniapp 数据源独立配置
+        db-miniapp:
+          max-active: 40
+          max-wait: 3000
       datasource:
         db-admin:
-          url: jdbc:mysql://server.kuaiyuman.cn:3306/charge_admin?tinyInt1isBit=false&serverTimezone=Asia/Shanghai
+          url: jdbc:mysql://server.kuaiyuman.cn:3306/charge_admin?tinyInt1isBit=false&serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
           username: root
           password: KuaiyuMan/*-
           driver-class-name: com.mysql.cj.jdbc.Driver
         db-miniapp:
-          url: jdbc:mysql://server.kuaiyuman.cn:3306/charge_app
+          url: jdbc:mysql://server.kuaiyuman.cn:3306/charge_app?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
           username: root
           password: KuaiyuMan/*-
           driver-class-name: com.mysql.cj.jdbc.Driver
@@ -79,13 +89,17 @@ spring:
       host: server.kuaiyuman.cn
       password: KtXA^Zx!TZmLEy(@JjB@2(TVG0kdy5)&
       database: 10
+      timeout: 3000ms
       lettuce:
         pool:
-          min-idle: 1
+          max-active: 50
+          max-idle: 20
+          min-idle: 5
+          max-wait: 2000ms
         cluster:
           refresh:
             adaptive: true
-            period: 20
+            period: 30
   cache:
     type: redis
     redis: