woody преди 7 месеца
родител
ревизия
c54fc92e29
променени са 22 файла, в които са добавени 246 реда и са изтрити 55 реда
  1. 14 10
      framework-base/src/main/java/com/chelvc/framework/base/context/ApplicationContextHolder.java
  2. 1 1
      framework-base/src/main/java/com/chelvc/framework/base/crypto/AESCipherFactory.java
  3. 1 1
      framework-base/src/main/java/com/chelvc/framework/base/crypto/CipherFactory.java
  4. 4 0
      framework-common/pom.xml
  5. 5 4
      framework-common/src/main/java/com/chelvc/framework/common/model/Pool.java
  6. 5 1
      framework-common/src/main/java/com/chelvc/framework/common/util/AESUtils.java
  7. 2 1
      framework-common/src/main/java/com/chelvc/framework/common/util/DesensitizeUtils.java
  8. 14 10
      framework-common/src/main/java/com/chelvc/framework/common/util/ObjectUtils.java
  9. 2 1
      framework-common/src/main/java/com/chelvc/framework/common/util/StringUtils.java
  10. 4 0
      framework-database/src/main/java/com/chelvc/framework/database/config/DatabaseConfigurer.java
  11. 26 0
      framework-database/src/main/java/com/chelvc/framework/database/config/DatabaseProperties.java
  12. 2 1
      framework-database/src/main/java/com/chelvc/framework/database/config/TypeHandlerConfigurer.java
  13. 48 0
      framework-database/src/main/java/com/chelvc/framework/database/context/CacheableDatabaseCryptoContext.java
  14. 13 7
      framework-database/src/main/java/com/chelvc/framework/database/context/DatabaseContextHolder.java
  15. 1 1
      framework-database/src/main/java/com/chelvc/framework/database/context/DatabaseCryptoContext.java
  16. 6 0
      framework-dependencies/pom.xml
  17. 1 1
      framework-security/src/main/java/com/chelvc/framework/security/context/SecurityCryptoContext.java
  18. 18 0
      framework-wechat/src/main/java/com/chelvc/framework/wechat/WechatPaymentHandler.java
  19. 5 0
      framework-wechat/src/main/java/com/chelvc/framework/wechat/WechatUnifiedOrder.java
  20. 5 1
      framework-wechat/src/main/java/com/chelvc/framework/wechat/context/WechatContextHolder.java
  21. 18 4
      framework-wechat/src/main/java/com/chelvc/framework/wechat/support/DefaultWechatPaymentHandler.java
  22. 51 11
      framework-wechat/src/main/java/com/chelvc/framework/wechat/support/DelegatingWechatPaymentHandler.java

+ 14 - 10
framework-base/src/main/java/com/chelvc/framework/base/context/ApplicationContextHolder.java

@@ -755,17 +755,21 @@ public class ApplicationContextHolder implements ApplicationContextAware, Proper
      */
      */
     @SuppressWarnings("unchecked")
     @SuppressWarnings("unchecked")
     public static <T> T getProperty(@NonNull String key, @NonNull Function<String, T> function) {
     public static <T> T getProperty(@NonNull String key, @NonNull Function<String, T> function) {
-        Object object = PROPERTY_OBJECT_MAPPING.computeIfAbsent(key, k -> {
-            Environment environment = getEnvironment(false);
-            if (environment != null) {
-                try {
-                    return function.apply(environment.getProperty(key));
-                } catch (Exception e) {
-                    log.error("Get property value failed: {}", key, e);
+        Object object = PROPERTY_OBJECT_MAPPING.get(key);
+        if (object == null) {
+            object = PROPERTY_OBJECT_MAPPING.computeIfAbsent(key, k -> {
+                Object value = null;
+                Environment environment = getEnvironment(false);
+                if (environment != null) {
+                    try {
+                        value = function.apply(environment.getProperty(key));
+                    } catch (Exception e) {
+                        log.error("Get property value failed: {}", key, e);
+                    }
                 }
                 }
-            }
-            return ObjectUtils.EMPTY;
-        });
+                return ObjectUtils.ifNull(value, ObjectUtils.EMPTY);
+            });
+        }
         return object == ObjectUtils.EMPTY ? null : (T) object;
         return object == ObjectUtils.EMPTY ? null : (T) object;
     }
     }
 
 

+ 1 - 1
framework-common/src/main/java/com/chelvc/framework/common/crypto/AESCipherFactory.java → framework-base/src/main/java/com/chelvc/framework/base/crypto/AESCipherFactory.java

@@ -1,4 +1,4 @@
-package com.chelvc.framework.common.crypto;
+package com.chelvc.framework.base.crypto;
 
 
 import javax.crypto.Cipher;
 import javax.crypto.Cipher;
 
 

+ 1 - 1
framework-common/src/main/java/com/chelvc/framework/common/crypto/CipherFactory.java → framework-base/src/main/java/com/chelvc/framework/base/crypto/CipherFactory.java

@@ -1,4 +1,4 @@
-package com.chelvc.framework.common.crypto;
+package com.chelvc.framework.base.crypto;
 
 
 import java.util.List;
 import java.util.List;
 import java.util.Set;
 import java.util.Set;

+ 4 - 0
framework-common/pom.xml

@@ -71,6 +71,10 @@
             <groupId>org.apache.xmlbeans</groupId>
             <groupId>org.apache.xmlbeans</groupId>
             <artifactId>xmlbeans</artifactId>
             <artifactId>xmlbeans</artifactId>
         </dependency>
         </dependency>
+        <dependency>
+            <groupId>com.github.ben-manes.caffeine</groupId>
+            <artifactId>caffeine</artifactId>
+        </dependency>
         <dependency>
         <dependency>
             <groupId>xerces</groupId>
             <groupId>xerces</groupId>
             <artifactId>xercesImpl</artifactId>
             <artifactId>xercesImpl</artifactId>

+ 5 - 4
framework-common/src/main/java/com/chelvc/framework/common/model/Pool.java

@@ -53,11 +53,12 @@ public class Pool<T> {
      */
      */
     public T get() {
     public T get() {
         // 从对象池获取对象实例
         // 从对象池获取对象实例
+        long thread = Thread.currentThread().getId();
         long timestamp = System.currentTimeMillis();
         long timestamp = System.currentTimeMillis();
-        Target target = this.cache.computeIfAbsent(
-                Thread.currentThread().getId(),
-                id -> new Target(this.supplier.get(), timestamp)
-        );
+        Target target = this.cache.get(thread);
+        if (target == null) {
+            target = this.cache.computeIfAbsent(thread, id -> new Target(this.supplier.get(), timestamp));
+        }
 
 
         // 更新对象访问时间
         // 更新对象访问时间
         target.timestamp = timestamp;
         target.timestamp = timestamp;

+ 5 - 1
framework-common/src/main/java/com/chelvc/framework/common/util/AESUtils.java

@@ -215,7 +215,11 @@ public final class AESUtils {
         } else {
         } else {
             throw new IllegalArgumentException("Not support cipher mode: " + mode);
             throw new IllegalArgumentException("Not support cipher mode: " + mode);
         }
         }
-        return cache.computeIfAbsent(key, k -> new Pool<>(supplier)).get();
+        Pool<Cipher> pool = cache.get(key);
+        if (pool == null) {
+            pool = cache.computeIfAbsent(key, k -> new Pool<>(supplier));
+        }
+        return pool.get();
     }
     }
 
 
     /**
     /**

+ 2 - 1
framework-common/src/main/java/com/chelvc/framework/common/util/DesensitizeUtils.java

@@ -82,7 +82,8 @@ public final class DesensitizeUtils {
      * @return 脱敏模式对象
      * @return 脱敏模式对象
      */
      */
     public static Mode analyse(@NonNull String expression) {
     public static Mode analyse(@NonNull String expression) {
-        return MODELS.computeIfAbsent(expression, k -> {
+        Mode mode = MODELS.get(expression);
+        return mode != null ? mode : MODELS.computeIfAbsent(expression, k -> {
             if (FIXED_PATTERN.matcher(expression).matches()) {
             if (FIXED_PATTERN.matcher(expression).matches()) {
                 int number = StringUtils.replace(expression, "[-?]", StringUtils.EMPTY).length();
                 int number = StringUtils.replace(expression, "[-?]", StringUtils.EMPTY).length();
                 return new Fixed(Position.of(expression), number, getOffset(expression));
                 return new Fixed(Position.of(expression), number, getOffset(expression));

+ 14 - 10
framework-common/src/main/java/com/chelvc/framework/common/util/ObjectUtils.java

@@ -73,20 +73,19 @@ public final class ObjectUtils {
     private static final Map<Class<?>, Map<String, Field>> CLASS_FIELD_MAPPING = Maps.newConcurrentMap();
     private static final Map<Class<?>, Map<String, Field>> CLASS_FIELD_MAPPING = Maps.newConcurrentMap();
 
 
     /**
     /**
-     * SerializedLambda 反序列化缓存
+     * Protostuff对象类型/Schema映射表
      */
      */
-    private static final Map<Class<?>, SerializedLambda> LAMBDA_FUNCTION_MAPPING = Maps.newConcurrentMap();
+    private static final Map<Class<?>, Schema<?>> PROTOSTUFF_SCHEMA_MAPPING = Maps.newConcurrentMap();
 
 
     /**
     /**
-     * Protostuff对象类型/Schema映射表
+     * SerializedLambda 反序列化缓存
      */
      */
-    private static final Map<Class<?>, Schema<?>> PROTOSTUFF_CLASS_SCHEMA_MAPPING = Maps.newConcurrentMap();
+    private static final Map<Class<?>, SerializedLambda> LAMBDA_FUNCTION_MAPPING = Maps.newConcurrentMap();
 
 
     /**
     /**
      * 类对象/属性描述映射表
      * 类对象/属性描述映射表
      */
      */
-    private static final Map<Class<?>, List<PropertyDescriptor>> CLASS_PROPERTY_DESCRIPTOR_MAPPING =
-            Maps.newConcurrentMap();
+    private static final Map<Class<?>, List<PropertyDescriptor>> PROPERTY_DESCRIPTOR_MAPPING = Maps.newConcurrentMap();
 
 
     static {
     static {
         // 注册简单的对象拷贝转换器
         // 注册简单的对象拷贝转换器
@@ -454,7 +453,8 @@ public final class ObjectUtils {
      * @return 字段名称/实例映射表
      * @return 字段名称/实例映射表
      */
      */
     public static Map<String, Field> getAllFields(@NonNull Class<?> clazz) {
     public static Map<String, Field> getAllFields(@NonNull Class<?> clazz) {
-        return CLASS_FIELD_MAPPING.computeIfAbsent(clazz, k -> {
+        Map<String, Field> cache = CLASS_FIELD_MAPPING.get(clazz);
+        return cache != null ? cache : CLASS_FIELD_MAPPING.computeIfAbsent(clazz, k -> {
             // 接口对象类型
             // 接口对象类型
             if (Modifier.isInterface(k.getModifiers())) {
             if (Modifier.isInterface(k.getModifiers())) {
                 return Collections.emptyMap();
                 return Collections.emptyMap();
@@ -797,7 +797,8 @@ public final class ObjectUtils {
      */
      */
     @SuppressWarnings("unchecked")
     @SuppressWarnings("unchecked")
     private static <M, T extends Schema<M>> T getProtostuffSchema(@NonNull Class<M> clazz) {
     private static <M, T extends Schema<M>> T getProtostuffSchema(@NonNull Class<M> clazz) {
-        return (T) PROTOSTUFF_CLASS_SCHEMA_MAPPING.computeIfAbsent(clazz, RuntimeSchema::getSchema);
+        T schema = (T) PROTOSTUFF_SCHEMA_MAPPING.get(clazz);
+        return schema != null ? schema : (T) PROTOSTUFF_SCHEMA_MAPPING.computeIfAbsent(clazz, RuntimeSchema::getSchema);
     }
     }
 
 
     /**
     /**
@@ -844,7 +845,8 @@ public final class ObjectUtils {
      * @return 属性描述列表
      * @return 属性描述列表
      */
      */
     public static List<PropertyDescriptor> getPropertyDescriptors(@NonNull Class<?> clazz) {
     public static List<PropertyDescriptor> getPropertyDescriptors(@NonNull Class<?> clazz) {
-        return CLASS_PROPERTY_DESCRIPTOR_MAPPING.computeIfAbsent(clazz, k -> {
+        List<PropertyDescriptor> descriptors = PROPERTY_DESCRIPTOR_MAPPING.get(clazz);
+        return descriptors != null ? descriptors : PROPERTY_DESCRIPTOR_MAPPING.computeIfAbsent(clazz, k -> {
             BeanInfo bean;
             BeanInfo bean;
             try {
             try {
                 bean = Introspector.getBeanInfo(clazz, Object.class);
                 bean = Introspector.getBeanInfo(clazz, Object.class);
@@ -874,7 +876,9 @@ public final class ObjectUtils {
      * @return Lambda对象序列化对象实例
      * @return Lambda对象序列化对象实例
      */
      */
     private static SerializedLambda lookupFunctionLambda(Serializable function) {
     private static SerializedLambda lookupFunctionLambda(Serializable function) {
-        return LAMBDA_FUNCTION_MAPPING.computeIfAbsent(function.getClass(), k -> {
+        Class<?> clazz = function.getClass();
+        SerializedLambda lambda = LAMBDA_FUNCTION_MAPPING.get(clazz);
+        return lambda != null ? lambda : LAMBDA_FUNCTION_MAPPING.computeIfAbsent(clazz, k -> {
             try {
             try {
                 Method method = k.getDeclaredMethod("writeReplace");
                 Method method = k.getDeclaredMethod("writeReplace");
                 method.setAccessible(true);
                 method.setAccessible(true);

+ 2 - 1
framework-common/src/main/java/com/chelvc/framework/common/util/StringUtils.java

@@ -338,7 +338,8 @@ public final class StringUtils {
      * @return 匹配模式对象
      * @return 匹配模式对象
      */
      */
     public static Pattern getPattern(@NonNull String regex) {
     public static Pattern getPattern(@NonNull String regex) {
-        return REGEX_PATTERN_MAPPING.computeIfAbsent(regex, Pattern::compile);
+        Pattern pattern = REGEX_PATTERN_MAPPING.get(regex);
+        return pattern != null ? pattern : REGEX_PATTERN_MAPPING.computeIfAbsent(regex, Pattern::compile);
     }
     }
 
 
     /**
     /**

+ 4 - 0
framework-database/src/main/java/com/chelvc/framework/database/config/DatabaseConfigurer.java

@@ -16,6 +16,7 @@ import com.chelvc.framework.common.model.File;
 import com.chelvc.framework.common.model.Modification;
 import com.chelvc.framework.common.model.Modification;
 import com.chelvc.framework.common.model.Period;
 import com.chelvc.framework.common.model.Period;
 import com.chelvc.framework.common.model.Region;
 import com.chelvc.framework.common.model.Region;
+import com.chelvc.framework.database.context.CacheableDatabaseCryptoContext;
 import com.chelvc.framework.database.context.DatabaseCryptoContext;
 import com.chelvc.framework.database.context.DatabaseCryptoContext;
 import com.chelvc.framework.database.context.DefaultDatabaseCryptoContext;
 import com.chelvc.framework.database.context.DefaultDatabaseCryptoContext;
 import com.chelvc.framework.database.context.Transactor;
 import com.chelvc.framework.database.context.Transactor;
@@ -114,6 +115,9 @@ public class DatabaseConfigurer {
     @Bean
     @Bean
     @ConditionalOnMissingBean(DatabaseCryptoContext.class)
     @ConditionalOnMissingBean(DatabaseCryptoContext.class)
     public DatabaseCryptoContext databaseCryptoContext() {
     public DatabaseCryptoContext databaseCryptoContext() {
+        if (this.properties.getSensitive().getCache().isEnabled()) {
+            return new CacheableDatabaseCryptoContext(this.properties);
+        }
         return new DefaultDatabaseCryptoContext(this.properties);
         return new DefaultDatabaseCryptoContext(this.properties);
     }
     }
 
 

+ 26 - 0
framework-database/src/main/java/com/chelvc/framework/database/config/DatabaseProperties.java

@@ -19,6 +19,27 @@ public class DatabaseProperties {
      */
      */
     private final Sensitive sensitive = new Sensitive();
     private final Sensitive sensitive = new Sensitive();
 
 
+    /**
+     * 缓存配置
+     */
+    @Data
+    public static class Cache {
+        /**
+         * 是否启用缓存
+         */
+        private boolean enabled;
+
+        /**
+         * 缓存容量
+         */
+        private int capacity = 100;
+
+        /**
+         * 缓存时间(秒)
+         */
+        private int duration = 60;
+    }
+
     /**
     /**
      * 敏感字段配置
      * 敏感字段配置
      */
      */
@@ -38,5 +59,10 @@ public class DatabaseProperties {
          * 是否可写入密文
          * 是否可写入密文
          */
          */
         private boolean writable = true;
         private boolean writable = true;
+
+        /**
+         * 加解密缓存配置
+         */
+        private final Cache cache = new Cache();
     }
     }
 }
 }

+ 2 - 1
framework-database/src/main/java/com/chelvc/framework/database/config/TypeHandlerConfigurer.java

@@ -319,7 +319,8 @@ public class TypeHandlerConfigurer extends MybatisConfigurer {
                 return ModificationArrayTypeHandler.class;
                 return ModificationArrayTypeHandler.class;
             }
             }
         }
         }
-        return this.handlers.computeIfAbsent(type, this::initializeJsonHandlerClass);
+        Class<? extends TypeHandler<?>> clazz = this.handlers.get(type);
+        return clazz != null ? clazz : this.handlers.computeIfAbsent(type, this::initializeJsonHandlerClass);
     }
     }
 
 
     /**
     /**

+ 48 - 0
framework-database/src/main/java/com/chelvc/framework/database/context/CacheableDatabaseCryptoContext.java

@@ -0,0 +1,48 @@
+package com.chelvc.framework.database.context;
+
+import java.time.Duration;
+import javax.crypto.Cipher;
+
+import com.chelvc.framework.common.util.AssertUtils;
+import com.chelvc.framework.common.util.CodecUtils;
+import com.chelvc.framework.common.util.StringUtils;
+import com.chelvc.framework.database.config.DatabaseProperties;
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import lombok.NonNull;
+
+/**
+ * 可缓存的数据库数据加解密上下文实现
+ *
+ * @author Woody
+ * @date 2024/9/6
+ */
+public class CacheableDatabaseCryptoContext extends DefaultDatabaseCryptoContext {
+    private final Cache<String, String> cache;
+
+    public CacheableDatabaseCryptoContext(@NonNull DatabaseProperties properties) {
+        super(properties);
+        int capacity = properties.getSensitive().getCache().getCapacity();
+        int duration = properties.getSensitive().getCache().getDuration();
+        AssertUtils.check(capacity > 0, () -> "capacity must be greater than 0");
+        AssertUtils.check(duration > 0, () -> "duration must be greater than 0");
+        this.cache = Caffeine.newBuilder().maximumSize(capacity)
+                .expireAfterAccess(Duration.ofSeconds(duration)).build();
+    }
+
+    @Override
+    public String encrypt(@NonNull Cipher cipher, String plaintext) {
+        if (StringUtils.isEmpty(plaintext)) {
+            return plaintext;
+        }
+        return this.cache.get(plaintext, k -> CodecUtils.encrypt(cipher, plaintext));
+    }
+
+    @Override
+    public String decrypt(@NonNull Cipher cipher, String ciphertext) {
+        if (StringUtils.isEmpty(ciphertext)) {
+            return ciphertext;
+        }
+        return this.cache.get(ciphertext, k -> CodecUtils.decrypt(cipher, ciphertext));
+    }
+}

+ 13 - 7
framework-database/src/main/java/com/chelvc/framework/database/context/DatabaseContextHolder.java

@@ -363,10 +363,13 @@ public final class DatabaseContextHolder {
      * @return 数据模型对象
      * @return 数据模型对象
      */
      */
     public static Class<?> getTableModel(@NonNull String table) {
     public static Class<?> getTableModel(@NonNull String table) {
-        Class<?> model = TABLE_MODEL_MAPPING.computeIfAbsent(table, k -> {
-            Class<?> type = ObjectUtils.ifNull(getTable(table), TableInfo::getEntityType);
-            return ObjectUtils.ifNull(type, Object.class);
-        });
+        Class<?> model = TABLE_MODEL_MAPPING.get(table);
+        if (model == null) {
+            model = TABLE_MODEL_MAPPING.computeIfAbsent(table, k -> {
+                Class<?> type = ObjectUtils.ifNull(getTable(table), TableInfo::getEntityType);
+                return ObjectUtils.ifNull(type, Object.class);
+            });
+        }
         return model == Object.class ? null : model;
         return model == Object.class ? null : model;
     }
     }
 
 
@@ -377,7 +380,8 @@ public final class DatabaseContextHolder {
      * @return 表字段名称/上下文映射表
      * @return 表字段名称/上下文映射表
      */
      */
     public static Map<String, TableFieldContext> getTableFieldContexts(@NonNull String table) {
     public static Map<String, TableFieldContext> getTableFieldContexts(@NonNull String table) {
-        return TABLE_FIELD_CONTEXT_MAPPING.computeIfAbsent(table, k -> {
+        Map<String, TableFieldContext> cache = TABLE_FIELD_CONTEXT_MAPPING.get(table);
+        return cache != null ? cache : TABLE_FIELD_CONTEXT_MAPPING.computeIfAbsent(table, k -> {
             List<TableFieldInfo> fields = ObjectUtils.ifNull(getTable(table), TableInfo::getFieldList);
             List<TableFieldInfo> fields = ObjectUtils.ifNull(getTable(table), TableInfo::getFieldList);
             if (ObjectUtils.isEmpty(fields)) {
             if (ObjectUtils.isEmpty(fields)) {
                 return Collections.emptyMap();
                 return Collections.emptyMap();
@@ -485,7 +489,8 @@ public final class DatabaseContextHolder {
      */
      */
     @SuppressWarnings("unchecked")
     @SuppressWarnings("unchecked")
     public static <T extends TypeHandler<?>> T initializeTypeHandler(@NonNull Class<T> clazz) {
     public static <T extends TypeHandler<?>> T initializeTypeHandler(@NonNull Class<T> clazz) {
-        return (T) TYPE_HANDLER_MAPPING.computeIfAbsent(clazz, k -> {
+        T handler = (T) TYPE_HANDLER_MAPPING.get(clazz);
+        return handler != null ? handler : (T) TYPE_HANDLER_MAPPING.computeIfAbsent(clazz, k -> {
             try {
             try {
                 return clazz.newInstance();
                 return clazz.newInstance();
             } catch (ReflectiveOperationException e) {
             } catch (ReflectiveOperationException e) {
@@ -554,7 +559,8 @@ public final class DatabaseContextHolder {
      */
      */
     @SuppressWarnings("unchecked")
     @SuppressWarnings("unchecked")
     public static <M, T extends IService<M>> T lookupEntityService(@NonNull Class<M> clazz) {
     public static <M, T extends IService<M>> T lookupEntityService(@NonNull Class<M> clazz) {
-        return (T) CLASS_SERVICE_MAPPING.computeIfAbsent(clazz, k -> {
+        T instance = (T) CLASS_SERVICE_MAPPING.get(clazz);
+        return instance != null ? instance : (T) CLASS_SERVICE_MAPPING.computeIfAbsent(clazz, k -> {
             ApplicationContext applicationContext = ApplicationContextHolder.getApplicationContext();
             ApplicationContext applicationContext = ApplicationContextHolder.getApplicationContext();
             IService<?> service = applicationContext.getBeansOfType(IService.class).values().stream()
             IService<?> service = applicationContext.getBeansOfType(IService.class).values().stream()
                     .filter(s -> Objects.equals(s.getEntityClass(), clazz)).findAny().orElse(null);
                     .filter(s -> Objects.equals(s.getEntityClass(), clazz)).findAny().orElse(null);

+ 1 - 1
framework-database/src/main/java/com/chelvc/framework/database/context/DatabaseCryptoContext.java

@@ -1,6 +1,6 @@
 package com.chelvc.framework.database.context;
 package com.chelvc.framework.database.context;
 
 
-import com.chelvc.framework.common.crypto.CipherFactory;
+import com.chelvc.framework.base.crypto.CipherFactory;
 
 
 /**
 /**
  * 数据库数据加解密上下文接口
  * 数据库数据加解密上下文接口

+ 6 - 0
framework-dependencies/pom.xml

@@ -29,6 +29,7 @@
         <protostuff.version>1.7.2</protostuff.version>
         <protostuff.version>1.7.2</protostuff.version>
         <xmpcore.version>6.1.11</xmpcore.version>
         <xmpcore.version>6.1.11</xmpcore.version>
         <xmlbeans.version>5.0.0</xmlbeans.version>
         <xmlbeans.version>5.0.0</xmlbeans.version>
+        <caffeine.version>2.9.3</caffeine.version>
         <thumbnailator.version>0.4.14</thumbnailator.version>
         <thumbnailator.version>0.4.14</thumbnailator.version>
         <geocoder.version>2.184</geocoder.version>
         <geocoder.version>2.184</geocoder.version>
         <libphonenumber.version>8.12.48</libphonenumber.version>
         <libphonenumber.version>8.12.48</libphonenumber.version>
@@ -115,6 +116,11 @@
                 <artifactId>xmlbeans</artifactId>
                 <artifactId>xmlbeans</artifactId>
                 <version>${xmlbeans.version}</version>
                 <version>${xmlbeans.version}</version>
             </dependency>
             </dependency>
+            <dependency>
+                <groupId>com.github.ben-manes.caffeine</groupId>
+                <artifactId>caffeine</artifactId>
+                <version>${caffeine.version}</version>
+            </dependency>
             <dependency>
             <dependency>
                 <groupId>io.protostuff</groupId>
                 <groupId>io.protostuff</groupId>
                 <artifactId>protostuff-core</artifactId>
                 <artifactId>protostuff-core</artifactId>

+ 1 - 1
framework-security/src/main/java/com/chelvc/framework/security/context/SecurityCryptoContext.java

@@ -1,7 +1,7 @@
 package com.chelvc.framework.security.context;
 package com.chelvc.framework.security.context;
 
 
 import com.chelvc.framework.base.context.Session;
 import com.chelvc.framework.base.context.Session;
-import com.chelvc.framework.common.crypto.CipherFactory;
+import com.chelvc.framework.base.crypto.CipherFactory;
 
 
 /**
 /**
  * 信息安全数据加解密上下文接口
  * 信息安全数据加解密上下文接口

+ 18 - 0
framework-wechat/src/main/java/com/chelvc/framework/wechat/WechatPaymentHandler.java

@@ -17,6 +17,24 @@ public interface WechatPaymentHandler {
      */
      */
     boolean supports(String name);
     boolean supports(String name);
 
 
+    /**
+     * 判断是否指定渠道
+     *
+     * @param name    应用名称
+     * @param channel 支付渠道
+     * @return true/false
+     */
+    boolean isChannel(String name, PayChannel channel);
+
+    /**
+     * 判断是否是指定商户
+     *
+     * @param name     应用名称
+     * @param merchant 商户标识
+     * @return true/false
+     */
+    boolean isMerchant(String name, String merchant);
+
     /**
     /**
      * 参数签名
      * 参数签名
      *
      *

+ 5 - 0
framework-wechat/src/main/java/com/chelvc/framework/wechat/WechatUnifiedOrder.java

@@ -72,4 +72,9 @@ public class WechatUnifiedOrder implements Serializable {
      * 时间戳
      * 时间戳
      */
      */
     private String timestamp;
     private String timestamp;
+
+    /**
+     * 支付渠道
+     */
+    private PayChannel channel;
 }
 }

+ 5 - 1
framework-wechat/src/main/java/com/chelvc/framework/wechat/context/WechatContextHolder.java

@@ -143,7 +143,11 @@ public final class WechatContextHolder implements ApplicationListener<Applicatio
      * @return 访问令牌
      * @return 访问令牌
      */
      */
     public static String getAccessToken(@NonNull String appid) {
     public static String getAccessToken(@NonNull String appid) {
-        return APPID_TOKEN_MAPPING.computeIfAbsent(appid, WechatContextHolder::invokeAccessToken).getLeft();
+        Pair<String, Long> token = APPID_TOKEN_MAPPING.get(appid);
+        if (token == null) {
+            token = APPID_TOKEN_MAPPING.computeIfAbsent(appid, WechatContextHolder::invokeAccessToken);
+        }
+        return token.getLeft();
     }
     }
 
 
     /**
     /**

+ 18 - 4
framework-wechat/src/main/java/com/chelvc/framework/wechat/support/DefaultWechatPaymentHandler.java

@@ -131,22 +131,36 @@ public class DefaultWechatPaymentHandler implements WechatPaymentHandler {
     }
     }
 
 
     @Override
     @Override
-    public boolean supports(@NonNull String name) {
+    public boolean supports(String name) {
         return Objects.equals(name, this.getName());
         return Objects.equals(name, this.getName());
     }
     }
 
 
+    @Override
+    public boolean isChannel(String name, PayChannel channel) {
+        return this.supports(name) && channel == this.getChannel();
+    }
+
+    @Override
+    public boolean isMerchant(String name, String merchant) {
+        return this.supports(name) && Objects.equals(merchant, this.getMchid());
+    }
+
     @Override
     @Override
     public String sign(@NonNull String name, @NonNull Map<String, String> parameters) {
     public String sign(@NonNull String name, @NonNull Map<String, String> parameters) {
         AssertUtils.check(this.supports(name), name);
         AssertUtils.check(this.supports(name), name);
-
+        if (log.isDebugEnabled()) {
+            log.error("Wechat payment signature: {}", parameters);
+        }
         return WechatContextHolder.sign(this.properties.getMchkey(), parameters);
         return WechatContextHolder.sign(this.properties.getMchkey(), parameters);
     }
     }
 
 
     @Override
     @Override
     public WechatPaymentCallback callback(@NonNull String name, @NonNull String content) {
     public WechatPaymentCallback callback(@NonNull String name, @NonNull String content) {
         AssertUtils.check(this.supports(name), name);
         AssertUtils.check(this.supports(name), name);
-
         Map<String, String> notification = WechatContextHolder.xml2map(content);
         Map<String, String> notification = WechatContextHolder.xml2map(content);
+        if (log.isDebugEnabled()) {
+            log.error("Wechat payment callback: {}", notification);
+        }
         if (Objects.equals(notification.get("return_code"), "SUCCESS")
         if (Objects.equals(notification.get("return_code"), "SUCCESS")
                 && Objects.equals(this.sign(name, notification), notification.get("sign"))) {
                 && Objects.equals(this.sign(name, notification), notification.get("sign"))) {
             return WechatPaymentCallback.builder().order(notification.get("out_trade_no"))
             return WechatPaymentCallback.builder().order(notification.get("out_trade_no"))
@@ -213,7 +227,7 @@ public class DefaultWechatPaymentHandler implements WechatPaymentHandler {
         }
         }
         WechatUnifiedOrder order = WechatUnifiedOrder.builder().mode(this.getMode()).appid(this.getAppid())
         WechatUnifiedOrder order = WechatUnifiedOrder.builder().mode(this.getMode()).appid(this.getAppid())
                 .mchid(this.getMchid()).nonce(WXPayUtil.generateNonceStr()).prepayid(result.get("prepay_id"))
                 .mchid(this.getMchid()).nonce(WXPayUtil.generateNonceStr()).prepayid(result.get("prepay_id"))
-                .qrcode(result.get("code_url")).redirect(result.get("mweb_url"))
+                .qrcode(result.get("code_url")).redirect(result.get("mweb_url")).channel(this.getChannel())
                 .timestamp(String.valueOf(System.currentTimeMillis() / 1000).substring(0, 10)).build();
                 .timestamp(String.valueOf(System.currentTimeMillis() / 1000).substring(0, 10)).build();
         if (this.getMode() == PayMode.MWEB || this.getMode() == PayMode.JSAPI) {
         if (this.getMode() == PayMode.MWEB || this.getMode() == PayMode.JSAPI) {
             order.setAlgorithm(MessageDigestAlgorithms.MD5);
             order.setAlgorithm(MessageDigestAlgorithms.MD5);

+ 51 - 11
framework-wechat/src/main/java/com/chelvc/framework/wechat/support/DelegatingWechatPaymentHandler.java

@@ -4,7 +4,10 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Objects;
 
 
+import com.chelvc.framework.base.context.ApplicationContextHolder;
 import com.chelvc.framework.common.util.AssertUtils;
 import com.chelvc.framework.common.util.AssertUtils;
+import com.chelvc.framework.common.util.StringUtils;
+import com.chelvc.framework.wechat.PayChannel;
 import com.chelvc.framework.wechat.WechatPayRequest;
 import com.chelvc.framework.wechat.WechatPayRequest;
 import com.chelvc.framework.wechat.WechatPaymentCallback;
 import com.chelvc.framework.wechat.WechatPaymentCallback;
 import com.chelvc.framework.wechat.WechatPaymentHandler;
 import com.chelvc.framework.wechat.WechatPaymentHandler;
@@ -28,8 +31,35 @@ public class DelegatingWechatPaymentHandler implements WechatPaymentHandler {
         this.handlers = Lists.newArrayList(handlers);
         this.handlers = Lists.newArrayList(handlers);
     }
     }
 
 
+    /**
+     * 查找微信支付处理器
+     *
+     * @param name 处理器名称
+     * @return 处理器实例
+     */
+    private WechatPaymentHandler lookupPaymentHandler(String name) {
+        WechatPaymentHandler optional = null;
+        String merchant = ApplicationContextHolder.getProperty("wechat.payment." + name + ".merchant");
+        for (WechatPaymentHandler handler : this.handlers) {
+            if (handler.supports(name)) {
+                if (optional == null) {
+                    optional = handler;
+                }
+
+                // 优先使用指定商户号支付配置
+                if (StringUtils.isEmpty(merchant) || handler.isMerchant(name, merchant)) {
+                    return handler;
+                }
+            }
+        }
+        if (optional == null) {
+            throw new IllegalArgumentException("Wechat payment handler not found: " + name);
+        }
+        return optional;
+    }
+
     @Override
     @Override
-    public boolean supports(@NonNull String name) {
+    public boolean supports(String name) {
         for (WechatPaymentHandler handler : this.handlers) {
         for (WechatPaymentHandler handler : this.handlers) {
             if (handler.supports(name)) {
             if (handler.supports(name)) {
                 return true;
                 return true;
@@ -39,13 +69,28 @@ public class DelegatingWechatPaymentHandler implements WechatPaymentHandler {
     }
     }
 
 
     @Override
     @Override
-    public String sign(@NonNull String name, @NonNull Map<String, String> parameters) {
+    public boolean isChannel(String name, PayChannel channel) {
         for (WechatPaymentHandler handler : this.handlers) {
         for (WechatPaymentHandler handler : this.handlers) {
-            if (handler.supports(name)) {
-                return handler.sign(name, parameters);
+            if (handler.isChannel(name, channel)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isMerchant(String name, String merchant) {
+        for (WechatPaymentHandler handler : this.handlers) {
+            if (handler.isMerchant(name, merchant)) {
+                return true;
             }
             }
         }
         }
-        throw new IllegalArgumentException("Wechat payment handler not found: " + name);
+        return false;
+    }
+
+    @Override
+    public String sign(@NonNull String name, @NonNull Map<String, String> parameters) {
+        return this.lookupPaymentHandler(name).sign(name, parameters);
     }
     }
 
 
     @Override
     @Override
@@ -67,11 +112,6 @@ public class DelegatingWechatPaymentHandler implements WechatPaymentHandler {
 
 
     @Override
     @Override
     public WechatUnifiedOrder unifiedorder(@NonNull String name, @NonNull WechatPayRequest request) {
     public WechatUnifiedOrder unifiedorder(@NonNull String name, @NonNull WechatPayRequest request) {
-        for (WechatPaymentHandler handler : this.handlers) {
-            if (handler.supports(name)) {
-                return handler.unifiedorder(name, request);
-            }
-        }
-        throw new IllegalArgumentException("Wechat payment handler not found: " + name);
+        return this.lookupPaymentHandler(name).unifiedorder(name, request);
     }
     }
 }
 }