xw 3 tygodni temu
rodzic
commit
69f053ec2c
100 zmienionych plików z 6072 dodań i 285 usunięć
  1. 1 1
      deploy.sh
  2. 6 0
      fs-ad-new-api/Dockerfile
  3. 98 0
      fs-ad-new-api/pom.xml
  4. 14 0
      fs-ad-new-api/src/main/java/com/fs/FSServletInitializer.java
  5. 22 0
      fs-ad-new-api/src/main/java/com/fs/FsAdNewApiApplication.java
  6. 130 0
      fs-ad-new-api/src/main/java/com/fs/app/controller/CallbackController.java
  7. 51 0
      fs-ad-new-api/src/main/java/com/fs/app/controller/LandingPageController.java
  8. 42 0
      fs-ad-new-api/src/main/java/com/fs/app/controller/TestController.java
  9. 48 0
      fs-ad-new-api/src/main/java/com/fs/app/controller/TrackingController.java
  10. 67 0
      fs-ad-new-api/src/main/java/com/fs/app/controller/WeChatController.java
  11. 29 0
      fs-ad-new-api/src/main/java/com/fs/app/facade/CallbackProcessingFacadeService.java
  12. 287 0
      fs-ad-new-api/src/main/java/com/fs/app/facade/CallbackProcessingFacadeServiceImpl.java
  13. 172 0
      fs-ad-new-api/src/main/java/com/fs/app/facade/ConversionServiceImpl.java
  14. 23 0
      fs-ad-new-api/src/main/java/com/fs/app/facade/IConversionService.java
  15. 71 0
      fs-ad-new-api/src/main/java/com/fs/app/mq/consumer/ConversionTrackingMessageConsumer.java
  16. 91 0
      fs-ad-new-api/src/main/java/com/fs/app/task/ConversionRetryTask.java
  17. 225 0
      fs-ad-new-api/src/main/java/com/fs/app/task/DataSyncTask.java
  18. 182 0
      fs-ad-new-api/src/main/java/com/fs/framework/aspectj/DataScopeAspect.java
  19. 73 0
      fs-ad-new-api/src/main/java/com/fs/framework/aspectj/DataSourceAspect.java
  20. 117 0
      fs-ad-new-api/src/main/java/com/fs/framework/aspectj/DistributeLockAspect.java
  21. 244 0
      fs-ad-new-api/src/main/java/com/fs/framework/aspectj/LogAspect.java
  22. 58 0
      fs-ad-new-api/src/main/java/com/fs/framework/aspectj/ScheduledTaskAspect.java
  23. 29 0
      fs-ad-new-api/src/main/java/com/fs/framework/aspectj/ScheduledTraceIdAspect.java
  24. 31 0
      fs-ad-new-api/src/main/java/com/fs/framework/config/ApplicationConfig.java
  25. 61 0
      fs-ad-new-api/src/main/java/com/fs/framework/config/AsyncConfig.java
  26. 19 0
      fs-ad-new-api/src/main/java/com/fs/framework/config/CorsConfig.java
  27. 92 0
      fs-ad-new-api/src/main/java/com/fs/framework/config/DataSourceConfig.java
  28. 72 0
      fs-ad-new-api/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  29. 59 0
      fs-ad-new-api/src/main/java/com/fs/framework/config/FilterConfig.java
  30. 30 0
      fs-ad-new-api/src/main/java/com/fs/framework/config/MdcTaskDecorator.java
  31. 148 0
      fs-ad-new-api/src/main/java/com/fs/framework/config/MyBatisConfig.java
  32. 158 0
      fs-ad-new-api/src/main/java/com/fs/framework/config/RedisConfig.java
  33. 50 0
      fs-ad-new-api/src/main/java/com/fs/framework/config/SecurityConfig.java
  34. 33 0
      fs-ad-new-api/src/main/java/com/fs/framework/config/ServerConfig.java
  35. 63 0
      fs-ad-new-api/src/main/java/com/fs/framework/config/ThreadPoolConfig.java
  36. 36 0
      fs-ad-new-api/src/main/java/com/fs/framework/config/TrackIdFilter.java
  37. 77 0
      fs-ad-new-api/src/main/java/com/fs/framework/config/properties/DruidProperties.java
  38. 27 0
      fs-ad-new-api/src/main/java/com/fs/framework/datasource/DynamicDataSource.java
  39. 45 0
      fs-ad-new-api/src/main/java/com/fs/framework/datasource/DynamicDataSourceContextHolder.java
  40. 56 0
      fs-ad-new-api/src/main/java/com/fs/framework/manager/AsyncManager.java
  41. 40 0
      fs-ad-new-api/src/main/java/com/fs/framework/manager/ShutdownManager.java
  42. 103 0
      fs-ad-new-api/src/main/java/com/fs/framework/manager/factory/AsyncFactory.java
  43. 1 0
      fs-ad-new-api/src/main/resources/META-INF/spring-devtools.properties
  44. 2 0
      fs-ad-new-api/src/main/resources/banner.txt
  45. 37 0
      fs-ad-new-api/src/main/resources/i18n/messages.properties
  46. 93 0
      fs-ad-new-api/src/main/resources/logback.xml
  47. 15 0
      fs-ad-new-api/src/main/resources/mybatis/mybatis-config.xml
  48. 6 0
      fs-admin/Dockerfile
  49. 103 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyRedPacketBalanceLogsController.java
  50. 41 0
      fs-admin/src/main/java/com/fs/course/business/FsVideoResourceBusinessService.java
  51. 18 6
      fs-admin/src/main/java/com/fs/course/controller/FsCourseAnswerLogsController.java
  52. 38 0
      fs-admin/src/main/java/com/fs/course/controller/FsCourseFinishTempController.java
  53. 24 0
      fs-admin/src/main/java/com/fs/course/controller/FsCoursePlaySourceConfigController.java
  54. 56 18
      fs-admin/src/main/java/com/fs/course/controller/FsCourseRedPacketLogController.java
  55. 55 4
      fs-admin/src/main/java/com/fs/course/controller/FsCourseWatchLogController.java
  56. 44 0
      fs-admin/src/main/java/com/fs/course/controller/FsUserCoursePeriodController.java
  57. 46 3
      fs-admin/src/main/java/com/fs/course/controller/FsUserVideoController.java
  58. 57 21
      fs-admin/src/main/java/com/fs/course/controller/FsVideoResourceController.java
  59. 27 0
      fs-admin/src/main/java/com/fs/course/controller/qw/QwFsCourseWatchLogController.java
  60. 6 6
      fs-admin/src/main/java/com/fs/his/controller/FsAdvController.java
  61. 1 0
      fs-admin/src/main/java/com/fs/his/controller/FsIntegralGoodsController.java
  62. 96 17
      fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java
  63. 41 5
      fs-admin/src/main/java/com/fs/his/controller/FsStoreOrderController.java
  64. 49 4
      fs-admin/src/main/java/com/fs/his/controller/FsUserAddressController.java
  65. 5 1
      fs-admin/src/main/java/com/fs/his/controller/FsUserCouponController.java
  66. 107 0
      fs-admin/src/main/java/com/fs/his/controller/MerchantAppConfigController.java
  67. 114 5
      fs-admin/src/main/java/com/fs/his/task/Task.java
  68. 66 4
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreAfterSalesScrmController.java
  69. 57 53
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreHealthOrderScrmController.java
  70. 93 28
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java
  71. 20 4
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStorePaymentScrmController.java
  72. 9 9
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreScrmController.java
  73. 78 11
      fs-admin/src/main/java/com/fs/hisStore/task/LiveTask.java
  74. 29 6
      fs-admin/src/main/java/com/fs/hisStore/task/MallStoreTask.java
  75. 78 6
      fs-admin/src/main/java/com/fs/live/controller/LiveAfterSalesController.java
  76. 63 2
      fs-admin/src/main/java/com/fs/live/controller/LiveController.java
  77. 66 0
      fs-admin/src/main/java/com/fs/live/controller/LiveDataController.java
  78. 88 14
      fs-admin/src/main/java/com/fs/live/controller/LiveOrderController.java
  79. 4 0
      fs-admin/src/main/java/com/fs/live/controller/LiveVideoController.java
  80. 307 0
      fs-admin/src/main/java/com/fs/live/controller/OrderController.java
  81. 14 1
      fs-admin/src/main/java/com/fs/qw/controller/QwExternalContactController.java
  82. 43 0
      fs-admin/src/main/java/com/fs/qw/controller/QwFriendWelcomeController.java
  83. 30 6
      fs-admin/src/main/java/com/fs/qw/controller/QwSopController.java
  84. 55 9
      fs-admin/src/main/java/com/fs/qw/controller/QwSopTempController.java
  85. 7 0
      fs-admin/src/main/java/com/fs/qw/controller/QwUserController.java
  86. 107 24
      fs-admin/src/main/java/com/fs/qw/qwTask/qwTask.java
  87. 111 0
      fs-admin/src/main/java/com/fs/task/MiniProgramSubTask.java
  88. 8 1
      fs-admin/src/main/java/com/fs/web/controller/common/CommonController.java
  89. 129 7
      fs-admin/src/main/java/com/fs/web/controller/system/SysLoginController.java
  90. 1 1
      fs-admin/src/main/resources/application.yml
  91. 3 3
      fs-admin/src/main/resources/logback.xml
  92. 6 0
      fs-common-api/Dockerfile
  93. 24 0
      fs-common-api/pom.xml
  94. 1 1
      fs-common-api/src/main/resources/application.yml
  95. 3 3
      fs-common-api/src/main/resources/logback.xml
  96. 60 0
      fs-common/src/main/java/com/fs/common/annotation/DistributeLock.java
  97. 13 0
      fs-common/src/main/java/com/fs/common/constant/DistributeLockConstant.java
  98. 9 1
      fs-common/src/main/java/com/fs/common/constant/LiveKeysConstant.java
  99. 71 0
      fs-common/src/main/java/com/fs/common/constant/RedisKeyConstant.java
  100. 57 0
      fs-common/src/main/java/com/fs/common/constant/RocketMqConstant.java

+ 1 - 1
deploy.sh

@@ -87,7 +87,7 @@ deploy_jar() {
     echo "启动服务..."
     ssh -f "$REMOTE_USER@$remote_host" \
     "cd $REMOTE_BASE_DIR/$remote_dir && \
-     nohup java -jar  -Dfile.encoding=UTF-8 $app_name.jar --spring.profiles.active=druid-bjzm --server.port=7114  \
+     nohup java -jar -Xms8g -Xmx8g -XX:+UseG1GC -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 $app_name.jar --spring.profiles.active=druid-bjzm --server.port=7114  \
      >> $app_name.log 2>&1 &"
 
     # 检查进程是否启动成功

+ 6 - 0
fs-ad-new-api/Dockerfile

@@ -0,0 +1,6 @@
+FROM openjdk:8-jre
+# java版本,最好使用openjdk,而不是类似于Java:1.8
+COPY ./target/fs-ad-new-api.jar fs-ad-new-api.jar
+# 向外暴露的接口,最好与项目yml文件中的端口一致
+ENTRYPOINT ["java","-jar","fs-ad-new-api.jar"]
+# 执行启动命令java -jar

+ 98 - 0
fs-ad-new-api/pom.xml

@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>fs</artifactId>
+        <groupId>com.fs</groupId>
+        <version>1.1.0</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>fs-ad-new-api</artifactId>
+    <description>
+       新投流接口
+    </description>
+    <dependencies>
+
+        <!-- Mysql驱动包 -->
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+
+        <!-- SpringBoot Web容器 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <!-- SpringBoot 拦截器 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+
+        <!-- 阿里数据库连接池 -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid-spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fs</groupId>
+            <artifactId>fs-service</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.rocketmq</groupId>
+            <artifactId>rocketmq-spring-boot-starter</artifactId>
+            <version>2.2.3</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>2.1.1.RELEASE</version>
+                <configuration>
+                    <fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-war-plugin</artifactId>
+                <version>3.1.0</version>
+                <configuration>
+                    <failOnMissingWebXml>false</failOnMissingWebXml>
+                    <warName>${project.artifactId}</warName>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <compilerArgs>
+                        <arg>-parameters</arg>
+                    </compilerArgs>
+                </configuration>
+            </plugin>
+
+        </plugins>
+        <finalName>${project.artifactId}</finalName>
+    </build>
+
+</project>

+ 14 - 0
fs-ad-new-api/src/main/java/com/fs/FSServletInitializer.java

@@ -0,0 +1,14 @@
+package com.fs;
+
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+
+
+public class FSServletInitializer extends SpringBootServletInitializer
+{
+    @Override
+    protected SpringApplicationBuilder configure(SpringApplicationBuilder application)
+    {
+        return application.sources(FsAdNewApiApplication.class);
+    }
+}

+ 22 - 0
fs-ad-new-api/src/main/java/com/fs/FsAdNewApiApplication.java

@@ -0,0 +1,22 @@
+package com.fs;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+/**
+ * 启动程序
+ */
+@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
+@EnableAsync
+@EnableScheduling
+public class FsAdNewApiApplication {
+    public static void main(String[] args) {
+        SpringApplication.run(FsAdNewApiApplication.class, args);
+        System.out.println("========================================");
+        System.out.println("ad-new-api启动成功");
+        System.out.println("========================================");
+    }
+}

+ 130 - 0
fs-ad-new-api/src/main/java/com/fs/app/controller/CallbackController.java

@@ -0,0 +1,130 @@
+package com.fs.app.controller;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.fs.newAdv.integration.client.IAccessTokenClient;
+import com.fs.newAdv.integration.client.IApiClient;
+import com.fs.newAdv.integration.factory.AdvertiserHandlerFactory;
+import com.fs.common.result.Result;
+import com.fs.newAdv.domain.PromotionAccount;
+import com.fs.newAdv.enums.AdvertiserTypeEnum;
+import com.fs.newAdv.service.IPromotionAccountService;
+import com.fs.newAdv.vo.AccessTokenByAuthCodeVo;
+import com.fs.newAdv.vo.AccessTokenVo;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+import java.time.LocalDateTime;
+
+/**
+ * 广告商回调接口
+ */
+@Slf4j
+@RestController
+@RequestMapping("/callBack")
+public class CallbackController {
+
+    @Autowired
+    private AdvertiserHandlerFactory advertiserHandlerFactory;
+    @Autowired
+    private IPromotionAccountService promotionAccountService;
+
+    /**
+     * 百度回调接口
+     *
+     * @param request
+     * @return
+     */
+    @GetMapping("/baidu/getAuthCode")
+    public Result<String> getBaiduAuthCode(HttpServletRequest request) {
+        getAccessToken(AdvertiserTypeEnum.BAIDU, request.getParameter("state"), request.getParameter("authCode"));
+        return Result.success("");
+    }
+
+    /**
+     * 巨量回调接口
+     *
+     * @param request
+     * @return
+     */
+    @GetMapping("/oceanEngine/getAuthCode")
+    public Result<String> getOceanEngineAuthCode(HttpServletRequest request) {
+        getAccessToken(AdvertiserTypeEnum.OCEANENGINE, request.getParameter("state"), request.getParameter("auth_code"));
+        return Result.success("");
+    }
+
+    /**
+     * 腾讯回调接口
+     *
+     * @param request
+     * @return
+     */
+    @GetMapping("/tencent/getAuthCode")
+    public Result<String> getTencentTAuthCode(HttpServletRequest request) {
+        getAccessToken(AdvertiserTypeEnum.TENCENT, request.getParameter("state"), request.getParameter("authorization_code"));
+        return Result.success("");
+    }
+
+    /**
+     * OPPO回调接口
+     *
+     * @param request
+     * @return
+     */
+    @GetMapping("/oppo/getAuthCode")
+    public Result<String> getOppoAuthCode(HttpServletRequest request) {
+        getAccessToken(AdvertiserTypeEnum.OCEANENGINE, request.getParameter("state"), request.getParameter("auth_code"));
+        return Result.success("");
+    }
+
+    /**
+     * vivo回调接口
+     *
+     * @param request
+     * @return
+     */
+    @GetMapping("/vivo/getAuthCode")
+    public Result<String> getViVoAuthCode(HttpServletRequest request) {
+        getAccessToken(AdvertiserTypeEnum.OCEANENGINE, request.getParameter("state"), request.getParameter("auth_code"));
+        return Result.success("");
+    }
+
+    /**
+     * iqiyi回调接口
+     *
+     * @param request
+     * @return
+     */
+    @GetMapping("/iqiyi/getAuthCode")
+    public Result<String> getiqiyiAuthCode(HttpServletRequest request) {
+        getAccessToken(AdvertiserTypeEnum.OCEANENGINE, request.getParameter("state"), request.getParameter("auth_code"));
+        return Result.success("");
+    }
+
+    private void getAccessToken(AdvertiserTypeEnum advertiserType, String state, String authCode) {
+        PromotionAccount byId = promotionAccountService.getById(state);
+        // 获取请求参数
+        IApiClient apiClient = advertiserHandlerFactory.getApiClient(advertiserType);
+        IAccessTokenClient tokenClient = (IAccessTokenClient) apiClient;
+        AccessTokenVo accessToken = tokenClient.getAccessTokenByAuthCode(AccessTokenByAuthCodeVo
+                .builder()
+                .userId(byId.getAdAccountId())
+                .appId(byId.getAppId())
+                .authCode(authCode)
+                .appSecret(byId.getAppSecret())
+                .appSecret(byId.getAppSecret())
+                .build());
+        if (ObjectUtil.isNotEmpty(accessToken)) {
+            byId.setAccessToken(accessToken.getAccessToken());
+            byId.setRefreshToken(accessToken.getRefreshToken());
+            byId.setExpireTime(accessToken.getExpireTime());
+            byId.setUpdateTime(LocalDateTime.now());
+            promotionAccountService.updateById(byId);
+        } else {
+            log.error("获取accessToken失败:{}", byId.getId());
+        }
+    }
+}

+ 51 - 0
fs-ad-new-api/src/main/java/com/fs/app/controller/LandingPageController.java

@@ -0,0 +1,51 @@
+package com.fs.app.controller;
+
+import com.fs.app.facade.CallbackProcessingFacadeService;
+import com.fs.common.result.Result;
+import com.fs.newAdv.dto.req.LandingIndexReq;
+import com.fs.newAdv.dto.req.WeChatLandingIndexReq;
+import com.fs.newAdv.dto.res.LandingIndexRes;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 落地页控制器
+ * 用于接收广告平台跳转时携带的参数
+ *
+ * @author zhangqin
+ * @date 2025-11-04
+ */
+@Slf4j
+@RestController
+@RequestMapping("/landing")
+public class LandingPageController {
+
+    @Autowired
+    private CallbackProcessingFacadeService facadeService;
+
+    /**
+     * 落地页访问
+     */
+    @PostMapping("/h5/home")
+    public Result<LandingIndexRes> track(
+            @RequestBody LandingIndexReq req) {
+        log.info("落地页访问追踪:req={}", req);
+        // 查询落地页模板
+        return Result.success(facadeService.getLandingIndexBySiteId(req.getAllParams()));
+    }
+
+    /**
+     * 小程序访问
+     */
+    @PostMapping("/mini/home")
+    public Result<LandingIndexRes> track(@RequestBody WeChatLandingIndexReq req) {
+        return Result.success(facadeService.getWxLandingIndexBySiteId(req));
+    }
+
+
+}
+

+ 42 - 0
fs-ad-new-api/src/main/java/com/fs/app/controller/TestController.java

@@ -0,0 +1,42 @@
+package com.fs.app.controller;
+
+import com.fs.newAdv.integration.client.advertiser.BaiduApiClient;
+import com.fs.newAdv.enums.SystemEventTypeEnum;
+import com.fs.newAdv.event.ConversionEventPublisher;
+import com.fs.newAdv.service.IPromotionAccountService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 广告商监测链接
+ *
+ * @author zhangqin
+ */
+@Slf4j
+@RestController
+@RequestMapping("/test")
+public class TestController {
+
+    @Autowired
+    private ConversionEventPublisher conversionEventPublisher;
+    @Autowired
+    private BaiduApiClient baiduApiClient;
+    @Autowired
+    private IPromotionAccountService promotionAccountService;
+
+    @GetMapping("/test1/{traceId}")
+    public void test1(@PathVariable("traceId") String traceId) {
+        log.info("模拟 当日加群 事件完成");
+        conversionEventPublisher.publishConversionEvent(traceId, SystemEventTypeEnum.GROUP_TODAY);
+    }
+
+    @GetMapping("/test2/{traceId}")
+    public void test2(@PathVariable("traceId") String traceId) {
+        log.info("模拟 当日加微 事件完成");
+        conversionEventPublisher.publishConversionEvent(traceId, SystemEventTypeEnum.WEI_CHAT_TODAY);
+    }
+}

+ 48 - 0
fs-ad-new-api/src/main/java/com/fs/app/controller/TrackingController.java

@@ -0,0 +1,48 @@
+package com.fs.app.controller;
+
+import com.fs.app.facade.CallbackProcessingFacadeService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.Map;
+
+/**
+ * 广告商监测链接
+ *
+ * @author zhangqin
+ */
+@Slf4j
+@RestController
+@RequestMapping("/track")
+public class TrackingController {
+
+    @Autowired
+    private CallbackProcessingFacadeService facadeService;
+
+
+    /**
+     * 监测链接端口
+     *
+     * @param allParams 所有URL参数
+     * @param response  HTTP响应
+     */
+    @GetMapping("/click/{advertiserCode}")
+    public void trackBaidu(
+            @RequestParam Map<String, String> allParams,
+            @PathVariable("advertiserCode") Long advertiserCode,
+            HttpServletResponse response) {
+        log.info("接收监测请求 | params={}", allParams);
+        try {
+            // 2. 保存点击追踪记录
+            facadeService.saveClickTrace(advertiserCode,allParams);
+            // 3. 返回 200 OK
+            response.setStatus(HttpServletResponse.SC_OK);
+        } catch (Exception e) {
+            log.error("监测请求处理失败 | params={}", allParams, e);
+            response.setStatus(HttpServletResponse.SC_OK);
+        }
+    }
+
+}

+ 67 - 0
fs-ad-new-api/src/main/java/com/fs/app/controller/WeChatController.java

@@ -0,0 +1,67 @@
+package com.fs.app.controller;
+
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.json.JSONUtil;
+import com.alibaba.fastjson.JSONObject;
+import com.fs.app.facade.CallbackProcessingFacadeService;
+import com.fs.common.constant.SystemConstant;
+import com.fs.common.result.Result;
+import com.fs.wx.miniapp.config.WxMaProperties;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 广告商监测链接
+ *
+ * @author zhangqin
+ */
+@Slf4j
+@RestController
+@RequestMapping("/scheme")
+public class WeChatController {
+
+    @Autowired
+    private CallbackProcessingFacadeService facadeService;
+
+    @Autowired
+    private WxMaProperties properties;
+
+    @GetMapping("/getSchemeUrl")
+    public Result<String> getSchemeUrl(@RequestParam(value = "traceId") String traceId) {
+        String appId = "wx0447a16ef6199f03";
+        String secret = "f063fcd818e31d4c89013a67f5123990";
+        HttpResponse execute2 = HttpRequest.get("https://api.weixin.qq.com/cgi-bin/token")
+                .form("grant_type", "client_credential")
+                .form("appid", appId)
+                .form("secret", secret)
+                .timeout(SystemConstant.API_TIMEOUT)
+                .execute();
+        JSONObject obj = JSONObject.parseObject(execute2.body());
+        String access_token = obj.getString("access_token");
+
+        Map<String, Object> map = new HashMap<>();
+        Map<String, Object> map2 = new HashMap<>();
+        map2.put("path", "/pages/shopping/productDetails");
+        map2.put("query", "traceId=" + traceId);
+        //map2.put("env_version", "trial");
+        map.put("jump_wxa", map2);
+        map.put("is_expire", false);
+        HttpResponse execute = HttpRequest.post("https://api.weixin.qq.com/wxa/generatescheme?access_token=" + access_token)
+                .header("Content-Type", "application/json")
+                .body(JSONUtil.toJsonStr(map))
+                .timeout(SystemConstant.API_TIMEOUT)
+                .execute();
+        log.info("getSchemeUrl:{}", execute.body());
+        obj = JSONObject.parseObject(execute.body());
+        //response.addHeader("Access-Control-Allow-Origin", "*");
+        return Result.success(obj.getString("openlink"));
+    }
+}

+ 29 - 0
fs-ad-new-api/src/main/java/com/fs/app/facade/CallbackProcessingFacadeService.java

@@ -0,0 +1,29 @@
+package com.fs.app.facade;
+
+import com.fs.newAdv.dto.req.WeChatLandingIndexReq;
+import com.fs.newAdv.dto.res.LandingIndexRes;
+
+import java.util.Map;
+
+public interface CallbackProcessingFacadeService {
+
+
+    /**
+     * 链化追踪点击
+     * @param allParams
+     */
+    void saveClickTrace(Long advertiserCode,Map<String,String> allParams);
+    /**
+     * 根据站点ID获取落地页信息
+     *
+     * @param siteId
+     * @return
+     */
+    LandingIndexRes getLandingIndexBySiteId(Map<String, String> allParams);
+
+    //----------------------code回调---------------------------------
+    void gdtGetAuthCode(Integer code, Long state);
+
+
+    LandingIndexRes getWxLandingIndexBySiteId(WeChatLandingIndexReq req);
+}

+ 287 - 0
fs-ad-new-api/src/main/java/com/fs/app/facade/CallbackProcessingFacadeServiceImpl.java

@@ -0,0 +1,287 @@
+package com.fs.app.facade;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.fs.newAdv.integration.adapter.IAdvertiserAdapter;
+import com.fs.newAdv.integration.factory.AdvertiserHandlerFactory;
+import com.fs.common.exception.base.BusinessException;
+import com.fs.common.utils.RedisUtil;
+import com.fs.newAdv.domain.LandingPageTemplate;
+import com.fs.newAdv.domain.Lead;
+import com.fs.newAdv.domain.Site;
+import com.fs.newAdv.dto.req.WeChatLandingIndexReq;
+import com.fs.newAdv.dto.res.LandingIndexRes;
+import com.fs.newAdv.enums.AdvertiserTypeEnum;
+import com.fs.newAdv.service.ILandingPageTemplateService;
+import com.fs.newAdv.service.ILeadService;
+import com.fs.newAdv.service.ISiteService;
+import com.fs.qw.domain.QwAssignRule;
+import com.fs.qw.domain.QwAssignRuleUser;
+import com.fs.qw.domain.QwContactWay;
+import com.fs.qw.domain.QwGroupActual;
+import com.fs.qw.service.IQwAssignRuleService;
+import com.fs.qw.service.IQwAssignRuleUserService;
+import com.fs.qw.service.IQwContactWayService;
+import com.fs.qw.service.IQwGroupActualService;
+import com.fs.qwApi.Result.QwAddContactWayResult;
+import com.fs.qwApi.param.QwAddContactWayParam;
+import com.fs.qwApi.service.QwApiService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+@Service
+@Slf4j
+public class CallbackProcessingFacadeServiceImpl implements CallbackProcessingFacadeService {
+    @Autowired
+    private AdvertiserHandlerFactory handlerFactory;
+    @Autowired
+    private ILeadService leadService;
+    @Autowired
+    private ISiteService siteService;
+    @Autowired
+    private IQwAssignRuleUserService assignRuleUserService;
+    @Autowired
+    private IQwAssignRuleService assignRuleService;
+    @Autowired
+    private ILandingPageTemplateService landingPageTemplateService;
+    @Autowired
+    private IQwGroupActualService actualService;
+    @Autowired
+    private QwApiService qwApiService;
+    @Autowired
+    private IQwContactWayService contactWayService;
+
+    @Autowired
+    private RedisUtil redisUtil;
+
+    private static final String TEMPLATE_DATA = "new-adv:template-data:";
+
+    @Override
+    public void saveClickTrace(Long advertiserCode, Map<String, String> allParams) {
+        IAdvertiserAdapter advertiserAdapter = handlerFactory.getAdapter(AdvertiserTypeEnum.getByCode(advertiserCode));
+        Lead lead = advertiserAdapter.adaptCallbackData(allParams);
+        Lead byTraceId = leadService.getByTraceId(lead.getTraceId());
+        if (ObjectUtil.isNotEmpty(byTraceId)) {
+            throw new BusinessException("监测信息已存在: " + lead.getTraceId());
+        }
+        lead.setStatus(0);
+        lead.setClickTrigger(1);
+        lead.setTraceRawParams(JSONUtil.toJsonStr(allParams));
+        boolean saved = leadService.save(lead);
+        if (!saved) {
+            log.error("线索保存失败:{}", lead);
+            throw new RuntimeException("线索保存失败");
+        }
+    }
+
+    private String getTraceIdByAdvertiser(AdvertiserTypeEnum byCode, Map<String, String> allParams) {
+        String traceId;
+        switch (byCode) {
+            case OCEANENGINE:
+            case TENCENT:
+            case OPPO:
+                traceId = allParams.get("click_id");
+                break;
+            case BAIDU:
+                traceId = allParams.get("bd_vid");
+                break;
+            case VIVO:
+                traceId = allParams.get("requestId");
+                break;
+            case IQIYI:
+                traceId = allParams.get("traceId");
+                break;
+            default:
+                traceId = "ylrz_test";
+        }
+        if (StrUtil.isEmpty(traceId)) {
+            traceId = "ylrz_test";
+        }
+
+        return traceId;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public LandingIndexRes getLandingIndexBySiteId(Map<String, String> allParams) {
+        // 站点信息
+        String paramsSiteId = allParams.get("siteId");
+        if (ObjectUtil.isEmpty(paramsSiteId)) {
+            log.info("站点id不存在:{}", paramsSiteId);
+            return null;
+        }
+        Long siteId = Long.valueOf(paramsSiteId);
+        Site byId = siteService.getById(siteId);
+        if (ObjectUtil.isEmpty(byId)) {
+            log.info("站点信息不存在:{}", siteId);
+            return null;
+        }
+        // 广告商信息
+        Long advertiserId = byId.getAdvertiserId();
+        // 访问链路id
+        String traceId = getTraceIdByAdvertiser(Objects.requireNonNull(AdvertiserTypeEnum.getByCode(advertiserId)), allParams);
+        // 线索信息
+        Lead byTraceId = leadService.getByTraceId(traceId);
+        boolean isNewLead = ObjectUtil.isEmpty(byTraceId);
+        if (isNewLead) {
+            IAdvertiserAdapter advertiserAdapter = handlerFactory.getAdapter(AdvertiserTypeEnum.getByCode(advertiserId));
+            byTraceId = advertiserAdapter.adaptCallbackData(allParams);
+            byTraceId.setAdvertiserId(advertiserId);
+            byTraceId.setSiteId(siteId);
+            byTraceId.setTraceId(traceId);
+            // 设置站点和落地页的关联
+            setSiteByIdeaId(siteId, byTraceId.getIdeaId());
+        } else {
+            // 检查站点和广告商信息是否异常
+            if (!Objects.equals(byTraceId.getSiteId(), siteId)) {
+                log.info("落地页站点信息异常:{}---{}", byTraceId.getSiteId(), siteId);
+            }
+            if (!Objects.equals(byTraceId.getAdvertiserId(), advertiserId)) {
+                log.info("落地页广告商信息异常:{}---{}", byTraceId.getAdvertiserId(), advertiserId);
+            }
+        }
+        // 模板缓存
+        Object ca = redisUtil.get(TEMPLATE_DATA + traceId);
+        String templateData;
+        if (ca != null) {
+            templateData = String.valueOf(ca);
+        } else {
+            // 查询模板数据
+            LandingPageTemplate landingPageTemplate = landingPageTemplateService.getById(byId.getLaunchPageId());
+            JSONObject jsonObject = JSONUtil.parseObj(landingPageTemplate.getTemplateData());
+            // 替换二维码链接
+            updateQrCodeInTemplate(jsonObject, traceId, byId, byTraceId);
+            templateData = JSONUtil.toJsonStr(jsonObject);
+        }
+
+
+        // 保存或更新 线索信息
+        LocalDateTime now = LocalDateTime.now();
+        byTraceId.setLandingPageRawParams(JSONUtil.toJsonStr(allParams));
+        byTraceId.setLandingPageTrigger(1);
+        byTraceId.setLandingPageTs(now);
+        byTraceId.setUpdateTime(now);
+        if (isNewLead) {
+            leadService.save(byTraceId);
+        } else {
+            leadService.updateById(byTraceId);
+        }
+
+        // 封装返回结果
+        LandingIndexRes res = new LandingIndexRes();
+        redisUtil.set(TEMPLATE_DATA + traceId, templateData, 24, TimeUnit.HOURS);
+        res.setTemplateData(templateData);
+        res.setTraceId(byTraceId.getTraceId());
+        return res;
+    }
+
+    private void setSiteByIdeaId(Long siteId, String ideaId) {
+        siteService.update(new LambdaUpdateWrapper<Site>()
+                .eq(Site::getId, siteId)
+                .set(Site::getIdeaId, ideaId));
+    }
+
+    /**
+     * 更新模板中的二维码信息
+     */
+    private void updateQrCodeInTemplate(JSONObject templateData, String traceId, Site site, Lead lead) {
+        JSONArray configList = templateData.getJSONArray("configList");
+        if (configList == null || configList.isEmpty()) {
+            return;
+        }
+        AtomicReference<String> qrCode = new AtomicReference<>("");
+        configList.stream()
+                .map(config -> (JSONObject) config)
+                .map(config -> config.getJSONArray("moduleList"))
+                .filter(ObjectUtil::isNotEmpty)
+                .flatMap(Collection::stream)
+                .map(module -> (JSONObject) module)
+                .filter(module -> "h5-qrcode".equals(module.getStr("type")))
+                .forEach(module -> {
+                    if (StrUtil.isEmpty(qrCode.get())) {
+                        qrCode.set(getQrCodeByAllocationRuleId(site.getLaunchType(), site.getAllocationRule(), site.getAllocationRuleId(), lead));
+                    }
+                    module.set("workUrl", qrCode.get());
+                });
+    }
+
+    private String getQrCodeByAllocationRuleId(Integer launchType,
+                                               Integer allocationRule,
+                                               Long allocationRuleId,
+                                               Lead byTraceId) {
+        // 二维码
+        String qrCode = "";
+        if (allocationRule == 1) {
+            // 个人分配规则
+            QwAssignRule byId1 = assignRuleService.getById(allocationRuleId);
+            QwAssignRuleUser qwAssignRuleUser = assignRuleUserService.getUserByAllocationRuleId(allocationRuleId, byId1.getAssignType());
+            byTraceId.setQwAssignRuleUserId(qwAssignRuleUser.getId());
+            if (byId1.getAssignType().equals(3)) {
+                List<String> users = Collections.singletonList(qwAssignRuleUser.getQwUserId());
+                QwAddContactWayParam qwAddContactWayParam = new QwAddContactWayParam();
+                qwAddContactWayParam.setType(1);
+                qwAddContactWayParam.setScene(2);
+                qwAddContactWayParam.setUser(users);
+                qwAddContactWayParam.setSkip_verify(true);
+                QwAddContactWayResult qwAddContactWayResult = qwApiService.addContactWay(qwAddContactWayParam, qwAssignRuleUser.getCorpId());
+                qrCode = qwAddContactWayResult.getQr_code();
+            }
+        } else if (allocationRule == 2) {
+            // 活码分配规则
+            if (launchType.equals(1)) {
+                // 线上规则--群活码
+                QwGroupActual qwGroupActual = actualService.getGroupActualByAllocationRuleId(allocationRuleId);
+                byTraceId.setQwGroupActualId(qwGroupActual.getId());
+                byTraceId.setQwGroupLiveCodeId(qwGroupActual.getLiveCodeId());
+                qrCode = qwGroupActual.getGroupUrl();
+            } else if (launchType.equals(2)) {
+                // 线下规则--个人活码
+                QwContactWay qwContactWay = contactWayService.selectQwContactWayById(allocationRuleId);
+                byTraceId.setQwContactWayId(qwContactWay.getId());
+                qrCode = qwContactWay.getQrCode();
+            }
+        }
+        log.info("落地页广告获取二维码: {}", qrCode);
+        return qrCode;
+    }
+
+    @Override
+    public void gdtGetAuthCode(Integer code, Long state) {
+   /*     if (code == null || state == null) {
+            return;
+        }
+        PromotionAccount byId = promotionAccountService.getById(state);
+        if (byId == null) {
+            return;
+        }
+        IApiClient apiClient = handlerFactory.getApiClient(AdvertiserTypeEnum.TENCENT);
+        if (apiClient instanceof IAccessTokenClient) {
+            IAccessTokenClient tokenClient = (IAccessTokenClient) apiClient;
+            Map<String, String> accessToken = tokenClient.getAccessToken(code, byId.getAppId(), byId.getAppSecret(), byId.getCallbackUrl());
+            byId.setAccessToken(accessToken.get("access_token"));
+            byId.setRefreshToken(accessToken.get("refresh_token"));
+        }*/
+    }
+
+    @Override
+    public LandingIndexRes getWxLandingIndexBySiteId(WeChatLandingIndexReq req) {
+        // 更新授权页访问信息记录
+        leadService.updateAuthIndex(req.getTraceId(),req.getType());
+        String templateData = String.valueOf(redisUtil.get(TEMPLATE_DATA + req.getTraceId()));
+        LandingIndexRes landingIndexRes = new LandingIndexRes();
+        landingIndexRes.setTemplateData(templateData);
+        return landingIndexRes;
+    }
+}

+ 172 - 0
fs-ad-new-api/src/main/java/com/fs/app/facade/ConversionServiceImpl.java

@@ -0,0 +1,172 @@
+package com.fs.app.facade;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.json.JSONUtil;
+import com.fs.newAdv.integration.adapter.IAdvertiserAdapter;
+import com.fs.newAdv.integration.client.IApiClient;
+import com.fs.newAdv.integration.factory.AdvertiserHandlerFactory;
+import com.fs.common.utils.RedisUtil;
+import com.fs.common.utils.SnowflakeUtil;
+import com.fs.newAdv.domain.*;
+import com.fs.newAdv.enums.AdvertiserTypeEnum;
+import com.fs.newAdv.enums.SystemEventTypeEnum;
+import com.fs.newAdv.mapper.ConversionLogMapper;
+import com.fs.newAdv.mapper.ConversionTargetMapper;
+import com.fs.newAdv.service.ICallbackAccountService;
+import com.fs.newAdv.service.ILeadService;
+import com.fs.newAdv.service.IPromotionAccountService;
+import com.fs.newAdv.service.ISiteService;
+import com.fs.newAdv.vo.ConversionEventVo;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 转化回传服务实现类
+ *
+ * @author zhangqin
+ * @date 2025-11-03
+ */
+@Slf4j
+@Service
+public class ConversionServiceImpl implements IConversionService {
+
+    @Autowired
+    private ISiteService siteService;
+    @Autowired
+    private ICallbackAccountService callbackAccountService;
+    @Autowired
+    private IPromotionAccountService promotionAccountService;
+
+
+    @Autowired
+    private ConversionLogMapper conversionLogMapper;
+
+    @Autowired
+    private ConversionTargetMapper conversionTargetMapper;
+
+    @Autowired
+    private AdvertiserHandlerFactory advertiserHandlerFactory;
+
+    @Autowired
+    private RedisUtil redisUtil;
+
+    @Autowired
+    private ILeadService leadService;
+
+    @Override
+    public boolean reportTrackingToAdvertiser(SystemEventTypeEnum systemEventTypeEnum, String traceId) {
+        // 线索信息
+        Lead lead = leadService.getByTraceId(traceId);
+        // 站点信息
+        Site site = siteService.getById(lead.getSiteId());
+        if (site == null) {
+            log.error("站点不存在:{}", site);
+            return false;
+        }
+        // 查询回传信息
+        CallbackAccount callbackAccount = callbackAccountService.getById(site.getCallbackAccountId());
+        // 回传事件
+        List<ConversionEventVo> conversionEventVo = callbackAccount.getConversionEventVo();
+        if (CollUtil.isEmpty(conversionEventVo)) {
+            log.error("回传事件不存在: {}", traceId);
+            return false;
+        }
+
+        ConversionEventVo advertiserEventType = conversionEventVo.stream()
+                .filter(item -> item.getSystemEventType().equals(systemEventTypeEnum.getCode()))
+                .findFirst()
+                .orElse(null);
+
+        if (ObjectUtil.isEmpty(advertiserEventType)) {
+            log.error("回传事件未匹配: {}", traceId);
+            return false;
+        }
+
+        // 查询广告商账号
+        PromotionAccount promotionAccount = promotionAccountService.getById(site.getPromotionAccountId());
+        if (promotionAccount == null) {
+            log.error("回传账号不存在:{}", site.getPromotionAccountId());
+            return false;
+        }
+
+        Map<String, Object> params = JSONUtil.toBean(lead.getTraceRawParams(), Map.class);
+        // 构建回传参数
+        Map<String, Object> conversionData = new HashMap<>();
+        // ------------------------------通用参数----------
+        conversionData.put("adAccountId", callbackAccount.getAdAccountId());
+        conversionData.put("appId", callbackAccount.getAppId());
+        conversionData.put("appSecret", callbackAccount.getAppSecret());
+        conversionData.put("accessToken", callbackAccount.getAccessToken());
+        conversionData.put("ip", lead.getIp());
+        conversionData.put("scr_id", callbackAccount.getScrId());
+        conversionData.put("traceId", traceId);
+        conversionData.put("eventType", advertiserEventType.getAdvertiserEventType());
+        conversionData.put("timestamp", System.currentTimeMillis() / 1000);
+        // 添加平台适应参数
+        IAdvertiserAdapter adapter = advertiserHandlerFactory.getAdapter(AdvertiserTypeEnum.getByCode(callbackAccount.getAdvertiserId()));
+        adapter.uploadConversionData(conversionData, params);
+
+
+        // 调用API回传
+        IApiClient apiClient = advertiserHandlerFactory.getApiClient(AdvertiserTypeEnum.getByCode(site.getAdvertiserId()));
+        boolean b = apiClient.reportConversion(conversionData);
+
+        // 保存转化日志(待回传状态)
+        ConversionLog conversionLog = saveConversionLog(
+                site.getCallbackAccountId(),
+                site.getId(), site.getAdvertiserId(), site.getAdvertiserName(),
+                advertiserEventType, traceId, conversionData, b);
+        return b;
+    }
+
+
+    /**
+     * 构建巨量引擎转化数据
+     */
+    private Map<String, Object> buildOceanEngineConversionData(
+            String clickId, String eventType, Double value) {
+
+  /*      Map<String, Object> data = new HashMap<>();
+        data.put("clickId", clickId);
+        data.put("eventType", mapEventType(eventType));
+
+        if (value != null && value > 0) {
+            data.put("value", value);
+        }*/
+
+        return null;
+    }
+
+    /**
+     * 保存转化日志
+     */
+    private ConversionLog saveConversionLog(Long callbackAccountId,Long siteId, Long advertiserId,
+                                            String advertiserName, ConversionEventVo eventType,
+                                            String clickId,
+                                            Map<String, Object> conversionData,
+                                            boolean status) {
+        ConversionLog log = new ConversionLog();
+        log.setId(SnowflakeUtil.nextId());
+        log.setCallbackAccountId(callbackAccountId);
+        log.setSiteId(siteId);
+        log.setAdvertiserId(advertiserId);
+        log.setAdvertiserName(advertiserName);
+        log.setSysConversionEvent(eventType.getSystemEventTypeName());
+        log.setAdvConversionEvent(eventType.getAdvertiserEventName());
+        log.setCallbackParams(JSONUtil.toJsonStr(conversionData));
+        log.setCallbackStatus(status ? 1 : 2);
+        log.setRetryCount(0);
+        log.setTraceId(clickId);
+        log.setCreateTime(LocalDateTime.now());
+        conversionLogMapper.insert(log);
+        return log;
+    }
+}
+

+ 23 - 0
fs-ad-new-api/src/main/java/com/fs/app/facade/IConversionService.java

@@ -0,0 +1,23 @@
+package com.fs.app.facade;
+
+
+import com.fs.newAdv.enums.SystemEventTypeEnum;
+
+/**
+ * 转化回传服务接口
+ *
+ * @author zhangqin
+ * @date 2025-11-03
+ * @updated 2025-11-05 使用clickId参数直接回传
+ */
+public interface IConversionService {
+    /**
+     * 链化追踪回传转化数据到广告商
+     *
+     * @param eventType 事件类型
+     * @param traceId   点击ID(广告平台提供)
+     * @return 是否成功
+     */
+    boolean reportTrackingToAdvertiser(SystemEventTypeEnum eventType, String traceId);
+}
+

+ 71 - 0
fs-ad-new-api/src/main/java/com/fs/app/mq/consumer/ConversionTrackingMessageConsumer.java

@@ -0,0 +1,71 @@
+package com.fs.app.mq.consumer;
+
+import com.fs.app.facade.IConversionService;
+import com.fs.common.annotation.DistributeLock;
+import com.fs.common.utils.RedisUtil;
+import com.fs.common.utils.TraceIdUtil;
+import com.fs.newAdv.constant.ConversionTrackingMessage;
+import com.fs.newAdv.constant.MqTopicConstant;
+import com.fs.newAdv.enums.SystemEventTypeEnum;
+import com.fs.newAdv.mapper.ConversionLogMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.rocketmq.spring.annotation.ConsumeMode;
+import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
+import org.apache.rocketmq.spring.core.RocketMQListener;
+import org.jboss.logging.MDC;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * 转化追踪
+ *
+ * @author zhangqin
+ */
+@Slf4j
+@Component
+@RocketMQMessageListener(
+        topic = MqTopicConstant.CONVERSION_TRACKING_TOPIC,
+        consumerGroup = MqTopicConstant.CONVERSION_TRACKING_TOPIC_CONSUMER_GROUP,
+        // 并发消费模式(多线程并发消费,线程数由RocketMQ自动管理)
+        consumeMode = ConsumeMode.CONCURRENTLY,
+        // 最大重试次数(RocketMQ默认16次)
+        maxReconsumeTimes = 16
+)
+public class ConversionTrackingMessageConsumer implements RocketMQListener<ConversionTrackingMessage> {
+
+    @Autowired
+    private IConversionService conversionService;
+
+    @Autowired
+    private RedisUtil redisUtil;
+
+    @Autowired
+    private ConversionLogMapper conversionLogMapper;
+
+    /**
+     * 消费转化消息
+     *
+     * @param message 转化消息
+     */
+    @Override
+    @DistributeLock(scene = "mq", keyExpression = "#message.traceId", waitTime = 0, errorMsg = "重复消费")
+    public void onMessage(ConversionTrackingMessage message) {
+        TraceIdUtil.put(message.getTrackId());
+
+        String traceId = message.getTraceId();
+        SystemEventTypeEnum eventType = message.getEventType();
+
+        log.info("转化追踪消息开始消费 | traceId={}, eventType={}",
+                traceId, eventType.getDescription());
+
+        //  调用广告平台API回传
+        boolean success = conversionService.reportTrackingToAdvertiser(eventType, traceId);
+
+        if (success) {
+            log.info("转化回传成功 | traceId={}, eventType={}", traceId, eventType);
+        } else {
+            log.info("转化回传失败 | traceId={}, eventType={}", traceId, eventType);
+        }
+    }
+}
+

+ 91 - 0
fs-ad-new-api/src/main/java/com/fs/app/task/ConversionRetryTask.java

@@ -0,0 +1,91 @@
+package com.fs.app.task;
+
+
+import cn.hutool.json.JSONUtil;
+import com.fs.common.annotation.DistributeLock;
+import com.fs.common.constant.SystemConstant;
+import com.fs.newAdv.domain.ConversionLog;
+import com.fs.newAdv.enums.AdvertiserTypeEnum;
+import com.fs.newAdv.integration.client.IApiClient;
+import com.fs.newAdv.integration.factory.AdvertiserHandlerFactory;
+import com.fs.newAdv.mapper.ConversionLogMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 转化回传重试定时任务
+ * 每10分钟执行一次,重试失败的转化回传
+ *
+ * @author zhangqin
+ * @date 2025-11-03
+ */
+@Slf4j
+@Component
+public class ConversionRetryTask {
+
+    @Autowired
+    private ConversionLogMapper conversionLogMapper;
+    @Autowired
+    private AdvertiserHandlerFactory advertiserHandlerFactory;
+
+    /**
+     * 转化回传重试任务
+     * cron: 每10分钟执行
+     */
+    @Scheduled(cron = "0 */5 * * * ?")
+    @DistributeLock(scene = "task", key = "conversion_retry", waitTime = 0, errorMsg = "conversion_retry任务已执行")
+    public void execute() {
+        // 查询待重试的转化记录
+        List<ConversionLog> pendingList = conversionLogMapper.selectPendingConversions();
+        log.info("查询到 {} 条待重试的转化记录", pendingList.size());
+
+        int successCount = 0;
+        int failCount = 0;
+
+        for (ConversionLog conversionLog : pendingList) {
+            try {
+                // 重试回传
+                boolean success = retryConversion(conversionLog);
+                if (success) {
+                    successCount++;
+                } else {
+                    failCount++;
+                }
+            } catch (Exception e) {
+                log.error("重试转化回传失败,ID:{}", conversionLog.getId(), e);
+                failCount++;
+            }
+        }
+        log.info("========== 转化回传重试任务完成,成功:{},失败:{} ==========", successCount, failCount);
+    }
+
+    /**
+     * 重试转化回传
+     */
+    private boolean retryConversion(ConversionLog conversionLog) {
+        log.info("重试转化回传,ID:{},重试次数:{}", conversionLog.getId(), conversionLog.getRetryCount());
+        IApiClient apiClient = advertiserHandlerFactory.getApiClient(AdvertiserTypeEnum.getByCode(conversionLog.getAdvertiserId()));
+        boolean b = apiClient.reportConversion(JSONUtil.parseObj(conversionLog.getCallbackParams()));
+        if (b) {
+            conversionLog.setCallbackStatus(1);
+            conversionLog.setSuccessTime(LocalDateTime.now());
+        }
+        // 更新重试次数
+        conversionLog.setRetryCount(conversionLog.getRetryCount() + 1);
+
+        // 如果重试次数超过最大值,标记为失败
+        if (conversionLog.getRetryCount() >= SystemConstant.MAX_RETRY_COUNT) {
+            conversionLog.setCallbackStatus(2); // 失败
+            log.warn("转化回传重试次数已达上限,ID:{}", conversionLog.getId());
+        }
+        conversionLog.setUpdateTime(LocalDateTime.now());
+        conversionLogMapper.updateById(conversionLog);
+        return b;
+    }
+}
+

+ 225 - 0
fs-ad-new-api/src/main/java/com/fs/app/task/DataSyncTask.java

@@ -0,0 +1,225 @@
+package com.fs.app.task;
+
+import cn.hutool.core.date.DateUtil;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.fs.common.annotation.DistributeLock;
+import com.fs.newAdv.domain.Lead;
+import com.fs.newAdv.service.ILeadService;
+import com.fs.newAdv.service.ISiteStatisticsService;
+import com.fs.qw.domain.QwAssignRuleUser;
+import com.fs.qw.domain.QwGroupLiveCode;
+import com.fs.qw.service.IQwAssignRuleUserService;
+import com.fs.qw.service.IQwGroupLiveCodeService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * 数据同步定时任务
+ * 每2小时执行一次,拉取各平台账户数据
+ *
+ * @author zhangqin
+ * @date 2025-11-03
+ */
+@Slf4j
+@Component
+public class DataSyncTask {
+
+
+    @Autowired
+    private ISiteStatisticsService statisticsService;
+    @Autowired
+    private ILeadService leadService;
+    @Autowired
+    private IQwGroupLiveCodeService qwGroupLiveCodeService;
+    @Autowired
+    private IQwAssignRuleUserService qwAssignRuleUserService;
+
+
+    /**
+     * 数据同步任务->同步昨日数据
+     * cron: 每天00:10
+     */
+    @Scheduled(cron = "0 10 0 * * ?")
+    @DistributeLock(scene = "task", key = "sync_yesterday_data", waitTime = 0, errorMsg = "sync_yesterday_data任务已执行")
+    public void syncYesterdayData() {
+        String batchNo = DateUtil.format(LocalDateTime.now().minusDays(1), "yyyy-MM-dd");
+        statisticsService.syncData(batchNo, 1);
+    }
+
+    /**
+     * 数据同步任务->当日数据
+     * cron: 每1小时统计站点数据
+     */
+    @Scheduled(cron = "0 0/1 * * * ?")
+    @DistributeLock(scene = "task", key = "sync_today_data", waitTime = 0, errorMsg = "sync_today_data任务已执行")
+    public void syncTodayData() throws InterruptedException {
+        String batchNo = DateUtil.format(LocalDateTime.now(), "yyyy-MM-dd");
+        statisticsService.syncData(batchNo, 1);
+    }
+
+
+    /**
+     * 今日加群数据
+     * cron: 每1小时统计站点数据
+     */
+    @Scheduled(cron = "0 0 0/1 * * ?")
+    @DistributeLock(scene = "task", key = "wei_chat_group_to_day_data", waitTime = 0, errorMsg = "wei_chat_group_to_day_data任务已执行")
+    public void weiChatGroupToDayData() {
+        // 统计今日加群数量
+        Optional.ofNullable(leadService.getToDayGroupNum())
+                .orElse(Collections.emptyList())
+                .stream()
+                .map(Lead::getQwGroupLiveCodeId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.groupingBy(
+                        Function.identity(),
+                        Collectors.counting()
+                ))
+                .forEach((k, v) -> {
+                    if (v > 0) {
+                        qwGroupLiveCodeService.update(
+                                new LambdaUpdateWrapper<QwGroupLiveCode>()
+                                        .eq(QwGroupLiveCode::getId, k)
+                                        .set(QwGroupLiveCode::getToDayNum, v)
+                        );
+                    }
+                });
+    }
+
+    /**
+     * 累计加群数据
+     * cron: 每天00:20
+     */
+    @Scheduled(cron = "0 20 0 * * ?")
+    @DistributeLock(scene = "task", key = "wei_chat_group_count_data", waitTime = 0, errorMsg = "wei_chat_group_count_data任务已执行")
+    public void weiChatGroupCountData() {
+        // 统计累积加群数量
+        Optional.ofNullable(leadService.getYesterdayGroupNum())
+                .orElse(Collections.emptyList())
+                .stream()
+                .map(Lead::getQwGroupLiveCodeId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.groupingBy(
+                        Function.identity(),
+                        Collectors.counting()
+                ))
+                .forEach((k, v) -> {
+                    if (v > 0) {
+                        QwGroupLiveCode byId = qwGroupLiveCodeService.getById(k);
+                        byId.setCountNum(byId.getCountNum() + v.intValue());
+                        qwGroupLiveCodeService.updateById(byId);
+                    }
+                });
+    }
+
+
+    /**
+     * 微信当天数据
+     * cron: 一小时执行一次
+     */
+    @Scheduled(cron = "0 0 0/1 * * ?")
+    @DistributeLock(scene = "task", key = "wei_chat_to_day_data", waitTime = 0, errorMsg = "wei_chat_to_day_data任务已执行")
+    public void weiChatToDayData() {
+        // 统计累积加微数量
+        Optional.ofNullable(leadService.getToDayWeiChatNum())
+                .ifPresent(item -> {
+                            // 统计分配数
+                            item.stream()
+                                    .map(Lead::getQwAssignRuleUserId)
+                                    .filter(Objects::nonNull)
+                                    .collect(Collectors.groupingBy(
+                                            Function.identity(),
+                                            Collectors.counting()
+                                    ))
+                                    .forEach((k, v) -> {
+                                        if (v > 0) {
+                                            qwAssignRuleUserService.update(
+                                                    new LambdaUpdateWrapper<QwAssignRuleUser>()
+                                                            .eq(QwAssignRuleUser::getId, k)
+                                                            .set(QwAssignRuleUser::getAssignNumToDay, v)
+                                            );
+                                        }
+                                    });
+                            // 统计添加数
+                            item.stream()
+                                    .filter(data -> data.getAddContactQw().equals(1))
+                                    .map(Lead::getQwAssignRuleUserId)
+                                    .filter(Objects::nonNull)
+                                    .collect(Collectors.groupingBy(
+                                            Function.identity(),
+                                            Collectors.counting()
+                                    ))
+                                    .forEach((k, v) -> {
+                                        if (v > 0) {
+                                            qwAssignRuleUserService.update(
+                                                    new LambdaUpdateWrapper<QwAssignRuleUser>()
+                                                            .eq(QwAssignRuleUser::getId, k)
+                                                            .set(QwAssignRuleUser::getAddNumToDay, v)
+                                            );
+                                        }
+                                    });
+                        }
+                );
+    }
+
+    /**
+     * 微信累计数据
+     * cron: 每天00:30
+     */
+    @Scheduled(cron = "0 30 0 * * ?")
+    @DistributeLock(scene = "task", key = "wei_chat_count_data", waitTime = 0, errorMsg = "wei_chat_count_data任务已执行")
+    public void weiChatCountData() {
+        // 统计累积加微数量
+        Optional.ofNullable(leadService.getYesterdayWeiChatNum())
+                .ifPresent(item -> {
+                            // 统计分配数
+                            item.stream()
+                                    .map(Lead::getQwAssignRuleUserId)
+                                    .filter(Objects::nonNull)
+                                    .collect(Collectors.groupingBy(
+                                            Function.identity(),
+                                            Collectors.counting()
+                                    ))
+                                    .forEach((k, v) -> {
+                                        if (v > 0) {
+                                            QwAssignRuleUser byId = qwAssignRuleUserService.getById(k);
+                                            qwAssignRuleUserService.update(
+                                                    new LambdaUpdateWrapper<QwAssignRuleUser>()
+                                                            .eq(QwAssignRuleUser::getId, k)
+                                                            .set(QwAssignRuleUser::getAssignNumCount, v + byId.getAssignNumCount())
+                                            );
+                                        }
+                                    });
+                            // 统计添加数
+                            item.stream()
+                                    .filter(data -> data.getAddContactQw().equals(1))
+                                    .map(Lead::getQwAssignRuleUserId)
+                                    .filter(Objects::nonNull)
+                                    .collect(Collectors.groupingBy(
+                                            Function.identity(),
+                                            Collectors.counting()
+                                    ))
+                                    .forEach((k, v) -> {
+                                        if (v > 0) {
+                                            QwAssignRuleUser byId = qwAssignRuleUserService.getById(k);
+                                            qwAssignRuleUserService.update(
+                                                    new LambdaUpdateWrapper<QwAssignRuleUser>()
+                                                            .eq(QwAssignRuleUser::getId, k)
+                                                            .set(QwAssignRuleUser::getAddNumCount, v + byId.getAddNumCount())
+                                            );
+                                        }
+                                    });
+                        }
+                );
+    }
+}
+

+ 182 - 0
fs-ad-new-api/src/main/java/com/fs/framework/aspectj/DataScopeAspect.java

@@ -0,0 +1,182 @@
+package com.fs.framework.aspectj;
+
+import com.fs.common.annotation.DataScope;
+import com.fs.common.core.domain.BaseEntity;
+import com.fs.common.core.domain.entity.SysRole;
+import com.fs.common.core.domain.entity.SysUser;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.utils.SecurityUtils;
+import com.fs.common.utils.StringUtils;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.Signature;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Method;
+
+/**
+ * 数据过滤处理
+ *
+
+ */
+@Aspect
+@Component
+public class DataScopeAspect
+{
+    /**
+     * 全部数据权限
+     */
+    public static final String DATA_SCOPE_ALL = "1";
+
+    /**
+     * 自定数据权限
+     */
+    public static final String DATA_SCOPE_CUSTOM = "2";
+
+    /**
+     * 部门数据权限
+     */
+    public static final String DATA_SCOPE_DEPT = "3";
+
+    /**
+     * 部门及以下数据权限
+     */
+    public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
+
+    /**
+     * 仅本人数据权限
+     */
+    public static final String DATA_SCOPE_SELF = "5";
+
+    /**
+     * 数据权限过滤关键字
+     */
+    public static final String DATA_SCOPE = "dataScope";
+
+    // 配置织入点
+    @Pointcut("@annotation(com.fs.common.annotation.DataScope)")
+    public void dataScopePointCut()
+    {
+    }
+
+    @Before("dataScopePointCut()")
+    public void doBefore(JoinPoint point) throws Throwable
+    {
+        clearDataScope(point);
+        handleDataScope(point);
+    }
+
+    protected void handleDataScope(final JoinPoint joinPoint)
+    {
+        // 获得注解
+        DataScope controllerDataScope = getAnnotationLog(joinPoint);
+        if (controllerDataScope == null)
+        {
+            return;
+        }
+        // 获取当前的用户
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        if (StringUtils.isNotNull(loginUser))
+        {
+            SysUser currentUser = loginUser.getUser();
+            // 如果是超级管理员,则不过滤数据
+            if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin())
+            {
+                dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
+                        controllerDataScope.userAlias());
+            }
+        }
+    }
+
+    /**
+     * 数据范围过滤
+     *
+     * @param joinPoint 切点
+     * @param user 用户
+     * @param userAlias 别名
+     */
+    public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias)
+    {
+        StringBuilder sqlString = new StringBuilder();
+
+        for (SysRole role : user.getRoles())
+        {
+            String dataScope = role.getDataScope();
+            if (DATA_SCOPE_ALL.equals(dataScope))
+            {
+                sqlString = new StringBuilder();
+                break;
+            }
+            else if (DATA_SCOPE_CUSTOM.equals(dataScope))
+            {
+                sqlString.append(StringUtils.format(
+                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias,
+                        role.getRoleId()));
+            }
+            else if (DATA_SCOPE_DEPT.equals(dataScope))
+            {
+                sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
+            }
+            else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
+            {
+                sqlString.append(StringUtils.format(
+                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
+                        deptAlias, user.getDeptId(), user.getDeptId()));
+            }
+            else if (DATA_SCOPE_SELF.equals(dataScope))
+            {
+                if (StringUtils.isNotBlank(userAlias))
+                {
+                    sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
+                }
+                else
+                {
+                    // 数据权限为仅本人且没有userAlias别名不查询任何数据
+                    sqlString.append(" OR 1=0 ");
+                }
+            }
+        }
+
+        if (StringUtils.isNotBlank(sqlString.toString()))
+        {
+            Object params = joinPoint.getArgs()[0];
+            if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
+            {
+                BaseEntity baseEntity = (BaseEntity) params;
+                baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
+            }
+        }
+    }
+
+    /**
+     * 是否存在注解,如果存在就获取
+     */
+    private DataScope getAnnotationLog(JoinPoint joinPoint)
+    {
+        Signature signature = joinPoint.getSignature();
+        MethodSignature methodSignature = (MethodSignature) signature;
+        Method method = methodSignature.getMethod();
+
+        if (method != null)
+        {
+            return method.getAnnotation(DataScope.class);
+        }
+        return null;
+    }
+
+    /**
+     * 拼接权限sql前先清空params.dataScope参数防止注入
+     */
+    private void clearDataScope(final JoinPoint joinPoint)
+    {
+        Object params = joinPoint.getArgs()[0];
+        if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
+        {
+            BaseEntity baseEntity = (BaseEntity) params;
+            baseEntity.getParams().put(DATA_SCOPE, "");
+        }
+    }
+}

+ 73 - 0
fs-ad-new-api/src/main/java/com/fs/framework/aspectj/DataSourceAspect.java

@@ -0,0 +1,73 @@
+package com.fs.framework.aspectj;
+
+import com.fs.common.annotation.DataSource;
+import com.fs.common.utils.StringUtils;
+import com.fs.framework.datasource.DynamicDataSourceContextHolder;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+import java.util.Objects;
+
+/**
+ * 多数据源处理
+ * 
+
+ */
+@Aspect
+@Order(1)
+@Component
+public class DataSourceAspect
+{
+    protected Logger logger = LoggerFactory.getLogger(getClass());
+
+    @Pointcut("@annotation(com.fs.common.annotation.DataSource)"
+            + "|| @within(com.fs.common.annotation.DataSource)")
+    public void dsPointCut()
+    {
+
+    }
+
+    @Around("dsPointCut()")
+    public Object around(ProceedingJoinPoint point) throws Throwable
+    {
+        DataSource dataSource = getDataSource(point);
+
+        if (StringUtils.isNotNull(dataSource))
+        {
+            DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
+        }
+
+        try
+        {
+            return point.proceed();
+        }
+        finally
+        {
+            // 销毁数据源 在执行方法之后
+            DynamicDataSourceContextHolder.clearDataSourceType();
+        }
+    }
+
+    /**
+     * 获取需要切换的数据源
+     */
+    public DataSource getDataSource(ProceedingJoinPoint point)
+    {
+        MethodSignature signature = (MethodSignature) point.getSignature();
+        DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
+        if (Objects.nonNull(dataSource))
+        {
+            return dataSource;
+        }
+
+        return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
+    }
+}

+ 117 - 0
fs-ad-new-api/src/main/java/com/fs/framework/aspectj/DistributeLockAspect.java

@@ -0,0 +1,117 @@
+package com.fs.framework.aspectj;
+
+import com.fs.common.annotation.DistributeLock;
+import com.fs.common.constant.DistributeLockConstant;
+import com.fs.common.exception.DistributeLockException;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.StandardReflectionParameterNameDiscoverer;
+import org.springframework.core.annotation.Order;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.Expression;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.TimeUnit;
+
+@Aspect
+@Component
+@Order(Integer.MIN_VALUE + 1)
+public class DistributeLockAspect {
+
+    private RedissonClient redissonClient;
+
+    public DistributeLockAspect(RedissonClient redissonClient) {
+        this.redissonClient = redissonClient;
+    }
+
+    private static final Logger LOG = LoggerFactory.getLogger(DistributeLockAspect.class);
+
+    @Around("@annotation(com.fs.common.annotation.DistributeLock)")
+    public Object process(ProceedingJoinPoint pjp) throws Throwable {
+        Object response = null;
+        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
+        DistributeLock distributeLock = method.getAnnotation(DistributeLock.class);
+
+        String key = distributeLock.key();
+        if (DistributeLockConstant.NONE_KEY.equals(key)) {
+            if (DistributeLockConstant.NONE_KEY.equals(distributeLock.keyExpression())) {
+                throw new DistributeLockException("no lock key found...");
+            }
+            SpelExpressionParser parser = new SpelExpressionParser();
+            Expression expression = parser.parseExpression(distributeLock.keyExpression());
+
+            EvaluationContext context = new StandardEvaluationContext();
+            // 获取参数值
+            Object[] args = pjp.getArgs();
+
+            // 获取运行时参数的名称
+            StandardReflectionParameterNameDiscoverer discoverer
+                    = new StandardReflectionParameterNameDiscoverer();
+            String[] parameterNames = discoverer.getParameterNames(method);
+
+            // 将参数绑定到context中
+            if (parameterNames != null) {
+                for (int i = 0; i < parameterNames.length; i++) {
+                    context.setVariable(parameterNames[i], args[i]);
+                }
+            }
+
+            // 解析表达式,获取结果
+            key = String.valueOf(expression.getValue(context));
+        }
+
+        String scene = distributeLock.scene();
+
+        String lockKey = scene + "#" + key;
+
+        int expireTime = distributeLock.expireTime();
+        int waitTime = distributeLock.waitTime();
+        RLock rLock= redissonClient.getLock(lockKey);
+        try {
+            boolean lockResult = false;
+            if (waitTime == DistributeLockConstant.DEFAULT_WAIT_TIME) {
+                if (expireTime == DistributeLockConstant.DEFAULT_EXPIRE_TIME) {
+                    LOG.info(String.format("lock for key : %s", lockKey));
+                    rLock.lock();
+                } else {
+                    LOG.info(String.format("lock for key : %s , expire : %s", lockKey, expireTime));
+                    rLock.lock(expireTime, TimeUnit.MILLISECONDS);
+                }
+                lockResult = true;
+            } else {
+                if (expireTime == DistributeLockConstant.DEFAULT_EXPIRE_TIME) {
+                    LOG.info(String.format("try lock for key : %s , wait : %s", lockKey, waitTime));
+                    lockResult = rLock.tryLock(waitTime, TimeUnit.MILLISECONDS);
+                } else {
+                    LOG.info(String.format("try lock for key : %s , expire : %s , wait : %s", lockKey, expireTime, waitTime));
+                    lockResult = rLock.tryLock(waitTime, expireTime, TimeUnit.MILLISECONDS);
+                }
+            }
+
+            if (!lockResult) {
+                LOG.warn(String.format("lock failed for key : %s , expire : %s", lockKey, expireTime));
+                throw new DistributeLockException(distributeLock.errorMsg());
+            }
+
+
+            LOG.info(String.format("lock success for key : %s , expire : %s", lockKey, expireTime));
+            response = pjp.proceed();
+        }  finally {
+            if (rLock.isHeldByCurrentThread()) {
+                rLock.unlock();
+                LOG.info(String.format("unlock for key : %s , expire : %s", lockKey, expireTime));
+            }
+        }
+        return response;
+    }
+}

+ 244 - 0
fs-ad-new-api/src/main/java/com/fs/framework/aspectj/LogAspect.java

@@ -0,0 +1,244 @@
+package com.fs.framework.aspectj;
+
+import com.alibaba.fastjson.JSON;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.enums.BusinessStatus;
+import com.fs.common.enums.HttpMethod;
+import com.fs.common.utils.SecurityUtils;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.ip.IpUtils;
+import com.fs.framework.manager.AsyncManager;
+import com.fs.framework.manager.factory.AsyncFactory;
+import com.fs.system.domain.SysOperLog;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.Signature;
+import org.aspectj.lang.annotation.AfterReturning;
+import org.aspectj.lang.annotation.AfterThrowing;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.servlet.HandlerMapping;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * 操作日志记录处理
+ *
+
+ */
+@Aspect
+@Component
+public class LogAspect
+{
+    private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
+
+    // 配置织入点
+    @Pointcut("@annotation(com.fs.common.annotation.Log)")
+    public void logPointCut()
+    {
+    }
+
+    /**
+     * 处理完请求后执行
+     *
+     * @param joinPoint 切点
+     */
+    @AfterReturning(pointcut = "logPointCut()", returning = "jsonResult")
+    public void doAfterReturning(JoinPoint joinPoint, Object jsonResult)
+    {
+        handleLog(joinPoint, null, jsonResult);
+    }
+
+    /**
+     * 拦截异常操作
+     *
+     * @param joinPoint 切点
+     * @param e 异常
+     */
+    @AfterThrowing(value = "logPointCut()", throwing = "e")
+    public void doAfterThrowing(JoinPoint joinPoint, Exception e)
+    {
+        handleLog(joinPoint, e, null);
+    }
+
+    protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult)
+    {
+        try
+        {
+            // 获得注解
+            Log controllerLog = getAnnotationLog(joinPoint);
+            if (controllerLog == null)
+            {
+                return;
+            }
+
+            // 获取当前的用户
+            LoginUser loginUser = SecurityUtils.getLoginUser();
+
+            // *========数据库日志=========*//
+            SysOperLog operLog = new SysOperLog();
+            operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
+            // 请求的地址
+            String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
+            operLog.setOperIp(ip);
+            // 返回参数
+            operLog.setJsonResult(JSON.toJSONString(jsonResult));
+
+            operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
+            if (loginUser != null)
+            {
+                operLog.setOperName(loginUser.getUsername());
+            }
+
+            if (e != null)
+            {
+                operLog.setStatus(BusinessStatus.FAIL.ordinal());
+                operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
+            }
+            // 设置方法名称
+            String className = joinPoint.getTarget().getClass().getName();
+            String methodName = joinPoint.getSignature().getName();
+            operLog.setMethod(className + "." + methodName + "()");
+            // 设置请求方式
+            operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
+            // 处理设置注解上的参数
+            getControllerMethodDescription(joinPoint, controllerLog, operLog);
+            // 保存数据库
+            AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
+        }
+        catch (Exception exp)
+        {
+            // 记录本地异常日志
+            log.error("==前置通知异常==");
+            log.error("异常信息:{}", exp.getMessage());
+            exp.printStackTrace();
+        }
+    }
+
+    /**
+     * 获取注解中对方法的描述信息 用于Controller层注解
+     *
+     * @param log 日志
+     * @param operLog 操作日志
+     * @throws Exception
+     */
+    public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog) throws Exception
+    {
+        // 设置action动作
+        operLog.setBusinessType(log.businessType().ordinal());
+        // 设置标题
+        operLog.setTitle(log.title());
+        // 设置操作人类别
+        operLog.setOperatorType(log.operatorType().ordinal());
+        // 是否需要保存request,参数和值
+        if (log.isSaveRequestData())
+        {
+            // 获取参数的信息,传入到数据库中。
+            setRequestValue(joinPoint, operLog);
+        }
+    }
+
+    /**
+     * 获取请求的参数,放到log中
+     *
+     * @param operLog 操作日志
+     * @throws Exception 异常
+     */
+    private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog) throws Exception
+    {
+        String requestMethod = operLog.getRequestMethod();
+        if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))
+        {
+            String params = argsArrayToString(joinPoint.getArgs());
+            operLog.setOperParam(StringUtils.substring(params, 0, 2000));
+        }
+        else
+        {
+            Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
+            operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000));
+        }
+    }
+
+    /**
+     * 是否存在注解,如果存在就获取
+     */
+    private Log getAnnotationLog(JoinPoint joinPoint) throws Exception
+    {
+        Signature signature = joinPoint.getSignature();
+        MethodSignature methodSignature = (MethodSignature) signature;
+        Method method = methodSignature.getMethod();
+
+        if (method != null)
+        {
+            return method.getAnnotation(Log.class);
+        }
+        return null;
+    }
+
+    /**
+     * 参数拼装
+     */
+    private String argsArrayToString(Object[] paramsArray)
+    {
+        String params = "";
+        if (paramsArray != null && paramsArray.length > 0)
+        {
+            for (int i = 0; i < paramsArray.length; i++)
+            {
+                if (StringUtils.isNotNull(paramsArray[i]) && !isFilterObject(paramsArray[i]))
+                {
+                    Object jsonObj = JSON.toJSON(paramsArray[i]);
+                    params += jsonObj.toString() + " ";
+                }
+            }
+        }
+        return params.trim();
+    }
+
+    /**
+     * 判断是否需要过滤的对象。
+     *
+     * @param o 对象信息。
+     * @return 如果是需要过滤的对象,则返回true;否则返回false。
+     */
+    @SuppressWarnings("rawtypes")
+    public boolean isFilterObject(final Object o)
+    {
+        Class<?> clazz = o.getClass();
+        if (clazz.isArray())
+        {
+            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
+        }
+        else if (Collection.class.isAssignableFrom(clazz))
+        {
+            Collection collection = (Collection) o;
+            for (Iterator iter = collection.iterator(); iter.hasNext();)
+            {
+                return iter.next() instanceof MultipartFile;
+            }
+        }
+        else if (Map.class.isAssignableFrom(clazz))
+        {
+            Map map = (Map) o;
+            for (Iterator iter = map.entrySet().iterator(); iter.hasNext();)
+            {
+                Map.Entry entry = (Map.Entry) iter.next();
+                return entry.getValue() instanceof MultipartFile;
+            }
+        }
+        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
+                || o instanceof BindingResult;
+    }
+}

+ 58 - 0
fs-ad-new-api/src/main/java/com/fs/framework/aspectj/ScheduledTaskAspect.java

@@ -0,0 +1,58 @@
+package com.fs.framework.aspectj;
+
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StopWatch;
+
+/**
+ * 定时任务执行时间切面
+ * 记录所有定时任务的执行时间和状态
+ *
+ * @author zhangqin
+ * @date 2025-12-15
+ */
+@Slf4j
+@Aspect
+@Component
+public class ScheduledTaskAspect {
+
+    @Pointcut("@annotation(org.springframework.scheduling.annotation.Scheduled) && execution(* com.fs.app.task..*(..))")
+    public void scheduledTaskPointcut() {
+    }
+
+    @Around("scheduledTaskPointcut()")
+    public Object aroundScheduledTask(ProceedingJoinPoint joinPoint) throws Throwable {
+        String taskName = getTaskName(joinPoint);
+        StopWatch stopWatch = new StopWatch(taskName);
+        stopWatch.start();
+
+        try {
+            Object result = joinPoint.proceed();
+            stopWatch.stop();
+            long executionTime = stopWatch.getTotalTimeMillis();
+            log.info("任务执行耗时: {} {}", taskName, executionTime);
+            return result;
+        } catch (Exception e) {
+            stopWatch.stop();
+            long executionTime = stopWatch.getTotalTimeMillis();
+            log.error("==================== 定时任务执行失败 ====================");
+            log.error("任务名称: [{}]", taskName);
+            log.error("执行耗时: [{}ms]", executionTime);
+            log.error("执行状态: [失败]");
+            log.error("错误信息: [{}]", e.getMessage(), e);
+            throw e;
+        }
+    }
+
+    /**
+     * 获取任务名称
+     */
+    private String getTaskName(ProceedingJoinPoint joinPoint) {
+        return joinPoint.getSignature().getName();
+    }
+}
+

+ 29 - 0
fs-ad-new-api/src/main/java/com/fs/framework/aspectj/ScheduledTraceIdAspect.java

@@ -0,0 +1,29 @@
+package com.fs.framework.aspectj;
+
+import com.fs.common.utils.TraceIdUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.springframework.stereotype.Component;
+
+@Aspect
+@Component
+@Slf4j
+public class ScheduledTraceIdAspect {
+
+    @Pointcut("@annotation(org.springframework.scheduling.annotation.Scheduled)")
+    public void scheduledPointcut() {
+    }
+
+    @Around("scheduledPointcut()")
+    public Object around(ProceedingJoinPoint pjp) throws Throwable {
+        try {
+            TraceIdUtil.init();
+            return pjp.proceed();
+        } finally {
+            TraceIdUtil.clear();
+        }
+    }
+}

+ 31 - 0
fs-ad-new-api/src/main/java/com/fs/framework/config/ApplicationConfig.java

@@ -0,0 +1,31 @@
+package com.fs.framework.config;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+
+import java.util.TimeZone;
+
+/**
+ * 程序注解配置
+ *
+
+ */
+@Configuration
+// 表示通过aop框架暴露该代理对象,AopContext能够访问
+@EnableAspectJAutoProxy(exposeProxy = true)
+// 指定要扫描的Mapper类的包的路径
+@MapperScan("com.fs.**.mapper")
+public class ApplicationConfig
+{
+    /**
+     * 时区配置
+     */
+    @Bean
+    public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization()
+    {
+        return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault());
+    }
+}

+ 61 - 0
fs-ad-new-api/src/main/java/com/fs/framework/config/AsyncConfig.java

@@ -0,0 +1,61 @@
+package com.fs.framework.config;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.AsyncConfigurer;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * 异步任务配置类
+ *
+ * @author zhangqin
+ * @date 2025-11-03
+ */
+@Slf4j
+@Configuration
+public class AsyncConfig implements AsyncConfigurer {
+
+    @Bean(name = "asyncExecutor")
+    @Override
+    public Executor getAsyncExecutor() {
+        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+
+        // 核心线程数
+        executor.setCorePoolSize(10);
+
+        // 最大线程数
+        executor.setMaxPoolSize(50);
+
+        // 队列容量
+        executor.setQueueCapacity(1000);
+
+        // 线程存活时间(秒)
+        executor.setKeepAliveSeconds(60);
+
+        // 线程名称前缀
+        executor.setThreadNamePrefix("async-task-");
+
+        // 拒绝策略:调用者运行
+        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+
+        // 等待所有任务完成后再关闭线程池
+        executor.setWaitForTasksToCompleteOnShutdown(true);
+
+        // 等待时间
+        executor.setAwaitTerminationSeconds(60);
+
+
+        // ⭐ 关键:传递 MDC
+        executor.setTaskDecorator(new MdcTaskDecorator());
+
+        executor.initialize();
+
+        log.info("异步线程池初始化完成");
+        return executor;
+    }
+}
+

+ 19 - 0
fs-ad-new-api/src/main/java/com/fs/framework/config/CorsConfig.java

@@ -0,0 +1,19 @@
+package com.fs.framework.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class CorsConfig implements WebMvcConfigurer {
+
+    @Override
+    public void addCorsMappings(CorsRegistry registry) {
+        registry.addMapping("/**")
+                .allowedOrigins("*") // ★旧版本用这个
+                .allowedMethods("*")
+                .allowedHeaders("*")
+                .allowCredentials(false) // ★注意:allowedOrigins("*") 不能和 true 一起用
+                .maxAge(3600);
+    }
+}

+ 92 - 0
fs-ad-new-api/src/main/java/com/fs/framework/config/DataSourceConfig.java

@@ -0,0 +1,92 @@
+package com.fs.framework.config;
+
+import com.alibaba.druid.pool.DruidDataSource;
+import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
+import com.alibaba.druid.util.Utils;
+import com.fs.common.enums.DataSourceType;
+import com.fs.framework.datasource.DynamicDataSource;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+
+import javax.servlet.*;
+import javax.sql.DataSource;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+@Configuration
+public class DataSourceConfig {
+
+    @Bean
+    @ConfigurationProperties(prefix = "spring.datasource.sop.druid.master")
+    public DataSource sopDataSource() {
+        return new DruidDataSource();
+    }
+
+    @Bean
+    @ConfigurationProperties(prefix = "spring.datasource.mysql.druid.master")
+    public DataSource masterDataSource() {
+        return new DruidDataSource();
+    }
+
+
+
+    @Bean
+    @Primary
+    public DynamicDataSource dataSource(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("sopDataSource") DataSource sopDataSource) {
+        Map<Object, Object> targetDataSources = new HashMap<>();
+        targetDataSources.put(DataSourceType.SOP.name(), sopDataSource);
+        return new DynamicDataSource(masterDataSource, targetDataSources);
+    }
+
+    /**
+     * 去除监控页面底部的广告
+     */
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Bean
+    @ConditionalOnProperty(name = "spring.datasource.mysql.druid.statViewServlet.enabled", havingValue = "true")
+    public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties)
+    {
+        // 获取web监控页面的参数
+        DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
+        // 提取common.js的配置路径
+        String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
+        String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
+        final String filePath = "support/http/resources/js/common.js";
+        // 创建filter进行过滤
+        Filter filter = new Filter()
+        {
+            @Override
+            public void init(javax.servlet.FilterConfig filterConfig) throws ServletException
+            {
+            }
+            @Override
+            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+                    throws IOException, ServletException
+            {
+                chain.doFilter(request, response);
+                // 重置缓冲区,响应头不会被重置
+                response.resetBuffer();
+                // 获取common.js
+                String text = Utils.readFromResource(filePath);
+                // 正则替换banner, 除去底部的广告信息
+                text = text.replaceAll("<a.*?banner\"></a><br/>", "");
+                text = text.replaceAll("powered.*?shrek.wang</a>", "");
+                response.getWriter().write(text);
+            }
+            @Override
+            public void destroy()
+            {
+            }
+        };
+        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
+        registrationBean.setFilter(filter);
+        registrationBean.addUrlPatterns(commonJsPattern);
+        return registrationBean;
+    }
+}

+ 72 - 0
fs-ad-new-api/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -0,0 +1,72 @@
+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;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.data.redis.serializer.SerializationException;
+import org.springframework.util.Assert;
+
+import java.nio.charset.Charset;
+
+/**
+ * Redis使用FastJson序列化
+ * 
+
+ */
+public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
+{
+    @SuppressWarnings("unused")
+    private ObjectMapper objectMapper = new ObjectMapper();
+
+    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
+
+    private Class<T> clazz;
+
+    static
+    {
+        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
+    }
+
+    public FastJson2JsonRedisSerializer(Class<T> clazz)
+    {
+        super();
+        this.clazz = clazz;
+    }
+
+    @Override
+    public byte[] serialize(T t) throws SerializationException
+    {
+        if (t == null)
+        {
+            return new byte[0];
+        }
+        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
+    }
+
+    @Override
+    public T deserialize(byte[] bytes) throws SerializationException
+    {
+        if (bytes == null || bytes.length <= 0)
+        {
+            return null;
+        }
+        String str = new String(bytes, DEFAULT_CHARSET);
+
+        return JSON.parseObject(str, clazz);
+    }
+
+    public void setObjectMapper(ObjectMapper objectMapper)
+    {
+        Assert.notNull(objectMapper, "'objectMapper' must not be null");
+        this.objectMapper = objectMapper;
+    }
+
+    protected JavaType getJavaType(Class<?> clazz)
+    {
+        return TypeFactory.defaultInstance().constructType(clazz);
+    }
+}

+ 59 - 0
fs-ad-new-api/src/main/java/com/fs/framework/config/FilterConfig.java

@@ -0,0 +1,59 @@
+package com.fs.framework.config;
+
+import com.fs.common.filter.RepeatableFilter;
+import com.fs.common.filter.XssFilter;
+import com.fs.common.utils.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.servlet.DispatcherType;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Filter配置
+ *
+
+ */
+@Configuration
+@ConditionalOnProperty(value = "xss.enabled", havingValue = "true")
+public class FilterConfig
+{
+    @Value("${xss.excludes}")
+    private String excludes;
+
+    @Value("${xss.urlPatterns}")
+    private String urlPatterns;
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Bean
+    public FilterRegistrationBean xssFilterRegistration()
+    {
+        FilterRegistrationBean registration = new FilterRegistrationBean();
+        registration.setDispatcherTypes(DispatcherType.REQUEST);
+        registration.setFilter(new XssFilter());
+        registration.addUrlPatterns(StringUtils.split(urlPatterns, ","));
+        registration.setName("xssFilter");
+        registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
+        Map<String, String> initParameters = new HashMap<String, String>();
+        initParameters.put("excludes", excludes);
+        registration.setInitParameters(initParameters);
+        return registration;
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Bean
+    public FilterRegistrationBean someFilterRegistration()
+    {
+        FilterRegistrationBean registration = new FilterRegistrationBean();
+        registration.setFilter(new RepeatableFilter());
+        registration.addUrlPatterns("/*");
+        registration.setName("repeatableFilter");
+        registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
+        return registration;
+    }
+
+}

+ 30 - 0
fs-ad-new-api/src/main/java/com/fs/framework/config/MdcTaskDecorator.java

@@ -0,0 +1,30 @@
+package com.fs.framework.config;
+
+import org.slf4j.MDC;
+import org.springframework.core.task.TaskDecorator;
+
+import java.util.Map;
+
+public class MdcTaskDecorator implements TaskDecorator {
+
+    @Override
+    public Runnable decorate(Runnable runnable) {
+
+        // 1. 获取提交任务时的 MDC 内容
+        Map<String, String> contextMap = MDC.getCopyOfContextMap();
+
+        return () -> {
+            try {
+                // 2. 任务执行前恢复 MDC
+                if (contextMap != null) {
+                    MDC.setContextMap(contextMap);
+                }
+
+                runnable.run();
+            } finally {
+                // 3. 清理,避免线程复用污染
+                MDC.clear();
+            }
+        };
+    }
+}

+ 148 - 0
fs-ad-new-api/src/main/java/com/fs/framework/config/MyBatisConfig.java

@@ -0,0 +1,148 @@
+package com.fs.framework.config;
+
+import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
+import org.apache.ibatis.io.VFS;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.env.Environment;
+import org.springframework.core.io.DefaultResourceLoader;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+import org.springframework.util.ClassUtils;
+
+import javax.sql.DataSource;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Mybatis支持*匹配扫描包
+ *
+
+ */
+@Configuration
+public class MyBatisConfig
+{
+    @Autowired
+    private Environment env;
+
+    static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
+
+    public static String setTypeAliasesPackage(String typeAliasesPackage)
+    {
+        ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver();
+        MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver);
+        List<String> allResult = new ArrayList<String>();
+        try
+        {
+            for (String aliasesPackage : typeAliasesPackage.split(","))
+            {
+                List<String> result = new ArrayList<String>();
+                aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+                        + ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + DEFAULT_RESOURCE_PATTERN;
+                Resource[] resources = resolver.getResources(aliasesPackage);
+                if (resources != null && resources.length > 0)
+                {
+                    MetadataReader metadataReader = null;
+                    for (Resource resource : resources)
+                    {
+                        if (resource.isReadable())
+                        {
+                            metadataReader = metadataReaderFactory.getMetadataReader(resource);
+                            try
+                            {
+                                result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName());
+                            }
+                            catch (ClassNotFoundException e)
+                            {
+                                e.printStackTrace();
+                            }
+                        }
+                    }
+                }
+                if (result.size() > 0)
+                {
+                    HashSet<String> hashResult = new HashSet<String>(result);
+                    allResult.addAll(hashResult);
+                }
+            }
+            if (allResult.size() > 0)
+            {
+                typeAliasesPackage = String.join(",", (String[]) allResult.toArray(new String[0]));
+            }
+            else
+            {
+                throw new RuntimeException("mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包");
+            }
+        }
+        catch (IOException e)
+        {
+            e.printStackTrace();
+        }
+        return typeAliasesPackage;
+    }
+
+    public Resource[] resolveMapperLocations(String[] mapperLocations)
+    {
+        ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
+        List<Resource> resources = new ArrayList<Resource>();
+        if (mapperLocations != null)
+        {
+            for (String mapperLocation : mapperLocations)
+            {
+                try
+                {
+                    Resource[] mappers = resourceResolver.getResources(mapperLocation);
+                    resources.addAll(Arrays.asList(mappers));
+                }
+                catch (IOException e)
+                {
+                    // ignore
+                }
+            }
+        }
+        return resources.toArray(new Resource[resources.size()]);
+    }
+
+//    @Bean
+//    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception
+//    {
+//        String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage");
+//        String mapperLocations = env.getProperty("mybatis.mapperLocations");
+//        String configLocation = env.getProperty("mybatis.configLocation");
+//        typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
+//        VFS.addImplClass(SpringBootVFS.class);
+//
+//        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
+//        sessionFactory.setDataSource(dataSource);
+//        sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
+//        sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ",")));
+//        sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
+//        return sessionFactory.getObject();
+//    }
+    @Bean
+    public SqlSessionFactory sqlSessionFactorys(DataSource dataSource) throws Exception
+    {
+        String typeAliasesPackage = env.getProperty("mybatis-plus.typeAliasesPackage");
+        String mapperLocations = env.getProperty("mybatis-plus.mapperLocations");
+        String configLocation = env.getProperty("mybatis-plus.configLocation");
+        typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
+        VFS.addImplClass(SpringBootVFS.class);
+
+        final MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
+        sessionFactory.setDataSource(dataSource);
+        sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
+        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
+        sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
+        return sessionFactory.getObject();
+    }
+}

+ 158 - 0
fs-ad-new-api/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -0,0 +1,158 @@
+package com.fs.framework.config;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
+import org.springframework.cache.annotation.CachingConfigurerSupport;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.data.redis.serializer.GenericToStringSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+import java.math.BigDecimal;
+
+/**
+ * redis配置
+ *
+
+ */
+@Configuration
+@EnableCaching
+public class RedisConfig extends CachingConfigurerSupport
+{
+    @Bean
+    @SuppressWarnings(value = { "unchecked", "rawtypes" })
+    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
+    {
+        RedisTemplate<Object, Object> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+
+        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
+
+        ObjectMapper mapper = new ObjectMapper();
+        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
+        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
+        serializer.setObjectMapper(mapper);
+
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+        template.setValueSerializer(serializer);
+
+        // Hash的key也采用StringRedisSerializer的序列化方式
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(serializer);
+
+        template.afterPropertiesSet();
+        return template;
+    }
+    @Bean
+    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+
+        // Hash的key也采用StringRedisSerializer的序列化方式
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+
+        template.afterPropertiesSet();
+        return template;
+    }
+    @Bean
+    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Integer> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
+
+        // Hash的key也采用StringRedisSerializer的序列化方式
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
+
+        template.afterPropertiesSet();
+        return template;
+    }
+
+    @Bean
+    @SuppressWarnings(value = { "unchecked", "rawtypes" })
+    public RedisTemplate<String, Object> redisTemplateForObject(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Object> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+
+        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
+
+        ObjectMapper mapper = new ObjectMapper();
+        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
+        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
+        serializer.setObjectMapper(mapper);
+
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+        template.setValueSerializer(serializer);
+
+        // Hash的key也采用StringRedisSerializer的序列化方式
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(serializer);
+
+        template.afterPropertiesSet();
+        return template;
+    }
+    @Bean
+    public RedisTemplate<String, BigDecimal> redisTemplateForBigDecimal(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, BigDecimal> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
+
+        // Hash的key也采用StringRedisSerializer的序列化方式
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
+
+        template.afterPropertiesSet();
+        return template;
+    }
+    @Bean
+    public DefaultRedisScript<Long> limitScript()
+    {
+        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
+        redisScript.setScriptText(limitScriptText());
+        redisScript.setResultType(Long.class);
+        return redisScript;
+    }
+
+    /**
+     * 限流脚本
+     */
+    private String limitScriptText()
+    {
+        return "local key = KEYS[1]\n" +
+                "local count = tonumber(ARGV[1])\n" +
+                "local time = tonumber(ARGV[2])\n" +
+                "local current = redis.call('get', key);\n" +
+                "if current and tonumber(current) > count then\n" +
+                "    return current;\n" +
+                "end\n" +
+                "current = redis.call('incr', key)\n" +
+                "if tonumber(current) == 1 then\n" +
+                "    redis.call('expire', key, time)\n" +
+                "end\n" +
+                "return current;";
+    }
+}

+ 50 - 0
fs-ad-new-api/src/main/java/com/fs/framework/config/SecurityConfig.java

@@ -0,0 +1,50 @@
+package com.fs.framework.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.BeanIds;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+
+/**
+ * spring security配置
+ * 
+
+ */
+@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
+public class SecurityConfig extends WebSecurityConfigurerAdapter
+{
+
+    /**
+     * anyRequest          |   匹配所有请求路径
+     * access              |   SpringEl表达式结果为true时可以访问
+     * anonymous           |   匿名可以访问
+     * denyAll             |   用户不能访问
+     * fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)
+     * hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问
+     * hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问
+     * hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问
+     * hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
+     * hasRole             |   如果有参数,参数表示角色,则其角色可以访问
+     * permitAll           |   用户可以任意访问
+     * rememberMe          |   允许通过remember-me登录的用户访问
+     * authenticated       |   用户登录后可访问
+     */
+    @Override
+    protected void configure(HttpSecurity http) throws Exception
+    {
+        http.authorizeRequests()
+                .antMatchers("/**").permitAll()
+                .anyRequest().authenticated()
+                .and().csrf().disable();
+    }
+
+    @Bean(name = BeanIds.AUTHENTICATION_MANAGER)
+    @Override
+    public AuthenticationManager authenticationManagerBean() throws Exception {
+        return super.authenticationManagerBean();
+    }
+
+
+}

+ 33 - 0
fs-ad-new-api/src/main/java/com/fs/framework/config/ServerConfig.java

@@ -0,0 +1,33 @@
+package com.fs.framework.config;
+
+import com.fs.common.utils.ServletUtils;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 服务相关配置
+ * 
+
+ */
+@Component
+public class ServerConfig
+{
+    /**
+     * 获取完整的请求路径,包括:域名,端口,上下文访问路径
+     * 
+     * @return 服务地址
+     */
+    public String getUrl()
+    {
+        HttpServletRequest request = ServletUtils.getRequest();
+        return getDomain(request);
+    }
+
+    public static String getDomain(HttpServletRequest request)
+    {
+        StringBuffer url = request.getRequestURL();
+        String contextPath = request.getServletContext().getContextPath();
+        return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString();
+    }
+}

+ 63 - 0
fs-ad-new-api/src/main/java/com/fs/framework/config/ThreadPoolConfig.java

@@ -0,0 +1,63 @@
+package com.fs.framework.config;
+
+import com.fs.common.utils.Threads;
+import org.apache.commons.lang3.concurrent.BasicThreadFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * 线程池配置
+ *
+
+ **/
+@Configuration
+public class ThreadPoolConfig
+{
+    // 核心线程池大小
+    private int corePoolSize = 50;
+
+    // 最大可创建的线程数
+    private int maxPoolSize = 200;
+
+    // 队列最大长度
+    private int queueCapacity = 1000;
+
+    // 线程池维护线程所允许的空闲时间
+    private int keepAliveSeconds = 300;
+
+    @Bean(name = "threadPoolTaskExecutor")
+    public ThreadPoolTaskExecutor threadPoolTaskExecutor()
+    {
+        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+        executor.setMaxPoolSize(maxPoolSize);
+        executor.setCorePoolSize(corePoolSize);
+        executor.setQueueCapacity(queueCapacity);
+        executor.setKeepAliveSeconds(keepAliveSeconds);
+        // 线程池对拒绝任务(无线程可用)的处理策略
+        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+        return executor;
+    }
+
+    /**
+     * 执行周期性或定时任务
+     */
+    @Bean(name = "scheduledExecutorService")
+    protected ScheduledExecutorService scheduledExecutorService()
+    {
+        return new ScheduledThreadPoolExecutor(corePoolSize,
+                new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build())
+        {
+            @Override
+            protected void afterExecute(Runnable r, Throwable t)
+            {
+                super.afterExecute(r, t);
+                Threads.printException(r, t);
+            }
+        };
+    }
+}

+ 36 - 0
fs-ad-new-api/src/main/java/com/fs/framework/config/TrackIdFilter.java

@@ -0,0 +1,36 @@
+package com.fs.framework.config;
+
+import cn.hutool.core.lang.UUID;
+import org.jboss.logging.MDC;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+@Component
+public class TrackIdFilter implements Filter {
+
+    public static final String TRACK_ID = "X-Track-Id";
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+            throws IOException, ServletException {
+
+        // 1. 生成唯一 trackId
+        String trackId = UUID.randomUUID().toString().replace("-", "");
+
+        // 2. 放入 MDC
+        MDC.put(TRACK_ID, trackId);
+
+        // 3. 响应头返回 traceId
+        HttpServletResponse httpResponse = (HttpServletResponse) response;
+        httpResponse.setHeader(TRACK_ID, trackId);
+
+        try {
+            chain.doFilter(request, response);
+        } finally {
+            MDC.remove(TRACK_ID);
+        }
+    }
+}

+ 77 - 0
fs-ad-new-api/src/main/java/com/fs/framework/config/properties/DruidProperties.java

@@ -0,0 +1,77 @@
+package com.fs.framework.config.properties;
+
+import com.alibaba.druid.pool.DruidDataSource;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * druid 配置属性
+ *
+
+ */
+@Configuration
+public class DruidProperties
+{
+    @Value("${spring.datasource.mysql.druid.initialSize}")
+    private int initialSize;
+
+    @Value("${spring.datasource.mysql.druid.minIdle}")
+    private int minIdle;
+
+    @Value("${spring.datasource.mysql.druid.maxActive}")
+    private int maxActive;
+
+    @Value("${spring.datasource.mysql.druid.maxWait}")
+    private int maxWait;
+
+    @Value("${spring.datasource.mysql.druid.timeBetweenEvictionRunsMillis}")
+    private int timeBetweenEvictionRunsMillis;
+
+    @Value("${spring.datasource.mysql.druid.minEvictableIdleTimeMillis}")
+    private int minEvictableIdleTimeMillis;
+
+    @Value("${spring.datasource.mysql.druid.maxEvictableIdleTimeMillis}")
+    private int maxEvictableIdleTimeMillis;
+
+    @Value("${spring.datasource.mysql.druid.validationQuery}")
+    private String validationQuery;
+
+    @Value("${spring.datasource.mysql.druid.testWhileIdle}")
+    private boolean testWhileIdle;
+
+    @Value("${spring.datasource.mysql.druid.testOnBorrow}")
+    private boolean testOnBorrow;
+
+    @Value("${spring.datasource.mysql.druid.testOnReturn}")
+    private boolean testOnReturn;
+
+    public DruidDataSource dataSource(DruidDataSource datasource)
+    {
+        /** 配置初始化大小、最小、最大 */
+        datasource.setInitialSize(initialSize);
+        datasource.setMaxActive(maxActive);
+        datasource.setMinIdle(minIdle);
+
+        /** 配置获取连接等待超时的时间 */
+        datasource.setMaxWait(maxWait);
+
+        /** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */
+        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
+
+        /** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */
+        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
+        datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);
+
+        /**
+         * 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
+         */
+        datasource.setValidationQuery(validationQuery);
+        /** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */
+        datasource.setTestWhileIdle(testWhileIdle);
+        /** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
+        datasource.setTestOnBorrow(testOnBorrow);
+        /** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
+        datasource.setTestOnReturn(testOnReturn);
+        return datasource;
+    }
+}

+ 27 - 0
fs-ad-new-api/src/main/java/com/fs/framework/datasource/DynamicDataSource.java

@@ -0,0 +1,27 @@
+package com.fs.framework.datasource;
+
+import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
+
+import javax.sql.DataSource;
+import java.util.Map;
+
+/**
+ * 动态数据源
+ * 
+
+ */
+public class DynamicDataSource extends AbstractRoutingDataSource
+{
+    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources)
+    {
+        super.setDefaultTargetDataSource(defaultTargetDataSource);
+        super.setTargetDataSources(targetDataSources);
+        super.afterPropertiesSet();
+    }
+
+    @Override
+    protected Object determineCurrentLookupKey()
+    {
+        return DynamicDataSourceContextHolder.getDataSourceType();
+    }
+}

+ 45 - 0
fs-ad-new-api/src/main/java/com/fs/framework/datasource/DynamicDataSourceContextHolder.java

@@ -0,0 +1,45 @@
+package com.fs.framework.datasource;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 数据源切换处理
+ * 
+
+ */
+public class DynamicDataSourceContextHolder
+{
+    public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
+
+    /**
+     * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
+     *  所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
+     */
+    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
+
+    /**
+     * 设置数据源的变量
+     */
+    public static void setDataSourceType(String dsType)
+    {
+//        log.info("切换到{}数据源", dsType);
+        CONTEXT_HOLDER.set(dsType);
+    }
+
+    /**
+     * 获得数据源的变量
+     */
+    public static String getDataSourceType()
+    {
+        return CONTEXT_HOLDER.get();
+    }
+
+    /**
+     * 清空数据源变量
+     */
+    public static void clearDataSourceType()
+    {
+        CONTEXT_HOLDER.remove();
+    }
+}

+ 56 - 0
fs-ad-new-api/src/main/java/com/fs/framework/manager/AsyncManager.java

@@ -0,0 +1,56 @@
+package com.fs.framework.manager;
+
+import com.fs.common.utils.Threads;
+import com.fs.common.utils.spring.SpringUtils;
+
+import java.util.TimerTask;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 异步任务管理器
+ * 
+
+ */
+public class AsyncManager
+{
+    /**
+     * 操作延迟10毫秒
+     */
+    private final int OPERATE_DELAY_TIME = 10;
+
+    /**
+     * 异步操作任务调度线程池
+     */
+    private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");
+
+    /**
+     * 单例模式
+     */
+    private AsyncManager(){}
+
+    private static AsyncManager me = new AsyncManager();
+
+    public static AsyncManager me()
+    {
+        return me;
+    }
+
+    /**
+     * 执行任务
+     * 
+     * @param task 任务
+     */
+    public void execute(TimerTask task)
+    {
+        executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
+    }
+
+    /**
+     * 停止任务线程池
+     */
+    public void shutdown()
+    {
+        Threads.shutdownAndAwaitTermination(executor);
+    }
+}

+ 40 - 0
fs-ad-new-api/src/main/java/com/fs/framework/manager/ShutdownManager.java

@@ -0,0 +1,40 @@
+package com.fs.framework.manager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PreDestroy;
+
+/**
+ * 确保应用退出时能关闭后台线程
+ *
+
+ */
+@Component
+public class ShutdownManager
+{
+    private static final Logger logger = LoggerFactory.getLogger("sys-user");
+
+    @PreDestroy
+    public void destroy()
+    {
+        shutdownAsyncManager();
+    }
+
+    /**
+     * 停止异步执行任务
+     */
+    private void shutdownAsyncManager()
+    {
+        try
+        {
+            logger.info("====关闭后台任务任务线程池====");
+            AsyncManager.me().shutdown();
+        }
+        catch (Exception e)
+        {
+            logger.error(e.getMessage(), e);
+        }
+    }
+}

+ 103 - 0
fs-ad-new-api/src/main/java/com/fs/framework/manager/factory/AsyncFactory.java

@@ -0,0 +1,103 @@
+package com.fs.framework.manager.factory;
+
+import com.fs.common.constant.Constants;
+import com.fs.common.utils.LogUtils;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.ip.AddressUtils;
+import com.fs.common.utils.ip.IpUtils;
+import com.fs.common.utils.spring.SpringUtils;
+import com.fs.system.domain.SysLogininfor;
+import com.fs.system.domain.SysOperLog;
+import com.fs.system.service.ISysLogininforService;
+import com.fs.system.service.ISysOperLogService;
+import eu.bitwalker.useragentutils.UserAgent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.TimerTask;
+
+/**
+ * 异步工厂(产生任务用)
+ * 
+
+ */
+public class AsyncFactory
+{
+    private static final Logger sys_user_logger = LoggerFactory.getLogger("sys-user");
+
+    /**
+     * 记录登录信息
+     * 
+     * @param username 用户名
+     * @param status 状态
+     * @param message 消息
+     * @param args 列表
+     * @return 任务task
+     */
+    public static TimerTask recordLogininfor(final String username, final String status, final String message,
+            final Object... args)
+    {
+        final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
+        final String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
+        return new TimerTask()
+        {
+            @Override
+            public void run()
+            {
+                String address = AddressUtils.getRealAddressByIP(ip);
+                StringBuilder s = new StringBuilder();
+                s.append(LogUtils.getBlock(ip));
+                s.append(address);
+                s.append(LogUtils.getBlock(username));
+                s.append(LogUtils.getBlock(status));
+                s.append(LogUtils.getBlock(message));
+                // 打印信息到日志
+                sys_user_logger.info(s.toString(), args);
+                // 获取客户端操作系统
+                String os = userAgent.getOperatingSystem().getName();
+                // 获取客户端浏览器
+                String browser = userAgent.getBrowser().getName();
+                // 封装对象
+                SysLogininfor logininfor = new SysLogininfor();
+                logininfor.setUserName(username);
+                logininfor.setIpaddr(ip);
+                logininfor.setLoginLocation(address);
+                logininfor.setBrowser(browser);
+                logininfor.setOs(os);
+                logininfor.setMsg(message);
+                // 日志状态
+                if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER))
+                {
+                    logininfor.setStatus(Constants.SUCCESS);
+                }
+                else if (Constants.LOGIN_FAIL.equals(status))
+                {
+                    logininfor.setStatus(Constants.FAIL);
+                }
+                // 插入数据
+                SpringUtils.getBean(ISysLogininforService.class).insertLogininfor(logininfor);
+            }
+        };
+    }
+
+    /**
+     * 操作日志记录
+     * 
+     * @param operLog 操作日志信息
+     * @return 任务task
+     */
+    public static TimerTask recordOper(final SysOperLog operLog)
+    {
+        return new TimerTask()
+        {
+            @Override
+            public void run()
+            {
+                // 远程查询操作地点
+                operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp()));
+                SpringUtils.getBean(ISysOperLogService.class).insertOperlog(operLog);
+            }
+        };
+    }
+}

+ 1 - 0
fs-ad-new-api/src/main/resources/META-INF/spring-devtools.properties

@@ -0,0 +1 @@
+restart.include.json=/com.alibaba.fastjson.*.jar

+ 2 - 0
fs-ad-new-api/src/main/resources/banner.txt

@@ -0,0 +1,2 @@
+Application Version: ${fs.version}
+Spring Boot Version: ${spring-boot.version}

+ 37 - 0
fs-ad-new-api/src/main/resources/i18n/messages.properties

@@ -0,0 +1,37 @@
+#错误消息
+not.null=* 必须填写
+user.jcaptcha.error=验证码错误
+user.jcaptcha.expire=验证码已失效
+user.not.exists=用户不存在/密码错误
+user.password.not.match=用户不存在/密码错误
+user.password.retry.limit.count=密码输入错误{0}次
+user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定10分钟
+user.password.delete=对不起,您的账号已被删除
+user.blocked=用户已封禁,请联系管理员
+role.blocked=角色已封禁,请联系管理员
+user.logout.success=退出成功
+
+length.not.valid=长度必须在{min}到{max}个字符之间
+
+user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头
+user.password.not.valid=* 5-50个字符
+ 
+user.email.not.valid=邮箱格式错误
+user.mobile.phone.number.not.valid=手机号格式错误
+user.login.success=登录成功
+user.register.success=注册成功
+user.notfound=请重新登录
+user.forcelogout=管理员强制退出,请重新登录
+user.unknown.error=未知错误,请重新登录
+
+##文件上传消息
+upload.exceed.maxSize=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB!
+upload.filename.exceed.length=上传的文件名最长{0}个字符
+
+##权限
+no.permission=您没有数据的权限,请联系管理员添加权限 [{0}]
+no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}]
+no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}]
+no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
+no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
+no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]

+ 93 - 0
fs-ad-new-api/src/main/resources/logback.xml

@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <!-- 日志存放路径 -->
+	<property name="log.path" value="/home/fs-ad-new-api/logs" />
+    <!-- 日志输出格式 -->
+	<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] [%X{X-Track-Id}] %-5level %logger{20} - [%method,%line] - %msg%n" />
+
+	<!-- 控制台输出 -->
+	<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+		<encoder>
+			<pattern>${log.pattern}</pattern>
+		</encoder>
+	</appender>
+
+	<!-- 系统日志输出 -->
+	<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
+	    <file>${log.path}/sys-info.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+			<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
+			<!-- 日志最大的历史 30 -->
+			<maxHistory>30</maxHistory>
+		</rollingPolicy>
+		<encoder>
+			<pattern>${log.pattern}</pattern>
+		</encoder>
+		<filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>INFO</level>
+            <!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+            <!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+	</appender>
+
+	<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
+	    <file>${log.path}/sys-error.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+            <fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
+			<!-- 日志最大的历史 60天 -->
+			<maxHistory>30</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>ERROR</level>
+			<!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+			<!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+	<!-- 用户访问日志输出  -->
+    <appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<file>${log.path}/sys-user.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 按天回滚 daily -->
+            <fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <!-- 日志最大的历史 60天 -->
+            <maxHistory>30</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+    </appender>
+
+	<!-- 系统模块日志级别控制  -->
+	<logger name="com.fs" level="debug" />
+	<!-- Spring日志级别控制  -->
+	<logger name="org.springframework" level="warn" />
+
+	<root level="info">
+		<appender-ref ref="console" />
+	</root>
+
+	<!--系统操作日志-->
+    <root level="info">
+        <appender-ref ref="file_info" />
+        <appender-ref ref="file_error" />
+    </root>
+
+	<!--系统用户操作日志-->
+    <logger name="sys-user" level="info">
+        <appender-ref ref="sys-user"/>
+    </logger>
+</configuration>

+ 15 - 0
fs-ad-new-api/src/main/resources/mybatis/mybatis-config.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE configuration
+PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-config.dtd">
+<configuration>
+	
+	<settings>
+		<setting name="cacheEnabled"             value="true" />  <!-- 全局映射器启用缓存 -->
+		<setting name="useGeneratedKeys"         value="true" />  <!-- 允许 JDBC 支持自动生成主键 -->
+		<setting name="defaultExecutorType"      value="REUSE" /> <!-- 配置默认的执行器 -->
+		<setting name="logImpl"                  value="SLF4J" /> <!-- 指定 MyBatis 所用日志的具体实现 -->
+		 <setting name="mapUnderscoreToCamelCase" value="true"/>
+	</settings>
+	
+</configuration>

+ 6 - 0
fs-admin/Dockerfile

@@ -0,0 +1,6 @@
+FROM openjdk:8-jre
+# java版本,最好使用openjdk,而不是类似于Java:1.8
+COPY ./target/fs-admin.jar fs-admin.jar
+# 向外暴露的接口,最好与项目yml文件中的端口一致
+ENTRYPOINT ["java","-jar","fs-admin.jar"]
+# 执行启动命令java -jar

+ 103 - 0
fs-admin/src/main/java/com/fs/company/controller/CompanyRedPacketBalanceLogsController.java

@@ -0,0 +1,103 @@
+package com.fs.company.controller;
+
+import java.util.List;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.enums.BusinessType;
+import com.fs.company.domain.CompanyRedPacketBalanceLogs;
+import com.fs.company.service.ICompanyRedPacketBalanceLogsService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 企业红包余额记录Controller
+ * 
+ * @author fs
+ * @date 2025-11-19
+ */
+@RestController
+@RequestMapping("/company/companyRedPacketBalanceLogs")
+public class CompanyRedPacketBalanceLogsController extends BaseController
+{
+    @Autowired
+    private ICompanyRedPacketBalanceLogsService companyRedPacketBalanceLogsService;
+
+    /**
+     * 查询企业红包余额记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRedPacketBalanceLogs:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs)
+    {
+        startPage();
+        List<CompanyRedPacketBalanceLogs> list = companyRedPacketBalanceLogsService.selectCompanyRedPacketBalanceLogsList(companyRedPacketBalanceLogs);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出企业红包余额记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRedPacketBalanceLogs:export')")
+    @Log(title = "企业红包余额记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs)
+    {
+        List<CompanyRedPacketBalanceLogs> list = companyRedPacketBalanceLogsService.selectCompanyRedPacketBalanceLogsList(companyRedPacketBalanceLogs);
+        ExcelUtil<CompanyRedPacketBalanceLogs> util = new ExcelUtil<CompanyRedPacketBalanceLogs>(CompanyRedPacketBalanceLogs.class);
+        return util.exportExcel(list, "企业红包余额记录数据");
+    }
+
+    /**
+     * 获取企业红包余额记录详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRedPacketBalanceLogs:query')")
+    @GetMapping(value = "/{logsId}")
+    public AjaxResult getInfo(@PathVariable("logsId") Long logsId)
+    {
+        return AjaxResult.success(companyRedPacketBalanceLogsService.selectCompanyRedPacketBalanceLogsByLogsId(logsId));
+    }
+
+    /**
+     * 新增企业红包余额记录
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRedPacketBalanceLogs:add')")
+    @Log(title = "企业红包余额记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs)
+    {
+        return toAjax(companyRedPacketBalanceLogsService.insertCompanyRedPacketBalanceLogs(companyRedPacketBalanceLogs));
+    }
+
+    /**
+     * 修改企业红包余额记录
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRedPacketBalanceLogs:edit')")
+    @Log(title = "企业红包余额记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs)
+    {
+        return toAjax(companyRedPacketBalanceLogsService.updateCompanyRedPacketBalanceLogs(companyRedPacketBalanceLogs));
+    }
+
+    /**
+     * 删除企业红包余额记录
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRedPacketBalanceLogs:remove')")
+    @Log(title = "企业红包余额记录", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{logsIds}")
+    public AjaxResult remove(@PathVariable Long[] logsIds)
+    {
+        return toAjax(companyRedPacketBalanceLogsService.deleteCompanyRedPacketBalanceLogsByLogsIds(logsIds));
+    }
+}

+ 41 - 0
fs-admin/src/main/java/com/fs/course/business/FsVideoResourceBusinessService.java

@@ -0,0 +1,41 @@
+package com.fs.course.business;
+
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.fs.course.domain.FsUserCourseVideo;
+import com.fs.course.domain.FsVideoResource;
+import com.fs.course.service.IFsUserCourseVideoService;
+import com.fs.course.service.IFsVideoResourceService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class FsVideoResourceBusinessService {
+
+    private final IFsVideoResourceService fsVideoResourceService;
+
+    private final IFsUserCourseVideoService fsUserCourseVideoService;
+
+    public FsVideoResourceBusinessService(IFsVideoResourceService fsVideoResourceService,
+                                          IFsUserCourseVideoService fsUserCourseVideoService) {
+        this.fsVideoResourceService = fsVideoResourceService;
+        this.fsUserCourseVideoService = fsUserCourseVideoService;
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    public void edit(FsVideoResource fsVideoResource) {
+        FsVideoResource oldResource = fsVideoResourceService.getById(fsVideoResource.getId());
+        fsVideoResourceService.updateById(fsVideoResource);
+        fsUserCourseVideoService.update(new UpdateWrapper<FsUserCourseVideo>()
+                .eq("video_url", oldResource.getVideoUrl())
+                .set("video_url", fsVideoResource.getVideoUrl())
+                .set("line_one", fsVideoResource.getLine1())
+                .set("line_two", fsVideoResource.getLine2())
+                .set("line_three", fsVideoResource.getLine3())
+                .set("duration", fsVideoResource.getDuration())
+                .set("file_size", fsVideoResource.getFileSize())
+                .set("file_key", fsVideoResource.getFileKey())
+                .set("file_name", fsVideoResource.getFileName())
+                .set("thumbnail", fsVideoResource.getThumbnail())
+        );
+    }
+}

+ 18 - 6
fs-admin/src/main/java/com/fs/course/controller/FsCourseAnswerLogsController.java

@@ -3,6 +3,7 @@ package com.fs.course.controller;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
+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;
@@ -10,6 +11,8 @@ import com.fs.course.domain.FsCourseAnswerLogs;
 import com.fs.course.param.FsCourseAnswerLogsParam;
 import com.fs.course.service.IFsCourseAnswerLogsService;
 import com.fs.course.vo.FsCourseAnswerLogsListVO;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
@@ -35,17 +38,26 @@ public class FsCourseAnswerLogsController extends BaseController
      * 查询答题日志列表
      */
     @PreAuthorize("@ss.hasPermi('course:courseAnswerLog:list')")
-    @GetMapping("/list")
-    public TableDataInfo list(FsCourseAnswerLogsParam param)
+    @PostMapping("/list")
+    public R list(@RequestBody  FsCourseAnswerLogsParam param)
     {
-        startPage();
+        if(param.getPageNum() == null) {
+            param.setPageNum(1);
+        }
+        if(param.getPageSize() == null) {
+            param.setPageSize(10);
+        }
 
         if (param.getPhoneMk() != null && param.getPhoneMk() != "") {
             param.setPhone(encryptPhone(param.getPhoneMk()));
         }
 
+
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+
         List<FsCourseAnswerLogsListVO> list = fsCourseAnswerLogsService.selectFsCourseAnswerLogsListVO(param);
-        return getDataTable(list);
+
+        return R.ok().put("data",new PageInfo<>(list));
     }
 
     /**
@@ -53,8 +65,8 @@ public class FsCourseAnswerLogsController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('course:courseAnswerLog:export')")
     @Log(title = "答题日志", businessType = BusinessType.EXPORT)
-    @GetMapping("/export")
-    public AjaxResult export(FsCourseAnswerLogsParam param)
+    @PostMapping("/export")
+    public AjaxResult export(@RequestBody FsCourseAnswerLogsParam param)
     {
         if (param.getPhoneMk() != null && param.getPhoneMk() != "") {
             param.setPhone(encryptPhone(param.getPhoneMk()));

+ 38 - 0
fs-admin/src/main/java/com/fs/course/controller/FsCourseFinishTempController.java

@@ -6,14 +6,22 @@ import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.service.impl.CompanyUserServiceImpl;
+import com.fs.company.vo.DocCompanyUserVO;
 import com.fs.course.domain.FsCourseFinishTemp;
 import com.fs.course.service.IFsCourseFinishTempService;
 import com.fs.course.vo.FsCourseFinishTempListVO;
+import com.fs.sop.domain.QwSopTemp;
+import com.fs.voice.utils.StringUtil;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 /**
  * 完课模板Controller
@@ -27,6 +35,8 @@ public class FsCourseFinishTempController extends BaseController
 {
     @Autowired
     private IFsCourseFinishTempService fsCourseFinishTempService;
+    @Autowired
+    private CompanyUserServiceImpl companyUserService;
 
     /**
      * 查询完课模板列表
@@ -37,6 +47,34 @@ public class FsCourseFinishTempController extends BaseController
     {
         startPage();
         List<FsCourseFinishTempListVO> list = fsCourseFinishTempService.selectFsCourseFinishTempListVO(fsCourseFinishTemp);
+        // 收集所有需要查询的用户ID
+        Set<Long> userIds = list.stream()
+                .map(FsCourseFinishTempListVO::getCreateBy)
+                .filter(str -> !StringUtil.strIsNullOrEmpty(str)) // 取反,保留非空值
+                .map(Long::valueOf)
+                .collect(Collectors.toSet());
+
+        if (!userIds.isEmpty()){
+            // 批量查询用户信息
+            Map<Long, DocCompanyUserVO> userMap = companyUserService
+                    .selectDocCompanyUserListByUserIds(userIds)
+                    .stream()
+                    .collect(Collectors.toMap(DocCompanyUserVO::getUserId, Function.identity()));
+
+
+            list.forEach(item->{
+
+                if (!StringUtil.strIsNullOrEmpty(item.getCreateBy())) {
+                    DocCompanyUserVO user = userMap.get(Long.valueOf(item.getCreateBy()));
+                    if (user != null) {
+                        item.setCreateByName(user.getNickName());
+                        item.setCreateByDeptName(user.getDeptName());
+                    }
+                }
+
+            });
+        }
+
         return getDataTable(list);
     }
 

+ 24 - 0
fs-admin/src/main/java/com/fs/course/controller/FsCoursePlaySourceConfigController.java

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
@@ -134,6 +135,29 @@ public class FsCoursePlaySourceConfigController extends BaseController {
         return AjaxResult.success();
     }
 
+    @PreAuthorize("@ss.hasPermi('course:playSourceConfig:bind')")
+    @Log(title = "绑定支付配置", businessType = BusinessType.UPDATE)
+    @PutMapping("/updateBindConfig")
+    public AjaxResult updateBindConfig(@RequestBody FsCoursePlaySourceConfigEditParam param) {
+
+        FsCoursePlaySourceConfig update =new FsCoursePlaySourceConfig();
+        update.setId(param.getId());
+        update.setMerchantConfigId(param.getMerchantConfigId());
+        fsCoursePlaySourceConfigService.updateById(update);
+        return AjaxResult.success();
+    }
+
+    @PreAuthorize("@ss.hasPermi('course:playSourceConfig:unbind')")
+    @Log(title = "解绑支付配置", businessType = BusinessType.UPDATE)
+    @PutMapping("/updateUnbindConfig")
+    public AjaxResult updateUnbindConfig(@RequestBody FsCoursePlaySourceConfigEditParam param) {
+        LambdaUpdateWrapper<FsCoursePlaySourceConfig> updateWrapper = Wrappers.lambdaUpdate();
+        updateWrapper.eq(FsCoursePlaySourceConfig::getId, param.getId())
+                .set(FsCoursePlaySourceConfig::getMerchantConfigId, null);
+        fsCoursePlaySourceConfigService.update(updateWrapper);
+        return AjaxResult.success();
+    }
+
     @PreAuthorize("@ss.hasPermi('course:playSourceConfig:remove')")
     @Log(title = "点播播放源配置", businessType = BusinessType.DELETE)
     @DeleteMapping("/{ids}")

+ 56 - 18
fs-admin/src/main/java/com/fs/course/controller/FsCourseRedPacketLogController.java

@@ -1,32 +1,27 @@
 package com.fs.course.controller;
 
-import java.util.ArrayList;
-import java.util.List;
-
+import java.util.*;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.json.JSONUtil;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.model.LoginUser;
 import com.fs.common.utils.ServletUtils;
 import com.fs.course.config.CourseConfig;
+import com.fs.course.domain.FsUserCoursePeriod;
 import com.fs.course.mapper.FsUserCourseMapper;
 import com.fs.course.mapper.FsUserCourseVideoMapper;
 import com.fs.course.param.FsCourseRedPacketLogParam;
+import com.fs.course.service.IFsUserCoursePeriodService;
 import com.fs.course.vo.FsCourseRedPacketLogListPVO;
 import com.fs.framework.web.service.TokenService;
 import com.fs.his.utils.PhoneUtil;
 import com.fs.his.vo.OptionsVO;
 import com.fs.system.service.ISysConfigService;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
@@ -50,6 +45,9 @@ public class FsCourseRedPacketLogController extends BaseController
 {
     @Autowired
     private IFsCourseRedPacketLogService fsCourseRedPacketLogService;
+
+    @Autowired
+    private IFsUserCoursePeriodService fsUserCoursePeriodService;
     @Autowired
     FsUserCourseMapper fsUserCourseMapper;
     @Autowired
@@ -62,21 +60,58 @@ public class FsCourseRedPacketLogController extends BaseController
      * 查询短链课程看课记录列表
      */
     @PreAuthorize("@ss.hasPermi('course:courseRedPacketLog:list')")
-    @GetMapping("/list")
-    public TableDataInfo list(FsCourseRedPacketLogParam fsCourseRedPacketLog)
+    @PostMapping("/list")
+    public R list(@RequestBody FsCourseRedPacketLogParam fsCourseRedPacketLog)
     {
-        startPage();
+        if(fsCourseRedPacketLog.getPageNum() == null) {
+            fsCourseRedPacketLog.setPageNum(1);
+        }
+        if(fsCourseRedPacketLog.getPageSize() == null) {
+            fsCourseRedPacketLog.setPageSize(10);
+        }
+
 
         if (fsCourseRedPacketLog.getPhoneMk() != null && fsCourseRedPacketLog.getPhoneMk() != "") {
             fsCourseRedPacketLog.setPhone(encryptPhone(fsCourseRedPacketLog.getPhoneMk()));
         }
+        PageHelper.startPage(fsCourseRedPacketLog.getPageNum(), fsCourseRedPacketLog.getPageSize());
 
         List<FsCourseRedPacketLogListPVO> list = fsCourseRedPacketLogService.selectFsCourseRedPacketLogListVO(fsCourseRedPacketLog);
         for (FsCourseRedPacketLogListPVO fsCourseRedPacketLogListPVO : list) {
+            if (ObjectUtil.isNotEmpty(fsCourseRedPacketLogListPVO.getPeriodId())){
+                FsUserCoursePeriod fsUserCoursePeriod = fsUserCoursePeriodService.selectFsUserCoursePeriodById(fsCourseRedPacketLogListPVO.getPeriodId().longValue());
+                fsCourseRedPacketLogListPVO.setPeriodName(fsUserCoursePeriod.getPeriodName());
+            }
+            fsCourseRedPacketLogListPVO.setPhone(PhoneUtil.decryptAutoPhoneMk(fsCourseRedPacketLogListPVO.getPhone()));
+        }
+        return R.ok().put("data",new PageInfo<>(list));
+    }
+
+    /**
+     * 查询短链课程看课记录列表分页
+     * 与上面请求方式不同,list集合数据在get传输掉包
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseRedPacketLog:pageList')")
+    @PostMapping("/pageList")
+    public R pageList(@RequestBody FsCourseRedPacketLogParam fsCourseRedPacketLog)
+    {
+
+        if (fsCourseRedPacketLog.getPhoneMk() != null && fsCourseRedPacketLog.getPhoneMk() != "") {
+            fsCourseRedPacketLog.setPhone(encryptPhone(fsCourseRedPacketLog.getPhoneMk()));
+        }
 
+        PageHelper.startPage(fsCourseRedPacketLog.getPageNum(), fsCourseRedPacketLog.getPageSize());
+
+        List<FsCourseRedPacketLogListPVO> list = fsCourseRedPacketLogService.selectFsCourseRedPacketLogListVO(fsCourseRedPacketLog);
+        for (FsCourseRedPacketLogListPVO fsCourseRedPacketLogListPVO : list) {
+            if (ObjectUtil.isNotEmpty(fsCourseRedPacketLogListPVO.getPeriodId())){
+                FsUserCoursePeriod fsUserCoursePeriod = fsUserCoursePeriodService.selectFsUserCoursePeriodById(fsCourseRedPacketLogListPVO.getPeriodId().longValue());
+                fsCourseRedPacketLogListPVO.setPeriodName(fsUserCoursePeriod.getPeriodName());
+            }
             fsCourseRedPacketLogListPVO.setPhone(PhoneUtil.decryptAutoPhoneMk(fsCourseRedPacketLogListPVO.getPhone()));
         }
-        return getDataTable(list);
+
+        return R.ok().put("data", new PageInfo<>(list));
     }
 
     /**
@@ -84,15 +119,18 @@ public class FsCourseRedPacketLogController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('course:courseRedPacketLog:export')")
     @Log(title = "短链课程看课记录", businessType = BusinessType.EXPORT)
-    @GetMapping("/export")
-    public AjaxResult export(FsCourseRedPacketLogParam fsCourseRedPacketLog)
+    @PostMapping("/export")
+    public AjaxResult export(@RequestBody FsCourseRedPacketLogParam fsCourseRedPacketLog)
     {
         if (fsCourseRedPacketLog.getPhoneMk()!=null&&fsCourseRedPacketLog.getPhoneMk()!=""){
             fsCourseRedPacketLog.setPhone(encryptPhone(fsCourseRedPacketLog.getPhoneMk()));
         }
         List<FsCourseRedPacketLogListPVO> list = fsCourseRedPacketLogService.selectFsCourseRedPacketLogListVO(fsCourseRedPacketLog);
         for (FsCourseRedPacketLogListPVO fsCourseRedPacketLogListPVO : list) {
-
+            if (ObjectUtil.isNotEmpty(fsCourseRedPacketLogListPVO.getPeriodId())){
+                FsUserCoursePeriod fsUserCoursePeriod = fsUserCoursePeriodService.selectFsUserCoursePeriodById(fsCourseRedPacketLogListPVO.getPeriodId().longValue());
+                fsCourseRedPacketLogListPVO.setPeriodName(fsUserCoursePeriod.getPeriodName());
+            }
             fsCourseRedPacketLogListPVO.setPhone(PhoneUtil.decryptAutoPhoneMk(fsCourseRedPacketLogListPVO.getPhone()));
         }
         ExcelUtil<FsCourseRedPacketLogListPVO> util = new ExcelUtil<FsCourseRedPacketLogListPVO>(FsCourseRedPacketLogListPVO.class);

+ 55 - 4
fs-admin/src/main/java/com/fs/course/controller/FsCourseWatchLogController.java

@@ -2,8 +2,12 @@ package com.fs.course.controller;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
+import cn.hutool.core.collection.CollectionUtil;
 import com.fs.common.constant.HttpStatus;
+import com.fs.common.core.domain.R;
 import com.fs.common.exception.CustomException;
 import com.fs.common.utils.ServletUtils;
 import com.fs.course.param.FsCourseOverParam;
@@ -16,6 +20,8 @@ import com.fs.course.vo.FsCourseWatchLogListVO;
 import com.fs.course.vo.FsCourseWatchLogStatisticsListVO;
 import com.fs.qw.param.QwWatchLogStatisticsListParam;
 import com.fs.qw.service.IQwWatchLogService;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -58,10 +64,29 @@ public class FsCourseWatchLogController extends BaseController
     public TableDataInfo list(FsCourseWatchLogListParam param)
     {
         startPage();
+        if(CollectionUtil.isNotEmpty(param.getUserIds())){
+            param.setUserIds(param.getUserIds().stream()
+                    .filter(userId -> userId != null && userId.startsWith("user_"))
+                    .map(userId -> userId.substring(5))
+                    .collect(Collectors.toList())
+            );
+        }
         List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVO(param);
         return getDataTable(list);
     }
 
+    /**
+     * 查询短链课程看课记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:pageList')")
+    @PostMapping("/pageList")
+    public R pageList(@RequestBody FsCourseWatchLogListParam param)
+    {
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVO(param);
+        return R.ok().put("data", new PageInfo<>(list));
+    }
+
     @GetMapping("/qwWatchLogAllStatisticsList")
     public TableDataInfo qwWatchLogAllStatisticsList(QwWatchLogStatisticsListParam param)
     {
@@ -111,13 +136,39 @@ public class FsCourseWatchLogController extends BaseController
         return getDataTable(list);
     }
 
+    /**
+     * 会员看课统计导出
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:statisticsExport')")
+    @Log(title = "会员看课统计导出", businessType = BusinessType.EXPORT)
+    @PostMapping("/statisticsExport")
+    public AjaxResult statisticsExport(@RequestBody FsCourseWatchLogStatisticsListParam param)
+    {
+
+        // 如果看指定用户就不用设置公司
+        if(param.getCompanyId() == null){
+            if(param.getUserId() == null) {
+                throw new CustomException("查看公司或者用户必填!");
+            }
+        }
+        if (param.getSTime()==null||param.getETime()==null){
+            throw new CustomException("必须选择开始时间和结束时间!");
+        }
+
+        List<FsCourseWatchLogStatisticsListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogStatisticsListVONew(param);
+
+        ExcelUtil<FsCourseWatchLogStatisticsListVO> util = new ExcelUtil<FsCourseWatchLogStatisticsListVO>(FsCourseWatchLogStatisticsListVO.class);
+        return util.exportExcel(list, "会员看课统计");
+    }
+
+
     /**
      * 导出短链课程看课记录列表
      */
     @PreAuthorize("@ss.hasPermi('course:courseWatchLog:export')")
     @Log(title = "短链课程看课记录", businessType = BusinessType.EXPORT)
-    @GetMapping("/export")
-    public AjaxResult export(FsCourseWatchLogListParam param)
+    @PostMapping("/export")
+    public AjaxResult export(@RequestBody FsCourseWatchLogListParam param)
     {
         List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVO(param);
         ExcelUtil<FsCourseWatchLogListVO> util = new ExcelUtil<FsCourseWatchLogListVO>(FsCourseWatchLogListVO.class);
@@ -177,7 +228,7 @@ public class FsCourseWatchLogController extends BaseController
         List<FsCourseOverVO> list = fsCourseWatchLogService.selectFsCourseWatchLogOverStatisticsListVO(param);
         return getDataTable(list);
     }
-    
+
     /**
      * 每个公司对应小程序观看人数统计
      *
@@ -199,7 +250,7 @@ public class FsCourseWatchLogController extends BaseController
      */
     @GetMapping("/getUserIdList")
     public TableDataInfo getUserIdList( FsCourseWatchLogParam fsCourseWatchLog) {
-        startPage(); 
+        startPage();
         List<FsCoureseWatchLogVO> list = fsCourseWatchLogService.selectUserIdList(fsCourseWatchLog);
         return getDataTable(list);
     }

+ 44 - 0
fs-admin/src/main/java/com/fs/course/controller/FsUserCoursePeriodController.java

@@ -1,5 +1,6 @@
 package com.fs.course.controller;
 
+import com.fs.baidu.domain.BdAccount;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
@@ -14,6 +15,8 @@ import com.fs.course.param.*;
 import com.fs.course.service.IFsUserCoursePeriodDaysService;
 import com.fs.course.service.IFsUserCoursePeriodService;
 import com.fs.course.service.IFsUserCourseVideoRedPackageService;
+import com.fs.course.vo.*;
+import com.fs.course.vo.newfs.FsCourseAnalysisCountVO;
 import com.fs.course.vo.FsPeriodCountVO;
 import com.fs.course.vo.FsUserCoursePeriodVO;
 import com.fs.course.vo.PeriodRedPacketVO;
@@ -28,6 +31,9 @@ import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -264,6 +270,44 @@ public class FsUserCoursePeriodController extends BaseController {
         return R.ok(result);
     }
 
+    /**
+     * @Description: 会员营期导出课程统计
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2025/12/12 11:37
+     */
+    @PostMapping("/exportInfo")
+    @ApiOperation("营期统计")
+    public AjaxResult exportInfo(@RequestBody PeriodCountParam param) {
+        List<FsPeriodCountExportVO> exportList = new ArrayList<>();
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        List<FsPeriodCountVO> list = fsUserCoursePeriodDaysService.periodCourseCount(param);
+        list.forEach(item -> {
+            FsPeriodCountExportVO exportVO = new FsPeriodCountExportVO();
+            exportVO.setTitle(item.getTitle());
+            exportVO.setDayDate(item.getDayDate());
+            if(item.getCountDetailsVO() != null){
+                FsCourseAnalysisCountVO countDetailsVO =item.getCountDetailsVO();
+                exportVO.setCourseWatchTimes(countDetailsVO.getCourseWatchTimes());
+                exportVO.setCourseCompleteTimes(countDetailsVO.getCourseCompleteTimes());
+                exportVO.setCourseWatchNum(countDetailsVO.getCourseWatchNum());
+                exportVO.setCourseCompleteNum(countDetailsVO.getCourseCompleteNum());
+                exportVO.setCompleteRate(countDetailsVO.getCompleteRate()+"%");
+                exportVO.setAnswerTimes(countDetailsVO.getAnswerTimes());
+                exportVO.setAnswerNum(countDetailsVO.getAnswerNum());
+                exportVO.setAnswerRightNum(countDetailsVO.getAnswerRightNum());
+                exportVO.setAnswerRightRate(countDetailsVO.getAnswerRightRate()+"%");
+                exportVO.setRedPacketNum(countDetailsVO.getRedPacketNum());
+                exportVO.setRedPacketAmount(countDetailsVO.getRedPacketAmount());
+            }
+            exportList.add(exportVO);
+        });
+
+        ExcelUtil<FsPeriodCountExportVO> util = new ExcelUtil<FsPeriodCountExportVO>(FsPeriodCountExportVO.class);
+        return util.exportExcel(exportList, "百度账号数据");
+    }
+
     @GetMapping("/getPeriodListLikeName")
     public R getPeriodListLikeName(@RequestParam(required = false) String name,
                                    @RequestParam(required = false) Long campId,

+ 46 - 3
fs-admin/src/main/java/com/fs/course/controller/FsUserVideoController.java

@@ -13,6 +13,7 @@ import com.fs.course.domain.FsUserCourseVideo;
 import com.fs.course.domain.FsUserVideo;
 import com.fs.course.domain.FsVideoResource;
 import com.fs.course.param.*;
+import com.fs.course.service.HsyAssumeRoleService;
 import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.course.service.IFsUserVideoService;
 import com.fs.course.service.IFsVideoResourceService;
@@ -23,17 +24,18 @@ import com.fs.his.service.IFsPackageService;
 import com.fs.his.vo.FsPackageVO;
 import com.fs.system.oss.CloudStorageService;
 import com.fs.system.oss.OSSFactory;
+import com.volcengine.model.response.AssumeRoleResponse;
+import com.volcengine.service.vod.IVodService;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.io.FilenameUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
 import java.io.*;
-import java.util.Date;
-import java.util.List;
-import java.util.UUID;
+import java.util.*;
 
 /**
  * 课堂视频Controller
@@ -60,6 +62,12 @@ public class FsUserVideoController extends BaseController
     @Autowired
     private IFsUserCourseVideoService fsUserCourseVideoService;
 
+
+
+    @Autowired
+    private RedisTemplate redisTemplate;
+    @Autowired
+    private HsyAssumeRoleService hsyAssumeRoleService;
     /**
      * 查询课堂视频列表
      */
@@ -389,4 +397,39 @@ public class FsUserVideoController extends BaseController
             return R.error("服务器内部错误");
         }
     }
+
+
+    @PreAuthorize("@ss.hasPermi('course:userVideo:uploadUserVideo')")
+    @Log(title = "课堂视频", businessType = BusinessType.INSERT)
+    @PostMapping("/uploadUserVideo")
+    public AjaxResult upload(@RequestParam("file") MultipartFile file, @RequestParam("uploadId")String uploadId) {
+        String vid = fsUserVideoService.uploadVideo(file, uploadId);
+
+        Map<String, Object> res = new HashMap<>();
+        res.put("uploadId", uploadId);
+        res.put("vid", vid);
+        return new AjaxResult(200, "上传成功", res);
+    }
+
+
+    @GetMapping("/uploadProgress")
+    public AjaxResult getUploadProgress(@RequestParam String uploadId) {
+        Object percentObj = redisTemplate.opsForValue()
+                .get("vod:upload:" + uploadId);
+
+        int percent = 0;
+        if (percentObj != null) {
+            percent = Integer.parseInt(percentObj.toString());
+        }
+
+        Map<String, Object> res = new HashMap<>();
+        res.put("percent", percent);
+        return AjaxResult.success(res);
+    }
+
+    @GetMapping("/HsyAssumeRoleService")
+    public AjaxResult HsyAssumeRoleService() throws Exception {
+        AssumeRoleResponse roleResponse = hsyAssumeRoleService.getRoleResponse();
+        return AjaxResult.success(roleResponse);
+    }
 }

+ 57 - 21
fs-admin/src/main/java/com/fs/course/controller/FsVideoResourceController.java

@@ -3,6 +3,7 @@ package com.fs.course.controller;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.json.JSONUtil;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
@@ -11,16 +12,21 @@ import com.fs.common.core.domain.model.LoginUser;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.ServletUtils;
+import com.fs.config.cloud.CloudHostProper;
+import com.fs.course.business.FsVideoResourceBusinessService;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.domain.FsUserCourseVideo;
 import com.fs.course.domain.FsVideoResource;
 import com.fs.course.service.IFsUserCourseVideoService;
+import com.fs.course.service.IFsUserVideoService;
+import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.course.service.IFsVideoResourceService;
 import com.fs.course.vo.FsVideoResourceVO;
 import com.fs.framework.web.service.TokenService;
 import com.fs.system.service.ISysConfigService;
 import com.github.pagehelper.PageHelper;
 import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
@@ -32,6 +38,7 @@ import java.util.stream.Collectors;
 /**
  * 资源库管理
  */
+@Slf4j
 @RestController
 @RequestMapping("/course/videoResource")
 @AllArgsConstructor
@@ -40,11 +47,16 @@ public class FsVideoResourceController extends BaseController {
     private final IFsVideoResourceService fsVideoResourceService;
     @Autowired
     private IFsUserCourseVideoService fsUserCourseVideoService;
-    @Autowired
-    private TokenService tokenService;
 
+    private final TokenService tokenService;
+
+    private final ISysConfigService configService;
+
+    private final CloudHostProper cloudHostProper;
+
+    private final FsVideoResourceBusinessService videoResourceBusinessService;
     @Autowired
-    private ISysConfigService configService;
+    private IFsUserVideoService fsUserVideoService;
 
     /**
      * 查询视频素材库列表
@@ -56,8 +68,7 @@ public class FsVideoResourceController extends BaseController {
                               @RequestParam(required = false) Integer typeId,
                               @RequestParam(required = false) Integer typeSubId,
                               @RequestParam(required = false, defaultValue = "1") Integer pageNum,
-                              @RequestParam(required = false, defaultValue = "10") Integer pageSize)
-    {
+                              @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
         Map<String, Object> params = new HashMap<>();
         params.put("resourceName", resourceName);
         params.put("fileName", fileName);
@@ -66,7 +77,7 @@ public class FsVideoResourceController extends BaseController {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         String json = configService.selectConfigByKey("course.config");
         CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
-        if (ObjectUtil.isNotEmpty(config.getIsBound())&&config.getIsBound()){
+        if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
             params.put("userId", loginUser.getUser().getUserId());
         }
         PageHelper.startPage(pageNum, pageSize);
@@ -80,8 +91,7 @@ public class FsVideoResourceController extends BaseController {
      */
     @PreAuthorize("@ss.hasPermi('course:videoResource:query')")
     @GetMapping(value = "/{id}")
-    public AjaxResult getInfo(@PathVariable("id") Long id)
-    {
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
         return AjaxResult.success(fsVideoResourceService.getById(id));
     }
 
@@ -91,17 +101,25 @@ public class FsVideoResourceController extends BaseController {
     @PreAuthorize("@ss.hasPermi('course:videoResource:add')")
     @Log(title = "视频素材库", businessType = BusinessType.INSERT)
     @PostMapping
-    public AjaxResult add(@RequestBody FsVideoResource fsVideoResource)
-    {
+    public AjaxResult add(@RequestBody FsVideoResource fsVideoResource) {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         Long userId = loginUser.getUser().getUserId();
         String json = configService.selectConfigByKey("course.config");
         CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
-        if (ObjectUtil.isNotEmpty(config.getIsBound())&&config.getIsBound()){
+        if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
             fsVideoResource.setUserId(userId);
         }
+
         fsVideoResource.setCreateTime(LocalDateTime.now());
-        fsVideoResourceService.save(fsVideoResource);
+        boolean save = fsVideoResourceService.save(fsVideoResource);
+        if (save&&StringUtils.isNotEmpty(fsVideoResource.getHsyVid())){
+            try {
+                fsUserCourseVideoService.updateMediaPublishStatus(fsVideoResource.getHsyVid());
+                log.info("更新视频发布状态成功,hsyVid: {}", fsVideoResource.getHsyVid());
+            } catch (Exception e) {
+                log.error("更新视频发布状态失败,hsyVid: {}, 错误: {}", fsVideoResource.getHsyVid(), e.getMessage());
+            }
+        }
         return AjaxResult.success();
     }
 
@@ -118,6 +136,11 @@ public class FsVideoResourceController extends BaseController {
         fsVideoResource.setUpdateTime(LocalDateTime.now());
         //12.10要求素材名称跟着视频名称走
         fsVideoResource.setResourceName(fsVideoResource.getFileName());
+        if (("今正科技".equals(cloudHostProper.getCompanyName()))) {
+            // 同步资源到课程
+            videoResourceBusinessService.edit(fsVideoResource);
+            return AjaxResult.success();
+        }
         fsVideoResourceService.updateById(fsVideoResource);
         fsUserCourseVideoService.updateVideoByVideoUrl(fsVideoResource.getVideoUrl(),fsVideoResourceResult.getOldVideoUrl(),fsVideoResource.getThumbnail(),fsVideoResource.getFileName());
         return AjaxResult.success();
@@ -129,8 +152,7 @@ public class FsVideoResourceController extends BaseController {
     @PreAuthorize("@ss.hasPermi('course:videoResource:remove')")
     @Log(title = "视频素材库", businessType = BusinessType.DELETE)
     @DeleteMapping("/{ids}")
-    public AjaxResult remove(@PathVariable Long[] ids)
-    {
+    public AjaxResult remove(@PathVariable Long[] ids) {
         Wrapper<FsVideoResource> updateWrapper = Wrappers.<FsVideoResource>lambdaUpdate()
                 .set(FsVideoResource::getIsDel, 1)
                 .in(FsVideoResource::getId, Arrays.asList(ids));
@@ -140,6 +162,7 @@ public class FsVideoResourceController extends BaseController {
 
     /**
      * 批量修改视频分类
+     *
      * @param typeId
      * @param typeSubId
      * @param ids
@@ -151,13 +174,13 @@ public class FsVideoResourceController extends BaseController {
     public AjaxResult batchUpdateClass(@RequestParam("typeId") Long typeId,
                                        @RequestParam("typeSubId") Long typeSubId,
                                        @RequestParam("ids") String ids) {
-        if(typeId == null || typeId <= 0){
+        if (typeId == null || typeId <= 0) {
             return AjaxResult.error("请选择分类");
         }
-        if(typeSubId == null || typeSubId <= 0){
+        if (typeSubId == null || typeSubId <= 0) {
             return AjaxResult.error("请选择子分类");
         }
-        if(ids == null || ids.isEmpty()){
+        if (ids == null || ids.isEmpty()) {
             return AjaxResult.error("请选择要修改的分类");
         }
 
@@ -187,13 +210,26 @@ public class FsVideoResourceController extends BaseController {
         String json = configService.selectConfigByKey("course.config");
         CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
 
-        list.forEach(v ->{
+        list.forEach(v -> {
             v.setCreateTime(LocalDateTime.now());
-            if (ObjectUtil.isNotEmpty(config.getIsBound())&&config.getIsBound()){
+            if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
                 v.setUserId(userId);
             }
-        } );
-        fsVideoResourceService.saveBatch(list);
+        });
+        boolean saveStatus = fsVideoResourceService.saveBatch(list);
+        if (saveStatus) {
+            list.forEach(fsVideoResource -> {
+                // 检查hsyVid是否存在且不为空
+                if (ObjectUtil.isNotEmpty(fsVideoResource.getHsyVid())) {
+                    try {
+                        fsUserCourseVideoService.updateMediaPublishStatus(fsVideoResource.getHsyVid());
+                        log.info("更新视频发布状态成功,hsyVid: {}", fsVideoResource.getHsyVid());
+                    } catch (Exception e) {
+                        log.error("更新视频发布状态失败,hsyVid: {}, 错误: {}", fsVideoResource.getHsyVid(), e.getMessage());
+                    }
+                }
+            });
+        }
         return AjaxResult.success();
     }
 }

+ 27 - 0
fs-admin/src/main/java/com/fs/course/controller/qw/QwFsCourseWatchLogController.java

@@ -89,6 +89,33 @@ public class QwFsCourseWatchLogController extends BaseController
         return getDataTable(list);
     }
 
+    /**
+     * 会员看课统计导出
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:statisticsExport')")
+    @Log(title = "企微看课统计导出", businessType = BusinessType.EXPORT)
+    @PostMapping("/statisticsExport")
+    public AjaxResult statisticsExport(@RequestBody FsCourseWatchLogStatisticsListParam param)
+    {
+
+        if (param.getSTime()==null||param.getETime()==null){
+            throw new CustomException("必须选择开始时间和结束时间!");
+        }
+
+        param.setSendType(2); //企微
+        List<FsCourseWatchLogStatisticsListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogStatisticsListVO(param);
+        if("济南联志健康".equals(signProjectName)){
+            FsCourseWatchLogStatisticsListVO totalData = fsCourseWatchLogService.getTotalDataAddItem(param);
+            list.add(totalData);
+        }
+
+        ExcelUtil<FsCourseWatchLogStatisticsListVO> util = new ExcelUtil<FsCourseWatchLogStatisticsListVO>(FsCourseWatchLogStatisticsListVO.class);
+
+        return util.exportExcel(list, "企微看课统计");
+    }
+
+
+
     @GetMapping("/getSignProjectName")
     public R getSignProjectName(){
         return R.ok().put("signProjectName", signProjectName);

+ 6 - 6
fs-admin/src/main/java/com/fs/his/controller/FsAdvController.java

@@ -40,7 +40,7 @@ public class FsAdvController extends BaseController
     /**
      * 查询广告列表
      */
-    @PreAuthorize("@ss.hasPermi('his:adv:list')")
+    @PreAuthorize("@ss.hasPermi('store:adv:list') or @ss.hasPermi('his:adv:list')")
     @GetMapping("/list")
     public TableDataInfo list(FsAdv fsAdv)
     {
@@ -52,7 +52,7 @@ public class FsAdvController extends BaseController
     /**
      * 导出广告列表
      */
-    @PreAuthorize("@ss.hasPermi('his:adv:export')")
+    @PreAuthorize("@ss.hasPermi('store:adv:export') or @ss.hasPermi('his:adv:export')")
     @Log(title = "广告", businessType = BusinessType.EXPORT)
     @GetMapping("/export")
     public AjaxResult export(FsAdv fsAdv)
@@ -65,7 +65,7 @@ public class FsAdvController extends BaseController
     /**
      * 获取广告详细信息
      */
-    @PreAuthorize("@ss.hasPermi('his:adv:query')")
+    @PreAuthorize("@ss.hasPermi('store:adv:query') or @ss.hasPermi('his:adv:query')")
     @GetMapping(value = "/{advId}")
     public AjaxResult getInfo(@PathVariable("advId") String advId)
     {
@@ -76,7 +76,7 @@ public class FsAdvController extends BaseController
     /**
      * 新增广告
      */
-    @PreAuthorize("@ss.hasPermi('his:adv:add')")
+    @PreAuthorize("@ss.hasPermi('store:adv:add') or @ss.hasPermi('his:adv:add')")
     @Log(title = "广告", businessType = BusinessType.INSERT)
     @PostMapping
     public AjaxResult add(@RequestBody FsAdv fsAdv)
@@ -91,7 +91,7 @@ public class FsAdvController extends BaseController
     /**
      * 修改广告
      */
-    @PreAuthorize("@ss.hasPermi('his:adv:edit')")
+    @PreAuthorize("@ss.hasPermi('store:adv:edit') or @ss.hasPermi('his:adv:edit')")
     @Log(title = "广告", businessType = BusinessType.UPDATE)
     @PutMapping
     public AjaxResult edit(@RequestBody FsAdv fsAdv)
@@ -105,7 +105,7 @@ public class FsAdvController extends BaseController
     /**
      * 删除广告
      */
-    @PreAuthorize("@ss.hasPermi('his:adv:remove')")
+    @PreAuthorize("@ss.hasPermi('store:adv:remove') or @ss.hasPermi('his:adv:remove')")
     @Log(title = "广告", businessType = BusinessType.DELETE)
 	@DeleteMapping("/{advIds}")
     public AjaxResult remove(@PathVariable String[] advIds)

+ 1 - 0
fs-admin/src/main/java/com/fs/his/controller/FsIntegralGoodsController.java

@@ -117,6 +117,7 @@ public class FsIntegralGoodsController extends BaseController
     {
 
         redisCacheUtil.delRedisKey("getIntegralGoodsList");
+        redisCacheUtil.delSpringCacheKey("getIntegralGoodsById", fsIntegralGoods.getGoodsId());
         redisCacheUtil.delRedisKey("getIntegralGoodsById");
         return toAjax(fsIntegralGoodsService.updateFsIntegralGoods(fsIntegralGoods));
     }

+ 96 - 17
fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java

@@ -7,6 +7,8 @@ import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.entity.SysRole;
+import com.fs.common.core.domain.entity.SysUser;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.CloudHostUtils;
@@ -22,6 +24,7 @@ import com.fs.his.param.BatchSetErpOrderParam;
 import com.fs.his.vo.*;
 import com.fs.his.param.FsIntegralOrderParam;
 import com.fs.his.service.*;
+import com.fs.system.service.ISysRoleService;
 import com.fs.utils.OrderContextHolder;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -29,6 +32,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
+import lombok.extern.slf4j.Slf4j;
 
 import java.text.ParseException;
 import java.time.LocalDateTime;
@@ -45,6 +49,7 @@ import static com.fs.his.utils.PhoneUtil.decryptPhone;
  * @author fs
  * @date 2023-11-02
  */
+@Slf4j
 @RestController
 @RequestMapping("/his/integralOrder")
 public class FsIntegralOrderController extends BaseController
@@ -78,44 +83,118 @@ public class FsIntegralOrderController extends BaseController
     public TableDataInfo list(FsIntegralOrderParam fsIntegralOrder)
     {
         startPage();
+        List<FsIntegralOrderListVO> list = new ArrayList<>();
         if (CloudHostUtils.hasCloudHostName("金牛明医")){
             /*目前只有金牛有状态为6的查询,其他项目避免使用6状态码*/
             if (fsIntegralOrder.getStatus() != null && fsIntegralOrder.getStatus().equals("6")) {
                 fsIntegralOrder.setStatus("1");
                 fsIntegralOrder.setIsPush(0);
             }
-            List<FsIntegralOrderListVO> list = fsIntegralOrderService.selectFsIntegralOrderListByJn(fsIntegralOrder);
+
+            // 金牛明医项目:支持多个订单ID查询
+//            if (fsIntegralOrder.getOrderCodes() != null && !fsIntegralOrder.getOrderCodes().isEmpty()) {
+//                // 如果传了orderIds参数,使用新的查询逻辑
+//                List<FsIntegralOrder> orders = fsIntegralOrderService.selectFsIntegralOrderByOrderIdsv2(fsIntegralOrder.getOrderCodes());
+//                List<FsIntegralOrderListVO> list = orders.stream()
+//                    .map(this::convertOrderToListVO)
+//                    .collect(Collectors.toList());
+//                for (FsIntegralOrderListVO vo : list) {
+//                    vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
+//                }
+//                return getDataTable(list);
+//            } else {
+//                // 原有逻辑:单个orderId或其他条件查询
+//                List<FsIntegralOrderListVO> list = fsIntegralOrderService.selectFsIntegralOrderListByJn(fsIntegralOrder);
+//                for (FsIntegralOrderListVO vo : list) {
+//                    vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
+//                }
+//                return getDataTable(list);
+//            }
+            list = fsIntegralOrderService.selectFsIntegralOrderListByJn(fsIntegralOrder);
+        } else {
+            list = fsIntegralOrderService.selectFsIntegralOrderListVO(fsIntegralOrder);
+        }
+        SysRole sysRole = isCheckPermission();
+        if (sysRole != null && !(sysRole.getIsCheckPhone()==1)) {
             for (FsIntegralOrderListVO vo : list) {
                 vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
             }
-            return getDataTable(list);
-        }
-        List<FsIntegralOrderListVO> list = fsIntegralOrderService.selectFsIntegralOrderListVO(fsIntegralOrder);
-        for (FsIntegralOrderListVO vo : list) {
-            vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
         }
         return getDataTable(list);
     }
 
+    @Autowired
+    private ISysRoleService sysRoleService;
+    private SysRole isCheckPermission() {
+        SysRole sysRole = new SysRole();
+        SysUser user = getLoginUser().getUser();
+        boolean flag = user.isAdmin();
+        if (flag) {
+            sysRole.setIsCheckPhone(1);
+            sysRole.setIsCheckAddress(1);
+        } else {
+            List<SysRole> roles = user.getRoles();
+            if (roles != null && !roles.isEmpty()) {
+                Long[] roleIds = roles.stream().map(SysRole::getRoleId).toArray(Long[]::new);
+                return sysRoleService.getIsCheckPermission(roleIds);
+            }
+        }
+        return sysRole;
+    }
+
     /**
      * 导出积分商品订单列表
      */
     @PreAuthorize("@ss.hasPermi('his:integralOrder:export')")
     @Log(title = "积分商品订单", businessType = BusinessType.EXPORT)
     @GetMapping("/export")
-    public AjaxResult export(FsIntegralOrder fsIntegralOrder)
-    {
-        if (CloudHostUtils.hasCloudHostName("金牛明医")&&fsIntegralOrder.getStatus() != null && fsIntegralOrder.getStatus().equals(6)) {
+    public AjaxResult export(FsIntegralOrderParam fsIntegralOrder) {
+        List<FsIntegralOrderListVO> fsIntegralOrderListVOS = new ArrayList<>();
+        if (CloudHostUtils.hasCloudHostName("金牛明医")){
             /*目前只有金牛有状态为6的查询,其他项目避免使用6状态码*/
-            FsIntegralOrderParam param = new FsIntegralOrderParam();
-            BeanUtil.copyProperties(fsIntegralOrder, param);
-            param.setStatus("1");
-            param.setIsPush(0);
-            List<FsIntegralOrderListVO> fsIntegralOrderListVOS = fsIntegralOrderService.selectFsIntegralOrderListByJn(param);
-            ExcelUtil<FsIntegralOrderListVO> util = new ExcelUtil<>(FsIntegralOrderListVO.class);
-            return util.exportExcel(new ArrayList<>(fsIntegralOrderListVOS), "积分商品订单数据");
+            if (fsIntegralOrder.getStatus() != null && fsIntegralOrder.getStatus().equals("6")) {
+                fsIntegralOrder.setStatus("1");
+                fsIntegralOrder.setIsPush(0);
+            }
+            fsIntegralOrderListVOS = fsIntegralOrderService.selectFsIntegralOrderListByJn(fsIntegralOrder);
+        } else {
+            fsIntegralOrderListVOS = fsIntegralOrderService.selectFsIntegralOrderListVO(fsIntegralOrder);
+        }
+        SysRole sysRole = isCheckPermission();
+        // 处理商品名称:解析item_json并设置goodsName
+        for (FsIntegralOrderListVO vo : fsIntegralOrderListVOS) {
+            if (StringUtils.isNotEmpty(vo.getItemJson())) {
+                try {
+                    // 尝试解析JSON格式的商品信息
+                    if (vo.getItemJson().startsWith("[")) {
+                        // 数组格式,取第一个商品
+                        com.alibaba.fastjson.JSONArray jsonArray = com.alibaba.fastjson.JSONArray.parseArray(vo.getItemJson());
+                        if (jsonArray != null && !jsonArray.isEmpty()) {
+                            com.alibaba.fastjson.JSONObject goods = jsonArray.getJSONObject(0);
+                            if (goods != null && goods.getString("goodsName") != null) {
+                                vo.setGoodsName(goods.getString("goodsName"));
+                            }
+                        }
+                    } else if (vo.getItemJson().startsWith("{")) {
+                        // 对象格式
+                        com.alibaba.fastjson.JSONObject goods = com.alibaba.fastjson.JSONObject.parseObject(vo.getItemJson());
+                        if (goods != null && goods.getString("goodsName") != null) {
+                            vo.setGoodsName(goods.getString("goodsName"));
+                        }
+                    }
+                } catch (Exception e) {
+                    // 解析失败时保持goodsName为空,避免导出出错
+                    log.warn("解析商品信息失败,订单编号:{}, 商品信息:{}", vo.getOrderCode(), vo.getItemJson());
+                }
+            }
+            if (!(sysRole.getIsCheckPhone()==1)){
+                // 加密手机号
+                vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
+            }
+
         }
-        return fsIntegralOrderService.export(fsIntegralOrder);
+        ExcelUtil<FsIntegralOrderListVO> util = new ExcelUtil<>(FsIntegralOrderListVO.class);
+        return util.exportExcel(new ArrayList<>(fsIntegralOrderListVOS), "积分商品订单数据");
     }
     /**
      * 发货

+ 41 - 5
fs-admin/src/main/java/com/fs/his/controller/FsStoreOrderController.java

@@ -47,6 +47,7 @@ import com.fs.system.mapper.SysConfigMapper;
 import com.fs.system.service.ISysRoleService;
 import com.github.pagehelper.PageHelper;
 import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -73,6 +74,7 @@ import static com.fs.his.utils.PhoneUtil.*;
  */
 @RestController
 @RequestMapping("/his/storeOrder")
+@Slf4j
 public class FsStoreOrderController extends BaseController
 {
     @Autowired
@@ -128,6 +130,9 @@ public class FsStoreOrderController extends BaseController
     private IFsDfAccountService fsDfAccountService;
     @Autowired
     private FsUserInformationCollectionMapper userInformationCollectionMapper;
+    @Autowired
+    private IFsPackageOrderService fsPackageOrderService;
+
     /**
      * 查询订单列表
      */
@@ -418,6 +423,14 @@ public class FsStoreOrderController extends BaseController
             tuiMoneyLogs=moneyLogsService.selectCompanyStoreOrderMoneyLogsList(moneyLogsMap);
         }
         if ((CloudHostUtils.hasCloudHostName("金牛明医"))){
+            if (order.getStatus() == 2 ){
+                FsUserInfoCollectionAndStoreOrderVo infoVo = fsPackageOrderService.selectInformationCollectionByStoreOrderId(orderId);
+                if (infoVo!=null) {
+                    if (infoVo.getDoctorType2Confirm() == null || infoVo.getDoctorType2Confirm()!=1) {
+                        order.setStatus(7);
+                    }
+                }
+            }
             return R.ok().put("data",order).put("tuiMoneyLogs",tuiMoneyLogs).put("isUpdateRefund",1).put("isUpdatePayRemain",1);
         } else {
             return R.ok().put("data",order).put("tuiMoneyLogs",tuiMoneyLogs).put("isUpdateRefund",0).put("isUpdatePayRemain",0);
@@ -479,6 +492,17 @@ public class FsStoreOrderController extends BaseController
     {
         AjaxResult error = moneyCheck(fsStoreOrder);
         if (error != null) return error;
+        try {
+            FsUserInfoCollectionAndStoreOrderVo infoVo = fsPackageOrderService.selectInformationCollectionByStoreOrderId(fsStoreOrder.getOrderId());
+            if (infoVo != null) {
+                Integer doctorType2Confirm = infoVo.getDoctorType2Confirm();
+                if (doctorType2Confirm == null || doctorType2Confirm != 1) {
+                    return AjaxResult.error("信息采集订单药师未确认,不能修改状态!");
+                }
+            }
+        } catch (Exception e) {
+            log.error("信息采集查询错误,orderId: " + fsStoreOrder.getOrderId() + ",信息:{}"+ e);
+        }
         return toAjax(fsStoreOrderService.updateFsStoreOrder(fsStoreOrder));
     }
 
@@ -652,8 +676,8 @@ public class FsStoreOrderController extends BaseController
     {
         String nickName = getLoginUser().getUser().getNickName();
         if(CloudHostUtils.hasCloudHostName("金牛明医")){
-            FsUserInformationCollection fsUserInformationCollection = userInformationCollectionMapper.selectFsUserInformationCollectionByOrderCode(fsStoreOrder.getOrderCode());
-            if (fsUserInformationCollection != null&&fsUserInformationCollection.getDoctorType2Confirm()!=1) {
+            FsUserInfoCollectionAndStoreOrderVo info = fsPackageOrderService.selectInformationCollectionByStoreOrderId(fsStoreOrder.getOrderId());
+            if (info != null&&info.getDoctorType2Confirm()!=1) {
                 return AjaxResult.error("药师未确认");
             }
         }
@@ -760,8 +784,16 @@ public class FsStoreOrderController extends BaseController
         if (orderIds.isEmpty()){
             return R.ok();
         }
-        orderIds.forEach(orderId->{
+        //失败的订单
+        StringBuilder msg = new StringBuilder();
+        for (Long orderId : orderIds) {
             try {
+                //判断是否是信息采集订单 且 药师已经确认
+                FsUserInfoCollectionAndStoreOrderVo info = fsPackageOrderService.selectInformationCollectionByStoreOrderId(orderId);
+                if (info != null&&info.getDoctorType2Confirm()!=1) {
+                    msg.append("订单编号:").append(info.getStoreOrderCode()).append(",推送erp失败,失败原因-信息采集订单,药师暂未确认!\n");
+                    continue;
+                }
                 df.setOrderId(orderId);
                 FsStoreOrderDf temp = fsStoreOrderDfService.selectFsStoreOrderDfByOrderId(df.getOrderId());
                 if (temp == null){
@@ -777,8 +809,12 @@ public class FsStoreOrderController extends BaseController
                 throw new RuntimeException(e);
             }
 
-        });
-        return R.ok();
+        }
+        if (msg.toString().isEmpty()){
+            return R.ok();
+        } else {
+            return R.error(msg.toString());
+        }
     }
 
 

+ 49 - 4
fs-admin/src/main/java/com/fs/his/controller/FsUserAddressController.java

@@ -1,6 +1,23 @@
 package com.fs.his.controller;
 
 import com.alibaba.fastjson.JSON;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.entity.SysRole;
+import com.fs.common.core.domain.entity.SysUser;
+import com.fs.common.utils.ParseUtils;
+import com.fs.common.utils.SecurityUtils;
+import com.fs.his.dto.AddressInfoDTO;
+import com.fs.system.service.ISysRoleService;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
@@ -17,6 +34,8 @@ import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
 
+import static com.fs.his.utils.PhoneUtil.decryptAutoPhoneMk;
+
 /**
  * 用户地址Controller
  *
@@ -39,15 +58,41 @@ public class FsUserAddressController extends BaseController
     {
         startPage();
         List<FsUserAddress> list = fsUserAddressService.selectFsUserAddressList(fsUserAddress);
+        SysRole sysRole = null;
+        try {
+            sysRole = isCheckPermission();
+        } catch (Exception e) {
+        }
         for (FsUserAddress userAddress : list) {
-//            userAddress.setPhone(decryptAutoPhoneMk(userAddress.getPhone()));
-            userAddress.setPhone(userAddress.getPhone());
-//            userAddress.setDetail(ParseUtils.parseAddress(userAddress.getDetail()));
-            userAddress.setDetail(userAddress.getDetail());
+            if (sysRole == null || sysRole.getIsCheckPhone() != 1 ) {
+                userAddress.setPhone(decryptAutoPhoneMk(userAddress.getPhone()));
+            }
+            if (sysRole == null || sysRole.getIsCheckAddress() != 1 ) {
+                userAddress.setDetail(ParseUtils.parseAddress(userAddress.getDetail()));
+            }
         }
         return getDataTable(list);
     }
 
+    @Autowired
+    private ISysRoleService sysRoleService;
+    private SysRole isCheckPermission() {
+        SysRole sysRole = new SysRole();
+        SysUser user = getLoginUser().getUser();
+        boolean flag = user.isAdmin();
+        if (flag) {
+            sysRole.setIsCheckPhone(1);
+            sysRole.setIsCheckAddress(1);
+        } else {
+            List<SysRole> roles = user.getRoles();
+            if (roles != null && !roles.isEmpty()) {
+                Long[] roleIds = roles.stream().map(SysRole::getRoleId).toArray(Long[]::new);
+                return sysRoleService.getIsCheckPermission(roleIds);
+            }
+        }
+        return sysRole;
+    }
+
     /**
      * 导出用户地址列表
      */

+ 5 - 1
fs-admin/src/main/java/com/fs/his/controller/FsUserCouponController.java

@@ -3,6 +3,7 @@ package com.fs.his.controller;
 import java.util.List;
 
 import com.fs.common.annotation.RepeatSubmit;
+import com.fs.common.utils.StringUtils;
 import com.fs.his.param.FsUserCouponParam;
 import com.fs.his.param.FsUserCouponSendParam;
 import com.fs.his.vo.FsFollowExcelListVO;
@@ -62,7 +63,10 @@ public class FsUserCouponController extends BaseController
         startPage();
         List<FsUserCouponListVO> list = fsUserCouponService.selectFsUserCouponListVO(fsUserCoupon);
         for (FsUserCouponListVO vo : list) {
-            vo.setPhone(vo.getPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+            String phone = vo.getPhone();
+            if (StringUtils.isNotBlank(phone)) {
+                vo.setPhone(phone.replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+            }
         }
         return getDataTable(list);
     }

+ 107 - 0
fs-admin/src/main/java/com/fs/his/controller/MerchantAppConfigController.java

@@ -0,0 +1,107 @@
+package com.fs.his.controller;
+
+import java.util.List;
+
+import com.fs.his.domain.MerchantAppConfig;
+import com.fs.his.service.IMerchantAppConfigService;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 商户应用配置Controller
+ *
+ * @author fs
+ * @date 2025-12-05
+ */
+@RestController
+@RequestMapping("/his/merchantAppConfig")
+public class MerchantAppConfigController extends BaseController
+{
+    @Autowired
+    private IMerchantAppConfigService merchantAppConfigService;
+
+    /**
+     * 查询商户应用配置列表
+     */
+    @PreAuthorize("@ss.hasPermi('his:merchantAppConfig:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(MerchantAppConfig merchantAppConfig)
+    {
+        startPage();
+        List<MerchantAppConfig> list = merchantAppConfigService.selectMerchantAppConfigList(merchantAppConfig);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出商户应用配置列表
+     */
+    @PreAuthorize("@ss.hasPermi('his:merchantAppConfig:export')")
+    @Log(title = "商户应用配置", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(MerchantAppConfig merchantAppConfig)
+    {
+        List<MerchantAppConfig> list = merchantAppConfigService.selectMerchantAppConfigList(merchantAppConfig);
+        ExcelUtil<MerchantAppConfig> util = new ExcelUtil<MerchantAppConfig>(MerchantAppConfig.class);
+        return util.exportExcel(list, "商户应用配置数据");
+    }
+
+    /**
+     * 获取商户应用配置详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('his:merchantAppConfig:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(merchantAppConfigService.selectMerchantAppConfigById(id));
+    }
+
+    /**
+     * 新增商户应用配置
+     */
+    @PreAuthorize("@ss.hasPermi('his:merchantAppConfig:add')")
+    @Log(title = "商户应用配置", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody MerchantAppConfig merchantAppConfig)
+    {
+        merchantAppConfig.setCreatedBy(getUsername());
+        merchantAppConfig.setUpdatedBy(getUsername());
+        return toAjax(merchantAppConfigService.insertMerchantAppConfig(merchantAppConfig));
+    }
+
+    /**
+     * 修改商户应用配置
+     */
+    @PreAuthorize("@ss.hasPermi('his:merchantAppConfig:edit')")
+    @Log(title = "商户应用配置", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody MerchantAppConfig merchantAppConfig)
+    {
+        merchantAppConfig.setUpdatedBy(getUsername());
+        return toAjax(merchantAppConfigService.updateMerchantAppConfig(merchantAppConfig));
+    }
+
+    /**
+     * 删除商户应用配置
+     */
+    @PreAuthorize("@ss.hasPermi('his:merchantAppConfig:remove')")
+    @Log(title = "商户应用配置", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long ids)
+    {
+        return toAjax(merchantAppConfigService.deleteMerchantAppConfigById(ids));
+    }
+}

+ 114 - 5
fs-admin/src/main/java/com/fs/his/task/Task.java

@@ -3,6 +3,7 @@ package com.fs.his.task;
 import cn.hutool.core.date.DateTime;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@@ -33,12 +34,10 @@ import com.fs.erp.dto.ErpOrderQueryResponse;
 import com.fs.erp.dto.ErpOrderResponse;
 import com.fs.erp.mapper.FsErpFinishPushMapper;
 import com.fs.erp.service.IErpOrderService;
-import com.fs.fastGpt.domain.FastGptEventTokenLog;
-import com.fs.fastGpt.domain.FastGptPushTokenTotal;
-import com.fs.fastGpt.domain.FastgptChatVoiceHomo;
-import com.fs.fastGpt.domain.FastgptEventLogTotal;
+import com.fs.fastGpt.domain.*;
 import com.fs.fastGpt.mapper.FastGptChatSessionMapper;
 import com.fs.fastGpt.mapper.FastgptChatVoiceHomoMapper;
+import com.fs.fastGpt.service.AiHookService;
 import com.fs.fastGpt.service.IFastgptEventLogTotalService;
 import com.fs.fastgptApi.util.AudioUtils;
 import com.fs.fastgptApi.vo.AudioVO;
@@ -57,12 +56,20 @@ import com.fs.his.service.impl.FsPackageOrderServiceImpl;
 import com.fs.his.utils.ConfigUtil;
 import com.fs.his.vo.FsSubOrderResultVO;
 import com.fs.hisStore.domain.FsStoreOrderScrm;
+import com.fs.hisStore.domain.FsStorePaymentScrm;
+import com.fs.hisStore.mapper.FsStorePaymentScrmMapper;
+import com.fs.hisStore.service.IFsStorePaymentScrmService;
+import com.fs.huifuPay.domain.HuiFuQueryOrderResult;
+import com.fs.huifuPay.sdk.opps.core.request.V2TradePaymentScanpayQueryRequest;
+import com.fs.huifuPay.service.HuiFuService;
 import com.fs.hisStore.service.IFsStoreOrderScrmService;
 import com.fs.im.dto.*;
 import com.fs.im.service.IImService;
 import com.fs.im.service.OpenIMService;
 import com.fs.qw.domain.QwCompany;
+import com.fs.qw.domain.QwUser;
 import com.fs.qw.mapper.QwRestrictionPushRecordMapper;
+import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.service.*;
 import com.fs.qwApi.service.QwApiService;
 import com.fs.sop.domain.QwSopTempVoice;
@@ -84,6 +91,8 @@ import org.springframework.stereotype.Component;
 
 import java.math.BigDecimal;
 import java.math.RoundingMode;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.util.*;
@@ -224,6 +233,51 @@ public class Task {
     @Autowired
     private FsIntegralOrderMapper fsIntegralOrderMapper;
 
+    private final String DELAY_MSG = "delayMsg";
+
+    @Autowired
+    private QwUserMapper qwUserMapper;
+
+    @Autowired
+    private AiHookService aiHookService;
+
+
+    /**
+     * 定时任务,处理ai禁止回复之后的消息
+     */
+    public void forbidTimeMsgTask() {
+        Map<String, Object> cacheMap = redisCache.hGetAll(DELAY_MSG);
+        for (String key : cacheMap.keySet()) {
+            String value = (String) cacheMap.get(key);
+            //获取sessionId
+            Long sessionId = Long.parseLong(key);
+            try {
+                Thread.sleep(800);
+            } catch (Exception e) {
+                log.error("定时消息处理异常,会话id:{},文本:{}",sessionId,value,e);
+            }
+            try {
+                if (value != null && !value.isEmpty()) {
+                    FastGptChatSession chatSession = fastGptChatSessionMapper.selectFastGptChatSessionBySessionId(sessionId);
+                    Long qwUserId = chatSession.getQwUserId();
+                    QwUser qwUser = qwUserMapper.selectQwUserById(qwUserId);
+                    String uid = qwUser.getUid();
+
+                    JSONObject jsonObject = JSONObject.parseObject(value);
+                    String content = jsonObject.getString("content");
+                    Long sender = jsonObject.getLong("sender");
+                    Integer type = jsonObject.getInteger("type");
+
+                    aiHookService.qwHookNotifyAiReply(qwUserId,sender,content,uid,type);
+                    //删除已经处理的缓存
+                    redisCache.hDel(DELAY_MSG,key);
+                }
+            } catch (Exception e) {
+                log.error("个人定时消息处理异常,会话id:{},文本:{}",sessionId,value,e);
+            }
+        }
+    }
+
     /**
      * sop任务token消耗统计
      */
@@ -261,7 +315,7 @@ public class Task {
                     // 计算金额,1元 = 10万token
                     double amount = count / 100000.0;
                     fastGptPushTotal.setAmount(Math.round(amount * 100.0) / 100.0);
-                    
+
                     FastGptPushTokenTotal pushTotal = qwRestrictionPushRecordMapper.selectFastGptPushTokenTotalByInfo(fastGptPushTotal);
                     if(pushTotal == null){
                         qwRestrictionPushRecordMapper.insertPushTokenTotal(fastGptPushTotal);
@@ -531,6 +585,7 @@ public class Task {
         return localDateTime.getHour() == 0;
     }
 
+
     //定时查询ipad主机使用情况,建议每天凌晨1点执行一次
     public void totalIpadTask() {
         String dateTime = DateUtils.addDateDays(-1); // 昨天
@@ -1085,6 +1140,59 @@ public class Task {
         System.out.println(msgResponseDTO);
     }
 
+    @Autowired
+    FsStorePaymentScrmMapper fsStorePaymentScrmMapper;
+
+    @Autowired
+    HuiFuService huiFuService;
+
+    /**
+     * 查询同步商城订单的支付转台
+     */
+    public void syncOrderPayStatus(){
+        //查询支付状态是待支付的数据
+        FsStorePaymentScrm paymentScrm = new FsStorePaymentScrm();
+        SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss");
+        paymentScrm.setStatus(0);
+        List<FsStorePaymentScrm> fsStorePaymentScrms = fsStorePaymentScrmMapper.selectFsStorePaymentList(paymentScrm);
+        if(null != fsStorePaymentScrms && !fsStorePaymentScrms.isEmpty()){
+            for (FsStorePaymentScrm payment : fsStorePaymentScrms) {
+                if(StringUtils.isBlank(payment.getTradeNo())){
+                    continue;
+                }
+                V2TradePaymentScanpayQueryRequest request = new V2TradePaymentScanpayQueryRequest();
+                request.setOrgReqDate(new SimpleDateFormat("yyyyMMdd").format(payment.getCreateTime()));
+                request.setOrgHfSeqId(payment.getTradeNo());
+                HuiFuQueryOrderResult o = null;
+                try {
+                    o = huiFuService.queryOrder(request);
+                } catch (Exception e) {
+                    log.error("查询失败:"+e.getMessage());
+                    continue;
+                }
+                log.info("汇付返回" + o);
+                if ("00000000".equals(o.getResp_code()) && "S".equals(o.getTrans_stat())) {
+                    try {
+                        Date payTime = formatter.parse(o.getEnd_time());
+                        FsStorePaymentScrm payUpdate = new FsStorePaymentScrm();
+                        payUpdate.setPaymentId(payment.getPaymentId());
+                        payUpdate.setPayTime(payTime);
+                        payUpdate.setStatus(1);
+                        payUpdate.setTradeNo(o.getOrg_hf_seq_id());
+                        payUpdate.setBankSerialNo(o.getParty_order_id());
+                        payUpdate.setBankTransactionId(o.getOut_trans_id());
+                        fsStorePaymentScrmMapper.updateFsStorePayment(payUpdate);
+                    } catch (ParseException e) {
+                        log.error("更新失败 payment:{}",payment ,e);
+//                        throw new RuntimeException(e);
+                    }
+
+
+                }
+            }
+        }
+    }
+
 //    @Autowired
 //    private FsStoreMapper fsStoreMapper;
 //    @Autowired
@@ -1597,6 +1705,7 @@ public class Task {
 
     }
 
+
     /**
      * 定时任务-im 会员催课,每一分钟执行一次
      */

+ 66 - 4
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreAfterSalesScrmController.java

@@ -1,5 +1,6 @@
 package com.fs.hisStore.controller;
 
+import cn.hutool.core.bean.BeanUtil;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
@@ -9,6 +10,7 @@ import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.ParseUtils;
 import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.framework.web.service.TokenService;
 import com.fs.his.domain.FsUser;
@@ -26,12 +28,18 @@ import com.fs.hisStore.service.IFsStoreAfterSalesScrmService;
 import com.fs.hisStore.service.IFsStoreAfterSalesStatusScrmService;
 import com.fs.hisStore.service.IFsStoreOrderScrmService;
 import com.fs.hisStore.vo.FsStoreAfterSalesVO;
+import com.fs.hisStore.vo.FsStoreOrderItemExportRefundZMVO;
+import com.fs.hisStore.vo.FsStoreOrderItemExportZMVO;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
+import java.math.BigDecimal;
 import java.text.ParseException;
 import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
 /**
  * 售后记录Controller
@@ -56,6 +64,10 @@ public class FsStoreAfterSalesScrmController extends BaseController
 
     @Autowired
     private TokenService tokenService;
+
+    @Value("${cloud_host.company_name}")
+    private String signProjectName;
+
     /**
      * 查询售后记录列表
      */
@@ -79,20 +91,70 @@ public class FsStoreAfterSalesScrmController extends BaseController
     @GetMapping("/export")
     public AjaxResult export(FsStoreAfterSalesScrm fsStoreAfterSales)
     {
-        if (fsStoreAfterSales.getBeginTime().equals("") && fsStoreAfterSales.getEndTime().equals("")){
+        if (StringUtils.isNotEmpty(fsStoreAfterSales.getBeginTime()) && StringUtils.isNotEmpty(fsStoreAfterSales.getEndTime())){
             fsStoreAfterSales.setBeginTime(null);
             fsStoreAfterSales.setEndTime(null);
         }
-        Boolean a = fsStoreAfterSalesService.isEntityNull(fsStoreAfterSales);
-        if (fsStoreAfterSalesService.isEntityNull(fsStoreAfterSales)){
+//        Boolean a = fsStoreAfterSalesService.isEntityNull(fsStoreAfterSales);
+        if (fsStoreAfterSalesService.isEntityNull(fsStoreAfterSales) && Objects.isNull(fsStoreAfterSales.getParams())){
             return AjaxResult.error("请筛选数据导出");
         }
+
         List<FsStoreAfterSalesVO> list = fsStoreAfterSalesService.selectFsStoreAfterSalesListVO(fsStoreAfterSales);
+        if("北京卓美".equals(signProjectName)){
+            List<FsStoreOrderItemExportRefundZMVO> zmvoList = list.stream()
+                    .map(vo -> {
+                        FsStoreOrderItemExportRefundZMVO zmvo = new FsStoreOrderItemExportRefundZMVO();
+                        try {
+                            zmvo.setPayCode(vo.getPayCode());
+                            zmvo.setOrderCode(vo.getOrderCode());
+                            zmvo.setStatus(vo.getOrderStatus().toString());
+                            zmvo.setUserId(vo.getUserId());
+                            zmvo.setProductName(vo.getProductName());
+                            zmvo.setBarCode(vo.getProductBarCode());
+                            zmvo.setSku(vo.getSku());
+                            zmvo.setNum(vo.getNum());
+                            zmvo.setPrice(vo.getPrice());
+                            zmvo.setCost(vo.getCost());
+//                            zmvo.setFPrice("");
+                            zmvo.setPayMoney(vo.getPayMoney());
+                            zmvo.setPayPostage(vo.getTotalPostage());
+                            zmvo.setCateName(vo.getCateName());
+                            zmvo.setRealName(vo.getUserName());
+                            zmvo.setUserPhone(vo.getUserPhone());
+                            zmvo.setUserAddress(vo.getUserAddress());
+                            zmvo.setCreateTime(vo.getCreateTime());
+                            zmvo.setPayTime(vo.getOrderPayTime());
+                            zmvo.setDeliverySn(vo.getOrderDeliverySn());
+                            zmvo.setDeliveryName(vo.getOrderDeliveryName());
+                            zmvo.setDeliveryId(vo.getOrderDeliveryId());
+                            zmvo.setCompanyName(vo.getCompanyName());
+                            zmvo.setCompanyUserNickName(vo.getCompanyUserNickName());
+                            zmvo.setRefundTime(vo.getCreateTime());
+//                            zmvo.setAfterSalesNumber
+                            zmvo.setRefundMoney(vo.getRefundAmount());
+                            zmvo.setBankTransactionId(vo.getBankTransactionId());
+                            zmvo.setReasons(vo.getReasons());
+                            zmvo.setExplains(vo.getExplains());
+
+                        } catch (Exception e) {
+                            // 处理异常
+                            e.printStackTrace();
+                        }
+                        return zmvo;
+                    })
+                    .collect(Collectors.toList());
+            for (FsStoreOrderItemExportRefundZMVO vo : zmvoList){
+                vo.setUserPhone(ParseUtils.parsePhone(vo.getUserPhone()));
+            }
+            ExcelUtil<FsStoreOrderItemExportRefundZMVO> util = new ExcelUtil<FsStoreOrderItemExportRefundZMVO>(FsStoreOrderItemExportRefundZMVO.class);
+            return util.exportExcel(zmvoList, "退款订单导出");
+        }
         for (FsStoreAfterSalesVO vo : list){
             vo.setUserPhone(ParseUtils.parsePhone(vo.getUserPhone()));
         }
         ExcelUtil<FsStoreAfterSalesVO> util = new ExcelUtil<FsStoreAfterSalesVO>(FsStoreAfterSalesVO.class);
-        return util.exportExcel(list, "storeAfterSales");
+        return util.exportExcel(list, "退款订单导出");
     }
     /**
      * 获取售后记录详细信息

+ 57 - 53
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreHealthOrderScrmController.java

@@ -37,8 +37,10 @@ import com.fs.hisStore.vo.FsStoreOrderVO;
 import com.fs.hisStore.vo.*;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
+import com.github.pagehelper.PageHelper;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
@@ -48,6 +50,7 @@ import java.util.Date;
 import java.math.BigDecimal;
 import java.text.SimpleDateFormat;
 import java.util.*;
+import java.util.stream.Collectors;
 
 @Api(tags = "健康商城订单管理")
 @RestController
@@ -88,13 +91,16 @@ public class FsStoreHealthOrderScrmController extends BaseController {
     // 最大文件大小(5MB)
     private static final long MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
 
+    @Value("${cloud_host.company_name}")
+    private String signProjectName;
+
     /**
      * 查询健康商城订单列表
      */
 //    @PreAuthorize("@ss.hasPermi('store:healthStoreOrder:list')")
       @PostMapping("/healthList")
       public TableDataInfo healthStoreList(@RequestBody FsStoreOrderParam param) {
-        startPage();
+          PageHelper.startPage(param.getPageNum(), param.getPageSize());
         if(!StringUtils.isEmpty(param.getCreateTimeRange())){
             param.setCreateTimeList(param.getCreateTimeRange().split("--"));
         }
@@ -131,11 +137,9 @@ public class FsStoreHealthOrderScrmController extends BaseController {
                     }
                 }
                 //
-                if (loginUser.getPermissions().contains("his:storeAfterSales:finance") || loginUser.getPermissions().contains("*:*:*")) {
-                    if (vo.getCost() != null && vo.getTotalNum() != null) {
+                if (loginUser.getPermissions().contains("his:storeAfterSales:finance") || loginUser.getPermissions().contains("*:*:*") ) {
+                    if((vo.getCost() !=null && vo.getTotalNum() != null)){
                         vo.setFPrice(vo.getCost().multiply(BigDecimal.valueOf(vo.getTotalNum())));
-                    } else {
-                        vo.setFPrice(BigDecimal.ZERO);
                     }
                 } else {
                     vo.setPayPostage(BigDecimal.ZERO);
@@ -144,6 +148,7 @@ public class FsStoreHealthOrderScrmController extends BaseController {
                     vo.setPayDelivery(BigDecimal.ZERO);
                     vo.setBarCode("");
                     vo.setCateName("");
+                    vo.setBankTransactionId("");
                 }
 
             }
@@ -323,18 +328,15 @@ public class FsStoreHealthOrderScrmController extends BaseController {
                     }
                 }
                 //
-                if (loginUser.getPermissions().contains("his:storeAfterSales:finance") || loginUser.getPermissions().contains("*:*:*")) {
-                    if (vo.getCost() != null && vo.getTotalNum() != null) {
-                        vo.setFPrice(vo.getCost().multiply(BigDecimal.valueOf(vo.getTotalNum())));
-                    } else {
-                        vo.setFPrice(BigDecimal.ZERO);
-                    }
+                if ((loginUser.getPermissions().contains("his:storeAfterSales:finance") || loginUser.getPermissions().contains("*:*:*") ) && !Objects.isNull(vo.getCost())) {
+                    vo.setFPrice(vo.getCost().multiply(BigDecimal.valueOf(vo.getTotalNum())));
                 } else {
                     vo.setPayPostage(BigDecimal.ZERO);
                     vo.setCost(BigDecimal.ZERO);
                     vo.setFPrice(BigDecimal.ZERO);
                     vo.setBarCode("");
                     vo.setCateName("");
+                    vo.setBankTransactionId("");
                 }
             }
         }
@@ -367,6 +369,45 @@ public class FsStoreHealthOrderScrmController extends BaseController {
         }
         param.setIsHealth("1");
         List<FsStoreOrderItemExportVO> list = orderItemService.selectFsStoreOrderItemListExportVO(param);
+        if("北京卓美".equals(signProjectName)){
+            List<FsStoreOrderItemExportZMVO> zmvoList = list.stream()
+                    .map(vo -> {
+                        FsStoreOrderItemExportZMVO zmvo = new FsStoreOrderItemExportZMVO();
+                        try {
+                            BeanUtil.copyProperties( vo,zmvo);
+                        } catch (Exception e) {
+                            // 处理异常
+                            e.printStackTrace();
+                        }
+                        return zmvo;
+                    })
+                    .collect(Collectors.toList());
+            //对手机号脱敏
+            if (zmvoList != null) {
+                    LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+                    for (FsStoreOrderItemExportZMVO vo : zmvoList) {
+                        if (!StringUtils.isEmpty(vo.getJsonInfo())) {
+                            try {
+                                StoreOrderProductDTO orderProductDTO = JSONObject.parseObject(vo.getJsonInfo(), StoreOrderProductDTO.class);
+                                BeanUtil.copyProperties(orderProductDTO, vo);
+                            } catch (Exception e) {
+                            }
+                        }
+                        if ((loginUser.getPermissions().contains("his:storeAfterSales:finance") || loginUser.getPermissions().contains("*:*:*") ) && !Objects.isNull(vo.getCost())) {
+                            vo.setFPrice(vo.getCost().multiply(BigDecimal.valueOf(vo.getTotalNum())));
+                        } else {
+                            vo.setPayPostage(BigDecimal.ZERO);
+                            vo.setCost(BigDecimal.ZERO);
+                            vo.setFPrice(BigDecimal.ZERO);
+                            vo.setBarCode("");
+                            vo.setCateName("");
+                            vo.setBankTransactionId("");
+                        }
+                    }
+                }
+                ExcelUtil<FsStoreOrderItemExportZMVO> util = new ExcelUtil<FsStoreOrderItemExportZMVO>(FsStoreOrderItemExportZMVO.class);
+                return util.exportExcel(zmvoList, "订单明细数据");
+        }
         //对手机号脱敏
         if (list != null) {
             LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
@@ -378,7 +419,7 @@ public class FsStoreHealthOrderScrmController extends BaseController {
                     } catch (Exception e) {
                     }
                 }
-                if (loginUser.getPermissions().contains("his:storeAfterSales:finance") || loginUser.getPermissions().contains("*:*:*")) {
+                if ((loginUser.getPermissions().contains("his:storeAfterSales:finance") || loginUser.getPermissions().contains("*:*:*") ) && !Objects.isNull(vo.getCost())) {
                     vo.setFPrice(vo.getCost().multiply(BigDecimal.valueOf(vo.getTotalNum())));
                 } else {
                     vo.setPayPostage(BigDecimal.ZERO);
@@ -386,6 +427,7 @@ public class FsStoreHealthOrderScrmController extends BaseController {
                     vo.setFPrice(BigDecimal.ZERO);
                     vo.setBarCode("");
                     vo.setCateName("");
+                    vo.setBankTransactionId("");
                 }
             }
         }
@@ -532,9 +574,9 @@ public class FsStoreHealthOrderScrmController extends BaseController {
             queryWrapper.eq("is_del", 0)  // 未删除
                        .ne("type", 2)      // 不是公众号
                        .orderByDesc("create_time");
-            
+
             List<FsCoursePlaySourceConfig> configList = coursePlaySourceConfigService.list(queryWrapper);
-            
+
             List<FsMiniProgramOptionVO> optionList = new java.util.ArrayList<>();
             for (FsCoursePlaySourceConfig config : configList) {
                 FsMiniProgramOptionVO vo = new FsMiniProgramOptionVO();
@@ -544,7 +586,7 @@ public class FsStoreHealthOrderScrmController extends BaseController {
                 vo.setType(config.getType());
                 optionList.add(vo);
             }
-            
+
             logger.info("查询小程序列表成功,数量:{}", optionList.size());
             return AjaxResult.success(optionList);
         } catch (Exception e) {
@@ -552,42 +594,4 @@ public class FsStoreHealthOrderScrmController extends BaseController {
             return AjaxResult.error("查询小程序列表失败:" + e.getMessage());
         }
     }
-
-
-//    /**
-//     * 发货单导出接口
-//     * **/
-//    @PreAuthorize("@ss.hasPermi('store:storeOrder:healthExportShippingOrder')")
-//    @GetMapping("/healthExportShippingOrder")
-//    public AjaxResult healthExportShippingOrder(FsStoreOrderParam param){
-//        if ("".equals(param.getBeginTime()) && "".equals(param.getEndTime())){
-//            param.setBeginTime(null);
-//            param.setEndTime(null);
-//        }
-//        if(fsStoreOrderService.isEntityNull(param)){
-//            param = new FsStoreOrderParam();
-//        }
-//        if(!StringUtils.isEmpty(param.getCreateTimeRange())){
-//            param.setCreateTimeList(param.getCreateTimeRange().split("--"));
-//        }
-//        if(!StringUtils.isEmpty(param.getPayTimeRange())){
-//            param.setPayTimeList(param.getPayTimeRange().split("--"));
-//        }
-//        if(!StringUtils.isEmpty(param.getDeliverySendTimeRange())){
-//            param.setDeliverySendTimeList(param.getDeliverySendTimeRange().split("--"));
-//        }
-//        if(!StringUtils.isEmpty(param.getDeliveryImportTimeRange())){
-//            param.setDeliveryImportTimeList(param.getDeliveryImportTimeRange().split("--"));
-//        }
-//        param.setIsHealth("1");
-//        List<FsStoreOrderDeliveryNoteExportVO> storeOrderDeliveryNoteExportVOList=fsStoreOrderService.getDeliveryNote(param);
-//        ExcelUtil<FsStoreOrderDeliveryNoteExportVO> util = new ExcelUtil<>(FsStoreOrderDeliveryNoteExportVO.class);
-//        //通过商品ID获取关键字
-//        String firstKeyword = storeOrderDeliveryNoteExportVOList.stream()
-//                .map(FsStoreOrderDeliveryNoteExportVO::getKeyword)
-//                .findFirst()
-//                .orElse("无订单");
-//        String fileName="077AC"+firstKeyword+new SimpleDateFormat("yyyyMMdd").format(new Date());
-//        return util.exportExcel(storeOrderDeliveryNoteExportVOList, fileName);
-//    }
 }

+ 93 - 28
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java

@@ -8,6 +8,8 @@ import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.entity.SysRole;
+import com.fs.common.core.domain.entity.SysUser;
 import com.fs.common.core.domain.model.LoginUser;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
@@ -50,10 +52,13 @@ import com.fs.hisStore.service.*;
 import com.fs.hisStore.vo.*;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.mapper.SysConfigMapper;
+import com.fs.system.service.ISysRoleService;
+import com.github.pagehelper.PageHelper;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
@@ -141,6 +146,9 @@ public class FsStoreOrderScrmController extends BaseController {
     @Autowired
     private IFsStoreOrderLogsScrmService fsStoreOrderLogsService;
 
+    @Value("${cloud_host.company_name}")
+    private String signProjectName;
+
     private IErpOrderService getErpService(){
         //判断是否开启erp
         IErpOrderService erpOrderService = null;
@@ -178,7 +186,7 @@ public class FsStoreOrderScrmController extends BaseController {
     @PreAuthorize("@ss.hasPermi('store:storeOrder:list')")
     @PostMapping("/list")
     public TableDataInfo list(@RequestBody FsStoreOrderParam param) {
-        startPage();
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
         if(!StringUtils.isEmpty(param.getCreateTimeRange())){
             param.setCreateTimeList(param.getCreateTimeRange().split("--"));
         }
@@ -215,12 +223,8 @@ public class FsStoreOrderScrmController extends BaseController {
                     }
                 }
                 //
-                if (loginUser.getPermissions().contains("his:storeAfterSales:finance") || loginUser.getPermissions().contains("*:*:*")) {
-                    if (vo.getCost() != null && vo.getTotalNum() != null) {
-                        vo.setFPrice(vo.getCost().multiply(BigDecimal.valueOf(vo.getTotalNum())));
-                    } else {
-                        vo.setFPrice(BigDecimal.ZERO);
-                    }
+                if ((loginUser.getPermissions().contains("his:storeAfterSales:finance")) || loginUser.getPermissions().contains("*:*:*") && (vo.getCost() != null && vo.getTotalNum() != null)) {
+                    vo.setFPrice(vo.getCost().multiply(BigDecimal.valueOf(vo.getTotalNum())));
                 } else {
                     vo.setPayPostage(BigDecimal.ZERO);
                     vo.setCost(BigDecimal.ZERO);
@@ -228,6 +232,7 @@ public class FsStoreOrderScrmController extends BaseController {
                     vo.setPayDelivery(BigDecimal.ZERO);
                     vo.setBarCode("");
                     vo.setCateName("");
+                    vo.setBankTransactionId("");
                 }
             }
         }
@@ -259,7 +264,7 @@ public class FsStoreOrderScrmController extends BaseController {
     @PreAuthorize("@ss.hasPermi('store:storeOrder:payRemainList')")
     @GetMapping("/payRemainList")
     public TableDataInfo payRemainList(FsStoreOrderParam param) {
-        startPage();
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
         if(!StringUtils.isEmpty(param.getCreateTimeRange())){
             param.setCreateTimeList(param.getCreateTimeRange().split("--"));
         }
@@ -347,21 +352,25 @@ public class FsStoreOrderScrmController extends BaseController {
         }
         param.setNotHealth(1);
         List<FsStoreOrderErpExportVO> list = fsStoreOrderService.selectFsStoreOrderListVOByExport(param);
+
         //对手机号脱敏
         if (list != null) {
-            //获取当前账号角色权限
-            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+            SysRole sysRole = isCheckPermission();
 
             for (FsStoreOrderErpExportVO vo : list) {
-                if (vo.getPhone() != null) {
+                if (vo.getPhone() != null && sysRole.getIsCheckPhone() != 1) {
                     vo.setPhone(vo.getPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
                 }
-                if (vo.getUserPhone() != null) {
+                if (vo.getUserPhone() != null && sysRole.getIsCheckPhone() != 1) {
                     vo.setUserPhone(vo.getUserPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
                 }
-                if (vo.getUserAddress()!=null){
+                if (vo.getUserAddress()!=null && sysRole.getIsCheckAddress() != 1){
                     vo.setUserAddress(ParseUtils.parseAddress(vo.getUserAddress()));
                 }
+                //商品明细
+                String orderItem = orderItemService.selectFsStoreOrderItemByOrderId(vo.getId());
+                vo.setOrderItem(orderItem);
+
             }
         }
         String filter = param.getFilter();
@@ -384,6 +393,25 @@ public class FsStoreOrderScrmController extends BaseController {
         }
     }
 
+    @Autowired
+    private ISysRoleService sysRoleService;
+    private SysRole isCheckPermission() {
+        SysRole sysRole = new SysRole();
+        SysUser user = getLoginUser().getUser();
+        boolean flag = user.isAdmin();
+        if (flag) {
+            sysRole.setIsCheckPhone(1);
+            sysRole.setIsCheckAddress(1);
+        } else {
+            List<SysRole> roles = user.getRoles();
+            if (roles != null && !roles.isEmpty()) {
+                Long[] roleIds = roles.stream().map(SysRole::getRoleId).toArray(Long[]::new);
+                return sysRoleService.getIsCheckPermission(roleIds);
+            }
+        }
+        return sysRole;
+    }
+
 
     /**
      * 导出订单列表(明文)
@@ -460,14 +488,15 @@ public class FsStoreOrderScrmController extends BaseController {
         List<FsStoreOrderItemExportVO> list = orderItemService.selectFsStoreOrderItemListExportVO(param);
         //对手机号脱敏
         if (list != null) {
+            SysRole sysRole = isCheckPermission();
             LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
 
             for (FsStoreOrderItemExportVO vo : list) {
-                if (vo.getUserPhone() != null) {
+                if (vo.getUserPhone() != null && sysRole.getIsCheckPhone() != 1) {
                     String phone = vo.getUserPhone().replaceAll("(\\d{3})\\d*(\\d{1})", "$1****$2");
                     vo.setUserPhone(phone);
                 }
-                if (vo.getUserAddress()!=null){
+                if (vo.getUserAddress()!=null && sysRole.getIsCheckAddress() != 1){
                     vo.setUserAddress(ParseUtils.parseAddress(vo.getUserAddress()));
                 }
                 if (!StringUtils.isEmpty(vo.getJsonInfo())) {
@@ -478,18 +507,15 @@ public class FsStoreOrderScrmController extends BaseController {
                     }
                 }
                 //
-                if (loginUser.getPermissions().contains("his:storeAfterSales:finance") || loginUser.getPermissions().contains("*:*:*")) {
-                    if (vo.getCost() != null && vo.getTotalNum() != null) {
-                        vo.setFPrice(vo.getCost().multiply(BigDecimal.valueOf(vo.getTotalNum())));
-                    } else {
-                        vo.setFPrice(BigDecimal.ZERO);
-                    }
+                if ((loginUser.getPermissions().contains("his:storeAfterSales:finance") || loginUser.getPermissions().contains("*:*:*") ) && !Objects.isNull(vo.getCost())) {
+                    vo.setFPrice(vo.getCost().multiply(BigDecimal.valueOf(vo.getTotalNum())));
                 } else {
                     vo.setPayPostage(BigDecimal.ZERO);
                     vo.setCost(BigDecimal.ZERO);
                     vo.setFPrice(BigDecimal.ZERO);
                     vo.setBarCode("");
                     vo.setCateName("");
+                    vo.setBankTransactionId("");
                 }
             }
         }
@@ -525,6 +551,47 @@ public class FsStoreOrderScrmController extends BaseController {
         }
         param.setNotHealth(1);
         List<FsStoreOrderItemExportVO> list = orderItemService.selectFsStoreOrderItemListExportVO(param);
+        if("北京卓美".equals(signProjectName)){
+            List<FsStoreOrderItemExportZMVO> zmvoList = list.stream()
+                    .map(vo -> {
+                        FsStoreOrderItemExportZMVO zmvo = new FsStoreOrderItemExportZMVO();
+                        try {
+                            BeanUtil.copyProperties( vo,zmvo);
+                        } catch (Exception e) {
+                            // 处理异常
+                            e.printStackTrace();
+                        }
+                        return zmvo;
+                    })
+                    .collect(Collectors.toList());
+            //对手机号脱敏
+            if (zmvoList != null) {
+                LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+                for (FsStoreOrderItemExportZMVO vo : zmvoList) {
+                    if (!StringUtils.isEmpty(vo.getJsonInfo())) {
+                        try {
+                            StoreOrderProductDTO orderProductDTO = JSONObject.parseObject(vo.getJsonInfo(), StoreOrderProductDTO.class);
+                            BeanUtil.copyProperties(orderProductDTO, vo);
+                        } catch (Exception e) {
+                        }
+                    }
+                    //
+                    if ((loginUser.getPermissions().contains("his:storeAfterSales:finance") || loginUser.getPermissions().contains("*:*:*") ) && !Objects.isNull(vo.getCost())) {
+                        vo.setFPrice(vo.getCost().multiply(BigDecimal.valueOf(vo.getTotalNum())));
+                    } else {
+                        vo.setPayPostage(BigDecimal.ZERO);
+                        vo.setCost(BigDecimal.ZERO);
+                        vo.setFPrice(BigDecimal.ZERO);
+                        vo.setBarCode("");
+                        vo.setCateName("");
+                        vo.setBankTransactionId("");
+                    }
+                }
+            }
+            ExcelUtil<FsStoreOrderItemExportZMVO> util = new ExcelUtil<FsStoreOrderItemExportZMVO>(FsStoreOrderItemExportZMVO.class);
+            return util.exportExcel(zmvoList, "订单明细数据");
+        }
+
         //对手机号脱敏
         if (list != null) {
             LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
@@ -537,18 +604,15 @@ public class FsStoreOrderScrmController extends BaseController {
                     }
                 }
                 //
-                if (loginUser.getPermissions().contains("his:storeAfterSales:finance") || loginUser.getPermissions().contains("*:*:*")) {
-                    if (vo.getCost() != null && vo.getTotalNum() != null) {
-                        vo.setFPrice(vo.getCost().multiply(BigDecimal.valueOf(vo.getTotalNum())));
-                    } else {
-                        vo.setFPrice(BigDecimal.ZERO);
-                    }
+                if ((loginUser.getPermissions().contains("his:storeAfterSales:finance") || loginUser.getPermissions().contains("*:*:*") ) && !Objects.isNull(vo.getCost())) {
+                    vo.setFPrice(vo.getCost().multiply(BigDecimal.valueOf(vo.getTotalNum())));
                 } else {
                     vo.setPayPostage(BigDecimal.ZERO);
                     vo.setCost(BigDecimal.ZERO);
                     vo.setFPrice(BigDecimal.ZERO);
                     vo.setBarCode("");
                     vo.setCateName("");
+                    vo.setBankTransactionId("");
                 }
             }
         }
@@ -811,7 +875,8 @@ public class FsStoreOrderScrmController extends BaseController {
 
     @GetMapping("/getCustomerOrderList")
     public TableDataInfo getCustomerOrderList(FsStoreOrderParam param) {
-        startPage();
+//        startPage();
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
         List<FsStoreOrderVO> list = fsStoreOrderService.selectFsCustomerStoreOrderListVO(param);
         if (list != null) {
             for (FsStoreOrderVO vo : list) {

+ 20 - 4
fs-admin/src/main/java/com/fs/hisStore/controller/FsStorePaymentScrmController.java

@@ -79,7 +79,7 @@ public class FsStorePaymentScrmController extends BaseController
     private CloudHostProper cloudHostProper;
     @Autowired
     private ISysConfigService configService;
-    
+
     @Autowired
     private IFsCoursePlaySourceConfigService coursePlaySourceConfigService;
     /**
@@ -125,9 +125,9 @@ public class FsStorePaymentScrmController extends BaseController
             queryWrapper.eq("is_del", 0)  // 未删除
                        .ne("type", 2)      // 不是公众号
                        .orderByDesc("create_time");
-            
+
             List<FsCoursePlaySourceConfig> configList = coursePlaySourceConfigService.list(queryWrapper);
-            
+
             List<FsMiniProgramOptionVO> optionList = new java.util.ArrayList<>();
             for (FsCoursePlaySourceConfig config : configList) {
                 FsMiniProgramOptionVO vo = new FsMiniProgramOptionVO();
@@ -137,7 +137,7 @@ public class FsStorePaymentScrmController extends BaseController
                 vo.setType(config.getType());
                 optionList.add(vo);
             }
-            
+
             logger.info("查询小程序列表成功,数量:{}", optionList.size());
             return AjaxResult.success(optionList);
         } catch (Exception e) {
@@ -282,6 +282,7 @@ public class FsStorePaymentScrmController extends BaseController
                 request.setOrdAmt(payment.getPayMoney().toString());
                 request.setOrgReqDate(new SimpleDateFormat("yyyyMMdd").format(payment.getCreateTime()));
                 request.setReqSeqId("refund-"+payment.getPayCode());
+                request.setAppId(payment.getAppId());
                 Map<String, Object> extendInfoMap = new HashMap<>();
                 extendInfoMap.put("org_party_order_id", payment.getBankSerialNo());
                 request.setExtendInfo(extendInfoMap);
@@ -394,4 +395,19 @@ public class FsStorePaymentScrmController extends BaseController
         return toAjax(fsStorePaymentService.deleteFsStorePaymentByIds(paymentIds));
     }
 
+    /**
+     * 一键发货
+     * @return R
+     * **/
+    @Log(title = "发货同步导入", businessType = BusinessType.IMPORT)
+    @PostMapping("/oneClickShipping")
+    public R oneClickShipping() {
+
+        try {
+            return fsStorePaymentService.oneClickShipping();
+        }catch (Exception e){
+            e.getStackTrace();
+        }
+        return R.ok();
+    }
 }

+ 9 - 9
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreScrmController.java

@@ -37,7 +37,7 @@ public class FsStoreScrmController extends BaseController
     /**
      * 查询店铺管理列表
      */
-    @PreAuthorize("@ss.hasPermi('his:store:list')")
+    @PreAuthorize("@ss.hasPermi('store:his:store:list')")
     @GetMapping("/list")
     public TableDataInfo list(FsStoreScrm fsStore)
     {
@@ -52,7 +52,7 @@ public class FsStoreScrmController extends BaseController
     /**
      * 导出店铺管理列表
      */
-    @PreAuthorize("@ss.hasPermi('his:store:export')")
+    @PreAuthorize("@ss.hasPermi('store:his:store:export')")
     @Log(title = "店铺管理", businessType = BusinessType.EXPORT,logParam = {"店铺","导出店铺信息"},isStoreLog = true)
     @GetMapping("/export")
     public AjaxResult export(FsStoreScrm fsStore)
@@ -69,7 +69,7 @@ public class FsStoreScrmController extends BaseController
     /**
      * 获取店铺管理详细信息
      */
-    @PreAuthorize("@ss.hasPermi('his:store:query')")
+    @PreAuthorize("@ss.hasPermi('store:his:store:query')")
     @GetMapping(value = "/{storeId}")
     public AjaxResult getInfo(@PathVariable("storeId") Long storeId)
     {
@@ -81,7 +81,7 @@ public class FsStoreScrmController extends BaseController
     /**
      * 新增店铺管理
      */
-    @PreAuthorize("@ss.hasPermi('his:store:add')")
+    @PreAuthorize("@ss.hasPermi('store:his:store:add')")
     @Log(title = "店铺管理", businessType = BusinessType.INSERT,logParam = {"店铺","新增店铺信息"},isStoreLog = true)
     @PostMapping
     public AjaxResult add(@RequestBody FsStoreScrm fsStore)
@@ -93,7 +93,7 @@ public class FsStoreScrmController extends BaseController
     /**
      * 修改店铺管理
      */
-    @PreAuthorize("@ss.hasPermi('his:store:edit')")
+    @PreAuthorize("@ss.hasPermi('store:his:store:edit')")
     @Log(title = "店铺管理", businessType = BusinessType.UPDATE,logParam = {"店铺","修改店铺信息"},isStoreLog = true)
     @PutMapping
     public AjaxResult edit(@RequestBody FsStoreScrm fsStore)
@@ -108,7 +108,7 @@ public class FsStoreScrmController extends BaseController
     /**
      * 删除店铺管理
      */
-    @PreAuthorize("@ss.hasPermi('his:store:remove')")
+    @PreAuthorize("@ss.hasPermi('store:his:store:remove')")
     @Log(title = "店铺管理", businessType = BusinessType.DELETE,logParam = {"店铺","删除店铺信息"},isStoreLog = true)
 	@DeleteMapping("/{storeIds}")
     public AjaxResult remove(@PathVariable Long[] storeIds)
@@ -119,7 +119,7 @@ public class FsStoreScrmController extends BaseController
     /**
      * 店铺审核
      */
-    @PreAuthorize("@ss.hasPermi('his:store:audit')")
+    @PreAuthorize("@ss.hasPermi('store:his:store:audit')")
     @Log(title = "店铺审核", businessType = BusinessType.AUDIT,logParam = {"店铺","店铺审核"},isStoreLog = true
     ,logParamExpression = "#p0.getIsAudit()==1?new String[]{'店铺','店铺审核通过'}: new String[]{'店铺','店铺审核退回'}")
     @PutMapping("/audit")
@@ -131,7 +131,7 @@ public class FsStoreScrmController extends BaseController
     /**
      * 重置店铺密码
      * */
-    @PreAuthorize("@ss.hasPermi('his:store:refresh')")
+    @PreAuthorize("@ss.hasPermi('store:his:store:refresh')")
     @Log(title = "店铺管理", businessType = BusinessType.UPDATE,logParam = {"店铺","重置店铺密码"},isStoreLog = true)
     @PutMapping("/refresh/{storeId}")
     public AjaxResult refresh(@PathVariable Long storeId)
@@ -143,7 +143,7 @@ public class FsStoreScrmController extends BaseController
     /**
      * 店铺审核日志
      * */
-    @PreAuthorize("@ss.hasPermi('his:store:auditLog')")
+    @PreAuthorize("@ss.hasPermi('store:his:store:auditLog')")
     @GetMapping("/auditLog/{storeId}")
     public R auditLog(@PathVariable Long storeId){
         return R.ok().put("auditLog",storeAuditLogUtil.selectOperLogByMainId(storeId));

+ 78 - 11
fs-admin/src/main/java/com/fs/hisStore/task/LiveTask.java

@@ -2,7 +2,9 @@ package com.fs.hisStore.task;
 
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.json.JSONUtil;
+import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.DateUtils;
 import com.fs.company.service.ICompanyService;
 import com.fs.company.vo.RedPacketMoneyVO;
 import com.fs.course.mapper.FsCourseRedPacketLogMapper;
@@ -10,6 +12,7 @@ import com.fs.erp.domain.ErpDeliverys;
 import com.fs.erp.domain.ErpOrderQuery;
 import com.fs.erp.dto.ErpOrderQueryRequert;
 import com.fs.erp.dto.ErpOrderQueryResponse;
+import com.fs.erp.service.FsJstAftersalePushScrmService;
 import com.fs.erp.service.IErpOrderService;
 import com.fs.his.config.FsSysConfig;
 import com.fs.his.dto.ExpressInfoDTO;
@@ -21,7 +24,11 @@ import com.fs.hisStore.dto.DateComparisonConfigDTO;
 import com.fs.hisStore.enums.ShipperCodeEnum;
 import com.fs.hisStore.mapper.FsStoreProductAttrValueScrmMapper;
 import com.fs.hisStore.param.FsStoreOrderAddTuiMoneyParam;
+import com.fs.hisStore.service.IFsStoreOrderScrmService;
 import com.fs.hisStore.service.IFsStoreProductScrmService;
+import com.fs.huifuPay.domain.HuiFuQueryOrderResult;
+import com.fs.huifuPay.sdk.opps.core.request.V2TradePaymentScanpayQueryRequest;
+import com.fs.huifuPay.service.HuiFuService;
 import com.fs.live.domain.LiveAfterSales;
 import com.fs.live.domain.LiveOrder;
 import com.fs.live.domain.LiveOrderItem;
@@ -49,8 +56,10 @@ import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 
 import java.text.ParseException;
+import java.text.SimpleDateFormat;
 import java.time.LocalTime;
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
@@ -89,9 +98,6 @@ public class LiveTask {
     @Autowired
     private ILiveOrderPaymentService liveOrderPaymentService;
 
-    @Autowired
-    private IFsUserService userService;
-
     @Autowired
     private ICompanyService companyService;
 
@@ -149,11 +155,69 @@ public class LiveTask {
     @Autowired
     private JdbcTemplate jdbcTemplate;
 
+    @Autowired
+    private HuiFuService huiFuService;
+
+    @Autowired
+    private IFsStoreOrderScrmService orderService;
+
+    @Autowired
+    private FsJstAftersalePushScrmService fsJstAftersalePushScrmService;
+
+    // 聚水潭 推送售后信息
+    public void pushJst(){
+        fsJstAftersalePushScrmService.pushJst();
+    }
+
+
+    // 订单银行回调数据丢失补偿
+    public void recoveryBankOrder() {
+        // 查询出来最近15分钟的订单 待支付 未退款
+        List<LiveOrder> list = liveOrderService.selectBankOrder();
+        if(list == null || list.isEmpty()) return;
+        for (LiveOrder liveOrder : list) {
+            List<LiveOrderPayment> liveOrderPayments = liveOrderPaymentMapper.selectLiveOrderPaymentByOrderId(liveOrder.getOrderId());
+            if(liveOrderPayments == null || liveOrderPayments.isEmpty()) continue;
+            for (LiveOrderPayment payment : liveOrderPayments) {
+                V2TradePaymentScanpayQueryRequest request = new V2TradePaymentScanpayQueryRequest();
+                request.setOrgReqDate(new SimpleDateFormat("yyyyMMdd").format(payment.getCreateTime()));
+                request.setOrgHfSeqId(payment.getTradeNo());
+                HuiFuQueryOrderResult o = null;
+                try {
+                    o = huiFuService.queryOrder(request);
+                } catch (Exception e) {
+                    log.error("查询失败:"+e.getMessage());
+                    continue;
+                }
+                log.info("汇付返回"+o);
+                if ("00000000".equals(o.getResp_code()) && "S".equals(o.getTrans_stat())) {
+                    String[] order=o.getOrg_req_seq_id().split("-");
+                    if ("live".equals(order[0])) {
+                        liveOrderService.payConfirm(1, null, order[1], o.getOrg_hf_seq_id(), o.getOut_trans_id(), o.getParty_order_id());
+                    }
+                }
+            }
+        }
+    }
+
+
     public void PushErp() throws ParseException {
-//        List<Long> ids = liveOrderMapper.selectOrderIdByNoErp();
-//        for (Long id : ids) {
-//            liveOrderService.createOmsOrder(id);
-//        }
+        List<Long> ids = liveOrderMapper.selectOrderIdByNoErp();
+        if(ids == null) return;
+        if (ids.size() > 50) {
+            ids = ids.subList(0, 50);
+        }
+//        liveOrderService.batchUpdateTimeIds(ids);
+        // 单个异常影响全部,跳过异常单子
+        for (Long id : ids) {
+            try {
+                liveOrderService.createOmsOrder(id);
+            } catch (Exception e) {
+                log.error("创建直播oms订单失败:"+id);
+                log.error("创建直播oms订单失败:"+e.getMessage());
+            }
+
+        }
     }
 
     public void redPacketSubMoney() throws Exception {
@@ -182,18 +246,21 @@ public class LiveTask {
     public void deliveryOp() {
         List<LiveOrder> list = liveOrderService.selectUpdateExpress();
         if(list == null || list.isEmpty()) return;
-        if (list.size() > 100) {
-            list = list.subList(0, 100);
-        }
+
         for (LiveOrder order : list) {
+            order.setUpdateTime(new Date());
+            liveOrderService.updateLiveOrder(order);
             ErpOrderQueryRequert request = new ErpOrderQueryRequert();
             request.setCode(order.getExtendOrderId());
             IErpOrderService erpOrderService = getErpOrderService();
             ErpOrderQueryResponse response = erpOrderService.getLiveOrder(request);
+            if(!response.getSuccess() && "429".equals(response.getCode())){
+                break;
+            }
             if (erpOrderService != dfOrderService) {
                 if (response.getOrders() != null && !response.getOrders().isEmpty()) {
                     for (ErpOrderQuery orderQuery : response.getOrders()) {
-                        if (orderQuery.getDeliverys() != null && orderQuery.getDeliverys().size() > 0) {
+                        if (orderQuery.getDeliverys() != null && !orderQuery.getDeliverys().isEmpty()) {
                             for (ErpDeliverys delivery : orderQuery.getDeliverys()) {
                                 if (delivery.getDelivery() && StringUtils.isNotEmpty(delivery.getMail_no())) {
                                     //更新商订单状态 删除REDIS

+ 29 - 6
fs-admin/src/main/java/com/fs/hisStore/task/MallStoreTask.java

@@ -3,7 +3,9 @@ package com.fs.hisStore.task;
 
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.json.JSONUtil;
+import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.DateUtils;
 import com.fs.company.service.ICompanyService;
 import com.fs.company.vo.RedPacketMoneyVO;
 import com.fs.course.mapper.FsCourseRedPacketLogMapper;
@@ -32,6 +34,7 @@ import com.fs.hisStore.mapper.FsStorePaymentScrmMapper;
 import com.fs.hisStore.mapper.FsStoreProductAttrValueScrmMapper;
 import com.fs.hisStore.param.*;
 import com.fs.hisStore.service.*;
+import com.fs.live.domain.LiveOrder;
 import com.fs.pay.pay.dto.OrderQueryDTO;
 import com.fs.pay.service.IPayService;
 import com.fs.store.config.StoreConfig;
@@ -49,6 +52,7 @@ import java.math.BigDecimal;
 import java.text.ParseException;
 import java.time.LocalTime;
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
@@ -165,8 +169,21 @@ public class MallStoreTask
         } else {
             ids = fsStoreOrderMapper.selectFsStoreOrderNoCreateOms();
         }
+        if (!ids.isEmpty() && ids.size() > 50) {
+            ids = ids.subList(0, 50);
+        }
+        // 单个异常影响全部,跳过异常单子
         for (Long id : ids) {
-            fsStoreOrderService.createOmsOrder(id);
+            try {
+                R omsOrder = fsStoreOrderService.createOmsOrder(id);
+                if ("500".equals(omsOrder.get("code"))) {
+
+                }
+            } catch (Exception e) {
+                log.error("创建商城oms订单失败:"+id);
+                log.error("创建商城oms订单失败:"+e.getMessage());
+            }
+
         }
     }
 
@@ -204,20 +221,26 @@ public class MallStoreTask
     public void deliveryOp()
     {
         List<FsStoreOrderScrm> list = fsStoreOrderMapper.selectUpdateExpress();
+        Date nowDate = DateUtils.getNowDate();
         for (FsStoreOrderScrm order : list){
+            order.setUpdateTime(new Date());
+            orderService.updateFsStoreOrderDb(order);
             ErpOrderQueryRequert request = new ErpOrderQueryRequert();
             request.setCode(order.getExtendOrderId());
             IErpOrderService erpOrderService = getErpOrderService();
             ErpOrderQueryResponse response = erpOrderService.getScrmOrder(request);
+            if(!response.getSuccess() && "429".equals(response.getCode())){
+                break;
+            }
             if (erpOrderService != dfOrderService) {
                 if(response.getOrders()!=null && !response.getOrders().isEmpty()){
                     for(ErpOrderQuery orderQuery : response.getOrders()){
-                        if(orderQuery.getDeliverys()!=null&&orderQuery.getDeliverys().size()>0){
-                            for(ErpDeliverys delivery:orderQuery.getDeliverys()){
-                                if(delivery.getDelivery()&&StringUtils.isNotEmpty(delivery.getMail_no())){
+                        if (orderQuery.getDeliverys() != null && !orderQuery.getDeliverys().isEmpty()) {
+                            for (ErpDeliverys delivery : orderQuery.getDeliverys()) {
+                                if (delivery.getDelivery() && StringUtils.isNotEmpty(delivery.getMail_no())) {
                                     //更新商订单状态 删除REDIS
-                                    orderService.deliveryOrder(order.getOrderCode(),delivery.getMail_no(),delivery.getExpress_code(),delivery.getExpress_name());
-                                    redisCache.deleteObject(DELIVERY+":"+order.getExtendOrderId());
+                                    orderService.deliveryOrder(order.getOrderCode(), delivery.getMail_no(), delivery.getExpress_code(), delivery.getExpress_name());
+                                    redisCache.deleteObject(DELIVERY + ":" + order.getExtendOrderId());
                                 }
                             }
 

+ 78 - 6
fs-admin/src/main/java/com/fs/live/controller/LiveAfterSalesController.java

@@ -1,5 +1,6 @@
 package com.fs.live.controller;
 
+import cn.hutool.core.date.DateTime;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
@@ -11,8 +12,11 @@ import com.fs.common.utils.ParseUtils;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.framework.web.service.TokenService;
+import com.fs.his.domain.FsStoreAfterSalesLogs;
 import com.fs.his.domain.FsUser;
+import com.fs.his.enums.FsStoreAfterSalesStatusEnum;
 import com.fs.his.service.IFsUserService;
+import com.fs.hisStore.vo.FsStoreOrderItemExportRefundZMVO;
 import com.fs.live.domain.LiveAfterSales;
 import com.fs.live.domain.LiveAfterSalesItem;
 import com.fs.live.domain.LiveAfterSalesLogs;
@@ -28,11 +32,13 @@ import com.fs.live.service.ILiveOrderService;
 import com.fs.live.vo.LiveAfterSalesVo;
 import com.github.pagehelper.PageHelper;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
 import java.text.ParseException;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * 售后记录Controller
@@ -57,11 +63,14 @@ public class LiveAfterSalesController extends BaseController
     private IFsUserService userService;
     @Autowired
     private ILiveOrderService orderService;
+    @Value("${cloud_host.company_name}")
+    private String signProjectName;
+
 
     /**
      * 获取售后记录详细信息
      */
-    @PreAuthorize("@ss.hasPermi('live:liveAfteraSales:query')")
+    @PreAuthorize("@ss.hasPermi('live:liveAfterSales:query')")
     @GetMapping(value = "/{id}")
     public R getInfo(@PathVariable("id") Long id)
     {
@@ -79,11 +88,15 @@ public class LiveAfterSalesController extends BaseController
     /**
      * 查询售后记录列表
      */
-    @PreAuthorize("@ss.hasPermi('live:liveAfteraSales:list')")
+    @PreAuthorize("@ss.hasPermi('live:liveAfterSales:list')")
     @GetMapping("/list")
     public TableDataInfo list(LiveAfterSalesVo liveAfterSales)
     {
         startPage();
+        // 将productName映射到productNameQuery用于查询
+        if (liveAfterSales.getProductName() != null && !liveAfterSales.getProductName().isEmpty()) {
+            liveAfterSales.setProductNameQuery(liveAfterSales.getProductName());
+        }
         List<LiveAfterSalesVo> list = liveAfterSalesService.selectLiveAfterSalesVoList(liveAfterSales);
         for (LiveAfterSalesVo liveAfterSalesVo : list) {
             liveAfterSalesVo.setUserPhone(ParseUtils.parsePhone(liveAfterSalesVo.getUserPhone()));
@@ -94,7 +107,7 @@ public class LiveAfterSalesController extends BaseController
     /**
      * 导出售后记录列表
      */
-    @PreAuthorize("@ss.hasPermi('live:liveAfteraSales:export')")
+    @PreAuthorize("@ss.hasPermi('live:liveAfterSales:export')")
     @Log(title = "售后记录", businessType = BusinessType.EXPORT)
     @GetMapping("/export")
     public AjaxResult export(LiveAfterSalesVo liveAfterSales)
@@ -102,6 +115,54 @@ public class LiveAfterSalesController extends BaseController
         PageHelper.clearPage();
         PageHelper.startPage(1, 10000, "");
         List<LiveAfterSalesVo> list = liveAfterSalesService.selectLiveAfterSalesVoList(liveAfterSales);
+        if("北京卓美".equals(signProjectName)){
+            List<FsStoreOrderItemExportRefundZMVO> zmvoList = list.stream()
+                    .map(vo -> {
+                        FsStoreOrderItemExportRefundZMVO zmvo = new FsStoreOrderItemExportRefundZMVO();
+                        try {
+                            zmvo.setPayCode(vo.getPayCode());
+                            zmvo.setOrderCode(vo.getOrderCode());
+                            zmvo.setStatus(vo.getOrderStatus().toString());
+                            zmvo.setUserId(vo.getUserId());
+                            zmvo.setProductName(vo.getProductName());
+                            zmvo.setBarCode(vo.getProductBarCode());
+                            zmvo.setSku(vo.getSku());
+                            zmvo.setNum(vo.getNum());
+                            zmvo.setPrice(vo.getPrice());
+                            zmvo.setCost(vo.getCost());
+//                            zmvo.setFPrice("");
+                            zmvo.setPayMoney(vo.getPayMoney());
+                            zmvo.setPayPostage(vo.getTotalPostage());
+                            zmvo.setCateName(vo.getCateName());
+                            zmvo.setRealName(vo.getUserName());
+                            zmvo.setUserPhone(vo.getUserPhone());
+                            zmvo.setUserAddress(vo.getUserAddress());
+                            zmvo.setCreateTime(vo.getCreateTime());
+                            zmvo.setPayTime(vo.getOrderPayTime());
+                            zmvo.setDeliverySn(vo.getOrderDeliverySn());
+                            zmvo.setDeliveryName(vo.getOrderDeliveryName());
+                            zmvo.setDeliveryId(vo.getOrderDeliveryId());
+                            zmvo.setCompanyName(vo.getCompanyName());
+                            zmvo.setCompanyUserNickName(vo.getCompanyUserNickName());
+                            zmvo.setRefundTime(vo.getCreateTime());
+//                            zmvo.setAfterSalesNumber
+                            zmvo.setRefundMoney(vo.getRefundAmount());
+                            zmvo.setBankTransactionId(vo.getBankTransactionId());
+                            zmvo.setReasons(vo.getReasons());
+                            zmvo.setExplains(vo.getExplains());
+                        } catch (Exception e) {
+                            // 处理异常
+                            e.printStackTrace();
+                        }
+                        return zmvo;
+                    })
+                    .collect(Collectors.toList());
+            for (FsStoreOrderItemExportRefundZMVO vo : zmvoList){
+                vo.setUserPhone(ParseUtils.parsePhone(vo.getUserPhone()));
+            }
+            ExcelUtil<FsStoreOrderItemExportRefundZMVO> util = new ExcelUtil<FsStoreOrderItemExportRefundZMVO>(FsStoreOrderItemExportRefundZMVO.class);
+            return util.exportExcel(zmvoList, "退款订单导出");
+        }
         for (LiveAfterSalesVo liveAfterSalesVo : list) {
             liveAfterSalesVo.setUserPhone(liveAfterSalesVo.getUserPhone() == null ? "" : liveAfterSalesVo.getUserPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
             liveAfterSalesVo.setPhoneNumber(liveAfterSalesVo.getPhoneNumber() == null ? "" : liveAfterSalesVo.getPhoneNumber().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
@@ -114,7 +175,7 @@ public class LiveAfterSalesController extends BaseController
     /**
      * 新增售后记录
      */
-    @PreAuthorize("@ss.hasPermi('live:liveAfteraSales:add')")
+    @PreAuthorize("@ss.hasPermi('live:liveAfterSales:add')")
     @Log(title = "售后记录", businessType = BusinessType.INSERT)
     @PostMapping
     public AjaxResult add(@RequestBody LiveAfterSales liveAfterSales)
@@ -125,18 +186,29 @@ public class LiveAfterSalesController extends BaseController
     /**
      * 修改售后记录
      */
-    @PreAuthorize("@ss.hasPermi('live:liveAfteraSales:edit')")
+    @PreAuthorize("@ss.hasPermi('live:liveAfterSales:edit')")
     @Log(title = "售后记录", businessType = BusinessType.UPDATE)
     @PutMapping
     public AjaxResult edit(@RequestBody LiveAfterSales liveAfterSales)
     {
+
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        //操作记录
+        LiveAfterSalesLogs logs = new LiveAfterSalesLogs();
+        logs.setChangeTime(new DateTime());
+        logs.setChangeType(2);
+        logs.setOperator(loginUser.getUser().getNickName());
+        logs.setStoreAfterSalesId(liveAfterSales.getId());
+        logs.setChangeMessage(FsStoreAfterSalesStatusEnum.STATUS_2.getDesc());
+        liveAfterSales.setStatus(FsStoreAfterSalesStatusEnum.STATUS_2.getValue());
+        liveAfterSalesLogsService.insertLiveAfterSalesLogs(logs);
         return toAjax(liveAfterSalesService.updateLiveAfterSales(liveAfterSales));
     }
 
     /**
      * 删除售后记录
      */
-    @PreAuthorize("@ss.hasPermi('live:liveAfteraSales:remove')")
+    @PreAuthorize("@ss.hasPermi('live:liveAfterSales:remove')")
     @Log(title = "售后记录", businessType = BusinessType.DELETE)
 	@DeleteMapping("/{ids}")
     public AjaxResult remove(@PathVariable Long[] ids)

+ 63 - 2
fs-admin/src/main/java/com/fs/live/controller/LiveController.java

@@ -4,14 +4,25 @@ import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.model.LoginUser;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.vo.CompanyVO;
+import com.fs.framework.web.service.TokenService;
 import com.fs.hisStore.task.LiveTask;
 import com.fs.hisStore.task.MallStoreTask;
 import com.fs.live.domain.Live;
 import com.fs.live.service.ILiveService;
 import com.fs.live.vo.LiveListVo;
+import com.fs.qw.domain.QwTagGroup;
+import com.fs.qw.service.IQwTagGroupService;
+import com.fs.qw.service.impl.QwUserServiceImpl;
+import com.fs.qw.vo.QwOptionsVO;
+import com.fs.qw.vo.QwTagGroupListVO;
+import com.hc.openapi.tool.fastjson.JSON;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
@@ -28,10 +39,20 @@ import java.util.Map;
  */
 @RestController
 @RequestMapping("/live/live")
+@Slf4j
 public class LiveController extends BaseController {
     @Autowired
     private ILiveService liveService;
 
+    @Autowired
+    private TokenService tokenService;
+
+    @Autowired
+    QwUserServiceImpl qwUserService;
+
+    @Autowired
+    private IQwTagGroupService qwTagGroupService;
+
 
     /**
      * 查询直播列表
@@ -63,7 +84,7 @@ public class LiveController extends BaseController {
     @PreAuthorize("@ss.hasPermi('live:live:query')")
     @GetMapping(value = "/{liveId}")
     public AjaxResult getInfo(@PathVariable("liveId") Long liveId) {
-        return AjaxResult.success(liveService.selectLiveByLiveId(liveId));
+        return AjaxResult.success(liveService.selectLiveDbByLiveId(liveId));
     }
 
     /**
@@ -83,6 +104,8 @@ public class LiveController extends BaseController {
     @Log(title = "直播", businessType = BusinessType.UPDATE)
     @PutMapping
     public AjaxResult edit(@RequestBody Live live) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        log.info("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( live));
         return toAjax(liveService.updateLive(live));
     }
 
@@ -93,7 +116,9 @@ public class LiveController extends BaseController {
     @Log(title = "直播", businessType = BusinessType.DELETE)
     @DeleteMapping("/{liveIds}")
     public AjaxResult remove(@PathVariable Long[] liveIds) {
-        return toAjax(liveService.deleteLiveByLiveIds(liveIds));
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        log.info("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( liveIds));
+        return toAjax(liveService.deleteLiveByLiveIds(liveIds, new Live()));
     }
 
     @PreAuthorize("@ss.hasPermi('live:live:query')")
@@ -116,6 +141,8 @@ public class LiveController extends BaseController {
     @PreAuthorize("@ss.hasPermi('live:live:edit')")
     @PostMapping("/handleShelfOrUn")
     public R handleShelfOrUn(@RequestBody LiveListVo listVo) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        log.info("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( listVo));
         return liveService.handleShelfOrUnAdmin(listVo);
     }
 
@@ -125,6 +152,8 @@ public class LiveController extends BaseController {
     @PreAuthorize("@ss.hasPermi('live:live:edit')")
     @PostMapping("/handleDeleteSelected")
     public R handleDeleteSelected(@RequestBody LiveListVo listVo) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        log.info("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( listVo));
         return liveService.handleDeleteSelectedAdmin(listVo);
     }
     /**
@@ -169,4 +198,36 @@ public class LiveController extends BaseController {
     }
 
 
+    /**
+     * 获取公司下拉列表
+     * @return
+     */
+    @GetMapping("/getCompanyDropList")
+    public R getCompanyDropList(){
+        List<CompanyVO> companyDropList = liveService.getCompanyDropList();
+        return R.ok().put("data",companyDropList);
+    }
+
+    @GetMapping("/getQwCorpList/{companyId}")
+    public R getQwCorpList(@PathVariable Long companyId){
+        List<QwOptionsVO> qwOptionsVOS = qwUserService.selectQwCompanyListOptionsVOByCompanyId(companyId);
+        return R.ok().put("data",qwOptionsVOS);
+    }
+
+    @GetMapping("/getTagsListByCorpId")
+    public TableDataInfo getTagsListByCorpId(QwTagGroup qwTagGroup){
+        startPage();
+        List<QwTagGroupListVO> list = qwTagGroupService.selectQwTagGroupListVO(qwTagGroup);
+        return getDataTable(list);
+    }
+
+    /**
+     * 清除直播间缓存
+     */
+    @Log(title = "直播", businessType = BusinessType.UPDATE)
+    @PostMapping("/clearCache/{liveId}")
+    public R clearCache(@PathVariable("liveId") Long liveId) {
+        return liveService.clearLiveCache(liveId);
+    }
+
 }

+ 66 - 0
fs-admin/src/main/java/com/fs/live/controller/LiveDataController.java

@@ -1,13 +1,18 @@
 package com.fs.live.controller;
 
+import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
 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.SecurityUtils;
+import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.live.domain.LiveData;
 import com.fs.live.param.LiveDataParam;
 import com.fs.live.service.ILiveDataService;
 import com.fs.live.vo.LiveUserFirstVo;
+import com.fs.live.vo.LiveUserDetailExportVO;
 import com.github.pagehelper.PageHelper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -109,5 +114,66 @@ public class LiveDataController extends BaseController {
         return R.ok(liveViewData);
     }
 
+    /**
+     * 查询直播间详情数据(SQL方式)
+     * @param liveId 直播间ID
+     * @return 详情数据
+     */
+    @PreAuthorize("@ss.hasPermi('liveData:liveData:query')")
+    @GetMapping("/getLiveDataDetailBySql")
+    public R getLiveDataDetailBySql(@RequestParam Long liveId) {
+        return liveDataService.getLiveDataDetailBySql(liveId);
+    }
+
+    /**
+     * 查询直播间用户详情列表(SQL方式)
+     * @param liveId 直播间ID
+     * @return 用户详情列表
+     */
+    @PreAuthorize("@ss.hasPermi('liveData:liveData:query')")
+    @GetMapping("/getLiveUserDetailListBySql")
+    public R getLiveUserDetailListBySql(@RequestParam Long liveId) {
+        return liveDataService.getLiveUserDetailListBySql(liveId,null,null);
+    }
+
+    /**
+     * 查询直播间详情数据(查询数据服务器处理方式)
+     * @param liveId 直播间ID
+     * @return 详情数据
+     */
+    @PreAuthorize("@ss.hasPermi('liveData:liveData:query')")
+    @GetMapping("/getLiveDataDetailByServer")
+    public R getLiveDataDetailByServer(@RequestParam Long liveId) {
+        return liveDataService.getLiveDataDetailByServer(liveId);
+    }
+
+    /**
+     * 查询直播间用户详情列表(查询数据服务器处理方式)
+     * @param liveId 直播间ID
+     * @return 用户详情列表
+     */
+    @PreAuthorize("@ss.hasPermi('liveData:liveData:query')")
+    @GetMapping("/getLiveUserDetailListByServer")
+    public R getLiveUserDetailListByServer(@RequestParam Long liveId) {
+        return liveDataService.getLiveUserDetailListByServer(liveId);
+    }
+
+    /**
+     * 导出直播间用户详情数据
+     * @param liveId 直播间ID
+     * @return Excel文件
+     */
+    @PreAuthorize("@ss.hasPermi('liveData:liveData:export')")
+    @Log(title = "直播间用户详情", businessType = BusinessType.EXPORT)
+    @GetMapping("/exportLiveUserDetail")
+    public AjaxResult exportLiveUserDetail(@RequestParam Long liveId) {
+        List<LiveUserDetailExportVO> list = liveDataService.exportLiveUserDetail(liveId,null,null);
+        if (list == null || list.isEmpty()) {
+            return AjaxResult.error("未找到用户详情数据");
+        }
+
+        ExcelUtil<LiveUserDetailExportVO> util = new ExcelUtil<>(LiveUserDetailExportVO.class);
+        return util.exportExcel(list, "直播间用户详情数据");
+    }
 
 }

+ 88 - 14
fs-admin/src/main/java/com/fs/live/controller/LiveOrderController.java

@@ -31,11 +31,14 @@ import com.fs.his.enums.FsStoreOrderLogEnum;
 import com.fs.his.service.IFsDfAccountService;
 import com.fs.his.service.IFsExpressService;
 import com.fs.his.service.IFsUserService;
+import com.fs.his.utils.ConfigUtil;
+import com.fs.hisStore.config.FsErpConfig;
 import com.fs.hisStore.dto.StoreOrderExpressExportDTO;
 import com.fs.hisStore.param.*;
 import com.fs.hisStore.service.IFsExpressScrmService;
 import com.fs.hisStore.task.ExpressTask;
 import com.fs.hisStore.task.LiveTask;
+import com.fs.hisStore.vo.FsStoreOrderItemExportZMVO;
 import com.fs.hisStore.vo.FsStoreOrderVO;
 import com.fs.live.domain.*;
 import com.fs.live.dto.LiveOrderCustomerExportDTO;
@@ -116,6 +119,27 @@ public class LiveOrderController extends BaseController
 
     @Autowired
     private LiveTask liveTask;
+    @Autowired
+    @Qualifier("erpOrderServiceImpl")
+    private IErpOrderService gyOrderService;
+
+    @Autowired
+    @Qualifier("wdtErpOrderServiceImpl")
+    private IErpOrderService wdtOrderService;
+    @Autowired
+    @Qualifier("hzOMSErpOrderServiceImpl")
+    private IErpOrderService hzOMSErpOrderService;
+    @Autowired
+    @Qualifier("dfOrderServiceImpl")
+    private IErpOrderService dfOrderService;
+    @Autowired
+    @Qualifier("k9OrderScrmServiceImpl")
+    private IErpOrderService k9OrderService;
+    @Autowired
+    @Qualifier("JSTErpOrderServiceImpl")
+    private IErpOrderService jSTOrderService;
+    @Autowired
+    private ConfigUtil configUtil;
 
 
 
@@ -181,6 +205,7 @@ public class LiveOrderController extends BaseController
                 vo.setPayDelivery(BigDecimal.ZERO);
                 vo.setBarCode("");
                 vo.setCateName("");
+                vo.setBankTransactionId("");
             }
             vo.setCost(vo.getCostPrice());
 
@@ -205,11 +230,12 @@ public class LiveOrderController extends BaseController
             vo.setUserAddress(ParseUtils.parseAddress(vo.getUserAddress()));
             // 财务独特字段
             if (loginUser.getPermissions().contains("live:liveOrder:finance") || loginUser.getPermissions().contains("*:*:*")) {
-                vo.setCostPrice(vo.getCostPrice());
+                vo.setCostPrice(vo.getCost());
                 vo.setFPrice(vo.getCostPrice().multiply(BigDecimal.valueOf(Long.parseLong(vo.getTotalNum()))));
             } else {
                 vo.setCostPrice(BigDecimal.ZERO);
                 vo.setFPrice(BigDecimal.ZERO);
+                vo.setBankTransactionId("");
             }
             vo.setCost(vo.getCostPrice());
         }
@@ -217,7 +243,34 @@ public class LiveOrderController extends BaseController
         return util.exportExcel(list, "订单数据");
     }
 
-
+    /**
+     * 导出订单列表
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveOrder:export')")
+    @Log(title = "订单", businessType = BusinessType.EXPORT)
+    @GetMapping("/exportZmNew")
+    public AjaxResult exportZmNew(LiveOrder liveOrder){
+        List<FsStoreOrderItemExportZMVO> list = liveOrderService.selectLiveOrderListZmNew(liveOrder);
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        for (FsStoreOrderItemExportZMVO vo : list){
+            vo.setUserPhone(ParseUtils.parsePhone(vo.getUserPhone()));
+//            vo.setCompanyUserPhone(ParseUtils.parsePhone(vo.getCompanyUserPhone()));
+//            vo.setUserBindPhone(ParseUtils.parsePhone(vo.getUserBindPhone()));
+            vo.setUserAddress(ParseUtils.parseAddress(vo.getUserAddress()));
+            // 财务独特字段
+            if (loginUser.getPermissions().contains("live:liveOrder:finance") || loginUser.getPermissions().contains("*:*:*")) {
+//                vo.setCostPrice(vo.getCost());
+                vo.setFPrice(vo.getCost().multiply(BigDecimal.valueOf(vo.getNum())));
+            } else {
+                vo.setCost(BigDecimal.ZERO);
+                vo.setFPrice(BigDecimal.ZERO);
+                vo.setBankTransactionId("");
+            }
+//            vo.setCost(vo.getCostPrice());
+        }
+        ExcelUtil<FsStoreOrderItemExportZMVO> util = new ExcelUtil<FsStoreOrderItemExportZMVO>(FsStoreOrderItemExportZMVO.class);
+        return util.exportExcel(list, "订单数据");
+    }
     /**
      * 获取订单详细信息
      */
@@ -435,20 +488,41 @@ public class LiveOrderController extends BaseController
     @PreAuthorize("@ss.hasPermi('live:liveOrder:getEroOrder')")
     @GetMapping("/getEroOrder")
     public R getEroOrder(@RequestParam("extendOrderId") String extendOrderId) {
+        IErpOrderService erpOrderService = getErpService();
         ErpOrderQueryRequert request = new ErpOrderQueryRequert();
         request.setCode(extendOrderId);
-        if(StringUtils.isEmpty(extendOrderId)) return R.error("物流订单ID为空!");
-
-        LiveOrder order = liveOrderService.selectLiveOrderByExtendId(extendOrderId);
-
-        // 根据仓库code找erp
-//        if(com.fs.common.utils.StringUtils.isNotBlank(order.getStoreHouseCode())){
-//            String erp = fsWarehousesMapper.selectErpByCode(order.getStoreHouseCode());
-//            ErpContextHolder.setErpType(erp);
-//        }
-
-//        ErpOrderQueryResponse response = erpOrderService.getOrderLive(request);
-        return R.ok().put("data","123");
+        ErpOrderQueryResponse response = erpOrderService.getLiveOrder(request);
+        return R.ok().put("data",response);
+    }
+    private IErpOrderService getErpService(){
+        //判断是否开启erp
+        IErpOrderService erpOrderService = null;
+        FsErpConfig erpConfig = configUtil.getErpConfig();
+        Integer erpOpen = erpConfig.getErpOpen();
+        if (erpOpen != null && erpOpen == 1) {
+            //判断erp类型
+            Integer erpType = erpConfig.getErpType();
+            if (erpType != null) {
+                if (erpType == 1){
+                    //管易
+                    erpOrderService =  gyOrderService;
+                } else if (erpType == 2){
+                    //旺店通
+                    erpOrderService =  wdtOrderService;
+                } else if (erpType == 3){
+                    //
+                    erpOrderService =  hzOMSErpOrderService;
+                } else if (erpType == 4){
+                    //代服
+                    erpOrderService =  dfOrderService;
+                }else if(erpType == 5){
+                    erpOrderService=jSTOrderService;
+                }else if(erpType == 6){
+                    erpOrderService=k9OrderService;
+                }
+            }
+        }
+        return erpOrderService;
     }
 
     @Log(title = "冻结、解冻佣金", businessType = BusinessType.UPDATE)

+ 4 - 0
fs-admin/src/main/java/com/fs/live/controller/LiveVideoController.java

@@ -6,6 +6,9 @@ import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.config.cloud.CloudHostProper;
+import com.fs.hisStore.enums.CompanyEnum;
+import com.fs.hisStore.enums.LiveEnum;
 import com.fs.live.domain.LiveVideo;
 import com.fs.live.service.ILiveVideoService;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -89,6 +92,7 @@ public class LiveVideoController extends BaseController
     @PostMapping
     public AjaxResult add(@RequestBody LiveVideo liveVideo)
     {
+
         return toAjax(liveVideoService.insertLiveVideo(liveVideo));
     }
 

+ 307 - 0
fs-admin/src/main/java/com/fs/live/controller/OrderController.java

@@ -0,0 +1,307 @@
+package com.fs.live.controller;
+
+import cn.hutool.core.bean.BeanUtil;
+import com.alibaba.fastjson.JSONObject;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ParseUtils;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.framework.web.service.TokenService;
+import com.fs.his.utils.PhoneUtil;
+import com.fs.hisStore.dto.StoreOrderProductDTO;
+import com.fs.hisStore.service.IMergedOrderService;
+import com.fs.hisStore.vo.FsStoreOrderItemExportVO;
+import com.fs.live.param.MergedOrderQueryParam;
+import com.fs.live.vo.MergedOrderVO;
+import com.fs.live.vo.MergedOrderExportVO;
+import com.fs.common.utils.poi.ExcelUtil;
+import org.springframework.beans.BeanUtils;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import io.swagger.annotations.Api;
+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.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * 合并订单Controller
+ *
+ * @author fs
+ * @date 2025-01-XX
+ */
+@Api("合并订单管理")
+@RestController
+@RequestMapping("/order")
+public class OrderController extends BaseController
+{
+    @Autowired
+    private IMergedOrderService mergedOrderService;
+    // 设置最大导出数量限制为20000条
+    private static final int maxExportCount = 20000;
+
+
+    @Autowired
+    private TokenService tokenService;
+
+    /**
+     * 查询合并订单列表
+     */
+    @ApiOperation("查询合并订单列表")
+    @GetMapping("/list")
+    public TableDataInfo list(MergedOrderQueryParam param)
+    {
+        startPage();
+        List<MergedOrderVO> list = mergedOrderService.selectMergedOrderList(param);
+        for (MergedOrderVO vo : list) {
+            vo.setUserPhone(ParseUtils.parsePhone(vo.getUserPhone()));
+            vo.setPhone(ParseUtils.parsePhone(vo.getPhone()));
+            vo.setSalesPhone(ParseUtils.parsePhone(vo.getSalesPhone()));
+            vo.setUserAddress(ParseUtils.parseAddress(vo.getUserAddress()));
+            vo.setCost(BigDecimal.ZERO);
+        }
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出合并订单列表
+     */
+    @PreAuthorize("@ss.hasPermi('live:order:export')")
+    @ApiOperation("导出合并订单列表")
+    @Log(title = "合并订单", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(MergedOrderQueryParam param)
+    {
+        // 先查询数据,限制查询20001条,用于判断是否超过限制
+        PageHelper.startPage(1, maxExportCount + 1);
+        List<MergedOrderVO> list = mergedOrderService.selectMergedOrderList(param);
+        
+        // 如果查询结果超过20000条,返回错误提示
+        if (list != null && list.size() > maxExportCount) {
+            return AjaxResult.error("导出数据量超过限制,最多只能导出" + maxExportCount + "条数据,请缩小查询范围后重试");
+        }
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+
+        for (MergedOrderVO vo : list) {
+            if (!StringUtils.isEmpty(vo.getItemJson())) {
+                try {
+                    StoreOrderProductDTO orderProductDTO = JSONObject.parseObject(vo.getItemJson(), StoreOrderProductDTO.class);
+                    BeanUtil.copyProperties(orderProductDTO, vo);
+                } catch (Exception e) {
+                    System.out.println(e.getMessage());
+                }
+            }
+        }
+
+        // 转换为导出VO
+        List<MergedOrderExportVO> exportList = convertToExportVO(list, false,loginUser);
+        
+        // 如果数据量在限制范围内,正常导出
+        ExcelUtil<MergedOrderExportVO> util = new ExcelUtil<>(MergedOrderExportVO.class);
+        return util.exportExcel(exportList, "合并订单数据");
+    }
+
+    /**
+     * 导出合并订单(明文)
+     */
+    @ApiOperation("导出合并订单(明文)")
+    @Log(title = "合并订单(明文)", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('live:order:exportAll')")
+    @GetMapping("/exportDetails")
+    public AjaxResult exportDetails(MergedOrderQueryParam param)
+    {
+        // 先查询数据,限制查询20001条,用于判断是否超过限制
+        PageHelper.startPage(1, maxExportCount + 1);
+        List<MergedOrderVO> list = mergedOrderService.selectMergedOrderList(param);
+
+        // 如果查询结果超过20000条,返回错误提示
+        if (list != null && list.size() > maxExportCount) {
+            return AjaxResult.error("导出数据量超过限制,最多只能导出" + maxExportCount + "条数据,请缩小查询范围后重试");
+        }
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+
+        for (MergedOrderVO vo : list) {
+            if (!StringUtils.isEmpty(vo.getItemJson())) {
+                try {
+                    StoreOrderProductDTO orderProductDTO = JSONObject.parseObject(vo.getItemJson(), StoreOrderProductDTO.class);
+                    BeanUtil.copyProperties(orderProductDTO, vo);
+                } catch (Exception e) {
+                }
+            }
+            //
+
+        }
+
+        // 转换为导出VO(明文模式,不脱敏)
+        List<MergedOrderExportVO> exportList = convertToExportVO(list, true,loginUser);
+
+        ExcelUtil<MergedOrderExportVO> util = new ExcelUtil<>(MergedOrderExportVO.class);
+        return util.exportExcel(exportList, "合并订单(明文)");
+    }
+
+    /**
+     * 导出合并订单明细
+     */
+    // 预留接口
+    @PreAuthorize("@ss.hasPermi('live:order:exportOther')")
+    @ApiOperation("导出合并订单明细")
+    @Log(title = "合并订单明细", businessType = BusinessType.EXPORT)
+    @GetMapping("/exportItems")
+    public AjaxResult exportItems(MergedOrderQueryParam param)
+    {
+        // 先查询数据,限制查询20001条,用于判断是否超过限制
+        PageHelper.startPage(1, maxExportCount + 1);
+        List<MergedOrderVO> list = mergedOrderService.selectMergedOrderList(param);
+
+        // 如果查询结果超过20000条,返回错误提示
+        if (list != null && list.size() > maxExportCount) {
+            return AjaxResult.error("导出数据量超过限制,最多只能导出" + maxExportCount + "条数据,请缩小查询范围后重试");
+        }
+
+        ExcelUtil<MergedOrderVO> util = new ExcelUtil<>(MergedOrderVO.class);
+        return util.exportExcel(list, "合并订单明细");
+    }
+
+
+
+    /**
+     * 导出合并订单明细(明文)
+     */
+    // 预留接口
+    @PreAuthorize("@ss.hasPermi('live:order:exportOther')")
+    @ApiOperation("导出合并订单明细(明文)")
+    @Log(title = "合并订单明细(明文)", businessType = BusinessType.EXPORT)
+    @GetMapping("/exportItemsDetails")
+    public AjaxResult exportItemsDetails(MergedOrderQueryParam param)
+    {
+        // 先查询数据,限制查询20001条,用于判断是否超过限制
+        PageHelper.startPage(1, maxExportCount + 1);
+        List<MergedOrderVO> list = mergedOrderService.selectMergedOrderList(param);
+
+        // 如果查询结果超过20000条,返回错误提示
+        if (list != null && list.size() > maxExportCount) {
+            return AjaxResult.error("导出数据量超过限制,最多只能导出" + maxExportCount + "条数据,请缩小查询范围后重试");
+        }
+
+        ExcelUtil<MergedOrderVO> util = new ExcelUtil<>(MergedOrderVO.class);
+        return util.exportExcel(list, "合并订单明细(明文)");
+    }
+
+    /**
+     * 导出合并订单发货单
+     */
+    @ApiOperation("导出合并订单发货单")
+    @Log(title = "合并订单发货单", businessType = BusinessType.EXPORT)
+    @GetMapping("/exportShipping")
+    public AjaxResult exportShipping(MergedOrderQueryParam param)
+    {
+        // 先查询数据,限制查询20001条,用于判断是否超过限制
+        PageHelper.startPage(1, maxExportCount + 1);
+        List<MergedOrderVO> list = mergedOrderService.selectMergedOrderList(param);
+        // 如果查询结果超过20000条,返回错误提示
+        if (list != null && list.size() > maxExportCount) {
+            return AjaxResult.error("导出数据量超过限制,最多只能导出" + maxExportCount + "条数据,请缩小查询范围后重试");
+        }
+        ExcelUtil<MergedOrderVO> util = new ExcelUtil<>(MergedOrderVO.class);
+        return util.exportExcel(list, "合并订单发货单");
+    }
+
+    /**
+     * 将 MergedOrderVO 转换为 MergedOrderExportVO
+     * @param list 原始数据列表
+     * @param isPlainText 是否为明文模式(true:不脱敏,false:脱敏)
+     * @return 导出VO列表
+     */
+    private List<MergedOrderExportVO> convertToExportVO(List<MergedOrderVO> list, boolean isPlainText,LoginUser loginUser)
+    {
+        if (list == null || list.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        return list.stream().map(vo -> {
+            MergedOrderExportVO exportVO = new MergedOrderExportVO();
+            
+            // 订单基本信息(参考 FsStoreOrderItemExportVO 的顺序)
+            exportVO.setOrderCode(vo.getOrderCode());
+            exportVO.setStatus(vo.getStatus() != null ? String.valueOf(vo.getStatus()) : null);
+            exportVO.setUserId(vo.getUserId());
+            
+            // 产品信息
+            exportVO.setProductName(vo.getProductName());
+            exportVO.setBarCode(vo.getBarCode());
+            exportVO.setProductSpec(StringUtils.isEmpty(vo.getProductSpec()) ? "默认" : vo.getProductSpec());
+            exportVO.setTotalNum(vo.getTotalNum());
+            exportVO.setPrice(vo.getTotalPrice()); // 产品价格使用订单总价
+            exportVO.setCost(vo.getCost());
+            exportVO.setFPrice(vo.getCost() != null ? vo.getCost().multiply(BigDecimal.valueOf(vo.getTotalNum())) : BigDecimal.ZERO); // 结算价,合并订单暂无此字段
+            exportVO.setPayPostage(vo.getPayDelivery());
+            exportVO.setCateName(vo.getCateName());
+            // 收货信息
+            exportVO.setRealName(vo.getRealName());
+            if (isPlainText) {
+                exportVO.setUserPhone(vo.getUserPhone());
+                exportVO.setUserAddress(vo.getUserAddress());
+            } else {
+                exportVO.setUserPhone(ParseUtils.parsePhone(vo.getUserPhone()));
+                exportVO.setUserAddress(ParseUtils.parseAddress(vo.getUserAddress()));
+            }
+            
+            // 时间信息
+            exportVO.setCreateTime(vo.getCreateTime());
+            exportVO.setPayTime(vo.getPayTime());
+            
+            // 物流信息
+            exportVO.setDeliverySn(vo.getDeliveryCode()); // 快递公司编号,合并订单暂无此字段
+            exportVO.setDeliveryName(vo.getDeliveryName()); // 快递公司,合并订单暂无此字段
+            exportVO.setDeliveryId(vo.getDeliveryId());
+            
+            // 公司和销售信息
+            exportVO.setCompanyName(vo.getCompanyName());
+            exportVO.setCompanyUserNickName(vo.getCompanyUserNickName());
+            
+            // 套餐信息
+            exportVO.setPackageName(null); // 套餐名称,合并订单暂无此字段
+            exportVO.setGroupBarCode(null); // 组合码,合并订单暂无此字段
+            
+            // 凭证信息
+            exportVO.setIsUpload(null); // 是否上传凭证,合并订单暂无此字段
+            exportVO.setUploadTime(null); // 上传时间,合并订单暂无此字段
+            
+            // 档期信息
+            exportVO.setScheduleName(null); // 归属档期,合并订单暂无此字段
+            
+            // 银行交易流水号
+            exportVO.setBankTransactionId(vo.getBankTransactionId());
+            
+            // 金额信息
+            exportVO.setTotalPrice(vo.getTotalPrice());
+            exportVO.setPayPrice(vo.getPayPrice());
+            exportVO.setPayMoney(vo.getPayMoney());
+            exportVO.setPayPostage(vo.getPayDelivery()); // 额外运费,合并订单暂无此字段
+            exportVO.setPayDelivery(vo.getPayDelivery());
+            if ((loginUser.getPermissions().contains("order:finance") || loginUser.getPermissions().contains("*:*:*") ) && !Objects.isNull(vo.getCost())) {
+                vo.setFPrice(vo.getCost().multiply(BigDecimal.valueOf(vo.getTotalNum())));
+            } else {
+                vo.setPayPostage(BigDecimal.ZERO);
+                vo.setCost(BigDecimal.ZERO);
+                vo.setFPrice(BigDecimal.ZERO);
+                vo.setBankTransactionId("");
+            }
+            
+            return exportVO;
+        }).collect(Collectors.toList());
+    }
+}

+ 14 - 1
fs-admin/src/main/java/com/fs/qw/controller/QwExternalContactController.java

@@ -5,14 +5,17 @@ import java.util.Objects;
 
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.util.ObjectUtil;
+import com.fs.common.core.domain.R;
 import com.fs.common.exception.ServiceException;
 import com.fs.qw.param.QwExternalContactParam;
 import com.fs.qw.param.QwTagSearchParam;
+import com.fs.qw.service.IQwExternalContactInfoService;
 import com.fs.qw.service.IQwTagService;
 import com.fs.qw.vo.QwExternalContactUnionIdExportVO;
 import com.fs.qw.vo.QwExternalContactVO;
 import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
@@ -33,7 +36,7 @@ import com.fs.common.core.page.TableDataInfo;
 
 /**
  * 企业微信客户Controller
- * 
+ *
  * @author fs
  * @date 2025-06-13
  */
@@ -44,6 +47,9 @@ public class QwExternalContactController extends BaseController
     private IQwExternalContactService qwExternalContactService;
     private IQwTagService iQwTagService;
 
+    @Autowired
+    private IQwExternalContactInfoService qwExternalContactInfoService;
+
     QwExternalContactController(IQwExternalContactService qwExternalContactService,IQwTagService iQwTagService){
         this.qwExternalContactService=qwExternalContactService;
         this.iQwTagService=iQwTagService;
@@ -168,4 +174,11 @@ public class QwExternalContactController extends BaseController
     {
         return toAjax(qwExternalContactService.deleteQwExternalContactByIds(ids));
     }
+
+    @GetMapping(value = "getUserInfo/{id}")
+    public R getUserInfo(@PathVariable("id") Long id)
+    {
+        return R.ok().put("data",qwExternalContactInfoService.selectQwExternalContactInfoByExternalContactId(id));
+    }
+
 }

+ 43 - 0
fs-admin/src/main/java/com/fs/qw/controller/QwFriendWelcomeController.java

@@ -0,0 +1,43 @@
+package com.fs.qw.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.qw.param.QwFriendWelcomeParam;
+import com.fs.qw.service.IQwFriendWelcomeService;
+import com.fs.qw.vo.QwFriendWelcomeVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/qw/friendWelcome")
+public class QwFriendWelcomeController extends BaseController {
+
+    @Autowired
+    private IQwFriendWelcomeService qwFriendWelcomeService;
+
+    /**
+     * 查询好友欢迎语列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:friendWelcome:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(QwFriendWelcomeParam qwFriendWelcomeParam) {
+        startPage();
+        List<QwFriendWelcomeVO> list = qwFriendWelcomeService.selectQwFriendWelcomeList(qwFriendWelcomeParam);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取好友欢迎语详细信息
+     */
+    @GetMapping(value = "/{id}")
+    public R getInfo(@PathVariable("id") Long id) {
+        return R.ok().put("row", qwFriendWelcomeService.selectQwFriendWelcomeById(id));
+    }
+}

+ 30 - 6
fs-admin/src/main/java/com/fs/qw/controller/QwSopController.java

@@ -21,12 +21,15 @@ import com.fs.sop.params.QwSopAutoTime;
 import com.fs.sop.params.QwSopEditQwUserParam;
 import com.fs.sop.service.IQwSopService;
 import com.fs.sop.vo.SopVoiceListVo;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
 import java.io.IOException;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 
@@ -53,16 +56,37 @@ public class QwSopController extends BaseController
     @Autowired
     private FsUserCourseVideoMapper fsUserCourseVideoMapper;
 
+
     /**
      * 查询企微sop列表
      */
     @PreAuthorize("@ss.hasPermi('qw:sop:list')")
-    @GetMapping("/list")
-    public TableDataInfo list(QwSop qwSop)
+    @PostMapping("/list")
+    public R list(@RequestBody  QwSop qwSop)
     {
-        startPage();
+        List<String> userIds = qwSop.getUserIds();
+        if (userIds != null && !userIds.isEmpty()) {
+            List<Long> longs = qwUserService.selectQwUserListByCompanyUserIdS(userIds);
+            if (longs != null && !longs.isEmpty()) {
+                qwSop.getQwUserIdList().addAll(longs);
+            }else {
+                return R.ok().put("data",new ArrayList<>());
+            }
+        }
+
+
+        if(qwSop.getPageNum() == null) {
+            qwSop.setPageNum(1);
+        }
+        if(qwSop.getPageSize() == null) {
+            qwSop.setPageSize(10);
+        }
+
+        PageHelper.startPage(qwSop.getPageNum(), qwSop.getPageSize());
+
         List<QwSop> list = qwSopService.selectQwSopList(qwSop);
-        return getDataTable(list);
+
+        return R.ok().put("data",new PageInfo<>(list));
     }
 
     /**
@@ -104,8 +128,8 @@ public class QwSopController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('qw:sop:export')")
     @Log(title = "企微sop", businessType = BusinessType.EXPORT)
-    @GetMapping("/export")
-    public AjaxResult export(QwSop qwSop)
+    @PostMapping("/export")
+    public AjaxResult export(@RequestBody QwSop qwSop)
     {
         List<QwSop> list = qwSopService.selectQwSopList(qwSop);
         ExcelUtil<QwSop> util = new ExcelUtil<QwSop>(QwSop.class);

+ 55 - 9
fs-admin/src/main/java/com/fs/qw/controller/QwSopTempController.java

@@ -12,6 +12,8 @@ import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.TimeUtils;
 import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.service.impl.CompanyUserServiceImpl;
+import com.fs.company.vo.DocCompanyUserVO;
 import com.fs.framework.web.service.TokenService;
 import com.fs.qw.vo.SortDayVo;
 import com.fs.sop.domain.QwSopTemp;
@@ -19,14 +21,16 @@ import com.fs.sop.domain.QwSopTempDay;
 import com.fs.sop.params.QwSopShareTempParam;
 import com.fs.sop.service.IQwSopTempService;
 import com.fs.sop.vo.UpdateRedVo;
+import com.fs.voice.utils.StringUtil;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
 import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
+import java.util.*;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
 /**
@@ -43,17 +47,56 @@ public class QwSopTempController extends BaseController
     private IQwSopTempService qwSopTempService;
     @Autowired
     private TokenService tokenService;
+    @Autowired
+    private CompanyUserServiceImpl companyUserService;
     /**
      * 查询sop模板列表
      */
     @PreAuthorize("@ss.hasPermi('qw:sopTemp:list')")
-    @GetMapping("/list")
-    public TableDataInfo list(QwSopTemp qwSopTemp)
+    @PostMapping("/list")
+    public R list(@RequestBody QwSopTemp qwSopTemp)
     {
-        startPage();
+
 //        List<QwSopTemp> list = qwSopTempService.selectQwSopTempList(qwSopTemp);
         List<QwSopTemp> list = qwSopTempService.selectQwSopTempListNew(qwSopTemp);
-        return getDataTable(list);
+        // 收集所有需要查询的用户ID
+        Set<Long> userIds = list.stream()
+                .map(QwSopTemp::getCreateBy)
+                .filter(str -> !StringUtil.strIsNullOrEmpty(str)) // 取反,保留非空值
+                .map(Long::valueOf)
+                .collect(Collectors.toSet());
+
+        if (!userIds.isEmpty()){
+            // 批量查询用户信息
+            Map<Long, DocCompanyUserVO> userMap = companyUserService
+                    .selectDocCompanyUserListByUserIds(userIds)
+                    .stream()
+                    .collect(Collectors.toMap(DocCompanyUserVO::getUserId, Function.identity()));
+
+
+            list.forEach(item->{
+
+                if (!StringUtil.strIsNullOrEmpty(item.getCreateBy())) {
+                    DocCompanyUserVO user = userMap.get(Long.valueOf(item.getCreateBy()));
+                    if (user != null) {
+                        item.setCreateByName(user.getNickName());
+                        item.setCreateByDeptName(user.getDeptName());
+                    }
+                }
+
+            });
+        }
+
+        if(qwSopTemp.getPageNum() == null) {
+            qwSopTemp.setPageNum(1);
+        }
+        if(qwSopTemp.getPageSize() == null) {
+            qwSopTemp.setPageSize(10);
+        }
+
+        PageHelper.startPage(qwSopTemp.getPageNum(), qwSopTemp.getPageSize());
+
+        return R.ok().put("data",new PageInfo<>(list));
     }
 
     /**
@@ -61,8 +104,8 @@ public class QwSopTempController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('qw:sopTemp:export')")
     @Log(title = "sop模板", businessType = BusinessType.EXPORT)
-    @GetMapping("/export")
-    public AjaxResult export(QwSopTemp qwSopTemp)
+    @PostMapping("/export")
+    public AjaxResult export(@RequestBody QwSopTemp qwSopTemp)
     {
         List<QwSopTemp> list = qwSopTempService.selectQwSopTempList(qwSopTemp);
         ExcelUtil<QwSopTemp> util = new ExcelUtil<QwSopTemp>(QwSopTemp.class);
@@ -205,6 +248,9 @@ public class QwSopTempController extends BaseController
     @PreAuthorize("@ss.hasPermi('qw:sopTemp:edit')")
     @PostMapping("/sortDay")
     public AjaxResult sortDay(@RequestBody List<SortDayVo> list){
+        if (list == null || list.isEmpty()){
+            return AjaxResult.error("请先调整排序");
+        }
         qwSopTempService.sortDay(list);
         return toAjax(1);
     }

+ 7 - 0
fs-admin/src/main/java/com/fs/qw/controller/QwUserController.java

@@ -354,4 +354,11 @@ public class QwUserController extends BaseController {
         List<QwUserVO> list = qwUserService.selectAllQwUserListVO(qwUser);
         return getDataTable(list);
     }
+
+    @GetMapping("/getQwAllUserList")
+    public R getQwAllUserList(@RequestParam String corpId, @RequestParam Long companyId)
+    {
+        List<QwUserVO> list = companyUserService.selectCompanyQwUserList(corpId, companyId);
+        return  R.ok().put("data",list);
+    }
 }

+ 107 - 24
fs-admin/src/main/java/com/fs/qw/qwTask/qwTask.java

@@ -5,6 +5,7 @@ import com.fs.his.service.IFsUserService;
 import com.fs.qw.domain.QwIpadServerLog;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.mapper.QwUserMapper;
+import com.fs.qw.param.QwMandatoryRegistrParam;
 import com.fs.qw.service.*;
 import com.fs.sop.service.impl.QwSopLogsServiceImpl;
 import com.fs.sop.service.impl.QwSopServiceImpl;
@@ -16,10 +17,14 @@ import com.fs.wxwork.service.WxWorkService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
+import java.time.Duration;
 import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
 import java.util.Date;
 import java.util.List;
+import java.util.Optional;
 
 @Component("qwTask")
 public class qwTask {
@@ -77,6 +82,10 @@ public class qwTask {
     private IFsUserService fsUserService;
 
 
+    @Autowired
+    private IQwCompanyService iQwCompanyService;
+
+
     //正在使用
     public void qwExternalContact()
     {
@@ -244,37 +253,41 @@ public class qwTask {
      * 定时清除 占着茅坑不拉屎的ipad 账号
      */
     public void selectQwUserByUnbindIpad(){
+
+        //查询所有状态为 绑定了AI主机的
         List<QwUser> list = qwUserMapper.selectQwUserByTest();
         for (QwUser qwUser : list) {
             try {
-                Integer serverStatus = qwUser.getServerStatus();
+
                 Long serverId = qwUser.getServerId();
-                if (serverStatus==0){
-                    System.out.println("不需要解绑");
-                }
+
                 if (serverId==null){
                     System.out.println("serverId不存在");
+                }else {
+                    //没绑定销售 或者 已经离职
+                    if (qwUser.getStatus()==0 || qwUser.getIsDel()==2){
+
+                        updateIpadStatus(qwUser,serverId);
+                    }
+
+                    //绑定了销售-也绑定了ipad,但是长时间离线的(离线状态,无操作超过2天的,也自动解绑)
+                    if(qwUser.getCreateTime()!=null){
+                        Date createTime = qwUser.getCreateTime();
+                        Integer serverStatus = qwUser.getServerStatus();
+                        Integer ipadStatus = qwUser.getIpadStatus();
+
+                        boolean result = isCreateTimeMoreThanDaysWithOptional(createTime, 2);
+                        //大于2天 ,绑定了ipad,离线
+                        if(result && serverStatus==1 && ipadStatus==0){
+                            updateIpadStatus(qwUser,serverId);
+
+                        }
+                    }
+
+
                 }
-                QwUser u = new QwUser();
-                u.setId(qwUser.getId());
-                u.setServerId(null);
-                u.setServerStatus(0);
-                qwUserMapper.updateQwUser(u);
-                ipadServerService.addServer(serverId);
-                QwIpadServerLog qwIpadServerLog = new QwIpadServerLog();
-                qwIpadServerLog.setType(2);
-                qwIpadServerLog.setTilie("解绑");
-                qwIpadServerLog.setServerId(serverId);
-                qwIpadServerLog.setQwUserId(qwUser.getId());
-                qwIpadServerLog.setCompanyUserId(qwUser.getCompanyUserId());
-                qwIpadServerLog.setCompanyId(qwUser.getCompanyId());
-                qwIpadServerLog.setCreateTime(new Date());
-                qwIpadServerLogService.insertQwIpadServerLog(qwIpadServerLog);
-                qwIpadServerUserService.deleteQwIpadServerUserByQwUserId(qwUser.getId());
-                WxWorkGetQrCodeDTO wxWorkGetQrCodeDTO = new WxWorkGetQrCodeDTO();
-                wxWorkGetQrCodeDTO.setUuid(qwUser.getUid());
-                wxWorkService.LoginOut(wxWorkGetQrCodeDTO,qwUser.getServerId());
-                updateIpadStatus(qwUser.getId(),0);
+
+
             } catch (Exception e) {
                 System.out.println("解绑ipad报错"+e);
 
@@ -282,6 +295,41 @@ public class qwTask {
         }
     }
 
+    public void updateIpadStatus(QwUser qwUser,Long serverId){
+        QwUser u = new QwUser();
+        u.setId(qwUser.getId());
+        u.setServerId(null);
+        u.setServerStatus(0);
+        qwUserMapper.updateQwUser(u);
+        ipadServerService.addServer(serverId);
+        QwIpadServerLog qwIpadServerLog = new QwIpadServerLog();
+        qwIpadServerLog.setType(2);
+        qwIpadServerLog.setTilie("解绑");
+        qwIpadServerLog.setServerId(serverId);
+        qwIpadServerLog.setQwUserId(qwUser.getId());
+        qwIpadServerLog.setCompanyUserId(qwUser.getCompanyUserId());
+        qwIpadServerLog.setCompanyId(qwUser.getCompanyId());
+        qwIpadServerLog.setCreateTime(new Date());
+        qwIpadServerLogService.insertQwIpadServerLog(qwIpadServerLog);
+        qwIpadServerUserService.deleteQwIpadServerUserByQwUserId(qwUser.getId());
+        WxWorkGetQrCodeDTO wxWorkGetQrCodeDTO = new WxWorkGetQrCodeDTO();
+        wxWorkGetQrCodeDTO.setUuid(qwUser.getUid());
+        wxWorkService.LoginOut(wxWorkGetQrCodeDTO,qwUser.getServerId());
+        updateIpadStatus(qwUser.getId(),0);
+    }
+
+    public static boolean isCreateTimeMoreThanDaysWithOptional(Date createTime, int days) {
+        return Optional.ofNullable(createTime)
+                .map(time -> {
+                    LocalDateTime createDateTime = time.toInstant()
+                            .atZone(ZoneId.systemDefault())
+                            .toLocalDateTime();
+                    LocalDateTime now = LocalDateTime.now();
+                    Duration duration = Duration.between(createDateTime, now);
+                    return duration.toDays() > days;
+                })
+                .orElse(false); // 为null时返回false,可根据需求调整
+    }
 
     void updateIpadStatus(Long id ,Integer status){
         QwUser u = new QwUser();
@@ -299,4 +347,39 @@ public class qwTask {
         fsUserService.autoUnbanMistakenlyDisabledUsers();
     }
 
+
+    /**
+     * 强制注册-2025-11-12 之后的 更新
+     */
+    public void QwExternalContactMandatoryRegistration(){
+        try {
+//            List<String> longs = qwExternalContactService.selectQwExternalContactMandatoryRegistration();
+            List<String> longs = iQwCompanyService.selectQwCompanyListFormCorpId();
+            longs.forEach(item->{
+                List<QwMandatoryRegistrParam> registrParamList = qwExternalContactService.selectQwExternalContactMandatoryRegistrationByIds(String.valueOf(item));
+
+                batchUpdateQwExternalContactMandatoryRegistration(registrParamList);
+
+            });
+        }catch (Exception e){
+
+        }
+
+    }
+
+
+    private void batchUpdateQwExternalContactMandatoryRegistration(List<QwMandatoryRegistrParam> registrParamList) {
+        // 定义批量插入的大小
+        int batchSize = 300;
+
+        // 循环处理外部用户 ID,每次处理批量大小的子集
+        for (int i = 0; i < registrParamList.size(); i += batchSize) {
+
+            int endIndex = Math.min(i + batchSize, registrParamList.size());
+            List<QwMandatoryRegistrParam> batchList = registrParamList.subList(i, endIndex);  // 获取当前批次的子集
+
+            qwExternalContactService.batchUpdateQwExternalContactMandatoryRegistration(batchList);
+
+        }
+    }
 }

+ 111 - 0
fs-admin/src/main/java/com/fs/task/MiniProgramSubTask.java

@@ -0,0 +1,111 @@
+package com.fs.task;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.TypeReference;
+import com.fs.live.domain.LiveMiniprogramSubNotifyTask;
+import com.fs.live.mapper.LiveMiniprogramSubNotifyTaskMapper;
+import com.fs.store.enums.MiniAppNotifyTaskStatusEnum;
+import com.fs.store.service.IWechatMiniProgrService;
+import com.fs.store.dto.ClientCredGrantReqDTO;
+import com.fs.store.dto.MiniGramSubsMsgResultDTO;
+import com.fs.store.dto.TemplateMessageSendRequestDTO;
+import com.fs.store.dto.WeXinAccessTokenDTO;
+import com.fs.wx.miniapp.config.WxMaProperties;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang.exception.ExceptionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 小程序订阅通知定时任务
+ */
+@Service("miniProgramSubTask")
+@Slf4j
+@RequiredArgsConstructor
+public class MiniProgramSubTask {
+    private final IWechatMiniProgrService wechatMiniProgrService;
+
+    private final LiveMiniprogramSubNotifyTaskMapper notifyTaskMapper;
+
+    private WxMaProperties.Config config = null;
+
+    @Autowired
+    public void setConfig(WxMaProperties properties) {
+        if(ObjectUtil.isNotNull(properties)){
+            this.config = properties.getConfigs().get(0);
+        }
+    }
+
+
+    /**
+     * 小程序订阅通知
+     */
+    public void notifyMiniLiveAppSub(){
+        log.info("小程序直播订阅通知定时任务");
+        // 先获取所有可用待处理任务
+        List<LiveMiniprogramSubNotifyTask> pendingData = notifyTaskMapper.selectLivePendingData();
+        if(CollectionUtils.isEmpty(pendingData)){
+            log.info("小程序直播阅通知定时任务, 无待处理数据");
+            return;
+        }
+        LocalDateTime now = LocalDateTime.now();
+        for (LiveMiniprogramSubNotifyTask pendingDatum : pendingData) {
+
+            if(pendingDatum.getUpdateTime().isAfter(now)) continue;
+
+            pendingDatum.setUpdateTime(LocalDateTime.now());
+
+            ClientCredGrantReqDTO clientCredGrantReqDTO = new ClientCredGrantReqDTO();
+            clientCredGrantReqDTO.setAppid(config.getAppid());
+            clientCredGrantReqDTO.setSecret(config.getSecret());
+            clientCredGrantReqDTO.setGrant_type("client_credential");
+
+            try{
+                // 获取accessToken
+                WeXinAccessTokenDTO stableToken = wechatMiniProgrService
+                        .getStableToken(clientCredGrantReqDTO);
+
+                String accessToken = stableToken.getAccessToken();
+
+                // 调用微信小程序订阅通知
+                TemplateMessageSendRequestDTO sendRequestDTO = new TemplateMessageSendRequestDTO();
+                sendRequestDTO.setTemplate_id(pendingDatum.getTemplateId());
+                sendRequestDTO.setTouser(pendingDatum.getTouser());
+                sendRequestDTO.setPage(pendingDatum.getPage());
+                TypeReference<Map<String, TemplateMessageSendRequestDTO.TemplateDataValue>> typeReference = new TypeReference<Map<String, TemplateMessageSendRequestDTO.TemplateDataValue>>() {};
+                sendRequestDTO.setData(JSON.parseObject(pendingDatum.getData(),typeReference));
+                MiniGramSubsMsgResultDTO miniGramSubsMsgResultDTO = wechatMiniProgrService.sendSubscribeMsg(accessToken, sendRequestDTO);
+                pendingDatum.setRequestBody(JSON.toJSONString(sendRequestDTO));
+                pendingDatum.setResponseBody(JSON.toJSONString(miniGramSubsMsgResultDTO));
+
+                // 如果推送消息成功
+                if(miniGramSubsMsgResultDTO.getErrcode() == 0){
+                    pendingDatum.setStatus(MiniAppNotifyTaskStatusEnum.SUCCESS.getValue());
+                } else {
+                    // 更新任务状态为执行失败
+                    pendingDatum.setStatus(MiniAppNotifyTaskStatusEnum.FAILED.getValue());
+                    pendingDatum.setErrorMessage(JSON.toJSONString(miniGramSubsMsgResultDTO));
+                    pendingDatum.setRetryCount(pendingDatum.getRetryCount() +1);
+                }
+            }catch (Throwable e){
+                // 更新任务状态为执行失败
+                pendingDatum.setStatus(MiniAppNotifyTaskStatusEnum.FAILED.getValue());
+                pendingDatum.setErrorMessage(ExceptionUtils.getStackTrace(e));
+                pendingDatum.setRetryCount(pendingDatum.getRetryCount() +1);
+                log.error("小程序直播订阅通知定时任务异常: {}", ExceptionUtils.getStackTrace(e));
+            }
+        }
+
+        if(CollectionUtils.isNotEmpty(pendingData)){
+            notifyTaskMapper.updateBatchById(pendingData);
+        }
+
+    }
+}

+ 8 - 1
fs-admin/src/main/java/com/fs/web/controller/common/CommonController.java

@@ -12,12 +12,12 @@ import com.fs.framework.config.ServerConfig;
 import com.fs.his.domain.FsExportTask;
 import com.fs.his.service.IFsExportTaskService;
 import com.fs.im.service.OpenIMService;
+import com.fs.qw.service.IQwUserService;
 import com.fs.system.oss.CloudStorageService;
 import com.fs.system.oss.OSSFactory;
 
 import com.fs.web.vo.WangUploadVO;
 
-import com.huaweicloud.sdk.vod.v1.model.BaseInfo;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -72,6 +72,9 @@ public class CommonController
     public RedisTemplate redisTemplate;
 
     org.slf4j.Logger logger= LoggerFactory.getLogger(getClass());
+    @Autowired
+    private IQwUserService qwUserService;
+
     @GetMapping(value = "common/getTask/{taskId}")
     public R getTask(@PathVariable("taskId") Long taskId)
     {
@@ -305,4 +308,8 @@ public class CommonController
 
     }
 
+    @PostMapping("/common/unbindQwUserByServerIds")
+    public R unbindQwUserByServerIds(@RequestBody List<String> serverIds){
+        return qwUserService.unbindQwUserByServerIds(serverIds);
+    }
 }

+ 129 - 7
fs-admin/src/main/java/com/fs/web/controller/system/SysLoginController.java

@@ -1,22 +1,28 @@
 package com.fs.web.controller.system;
 
-import java.util.List;
-import java.util.Set;
+import java.util.*;
+import java.util.stream.Collectors;
 
 import com.alibaba.fastjson.JSONObject;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.entity.SysRole;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.common.exception.ServiceException;
 import com.fs.common.utils.PatternUtils;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.ip.IpUtils;
+import com.fs.framework.web.service.TokenService;
 import com.fs.his.utils.ConfigUtil;
 import com.fs.hisStore.config.MedicalMallConfig;
 import com.fs.system.service.ISysRoleService;
+import com.fs.system.service.ISysUserService;
 import lombok.Synchronized;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Async;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.web.bind.annotation.*;
 import com.fs.common.constant.Constants;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.entity.SysMenu;
@@ -33,7 +39,7 @@ import com.fs.system.service.ISysMenuService;
 
  */
 @RestController
-
+@Slf4j
 public class SysLoginController
 {
     @Autowired
@@ -54,6 +60,18 @@ public class SysLoginController
     @Autowired
     private MedicalMallConfig medicalMallConfig;
 
+    @Autowired
+    RedisCache redisCache;
+
+    @Autowired
+    private ISysUserService userService;
+
+    @Autowired
+    private TokenService tokenService;
+
+    @Autowired
+    private UserDetailsService userDetailsService;
+
     /**
      * 登录方法
      *
@@ -141,4 +159,108 @@ public class SysLoginController
         List<SysMenu> menus = menuService.selectMenuTreeByUserId(userId);
         return AjaxResult.success(menuService.buildMenus(menus));
     }
+
+    @PostMapping("/checkIsNeedCheck")
+    public boolean checkIsNeedCheck(@RequestBody LoginBody loginBody)
+    {
+//        return  false;
+        return loginService.checkIsNeedCheck(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(), loginBody.getUuid());
+    }
+
+    @PostMapping("/checkCode")
+    public AjaxResult checkCode(@RequestBody Map<String,String> map)
+    {
+        String phone = map.get("phone");
+        String code = map.get("code");
+        String smsKey = "doctorLogin:sms:" + map.get("phone");
+        String smsCode = redisCache.getCacheObject(smsKey);
+
+        if (smsCode == null) {
+            throw new ServiceException("验证码已过期,请重新发送");
+        } else {
+            String string = redisCache.getCacheObject("doctorLogin:sms:" + phone).toString();
+            if (!string.equals(code)){
+                throw new ServiceException("验证码错误");
+            }else{
+                redisCache.deleteObject("doctorLogin:sms:" + phone);
+                List<SysUser> sysUsers = userService.selectUserByPhone(phone);
+                if(sysUsers.size()>1){
+                    throw new ServiceException("此电话号码绑定了多个医生,请核实");
+                }
+                SysUser sysUser = sysUsers.get(0);
+                String ipAddr = IpUtils.getIpAddr(ServletUtils.getRequest());
+                String username = sysUser.getUserName();
+
+                // 调 UserDetailsServiceImpl.loadUserByUsername 获取完整 LoginUser
+                sysUser.setLoginIp(ipAddr);
+                sysUser.setLoginDate(new Date());
+                userService.updateUserProfile(sysUser);
+                LoginUser loginUser = (LoginUser) userDetailsService.loadUserByUsername(username);
+                String token = tokenService.createToken(loginUser);
+                AjaxResult ajax = AjaxResult.success();
+                ajax.put(Constants.TOKEN, token);
+                return ajax;
+            }
+        }
+    }
+
+    @GetMapping("/checkWechatScan")
+    public AjaxResult checkWechatScan(@RequestParam String ticket)
+    {
+        String status = redisCache.getCacheObject("wechat:scan:" + ticket);
+        if ("ok".equals(status)) {
+            String username = redisCache.getCacheObject("login:ticket:" + ticket);
+            redisCache.deleteObject("login:ticket:" + ticket);
+            redisCache.deleteObject("wechat:scan:" + ticket);
+            SysUser sysUser = userService.selectUserByUserName(username);
+
+            String ipAddr = IpUtils.getIpAddr(ServletUtils.getRequest());
+            String loginIp = sysUser.getLoginIp();
+            if (com.fs.common.utils.StringUtils.isEmpty(loginIp)) {
+                sysUser.setLoginIp(ipAddr.trim());
+            } else {
+                List<String> ipList = Arrays.stream(loginIp.split(","))
+                        .map(String::trim)       // 去掉前后空格
+                        .filter(s -> !s.isEmpty())
+                        .distinct()              // 去重
+                        .collect(Collectors.toList());
+
+                String newIp = ipAddr.trim();
+                if (!ipList.contains(newIp)) {
+                    ipList.add(newIp);
+                }
+
+                sysUser.setLoginIp(String.join(",", ipList));
+            }
+
+            sysUser.setLoginDate(new Date());
+            userService.updateUserProfile(sysUser);
+            LoginUser loginUser = (LoginUser) userDetailsService.loadUserByUsername(username);
+            String token = tokenService.createToken(loginUser);
+            if (token != null){
+                return AjaxResult.success(Constants.TOKEN, token);
+            }
+            return AjaxResult.success("waiting");
+        }else if (com.fs.common.utils.StringUtils.isNotEmpty(status)&&status.startsWith("error:")) {
+            // 把错误返回给前端
+            throw new ServiceException(status);
+        }
+        return null;
+    }
+
+    @PostMapping("/getWechatQrCode")
+    public AjaxResult getWechatQrCode(@RequestBody Map<String,String> params) throws Exception {
+        Map<String,String> qr = loginService.getWechatQrCode(params.get("username"));
+        return AjaxResult.success(qr);
+    }
+    @GetMapping("/callback")
+    public String wechatCallback(@RequestParam String code, @RequestParam String state) {
+        try {
+            log.info("触发回调");
+            loginService.handleCallback(code, state);
+            return "success"; // 微信要求返回内容,显示给用户即可
+        } catch (Exception e) {
+            return "error";
+        }
+    }
 }

+ 1 - 1
fs-admin/src/main/resources/application.yml

@@ -7,7 +7,7 @@ spring:
 #    active: dev
 #    active: druid-hdt
 #    active: druid-yzt
-    active: dev
+    active: druid-bjczwh-test
 #    active: druid-sft
 #    active: druid-fby
 #    active: dev

+ 3 - 3
fs-admin/src/main/resources/logback.xml

@@ -20,7 +20,7 @@
             <!-- 日志文件名格式 -->
 			<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
 			<!-- 日志最大的历史 30 -->
-			<maxHistory>30</maxHistory>
+			<maxHistory>7</maxHistory>
 		</rollingPolicy>
 		<encoder>
 			<pattern>${log.pattern}</pattern>
@@ -42,7 +42,7 @@
             <!-- 日志文件名格式 -->
             <fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
 			<!-- 日志最大的历史 30 -->
-			<maxHistory>30</maxHistory>
+			<maxHistory>7</maxHistory>
         </rollingPolicy>
         <encoder>
             <pattern>${log.pattern}</pattern>
@@ -64,7 +64,7 @@
             <!-- 按天回滚 daily -->
             <fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>
             <!-- 日志最大的历史 30 -->
-            <maxHistory>30</maxHistory>
+            <maxHistory>7</maxHistory>
         </rollingPolicy>
         <encoder>
             <pattern>${log.pattern}</pattern>

+ 6 - 0
fs-common-api/Dockerfile

@@ -0,0 +1,6 @@
+FROM openjdk:8-jre
+# java版本,最好使用openjdk,而不是类似于Java:1.8
+COPY ./target/fs-common-api.jar fs-common-api.jar
+# 向外暴露的接口,最好与项目yml文件中的端口一致
+ENTRYPOINT ["java","-jar","fs-common-api.jar"]
+# 执行启动命令java -jar

+ 24 - 0
fs-common-api/pom.xml

@@ -118,6 +118,30 @@
                     <warName>${project.artifactId}</warName>
                 </configuration>
             </plugin>
+<!--            <plugin>-->
+<!--                <groupId>com.spotify</groupId>-->
+<!--                <artifactId>dockerfile-maven-plugin</artifactId>-->
+<!--                <version>1.4.13</version>-->
+<!--                <executions>-->
+<!--                    <execution>-->
+<!--                        <id>harbor</id>-->
+<!--                        <goals>-->
+<!--                            <goal>build</goal>-->
+<!--                            <goal>push</goal>-->
+<!--                        </goals>-->
+<!--                    </execution>-->
+<!--                </executions>-->
+<!--                <configuration>-->
+<!--                    <repository>ylrz-docker.tencentcloudcr.com/ylrz/sub1/repo</repository>-->
+<!--                    <tag>${project.version}</tag>-->
+<!--                    <useMavenSettingsForAuth>true</useMavenSettingsForAuth>-->
+<!--                    <username>100044034444</username>-->
+<!--                    <password>eyJhbGciOiJSUzI1NiIsImtpZCI6Ilg0R1k6SDU1TjpCQU9SOk5GVk86RkdSWDpYTjM1OjZGQUM6S1BBWjo0TE4yOkhDSUQ6UTJDRzpSN1NHIn0.eyJvd25lclVpbiI6IjEwMDAzNDY4ODY0OCIsIm9wZXJhdG9yVWluIjoiMTAwMDQ0MDM0NDQ0IiwidG9rZW5JZCI6ImQ0ZnZja2o4a2NnbnFjaTVvanQwIiwiZXhwIjoyMDc5MDYyMzU0LCJuYmYiOjE3NjM3MDIzNTQsImlhdCI6MTc2MzcwMjM1NH0.PhNxx6pBQ-ItrNlSs_gojvXeHghhYqDqxh8nLUIuhBeRzAgmVnY8F3bFPVgbHGydQNxvgyqLYv3nRIE1j020LGgzUetF5b-NBqSWYMiXfu6uZNWctRkwm5hdlWBrMlV8k8zGxY4ZDGUNEG0ksrk7kk3UZ-lHj4ButI2GIEhTx0lQEPHjhEY0xuteocJVYMHdVUqF-Bc5Jr0nvbwxUbmCGakN1VszxBoMpI-zA2O8anMvYq8h7EqOJLU4dlBVcsbkz-4sMi97Xev-mcGh7THbEGWoRWGWNSa4QwsdXEXS5-mhfrvOw6FGuuiIeEQvcuR8zDztDzSApl4ko57Yat-AQQ</password>-->
+<!--                    <buildArgs>-->
+<!--                        <JAR_FILE>${project.artifactId}.jar</JAR_FILE>-->
+<!--                    </buildArgs>-->
+<!--                </configuration>-->
+<!--            </plugin>-->
         </plugins>
         <finalName>${project.artifactId}</finalName>
     </build>

+ 1 - 1
fs-common-api/src/main/resources/application.yml

@@ -5,4 +5,4 @@ server:
 spring:
   profiles:
 #    active: dev
-    active: druid-myhk-test
+    active: druid-ylrz

+ 3 - 3
fs-common-api/src/main/resources/logback.xml

@@ -20,7 +20,7 @@
             <!-- 日志文件名格式 -->
 			<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
 			<!-- 日志最大的历史 30 -->
-			<maxHistory>30</maxHistory>
+			<maxHistory>7</maxHistory>
 		</rollingPolicy>
 		<encoder>
 			<pattern>${log.pattern}</pattern>
@@ -42,7 +42,7 @@
             <!-- 日志文件名格式 -->
             <fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
 			<!-- 日志最大的历史 30 -->
-			<maxHistory>30</maxHistory>
+			<maxHistory>7</maxHistory>
         </rollingPolicy>
         <encoder>
             <pattern>${log.pattern}</pattern>
@@ -64,7 +64,7 @@
             <!-- 按天回滚 daily -->
             <fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>
             <!-- 日志最大的历史 30 -->
-            <maxHistory>30</maxHistory>
+            <maxHistory>7</maxHistory>
         </rollingPolicy>
         <encoder>
             <pattern>${log.pattern}</pattern>

+ 60 - 0
fs-common/src/main/java/com/fs/common/annotation/DistributeLock.java

@@ -0,0 +1,60 @@
+package com.fs.common.annotation;
+
+import com.fs.common.constant.DistributeLockConstant;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 分布式锁注解
+ *
+ * @author Hollis
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DistributeLock {
+
+    /**
+     * 锁的场景
+     *
+     * @return
+     */
+    public String scene();
+
+    /**
+     * 加锁的key,优先取key(),如果没有,则取keyExpression()
+     *
+     * @return
+     */
+    public String key() default DistributeLockConstant.NONE_KEY;
+
+    /**
+     * SPEL表达式:
+     * <pre>
+     *     #id
+     *     #insertResult.id
+     * </pre>
+     *
+     * @return
+     */
+    public String keyExpression() default DistributeLockConstant.NONE_KEY;
+
+    /**
+     * 超时时间,毫秒
+     * 默认情况下不设置超时时间,会自动续期
+     *
+     * @return
+     */
+    public int expireTime() default DistributeLockConstant.DEFAULT_EXPIRE_TIME;
+
+    public String errorMsg() default DistributeLockConstant.ERROR_MSG;
+
+    /**
+     * 加锁等待时长,毫秒
+     * 默认情况下不设置等待时长,会一直等待直到获取到锁
+     * @return
+     */
+    public int waitTime() default DistributeLockConstant.DEFAULT_WAIT_TIME;
+}

+ 13 - 0
fs-common/src/main/java/com/fs/common/constant/DistributeLockConstant.java

@@ -0,0 +1,13 @@
+package com.fs.common.constant;
+
+public class DistributeLockConstant {
+
+    public static final String NONE_KEY = "NONE";
+
+    public static final String DEFAULT_OWNER = "DEFAULT";
+
+    public static final int DEFAULT_EXPIRE_TIME = -1;
+
+    public static final int DEFAULT_WAIT_TIME = Integer.MAX_VALUE;
+    public static final String ERROR_MSG  = "请勿重复操作";
+}

+ 9 - 1
fs-common/src/main/java/com/fs/common/constant/LiveKeysConstant.java

@@ -28,7 +28,15 @@ public class LiveKeysConstant {
     public static final String TOP_MSG = "topMsg"; //抽奖记录
 
     public static final String LIVE_FLAG_CACHE = "live:flag:%s"; //直播间直播/回放状态缓存
-    public static final Integer LIVE_FLAG_CACHE_EXPIRE = 300; //直播间状态缓存过期时间(秒)
+    public static final Integer LIVE_FLAG_CACHE_EXPIRE = 60; //直播间状态缓存过期时间(秒)
+
+    public static final String LIVE_DATA_CACHE = "live:data:%s"; //直播间数据缓存
+    public static final Integer LIVE_DATA_CACHE_EXPIRE = 300; //直播间数据缓存过期时间(秒)
+
+    public static final String PRODUCT_DETAIL_CACHE = "product:detail:%s"; //商品详情缓存
+    public static final Integer PRODUCT_DETAIL_CACHE_EXPIRE = 300; //商品详情缓存过期时间(秒)
+
+    public static final String LIVE_TAG_MARK_CACHE = "live:tag:mark:%s"; //直播间打标签缓存,存储直播间ID、开始时间和视频时长
 
 
 }

+ 71 - 0
fs-common/src/main/java/com/fs/common/constant/RedisKeyConstant.java

@@ -0,0 +1,71 @@
+package com.fs.common.constant;
+
+/**
+ * Redis Key常量
+ *
+ * @author zhangqin
+ * @date 2025-11-03
+ */
+public class RedisKeyConstant {
+
+    /**
+     * 分布式锁前缀
+     */
+    public static final String LOCK_PREFIX = "crm:adv:lock:";
+
+    /**
+     * 站点统计缓存前缀
+     */
+    public static final String SITE_STATS_PREFIX = "crm:adv:site:stats:";
+
+    /**
+     * 展示数计数器前缀
+     */
+    public static final String IMPRESSION_COUNT_PREFIX = "crm:adv:impression:";
+
+    /**
+     * 点击数计数器前缀
+     */
+    public static final String CLICK_COUNT_PREFIX = "crm:adv:click:";
+
+    /**
+     * 转化数计数器前缀
+     */
+    public static final String CONVERSION_COUNT_PREFIX = "crm:adv:conversion:";
+
+    /**
+     * 任务执行锁前缀
+     */
+    public static final String TASK_LOCK_PREFIX = "crm:adv:task:lock:";
+
+    /**
+     * 广告商配置缓存前缀
+     */
+    public static final String ADVERTISER_CONFIG_PREFIX = "crm:adv:advertiser:config:";
+
+    /**
+     * 站点配置缓存前缀
+     */
+    public static final String SITE_CONFIG_PREFIX = "crm:adv:site:config:";
+
+    /**
+     * 百度AccessToken缓存前缀
+     */
+    public static final String BAIDU_ACCESS_TOKEN_PREFIX = "crm:adv:baidu:token:";
+
+    /**
+     * 巨量引擎AccessToken缓存前缀
+     */
+    public static final String OCEANENGINE_ACCESS_TOKEN_PREFIX = "crm:adv:oceanengine:token:";
+
+    /**
+     * 缓存过期时间(秒)
+     */
+    public static final long CACHE_EXPIRE_TIME = 3600;
+
+    /**
+     * 锁过期时间(秒)
+     */
+    public static final long LOCK_EXPIRE_TIME = 300;
+}
+

+ 57 - 0
fs-common/src/main/java/com/fs/common/constant/RocketMqConstant.java

@@ -0,0 +1,57 @@
+package com.fs.common.constant;
+
+/**
+ * RocketMQ常量
+ *
+ * @author zhangqin
+ * @date 2025-11-05
+ */
+public class RocketMqConstant {
+
+    /**
+     * Topic: 转化回传
+     */
+    public static final String TOPIC_CONVERSION_CALLBACK = "CONVERSION_CALLBACK_TOPIC";
+
+    /**
+     * Topic: 事件反馈(表单提交等转化事件)
+     */
+    public static final String TOPIC_EVENT_FEEDBACK = "event-feedback";
+
+    /**
+     * Topic: 点击追踪
+     */
+    public static final String TOPIC_CLICK_TRACE = "CLICK_TRACE_TOPIC";
+
+    /**
+     * Topic: 数据同步
+     */
+    public static final String TOPIC_DATA_SYNC = "DATA_SYNC_TOPIC";
+
+    /**
+     * Consumer Group: 转化回传消费组
+     */
+    public static final String CONSUMER_GROUP_CONVERSION = "conversion-consumer-group";
+
+    /**
+     * Consumer Group: 事件反馈消费组
+     */
+    public static final String CONSUMER_GROUP_EVENT_FEEDBACK = "event-feedback-consumer-group";
+
+    /**
+     * Consumer Group: 点击追踪消费组
+     */
+    public static final String CONSUMER_GROUP_CLICK_TRACE = "click-trace-consumer-group";
+
+    /**
+     * Consumer Group: 数据同步消费组
+     */
+    public static final String CONSUMER_GROUP_DATA_SYNC = "data-sync-consumer-group";
+
+    /**
+     * Consumer Group: 死信队列消费组
+     */
+    public static final String CONSUMER_GROUP_DLQ = "dlq-consumer-group";
+}
+
+

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików