Explorar el Código

update 优化

ct hace 19 horas
padre
commit
8ed5b5c7b1
Se han modificado 100 ficheros con 1111 adiciones y 951 borrados
  1. 3 7
      fs-ad-api/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  2. 2 8
      fs-ad-api/src/main/java/com/fs/framework/config/ResourcesConfig.java
  3. 2 11
      fs-ad-new-api/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  4. 2 4
      fs-common-api/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  5. 2 8
      fs-common-api/src/main/java/com/fs/framework/config/ResourcesConfig.java
  6. 51 0
      fs-common/src/main/java/com/fs/common/config/CorsSupport.java
  7. 53 5
      fs-common/src/main/java/com/fs/common/utils/SortUtils.java
  8. 28 4
      fs-common/src/main/java/com/fs/common/utils/file/FileUtils.java
  9. 119 0
      fs-common/src/main/java/com/fs/common/utils/http/SafeHttpUrl.java
  10. 64 0
      fs-common/src/main/java/com/fs/common/utils/http/SafeUrlFile.java
  11. 13 0
      fs-company-app/src/main/java/com/fs/app/annotation/Anonymous.java
  12. 11 2
      fs-company-app/src/main/java/com/fs/app/controller/CompanyUserController.java
  13. 9 0
      fs-company-app/src/main/java/com/fs/app/controller/UserController.java
  14. 36 21
      fs-company-app/src/main/java/com/fs/app/interceptor/AuthorizationInterceptor.java
  15. 2 4
      fs-company-app/src/main/java/com/fs/core/config/FastJson2JsonRedisSerializer.java
  16. 2 8
      fs-company-app/src/main/java/com/fs/core/config/ResourcesConfig.java
  17. 32 5
      fs-company/src/main/java/com/fs/company/utils/AudioUtils.java
  18. 2 4
      fs-company/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  19. 2 8
      fs-company/src/main/java/com/fs/framework/config/ResourcesConfig.java
  20. 3 7
      fs-company/src/main/java/com/fs/framework/config/SecurityConfig.java
  21. 13 0
      fs-doctor-app/src/main/java/com/fs/app/annotation/Anonymous.java
  22. 4 0
      fs-doctor-app/src/main/java/com/fs/app/controller/DoctorController.java
  23. 8 0
      fs-doctor-app/src/main/java/com/fs/app/controller/FollowController.java
  24. 46 2
      fs-doctor-app/src/main/java/com/fs/app/controller/FsUserInformationCollectionController.java
  25. 31 0
      fs-doctor-app/src/main/java/com/fs/app/controller/InquiryOrderController.java
  26. 17 12
      fs-doctor-app/src/main/java/com/fs/app/controller/PatientController.java
  27. 12 0
      fs-doctor-app/src/main/java/com/fs/app/controller/PrescribeController.java
  28. 46 18
      fs-doctor-app/src/main/java/com/fs/app/interceptor/AuthorizationInterceptor.java
  29. 2 4
      fs-doctor-app/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  30. 2 8
      fs-doctor-app/src/main/java/com/fs/framework/config/ResourcesConfig.java
  31. 2 4
      fs-framework/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  32. 2 8
      fs-framework/src/main/java/com/fs/framework/config/ResourcesConfig.java
  33. 1 5
      fs-framework/src/main/java/com/fs/framework/config/SecurityConfig.java
  34. 1 1
      fs-ipad-task/src/main/java/com/fs/app/task/SendAppMsg.java
  35. 2 4
      fs-ipad-task/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  36. 2 8
      fs-ipad-task/src/main/java/com/fs/framework/config/ResourcesConfig.java
  37. 2 4
      fs-live-app/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  38. 2 8
      fs-live-app/src/main/java/com/fs/framework/config/ResourcesConfig.java
  39. 13 0
      fs-live-app/src/main/java/com/fs/live/annotation/Anonymous.java
  40. 20 0
      fs-live-app/src/main/java/com/fs/live/config/WebMvcConfig.java
  41. 2 0
      fs-live-app/src/main/java/com/fs/live/controller/LiveController.java
  42. 3 1
      fs-live-app/src/main/java/com/fs/live/controller/LiveDataController.java
  43. 89 0
      fs-live-app/src/main/java/com/fs/live/interceptor/AuthorizationInterceptor.java
  44. 2 4
      fs-qw-api-msg/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  45. 2 8
      fs-qw-api-msg/src/main/java/com/fs/framework/config/ResourcesConfig.java
  46. 2 4
      fs-qw-api/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  47. 2 8
      fs-qw-api/src/main/java/com/fs/framework/config/ResourcesConfig.java
  48. 2 4
      fs-qw-mq/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  49. 2 8
      fs-qw-mq/src/main/java/com/fs/framework/config/ResourcesConfig.java
  50. 2 4
      fs-qw-task/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  51. 2 8
      fs-qw-task/src/main/java/com/fs/framework/config/ResourcesConfig.java
  52. 2 4
      fs-qw-voice/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  53. 2 8
      fs-qw-voice/src/main/java/com/fs/framework/config/ResourcesConfig.java
  54. 26 2
      fs-qwhook-msg/src/main/java/com/fs/app/utils/AudioUtils.java
  55. 2 4
      fs-qwhook-msg/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  56. 2 8
      fs-qwhook-msg/src/main/java/com/fs/framework/config/ResourcesConfig.java
  57. 26 2
      fs-qwhook-sop/src/main/java/com/fs/app/utils/AudioUtils.java
  58. 2 4
      fs-qwhook-sop/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  59. 2 8
      fs-qwhook-sop/src/main/java/com/fs/framework/config/ResourcesConfig.java
  60. 26 2
      fs-qwhook/src/main/java/com/fs/app/utils/AudioUtils.java
  61. 2 4
      fs-qwhook/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  62. 2 8
      fs-qwhook/src/main/java/com/fs/framework/config/ResourcesConfig.java
  63. 2 4
      fs-redis/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  64. 2 8
      fs-redis/src/main/java/com/fs/framework/config/ResourcesConfig.java
  65. 2 4
      fs-repeat-api/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  66. 2 8
      fs-repeat-api/src/main/java/com/fs/framework/config/ResourcesConfig.java
  67. 10 1
      fs-service/src/main/java/com/fs/common/QRutils.java
  68. 1 1
      fs-service/src/main/java/com/fs/company/mapper/CompanySmsLogsMapper.java
  69. 4 4
      fs-service/src/main/java/com/fs/company/mapper/CompanyTcmReportMapper.java
  70. 3 3
      fs-service/src/main/java/com/fs/company/mapper/CompanyVoiceCallerMapper.java
  71. 6 6
      fs-service/src/main/java/com/fs/company/mapper/CompanyVoiceLogsMapper.java
  72. 1 1
      fs-service/src/main/java/com/fs/company/mapper/CompanyVoiceMobileMapper.java
  73. 1 1
      fs-service/src/main/java/com/fs/core/config/WxMaConfiguration.java
  74. 1 1
      fs-service/src/main/java/com/fs/course/mapper/FsCourseRedPacketLogMapper.java
  75. 1 23
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java
  76. 1 3
      fs-service/src/main/java/com/fs/course/service/impl/TencentCloudCosService.java
  77. 1 34
      fs-service/src/main/java/com/fs/fastGpt/service/impl/FastGptCollectionServiceImpl.java
  78. 61 28
      fs-service/src/main/java/com/fs/fastgptApi/util/AudioUtils.java
  79. 13 6
      fs-service/src/main/java/com/fs/his/utils/PhoneUtil.java
  80. 4 4
      fs-service/src/main/java/com/fs/huifuPay/sdk/opps/core/net/AbstractRequest.java
  81. 5 1
      fs-service/src/main/java/com/fs/im/service/impl/OpenIMServiceImpl.java
  82. 38 9
      fs-service/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java
  83. 19 16
      fs-service/src/main/java/com/fs/live/utils/ProcessManager.java
  84. 2 2
      fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java
  85. 1 32
      fs-service/src/main/java/com/fs/qw/service/impl/QwFriendWelcomeServiceImpl.java
  86. 1 34
      fs-service/src/main/java/com/fs/qw/service/impl/QwGroupMsgServiceImpl.java
  87. 1 57
      fs-service/src/main/java/com/fs/qw/service/impl/QwMaterialServiceImpl.java
  88. 1 32
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java
  89. 1 32
      fs-service/src/main/java/com/fs/qw/service/impl/QwWelcomeServiceImpl.java
  90. 7 6
      fs-service/src/main/resources/application-common.yml
  91. 27 29
      fs-service/src/main/resources/application-config-druid-jnmy.yml
  92. 0 250
      fs-service/src/main/resources/application-druid-jnmy-test.yml
  93. 1 1
      fs-service/src/main/resources/mapper/company/CompanyDeptMapper.xml
  94. 2 1
      fs-service/src/main/resources/mapper/course/FsCourseAnswerLogsMapper.xml
  95. 1 1
      fs-service/src/main/resources/mapper/his/FsPackageOrderMapper.xml
  96. 2 1
      fs-service/src/main/resources/mapper/his/FsUserMapper.xml
  97. 7 5
      fs-service/src/main/resources/mapper/hisStore/FsUserScrmMapper.xml
  98. 2 1
      fs-service/src/main/resources/mapper/qw/HyWorkTaskMapper.xml
  99. 5 4
      fs-service/src/main/resources/mapper/qw/QwWatchLogMapper.xml
  100. 2 2
      fs-service/src/main/resources/mapper/statis/FsStatisEveryDayMapper.xml

+ 3 - 7
fs-ad-api/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,15 +1,11 @@
 package com.fs.framework.config;
 
 import com.alibaba.fastjson2.JSON;
-import com.alibaba.fastjson2.JSONReader;
-import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
 
 /**
- * Redis使用FastJson序列化
- * 
-
+ * Redis使用FastJson序列化(禁用 WriteClassName / AutoType)
  */
 public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
 {
@@ -24,7 +20,7 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (t == null) {
             return new byte[0];
         }
-        return com.alibaba.fastjson2.JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
+        return JSON.toJSONBytes(t);
     }
 
     @Override
@@ -32,6 +28,6 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (bytes == null || bytes.length == 0) {
             return null;
         }
-        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
+        return JSON.parseObject(bytes, clazz);
     }
 }

+ 2 - 8
fs-ad-api/src/main/java/com/fs/framework/config/ResourcesConfig.java

@@ -50,14 +50,8 @@ public class ResourcesConfig implements WebMvcConfigurer
     public CorsFilter corsFilter()
     {
         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
-        CorsConfiguration config = new CorsConfiguration();
-        config.setAllowCredentials(true);
-        // 设置访问源地址
-        config.addAllowedOrigin("*");
-        // 设置访问源请求头
-        config.addAllowedHeader("*");
-        // 设置访问源请求方法
-        config.addAllowedMethod("*");
+                // 显式域名白名单(Boot 2.2 无 originPattern);可用环境变量 CORS_ALLOWED_ORIGINS 覆盖
+        CorsConfiguration config = com.fs.common.config.CorsSupport.buildConfig();
         // 对接口配置跨域设置
         source.registerCorsConfiguration("/**", config);
         return new CorsFilter(source);

+ 2 - 11
fs-ad-new-api/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,8 +1,6 @@
 package com.fs.framework.config;
 
 import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.parser.ParserConfig;
-import com.alibaba.fastjson.serializer.SerializerFeature;
 import com.fasterxml.jackson.databind.JavaType;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.type.TypeFactory;
@@ -13,9 +11,7 @@ import org.springframework.util.Assert;
 import java.nio.charset.Charset;
 
 /**
- * Redis使用FastJson序列化
- * 
-
+ * Redis使用FastJson序列化(禁用 AutoType / WriteClassName,降低反序列化 RCE 风险)
  */
 public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
 {
@@ -26,11 +22,6 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
 
     private Class<T> clazz;
 
-    static
-    {
-        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
-    }
-
     public FastJson2JsonRedisSerializer(Class<T> clazz)
     {
         super();
@@ -44,7 +35,7 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         {
             return new byte[0];
         }
-        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
+        return JSON.toJSONString(t).getBytes(DEFAULT_CHARSET);
     }
 
     @Override

+ 2 - 4
fs-common-api/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,8 +1,6 @@
 package com.fs.framework.config;
 
 import com.alibaba.fastjson2.JSON;
-import com.alibaba.fastjson2.JSONReader;
-import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
 
@@ -25,7 +23,7 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
+        return JSON.toJSONBytes(t);
     }
 
     @Override
@@ -33,6 +31,6 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (bytes == null || bytes.length == 0) {
             return null;
         }
-        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
+        return JSON.parseObject(bytes, clazz);
     }
 }

+ 2 - 8
fs-common-api/src/main/java/com/fs/framework/config/ResourcesConfig.java

@@ -50,14 +50,8 @@ public class ResourcesConfig implements WebMvcConfigurer
     public CorsFilter corsFilter()
     {
         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
-        CorsConfiguration config = new CorsConfiguration();
-        config.setAllowCredentials(true);
-        // 设置访问源地址
-        config.addAllowedOrigin("*");
-        // 设置访问源请求头
-        config.addAllowedHeader("*");
-        // 设置访问源请求方法
-        config.addAllowedMethod("*");
+                // 显式域名白名单(Boot 2.2 无 originPattern);可用环境变量 CORS_ALLOWED_ORIGINS 覆盖
+        CorsConfiguration config = com.fs.common.config.CorsSupport.buildConfig();
         // 对接口配置跨域设置
         source.registerCorsConfiguration("/**", config);
         return new CorsFilter(source);

+ 51 - 0
fs-common/src/main/java/com/fs/common/config/CorsSupport.java

@@ -0,0 +1,51 @@
+package com.fs.common.config;
+
+import org.springframework.util.StringUtils;
+import org.springframework.web.cors.CorsConfiguration;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * CORS 白名单(Spring Boot 2.2 无 setAllowedOriginPatterns,使用显式 Origin 列表)。
+ * 通过环境变量 CORS_ALLOWED_ORIGINS 配置,逗号分隔;未配置时使用内置业务域名。
+ */
+public final class CorsSupport {
+
+    private static final List<String> DEFAULT_ORIGINS = Arrays.asList(
+            "https://admin.cdwjyyh.com",
+            "https://company.cdwjyyh.com",
+            "https://doctor.cdwjyyh.com",
+            "https://h5.cdwjyyh.com",
+            "http://localhost:80",
+            "http://localhost:8080",
+            "http://localhost:8081",
+            "http://localhost:1024",
+            "http://127.0.0.1:80",
+            "http://127.0.0.1:8080",
+            "http://127.0.0.1:8081",
+            "http://127.0.0.1:1024"
+    );
+
+    private CorsSupport() {
+    }
+
+    public static CorsConfiguration buildConfig() {
+        CorsConfiguration config = new CorsConfiguration();
+        config.setAllowCredentials(true);
+        for (String origin : resolveOrigins()) {
+            config.addAllowedOrigin(origin);
+        }
+        config.addAllowedHeader("*");
+        config.addAllowedMethod("*");
+        return config;
+    }
+
+    public static List<String> resolveOrigins() {
+        String env = System.getenv("CORS_ALLOWED_ORIGINS");
+        if (StringUtils.hasText(env)) {
+            return Arrays.asList(env.split("\\s*,\\s*"));
+        }
+        return DEFAULT_ORIGINS;
+    }
+}

+ 53 - 5
fs-common/src/main/java/com/fs/common/utils/SortUtils.java

@@ -3,21 +3,63 @@ package com.fs.common.utils;
 import com.fs.common.core.domain.SortRule;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.regex.Pattern;
 
 public class SortUtils {
 
+    /** 仅允许标识符形式的列名,防止 SQL 注入 */
+    private static final Pattern SAFE_COLUMN = Pattern.compile(
+            "^[a-zA-Z_][a-zA-Z0-9_]{0,63}(\\.[a-zA-Z_][a-zA-Z0-9_]{0,63})?$");
+
     /**
-     * 解析排序规则
+     * 训练营等业务当前使用的排序列白名单。
+     * 新增动态排序字段时必须同步追加,禁止放开任意列名。
+     */
+    private static final Set<String> DEFAULT_ALLOWED_COLUMNS = Collections.unmodifiableSet(
+            new HashSet<>(Arrays.asList(
+                    "order_number",
+                    "training_camp_id",
+                    "training_camp_name",
+                    "recent_date",
+                    "period_count",
+                    "ctc.order_number",
+                    "ctc.training_camp_id",
+                    "ctc.training_camp_name"
+            )));
+
+    /**
+     * 解析排序规则(使用默认列白名单)
      * @param sortStr   排序字符串  a(desc),b(asc),c(desc)
      * @return list
      */
     public static List<SortRule> parseSort(String sortStr) {
+        return parseSort(sortStr, DEFAULT_ALLOWED_COLUMNS);
+    }
+
+    /**
+     * 解析排序规则
+     * @param sortStr          排序字符串
+     * @param allowedColumns   允许的列名(小写比较)
+     */
+    public static List<SortRule> parseSort(String sortStr, Set<String> allowedColumns) {
         List<SortRule> rules = new ArrayList<>();
-        if (StringUtils.isBlank(sortStr)) {
+        if (StringUtils.isBlank(sortStr) || allowedColumns == null || allowedColumns.isEmpty()) {
             return rules;
         }
 
+        Set<String> allowedLower = new HashSet<>();
+        for (String col : allowedColumns) {
+            if (col != null) {
+                allowedLower.add(col.toLowerCase(Locale.ROOT));
+            }
+        }
+
         String[] sortParts = sortStr.split(",");
         for (String part : sortParts) {
             String[] split = part.trim().split("\\(");
@@ -25,14 +67,20 @@ public class SortUtils {
                 continue;
             }
 
-            String field = split[0].trim().replace(" ", ""); // 字段名:a, b, c
+            String field = split[0].trim().replace(" ", "");
+            if (!SAFE_COLUMN.matcher(field).matches()) {
+                continue;
+            }
+            if (!allowedLower.contains(field.toLowerCase(Locale.ROOT))) {
+                continue;
+            }
 
-            String direction = split[1].replace(")", "").trim(); // 方向:desc
+            String direction = split[1].replace(")", "").trim();
             if (!"asc".equalsIgnoreCase(direction) && !"desc".equalsIgnoreCase(direction)) {
                 continue;
             }
 
-            rules.add(new SortRule(field, direction));
+            rules.add(new SortRule(field, direction.toLowerCase(Locale.ROOT)));
         }
         return rules;
     }

+ 28 - 4
fs-common/src/main/java/com/fs/common/utils/file/FileUtils.java

@@ -147,13 +147,37 @@ public class FileUtils
         }
 
         // 检查允许下载的文件规则
-        if (ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource)))
+        if (!ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource)))
         {
-            return true;
+            return false;
+        }
+
+        // 限制必须在上传根目录之下(防路径穿越)
+        try
+        {
+            String profile = FSConfig.getProfile();
+            if (StringUtils.isNotEmpty(profile) && StringUtils.isNotEmpty(resource))
+            {
+                File base = new File(profile).getCanonicalFile();
+                File target = new File(resource).getCanonicalFile();
+                // resource 可能是相对文件名或绝对路径
+                if (target.isAbsolute())
+                {
+                    String basePath = base.getPath();
+                    String targetPath = target.getPath();
+                    if (!targetPath.startsWith(basePath + File.separator) && !targetPath.equals(basePath))
+                    {
+                        return false;
+                    }
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            return false;
         }
 
-        // 不在允许下载的文件规则
-        return false;
+        return true;
     }
 
     /**

+ 119 - 0
fs-common/src/main/java/com/fs/common/utils/http/SafeHttpUrl.java

@@ -0,0 +1,119 @@
+package com.fs.common.utils.http;
+
+import java.net.InetAddress;
+import java.net.URI;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ * 服务端拉取外部 URL 前的 SSRF 防护校验。
+ * 允许域名可通过环境变量 SAFE_FETCH_HOSTS 覆盖(逗号分隔),未配置时使用内置 OBS/COS 常见后缀。
+ */
+public final class SafeHttpUrl {
+
+    private static final Set<String> DEFAULT_HOST_SUFFIXES = new HashSet<>(Arrays.asList(
+            "myhuaweicloud.com",
+            "myqcloud.com",
+            "aliyuncs.com",
+            "qcloud.com",
+            "cdwjyyh.com",
+            "obs.cn-north-4.myhuaweicloud.com"
+    ));
+
+    private static final long MAX_BYTES = 30L * 1024 * 1024;
+
+    private SafeHttpUrl() {
+    }
+
+    public static void validateFetchUrl(String fileUrl) {
+        if (fileUrl == null || fileUrl.trim().isEmpty()) {
+            throw new IllegalArgumentException("下载地址不能为空");
+        }
+        String raw = fileUrl.trim();
+        URI uri;
+        try {
+            uri = URI.create(raw);
+        } catch (Exception e) {
+            throw new IllegalArgumentException("非法下载地址");
+        }
+        String scheme = uri.getScheme() == null ? "" : uri.getScheme().toLowerCase(Locale.ROOT);
+        if (!"https".equals(scheme) && !"http".equals(scheme)) {
+            throw new IllegalArgumentException("仅允许 http/https 下载");
+        }
+        // 生产建议仅 https;兼容历史 http OSS 链接,但禁止非标准端口以外的高危场景
+        String host = uri.getHost();
+        if (host == null || host.isEmpty()) {
+            throw new IllegalArgumentException("下载地址缺少主机名");
+        }
+        host = host.toLowerCase(Locale.ROOT);
+        if ("localhost".equals(host) || host.endsWith(".local") || host.endsWith(".internal")) {
+            throw new IllegalArgumentException("禁止访问内网地址");
+        }
+        if (!isAllowedHost(host)) {
+            throw new IllegalArgumentException("下载域名不在白名单内");
+        }
+        try {
+            InetAddress[] addrs = InetAddress.getAllByName(host);
+            for (InetAddress addr : addrs) {
+                if (isForbiddenIp(addr)) {
+                    throw new IllegalArgumentException("禁止访问内网或元数据地址");
+                }
+            }
+        } catch (IllegalArgumentException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new IllegalArgumentException("无法解析下载主机");
+        }
+    }
+
+    public static long maxBytes() {
+        return MAX_BYTES;
+    }
+
+    private static boolean isAllowedHost(String host) {
+        Set<String> suffixes = new HashSet<>(DEFAULT_HOST_SUFFIXES);
+        String env = System.getenv("SAFE_FETCH_HOSTS");
+        if (env != null && !env.trim().isEmpty()) {
+            for (String part : env.split(",")) {
+                String s = part.trim().toLowerCase(Locale.ROOT);
+                if (!s.isEmpty()) {
+                    suffixes.add(s);
+                }
+            }
+        }
+        for (String suffix : suffixes) {
+            if (host.equals(suffix) || host.endsWith("." + suffix)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean isForbiddenIp(InetAddress addr) {
+        return addr.isAnyLocalAddress()
+                || addr.isLoopbackAddress()
+                || addr.isLinkLocalAddress()
+                || addr.isSiteLocalAddress()
+                || addr.isMulticastAddress()
+                || isMetadataIp(addr);
+    }
+
+    private static boolean isMetadataIp(InetAddress addr) {
+        byte[] b = addr.getAddress();
+        // 169.254.169.254 cloud metadata
+        return b.length == 4
+                && (b[0] & 0xff) == 169
+                && (b[1] & 0xff) == 254
+                && (b[2] & 0xff) == 169
+                && (b[3] & 0xff) == 254;
+    }
+
+    /** 打开连接前再校验一次(防 DNS rebinding 可在连接后核对,此处做入口校验) */
+    public static URL toValidatedUrl(String fileUrl) throws Exception {
+        validateFetchUrl(fileUrl);
+        return new URL(fileUrl.trim());
+    }
+}

+ 64 - 0
fs-common/src/main/java/com/fs/common/utils/http/SafeUrlFile.java

@@ -0,0 +1,64 @@
+package com.fs.common.utils.http;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.UUID;
+
+/**
+ * 安全地将远程 URL 下载为临时文件(含 SSRF 防护)。
+ */
+public final class SafeUrlFile {
+
+    private SafeUrlFile() {
+    }
+
+    public static File downloadToTemp(String fileUrl) throws Exception {
+        SafeHttpUrl.validateFetchUrl(fileUrl);
+        URL url = SafeHttpUrl.toValidatedUrl(fileUrl);
+        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+        connection.setInstanceFollowRedirects(false);
+        connection.setConnectTimeout(10000);
+        connection.setReadTimeout(30000);
+        connection.setRequestMethod("GET");
+        connection.connect();
+        int code = connection.getResponseCode();
+        if (code != HttpURLConnection.HTTP_OK) {
+            throw new IllegalArgumentException("下载失败,HTTP " + code);
+        }
+
+        String name = url.getPath();
+        int slash = name.lastIndexOf('/');
+        String base = slash >= 0 ? name.substring(slash + 1) : "download";
+        int dot = base.lastIndexOf('.');
+        String suffix = (dot > 0 && dot < base.length() - 1) ? base.substring(dot) : ".tmp";
+        if (suffix.length() > 10) {
+            suffix = ".tmp";
+        }
+        File tempFile = File.createTempFile("safe_" + UUID.randomUUID().toString().replace("-", ""), suffix);
+
+        long total = 0;
+        long max = SafeHttpUrl.maxBytes();
+        try (BufferedInputStream in = new BufferedInputStream(connection.getInputStream());
+             FileOutputStream out = new FileOutputStream(tempFile)) {
+            byte[] buffer = new byte[8192];
+            int len;
+            while ((len = in.read(buffer)) != -1) {
+                total += len;
+                if (total > max) {
+                    tempFile.delete();
+                    throw new IllegalArgumentException("下载文件超过大小限制");
+                }
+                out.write(buffer, 0, len);
+            }
+            out.flush();
+        } catch (Exception e) {
+            //noinspection ResultOfMethodCallIgnored
+            tempFile.delete();
+            throw e;
+        }
+        return tempFile;
+    }
+}

+ 13 - 0
fs-company-app/src/main/java/com/fs/app/annotation/Anonymous.java

@@ -0,0 +1,13 @@
+package com.fs.app.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 允许匿名访问(默认鉴权模式下的白名单)。
+ * 方法上的 {@link Login} 优先:有 @Login 时仍须登录。
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Anonymous {
+}

+ 11 - 2
fs-company-app/src/main/java/com/fs/app/controller/CompanyUserController.java

@@ -183,12 +183,16 @@ public class CompanyUserController extends AppBaseController {
     @ApiOperation("修改用户信息")
     @PostMapping("/updateUserInfo")
     public R updateUserInfo(@Valid @RequestBody CompanyUserUpdateParam param) {
+        Long currentUserId = Long.parseLong(getUserId());
+        if (!Objects.equals(currentUserId, param.getUserId())) {
+            return R.error("非法操作");
+        }
         CompanyUser companyUser = companyUserService.selectCompanyUserById(param.getUserId());
         if (Objects.isNull(companyUser)) {
             throw new ServiceException("用户不存在");
         }
 
-        companyUser.setUserId(param.getUserId());
+        companyUser.setUserId(currentUserId);
         companyUser.setNickName(param.getNickName());
         companyUser.setPhonenumber(param.getPhoneNumber());
         companyUser.setRemark(param.getRemark());
@@ -480,10 +484,15 @@ public class CompanyUserController extends AppBaseController {
 
         //转换音频格式 mp3-wav
         String s = AudioUtils.audioWAVFromUrl(param.getVoicePrintUrl());
+        if (s == null || s.trim().isEmpty()) {
+            return R.error("声纹音频转换失败,请检查链接或本机 ffmpeg");
+        }
 
         //保存文件并且上传存储桶
-        System.out.println(s);
         File file = new File(s);
+        if (!file.exists() || !file.isFile()) {
+            return R.error("声纹临时文件不存在");
+        }
         FileInputStream fileInputStream = new FileInputStream(file);
         CloudStorageService storage = OSSFactory.build();
         String wavUrl = storage.uploadSuffix(fileInputStream, ".wav");

+ 9 - 0
fs-company-app/src/main/java/com/fs/app/controller/UserController.java

@@ -4,6 +4,7 @@ import cn.hutool.core.lang.Validator;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSONObject;
+import com.fs.app.annotation.Anonymous;
 import com.fs.app.annotation.Login;
 import com.fs.app.param.LoginParam;
 import com.fs.app.utils.JwtUtils;
@@ -147,6 +148,7 @@ public class UserController extends AppBaseController {
 //		}
 //	}
 
+    @Anonymous
     @PostMapping("/login")
     @ApiOperation("密码登录")
     public R login(@Validated @RequestBody LoginParam param) {
@@ -666,10 +668,17 @@ public class UserController extends AppBaseController {
     @PostMapping("/changeUserState")
     public R changeUserState(@RequestParam Long userId) {
         log.debug("修改用户状态 userId :{}", userId);
+        CompanyUser operator = companyUserService.selectCompanyUserByUserId(Long.parseLong(getUserId()));
+        if (ObjectUtil.isEmpty(operator) || !operator.isAdmin()) {
+            return R.error("没有权限");
+        }
         CompanyUser companyUser = companyUserService.selectCompanyUserById(userId);
         if (Objects.isNull(companyUser)) {
             throw new ServiceException("用户不存在");
         }
+        if (!Objects.equals(operator.getCompanyId(), companyUser.getCompanyId())) {
+            return R.error("非法操作");
+        }
 
         String state = "0".equals(companyUser.getStatus()) ? "1" : "0";
         companyUser.setStatus(state);

+ 36 - 21
fs-company-app/src/main/java/com/fs/app/interceptor/AuthorizationInterceptor.java

@@ -1,6 +1,7 @@
 package com.fs.app.interceptor;
 
 
+import com.fs.app.annotation.Anonymous;
 import com.fs.app.annotation.Login;
 import com.fs.app.exception.FSException;
 import com.fs.app.utils.JwtUtils;
@@ -17,7 +18,7 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 /**
- * 权限(Token)验证
+ * 权限(Token)验证 —— 默认需要登录;{@link Anonymous} 或路径白名单可匿名。
  */
 @Component
 public class AuthorizationInterceptor extends HandlerInterceptorAdapter {
@@ -27,44 +28,58 @@ public class AuthorizationInterceptor extends HandlerInterceptorAdapter {
     RedisCache redisCache;
     public static final String USER_KEY = "userId";
 
+    private static final String[] ANONYMOUS_PATH_PARTS = {
+            "/app/user/login",
+            "/app/companyUser/resisterCompanyUser",
+            "/app/sms/notify",
+            "/app/wx/",
+            "/app/common/captcha",
+            "/app/companyUser/getDict"
+    };
+
     @Override
     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
-        Login annotation;
-        if(handler instanceof HandlerMethod) {
-            annotation = ((HandlerMethod) handler).getMethodAnnotation(Login.class);
-        }else{
+        if (!(handler instanceof HandlerMethod)) {
             return true;
         }
+        HandlerMethod hm = (HandlerMethod) handler;
+        Login login = hm.getMethodAnnotation(Login.class);
+        Anonymous anonymous = hm.getMethodAnnotation(Anonymous.class);
+        if (anonymous == null) {
+            anonymous = hm.getBeanType().getAnnotation(Anonymous.class);
+        }
 
-        if(annotation == null){
+        String uri = request.getRequestURI();
+        boolean allowAnonymous = (login == null) && (anonymous != null || isAnonymousPath(uri));
+        if (allowAnonymous) {
             return true;
         }
 
-        //获取用户凭证
         String token = request.getHeader(jwtUtils.getHeader());
-        if(StringUtils.isBlank(token)){
+        if (StringUtils.isBlank(token)) {
             token = request.getParameter(jwtUtils.getHeader());
         }
-
-        //凭证为空
-        if(StringUtils.isBlank(token)){
+        if (StringUtils.isBlank(token)) {
             throw new FSException(jwtUtils.getHeader() + "不能为空", HttpStatus.UNAUTHORIZED.value());
         }
 
         Claims claims = jwtUtils.getClaimByToken(token);
-        if(claims == null || jwtUtils.isTokenExpired(claims.getExpiration())){
+        if (claims == null || jwtUtils.isTokenExpired(claims.getExpiration())) {
             throw new FSException(jwtUtils.getHeader() + "失效,请重新登录", HttpStatus.UNAUTHORIZED.value());
         }
-
-        //查询用户的TOKEN是否和REDIS中的一样
-//        String redisToken=redisCache.getCacheObject("AiChatToken:"+ Long.parseLong(claims.getSubject()));
-//        if(redisToken==null||!redisToken.equals(token)){
-//            throw new FSException(jwtUtils.getHeader() + "失效,请重新登录", HttpStatus.UNAUTHORIZED.value());
-//        }
-//        long l = Long.parseLong(claims.getSubject());
-        //设置userId到request里,后续根据userId,获取用户信息
         request.setAttribute(USER_KEY, Long.parseLong(claims.getSubject()));
-
         return true;
     }
+
+    private boolean isAnonymousPath(String uri) {
+        if (uri == null) {
+            return false;
+        }
+        for (String part : ANONYMOUS_PATH_PARTS) {
+            if (uri.contains(part)) {
+                return true;
+            }
+        }
+        return false;
+    }
 }

+ 2 - 4
fs-company-app/src/main/java/com/fs/core/config/FastJson2JsonRedisSerializer.java

@@ -1,8 +1,6 @@
 package com.fs.core.config;
 
 import com.alibaba.fastjson2.JSON;
-import com.alibaba.fastjson2.JSONReader;
-import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
 
@@ -25,7 +23,7 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
+        return JSON.toJSONBytes(t);
     }
 
     @Override
@@ -33,6 +31,6 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (bytes == null || bytes.length == 0) {
             return null;
         }
-        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
+        return JSON.parseObject(bytes, clazz);
     }
 }

+ 2 - 8
fs-company-app/src/main/java/com/fs/core/config/ResourcesConfig.java

@@ -50,14 +50,8 @@ public class ResourcesConfig implements WebMvcConfigurer
     @Bean
     public CorsFilter corsFilter() {
         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
-        CorsConfiguration config = new CorsConfiguration();
-        config.setAllowCredentials(true);
-        // 设置访问源地址
-        config.addAllowedOrigin("*");
-        // 设置访问源请求头
-        config.addAllowedHeader("*");
-        // 设置访问源请求方法
-        config.addAllowedMethod("*");
+                // 显式域名白名单(Boot 2.2 无 originPattern);可用环境变量 CORS_ALLOWED_ORIGINS 覆盖
+        CorsConfiguration config = com.fs.common.config.CorsSupport.buildConfig();
         // 对接口配置跨域设置
         source.registerCorsConfiguration("/**", config);
         return new CorsFilter(source);

+ 32 - 5
fs-company/src/main/java/com/fs/company/utils/AudioUtils.java

@@ -1,6 +1,7 @@
 package com.fs.company.utils;
 
 import com.fs.common.exception.ServiceException;
+import com.fs.common.utils.http.SafeHttpUrl;
 import com.fs.system.oss.CloudStorageService;
 import com.fs.system.oss.OSSFactory;
 
@@ -175,8 +176,20 @@ public class AudioUtils {
              process = Runtime.getRuntime().exec("taskkill -f -t -im silk_v3_encoder.exe");
              */
             // 方法2,除了会弹出弹窗,没什么问题 cmd /c 极为重要,执行完毕后会自动关闭
-            process = Runtime.getRuntime().exec("cmd /c start " + path + "silk_v3_encoder.exe " + pcmPath + " " + target + " -tencent");
-            process .waitFor();
+            assertSafeLocalPath(pcmPath);
+            assertSafeLocalPath(target);
+            java.util.List<String> silkCmd = new java.util.ArrayList<>();
+            silkCmd.add(path + "silk_v3_encoder.exe");
+            silkCmd.add(pcmPath);
+            silkCmd.add(target);
+            silkCmd.add("-tencent");
+            ProcessBuilder silkBuilder = new ProcessBuilder(silkCmd);
+            silkBuilder.redirectErrorStream(true);
+            process = silkBuilder.start();
+            try (java.io.BufferedReader silkReader = new java.io.BufferedReader(new java.io.InputStreamReader(process.getInputStream()))) {
+                while (silkReader.readLine() != null) { /* drain */ }
+            }
+            process.waitFor();
             Thread.sleep(1000);
             // 有更好的方法会后续慢慢更新..
         } catch (Exception e) {
@@ -197,13 +210,15 @@ public class AudioUtils {
         InputStream inputStream = null;
         FileOutputStream outputStream = null;
         try {
-            // 创建 HTTP 连接
-            URL url = new URL(fileUrl);
+            SafeHttpUrl.validateFetchUrl(fileUrl);
+            URL url = SafeHttpUrl.toValidatedUrl(fileUrl);
             HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+            connection.setInstanceFollowRedirects(false);
+            connection.setConnectTimeout(10000);
+            connection.setReadTimeout(30000);
             connection.setRequestMethod("GET");
             connection.connect();
 
-            // 检查是否成功连接
             if (connection.getResponseCode() != 200) {
                 throw new ServiceException("无法下载音频文件,HTTP 响应码:" + connection.getResponseCode());
             }
@@ -301,4 +316,16 @@ public class AudioUtils {
     }
 
     // 省略其他方法的实现
+
+    private static void assertSafeLocalPath(String p) {
+        if (p == null || p.isEmpty()) {
+            throw new IllegalArgumentException("路径为空");
+        }
+        if (p.indexOf(0) >= 0 || p.contains("..") || p.contains("|") || p.contains("&")
+                || p.contains(";") || p.contains("`") || p.contains("$(")
+                || p.indexOf('\n') >= 0 || p.indexOf('\r') >= 0
+                || p.contains("\"") || p.contains("'")) {
+            throw new IllegalArgumentException("非法路径参数");
+        }
+    }
 }

+ 2 - 4
fs-company/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,8 +1,6 @@
 package com.fs.framework.config;
 
 import com.alibaba.fastjson2.JSON;
-import com.alibaba.fastjson2.JSONReader;
-import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
 
@@ -24,7 +22,7 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
+        return JSON.toJSONBytes(t);
     }
 
     @Override
@@ -32,6 +30,6 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (bytes == null || bytes.length == 0) {
             return null;
         }
-        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
+        return JSON.parseObject(bytes, clazz);
     }
 }

+ 2 - 8
fs-company/src/main/java/com/fs/framework/config/ResourcesConfig.java

@@ -50,14 +50,8 @@ public class ResourcesConfig implements WebMvcConfigurer
     public CorsFilter corsFilter()
     {
         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
-        CorsConfiguration config = new CorsConfiguration();
-        config.setAllowCredentials(true);
-        // 设置访问源地址
-        config.addAllowedOrigin("*");
-        // 设置访问源请求头
-        config.addAllowedHeader("*");
-        // 设置访问源请求方法
-        config.addAllowedMethod("*");
+                // 显式域名白名单(Boot 2.2 无 originPattern);可用环境变量 CORS_ALLOWED_ORIGINS 覆盖
+        CorsConfiguration config = com.fs.common.config.CorsSupport.buildConfig();
         // 对接口配置跨域设置
         source.registerCorsConfiguration("/**", config);
         return new CorsFilter(source);

+ 3 - 7
fs-company/src/main/java/com/fs/framework/config/SecurityConfig.java

@@ -99,8 +99,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
                 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                 // 过滤请求
                 .authorizeRequests()
-                // 对于登录login 注册register 验证码captchaImage 允许匿名访问
-                .antMatchers("/chat/upload/**","/login", "/register", "/captchaImage","/checkIsNeedCheck","/getWechatQrCode","/checkWechatScan","/callback").anonymous()
+                // 对于登录login 注册register 验证码captchaImage 允许匿名访问(chat/upload 需登录)
+                .antMatchers("/login", "/register", "/captchaImage","/checkIsNeedCheck","/getWechatQrCode","/checkWechatScan","/callback").anonymous()
                 .antMatchers(
                         HttpMethod.GET,
                         "/",
@@ -118,13 +118,9 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
                 .antMatchers("/msg/**/**").anonymous()
                 .antMatchers("/msg").anonymous()
                 .antMatchers("/common/getId**").anonymous()
-                .antMatchers("/common/uploadOSS**").anonymous()
-                .antMatchers("/company/user/common/uploadOSS").anonymous()
+                // uploadOSS / uploadWang / download 需登录,禁止匿名上传下载
                 .antMatchers("/pay/wxPay/payNotify**").anonymous()
-                .antMatchers("/common/uploadWang**").anonymous()
-                .antMatchers("/common/download**").anonymous()
                 .antMatchers("/common/test").anonymous()
-                .antMatchers("/common/download/resource**").anonymous()
                 .antMatchers("/swagger-ui.html").anonymous()
                 .antMatchers("/swagger-resources/**").anonymous()
                 .antMatchers("/webjars/**").anonymous()

+ 13 - 0
fs-doctor-app/src/main/java/com/fs/app/annotation/Anonymous.java

@@ -0,0 +1,13 @@
+package com.fs.app.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 允许匿名访问(默认鉴权模式下的白名单)。
+ * 方法上的 {@link Login} 优先:有 @Login 时仍须登录。
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Anonymous {
+}

+ 4 - 0
fs-doctor-app/src/main/java/com/fs/app/controller/DoctorController.java

@@ -3,6 +3,7 @@ package com.fs.app.controller;
 
 import cn.hutool.http.HttpRequest;
 import cn.hutool.json.JSONUtil;
+import com.fs.app.annotation.Anonymous;
 import com.fs.app.annotation.Login;
 import com.fs.app.param.DoctorEditParam;
 import com.fs.app.param.DoctorEditPwdParam;
@@ -70,6 +71,7 @@ public class DoctorController extends  AppBaseController {
     @Autowired
     private SmsService smsService;
     @ApiOperation("登录")
+    @Anonymous
     @PostMapping("/login")
     public R login(@Validated  @RequestBody DoctorLoginParam param) {
         FsDoctor doctor=doctorService.selectFsDoctorByAccount(param.getAccount());
@@ -108,6 +110,7 @@ public class DoctorController extends  AppBaseController {
         }
     }
     @ApiOperation("校验医生是否注册新的im")
+    @Anonymous
     @PostMapping("/accountCheck")
     public R accountCheck(@RequestBody Map<String, String> userIdMap){
         //获取管理员token
@@ -175,6 +178,7 @@ public class DoctorController extends  AppBaseController {
         }
     }
     @ApiOperation("登录")
+    @Anonymous
     @PostMapping("/loginByWeb")
     public R loginByWeb(@Validated @RequestBody DoctorLoginParam param) {
         FsDoctor doctor=doctorService.selectFsDoctorByAccount(param.getAccount());

+ 8 - 0
fs-doctor-app/src/main/java/com/fs/app/controller/FollowController.java

@@ -39,6 +39,7 @@ public class FollowController extends AppBaseController {
 
     @Autowired
     private IFsUserService userService;
+    @Login
     @ApiOperation("获取随访列表")
     @GetMapping("/getFollowList")
     public R getFollowList(FsFollowListDParam param)
@@ -56,10 +57,17 @@ public class FollowController extends AppBaseController {
         return R.ok().put("data",listPageInfo);
     }
 
+    @Login
     @ApiOperation("获取随访详情")
     @GetMapping("/getFollowById")
     public R getFollowById(@RequestParam("followId")Long followId, HttpServletRequest request){
         FsFollow follow=followService.selectFsFollowByFollowId(followId);
+        if(follow == null){
+            return R.error("随访不存在");
+        }
+        if(follow.getDoctorId() == null || !follow.getDoctorId().equals(Long.parseLong(getDoctorId()))){
+            return R.error("非法操作");
+        }
         if(follow.getPatientPhone()!=null&&!follow.getPatientPhone().equals("")) {
             follow.setPatientPhone(follow.getPatientPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
         }

+ 46 - 2
fs-doctor-app/src/main/java/com/fs/app/controller/FsUserInformationCollectionController.java

@@ -1,5 +1,6 @@
 package com.fs.app.controller;
 
+import com.fs.app.annotation.Login;
 import com.fs.common.core.domain.R;
 import com.fs.his.domain.FsUserInformationCollection;
 import com.fs.his.param.UserInformationDoctorType2Param;
@@ -10,25 +11,53 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
+import java.util.Objects;
 
 @RestController
 @RequestMapping("/user/collection")
 public class FsUserInformationCollectionController extends  AppBaseController {
     @Autowired
     private IFsUserInformationCollectionService fsUserInformationCollectionService;
+
+    @Login
     @GetMapping("/getUserInformation")
     public R getUserInformation(@RequestParam("id") Long id) {
-
+        FsUserInformationCollection info = fsUserInformationCollectionService.selectFsUserInformationCollectionById(id);
+        if (info == null) {
+            return R.error("未查询到信息");
+        }
+        Long doctorId = Long.parseLong(getDoctorId());
+        boolean allowed = Objects.equals(info.getDoctorId(), doctorId)
+                || Objects.equals(info.getDoctorType2Id(), doctorId);
+        if (!allowed) {
+            return R.error("非法操作");
+        }
         return R.ok().put("data", fsUserInformationCollectionService.selectFsUserInformationCollectionVoById(id));
     }
+
     //医生确认
+    @Login
     @PostMapping("/doctorConfirm")
     public R doctorConfirm(@RequestBody FsUserInformationCollection collection){
+        if (collection.getId() == null) {
+            return R.error("参数错误");
+        }
+        FsUserInformationCollection info = fsUserInformationCollectionService.selectFsUserInformationCollectionById(collection.getId());
+        if (info == null) {
+            return R.error("未查询到信息");
+        }
+        Long doctorId = Long.parseLong(getDoctorId());
+        if (!Objects.equals(info.getDoctorId(), doctorId)) {
+            return R.error("非法操作");
+        }
+        collection.setDoctorId(doctorId);
+        collection.setUserId(info.getUserId());
         return fsUserInformationCollectionService.doctorConfirm(collection);
     }
 
+    @Login
     @GetMapping("/getCollectionList")
-    private R getCollectionList(UserInformationDoctorType2Param userInformationDoctorType2Param) {
+    public R getCollectionList(UserInformationDoctorType2Param userInformationDoctorType2Param) {
 
         PageHelper.startPage(userInformationDoctorType2Param.getPageNum(), userInformationDoctorType2Param.getPageSize());
         if (userInformationDoctorType2Param.getDoctorType()==2){
@@ -48,8 +77,23 @@ public class FsUserInformationCollectionController extends  AppBaseController {
 
 
     //药师确认
+    @Login
     @PostMapping("/doctorType2Confirm")
     public R doctorType2Confirm(@RequestBody FsUserInformationCollection collection){
+        if (collection.getId() == null) {
+            return R.error("参数错误");
+        }
+        FsUserInformationCollection info = fsUserInformationCollectionService.selectFsUserInformationCollectionById(collection.getId());
+        if (info == null) {
+            return R.error("未查询到信息");
+        }
+        Long doctorId = Long.parseLong(getDoctorId());
+        if (!Objects.equals(info.getDoctorType2Id(), doctorId)) {
+            return R.error("非法操作");
+        }
+        collection.setDoctorType2Id(doctorId);
+        collection.setUserId(info.getUserId());
+        collection.setPackageOrderCode(info.getPackageOrderCode());
         return fsUserInformationCollectionService.doctorType2Confirm(collection);
     }
 }

+ 31 - 0
fs-doctor-app/src/main/java/com/fs/app/controller/InquiryOrderController.java

@@ -117,6 +117,13 @@ public class InquiryOrderController extends  AppBaseController {
     public R getInquiryOrderDetailsByOrderId(@RequestParam("orderId")Long orderId)
     {
         FsInquiryOrder order=inquiryOrderService.selectFsInquiryOrderByOrderId(orderId);
+        if(order == null){
+            return R.error("订单不存在");
+        }
+        Long doctorId = Long.parseLong(getDoctorId());
+        if(order.getDoctorId() != null && !order.getDoctorId().equals(doctorId)){
+            return R.error("非法操作");
+        }
         return R.ok().put("data",order);
     }
 
@@ -133,6 +140,13 @@ public class InquiryOrderController extends  AppBaseController {
     {
         Map<String,Object> maps=new HashMap<>();
         FsInquiryOrder order=inquiryOrderService.selectFsInquiryOrderByOrderId(orderId);
+        if(order == null){
+            return R.error("订单不存在");
+        }
+        Long doctorId = Long.parseLong(getDoctorId());
+        if(order.getDoctorId() != null && !order.getDoctorId().equals(doctorId)){
+            return R.error("非法操作");
+        }
         if (order.getPatientJson() != null&&!"".equals(order.getPatientJson())) {
             FsInquiryOrderPatientDTO fsInquiryOrderPatientDTO = JSON.parseObject(order.getPatientJson(), FsInquiryOrderPatientDTO.class);
             if(fsInquiryOrderPatientDTO.getMobile()!=null&&!"".equals(fsInquiryOrderPatientDTO.getMobile())){
@@ -353,11 +367,19 @@ public class InquiryOrderController extends  AppBaseController {
     }
 
 
+    @Login
     @GetMapping(value = "/queryPhone/{orderId}")
     @Log(title = "查看电话", businessType = BusinessType.GRANT)
     public R getPhone(@PathVariable("orderId") Long orderId)
     {
         FsInquiryOrder fsInquiryOrder = inquiryOrderService.selectFsInquiryOrderByOrderId(orderId);
+        if(fsInquiryOrder == null){
+            return R.error("订单不存在");
+        }
+        Long doctorId = Long.parseLong(getDoctorId());
+        if(fsInquiryOrder.getDoctorId() == null || !fsInquiryOrder.getDoctorId().equals(doctorId)){
+            return R.error("非法操作");
+        }
         String patientJson = fsInquiryOrder.getPatientJson();
         if (patientJson != null&&!"".equals(patientJson)) {
             FsInquiryOrderPatientDTO fsInquiryOrderPatientDTO = JSON.parseObject(patientJson, FsInquiryOrderPatientDTO.class);
@@ -371,8 +393,17 @@ public class InquiryOrderController extends  AppBaseController {
         return R.ok().put("data","");
     }
 
+    @Login
     @PostMapping("/closeOrder")
     public R closeOrder(@RequestBody Long orderId){
+        FsInquiryOrder order = inquiryOrderService.selectFsInquiryOrderByOrderId(orderId);
+        if(order == null){
+            return R.error("订单不存在");
+        }
+        Long doctorId = Long.parseLong(getDoctorId());
+        if(order.getDoctorId() == null || !order.getDoctorId().equals(doctorId)){
+            return R.error("非法操作");
+        }
         inquiryOrderService.closeOrder(orderId);
         logger.info("closeOrder: {}", orderId);
         return R.ok();

+ 17 - 12
fs-doctor-app/src/main/java/com/fs/app/controller/PatientController.java

@@ -1,25 +1,17 @@
 package com.fs.app.controller;
 
-import com.fs.common.annotation.Log;
-import com.fs.common.core.controller.BaseController;
-import com.fs.common.core.domain.AjaxResult;
+import com.fs.app.annotation.Login;
 import com.fs.common.core.domain.R;
-import com.fs.common.core.page.TableDataInfo;
-import com.fs.common.enums.BusinessType;
-import com.fs.common.utils.poi.ExcelUtil;
-import com.fs.his.domain.FsDoctor;
-import com.fs.his.domain.FsFollow;
+import com.fs.his.domain.FsInquiryOrder;
 import com.fs.his.domain.FsPatient;
 import com.fs.his.param.FsPatientListDParam;
+import com.fs.his.service.IFsInquiryOrderService;
 import com.fs.his.service.IFsPatientService;
-import com.fs.his.vo.FsFollowListDVO;
 import com.fs.his.vo.FsPatientListDVO;
-import com.fs.his.vo.FsPatientVO;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletRequest;
@@ -31,8 +23,10 @@ public class PatientController extends AppBaseController
 {
     @Autowired
     private IFsPatientService fsPatientService;
+    @Autowired
+    private IFsInquiryOrderService inquiryOrderService;
 
-
+    @Login
     @GetMapping("/getPatientList")
     public R getPatientList(FsPatientListDParam param)
     {
@@ -43,10 +37,21 @@ public class PatientController extends AppBaseController
         return R.ok().put("data",listPageInfo);
     }
 
+    @Login
     @ApiOperation("获取详情")
     @GetMapping("/getPatientByPatientId")
     public R getPatientByPatientId(@RequestParam("patientId")Long patientId, HttpServletRequest request){
         FsPatient patient=fsPatientService.selectFsPatientByPatientId(patientId);
+        if(patient == null){
+            return R.error("患者不存在");
+        }
+        FsInquiryOrder query = new FsInquiryOrder();
+        query.setDoctorId(Long.parseLong(getDoctorId()));
+        query.setPatientId(patientId);
+        List<FsInquiryOrder> orders = inquiryOrderService.selectFsInquiryOrderList(query);
+        if(orders == null || orders.isEmpty()){
+            return R.error("非法操作");
+        }
         return R.ok().put("data",patient);
     }
 }

+ 12 - 0
fs-doctor-app/src/main/java/com/fs/app/controller/PrescribeController.java

@@ -105,6 +105,12 @@ public class PrescribeController extends  AppBaseController {
     public R getPrescribeById(@RequestParam("prescribeId")Long prescribeId)
     {
         FsPrescribe prescribe=prescribeService.selectFsPrescribeByPrescribeId(prescribeId);
+        if(prescribe == null){
+            return R.error("处方不存在");
+        }
+        if(prescribe.getDoctorId() == null || !prescribe.getDoctorId().equals(Long.parseLong(getDoctorId()))){
+            return R.error("非法操作");
+        }
         FsPrescribeDrug map=new FsPrescribeDrug();
         map.setPrescribeId(prescribeId);
         List<FsPrescribeDrug> drugs=prescribeDrugService.selectFsPrescribeDrugList(map);
@@ -133,6 +139,12 @@ public class PrescribeController extends  AppBaseController {
     public R getDoctorPrescribeById(@RequestParam("prescribeId")Long prescribeId)
     {
         FsDoctorPrescribe prescribe=doctorPrescribeService.selectFsDoctorPrescribeByPrescribeId(prescribeId);
+        if(prescribe == null){
+            return R.error("处方不存在");
+        }
+        if(prescribe.getDoctorId() == null || !prescribe.getDoctorId().equals(Long.parseLong(getDoctorId()))){
+            return R.error("非法操作");
+        }
         FsDoctorPrescribeDrug map=new FsDoctorPrescribeDrug();
         map.setPrescribeId(prescribeId);
         List<FsDoctorPrescribeDrug> drugs=doctorPrescribeDrugService.selectFsDoctorPrescribeDrugList(map);

+ 46 - 18
fs-doctor-app/src/main/java/com/fs/app/interceptor/AuthorizationInterceptor.java

@@ -1,6 +1,7 @@
 package com.fs.app.interceptor;
 
 
+import com.fs.app.annotation.Anonymous;
 import com.fs.app.annotation.Login;
 import com.fs.app.exception.FSException;
 import com.fs.app.utils.JwtUtils;
@@ -17,7 +18,7 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 /**
- * 权限(Token)验证
+ * 权限(Token)验证 —— 默认需要登录;{@link Anonymous} 或路径白名单可匿名。
  */
 @Component
 public class AuthorizationInterceptor extends HandlerInterceptorAdapter {
@@ -27,42 +28,69 @@ public class AuthorizationInterceptor extends HandlerInterceptorAdapter {
     RedisCache redisCache;
     public static final String USER_KEY = "userId";
 
+    private static final String[] ANONYMOUS_PATH_PARTS = {
+            "/app/doctor/login",
+            "/app/doctor/accountCheck",
+            "/app/doctor/loginByWeb",
+            "/app/common/captchaImage",
+            "/app/common/notifyImMsg",
+            "/app/common/callback"
+    };
+
     @Override
     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
-        Login annotation;
-        if(handler instanceof HandlerMethod) {
-            annotation = ((HandlerMethod) handler).getMethodAnnotation(Login.class);
-        }else{
+        if (!(handler instanceof HandlerMethod)) {
             return true;
         }
+        HandlerMethod hm = (HandlerMethod) handler;
+        Login login = hm.getMethodAnnotation(Login.class);
+        Anonymous anonymous = hm.getMethodAnnotation(Anonymous.class);
+        if (anonymous == null) {
+            anonymous = hm.getBeanType().getAnnotation(Anonymous.class);
+        }
 
-        if(annotation == null){
+        String uri = request.getRequestURI();
+        boolean allowAnonymous = (login == null) && (anonymous != null || isAnonymousPath(uri));
+        if (allowAnonymous) {
             return true;
         }
 
-        //获取用户凭证
         String token = request.getHeader(jwtUtils.getHeader());
-        if(StringUtils.isBlank(token)){
+        if (StringUtils.isBlank(token)) {
             token = request.getParameter(jwtUtils.getHeader());
         }
-
-        //凭证为空
-        if(StringUtils.isBlank(token)){
+        if (StringUtils.isBlank(token)) {
             throw new FSException(jwtUtils.getHeader() + "不能为空", HttpStatus.UNAUTHORIZED.value());
         }
 
         Claims claims = jwtUtils.getClaimByToken(token);
-        if(claims == null || jwtUtils.isTokenExpired(claims.getExpiration())){
+        if (claims == null || jwtUtils.isTokenExpired(claims.getExpiration())) {
             throw new FSException(jwtUtils.getHeader() + "失效,请重新登录", HttpStatus.UNAUTHORIZED.value());
         }
-        //查询用户的TOKEN是否和REDIS中的一样
-        String redisToken=redisCache.getCacheObject("doctorToken:"+ Long.parseLong(claims.getSubject()));
-        if(redisToken==null||!redisToken.equals(token)){
-            throw new FSException(jwtUtils.getHeader() + "失效,请重新登录", HttpStatus.UNAUTHORIZED.value());
+        // doctor Redis token 一致性(若存在则校验)
+        try {
+            String redisToken = redisCache.getCacheObject("doctorToken:" + Long.parseLong(claims.getSubject()));
+            if (redisToken != null && !redisToken.equals(token)) {
+                throw new FSException(jwtUtils.getHeader() + "失效,请重新登录", HttpStatus.UNAUTHORIZED.value());
+            }
+        } catch (FSException e) {
+            throw e;
+        } catch (Exception ignored) {
+            // Redis 异常时不阻断(降级为仅 JWT)
         }
-        //设置userId到request里,后续根据userId,获取用户信息
         request.setAttribute(USER_KEY, Long.parseLong(claims.getSubject()));
-
         return true;
     }
+
+    private boolean isAnonymousPath(String uri) {
+        if (uri == null) {
+            return false;
+        }
+        for (String part : ANONYMOUS_PATH_PARTS) {
+            if (uri.contains(part)) {
+                return true;
+            }
+        }
+        return false;
+    }
 }

+ 2 - 4
fs-doctor-app/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,8 +1,6 @@
 package com.fs.framework.config;
 
 import com.alibaba.fastjson2.JSON;
-import com.alibaba.fastjson2.JSONReader;
-import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
 
@@ -25,7 +23,7 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
+        return JSON.toJSONBytes(t);
     }
 
     @Override
@@ -33,6 +31,6 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (bytes == null || bytes.length == 0) {
             return null;
         }
-        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
+        return JSON.parseObject(bytes, clazz);
     }
 }

+ 2 - 8
fs-doctor-app/src/main/java/com/fs/framework/config/ResourcesConfig.java

@@ -50,14 +50,8 @@ public class ResourcesConfig implements WebMvcConfigurer
     public CorsFilter corsFilter()
     {
         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
-        CorsConfiguration config = new CorsConfiguration();
-        config.setAllowCredentials(true);
-        // 设置访问源地址
-        config.addAllowedOrigin("*");
-        // 设置访问源请求头
-        config.addAllowedHeader("*");
-        // 设置访问源请求方法
-        config.addAllowedMethod("*");
+                // 显式域名白名单(Boot 2.2 无 originPattern);可用环境变量 CORS_ALLOWED_ORIGINS 覆盖
+        CorsConfiguration config = com.fs.common.config.CorsSupport.buildConfig();
         // 对接口配置跨域设置
         source.registerCorsConfiguration("/**", config);
         return new CorsFilter(source);

+ 2 - 4
fs-framework/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,8 +1,6 @@
 package com.fs.framework.config;
 
 import com.alibaba.fastjson2.JSON;
-import com.alibaba.fastjson2.JSONReader;
-import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
 
@@ -24,7 +22,7 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
+        return JSON.toJSONBytes(t);
     }
 
     @Override
@@ -32,6 +30,6 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (bytes == null || bytes.length == 0) {
             return null;
         }
-        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
+        return JSON.parseObject(bytes, clazz);
     }
 }

+ 2 - 8
fs-framework/src/main/java/com/fs/framework/config/ResourcesConfig.java

@@ -59,14 +59,8 @@ public class ResourcesConfig implements WebMvcConfigurer
     public CorsFilter corsFilter()
     {
         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
-        CorsConfiguration config = new CorsConfiguration();
-        config.setAllowCredentials(true);
-        // 设置访问源地址
-        config.addAllowedOrigin("*");
-        // 设置访问源请求头
-        config.addAllowedHeader("*");
-        // 设置访问源请求方法
-        config.addAllowedMethod("*");
+                // 显式域名白名单(Boot 2.2 无 originPattern);可用环境变量 CORS_ALLOWED_ORIGINS 覆盖
+        CorsConfiguration config = com.fs.common.config.CorsSupport.buildConfig();
         // 对接口配置跨域设置
         source.registerCorsConfiguration("/**", config);
         return new CorsFilter(source);

+ 1 - 5
fs-framework/src/main/java/com/fs/framework/config/SecurityConfig.java

@@ -132,11 +132,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
                 .antMatchers("/tzPay/*").anonymous()
                 .antMatchers("//his/pay/*").anonymous()
                 .antMatchers("/common/getId**").anonymous()
-                .antMatchers("/common/uploadOSS**").anonymous()
-                .antMatchers("/chat/upload/uploadFile**").anonymous()
-                .antMatchers("/common/uploadWang**").anonymous()
-                .antMatchers("/common/download**").anonymous()
-                .antMatchers("/common/download/resource**").anonymous()
+                // uploadOSS / uploadWang / download 需登录,禁止匿名上传下载
                 .antMatchers("/common/unbindQwUserByServerIds").anonymous()
                 .antMatchers("/swagger-ui.html").anonymous()
                 .antMatchers("/swagger-resources/**").anonymous()

+ 1 - 1
fs-ipad-task/src/main/java/com/fs/app/task/SendAppMsg.java

@@ -130,7 +130,7 @@ public class SendAppMsg {
         qwUserList.clear();
     }
 
-    @Scheduled(fixedDelay = 20000) // 每20秒执行一次
+    @Scheduled(fixedDelay = 30000) // 每20秒执行一次
     public void sendMsg2() {
         if (StringUtils.isEmpty(groupNo)) {
             log.error("corpId为空不执行");

+ 2 - 4
fs-ipad-task/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,8 +1,6 @@
 package com.fs.framework.config;
 
 import com.alibaba.fastjson2.JSON;
-import com.alibaba.fastjson2.JSONReader;
-import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
 
@@ -25,7 +23,7 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
+        return JSON.toJSONBytes(t);
     }
 
     @Override
@@ -33,6 +31,6 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (bytes == null || bytes.length == 0) {
             return null;
         }
-        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
+        return JSON.parseObject(bytes, clazz);
     }
 }

+ 2 - 8
fs-ipad-task/src/main/java/com/fs/framework/config/ResourcesConfig.java

@@ -54,14 +54,8 @@ public class ResourcesConfig implements WebMvcConfigurer
     public CorsFilter corsFilter()
     {
         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
-        CorsConfiguration config = new CorsConfiguration();
-        config.setAllowCredentials(true);
-        // 设置访问源地址
-        config.addAllowedOrigin("*");
-        // 设置访问源请求头
-        config.addAllowedHeader("*");
-        // 设置访问源请求方法
-        config.addAllowedMethod("*");
+                // 显式域名白名单(Boot 2.2 无 originPattern);可用环境变量 CORS_ALLOWED_ORIGINS 覆盖
+        CorsConfiguration config = com.fs.common.config.CorsSupport.buildConfig();
         // 对接口配置跨域设置
         source.registerCorsConfiguration("/**", config);
         return new CorsFilter(source);

+ 2 - 4
fs-live-app/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,8 +1,6 @@
 package com.fs.framework.config;
 
 import com.alibaba.fastjson2.JSON;
-import com.alibaba.fastjson2.JSONReader;
-import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
 
@@ -25,7 +23,7 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
+        return JSON.toJSONBytes(t);
     }
 
     @Override
@@ -33,6 +31,6 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (bytes == null || bytes.length == 0) {
             return null;
         }
-        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
+        return JSON.parseObject(bytes, clazz);
     }
 }

+ 2 - 8
fs-live-app/src/main/java/com/fs/framework/config/ResourcesConfig.java

@@ -50,14 +50,8 @@ public class ResourcesConfig implements WebMvcConfigurer
     public CorsFilter corsFilter()
     {
         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
-        CorsConfiguration config = new CorsConfiguration();
-        config.setAllowCredentials(true);
-        // 设置访问源地址
-        config.addAllowedOrigin("*");
-        // 设置访问源请求头
-        config.addAllowedHeader("*");
-        // 设置访问源请求方法
-        config.addAllowedMethod("*");
+                // 显式域名白名单(Boot 2.2 无 originPattern);可用环境变量 CORS_ALLOWED_ORIGINS 覆盖
+        CorsConfiguration config = com.fs.common.config.CorsSupport.buildConfig();
         // 对接口配置跨域设置
         source.registerCorsConfiguration("/**", config);
         return new CorsFilter(source);

+ 13 - 0
fs-live-app/src/main/java/com/fs/live/annotation/Anonymous.java

@@ -0,0 +1,13 @@
+package com.fs.live.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 允许匿名访问(默认鉴权模式下的白名单)。
+ * 方法上的 {@link Login} 优先:有 @Login 时仍须登录。
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Anonymous {
+}

+ 20 - 0
fs-live-app/src/main/java/com/fs/live/config/WebMvcConfig.java

@@ -0,0 +1,20 @@
+package com.fs.live.config;
+
+import com.fs.live.interceptor.AuthorizationInterceptor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class WebMvcConfig implements WebMvcConfigurer {
+
+    @Autowired
+    private AuthorizationInterceptor authorizationInterceptor;
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(authorizationInterceptor)
+                .addPathPatterns("/app/**", "/ws/app/**");
+    }
+}

+ 2 - 0
fs-live-app/src/main/java/com/fs/live/controller/LiveController.java

@@ -8,6 +8,7 @@ import com.fs.common.core.domain.R;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.bean.BeanUtils;
+import com.fs.live.annotation.Anonymous;
 import com.fs.live.vo.LiveVo;
 import com.fs.live.domain.Live;
 import com.fs.live.domain.LiveMsg;
@@ -36,6 +37,7 @@ import java.util.*;
 
 
 @Api("直播信息接口")
+@Anonymous
 @RestController("wsAppLiveController")
 @AllArgsConstructor
 @RequestMapping(value="/ws/app/live")

+ 3 - 1
fs-live-app/src/main/java/com/fs/live/controller/LiveDataController.java

@@ -3,6 +3,7 @@ package com.fs.live.controller;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.live.annotation.Login;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -16,8 +17,9 @@ public class LiveDataController extends BaseController {
     @Autowired
     private RedisCache redisCache;
     /**
-     * 点赞
+     * 点赞(需登录,防止匿名刷赞)
      * */
+    @Login
     @GetMapping("/like/{liveId}")
     public R like(@PathVariable("liveId") Long liveId) {
         //直播间总点赞数

+ 89 - 0
fs-live-app/src/main/java/com/fs/live/interceptor/AuthorizationInterceptor.java

@@ -0,0 +1,89 @@
+package com.fs.live.interceptor;
+
+import com.fs.live.annotation.Anonymous;
+import com.fs.live.annotation.Login;
+import com.fs.live.exception.FSException;
+import com.fs.live.utils.JwtUtils;
+import com.fs.common.utils.StringUtils;
+import io.jsonwebtoken.Claims;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Component;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * live-app HTTP 鉴权:默认需登录;腾讯云回调等可 {@link Anonymous}。
+ */
+@Component
+public class AuthorizationInterceptor extends HandlerInterceptorAdapter {
+
+    public static final String USER_KEY = "userId";
+
+    @Autowired
+    private JwtUtils jwtUtils;
+
+    private static final String[] ANONYMOUS_PATH_PARTS = {
+            "/ws/app/live/startLiving",
+            "/ws/app/live/endLiving",
+            "/ws/app/live/liveReplayFile"
+    };
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
+        if (!(handler instanceof HandlerMethod)) {
+            return true;
+        }
+        HandlerMethod hm = (HandlerMethod) handler;
+        Login login = hm.getMethodAnnotation(Login.class);
+        Anonymous anonymous = hm.getMethodAnnotation(Anonymous.class);
+        if (anonymous == null) {
+            anonymous = hm.getBeanType().getAnnotation(Anonymous.class);
+        }
+        String uri = request.getRequestURI();
+        boolean allowAnonymous = (login == null) && (anonymous != null || isAnonymousPath(uri));
+        if (allowAnonymous) {
+            // 回调可选共享密钥
+            if (uri != null && uri.contains("/ws/app/live/")) {
+                String secret = System.getenv("LIVE_CALLBACK_SECRET");
+                if (StringUtils.isNotBlank(secret)) {
+                    String header = request.getHeader("X-Live-Callback-Secret");
+                    String param = request.getParameter("liveSecret");
+                    if (!secret.equals(header) && !secret.equals(param)) {
+                        throw new FSException("回调鉴权失败", HttpStatus.UNAUTHORIZED.value());
+                    }
+                }
+            }
+            return true;
+        }
+
+        String token = request.getHeader(jwtUtils.getHeader());
+        if (StringUtils.isBlank(token)) {
+            token = request.getParameter(jwtUtils.getHeader());
+        }
+        if (StringUtils.isBlank(token)) {
+            throw new FSException(jwtUtils.getHeader() + "不能为空", HttpStatus.UNAUTHORIZED.value());
+        }
+        Claims claims = jwtUtils.getClaimByToken(token);
+        if (claims == null || jwtUtils.isTokenExpired(claims.getExpiration())) {
+            throw new FSException(jwtUtils.getHeader() + "失效,请重新登录", HttpStatus.UNAUTHORIZED.value());
+        }
+        request.setAttribute(USER_KEY, Long.parseLong(claims.getSubject()));
+        return true;
+    }
+
+    private boolean isAnonymousPath(String uri) {
+        if (uri == null) {
+            return false;
+        }
+        for (String part : ANONYMOUS_PATH_PARTS) {
+            if (uri.contains(part)) {
+                return true;
+            }
+        }
+        return false;
+    }
+}

+ 2 - 4
fs-qw-api-msg/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,8 +1,6 @@
 package com.fs.framework.config;
 
 import com.alibaba.fastjson2.JSON;
-import com.alibaba.fastjson2.JSONReader;
-import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
 
@@ -25,7 +23,7 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
+        return JSON.toJSONBytes(t);
     }
 
     @Override
@@ -33,6 +31,6 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (bytes == null || bytes.length <= 0) {
             return null;
         }
-        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
+        return JSON.parseObject(bytes, clazz);
     }
 }

+ 2 - 8
fs-qw-api-msg/src/main/java/com/fs/framework/config/ResourcesConfig.java

@@ -50,14 +50,8 @@ public class ResourcesConfig implements WebMvcConfigurer
     public CorsFilter corsFilter()
     {
         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
-        CorsConfiguration config = new CorsConfiguration();
-        config.setAllowCredentials(true);
-        // 设置访问源地址
-        config.addAllowedOrigin("*");
-        // 设置访问源请求头
-        config.addAllowedHeader("*");
-        // 设置访问源请求方法
-        config.addAllowedMethod("*");
+                // 显式域名白名单(Boot 2.2 无 originPattern);可用环境变量 CORS_ALLOWED_ORIGINS 覆盖
+        CorsConfiguration config = com.fs.common.config.CorsSupport.buildConfig();
         // 对接口配置跨域设置
         source.registerCorsConfiguration("/**", config);
         return new CorsFilter(source);

+ 2 - 4
fs-qw-api/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,8 +1,6 @@
 package com.fs.framework.config;
 
 import com.alibaba.fastjson2.JSON;
-import com.alibaba.fastjson2.JSONReader;
-import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
 
@@ -25,7 +23,7 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
+        return JSON.toJSONBytes(t);
     }
 
     @Override
@@ -33,6 +31,6 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (bytes == null || bytes.length == 0) {
             return null;
         }
-        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
+        return JSON.parseObject(bytes, clazz);
     }
 }

+ 2 - 8
fs-qw-api/src/main/java/com/fs/framework/config/ResourcesConfig.java

@@ -50,14 +50,8 @@ public class ResourcesConfig implements WebMvcConfigurer
     public CorsFilter corsFilter()
     {
         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
-        CorsConfiguration config = new CorsConfiguration();
-        config.setAllowCredentials(true);
-        // 设置访问源地址
-        config.addAllowedOrigin("*");
-        // 设置访问源请求头
-        config.addAllowedHeader("*");
-        // 设置访问源请求方法
-        config.addAllowedMethod("*");
+                // 显式域名白名单(Boot 2.2 无 originPattern);可用环境变量 CORS_ALLOWED_ORIGINS 覆盖
+        CorsConfiguration config = com.fs.common.config.CorsSupport.buildConfig();
         // 对接口配置跨域设置
         source.registerCorsConfiguration("/**", config);
         return new CorsFilter(source);

+ 2 - 4
fs-qw-mq/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,8 +1,6 @@
 package com.fs.framework.config;
 
 import com.alibaba.fastjson2.JSON;
-import com.alibaba.fastjson2.JSONReader;
-import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
 
@@ -25,7 +23,7 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
+        return JSON.toJSONBytes(t);
     }
 
     @Override
@@ -33,6 +31,6 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (bytes == null || bytes.length == 0) {
             return null;
         }
-        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
+        return JSON.parseObject(bytes, clazz);
     }
 }

+ 2 - 8
fs-qw-mq/src/main/java/com/fs/framework/config/ResourcesConfig.java

@@ -50,14 +50,8 @@ public class ResourcesConfig implements WebMvcConfigurer
     public CorsFilter corsFilter()
     {
         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
-        CorsConfiguration config = new CorsConfiguration();
-        config.setAllowCredentials(true);
-        // 设置访问源地址
-        config.addAllowedOrigin("*");
-        // 设置访问源请求头
-        config.addAllowedHeader("*");
-        // 设置访问源请求方法
-        config.addAllowedMethod("*");
+                // 显式域名白名单(Boot 2.2 无 originPattern);可用环境变量 CORS_ALLOWED_ORIGINS 覆盖
+        CorsConfiguration config = com.fs.common.config.CorsSupport.buildConfig();
         // 对接口配置跨域设置
         source.registerCorsConfiguration("/**", config);
         return new CorsFilter(source);

+ 2 - 4
fs-qw-task/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,8 +1,6 @@
 package com.fs.framework.config;
 
 import com.alibaba.fastjson2.JSON;
-import com.alibaba.fastjson2.JSONReader;
-import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
 
@@ -25,7 +23,7 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
+        return JSON.toJSONBytes(t);
     }
 
     @Override
@@ -33,6 +31,6 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (bytes == null || bytes.length == 0) {
             return null;
         }
-        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
+        return JSON.parseObject(bytes, clazz);
     }
 }

+ 2 - 8
fs-qw-task/src/main/java/com/fs/framework/config/ResourcesConfig.java

@@ -54,14 +54,8 @@ public class ResourcesConfig implements WebMvcConfigurer
     public CorsFilter corsFilter()
     {
         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
-        CorsConfiguration config = new CorsConfiguration();
-        config.setAllowCredentials(true);
-        // 设置访问源地址
-        config.addAllowedOrigin("*");
-        // 设置访问源请求头
-        config.addAllowedHeader("*");
-        // 设置访问源请求方法
-        config.addAllowedMethod("*");
+                // 显式域名白名单(Boot 2.2 无 originPattern);可用环境变量 CORS_ALLOWED_ORIGINS 覆盖
+        CorsConfiguration config = com.fs.common.config.CorsSupport.buildConfig();
         // 对接口配置跨域设置
         source.registerCorsConfiguration("/**", config);
         return new CorsFilter(source);

+ 2 - 4
fs-qw-voice/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,8 +1,6 @@
 package com.fs.framework.config;
 
 import com.alibaba.fastjson2.JSON;
-import com.alibaba.fastjson2.JSONReader;
-import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
 
@@ -25,7 +23,7 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
+        return JSON.toJSONBytes(t);
     }
 
     @Override
@@ -33,6 +31,6 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (bytes == null || bytes.length == 0) {
             return null;
         }
-        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
+        return JSON.parseObject(bytes, clazz);
     }
 }

+ 2 - 8
fs-qw-voice/src/main/java/com/fs/framework/config/ResourcesConfig.java

@@ -50,14 +50,8 @@ public class ResourcesConfig implements WebMvcConfigurer
     public CorsFilter corsFilter()
     {
         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
-        CorsConfiguration config = new CorsConfiguration();
-        config.setAllowCredentials(true);
-        // 设置访问源地址
-        config.addAllowedOrigin("*");
-        // 设置访问源请求头
-        config.addAllowedHeader("*");
-        // 设置访问源请求方法
-        config.addAllowedMethod("*");
+                // 显式域名白名单(Boot 2.2 无 originPattern);可用环境变量 CORS_ALLOWED_ORIGINS 覆盖
+        CorsConfiguration config = com.fs.common.config.CorsSupport.buildConfig();
         // 对接口配置跨域设置
         source.registerCorsConfiguration("/**", config);
         return new CorsFilter(source);

+ 26 - 2
fs-qwhook-msg/src/main/java/com/fs/app/utils/AudioUtils.java

@@ -172,8 +172,20 @@ public class AudioUtils {
              process = Runtime.getRuntime().exec("taskkill -f -t -im silk_v3_encoder.exe");
              */
             // 方法2,除了会弹出弹窗,没什么问题 cmd /c 极为重要,执行完毕后会自动关闭
-            process = Runtime.getRuntime().exec("cmd /c start " + path + "silk_v3_encoder.exe " + pcmPath + " " + target + " -tencent");
-            process .waitFor();
+            assertSafeLocalPath(pcmPath);
+            assertSafeLocalPath(target);
+            java.util.List<String> silkCmd = new java.util.ArrayList<>();
+            silkCmd.add(path + "silk_v3_encoder.exe");
+            silkCmd.add(pcmPath);
+            silkCmd.add(target);
+            silkCmd.add("-tencent");
+            ProcessBuilder silkBuilder = new ProcessBuilder(silkCmd);
+            silkBuilder.redirectErrorStream(true);
+            process = silkBuilder.start();
+            try (java.io.BufferedReader silkReader = new java.io.BufferedReader(new java.io.InputStreamReader(process.getInputStream()))) {
+                while (silkReader.readLine() != null) { /* drain */ }
+            }
+            process.waitFor();
             Thread.sleep(1000);
             // 有更好的方法会后续慢慢更新..
         } catch (Exception e) {
@@ -245,4 +257,16 @@ public class AudioUtils {
         return null;
     }
 
+
+    private static void assertSafeLocalPath(String p) {
+        if (p == null || p.isEmpty()) {
+            throw new IllegalArgumentException("路径为空");
+        }
+        if (p.indexOf(0) >= 0 || p.contains("..") || p.contains("|") || p.contains("&")
+                || p.contains(";") || p.contains("`") || p.contains("$(")
+                || p.indexOf('\n') >= 0 || p.indexOf('\r') >= 0
+                || p.contains("\"") || p.contains("'")) {
+            throw new IllegalArgumentException("非法路径参数");
+        }
+    }
 }

+ 2 - 4
fs-qwhook-msg/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,8 +1,6 @@
 package com.fs.framework.config;
 
 import com.alibaba.fastjson2.JSON;
-import com.alibaba.fastjson2.JSONReader;
-import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
 
@@ -25,7 +23,7 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
+        return JSON.toJSONBytes(t);
     }
 
     @Override
@@ -33,6 +31,6 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (bytes == null || bytes.length == 0) {
             return null;
         }
-        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
+        return JSON.parseObject(bytes, clazz);
     }
 }

+ 2 - 8
fs-qwhook-msg/src/main/java/com/fs/framework/config/ResourcesConfig.java

@@ -50,14 +50,8 @@ public class ResourcesConfig implements WebMvcConfigurer
     public CorsFilter corsFilter()
     {
         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
-        CorsConfiguration config = new CorsConfiguration();
-        config.setAllowCredentials(true);
-        // 设置访问源地址
-        config.addAllowedOrigin("*");
-        // 设置访问源请求头
-        config.addAllowedHeader("*");
-        // 设置访问源请求方法
-        config.addAllowedMethod("*");
+                // 显式域名白名单(Boot 2.2 无 originPattern);可用环境变量 CORS_ALLOWED_ORIGINS 覆盖
+        CorsConfiguration config = com.fs.common.config.CorsSupport.buildConfig();
         // 对接口配置跨域设置
         source.registerCorsConfiguration("/**", config);
         return new CorsFilter(source);

+ 26 - 2
fs-qwhook-sop/src/main/java/com/fs/app/utils/AudioUtils.java

@@ -173,8 +173,20 @@ public class AudioUtils {
              process = Runtime.getRuntime().exec("taskkill -f -t -im silk_v3_encoder.exe");
              */
             // 方法2,除了会弹出弹窗,没什么问题 cmd /c 极为重要,执行完毕后会自动关闭
-            process = Runtime.getRuntime().exec("cmd /c start " + path + "silk_v3_encoder.exe " + pcmPath + " " + target + " -tencent");
-            process .waitFor();
+            assertSafeLocalPath(pcmPath);
+            assertSafeLocalPath(target);
+            java.util.List<String> silkCmd = new java.util.ArrayList<>();
+            silkCmd.add(path + "silk_v3_encoder.exe");
+            silkCmd.add(pcmPath);
+            silkCmd.add(target);
+            silkCmd.add("-tencent");
+            ProcessBuilder silkBuilder = new ProcessBuilder(silkCmd);
+            silkBuilder.redirectErrorStream(true);
+            process = silkBuilder.start();
+            try (java.io.BufferedReader silkReader = new java.io.BufferedReader(new java.io.InputStreamReader(process.getInputStream()))) {
+                while (silkReader.readLine() != null) { /* drain */ }
+            }
+            process.waitFor();
             Thread.sleep(1000);
             // 有更好的方法会后续慢慢更新..
         } catch (Exception e) {
@@ -246,4 +258,16 @@ public class AudioUtils {
         return null;
     }
 
+
+    private static void assertSafeLocalPath(String p) {
+        if (p == null || p.isEmpty()) {
+            throw new IllegalArgumentException("路径为空");
+        }
+        if (p.indexOf(0) >= 0 || p.contains("..") || p.contains("|") || p.contains("&")
+                || p.contains(";") || p.contains("`") || p.contains("$(")
+                || p.indexOf('\n') >= 0 || p.indexOf('\r') >= 0
+                || p.contains("\"") || p.contains("'")) {
+            throw new IllegalArgumentException("非法路径参数");
+        }
+    }
 }

+ 2 - 4
fs-qwhook-sop/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,8 +1,6 @@
 package com.fs.framework.config;
 
 import com.alibaba.fastjson2.JSON;
-import com.alibaba.fastjson2.JSONReader;
-import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
 
@@ -25,7 +23,7 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
+        return JSON.toJSONBytes(t);
     }
 
     @Override
@@ -33,6 +31,6 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (bytes == null || bytes.length == 0) {
             return null;
         }
-        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
+        return JSON.parseObject(bytes, clazz);
     }
 }

+ 2 - 8
fs-qwhook-sop/src/main/java/com/fs/framework/config/ResourcesConfig.java

@@ -50,14 +50,8 @@ public class ResourcesConfig implements WebMvcConfigurer
     public CorsFilter corsFilter()
     {
         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
-        CorsConfiguration config = new CorsConfiguration();
-        config.setAllowCredentials(true);
-        // 设置访问源地址
-        config.addAllowedOrigin("*");
-        // 设置访问源请求头
-        config.addAllowedHeader("*");
-        // 设置访问源请求方法
-        config.addAllowedMethod("*");
+                // 显式域名白名单(Boot 2.2 无 originPattern);可用环境变量 CORS_ALLOWED_ORIGINS 覆盖
+        CorsConfiguration config = com.fs.common.config.CorsSupport.buildConfig();
         // 对接口配置跨域设置
         source.registerCorsConfiguration("/**", config);
         return new CorsFilter(source);

+ 26 - 2
fs-qwhook/src/main/java/com/fs/app/utils/AudioUtils.java

@@ -172,8 +172,20 @@ public class AudioUtils {
              process = Runtime.getRuntime().exec("taskkill -f -t -im silk_v3_encoder.exe");
              */
             // 方法2,除了会弹出弹窗,没什么问题 cmd /c 极为重要,执行完毕后会自动关闭
-            process = Runtime.getRuntime().exec("cmd /c start " + path + "silk_v3_encoder.exe " + pcmPath + " " + target + " -tencent");
-            process .waitFor();
+            assertSafeLocalPath(pcmPath);
+            assertSafeLocalPath(target);
+            java.util.List<String> silkCmd = new java.util.ArrayList<>();
+            silkCmd.add(path + "silk_v3_encoder.exe");
+            silkCmd.add(pcmPath);
+            silkCmd.add(target);
+            silkCmd.add("-tencent");
+            ProcessBuilder silkBuilder = new ProcessBuilder(silkCmd);
+            silkBuilder.redirectErrorStream(true);
+            process = silkBuilder.start();
+            try (java.io.BufferedReader silkReader = new java.io.BufferedReader(new java.io.InputStreamReader(process.getInputStream()))) {
+                while (silkReader.readLine() != null) { /* drain */ }
+            }
+            process.waitFor();
             Thread.sleep(1000);
             // 有更好的方法会后续慢慢更新..
         } catch (Exception e) {
@@ -245,4 +257,16 @@ public class AudioUtils {
         return null;
     }
 
+
+    private static void assertSafeLocalPath(String p) {
+        if (p == null || p.isEmpty()) {
+            throw new IllegalArgumentException("路径为空");
+        }
+        if (p.indexOf(0) >= 0 || p.contains("..") || p.contains("|") || p.contains("&")
+                || p.contains(";") || p.contains("`") || p.contains("$(")
+                || p.indexOf('\n') >= 0 || p.indexOf('\r') >= 0
+                || p.contains("\"") || p.contains("'")) {
+            throw new IllegalArgumentException("非法路径参数");
+        }
+    }
 }

+ 2 - 4
fs-qwhook/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,8 +1,6 @@
 package com.fs.framework.config;
 
 import com.alibaba.fastjson2.JSON;
-import com.alibaba.fastjson2.JSONReader;
-import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
 
@@ -25,7 +23,7 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
+        return JSON.toJSONBytes(t);
     }
 
     @Override
@@ -33,6 +31,6 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (bytes == null || bytes.length == 0) {
             return null;
         }
-        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
+        return JSON.parseObject(bytes, clazz);
     }
 }

+ 2 - 8
fs-qwhook/src/main/java/com/fs/framework/config/ResourcesConfig.java

@@ -50,14 +50,8 @@ public class ResourcesConfig implements WebMvcConfigurer
     public CorsFilter corsFilter()
     {
         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
-        CorsConfiguration config = new CorsConfiguration();
-        config.setAllowCredentials(true);
-        // 设置访问源地址
-        config.addAllowedOrigin("*");
-        // 设置访问源请求头
-        config.addAllowedHeader("*");
-        // 设置访问源请求方法
-        config.addAllowedMethod("*");
+                // 显式域名白名单(Boot 2.2 无 originPattern);可用环境变量 CORS_ALLOWED_ORIGINS 覆盖
+        CorsConfiguration config = com.fs.common.config.CorsSupport.buildConfig();
         // 对接口配置跨域设置
         source.registerCorsConfiguration("/**", config);
         return new CorsFilter(source);

+ 2 - 4
fs-redis/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,8 +1,6 @@
 package com.fs.framework.config;
 
 import com.alibaba.fastjson2.JSON;
-import com.alibaba.fastjson2.JSONReader;
-import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
 
@@ -25,7 +23,7 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
+        return JSON.toJSONBytes(t);
     }
 
     @Override
@@ -33,6 +31,6 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (bytes == null || bytes.length == 0) {
             return null;
         }
-        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
+        return JSON.parseObject(bytes, clazz);
     }
 }

+ 2 - 8
fs-redis/src/main/java/com/fs/framework/config/ResourcesConfig.java

@@ -50,14 +50,8 @@ public class ResourcesConfig implements WebMvcConfigurer
     public CorsFilter corsFilter()
     {
         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
-        CorsConfiguration config = new CorsConfiguration();
-        config.setAllowCredentials(true);
-        // 设置访问源地址
-        config.addAllowedOrigin("*");
-        // 设置访问源请求头
-        config.addAllowedHeader("*");
-        // 设置访问源请求方法
-        config.addAllowedMethod("*");
+                // 显式域名白名单(Boot 2.2 无 originPattern);可用环境变量 CORS_ALLOWED_ORIGINS 覆盖
+        CorsConfiguration config = com.fs.common.config.CorsSupport.buildConfig();
         // 对接口配置跨域设置
         source.registerCorsConfiguration("/**", config);
         return new CorsFilter(source);

+ 2 - 4
fs-repeat-api/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,8 +1,6 @@
 package com.fs.framework.config;
 
 import com.alibaba.fastjson2.JSON;
-import com.alibaba.fastjson2.JSONReader;
-import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
 
@@ -25,7 +23,7 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
+        return JSON.toJSONBytes(t);
     }
 
     @Override
@@ -33,6 +31,6 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         if (bytes == null || bytes.length == 0) {
             return null;
         }
-        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
+        return JSON.parseObject(bytes, clazz);
     }
 }

+ 2 - 8
fs-repeat-api/src/main/java/com/fs/framework/config/ResourcesConfig.java

@@ -50,14 +50,8 @@ public class ResourcesConfig implements WebMvcConfigurer
     public CorsFilter corsFilter()
     {
         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
-        CorsConfiguration config = new CorsConfiguration();
-        config.setAllowCredentials(true);
-        // 设置访问源地址
-        config.addAllowedOrigin("*");
-        // 设置访问源请求头
-        config.addAllowedHeader("*");
-        // 设置访问源请求方法
-        config.addAllowedMethod("*");
+                // 显式域名白名单(Boot 2.2 无 originPattern);可用环境变量 CORS_ALLOWED_ORIGINS 覆盖
+        CorsConfiguration config = com.fs.common.config.CorsSupport.buildConfig();
         // 对接口配置跨域设置
         source.registerCorsConfiguration("/**", config);
         return new CorsFilter(source);

+ 10 - 1
fs-service/src/main/java/com/fs/common/QRutils.java

@@ -6,6 +6,7 @@ import cn.hutool.extra.qrcode.QrCodeUtil;
 import cn.hutool.extra.qrcode.QrConfig;
 import com.fs.system.oss.CloudStorageService;
 import com.fs.system.oss.OSSFactory;
+import com.fs.common.utils.http.SafeHttpUrl;
 
 import javax.imageio.ImageIO;
 import java.awt.*;
@@ -19,8 +20,10 @@ import java.net.URL;
 
 public class QRutils {
     public static InputStream downloadAndEncodeImageToInputStream(String imageUrl) throws Exception {
-        URL url = new URL(imageUrl);
+        SafeHttpUrl.validateFetchUrl(imageUrl);
+        URL url = SafeHttpUrl.toValidatedUrl(imageUrl);
         HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
+        httpURLConnection.setInstanceFollowRedirects(false);
         httpURLConnection.setRequestMethod("GET");
         httpURLConnection.connect();
 
@@ -29,7 +32,13 @@ public class QRutils {
                  ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
                 byte[] buffer = new byte[4096];
                 int bytesRead;
+                long total = 0;
+                long max = SafeHttpUrl.maxBytes();
                 while ((bytesRead = inputStream.read(buffer)) != -1) {
+                    total += bytesRead;
+                    if (total > max) {
+                        throw new Exception("下载图片超过大小限制");
+                    }
                     outputStream.write(buffer, 0, bytesRead);
                 }
                 return new ByteArrayInputStream(outputStream.toByteArray());

+ 1 - 1
fs-service/src/main/java/com/fs/company/mapper/CompanySmsLogsMapper.java

@@ -123,7 +123,7 @@ public interface CompanySmsLogsMapper
             "and l.status = #{maps.status}" +
             "</if>" +
             "<if test = 'maps.userNickName != null and maps.userNickName != \"\" '> " +
-            "and u.nick_name like '%${maps.userNickName}%' " +
+            "and u.nick_name like concat('%', #{maps.userNickName}, '%') " +
             "</if>" +
             "<if test = 'maps.customerNo != null  and maps.customerNo!=\"\"  '> " +
             "and cc.customer_no = #{maps.customerNo}" +

+ 4 - 4
fs-service/src/main/java/com/fs/company/mapper/CompanyTcmReportMapper.java

@@ -83,7 +83,7 @@ public interface CompanyTcmReportMapper
             "</if>" +
 
             "<if test = 'maps.userNickName != null and maps.userNickName != \"\" '> " +
-            "and u.nick_name like '%${maps.userNickName}%'" +
+            "and u.nick_name like concat('%', #{maps.userNickName}, '%')" +
             "</if>" +
             "<if test = 'maps.companyUserId != null '> " +
             " and r.company_user_id = #{maps.companyUserId}" +
@@ -121,7 +121,7 @@ public interface CompanyTcmReportMapper
             "</if>" +
 
             "<if test = 'maps.userNickName != null and maps.userNickName != \"\" '> " +
-            "and u.nick_name like '%${maps.userNickName}%'" +
+            "and u.nick_name like concat('%', #{maps.userNickName}, '%')" +
             "</if>" +
             "<if test = 'maps.companyUserId != null '> " +
             " and r.company_user_id = #{maps.companyUserId}" +
@@ -169,7 +169,7 @@ public interface CompanyTcmReportMapper
             "</if>" +
 
             "<if test = 'maps.userNickName != null and maps.userNickName != \"\" '> " +
-            "and comu.nick_name like '%${maps.userNickName}%'" +
+            "and comu.nick_name like concat('%', #{maps.userNickName}, '%')" +
             "</if>" +
 
             "<if test = 'maps.scheduleId != null   and maps.scheduleId != \"\"     '> " +
@@ -246,7 +246,7 @@ public interface CompanyTcmReportMapper
             "<foreach collection=\"maps.companyId.split(',')\"  item='item' index='index'  open='(' separator=',' close=')'> #{item} </foreach>"+
             "</if>" +
             "<if test = 'maps.userNickName != null and maps.userNickName != \"\" '> " +
-            "and cu.nick_name like '%${maps.userNickName}%'" +
+            "and cu.nick_name like concat('%', #{maps.userNickName}, '%')" +
             "</if>" +
             "<if test = 'maps.companyUserId != null '> " +
             " and r.company_user_id = #{maps.companyUserId}" +

+ 3 - 3
fs-service/src/main/java/com/fs/company/mapper/CompanyVoiceCallerMapper.java

@@ -74,13 +74,13 @@ public interface CompanyVoiceCallerMapper
             "and vc.company_user_id = #{maps.companyUserId}" +
             "</if>" +
             "<if test = 'maps.callerNo != null and maps.callerNo != \"\" '> " +
-            "and vc.caller_no like '%${maps.callerNo}%' " +
+            "and vc.caller_no like concat('%', #{maps.callerNo}, '%') " +
             "</if>" +
             "<if test = 'maps.mobile != null and maps.mobile != \"\" '> " +
-            "and vc.mobile like '%${maps.mobile}%' " +
+            "and vc.mobile like concat('%', #{maps.mobile}, '%') " +
             "</if>" +
             "<if test = 'maps.companyUserNickName != null and maps.companyUserNickName != \"\" '> " +
-            "and u.nick_name like '%${maps.companyUserNickName}%' " +
+            "and u.nick_name like concat('%', #{maps.companyUserNickName}, '%') " +
             "</if>" +
             "<if test = 'maps.status != null '> " +
             "and vc.status = #{maps.status}" +

+ 6 - 6
fs-service/src/main/java/com/fs/company/mapper/CompanyVoiceLogsMapper.java

@@ -83,13 +83,13 @@ public interface CompanyVoiceLogsMapper
             "and l.customer_id = #{maps.customerId}" +
             "</if>" +
             "<if test = 'maps.userNickName != null and maps.userNickName != \"\" '> " +
-            "and u.nick_name like '%${maps.userNickName}%' " +
+            "and u.nick_name like concat('%', #{maps.userNickName}, '%') " +
             "</if>" +
             "<if test = 'maps.callerPhone != null and maps.callerPhone != \"\" '> " +
-            "and l.caller_phone like '%${maps.callerPhone}%' " +
+            "and l.caller_phone like concat('%', #{maps.callerPhone}, '%') " +
             "</if>" +
             "<if test = 'maps.calleePhone != null and maps.calleePhone != \"\" '> " +
-            "and l.callee_phone like '%${maps.calleePhone}%' " +
+            "and l.callee_phone like concat('%', #{maps.calleePhone}, '%') " +
             "</if>" +
             "<if test = 'maps.status != null '> " +
             "and l.status = #{maps.status}" +
@@ -128,13 +128,13 @@ public interface CompanyVoiceLogsMapper
             "and l.customer_id = #{maps.customerId}" +
             "</if>" +
             "<if test = 'maps.userNickName != null and maps.userNickName != \"\" '> " +
-            "and u.nick_name like '%${maps.userNickName}%' " +
+            "and u.nick_name like concat('%', #{maps.userNickName}, '%') " +
             "</if>" +
             "<if test = 'maps.callerPhone != null and maps.callerPhone != \"\" '> " +
-            "and l.caller_phone like '%${maps.callerPhone}%' " +
+            "and l.caller_phone like concat('%', #{maps.callerPhone}, '%') " +
             "</if>" +
             "<if test = 'maps.calleePhone != null and maps.calleePhone != \"\" '> " +
-            "and l.callee_phone like '%${maps.calleePhone}%' " +
+            "and l.callee_phone like concat('%', #{maps.calleePhone}, '%') " +
             "</if>" +
             "<if test = 'maps.status != null '> " +
             "and l.status = #{maps.status}" +

+ 1 - 1
fs-service/src/main/java/com/fs/company/mapper/CompanyVoiceMobileMapper.java

@@ -66,7 +66,7 @@ public interface CompanyVoiceMobileMapper
             "from company_voice_mobile m left join company_voice_api a  on a.api_id=m.api_id left join company c  on c.company_id=m.company_id  " +
             "where 1=1 " +
             "<if test = 'maps.mobile != null and maps.mobile != \"\" '> " +
-            "and m.mobile like '%${maps.mobile}%'" +
+            "and m.mobile like concat('%', #{maps.mobile}, '%')" +
             "</if>" +
             "<if test = 'maps.apiId != null  '> " +
             "and m.api_id = #{maps.apiId}" +

+ 1 - 1
fs-service/src/main/java/com/fs/core/config/WxMaConfiguration.java

@@ -103,7 +103,7 @@ public class WxMaConfiguration {
 
         wx.setConfigs(c);
         this.properties = wx;
-        log.info("配置加载完毕! 配置文件: {}",JSON.toJSONString(this.properties));
+        log.info("小程序配置加载完毕, app 数量: {}", c == null ? 0 : c.size());
     }
 
     public static WxMaService getMaService(String appid) {

+ 1 - 1
fs-service/src/main/java/com/fs/course/mapper/FsCourseRedPacketLogMapper.java

@@ -136,7 +136,7 @@ public interface FsCourseRedPacketLogMapper
             "<if test=\"maps.userIds != null and maps.userIds.size() > 0\">\n" +
             "                AND l.company_user_id IN\n" +
             "                <foreach collection=\"maps.userIds\" open=\"(\" close=\")\" separator=\",\" item=\"item\">\n" +
-            "                    ${item}\n" +
+            "                    #{item}\n" +
             "                </foreach>\n" +
             "            </if>" +
             " order by l.log_id desc  "+

+ 1 - 23
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java

@@ -972,29 +972,7 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
      * @throws Exception URL解析或IO异常
      */
     private static File urlToFile(String fileUrl) throws Exception {
-        URL url = new URL(fileUrl);
-        // 生成临时文件名(原文件名+时间戳)
-        String fileName = extractFileName(url) + System.currentTimeMillis();
-        String extension = getFileExtension(new File(fileUrl).getName());
-
-        // 创建临时文件
-        File tempFile = StringUtil.strIsNullOrEmpty(extension) ?
-                File.createTempFile(fileName, null) :
-                File.createTempFile(fileName, "." + extension);
-
-        // 下载文件内容
-        try (BufferedInputStream in = new BufferedInputStream(url.openStream());
-             FileOutputStream out = new FileOutputStream(tempFile)) {
-            byte[] buffer = new byte[8192];
-            int len;
-            while ((len = in.read(buffer)) != -1) {
-                out.write(buffer, 0, len);
-            }
-            return tempFile;
-        } catch (IOException e) {
-            tempFile.delete();
-            throw new IOException("下载文件失败: " + fileUrl, e);
-        }
+        return com.fs.common.utils.http.SafeUrlFile.downloadToTemp(fileUrl);
     }
 
     /**

+ 1 - 3
fs-service/src/main/java/com/fs/course/service/impl/TencentCloudCosService.java

@@ -48,9 +48,7 @@ public class TencentCloudCosService implements ITencentCloudCosService {
 
         try {
             Response response = CosStsClient.getCredential(config);
-            System.out.println(response.credentials.tmpSecretId);
-            System.out.println(response.credentials.tmpSecretKey);
-            System.out.println(response.credentials.sessionToken);
+            // 禁止将临时密钥打印到日志
             return R.ok().put("data",response);
         } catch (Exception e) {
             e.printStackTrace();

+ 1 - 34
fs-service/src/main/java/com/fs/fastGpt/service/impl/FastGptCollectionServiceImpl.java

@@ -348,40 +348,7 @@ public class FastGptCollectionServiceImpl implements IFastGptCollectionService
      *  url转临时文件filr
      */
     public static File urlToFile(String fileUrl) throws Exception {
-
-        // 从URL中提取文件名,文件后缀
-        String fileExtension = getFileExtension(new File(fileUrl).getName());
-        String fileName = extractFileName(new URL(fileUrl))+new Date().getTime();
-        File tempFile=null;
-        if (!StringUtil.strIsNullOrEmpty(fileExtension)){
-            // 创建一个临时文件
-            tempFile = File.createTempFile(fileName, "."+fileExtension);
-        }else {
-            tempFile = File.createTempFile(fileName, null);
-        }
-
-
-        // 使用 try-with-resources 语句自动关闭资源
-        try (BufferedInputStream in = new BufferedInputStream(new URL(fileUrl).openStream());
-             FileOutputStream out = new FileOutputStream(tempFile)) {
-
-            byte[] buffer = new byte[1024];
-            int len;
-
-            // 读取数据并写入到临时文件
-            while ((len = in.read(buffer)) != -1) {
-                out.write(buffer, 0, len);
-            }
-            out.flush();
-        } catch (IOException e) {
-            // 处理异常,例如删除创建的临时文件
-            tempFile.delete();
-            throw e;
-        }finally {
-
-        }
-
-        return tempFile;
+        return com.fs.common.utils.http.SafeUrlFile.downloadToTemp(fileUrl);
     }
 
     /**

+ 61 - 28
fs-service/src/main/java/com/fs/fastgptApi/util/AudioUtils.java

@@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fs.common.exception.ServiceException;
 import com.fs.common.exception.base.BaseException;
+import com.fs.common.utils.http.SafeHttpUrl;
 import com.fs.config.ai.AiHostProper;
 import com.fs.fastGpt.domain.FastgptChatVoiceHomo;
 import com.fs.fastGpt.service.IFastGptChatMsgService;
@@ -637,8 +638,20 @@ public class AudioUtils {
              process = Runtime.getRuntime().exec("taskkill -f -t -im silk_v3_encoder.exe");
              */
             // 方法2,除了会弹出弹窗,没什么问题 cmd /c 极为重要,执行完毕后会自动关闭
-            process = Runtime.getRuntime().exec("cmd /c start  " + path + "silk_v3_encoder.exe " + pcmPath + " " + target + " -tencent");
-            process .waitFor();
+            assertSafeLocalPath(pcmPath);
+            assertSafeLocalPath(target);
+            java.util.List<String> silkCmd = new java.util.ArrayList<>();
+            silkCmd.add(path + "silk_v3_encoder.exe");
+            silkCmd.add(pcmPath);
+            silkCmd.add(target);
+            silkCmd.add("-tencent");
+            ProcessBuilder silkBuilder = new ProcessBuilder(silkCmd);
+            silkBuilder.redirectErrorStream(true);
+            process = silkBuilder.start();
+            try (java.io.BufferedReader silkReader = new java.io.BufferedReader(new java.io.InputStreamReader(process.getInputStream()))) {
+                while (silkReader.readLine() != null) { /* drain */ }
+            }
+            process.waitFor();
             Thread.sleep(1000);
             // 有更好的方法会后续慢慢更新..
         } catch (Exception e) {
@@ -658,12 +671,9 @@ public class AudioUtils {
     public static void transferPcmSilkSecond(String pcmPath, String target) {
         Process process = null;
         try {
-            // 使用 ProcessBuilder 替代 Runtime.exec 提高可靠性
+            assertSafeLocalPath(pcmPath);
+            assertSafeLocalPath(target);
             List<String> command = new ArrayList<>();
-            command.add("cmd");
-            command.add("/c");
-            command.add("start");
-            command.add("/wait");  // 等待程序执行完毕才退出
             command.add(path + "silk_v3_encoder.exe");
             command.add(pcmPath);
             command.add(target);
@@ -699,22 +709,26 @@ public class AudioUtils {
     public static void transferPcmSilkNew(String pcmPath, String target) {
         Process process = null;
         try {
+            assertSafeLocalPath(pcmPath);
+            assertSafeLocalPath(target);
             List<String> command = new ArrayList<>();
-            command.add("cmd");
-            command.add("/c");
-            command.add("start");
-            command.add("/B");
             command.add(path + "silk_v3_encoder.exe");
             command.add(pcmPath);
             command.add(target);
             command.add("-tencent");
-            ProcessBuilder builder = new ProcessBuilder();
-            builder.command(command);
-            Process p = builder.start();
-            p.waitFor();
-            p.destroy();
+            ProcessBuilder builder = new ProcessBuilder(command);
+            builder.redirectErrorStream(true);
+            process = builder.start();
+            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
+                while (reader.readLine() != null) { /* drain */ }
+            }
+            process.waitFor();
         } catch (Exception e) {
             e.printStackTrace();
+        } finally {
+            if (process != null) {
+                process.destroy();
+            }
         }
     }
     /**
@@ -727,45 +741,52 @@ public class AudioUtils {
         InputStream inputStream = null;
         FileOutputStream outputStream = null;
         try {
-            // 创建 HTTP 连接
-            URL url = new URL(fileUrl);
+            // SSRF 防护:协议/域名白名单 + 禁私网 IP
+            SafeHttpUrl.validateFetchUrl(fileUrl);
+            URL url = SafeHttpUrl.toValidatedUrl(fileUrl);
             HttpURLConnection connection = (HttpURLConnection) url.openConnection();
-            // 设置Referer请求头
-//            connection.setRequestProperty("Referer", "cos.his.cdwjyyh.com");
+            connection.setInstanceFollowRedirects(false);
+            connection.setConnectTimeout(10000);
+            connection.setReadTimeout(30000);
             connection.setRequestMethod("GET");
             connection.connect();
 
-            // 检查是否成功连接
-            if (connection.getResponseCode() != 200) {
-                throw new ServiceException("无法下载音频文件,HTTP 响应码:" + connection.getResponseCode());
+            int code = connection.getResponseCode();
+            if (code != 200) {
+                throw new ServiceException("无法下载音频文件,HTTP 响应码:" + code);
             }
 
-            // 获取输入流
             inputStream = connection.getInputStream();
 
-            // 创建临时文件,并指定存放地址
             String tempFileName = "temp_" + UUID.randomUUID() + "_" + getFileExtension(fileUrl);
             File destinationDirectory = new File(destinationDir);
 
-            // 参照 transferAudioSilk 方法,同步确保目录创建的线程安全
             synchronized (AudioUtils.class) {
                 if (!destinationDirectory.exists()) {
                     destinationDirectory.mkdirs();
                 }
             }
 
-            // 将文件保存到指定路径
             File tempFile = new File(destinationDirectory, tempFileName);
 
-            // 写入文件
             outputStream = new FileOutputStream(tempFile);
             byte[] buffer = new byte[8192];
             int bytesRead;
+            long total = 0;
+            long max = SafeHttpUrl.maxBytes();
             while ((bytesRead = inputStream.read(buffer)) != -1) {
+                total += bytesRead;
+                if (total > max) {
+                    throw new ServiceException("下载文件超过大小限制");
+                }
                 outputStream.write(buffer, 0, bytesRead);
             }
 
             return tempFile;
+        } catch (ServiceException e) {
+            throw e;
+        } catch (IllegalArgumentException e) {
+            throw new ServiceException(e.getMessage());
         } catch (Exception e) {
             e.printStackTrace();
         } finally {
@@ -963,4 +984,16 @@ public class AudioUtils {
         return byteOutput.toByteArray();
     }
 
+
+    private static void assertSafeLocalPath(String p) {
+        if (p == null || p.isEmpty()) {
+            throw new IllegalArgumentException("路径为空");
+        }
+        if (p.indexOf(0) >= 0 || p.contains("..") || p.contains("|") || p.contains("&")
+                || p.contains(";") || p.contains("`") || p.contains("$(")
+                || p.indexOf('\n') >= 0 || p.indexOf('\r') >= 0
+                || p.contains("\"") || p.contains("'")) {
+            throw new IllegalArgumentException("非法路径参数");
+        }
+    }
 }

+ 13 - 6
fs-service/src/main/java/com/fs/his/utils/PhoneUtil.java

@@ -8,13 +8,20 @@ import java.util.Base64;
 
 public class PhoneUtil {
 
-    /** 可通过环境变量 PHONE_AES_KEY 覆盖;默认值保留以兼容历史密文 */
-    private static final String AES_KEY = firstNonBlank(System.getenv("PHONE_AES_KEY"), "AESAabCdeREssREA");
-    /** 可通过环境变量 PHONE_AES_OLD_KEY 覆盖 */
-    private static final String OLD_KEY = firstNonBlank(System.getenv("PHONE_AES_OLD_KEY"), "2c8d1a7f4e9b3c6ae6d5c4b3a291f8c9");
+    /**
+     * 手机号加解密密钥,必须通过环境变量配置,禁止在代码中硬编码。
+     * PHONE_AES_KEY:当前密钥;PHONE_AES_OLD_KEY:历史密文兼容密钥。
+     */
+    private static final String AES_KEY = requireEnv("PHONE_AES_KEY");
+    private static final String OLD_KEY = requireEnv("PHONE_AES_OLD_KEY");
 
-    private static String firstNonBlank(String env, String fallback) {
-        return env != null && !env.trim().isEmpty() ? env.trim() : fallback;
+    private static String requireEnv(String name) {
+        String env = System.getenv(name);
+        if (env == null || env.trim().isEmpty()) {
+            throw new IllegalStateException(
+                    "缺少环境变量 " + name + ",请在启动环境中配置手机号 AES 密钥(勿写入代码仓库)");
+        }
+        return env.trim();
     }
 
     public static String encryptPhone(String text) {

+ 4 - 4
fs-service/src/main/java/com/fs/huifuPay/sdk/opps/core/net/AbstractRequest.java

@@ -78,9 +78,9 @@ public abstract class AbstractRequest {
         String reqData = JSONObject.toJSONString(params);
 
         String privateKey = config.getRsaPrivateKey();
-        if (BasePay.debug) {
-            System.out.println("PRIVATE_KEY=" + privateKey);
-        }
+//        if (BasePay.debug) {
+//            System.out.println("PRIVATE_KEY=" + privateKey);
+//        }
         String requestSign;
         try {
             String sortedData = JsonUtils.sort4JsonString(reqData, 0);
@@ -143,7 +143,7 @@ public abstract class AbstractRequest {
             publicKey = BasePay.HUIFU_DEFAULT_PUBLIC_KEY;
         }
         if (BasePay.debug) {
-            System.out.println("PUBLIC_KEY=" + publicKey);
+//            System.out.println("PUBLIC_KEY=" + publicKey);
             System.out.println("response data=" + data);
             System.out.println("response sign:" + sign);
         }

+ 5 - 1
fs-service/src/main/java/com/fs/im/service/impl/OpenIMServiceImpl.java

@@ -521,7 +521,7 @@ public class OpenIMServiceImpl implements OpenIMService {
         ObjectMapper objectMapper = new ObjectMapper();
         //userId = 61l;
         objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 忽略null字段
-//        checkAndImportFriendByDianBo(companyUserId,userId.toString(),cropId,true);
+        checkAndImportFriendByDianBo(companyUserId,userId.toString(),cropId,true);
         OpenImMsgDTO.Content content = new OpenImMsgDTO.Content();
         OpenImMsgDTO.ImData imData = new OpenImMsgDTO.ImData();
         PayloadDTO payload = new PayloadDTO();
@@ -931,6 +931,10 @@ public class OpenIMServiceImpl implements OpenIMService {
                     .body(requestBody.toString())
                     .execute()
                     .body();
+            if (body == null || body.trim().isEmpty() || body.trim().charAt(0) != '{') {
+                log.error("OpenIM account_check 非JSON响应: {}", body == null ? "null" : body.substring(0, Math.min(200, body.length())));
+                return R.error("IM服务响应异常");
+            }
             JSONObject jsonObject = new JSONObject(body);
             JSONArray results = jsonObject.getJSONObject("data").getJSONArray("results");
             if (results != null && results.length() > 0) {

+ 38 - 9
fs-service/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java

@@ -824,8 +824,8 @@ public class LiveServiceImpl implements ILiveService
             // 生成唯一的流密钥
             String streamKey = "stream_" + live.getLiveId() + "_" + System.currentTimeMillis();
 
-            // 构建FFmpeg推流命令
-            String ffmpegCmd = buildFFmpegCommand(curLiveVideo.getVideoUrl(), streamKey);
+            // 构建FFmpeg推流命令(参数列表,防命令注入)
+            java.util.List<String> ffmpegCmd = buildFFmpegCommand(curLiveVideo.getVideoUrl(), streamKey);
 
             // 启动推流进程
             Process process = processManager.startProcess(ffmpegCmd);
@@ -1433,14 +1433,43 @@ public class LiveServiceImpl implements ILiveService
 
 
     /**
-     * 构建FFmpeg推流命令
+     * 构建FFmpeg推流命令(参数列表,禁止字符串拼 shell)
      */
-    private String buildFFmpegCommand(String videoPath, String streamKey) {
-        return String.format(
-                "ffmpeg -re -stream_loop -1 -i \"%s\" -c:v libx264 -preset ultrafast -b:v 1000k " +
-                        "-c:a aac -b:a 128k -f flv rtmp://your-srs-server/live/%s",
-                videoPath, streamKey
-        );
+    private java.util.List<String> buildFFmpegCommand(String videoPath, String streamKey) {
+        if (videoPath == null || videoPath.trim().isEmpty()) {
+            throw new IllegalArgumentException("视频地址不能为空");
+        }
+        if (streamKey == null || !streamKey.matches("^[A-Za-z0-9_\\-]+$")) {
+            throw new IllegalArgumentException("非法 streamKey");
+        }
+        // 远程 URL 走白名单;本地路径禁止 ..
+        String path = videoPath.trim();
+        if (path.startsWith("http://") || path.startsWith("https://")) {
+            com.fs.common.utils.http.SafeHttpUrl.validateFetchUrl(path);
+        } else if (path.contains("..") || path.contains("`") || path.contains("$(") || path.contains("|") || path.contains(";")) {
+            throw new IllegalArgumentException("非法视频路径");
+        }
+        java.util.List<String> cmd = new java.util.ArrayList<>();
+        cmd.add("ffmpeg");
+        cmd.add("-re");
+        cmd.add("-stream_loop");
+        cmd.add("-1");
+        cmd.add("-i");
+        cmd.add(path);
+        cmd.add("-c:v");
+        cmd.add("libx264");
+        cmd.add("-preset");
+        cmd.add("ultrafast");
+        cmd.add("-b:v");
+        cmd.add("1000k");
+        cmd.add("-c:a");
+        cmd.add("aac");
+        cmd.add("-b:a");
+        cmd.add("128k");
+        cmd.add("-f");
+        cmd.add("flv");
+        cmd.add("rtmp://your-srs-server/live/" + streamKey);
+        return cmd;
     }
 
     /**

+ 19 - 16
fs-service/src/main/java/com/fs/live/utils/ProcessManager.java

@@ -4,12 +4,14 @@ import org.springframework.stereotype.Component;
 
 import java.io.IOException;
 import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
 
 /**
- * &#064;Description:  专门使用 ff mpg 推流srs直播
+ * &#064;Description:  专门使用 ffmpeg 推流 srs 直播
  * &#064;Author:  yhq
  * &#064;Date:  20250723
  */
@@ -18,16 +20,28 @@ public class ProcessManager {
     private final Map<String, Process> processMap = new ConcurrentHashMap<>();
 
     /**
-     * 启动进程
+     * 启动进程(参数列表,禁止 shell 拼接)
      */
-    public Process startProcess(String command) throws IOException {
-        ProcessBuilder processBuilder = new ProcessBuilder(command.split(" "));
+    public Process startProcess(List<String> command) throws IOException {
+        if (command == null || command.isEmpty()) {
+            throw new IllegalArgumentException("命令不能为空");
+        }
+        String bin = command.get(0);
+        String lower = bin == null ? "" : bin.toLowerCase();
+        if (!(lower.equals("ffmpeg") || lower.endsWith("ffmpeg") || lower.endsWith("ffmpeg.exe"))) {
+            throw new IllegalArgumentException("仅允许启动 ffmpeg 进程");
+        }
+        for (String arg : command) {
+            if (arg != null && (arg.contains("\n") || arg.contains("\r") || arg.contains("`") || arg.contains("$("))) {
+                throw new IllegalArgumentException("命令参数包含非法字符");
+            }
+        }
+        ProcessBuilder processBuilder = new ProcessBuilder(new ArrayList<>(command));
         Process process = processBuilder.start();
 
         String processId = getProcessId(process);
         processMap.put(processId, process);
 
-        // 监听进程结束
         CompletableFuture.runAsync(() -> {
             try {
                 process.waitFor();
@@ -40,9 +54,6 @@ public class ProcessManager {
         return process;
     }
 
-    /**
-     * 获取进程ID
-     */
     private String getProcessId(Process process) {
         try {
             if (process.getClass().getName().equals("java.lang.UNIXProcess")) {
@@ -50,18 +61,13 @@ public class ProcessManager {
                 pidField.setAccessible(true);
                 return String.valueOf(pidField.get(process));
             } else {
-                // 对于Windows系统或其他Process实现
                 return String.valueOf(process.hashCode());
             }
         } catch (Exception e) {
-            // 如果获取失败,使用进程的hashCode作为备选方案
             return String.valueOf(process.hashCode());
         }
     }
 
-    /**
-     * 停止进程
-     */
     public boolean stopProcess(String processId) {
         Process process = processMap.get(processId);
         if (process != null && process.isAlive()) {
@@ -72,9 +78,6 @@ public class ProcessManager {
         return false;
     }
 
-    /**
-     * 检查进程是否存活
-     */
     public boolean isProcessAlive(String processId) {
         Process process = processMap.get(processId);
         return process != null && process.isAlive();

+ 2 - 2
fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java

@@ -418,7 +418,7 @@ public interface QwUserMapper extends BaseMapper<QwUser>
     @Select("select qw_user_name from qw_user where qw_user_id=#{qwUserId} and corp_id=#{corpId}")
     String selectQwUserName(@Param("qwUserId") String qwUserId,@Param("corpId") String corpId);
 
-    @Select("select qw_user_id from qw_user where company_user_id = ${companyUserId}")
+    @Select("select qw_user_id from qw_user where company_user_id = #{companyUserId}")
     List<String> findQwUserIdListByCompanyUserId(@Param("companyUserId") Long companyUserId);
 
     @Select("select qw_user_id from qw_user where company_user_id = #{userId} and corp_id = #{corpId}")
@@ -504,7 +504,7 @@ public interface QwUserMapper extends BaseMapper<QwUser>
             "            <if test=\"userIds != null and userIds.size() > 0\">\n" +
             "                AND company_user_id IN\n" +
             "                <foreach collection=\"userIds\" open=\"(\" close=\")\" separator=\",\" item=\"item\">\n" +
-            "                    ${item}\n" +
+            "                    #{item}\n" +
             "                </foreach>\n" +
             "            </if>" +
             "</script>")

+ 1 - 32
fs-service/src/main/java/com/fs/qw/service/impl/QwFriendWelcomeServiceImpl.java

@@ -426,38 +426,7 @@ public class QwFriendWelcomeServiceImpl implements IQwFriendWelcomeService {
      * url转临时文件filr
      */
     public static File urlToFile(String fileUrl) throws Exception {
-
-        // 从URL中提取文件名,文件后缀
-        String fileExtension = getFileExtension(new File(fileUrl).getName());
-        String fileName = extractFileName(new URL(fileUrl)) + new Date().getTime();
-        File tempFile = null;
-        if (!StringUtil.strIsNullOrEmpty(fileExtension)) {
-            // 创建一个临时文件
-            tempFile = File.createTempFile(fileName, "." + fileExtension);
-        } else {
-            tempFile = File.createTempFile(fileName, null);
-        }
-
-
-        // 使用 try-with-resources 语句自动关闭资源
-        try (BufferedInputStream in = new BufferedInputStream(new URL(fileUrl).openStream());
-             FileOutputStream out = new FileOutputStream(tempFile)) {
-
-            byte[] buffer = new byte[1024];
-            int len;
-
-            // 读取数据并写入到临时文件
-            while ((len = in.read(buffer)) != -1) {
-                out.write(buffer, 0, len);
-            }
-            out.flush();
-        } catch (IOException e) {
-            // 处理异常,例如删除创建的临时文件
-            tempFile.delete();
-            throw e;
-        }
-
-        return tempFile;
+        return com.fs.common.utils.http.SafeUrlFile.downloadToTemp(fileUrl);
     }
 
     /**

+ 1 - 34
fs-service/src/main/java/com/fs/qw/service/impl/QwGroupMsgServiceImpl.java

@@ -1094,40 +1094,7 @@ public class QwGroupMsgServiceImpl implements IQwGroupMsgService
      *  url转临时文件filr
      */
     public static File urlToFile(String fileUrl) throws Exception {
-
-        // 从URL中提取文件名,文件后缀
-        String fileExtension = getFileExtension(new File(fileUrl).getName());
-        String fileName = extractFileName(new URL(fileUrl))+new Date().getTime();
-        File tempFile=null;
-        if (!StringUtil.strIsNullOrEmpty(fileExtension)){
-            // 创建一个临时文件
-            tempFile = File.createTempFile(fileName, "."+fileExtension);
-        }else {
-            tempFile = File.createTempFile(fileName, null);
-        }
-
-
-        // 使用 try-with-resources 语句自动关闭资源
-        try (BufferedInputStream in = new BufferedInputStream(new URL(fileUrl).openStream());
-             FileOutputStream out = new FileOutputStream(tempFile)) {
-
-            byte[] buffer = new byte[1024];
-            int len;
-
-            // 读取数据并写入到临时文件
-            while ((len = in.read(buffer)) != -1) {
-                out.write(buffer, 0, len);
-            }
-            out.flush();
-        } catch (IOException e) {
-            // 处理异常,例如删除创建的临时文件
-            tempFile.delete();
-            throw e;
-        }finally {
-
-        }
-
-        return tempFile;
+        return com.fs.common.utils.http.SafeUrlFile.downloadToTemp(fileUrl);
     }
 
     /**

+ 1 - 57
fs-service/src/main/java/com/fs/qw/service/impl/QwMaterialServiceImpl.java

@@ -150,63 +150,7 @@ public class QwMaterialServiceImpl extends ServiceImpl<QwMaterialMapper, QwMater
      *  url转临时文件filr
      */
     public static File urlToFile(String fileUrl) throws Exception {
-
-        // 编码文件名部分
-        String encodedFileUrl = fileUrl.replace(
-                new File(fileUrl).getName(),
-                URLEncoder.encode(new File(fileUrl).getName(), "UTF-8")
-        );
-
-        // 生成安全的临时文件名
-        String fileExtension = getFileExtension(new File(fileUrl).getName());
-        String safeFileName = "temp_" + System.currentTimeMillis();
-        File tempFile = File.createTempFile(safeFileName, "." + fileExtension);
-
-        // 使用 HttpClient 下载
-        try (CloseableHttpClient client = HttpClients.createDefault()) {
-            HttpGet request = new HttpGet(encodedFileUrl);
-            request.setHeader("User-Agent", "Mozilla/5.0");
-            FileUtils.copyInputStreamToFile(
-                    client.execute(request).getEntity().getContent(),
-                    tempFile
-            );
-        } catch (IOException e) {
-            tempFile.delete();
-            throw new RuntimeException("下载文件失败: " + encodedFileUrl, e);
-        }
-
-
-//        // 从URL中提取文件名,文件后缀
-//        String fileExtension = getFileExtension(new File(fileUrl).getName());
-//        String fileName = extractFileName(new URL(fileUrl))+new Date().getTime();
-//        File tempFile=null;
-//        if (!StringUtil.strIsNullOrEmpty(fileExtension)){
-//            // 创建一个临时文件
-//            tempFile = File.createTempFile(fileName, "."+fileExtension);
-//        }else {
-//            tempFile = File.createTempFile(fileName, null);
-//        }
-//
-//
-//        // 使用 try-with-resources 语句自动关闭资源
-//        try (BufferedInputStream in = new BufferedInputStream(new URL(fileUrl).openStream());
-//             FileOutputStream out = new FileOutputStream(tempFile)) {
-//
-//            byte[] buffer = new byte[1024];
-//            int len;
-//
-//            // 读取数据并写入到临时文件
-//            while ((len = in.read(buffer)) != -1) {
-//                out.write(buffer, 0, len);
-//            }
-//            out.flush();
-//        } catch (IOException e) {
-//            // 处理异常,例如删除创建的临时文件
-//            tempFile.delete();
-//            throw e;
-//        }
-
-        return tempFile;
+        return com.fs.common.utils.http.SafeUrlFile.downloadToTemp(fileUrl);
     }
 
     /**

+ 1 - 32
fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java

@@ -1150,38 +1150,7 @@ public class QwUserServiceImpl implements IQwUserService
      *  url转临时文件filr
      */
     public static File urlToFile(String fileUrl) throws Exception {
-
-        // 从URL中提取文件名,文件后缀
-        String fileExtension = getFileExtension(new File(fileUrl).getName());
-        String fileName = extractFileName(new URL(fileUrl))+new Date().getTime();
-        File tempFile=null;
-        if (!StringUtil.strIsNullOrEmpty(fileExtension)){
-            // 创建一个临时文件
-            tempFile = File.createTempFile(fileName, "."+fileExtension);
-        }else {
-            tempFile = File.createTempFile(fileName, null);
-        }
-
-
-        // 使用 try-with-resources 语句自动关闭资源
-        try (BufferedInputStream in = new BufferedInputStream(new URL(fileUrl).openStream());
-             FileOutputStream out = new FileOutputStream(tempFile)) {
-
-            byte[] buffer = new byte[1024];
-            int len;
-
-            // 读取数据并写入到临时文件
-            while ((len = in.read(buffer)) != -1) {
-                out.write(buffer, 0, len);
-            }
-            out.flush();
-        } catch (IOException e) {
-            // 处理异常,例如删除创建的临时文件
-            tempFile.delete();
-            throw e;
-        }
-
-        return tempFile;
+        return com.fs.common.utils.http.SafeUrlFile.downloadToTemp(fileUrl);
     }
 
     @Autowired

+ 1 - 32
fs-service/src/main/java/com/fs/qw/service/impl/QwWelcomeServiceImpl.java

@@ -413,38 +413,7 @@ public class QwWelcomeServiceImpl implements IQwWelcomeService
      *  url转临时文件filr
      */
     public static File urlToFile(String fileUrl) throws Exception {
-
-        // 从URL中提取文件名,文件后缀
-        String fileExtension = getFileExtension(new File(fileUrl).getName());
-        String fileName = extractFileName(new URL(fileUrl))+new Date().getTime();
-        File tempFile=null;
-        if (!StringUtil.strIsNullOrEmpty(fileExtension)){
-            // 创建一个临时文件
-            tempFile = File.createTempFile(fileName, "."+fileExtension);
-        }else {
-            tempFile = File.createTempFile(fileName, null);
-        }
-
-
-        // 使用 try-with-resources 语句自动关闭资源
-        try (BufferedInputStream in = new BufferedInputStream(new URL(fileUrl).openStream());
-             FileOutputStream out = new FileOutputStream(tempFile)) {
-
-            byte[] buffer = new byte[1024];
-            int len;
-
-            // 读取数据并写入到临时文件
-            while ((len = in.read(buffer)) != -1) {
-                out.write(buffer, 0, len);
-            }
-            out.flush();
-        } catch (IOException e) {
-            // 处理异常,例如删除创建的临时文件
-            tempFile.delete();
-            throw e;
-        }
-
-        return tempFile;
+        return com.fs.common.utils.http.SafeUrlFile.downloadToTemp(fileUrl);
     }
 
     /**

+ 7 - 6
fs-service/src/main/resources/application-common.yml

@@ -71,8 +71,8 @@ spring:
 token:
     # 令牌自定义标识
     header: Authorization
-    # 令牌密钥
-    secret: abcdefghijklmnopqrstuvwxyz
+    # 令牌密钥已迁 scrm.env → TOKEN_SECRET
+    secret: ${TOKEN_SECRET:}
     # 令牌有效期(默认30分钟)
     expireTime: 720
 mybatis-plus:
@@ -144,10 +144,11 @@ wechat:
     base-url: https://api.weixin.qq.com
     upload-shipping-info: /wxa/sec/order/upload_shipping_info
 hsy:
-  access_key: AKLTZTc4YTE4ZjI2OWViNDNjZGI2NjhiYTI5Njc5ZjA1Mzk
-  secret_key: WXpjelpUYzFOakF5TUdObE5EZGtNR0ZsWXpKaU1tTmtZakk1WXpObE4yRQ==
+  # 密钥已迁 scrm.env → HSY_ACCESS_KEY / HSY_SECRET_KEY / HSY_ROLE_*
+  access_key: ${HSY_ACCESS_KEY:}
+  secret_key: ${HSY_SECRET_KEY:}
   region: cn-north-1
-  role_access_key: AKLTNmMwNjJkNDFhYTVjNDIzYzhhNzEyZmZmZTlmYzBhNGM
-  role_secret_key: T0RaaFl6UmhZV1V4WXpKbU5EWTBNMkZpT0RNNU9UY3daak0wTjJFd09XUQ==
+  role_access_key: ${HSY_ROLE_ACCESS_KEY:}
+  role_secret_key: ${HSY_ROLE_SECRET_KEY:}
   role_trn: trn:iam::2114522511:role/hylj
 

+ 27 - 29
fs-service/src/main/resources/application-config-druid-jnmy.yml

@@ -1,5 +1,6 @@
 baidu:
-  token: 12313231232
+  # 原 token 已迁 scrm.env → BAIDU_TOKEN
+  token: ${BAIDU_TOKEN:}
   back-domain: https://www.xxxx.com
 #配置
 logging:
@@ -11,21 +12,24 @@ wx:
   miniapp:
     configs:
       - appid:
-        secret:
-        token:
-        aesKey: HlEiBB55eaWUaeBVAQO3cWKWPYv1vOVQSq7nFNICw4E
+        # 密钥已迁 scrm.env → WX_MINIAPP_SECRET / WX_MINIAPP_TOKEN / WX_MINIAPP_AES_KEY
+        secret: ${WX_MINIAPP_SECRET:}
+        token: ${WX_MINIAPP_TOKEN:}
+        aesKey: ${WX_MINIAPP_AES_KEY:}
         msgDataFormat: JSON
   cp:
     corpId: wwb2a1055fb6c9a7c2
     appConfigs:
       - agentId: 1000005
-        secret: ec7okROXJqkNafq66-L6aKNv0asTzQIG0CYrj3vyBbo
-        token: PPKOdAlCoMO
-        aesKey: PKvaxtpSv8NGpfTDm7VUHIK8Wok2ESyYX24qpXJAdMP
+        # 密钥已迁 scrm.env → WX_CP_SECRET / WX_CP_TOKEN / WX_CP_AES_KEY
+        secret: ${WX_CP_SECRET:}
+        token: ${WX_CP_TOKEN:}
+        aesKey: ${WX_CP_AES_KEY:}
   pay:
     appId: wx7cf96953a4be5181 #微信公众号或者小程序等的appid
     mchId: 1611402045 #微信支付商户号
-    mchKey: 8cab128997a3547c1363b0898b877f38 #微信支付商户密钥
+    # 商户密钥已迁 scrm.env → WX_PAY_MCH_KEY
+    mchKey: ${WX_PAY_MCH_KEY:}
     subAppId:  #服务商模式下的子商户公众账号ID
     subMchId:  #服务商模式下的子商户号
     keyPath: c:\\cert\\apiclient_cert.p12 # p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头)
@@ -38,52 +42,48 @@ wx:
       timeout: 2000
     configs:
       - appId: wx6ee517a8d8743f88  # 第一个公众号的appid
-        secret: 1fac75465a61f9259a0fe19795d9e80d # 公众号的appsecret
-        token: PPKOdAlCoMO # 接口配置里的Token值
-        aesKey: Eswa6VjwtVMCcw03qZy6fWllgrv5aytIA1SZPEU0kU2 # 接口配置里的EncodingAESKey值
+        # 密钥已迁 scrm.env → WX_MP_SECRET / WX_MP_TOKEN / WX_MP_AES_KEY
+        secret: ${WX_MP_SECRET:}
+        token: ${WX_MP_TOKEN:}
+        aesKey: ${WX_MP_AES_KEY:}
   open:
     appId: wxda2eb168e5b09e56
-    secret: dde09b45d0f6d5b0925965b964b6ab48
+    # 密钥已迁 scrm.env → WX_OPEN_SECRET
+    secret: ${WX_OPEN_SECRET:}
 aifabu:  #爱链接
-  appKey: 7b471be905ab17e00f3b858c6710dd117601d008
+  # 密钥已迁 scrm.env → AIFABU_APP_KEY
+  appKey: ${AIFABU_APP_KEY:}
 watch:
   watchUrl: watch.ylrzcloud.com/prod-api
-  #  account: tcloud
-  #  password: mdf-m2h_6yw2$hq
   account1: ccif #866655060138751
-  password1: cp-t5or_6xw7$mt
+  # 密码已迁 scrm.env → WATCH_PASSWORD_1/2/3
+  password1: ${WATCH_PASSWORD_1:}
   account2: tcloud #rt500台
-  password2: mdf-m2h_6yw2$hq
+  password2: ${WATCH_PASSWORD_2:}
   account3: whr
-  password3: v9xsKuqn_$d2y
+  password3: ${WATCH_PASSWORD_3:}
 
 fs :
   commonApi: http://192.168.0.240:7771
   h5CommonApi: http://192.168.0.240:7771
   jwt:
-    # 加密秘钥
-    secret: f4e2e52034348f86b67cde581c0f9eb5
+    # 加密秘钥已迁 scrm.env → FS_JWT_SECRET
+    secret: ${FS_JWT_SECRET:}
     # token有效时长,7天,单位秒
     expire: 31536000
     header: AppToken
 nuonuo:
   # 密钥改由环境变量 NUONUO_APP_KEY / NUONUO_APP_SECRET / NUONUO_TOKEN(见 scrm.env)
-  # key: 10924508
-  # secret: A2EB20764D304D16
 
 # 存储捅配置
 tencent_cloud_config:
   # 密钥改由环境变量 TENCENT_CLOUD_SECRET_ID / TENCENT_CLOUD_SECRET_KEY(见 scrm.env)
-  # secret_id: AKIDLl1tguyrZ6QddTCi2BLJ4e3OXVIuJVVK
-  # secret_key: g9R6kLrMp8EDzXszylLispiQxHRN6cw5
   bucket: jnmy-1323137866
   app_id: 1323137866
   region: ap-chongqing
   proxy: jnmy
 tmp_secret_config:
-  # 临时密钥:测试环境变量时一并注释;若临时凭证上传失败可恢复
-  # secret_id: AKIDCj7NSNAovtqeJpBau8GZ4CGB71thXIx
-  # secret_key: lTB5zwqqz7CNhzDOWivFWedgfTBgxgB
+  # 临时密钥改由 TENCENT_CLOUD_* 环境变量;勿回填明文
   bucket: fs-131972100
   app_id: 1319721001
   region: ap-chongqing
@@ -105,5 +105,3 @@ ipad:
 wx_miniapp_temp:
   pay_order_temp_id: -SjnK9K6cNKASa6AD9Q_c0YT7J1lPTEpPIpqbMJF8F0
   inquiry_temp_id: hwFXVh0AWqeasBsZpa0-urb3CrPeYEwBiy3P6AMMGFQ
-
-

+ 0 - 250
fs-service/src/main/resources/application-druid-jnmy-test.yml

@@ -1,250 +0,0 @@
-# 数据源配置
-spring:
-    profiles:
-        include: config-druid-jnmy,common
-    # redis 配置
-    redis:
-        # 地址
-        host: 127.0.0.1
-        # 端口,默认为6379
-        port: 6379
-        # 数据库索引
-        database: 0
-        # 密码
-        password:
-        # 连接超时时间
-        timeout: 20s
-        lettuce:
-            pool:
-                # 连接池中的最小空闲连接
-                min-idle: 0
-                # 连接池中的最大空闲连接
-                max-idle: 8
-                # 连接池的最大数据库连接数
-                max-active: 8
-                # #连接池最大阻塞等待时间(使用负值表示没有限制)
-                max-wait: -1ms
-    datasource:
-        #        clickhouse:
-        #            type: com.alibaba.druid.pool.DruidDataSource
-        #            driverClassName: com.clickhouse.jdbc.ClickHouseDriver
-        #            url: jdbc:clickhouse://cc-2vc8zzo26w0l7m2l6.public.clickhouse.ads.aliyuncs.com/sop?compress=0&use_server_time_zone=true&use_client_time_zone=false&timezone=Asia/Shanghai
-        #            username: rt_2024
-        #            password: Yzx_19860213
-        #            initialSize: 10
-        #            maxActive: 100
-        #            minIdle: 10
-        #            maxWait: 6000
-        mysql:
-            type: com.alibaba.druid.pool.DruidDataSource
-            driverClassName: com.mysql.cj.jdbc.Driver
-            druid:
-                # 主库数据源
-                master:
-                    url: jdbc:mysql://1.94.236.76:2345/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
-                    username: root
-                    password: Ylrztek250218!3@.
-                # 从库数据源
-                slave:
-                    # 从数据源开关/默认关闭
-                    enabled: false
-                    url:
-                    username:
-                    password:
-                # 初始连接数
-                initialSize: 5
-                # 最小连接池数量
-                minIdle: 10
-                # 最大连接池数量
-                maxActive: 20
-                # 配置获取连接等待超时的时间
-                maxWait: 60000
-                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
-                timeBetweenEvictionRunsMillis: 60000
-                # 配置一个连接在池中最小生存的时间,单位是毫秒
-                minEvictableIdleTimeMillis: 300000
-                # 配置一个连接在池中最大生存的时间,单位是毫秒
-                maxEvictableIdleTimeMillis: 900000
-                # 配置检测连接是否有效
-                validationQuery: SELECT 1 FROM DUAL
-                testWhileIdle: true
-                testOnBorrow: false
-                testOnReturn: false
-                webStatFilter:
-                    enabled: true
-                statViewServlet:
-                    enabled: true
-                    # 设置白名单,不填则允许所有访问
-                    allow:
-                    url-pattern: /druid/*
-                    # 控制台管理用户名和密码
-                    login-username: fs
-                    login-password: ${DRUID_LOGIN_PASSWORD:}
-                filter:
-                    stat:
-                        enabled: true
-                        # 慢SQL记录
-                        log-slow-sql: true
-                        slow-sql-millis: 1000
-                        merge-sql: true
-                    wall:
-                        config:
-                            multi-statement-allow: true
-        sop:
-            type: com.alibaba.druid.pool.DruidDataSource
-            driverClassName: com.mysql.cj.jdbc.Driver
-            druid:
-                # 主库数据源
-                master:
-                    url: jdbc:mysql://1.94.236.76:2345/sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
-                    username: root
-                    password: Ylrztek250218!3@.
-                # 初始连接数
-                initialSize: 5
-                # 最小连接池数量
-                minIdle: 10
-                # 最大连接池数量
-                maxActive: 20
-                # 配置获取连接等待超时的时间
-                maxWait: 60000
-                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
-                timeBetweenEvictionRunsMillis: 60000
-                # 配置一个连接在池中最小生存的时间,单位是毫秒
-                minEvictableIdleTimeMillis: 300000
-                # 配置一个连接在池中最大生存的时间,单位是毫秒
-                maxEvictableIdleTimeMillis: 900000
-                # 配置检测连接是否有效
-                validationQuery: SELECT 1 FROM DUAL
-                testWhileIdle: true
-                testOnBorrow: false
-                testOnReturn: false
-                webStatFilter:
-                    enabled: true
-                statViewServlet:
-                    enabled: true
-                    # 设置白名单,不填则允许所有访问
-                    allow:
-                    url-pattern: /druid/*
-                    # 控制台管理用户名和密码
-                    login-username: fs
-                    login-password: ${DRUID_LOGIN_PASSWORD:}
-                filter:
-                    stat:
-                        enabled: true
-                        # 慢SQL记录
-                        log-slow-sql: true
-                        slow-sql-millis: 1000
-                        merge-sql: true
-                    wall:
-                        config:
-                            multi-statement-allow: true
-    shardingsphere:
-        datasource:
-            names: ds0
-            ds0:
-                # 主库数据源
-                type: com.alibaba.druid.pool.DruidDataSource
-                driverClassName: com.mysql.cj.jdbc.Driver
-                url: jdbc:mysql://1.94.236.76:2345/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
-                username: root
-                password: Ylrztek250218!3@.
-                # 初始连接数
-                initialSize: 5
-                # 最小连接池数量
-                minIdle: 10
-                # 最大连接池数量
-                maxActive: 20
-                # 配置获取连接等待超时的时间
-                maxWait: 60000
-                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
-                timeBetweenEvictionRunsMillis: 60000
-                # 配置一个连接在池中最小生存的时间,单位是毫秒
-                minEvictableIdleTimeMillis: 300000
-                # 配置一个连接在池中最大生存的时间,单位是毫秒
-                maxEvictableIdleTimeMillis: 900000
-                # 配置检测连接是否有效
-                validationQuery: SELECT 1 FROM DUAL
-                testWhileIdle: true
-                testOnBorrow: false
-                testOnReturn: false
-                webStatFilter:
-                    enabled: true
-                statViewServlet:
-                    enabled: true
-                    # 设置白名单,不填则允许所有访问
-                    allow:
-                    url-pattern: /druid/*
-                    # 控制台管理用户名和密码
-                    login-username: fs
-                    login-password: ${DRUID_LOGIN_PASSWORD:}
-                filter:
-                    stat:
-                        enabled: true
-                        # 慢SQL记录
-                        log-slow-sql: true
-                        slow-sql-millis: 1000
-                        merge-sql: true
-                    wall:
-                        config:
-                            multi-statement-allow: true
-        rules:
-            sharding:
-                tables:
-                    qw_msg:
-                        actual-data-nodes: ds0.qw_msg_$->{0..9}
-                        table-strategy:
-                            standard:
-                                sharding-column: session_id
-                                sharding-algorithm-name: msg-inline
-                    fs_user_operation_log:
-                        actual-data-nodes: ds0.fs_user_operation_log_$->{0..9}
-                        table-strategy:
-                            standard:
-                                sharding-column: user_id
-                                sharding-algorithm-name: oper-inline
-                sharding-algorithms:
-                    msg-inline:
-                        type: INLINE
-                        props:
-                            algorithm-expression: qw_msg_$->{session_id % 10}
-                    oper-inline:
-                        type: INLINE
-                        props:
-                            algorithm-expression: fs_user_operation_log_$->{user_id % 10}
-rocketmq:
-    name-server: rmq-1243b25nj.rocketmq.gz.public.tencenttdmq.com:8080 # RocketMQ NameServer 地址
-    producer:
-        group: my-producer-group
-        access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
-        secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey
-    consumer:
-        group: test-group
-        access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
-        secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey
-openIM:
-    secret: openIM123
-    userID: imAdmin
-    url: https://web.jnmyim.ylrzfs.com/api
-#是否使用新im
-im:
-    type: OPENIM
-isNewWxMerchant: true
-ipad:
-    url: http://localhost:8999/dev-api
-    companyId: 13
-wechat:
-#    company:
-#        appid: wxd7c1e221622a0ccf
-#        secret: 70d3ed4f8eb68cca0cf525b8ce07405d
-#        redirectUri: http://ta6d97ec.natappfree.cc/callback
-#    admin:
-#        appid: wxd7c1e221622a0ccf
-#        secret: 70d3ed4f8eb68cca0cf525b8ce07405d
-#        redirectUri: http://ta6d97ec.natappfree.cc/callback
-#    isNeedScan: true
-
-# 飞书(已改为库表 fs_feishu_config 动态配置,以下保留兼容注释)
-#feishu:
-#    appId: "cli_aab0956445f89beb"
-#    appSecret: "zPonwfW704MLe0YnCfpwzhRjDsFkk0nT"
-

+ 1 - 1
fs-service/src/main/resources/mapper/company/CompanyDeptMapper.xml

@@ -164,7 +164,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
             update_time = sysdate()
         </set>
-        where dept_id in (${ancestors})
+        where find_in_set(dept_id, #{ancestors})
     </update>
 
     <select id="selectChildrenDeptById" parameterType="Long" resultMap="CompanyDeptResult">

+ 2 - 1
fs-service/src/main/resources/mapper/course/FsCourseAnswerLogsMapper.xml

@@ -143,7 +143,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             </if>
         </where>
         ORDER BY cal_inner.log_id DESC
-        LIMIT ${(pageNum-1)*pageSize}, ${pageSize}
+        <bind name="__limitOffset" value="(pageNum - 1) * pageSize"/>
+        limit #{__limitOffset}, #{pageSize}
         ) AS paged_ids ON cal.log_id = paged_ids.log_id
         LEFT JOIN fs_user_course uc ON cal.course_id = uc.course_id
         ORDER BY

+ 1 - 1
fs-service/src/main/resources/mapper/his/FsPackageOrderMapper.xml

@@ -318,7 +318,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         where O.is_pay = 1 ORDER BY RAND() desc LIMIT 20
     </select>
     <select id="selectOutTimeOrderList" resultType="com.fs.his.domain.FsPackageOrder">
-        select * from fs_package_order  where status = 1 AND NOW() &gt; DATE_ADD(create_time, INTERVAL ${unPayTime} MINUTE)
+        select * from fs_package_order  where status = 1 AND NOW() &gt; DATE_ADD(create_time, INTERVAL #{unPayTime} MINUTE)
     </select>
     <select id="selectInformationCollectionByStoreOrderId" resultType="com.fs.his.vo.FsUserInfoCollectionAndStoreOrderVo">
         SELECT ic.* FROM `fs_package_order` po

+ 2 - 1
fs-service/src/main/resources/mapper/his/FsUserMapper.xml

@@ -420,7 +420,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 AND fs_user.create_time &gt;= #{sTime} AND fs_user.create_time &lt;= #{eTime}
             </if>
         </where>
-        limit ${(pageNum-1)*pageSize},${pageSize}
+        <bind name="__limitOffset" value="(pageNum - 1) * pageSize"/>
+        limit #{__limitOffset}, #{pageSize}
     </select>
 
     <select id="selectFsUserPageListCount" resultType="java.lang.Long">

+ 7 - 5
fs-service/src/main/resources/mapper/hisStore/FsUserScrmMapper.xml

@@ -202,7 +202,8 @@
             </if>
         </where>
         order by u.user_id desc
-        limit ${(pageNum-1)*pageSize},${pageSize}
+        <bind name="__limitOffset" value="(pageNum - 1) * pageSize"/>
+        limit #{__limitOffset}, #{pageSize}
         ) t on t.user_id = `user`.user_id
         left join company_user cu on cu.user_id = t.companyUserId
     </select>
@@ -577,7 +578,7 @@
         where
             user_id in
         <foreach collection="userIds" open="(" close=")" separator="," item="item">
-            ${item}
+            #{item}
         </foreach>
     </update>
 
@@ -628,7 +629,7 @@
             <if test="companyUserIds != null and companyUserIds.size > 0">
                 AND ucu.company_user_id in
                 <foreach collection="companyUserIds" item="item" index="index" open="(" separator="," close=")">
-                    ${item}
+                    #{item}
                 </foreach>
             </if>
             <if test="nickname != null and nickname != ''">
@@ -640,7 +641,8 @@
             <if test="projectId != null">
                 AND ucu.project_id = #{projectId}
             </if>
-        limit ${(pageNum-1)*pageSize},${pageSize}
+        <bind name="__limitOffset" value="(pageNum - 1) * pageSize"/>
+        limit #{__limitOffset}, #{pageSize}
     </select>
 
     <select id="selectFsUserPageList" resultType="com.fs.store.vo.h5.FsUserPageListVO">
@@ -1695,7 +1697,7 @@
             <if test="companyUserIds != null and companyUserIds.size > 0">
                 AND ucu.company_user_id in
                 <foreach collection="companyUserIds" item="item" index="index" open="(" separator="," close=")">
-                    ${item}
+                    #{item}
                 </foreach>
             </if>
             <if test="nickname != null and nickname!=''">

+ 2 - 1
fs-service/src/main/resources/mapper/qw/HyWorkTaskMapper.xml

@@ -60,7 +60,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="companyUserId != null "> and t_inner.company_user_id = #{companyUserId}</if>
         </where>
         ORDER BY t_inner.score DESC, t_inner.id DESC
-        LIMIT ${(pageNum-1)*pageSize}, ${pageSize}
+        <bind name="__limitOffset" value="(pageNum - 1) * pageSize"/>
+        limit #{__limitOffset}, #{pageSize}
         ) AS filtered_ids ON t.id = filtered_ids.id
         ORDER BY t.score DESC, t.id DESC
     </select>

+ 5 - 4
fs-service/src/main/resources/mapper/qw/QwWatchLogMapper.xml

@@ -107,7 +107,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="companyUserIds != null and companyUserIds.size() != 0">
                 and company_user_id in
                 <foreach collection="companyUserIds" item="item" open="(" close=")" separator=",">
-                    ${item}
+                    #{item}
                 </foreach>
             </if>
             <if test="project != null">
@@ -131,7 +131,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="companyUserIds != null">
                 and company_user_id in
                 <foreach collection="companyUserIds" item="item" open="(" close=")" separator=",">
-                    ${item}
+                    #{item}
                 </foreach>
             </if>
             <if test="project != null">
@@ -160,7 +160,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="companyUserIds != null and companyUserIds.size() != 0">
                 and company_user_id in
                 <foreach collection="companyUserIds" item="item" open="(" close=")" separator=",">
-                    ${item}
+                    #{item}
                 </foreach>
             </if>
             <if test="project != null">
@@ -175,7 +175,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             and DATE(line_time) between #{sTime} and #{dTime}
         </where>
         group by project,course_id,video_id
-        limit ${(pageNum-1)*pageSize},${pageSize}
+        <bind name="__limitOffset" value="(pageNum - 1) * pageSize"/>
+        limit #{__limitOffset}, #{pageSize}
     </select>
     <select id="selectQwWatchLogByCompanyUserIdCount" resultType="java.lang.Long">
         SELECT COUNT(*)

+ 2 - 2
fs-service/src/main/resources/mapper/statis/FsStatisEveryDayMapper.xml

@@ -144,13 +144,13 @@
             <if test="userIds != null and userIds.length > 0">
                 AND company_user_id IN
                 <foreach collection="userIds" open="(" close=")" separator="," item="item">
-                    ${item}
+                    #{item}
                 </foreach>
             </if>
             <if test="periodList != null and periodList.length > 0">
                 AND period_id IN
                 <foreach collection="periodList" open="(" close=")" separator="," item="item">
-                    ${item}
+                    #{item}
                 </foreach>
             </if>
             <if test="startDate != null and endDate != null">

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio