Parcourir la source

Merge remote-tracking branch 'origin/saas-api' into saas-api

yys il y a 1 mois
Parent
commit
c9b22cc563
100 fichiers modifiés avec 13415 ajouts et 0 suppressions
  1. 6 0
      fs-admin-agent/Dockerfile
  2. 256 0
      fs-admin-agent/pom.xml
  3. 40 0
      fs-admin-agent/src/main/java/com/fs/FSAdminSaasApplication.java
  4. 14 0
      fs-admin-agent/src/main/java/com/fs/FSServletInitializer.java
  5. 31 0
      fs-admin-agent/src/main/java/com/fs/ad/controller/AdAccountController.java
  6. 108 0
      fs-admin-agent/src/main/java/com/fs/ad/controller/AdDomainController.java
  7. 105 0
      fs-admin-agent/src/main/java/com/fs/ad/controller/AdDyAccountController.java
  8. 116 0
      fs-admin-agent/src/main/java/com/fs/ad/controller/AdDyApiController.java
  9. 99 0
      fs-admin-agent/src/main/java/com/fs/ad/controller/AdHtmlClickLogController.java
  10. 105 0
      fs-admin-agent/src/main/java/com/fs/ad/controller/AdHtmlTemplateController.java
  11. 108 0
      fs-admin-agent/src/main/java/com/fs/ad/controller/AdIqiyiAccountController.java
  12. 111 0
      fs-admin-agent/src/main/java/com/fs/ad/controller/AdSiteController.java
  13. 99 0
      fs-admin-agent/src/main/java/com/fs/ad/controller/AdUploadLogController.java
  14. 108 0
      fs-admin-agent/src/main/java/com/fs/ad/controller/AdYoukuAccountController.java
  15. 143 0
      fs-admin-agent/src/main/java/com/fs/ad/controller/BdAccountController.java
  16. 218 0
      fs-admin-agent/src/main/java/com/fs/ad/controller/MockAppController.java
  17. 86 0
      fs-admin-agent/src/main/java/com/fs/ad/controller/StatisticsController.java
  18. 26 0
      fs-admin-agent/src/main/java/com/fs/ad/controller/task/BaiduTask.java
  19. 97 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/AdminAdController.java
  20. 275 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/AdminAiWorkflowBridgeController.java
  21. 106 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/AdminCommissionRecordController.java
  22. 454 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/AdminCompanyBridgeController.java
  23. 184 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/AdminConsumeRecordController.java
  24. 106 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/AdminCourseBridgeController.java
  25. 94 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/AdminCrmController.java
  26. 136 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/AdminHisBridgeController.java
  27. 74 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/AdminLiveBridgeController.java
  28. 2 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/AdminLiveVideoController.java
  29. 183 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/AdminLobsterBridgeController.java
  30. 116 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/AdminMiscBridge2Controller.java
  31. 682 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/AdminMissingApisBridgeController.java
  32. 109 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/AdminModuleUsageController.java
  33. 105 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/AdminProxyController.java
  34. 182 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/AdminRechargeRecordController.java
  35. 72 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/AdminServiceCostController.java
  36. 116 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/AdminSopController.java
  37. 399 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/AdminStatisticsController.java
  38. 335 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/AdminStoreMiscController.java
  39. 2 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/AdminStoreOrderAdminController.java
  40. 184 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/AdminStoreOrderController.java
  41. 253 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/AdminStoreProductController.java
  42. 48 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/AdminVideoResourceController.java
  43. 98 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/AdminWithdrawalController.java
  44. 109 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/AiChatQualityController.java
  45. 104 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/AiProviderAdminController.java
  46. 2 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/ArticleAdminController.java
  47. 158 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/CallRecordAdminController.java
  48. 285 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/CompanyAdminController.java
  49. 148 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/CompanyUserAdminController.java
  50. 124 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/CourseAdminController.java
  51. 61 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/DbConfigController.java
  52. 2 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/LiveAdminController.java
  53. 59 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/LobsterAdminController.java
  54. 2 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/ProductAdminController.java
  55. 63 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/ProxyOperLogController.java
  56. 102 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/QwExternalContactAdminController.java
  57. 82 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsAdvScrmBridgeController.java
  58. 82 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsMenuScrmBridgeController.java
  59. 82 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsPrescribeDrugScrmBridgeController.java
  60. 82 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsPrescribeScrmBridgeController.java
  61. 82 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsShippingTemplatesFreeScrmBridgeController.java
  62. 82 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsShippingTemplatesRegionScrmBridgeController.java
  63. 93 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsShippingTemplatesScrmBridgeController.java
  64. 93 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreActivityScrmBridgeController.java
  65. 82 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreAfterSalesItemScrmBridgeController.java
  66. 233 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreAfterSalesScrmBridgeController.java
  67. 82 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreAfterSalesStatusScrmBridgeController.java
  68. 82 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreCartScrmBridgeController.java
  69. 83 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreCouponIssueScrmBridgeController.java
  70. 82 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreCouponIssueUserScrmBridgeController.java
  71. 131 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreCouponScrmBridgeController.java
  72. 82 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreCouponUserScrmBridgeController.java
  73. 96 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreOrderAuditScrmBridgeController.java
  74. 90 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreOrderItemScrmBridgeController.java
  75. 82 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreOrderNoticeScrmBridgeController.java
  76. 134 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreOrderOfflineScrmBridgeController.java
  77. 141 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreOrderPromotionScrmBridgeController.java
  78. 1062 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreOrderScrmBridgeController.java
  79. 82 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreOrderStatusScrmBridgeController.java
  80. 82 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreProductAttrScrmBridgeController.java
  81. 82 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreProductAttrValueScrmBridgeController.java
  82. 82 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreProductCategoryScrmBridgeController.java
  83. 82 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreProductDetailsScrmBridgeController.java
  84. 117 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreProductGroupScrmBridgeController.java
  85. 61 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreProductRelationScrmBridgeController.java
  86. 95 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreProductReplyScrmBridgeController.java
  87. 103 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreProductRuleScrmBridgeController.java
  88. 82 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreProductTemplateScrmBridgeController.java
  89. 90 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreRecommendScrmBridgeController.java
  90. 82 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreShopStaffScrmBridgeController.java
  91. 82 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreVisitScrmBridgeController.java
  92. 84 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsUserPromoterApplyScrmBridgeController.java
  93. 63 0
      fs-admin-agent/src/main/java/com/fs/admin/controller/store/SysOperlogScrmBridgeController.java
  94. 262 0
      fs-admin-agent/src/main/java/com/fs/admin/sync/LobsterBridgeDataSyncService.java
  95. 41 0
      fs-admin-agent/src/main/java/com/fs/admin/vo/ConsumeRecordExportVO.java
  96. 41 0
      fs-admin-agent/src/main/java/com/fs/admin/vo/RechargeRecordExportVO.java
  97. 183 0
      fs-admin-agent/src/main/java/com/fs/aiSoundReplication/VoiceCloneController.java
  98. 881 0
      fs-admin-agent/src/main/java/com/fs/api/controller/IndexStatisticsController.java
  99. 81 0
      fs-admin-agent/src/main/java/com/fs/api/controller/StatisticManageController.java
  100. 107 0
      fs-admin-agent/src/main/java/com/fs/bean/AliPayBean.java

+ 6 - 0
fs-admin-agent/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

+ 256 - 0
fs-admin-agent/pom.xml

@@ -0,0 +1,256 @@
+<?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>
+    <packaging>jar</packaging>
+    <artifactId>fs-admin-agent</artifactId>
+
+    <description>
+        web服务入口
+    </description>
+
+    <dependencies>
+        <!-- Spring 5.3 @Resource.lookup() 需要 javax.annotation-api 1.3+,勿用 tomcat 6.0.53 旧包 -->
+        <dependency>
+            <groupId>javax.annotation</groupId>
+            <artifactId>javax.annotation-api</artifactId>
+        </dependency>
+
+        <!-- 验证码 -->
+        <dependency>
+            <groupId>com.github.penggle</groupId>
+            <artifactId>kaptcha</artifactId>
+            <exclusions>
+                <exclusion>
+                    <artifactId>javax.servlet-api</artifactId>
+                    <groupId>javax.servlet</groupId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.tomcat</groupId>
+                    <artifactId>annotations-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.javen205</groupId>
+            <artifactId>IJPay-All</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.tomcat</groupId>
+                    <artifactId>annotations-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.alipay.sdk</groupId>
+            <artifactId>alipay-sdk-java</artifactId>
+            <version>4.8.62.ALL</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.tomcat</groupId>
+                    <artifactId>annotations-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <!-- spring-boot-devtools -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-devtools</artifactId>
+            <optional>true</optional> <!-- 表示依赖不会传递 -->
+        </dependency>
+         <!-- Mysql驱动包 -->
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+
+        <!-- 核心模块-->
+        <dependency>
+            <groupId>com.fs</groupId>
+            <artifactId>fs-framework</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>javax.annotation</groupId>
+                    <artifactId>annotations-api</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.tomcat</groupId>
+                    <artifactId>annotations-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+
+
+        <!-- 定时任务-->
+        <dependency>
+            <groupId>com.fs</groupId>
+            <artifactId>fs-quartz</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.tomcat</groupId>
+                    <artifactId>annotations-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <!-- lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.18.32</version>
+        </dependency>
+
+        <!-- 代码生成-->
+        <dependency>
+            <groupId>com.fs</groupId>
+            <artifactId>fs-generator</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.tomcat</groupId>
+                    <artifactId>annotations-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <!-- swagger2-->
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.clickhouse</groupId>
+            <artifactId>clickhouse-jdbc</artifactId>
+            <version>0.4.6</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.tomcat</groupId>
+                    <artifactId>annotations-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <!-- swagger2-UI-->
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger-ui</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>swagger-bootstrap-ui</artifactId>
+            <version>1.9.3</version>
+        </dependency>
+        <dependency>
+            <groupId>com.tencentcloudapi</groupId>
+            <artifactId>tencentcloud-sdk-java</artifactId>
+            <version>3.1.322</version>
+            <scope>compile</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.squareup.okio</groupId>
+                    <artifactId>okio</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>com.squareup.okhttp</groupId>
+                    <artifactId>okhttp</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.tomcat</groupId>
+                    <artifactId>annotations-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.squareup.okio</groupId>
+            <artifactId>okio</artifactId>
+            <version>2.10.0</version>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/com.qcloud/qcloud-java-sdk -->
+        <dependency>
+            <groupId>com.qcloud</groupId>
+            <artifactId>qcloud-java-sdk</artifactId>
+            <version>2.0.1</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.tomcat</groupId>
+                    <artifactId>annotations-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.11.0</version>
+                <configuration>
+                    <source>${java.version}</source>
+                    <target>${java.version}</target>
+                    <encoding>${project.build.sourceEncoding}</encoding>
+                    <annotationProcessorPaths>
+                        <path>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok</artifactId>
+                            <version>${lombok.version}</version>
+                        </path>
+                        <path>
+                            <groupId>org.mapstruct</groupId>
+                            <artifactId>mapstruct-processor</artifactId>
+                            <version>${org.mapstruct.version}</version>
+                        </path>
+                        <path>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok-mapstruct-binding</artifactId>
+                            <version>0.2.0</version>
+                        </path>
+                    </annotationProcessorPaths>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>2.7.18</version>
+                <configuration>
+                    <fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
+                    <excludes>
+                        <exclude>
+                            <groupId>org.apache.tomcat</groupId>
+                            <artifactId>annotations-api</artifactId>
+                        </exclude>
+                    </excludes>
+                    <classifier>exec</classifier> <!-- repackage后的fat jar带exec后缀,原始jar保持普通格式可被其他项目依赖 -->
+                </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>
+        </plugins>
+        <finalName>${project.artifactId}</finalName>
+    </build>
+
+</project>

+ 40 - 0
fs-admin-agent/src/main/java/com/fs/FSAdminSaasApplication.java

@@ -0,0 +1,40 @@
+package com.fs;
+
+import com.fs.config.OverridingBeanNameGenerator;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.redisson.spring.starter.RedissonAutoConfiguration;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.FilterType;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * 启动程序
+ */
+@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, RedissonAutoConfiguration.class})
+@ComponentScan(
+    basePackages = "com.fs",
+    nameGenerator = OverridingBeanNameGenerator.class,
+    excludeFilters = {
+        @ComponentScan.Filter(type = FilterType.REGEX, pattern = {
+            "com\\.fs\\.framework\\.service\\.PermissionService",
+            "com\\.fs\\.framework\\.service\\.UserDetailsServiceImpl",
+            "com\\.fs\\.company\\.controller\\..*",
+            "com\\.fs\\.hisStore\\.controller\\..*",
+            "com\\.fs\\.his\\.controller\\.FsAiWorkflowController"
+        })
+    }
+)
+@Transactional
+@EnableAsync
+@EnableScheduling
+public class FSAdminSaasApplication {
+    public static void main(String[] args) {
+        // System.setProperty("spring.devtools.restart.enabled", "false");
+        SpringApplication.run(FSAdminSaasApplication.class, args);
+        System.out.println("admin启动成功");
+    }
+}

+ 14 - 0
fs-admin-agent/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(FSAdminSaasApplication.class);
+    }
+}

+ 31 - 0
fs-admin-agent/src/main/java/com/fs/ad/controller/AdAccountController.java

@@ -0,0 +1,31 @@
+package com.fs.ad.controller;
+
+import com.fs.ad.service.IAdAccountService;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.R;
+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 org.springframework.context.annotation.Profile;
+
+/**
+ * 站点管理Controller
+ * 
+ * @author fs
+ * @date 2025-03-04
+ */
+@Profile({"admin", "company"})
+@RestController
+@RequestMapping("/ad/adAccount")
+public class AdAccountController extends BaseController
+{
+    @Autowired
+    private IAdAccountService adAccountService;
+
+    @GetMapping("/listAll")
+    public R listAll(){
+        return R.ok().put("data", adAccountService.listAll());
+    }
+
+}

+ 108 - 0
fs-admin-agent/src/main/java/com/fs/ad/controller/AdDomainController.java

@@ -0,0 +1,108 @@
+package com.fs.ad.controller;
+
+import com.fs.ad.domain.AdDomain;
+import com.fs.ad.service.IAdDomainService;
+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;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 广告域名Controller
+ * 
+ * @author fs
+ * @date 2025-03-04
+ */
+@Profile({"admin", "company"})
+@RestController
+@RequestMapping("/ad/adDomain")
+public class AdDomainController extends BaseController
+{
+    @Autowired
+    private IAdDomainService adDomainService;
+
+    /**
+     * 查询广告域名列表
+     */
+    @PreAuthorize("@ss.hasPermi('ad:adDomain:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(AdDomain adDomain)
+    {
+        startPage();
+        List<AdDomain> list = adDomainService.selectAdDomainList(adDomain);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出广告域名列表
+     */
+    @PreAuthorize("@ss.hasPermi('ad:adDomain:export')")
+    @Log(title = "广告域名", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(AdDomain adDomain)
+    {
+        List<AdDomain> list = adDomainService.selectAdDomainList(adDomain);
+        ExcelUtil<AdDomain> util = new ExcelUtil<AdDomain>(AdDomain.class);
+        return util.exportExcel(list, "广告域名数据");
+    }
+
+    /**
+     * 获取广告域名详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('ad:adDomain:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(adDomainService.selectAdDomainById(id));
+    }
+
+    /**
+     * 新增广告域名
+     */
+    @PreAuthorize("@ss.hasPermi('ad:adDomain:add')")
+    @Log(title = "广告域名", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody AdDomain adDomain)
+    {
+        return toAjax(adDomainService.insertAdDomain(adDomain));
+    }
+
+    /**
+     * 修改广告域名
+     */
+    @PreAuthorize("@ss.hasPermi('ad:adDomain:edit')")
+    @Log(title = "广告域名", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody AdDomain adDomain)
+    {
+        return toAjax(adDomainService.updateAdDomain(adDomain));
+    }
+
+    /**
+     * 删除广告域名
+     */
+    @PreAuthorize("@ss.hasPermi('ad:adDomain:remove')")
+    @Log(title = "广告域名", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(adDomainService.deleteAdDomainByIds(ids));
+    }
+
+
+
+
+    @GetMapping(value = "/listAll")
+    public R listAll(){
+        return R.ok().put("data", adDomainService.list());
+    }
+}

+ 105 - 0
fs-admin-agent/src/main/java/com/fs/ad/controller/AdDyAccountController.java

@@ -0,0 +1,105 @@
+package com.fs.ad.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.ad.domain.AdDyAccount;
+import com.fs.ad.service.IAdDyAccountService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+import org.springframework.context.annotation.Profile;
+
+/**
+ * 抖音账户Controller
+ * 
+ * @author 吴树波
+ * @date 2025-05-27
+ */
+@Profile({"admin", "company"})
+@RestController
+@RequestMapping("/ad/AdDyAccount")
+public class AdDyAccountController extends BaseController
+{
+    @Autowired
+    private IAdDyAccountService adDyAccountService;
+
+    /**
+     * 查询抖音账户列表
+     */
+    @PreAuthorize("@ss.hasPermi('ad:AdDyAccount:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(AdDyAccount adDyAccount)
+    {
+        startPage();
+        List<AdDyAccount> list = adDyAccountService.selectAdDyAccountList(adDyAccount);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出抖音账户列表
+     */
+    @PreAuthorize("@ss.hasPermi('ad:AdDyAccount:export')")
+    @Log(title = "抖音账户", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(AdDyAccount adDyAccount)
+    {
+        List<AdDyAccount> list = adDyAccountService.selectAdDyAccountList(adDyAccount);
+        ExcelUtil<AdDyAccount> util = new ExcelUtil<AdDyAccount>(AdDyAccount.class);
+        return util.exportExcel(list, "抖音账户数据");
+    }
+
+    /**
+     * 获取抖音账户详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('ad:AdDyAccount:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(adDyAccountService.selectAdDyAccountById(id));
+    }
+
+    /**
+     * 新增抖音账户
+     */
+    @PreAuthorize("@ss.hasPermi('ad:AdDyAccount:add')")
+    @Log(title = "抖音账户", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody AdDyAccount adDyAccount)
+    {
+        return toAjax(adDyAccountService.insertAdDyAccount(adDyAccount));
+    }
+
+    /**
+     * 修改抖音账户
+     */
+    @PreAuthorize("@ss.hasPermi('ad:AdDyAccount:edit')")
+    @Log(title = "抖音账户", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody AdDyAccount adDyAccount)
+    {
+        return toAjax(adDyAccountService.updateAdDyAccount(adDyAccount));
+    }
+
+    /**
+     * 删除抖音账户
+     */
+    @PreAuthorize("@ss.hasPermi('ad:AdDyAccount:remove')")
+    @Log(title = "抖音账户", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(adDyAccountService.deleteAdDyAccountByIds(ids));
+    }
+}

+ 116 - 0
fs-admin-agent/src/main/java/com/fs/ad/controller/AdDyApiController.java

@@ -0,0 +1,116 @@
+package com.fs.ad.controller;
+
+import java.util.List;
+import org.springframework.context.annotation.Profile;
+
+import com.fs.ad.vo.DyAuthorizedVo;
+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.ad.domain.AdDyApi;
+import com.fs.ad.service.IAdDyApiService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 抖音投流API信息Controller
+ * 
+ * @author 吴树波
+ * @date 2025-05-27
+ */
+@Profile({"admin", "company"})
+@RestController
+@RequestMapping("/ad/adDyApi")
+public class AdDyApiController extends BaseController
+{
+    @Autowired
+    private IAdDyApiService adDyApiService;
+
+    /**
+     * 查询抖音投流API信息列表
+     */
+    @PreAuthorize("@ss.hasPermi('ad:adDyApi:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(AdDyApi adDyApi)
+    {
+        startPage();
+        List<AdDyApi> list = adDyApiService.selectAdDyApiList(adDyApi);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出抖音投流API信息列表
+     */
+    @PreAuthorize("@ss.hasPermi('ad:adDyApi:export')")
+    @Log(title = "抖音投流API信息", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(AdDyApi adDyApi)
+    {
+        List<AdDyApi> list = adDyApiService.selectAdDyApiList(adDyApi);
+        ExcelUtil<AdDyApi> util = new ExcelUtil<AdDyApi>(AdDyApi.class);
+        return util.exportExcel(list, "抖音投流API信息数据");
+    }
+
+    /**
+     * 获取抖音投流API信息详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('ad:adDyApi:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(adDyApiService.selectAdDyApiById(id));
+    }
+
+    /**
+     * 新增抖音投流API信息
+     */
+    @PreAuthorize("@ss.hasPermi('ad:adDyApi:add')")
+    @Log(title = "抖音投流API信息", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody AdDyApi adDyApi)
+    {
+        return toAjax(adDyApiService.insertAdDyApi(adDyApi));
+    }
+
+    /**
+     * 修改抖音投流API信息
+     */
+    @PreAuthorize("@ss.hasPermi('ad:adDyApi:edit')")
+    @Log(title = "抖音投流API信息", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody AdDyApi adDyApi)
+    {
+        return toAjax(adDyApiService.updateAdDyApi(adDyApi));
+    }
+
+    /**
+     * 删除抖音投流API信息
+     */
+    @PreAuthorize("@ss.hasPermi('ad:adDyApi:remove')")
+    @Log(title = "抖音投流API信息", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(adDyApiService.deleteAdDyApiByIds(ids));
+    }
+    /**
+     * 删除抖音投流API信息
+     */
+    @Log(title = "抖音API授权", businessType = BusinessType.UPDATE)
+	@GetMapping("/authorized")
+    public String authorized(DyAuthorizedVo vo){
+        System.out.println(vo);
+        return "授权成功";
+    }
+}

+ 99 - 0
fs-admin-agent/src/main/java/com/fs/ad/controller/AdHtmlClickLogController.java

@@ -0,0 +1,99 @@
+package com.fs.ad.controller;
+
+import com.fs.ad.domain.AdHtmlClickLog;
+import com.fs.ad.service.IAdHtmlClickLogService;
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 广告点击Controller
+ * 
+ * @author fs
+ * @date 2025-01-09
+ */
+@Profile({"admin", "company"})
+@RestController
+@RequestMapping("/ad/clickLog")
+public class AdHtmlClickLogController extends BaseController
+{
+    @Autowired
+    private IAdHtmlClickLogService adHtmlClickLogService;
+
+    /**
+     * 查询广告点击列表
+     */
+    @PreAuthorize("@ss.hasPermi('ad:clickLog:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(AdHtmlClickLog adHtmlClickLog)
+    {
+        startPage();
+        List<AdHtmlClickLog> list = adHtmlClickLogService.selectAdHtmlClickLogList(adHtmlClickLog);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出广告点击列表
+     */
+    @PreAuthorize("@ss.hasPermi('ad:clickLog:export')")
+    @Log(title = "广告点击", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(AdHtmlClickLog adHtmlClickLog)
+    {
+        List<AdHtmlClickLog> list = adHtmlClickLogService.selectAdHtmlClickLogList(adHtmlClickLog);
+        ExcelUtil<AdHtmlClickLog> util = new ExcelUtil<AdHtmlClickLog>(AdHtmlClickLog.class);
+        return util.exportExcel(list, "广告点击数据");
+    }
+
+    /**
+     * 获取广告点击详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('ad:clickLog:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(adHtmlClickLogService.selectAdHtmlClickLogById(id));
+    }
+
+    /**
+     * 新增广告点击
+     */
+    @PreAuthorize("@ss.hasPermi('ad:clickLog:add')")
+    @Log(title = "广告点击", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody AdHtmlClickLog adHtmlClickLog)
+    {
+        return toAjax(adHtmlClickLogService.insertAdHtmlClickLog(adHtmlClickLog));
+    }
+
+    /**
+     * 修改广告点击
+     */
+    @PreAuthorize("@ss.hasPermi('ad:clickLog:edit')")
+    @Log(title = "广告点击", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody AdHtmlClickLog adHtmlClickLog)
+    {
+        return toAjax(adHtmlClickLogService.updateAdHtmlClickLog(adHtmlClickLog));
+    }
+
+    /**
+     * 删除广告点击
+     */
+    @PreAuthorize("@ss.hasPermi('ad:clickLog:remove')")
+    @Log(title = "广告点击", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(adHtmlClickLogService.deleteAdHtmlClickLogByIds(ids));
+    }
+}

+ 105 - 0
fs-admin-agent/src/main/java/com/fs/ad/controller/AdHtmlTemplateController.java

@@ -0,0 +1,105 @@
+package com.fs.ad.controller;
+
+import com.fs.ad.domain.AdHtmlTemplate;
+import com.fs.ad.service.IAdHtmlTemplateService;
+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;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 广告信息流链接Controller
+ * 
+ * @author fs
+ * @date 2024-12-31
+ */
+@Profile({"admin", "company"})
+@RestController
+@RequestMapping("/ad/html/template")
+public class AdHtmlTemplateController extends BaseController
+{
+    @Autowired
+    private IAdHtmlTemplateService adHtmlUrlService;
+
+    /**
+     * 查询广告信息流链接列表
+     */
+    @PreAuthorize("@ss.hasPermi('ad:html:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(AdHtmlTemplate adHtmlTemplate)
+    {
+        startPage();
+        List<AdHtmlTemplate> list = adHtmlUrlService.selectAdHtmlUrlList(adHtmlTemplate);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出广告信息流链接列表
+     */
+    @PreAuthorize("@ss.hasPermi('ad:html:export')")
+    @Log(title = "广告信息流链接", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(AdHtmlTemplate adHtmlTemplate)
+    {
+        List<AdHtmlTemplate> list = adHtmlUrlService.selectAdHtmlUrlList(adHtmlTemplate);
+        ExcelUtil<AdHtmlTemplate> util = new ExcelUtil<AdHtmlTemplate>(AdHtmlTemplate.class);
+        return util.exportExcel(list, "广告信息流链接数据");
+    }
+
+    /**
+     * 获取广告信息流链接详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('ad:html:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(adHtmlUrlService.selectAdHtmlUrlById(id));
+    }
+
+    /**
+     * 新增广告信息流链接
+     */
+    @PreAuthorize("@ss.hasPermi('ad:html:add')")
+    @Log(title = "广告信息流链接", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody AdHtmlTemplate adHtmlTemplate)
+    {
+        return toAjax(adHtmlUrlService.insertAdHtmlUrl(adHtmlTemplate));
+    }
+
+    /**
+     * 修改广告信息流链接
+     */
+    @PreAuthorize("@ss.hasPermi('ad:html:edit')")
+    @Log(title = "广告信息流链接", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody AdHtmlTemplate adHtmlTemplate)
+    {
+        return toAjax(adHtmlUrlService.updateAdHtmlUrl(adHtmlTemplate));
+    }
+
+    /**
+     * 删除广告信息流链接
+     */
+    @PreAuthorize("@ss.hasPermi('ad:html:remove')")
+    @Log(title = "广告信息流链接", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(adHtmlUrlService.deleteAdHtmlUrlByIds(ids));
+    }
+
+    @GetMapping(value = "/listAll")
+    public R listAll(){
+        return R.ok().put("data", adHtmlUrlService.list());
+    }
+}

+ 108 - 0
fs-admin-agent/src/main/java/com/fs/ad/controller/AdIqiyiAccountController.java

@@ -0,0 +1,108 @@
+package com.fs.ad.controller;
+
+import com.fs.ad.domain.AdIqiyiAccount;
+import com.fs.ad.service.IAdIqiyiAccountService;
+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;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 爱奇艺广告账号Controller
+ * 
+ * @author fs
+ * @date 2025-03-03
+ */
+@Profile({"admin", "company"})
+@RestController
+@RequestMapping("/ad/AdIqiyiAccount")
+public class AdIqiyiAccountController extends BaseController
+{
+    @Autowired
+    private IAdIqiyiAccountService adIqiyiAccountService;
+
+    /**
+     * 查询爱奇艺广告账号列表
+     */
+    @PreAuthorize("@ss.hasPermi('ad:AdIqiyiAccount:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(AdIqiyiAccount adIqiyiAccount)
+    {
+        startPage();
+        List<AdIqiyiAccount> list = adIqiyiAccountService.selectAdIqiyiAccountList(adIqiyiAccount);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出爱奇艺广告账号列表
+     */
+    @PreAuthorize("@ss.hasPermi('ad:AdIqiyiAccount:export')")
+    @Log(title = "爱奇艺广告账号", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(AdIqiyiAccount adIqiyiAccount)
+    {
+        List<AdIqiyiAccount> list = adIqiyiAccountService.selectAdIqiyiAccountList(adIqiyiAccount);
+        ExcelUtil<AdIqiyiAccount> util = new ExcelUtil<AdIqiyiAccount>(AdIqiyiAccount.class);
+        return util.exportExcel(list, "爱奇艺广告账号数据");
+    }
+
+    /**
+     * 获取爱奇艺广告账号详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('ad:AdIqiyiAccount:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(adIqiyiAccountService.selectAdIqiyiAccountById(id));
+    }
+
+    /**
+     * 新增爱奇艺广告账号
+     */
+    @PreAuthorize("@ss.hasPermi('ad:AdIqiyiAccount:add')")
+    @Log(title = "爱奇艺广告账号", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody AdIqiyiAccount adIqiyiAccount)
+    {
+        return toAjax(adIqiyiAccountService.insertAdIqiyiAccount(adIqiyiAccount));
+    }
+
+    /**
+     * 修改爱奇艺广告账号
+     */
+    @PreAuthorize("@ss.hasPermi('ad:AdIqiyiAccount:edit')")
+    @Log(title = "爱奇艺广告账号", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody AdIqiyiAccount adIqiyiAccount)
+    {
+        return toAjax(adIqiyiAccountService.updateAdIqiyiAccount(adIqiyiAccount));
+    }
+
+    /**
+     * 删除爱奇艺广告账号
+     */
+    @PreAuthorize("@ss.hasPermi('ad:AdIqiyiAccount:remove')")
+    @Log(title = "爱奇艺广告账号", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(adIqiyiAccountService.deleteAdIqiyiAccountByIds(ids));
+    }
+    /**
+     * 删除优酷广告账号
+     */
+    @PreAuthorize("@ss.hasPermi('ad:AdYouKuaccount:list')")
+    @GetMapping("/listAll")
+    public R listAll(){
+        return R.ok().put("data", adIqiyiAccountService.listAll());
+    }
+}

+ 111 - 0
fs-admin-agent/src/main/java/com/fs/ad/controller/AdSiteController.java

@@ -0,0 +1,111 @@
+package com.fs.ad.controller;
+
+import com.fs.ad.domain.AdSite;
+import com.fs.ad.service.IAdSiteService;
+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;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 站点管理Controller
+ * 
+ * @author fs
+ * @date 2025-03-04
+ */
+@Profile({"admin", "company"})
+@RestController
+@RequestMapping("/ad/adSite")
+public class AdSiteController extends BaseController
+{
+    @Autowired
+    private IAdSiteService adSiteService;
+
+    /**
+     * 查询站点管理列表
+     */
+    @PreAuthorize("@ss.hasPermi('ad:adSite:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(AdSite adSite)
+    {
+        startPage();
+        List<AdSite> list = adSiteService.selectAdSiteList(adSite);
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询站点管理列表
+     */
+    @PreAuthorize("@ss.hasPermi('ad:adSite:list')")
+    @GetMapping("/listAll")
+    public R listAll(AdSite adSite){
+        List<AdSite> list = adSiteService.selectAdSiteList(adSite);
+        return R.ok().put("data", list);
+    }
+
+    /**
+     * 导出站点管理列表
+     */
+    @PreAuthorize("@ss.hasPermi('ad:adSite:export')")
+    @Log(title = "站点管理", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(AdSite adSite)
+    {
+        List<AdSite> list = adSiteService.selectAdSiteList(adSite);
+        ExcelUtil<AdSite> util = new ExcelUtil<AdSite>(AdSite.class);
+        return util.exportExcel(list, "站点管理数据");
+    }
+
+    /**
+     * 获取站点管理详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('ad:adSite:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(adSiteService.selectAdSiteById(id));
+    }
+
+    /**
+     * 新增站点管理
+     */
+    @PreAuthorize("@ss.hasPermi('ad:adSite:add')")
+    @Log(title = "站点管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody AdSite adSite)
+    {
+        return toAjax(adSiteService.insertAdSite(adSite));
+    }
+
+    /**
+     * 修改站点管理
+     */
+    @PreAuthorize("@ss.hasPermi('ad:adSite:edit')")
+    @Log(title = "站点管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody AdSite adSite)
+    {
+        return toAjax(adSiteService.updateAdSite(adSite));
+    }
+
+    /**
+     * 删除站点管理
+     */
+    @PreAuthorize("@ss.hasPermi('ad:adSite:remove')")
+    @Log(title = "站点管理", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(adSiteService.deleteAdSiteByIds(ids));
+    }
+
+}

+ 99 - 0
fs-admin-agent/src/main/java/com/fs/ad/controller/AdUploadLogController.java

@@ -0,0 +1,99 @@
+package com.fs.ad.controller;
+
+import com.fs.ad.domain.AdUploadLog;
+import com.fs.ad.service.IAdUploadLogService;
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 百度回传日志Controller
+ * 
+ * @author fs
+ * @date 2025-03-14
+ */
+@Profile({"admin", "company"})
+@RestController
+@RequestMapping("/ad/AdUploadLog")
+public class AdUploadLogController extends BaseController
+{
+    @Autowired
+    private IAdUploadLogService adUploadLogService;
+
+    /**
+     * 查询百度回传日志列表
+     */
+    @PreAuthorize("@ss.hasPermi('ad:AdUploadLog:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(AdUploadLog adUploadLog)
+    {
+        startPage();
+        List<AdUploadLog> list = adUploadLogService.selectAdUploadLogList(adUploadLog);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出百度回传日志列表
+     */
+    @PreAuthorize("@ss.hasPermi('ad:AdUploadLog:export')")
+    @Log(title = "百度回传日志", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(AdUploadLog adUploadLog)
+    {
+        List<AdUploadLog> list = adUploadLogService.selectAdUploadLogList(adUploadLog);
+        ExcelUtil<AdUploadLog> util = new ExcelUtil<AdUploadLog>(AdUploadLog.class);
+        return util.exportExcel(list, "百度回传日志数据");
+    }
+
+    /**
+     * 获取百度回传日志详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('ad:AdUploadLog:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(adUploadLogService.selectAdUploadLogById(id));
+    }
+
+    /**
+     * 新增百度回传日志
+     */
+    @PreAuthorize("@ss.hasPermi('ad:AdUploadLog:add')")
+    @Log(title = "百度回传日志", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody AdUploadLog adUploadLog)
+    {
+        return toAjax(adUploadLogService.insertAdUploadLog(adUploadLog));
+    }
+
+    /**
+     * 修改百度回传日志
+     */
+    @PreAuthorize("@ss.hasPermi('ad:AdUploadLog:edit')")
+    @Log(title = "百度回传日志", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody AdUploadLog adUploadLog)
+    {
+        return toAjax(adUploadLogService.updateAdUploadLog(adUploadLog));
+    }
+
+    /**
+     * 删除百度回传日志
+     */
+    @PreAuthorize("@ss.hasPermi('ad:AdUploadLog:remove')")
+    @Log(title = "百度回传日志", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(adUploadLogService.deleteAdUploadLogByIds(ids));
+    }
+}

+ 108 - 0
fs-admin-agent/src/main/java/com/fs/ad/controller/AdYoukuAccountController.java

@@ -0,0 +1,108 @@
+package com.fs.ad.controller;
+
+import com.fs.ad.domain.AdYoukuAccount;
+import com.fs.ad.service.IAdYoukuAccountService;
+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;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 优酷广告账号Controller
+ * 
+ * @author fs
+ * @date 2025-02-19
+ */
+@Profile({"admin", "company"})
+@RestController
+@RequestMapping("/ad/AdYouKuAccount")
+public class AdYoukuAccountController extends BaseController
+{
+    @Autowired
+    private IAdYoukuAccountService adYoukuAccountService;
+
+    /**
+     * 查询优酷广告账号列表
+     */
+    @PreAuthorize("@ss.hasPermi('ad:AdYouKuaccount:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(AdYoukuAccount adYoukuAccount)
+    {
+        startPage();
+        List<AdYoukuAccount> list = adYoukuAccountService.selectAdYoukuAccountList(adYoukuAccount);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出优酷广告账号列表
+     */
+    @PreAuthorize("@ss.hasPermi('ad:AdYouKuaccount:export')")
+    @Log(title = "优酷广告账号", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(AdYoukuAccount adYoukuAccount)
+    {
+        List<AdYoukuAccount> list = adYoukuAccountService.selectAdYoukuAccountList(adYoukuAccount);
+        ExcelUtil<AdYoukuAccount> util = new ExcelUtil<AdYoukuAccount>(AdYoukuAccount.class);
+        return util.exportExcel(list, "优酷广告账号数据");
+    }
+
+    /**
+     * 获取优酷广告账号详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('ad:AdYouKuaccount:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(adYoukuAccountService.selectAdYoukuAccountById(id));
+    }
+
+    /**
+     * 新增优酷广告账号
+     */
+    @PreAuthorize("@ss.hasPermi('ad:AdYouKuaccount:add')")
+    @Log(title = "优酷广告账号", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody AdYoukuAccount adYoukuAccount)
+    {
+        return toAjax(adYoukuAccountService.insertAdYoukuAccount(adYoukuAccount));
+    }
+
+    /**
+     * 修改优酷广告账号
+     */
+    @PreAuthorize("@ss.hasPermi('ad:AdYouKuaccount:edit')")
+    @Log(title = "优酷广告账号", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody AdYoukuAccount adYoukuAccount)
+    {
+        return toAjax(adYoukuAccountService.updateAdYoukuAccount(adYoukuAccount));
+    }
+
+    /**
+     * 删除优酷广告账号
+     */
+    @PreAuthorize("@ss.hasPermi('ad:AdYouKuaccount:remove')")
+    @Log(title = "优酷广告账号", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(adYoukuAccountService.deleteAdYoukuAccountByIds(ids));
+    }
+    /**
+     * 删除优酷广告账号
+     */
+    @PreAuthorize("@ss.hasPermi('ad:AdYouKuaccount:list')")
+	@GetMapping("/listAll")
+    public R listAll(){
+        return R.ok().put("data", adYoukuAccountService.listAll());
+    }
+}

+ 143 - 0
fs-admin-agent/src/main/java/com/fs/ad/controller/BdAccountController.java

@@ -0,0 +1,143 @@
+package com.fs.ad.controller;
+
+import com.fs.baidu.api.BaiduApis;
+import com.fs.baidu.domain.*;
+import com.fs.baidu.service.*;
+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;
+import lombok.AllArgsConstructor;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * 百度账号Controller
+ * 
+ * @author fs
+ * @date 2025-01-20
+ */
+@Profile({"admin", "company"})
+@RestController
+@AllArgsConstructor
+@RequestMapping("/bd/BdAccount")
+public class BdAccountController extends BaseController {
+    private final IBdAccountService bdAccountService;
+    private final IBdPlanService bdPlanService;
+    private final IBdUnitService bdUnitService;
+    private final IBdCreativeService bdCreativeService;
+    private final IBdApiService bdApiService;
+    private final BaiduApis baiduApis;
+
+    /**
+     * 查询百度账号列表
+     */
+    @PreAuthorize("@ss.hasPermi('baidu:BdAccount:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(BdAccount bdAccount){
+        startPage();
+        List<BdAccount> list = bdAccountService.selectBdAccountList(bdAccount);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出百度账号列表
+     */
+    @PreAuthorize("@ss.hasPermi('baidu:BdAccount:export')")
+    @Log(title = "百度账号", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(BdAccount bdAccount)
+    {
+        List<BdAccount> list = bdAccountService.selectBdAccountList(bdAccount);
+        ExcelUtil<BdAccount> util = new ExcelUtil<BdAccount>(BdAccount.class);
+        return util.exportExcel(list, "百度账号数据");
+    }
+
+    /**
+     * 获取百度账号详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('baidu:BdAccount:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(bdAccountService.selectBdAccountById(id));
+    }
+
+    /**
+     * 新增百度账号
+     */
+    @PreAuthorize("@ss.hasPermi('baidu:BdAccount:add')")
+    @Log(title = "百度账号", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody BdAccount bdAccount)
+    {
+        return toAjax(bdAccountService.insertBdAccount(bdAccount));
+    }
+
+    /**
+     * 修改百度账号
+     */
+    @PreAuthorize("@ss.hasPermi('baidu:BdAccount:edit')")
+    @Log(title = "百度账号", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody BdAccount bdAccount)
+    {
+        return toAjax(bdAccountService.updateBdAccount(bdAccount));
+    }
+
+    /**
+     * 删除百度账号
+     */
+    @PreAuthorize("@ss.hasPermi('baidu:BdAccount:remove')")
+    @Log(title = "百度账号", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(bdAccountService.deleteBdAccountByIds(ids));
+    }
+
+
+    /**
+     * 查询百度账号列表
+     */
+    @GetMapping("/listAll")
+    public R listAll(BdAccount bdAccount){
+        return R.ok().put("data", bdAccountService.selectBdAccountList(bdAccount));
+    }
+    @GetMapping("/listAllPlan")
+    public R listAllPlan(BdPlan plan){
+        return R.ok().put("data", bdPlanService.selectBdPlanList(plan));
+    }
+    @GetMapping("/listAllUnit")
+    public R listAllUnit(BdUnit unit){
+        return R.ok().put("data", bdUnitService.selectBdUnitList(unit));
+    }
+    @GetMapping("/listAllCreative")
+    public R listAllCreative(BdCreative creative){
+        return R.ok().put("data", bdCreativeService.selectBdCreativeList(creative));
+    }
+    @GetMapping("/authorizationUrl")
+    public R authorizationUrl(Long id){
+        Optional<BdApi> first = bdApiService.list().stream().findFirst();
+        if(!first.isPresent()){
+            return R.error("未找到API信息");
+        }
+        return R.ok().put("url", bdApiService.authorizationUrl(first.get().getId()));
+    }
+    @GetMapping("/syncAccount")
+    public R syncAccount(Long id){
+        Optional<BdApi> first = bdApiService.list().stream().findFirst();
+        if(!first.isPresent()){
+            return R.error("未找到API信息");
+        }
+        baiduApis.listAccount(1L, id);
+        return R.ok();
+    }
+}

+ 218 - 0
fs-admin-agent/src/main/java/com/fs/ad/controller/MockAppController.java

@@ -0,0 +1,218 @@
+package com.fs.ad.controller;
+
+import com.fs.baidu.domain.BdApi;
+import com.fs.baidu.service.IBdApiService;
+import com.fs.baidu.utils.SignService;
+import com.fs.huifuPay.sdk.opps.core.exception.BasePayException;
+import com.fs.huifuPay.sdk.opps.core.utils.HttpClientUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.commons.lang3.StringUtils;
+import org.json.JSONObject;
+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 org.springframework.context.annotation.Profile;
+
+import javax.servlet.http.HttpServletRequest;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
+
+@Profile({"admin", "company"})
+@RestController
+@RequestMapping("/baiduBack")
+public class MockAppController {
+    private static final Logger log = LoggerFactory.getLogger(MockAppController.class);
+    private static final String APPID = "appId";
+    private static final String AUTH_CODE = "authCode";
+    private static final String USERID = "userId";
+    private static final String TIMESTAMP = "timestamp";
+    private static final String SIGNATURE = "signature";
+    private static final String STATE = "state";
+    private static final String ACCESSTOKEN_URL = "https://u.baidu.com/oauth/accessToken";
+
+    @Autowired
+    private IBdApiService bdApiService; // 开发者自定义
+    @Autowired
+    private SignService signService; // 开发者可自定义
+
+    @GetMapping("/callback")
+    public String mockApp(HttpServletRequest request) throws BasePayException {
+
+        // 获取请求参数
+        Map<String, String> params = new HashMap<>();
+        this.fillParams(params, request);
+        log.info("callback: params = {}", JSONObject.valueToString(params));
+        int userIdInt = 0;
+        try {
+            userIdInt = Integer.parseInt(params.get(USERID));
+        } catch (Exception e) {
+            return this.getResponseJson(600011, "参数错误", new Object());
+        }
+        // 对签名内容进行判空
+        if (StringUtils.isBlank(params.get(SIGNATURE))) {
+            return this.getResponseJson(600011, "参数错误", new Object());
+        }
+
+        // 获取应用对应的密钥
+        BdApi app = bdApiService.getAppByAppId(params.get(APPID));
+        String sk = app.getAppSecretKey();
+        // 开发者进行验签
+        // 检查状态码
+//        if (!this.checkState(params.get(APPID), app.getAppUserId(), params.get(STATE))) {
+//            log.info("callback: state check fail");
+//            return this.getResponseJson(600011, "状态码错误", new Object());
+//        }
+
+        // 签名验证
+        if (!this.checkSignature(params, sk)) {
+            log.info("callback: signature check fail");
+            return this.getResponseJson(600011, "签名错误", new Object());
+        }
+
+        // 调用接口换取授权令牌
+        String response = this.getAccessToken(params, sk, userIdInt);
+        log.info("callback:getAccesstoken, response={}", response);
+        String accessToken = null;
+        String refreshToken = null;
+        int uid;
+
+        if (response.length() > 0) {
+            JSONObject res = new JSONObject(response);
+            int code = (int) res.get("code");
+            if (code == 0) {
+                JSONObject data = res.getJSONObject("data");
+                accessToken = (String) data.get("accessToken");
+                // 注释部分为 accessToken 其他信息,各应用开发者酌情使用即可
+                 refreshToken = (String) data.get("refreshToken");
+                String openId = data.getString("openId");
+                int expiresIn = data.getInt("expiresIn");
+                int refreshExpiresIn = data.getInt("refreshExpiresIn");
+                app.setAccessToken(accessToken);
+                app.setRefreshToken(refreshToken);
+                app.setExpiresIn(expiresIn);
+                app.setOpenId(openId);
+                app.setRefreshExpiresIn(refreshExpiresIn);
+                app.setAuthCode(params.get(AUTH_CODE));
+                bdApiService.updateById(app);
+
+                // TODO 相关信息落库处理
+            } else {
+                return this.getResponseJson(600011, "未获取到 access_token", new Object());
+            }
+        }
+        Map<String, String> data = new HashMap<>();
+        data.put("accessToken", accessToken);
+        return this.getResponseJson(0, "success", data);
+    }
+
+    /**
+     * 填充请求参数,开发者可用实体代替map
+     *
+     * @param params
+     * @param request
+     */
+    private void fillParams(Map<String, String> params, HttpServletRequest request) {
+        params.put(APPID, request.getParameter(APPID));
+        params.put(AUTH_CODE, request.getParameter(AUTH_CODE));
+        params.put(USERID, request.getParameter(USERID));
+        params.put(TIMESTAMP, request.getParameter(TIMESTAMP));
+        params.put(SIGNATURE, request.getParameter(SIGNATURE));
+        params.put(STATE, request.getParameter(STATE));
+    }
+
+    /**
+     * 校验状态码是否符合预期
+     *
+     * @param appId  应用ID
+     * @param userId 创建应用的ID
+     * @param state  请求参数中的状态码
+     * @return
+     */
+    private boolean checkState(String appId, Long userId, String state) {
+        // md5util 开发者自行开发即可
+        String newState = md5(appId.concat("_").concat(String.valueOf(userId)));
+        return newState.equals(state);
+    }
+    public static String md5(String input) {
+        try {
+            MessageDigest md = MessageDigest.getInstance("MD5");
+            byte[] messageDigest = md.digest(input.getBytes());
+            StringBuilder hexString = new StringBuilder();
+            for (byte b : messageDigest) {
+                String hex = Integer.toHexString(0xff & b);
+                if (hex.length() == 1) {
+                    hexString.append('0');
+                }
+                hexString.append(hex);
+            }
+            return hexString.toString();
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException(e);
+        }
+    }
+    /**
+     * 签名验证方法
+     *
+     * @param params
+     * @param sk
+     * @return
+     */
+    private boolean checkSignature(Map<String, String> params, String sk) {
+        // 获取验签字符串:按key自然排序,拼接成 json 字符串,
+        // 示例:str1 = {"appId": xxxxx, "authCode": xxx, "state": xxx,"timestamp": xxx}
+        TreeMap<String, Object> map = new TreeMap<>();
+        map.put(APPID, params.get(APPID));
+        map.put(AUTH_CODE, params.get(AUTH_CODE));
+        map.put(USERID, params.get(USERID));
+        map.put(STATE, params.get(STATE));
+        map.put(TIMESTAMP, params.get(TIMESTAMP));
+        // 根据上述签名算法对接收到的参数签名
+        String sign = signService.paramsSign(sk, map);
+        log.info("callback: signature = {}", sign);
+        // 判断签名和URL传参签名是否一致
+        return params.get(SIGNATURE).equals(sign);
+    }
+
+    /**
+     * 换取授权令牌
+     *
+     * @param params
+     * @param sk
+     * @param userIdInt 授权账户ID
+     * @return
+     */
+    private String getAccessToken(Map<String, String> params, String sk, int userIdInt) throws BasePayException {
+        Map<String, Object> requestMap = new HashMap<>();
+        requestMap.put(APPID, params.get(APPID));
+        requestMap.put("secretKey", sk);
+        requestMap.put(AUTH_CODE, params.get(AUTH_CODE));
+        requestMap.put("grantType", "access_token");
+        requestMap.put("userId", userIdInt);
+
+        String paramsJson = JSONObject.valueToString(requestMap);
+        // 开发者自行增加异常判断
+        return HttpClientUtils.httpPostJson(ACCESSTOKEN_URL, new HashMap<>(), paramsJson);
+    }
+
+    /**
+     * 封装返回json串
+     *
+     * @param code
+     * @param message
+     * @param data
+     * @return
+     */
+    private String getResponseJson(int code, String message, Object data) {
+        // 封装返回字段的map
+        Map<String, Object> responseMap = new HashMap<>();
+        responseMap.put("code", code);
+        responseMap.put("message", message);
+        responseMap.put("data", data);
+        return JSONObject.valueToString(responseMap);
+    }
+}

+ 86 - 0
fs-admin-agent/src/main/java/com/fs/ad/controller/StatisticsController.java

@@ -0,0 +1,86 @@
+package com.fs.ad.controller;
+
+import com.fs.baidu.api.BaiduApis;
+import com.fs.baidu.domain.BdAccount;
+import com.fs.baidu.domain.BdApi;
+import com.fs.baidu.enums.BdTimeUnit;
+import com.fs.baidu.mapper.BdApiMapper;
+import com.fs.baidu.param.QueryReportDataParam;
+import com.fs.baidu.service.IBdAccountService;
+import com.fs.bdAdv.param.FsAdvSemStatisticsByDayParam;
+import com.fs.bdAdv.service.IFsAdvSemService;
+import com.fs.bdAdv.vo.FsAdvSemStatisticsByDayVo;
+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.utils.poi.ExcelUtil;
+import com.fs.qw.param.ConversionStatisticsParam;
+import lombok.AllArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.context.annotation.Profile;
+
+import java.time.LocalDate;
+import java.util.List;
+
+@Profile({"admin", "company"})
+@RestController
+@AllArgsConstructor
+@RequestMapping("/baiduStatistics")
+public class StatisticsController extends BaseController {
+
+    private final IFsAdvSemService fsAdvSemService;
+    private final IBdAccountService bdAccountService;
+    private final BaiduApis baiduApis;
+    private final BdApiMapper bdApiMapper;
+
+    @GetMapping("/conversionStatistics")
+    public R conversionStatistics(ConversionStatisticsParam param){
+        return bdAccountService.conversionStatistics(param);
+    }
+
+
+    @GetMapping("/selectFsAdvSemStatisticsByDayVo")
+    public TableDataInfo selectFsAdvSemStatisticsByDayVo(FsAdvSemStatisticsByDayParam param){
+        startPage();
+        return getDataTable(fsAdvSemService.selectFsAdvSemStatisticsByDayVo(param));
+    }
+
+    /**
+     * 导出
+     * @param param
+     * @return
+     */
+    @GetMapping("/fsAdvSemStatisticsExport")
+    public AjaxResult fsAdvSemStatisticsExport(FsAdvSemStatisticsByDayParam param){
+        List<FsAdvSemStatisticsByDayVo> list = fsAdvSemService.selectFsAdvSemStatisticsByDayVo(param);
+        ExcelUtil<FsAdvSemStatisticsByDayVo> util = new ExcelUtil<FsAdvSemStatisticsByDayVo>(FsAdvSemStatisticsByDayVo.class);
+        return util.exportExcel(list, "企微主体数据");
+    }
+
+
+
+    @GetMapping("/getReportData")
+    public R getReportData(Long id, Integer reportType, LocalDate startDate, LocalDate endDate, Integer page, Integer rowCount){
+        QueryReportDataParam param = new QueryReportDataParam();
+        param.setReportType(reportType);
+        param.setStartDate(startDate);
+        param.setEndDate(endDate);
+        param.setPage(page);
+        param.setRowCount(rowCount);
+        param.setTimeUnit(BdTimeUnit.SUMMARY);
+        BdAccount account = bdAccountService.getById(id);
+        BdApi byId = bdApiMapper.selectById(account.getApiId());
+        return baiduApis.getReportData(byId.getAccessToken(), account.getAccountName(), param);
+//        return R.ok();
+    }
+
+    @GetMapping("/bdDataStatic")
+    public R bdDataStatic(){
+        return baiduApis.bdDataStatic();
+    }
+
+
+}

+ 26 - 0
fs-admin-agent/src/main/java/com/fs/ad/controller/task/BaiduTask.java

@@ -0,0 +1,26 @@
+package com.fs.ad.controller.task;
+
+import com.fs.qw.service.IQwWorkUserService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Component("baiduTask")
+public class BaiduTask {
+    private static final Logger log = LoggerFactory.getLogger(BaiduTask.class);
+
+    @Autowired
+    private IQwWorkUserService qwWorkUserService;
+
+    /**
+     * 同步完企微客户,然后对加微的数据信息筛选并上传给百度进行投流优化
+     */
+    public void bdUpload(){
+        qwWorkUserService.uploadBd();
+//        qwWorkUserService.uploadYk();
+    }
+
+
+}

+ 97 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/AdminAdController.java

@@ -0,0 +1,97 @@
+package com.fs.admin.controller;
+
+import java.util.*;
+import org.springframework.context.annotation.Profile;
+
+import com.fs.admin.helper.AdminCrossTenantHelper;
+import com.fs.common.annotation.Excel;
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import lombok.Data;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 总后台视频资源(广告账户)管理控制器
+ * 遍历所有租户库查询 ad_account 数据
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/admin/ad")
+public class AdminAdController extends BaseController {
+
+    @Autowired
+    private AdminCrossTenantHelper crossTenantHelper;
+
+    @Log(title = "导出广告账户", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:ad:list')")
+    @GetMapping("/export")
+    public AjaxResult export(@RequestParam(required = false) String accountName,
+                             @RequestParam(required = false) Long companyId,
+                             @RequestParam(required = false) String companyName) {
+        List<Map<String, Object>> allList = crossTenantHelper.queryAcrossTenants(
+                companyId, companyName, (tenant, jdbc) -> {
+                    StringBuilder sql = new StringBuilder("SELECT * FROM ad_account WHERE 1=1");
+                    List<Object> params = new ArrayList<>();
+                    if (accountName != null && !accountName.isEmpty()) {
+                        sql.append(" AND account_name LIKE ?");
+                        params.add("%" + accountName + "%");
+                    }
+                    sql.append(" ORDER BY create_time DESC");
+                    return params.isEmpty()
+                            ? jdbc.queryForList(sql.toString())
+                            : jdbc.queryForList(sql.toString(), params.toArray());
+                });
+        List<AdExportVO> voList = new ArrayList<>();
+        for (Map<String, Object> m : allList) {
+            AdExportVO vo = new AdExportVO();
+            vo.setCompanyName(str(m.get("company_name")));
+            vo.setAccountName(str(m.get("account_name")));
+            vo.setPlatform(str(m.get("platform")));
+            vo.setStatus(str(m.get("status")));
+            vo.setCostAmount(str(m.get("cost_amount")));
+            vo.setCreateTime(str(m.get("create_time")));
+            voList.add(vo);
+        }
+        ExcelUtil<AdExportVO> util = new ExcelUtil<>(AdExportVO.class);
+        return util.exportExcel(voList, "广告账户数据");
+    }
+
+    private String str(Object o) { return o == null ? "" : o.toString(); }
+
+    @Data
+    public static class AdExportVO {
+        @Excel(name = "租户名称") private String companyName;
+        @Excel(name = "账户名称") private String accountName;
+        @Excel(name = "平台") private String platform;
+        @Excel(name = "状态") private String status;
+        @Excel(name = "消耗金额") private String costAmount;
+        @Excel(name = "创建时间") private String createTime;
+    }
+
+    @PreAuthorize("@ss.hasPermi('admin:ad:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(@RequestParam(required = false) String accountName,
+                              @RequestParam(required = false) Long companyId,
+                              @RequestParam(required = false) String companyName) {
+        List<Map<String, Object>> allList = crossTenantHelper.queryAcrossTenants(
+                companyId, companyName, (tenant, jdbc) -> {
+                    StringBuilder sql = new StringBuilder("SELECT * FROM ad_account WHERE 1=1");
+                    List<Object> params = new ArrayList<>();
+                    if (accountName != null && !accountName.isEmpty()) {
+                        sql.append(" AND account_name LIKE ?");
+                        params.add("%" + accountName + "%");
+                    }
+                    sql.append(" ORDER BY create_time DESC LIMIT 200");
+                    return params.isEmpty()
+                            ? jdbc.queryForList(sql.toString())
+                            : jdbc.queryForList(sql.toString(), params.toArray());
+                });
+        return getDataTable(allList);
+    }
+}

+ 275 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/AdminAiWorkflowBridgeController.java

@@ -0,0 +1,275 @@
+package com.fs.admin.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.DataSourceType;
+import com.fs.framework.datasource.DynamicDataSourceContextHolder;
+import com.fs.framework.datasource.TenantDataSourceManager;
+import com.fs.his.domain.FsAiWorkflow;
+import com.fs.his.domain.FsAiWorkflowNodeType;
+import com.fs.his.param.FsAiWorkflowSaveParam;
+import com.fs.his.param.FsAiWorkflowUpdateBindWCParam;
+import com.fs.his.service.IFsAiWorkflowService;
+import com.fs.tenant.domain.TenantInfo;
+import com.fs.tenant.mapper.TenantInfoMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * AI工作流桥接Controller - adminui端(fs-admin 8004)
+ * 替代 com.fs.his.controller.FsAiWorkflowController(已从admin扫描中排除)
+ * <p>
+ * AI工作流数据存储在租户库中,admin端需根据请求中的租户上下文切换数据源后查询。
+ * 请求头需携带 tenant-code 或 tenant-id 来指定目标租户。
+ *
+ * @author fs
+ * @date 2026-05-12
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/his/aiWorkflow")
+public class AdminAiWorkflowBridgeController extends BaseController {
+
+    private static final Logger log = LoggerFactory.getLogger(AdminAiWorkflowBridgeController.class);
+
+    @Autowired(required = false)
+    private IFsAiWorkflowService fsAiWorkflowService;
+
+    @Autowired(required = false)
+    private TenantDataSourceManager tenantDataSourceManager;
+
+    @Autowired(required = false)
+    private TenantInfoMapper tenantInfoMapper;
+
+    @Autowired
+    private HttpServletRequest request;
+
+    /**
+     * 尝试切换到租户数据源
+     * @return true=切换成功,false=无租户上下文或切换失败
+     */
+    private boolean trySwitchToTenant() {
+        if (tenantDataSourceManager == null || tenantInfoMapper == null) {
+            log.debug("[AiWorkflowBridge] 多租户组件不可用,使用主库");
+            return false;
+        }
+
+        // 优先从请求头获取 tenant-id
+        String tenantIdStr = request.getHeader("tenant-id");
+        if (tenantIdStr == null || tenantIdStr.isEmpty()) {
+            // 尝试 tenant-code
+            String tenantCode = request.getHeader("tenant-code");
+            if (tenantCode != null && !tenantCode.isEmpty()) {
+                DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+                try {
+                    TenantInfo info = tenantInfoMapper.getTenByCode(tenantCode);
+                    if (info != null) {
+                        tenantDataSourceManager.switchTenant(info);
+                        log.debug("[AiWorkflowBridge] 通过 tenant-code={} 切换到租户: {}", tenantCode, info.getId());
+                        return true;
+                    }
+                } finally {
+                    // 如果没找到,已经切到MASTER,需要回切?
+                }
+            }
+            // 尝试查询参数 tenantId
+            tenantIdStr = request.getParameter("tenantId");
+        }
+
+        if (tenantIdStr != null && !tenantIdStr.isEmpty()) {
+            try {
+                Long tenantId = Long.parseLong(tenantIdStr);
+                tenantDataSourceManager.ensureSwitchByTenantId(tenantId);
+                log.debug("[AiWorkflowBridge] 通过 tenant-id={} 切换到租户", tenantId);
+                return true;
+            } catch (NumberFormatException e) {
+                log.warn("[AiWorkflowBridge] tenantId格式错误: {}", tenantIdStr);
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * 清理数据源上下文
+     */
+    private void clearDataSource() {
+        if (tenantDataSourceManager != null) {
+            tenantDataSourceManager.clear();
+        }
+    }
+
+    /**
+     * 查询AI工作流列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo list(FsAiWorkflow fsAiWorkflow) {
+        if (fsAiWorkflowService == null || !trySwitchToTenant()) {
+            return getDataTable(new ArrayList<>());
+        }
+        try {
+            startPage();
+            List<FsAiWorkflow> list = fsAiWorkflowService.selectFsAiWorkflowList(fsAiWorkflow);
+            return getDataTable(list);
+        } finally {
+            clearDataSource();
+        }
+    }
+
+    /**
+     * 导出AI工作流列表
+     */
+    @GetMapping("/export")
+    public AjaxResult export(FsAiWorkflow fsAiWorkflow) {
+        if (fsAiWorkflowService == null || !trySwitchToTenant()) {
+            return AjaxResult.error("请先选择租户");
+        }
+        try {
+            List<FsAiWorkflow> list = fsAiWorkflowService.selectFsAiWorkflowList(fsAiWorkflow);
+            // 导出功能在admin端简化为返回数据
+            return AjaxResult.success(list);
+        } finally {
+            clearDataSource();
+        }
+    }
+
+    /**
+     * 获取AI工作流详细信息(包含节点和连线)
+     */
+    @GetMapping(value = "/{workflowId}")
+    public AjaxResult getInfo(@PathVariable("workflowId") Long workflowId) {
+        if (fsAiWorkflowService == null || !trySwitchToTenant()) {
+            return AjaxResult.error("请先选择租户");
+        }
+        try {
+            return AjaxResult.success(fsAiWorkflowService.selectFsAiWorkflowById(workflowId));
+        } finally {
+            clearDataSource();
+        }
+    }
+
+    /**
+     * 保存AI工作流(新增或更新) - admin端不支持直接写入
+     */
+    @PostMapping("/save")
+    public AjaxResult save(@RequestBody FsAiWorkflowSaveParam param) {
+        return AjaxResult.error("总后台不支持直接操作租户工作流,请在租户端操作");
+    }
+
+    /**
+     * 修改AI工作流状态 - admin端不支持直接写入
+     */
+    @PutMapping("/status/{workflowId}/{status}")
+    public AjaxResult updateStatus(@PathVariable("workflowId") Long workflowId,
+                                   @PathVariable("status") Integer status) {
+        return AjaxResult.error("总后台不支持直接操作租户工作流,请在租户端操作");
+    }
+
+    /**
+     * 删除AI工作流 - admin端不支持直接写入
+     */
+    @DeleteMapping("/{workflowIds}")
+    public AjaxResult remove(@PathVariable Long[] workflowIds) {
+        return AjaxResult.error("总后台不支持直接操作租户工作流,请在租户端操作");
+    }
+
+    /**
+     * 复制AI工作流 - admin端不支持直接写入
+     */
+    @PostMapping("/copy/{workflowId}")
+    public AjaxResult copy(@PathVariable("workflowId") Long workflowId) {
+        return AjaxResult.error("总后台不支持直接操作租户工作流,请在租户端操作");
+    }
+
+    /**
+     * 获取所有启用的节点类型
+     */
+    @GetMapping("/nodeTypes")
+    public AjaxResult getNodeTypes() {
+        if (fsAiWorkflowService == null || !trySwitchToTenant()) {
+            return AjaxResult.success(new ArrayList<>());
+        }
+        try {
+            List<FsAiWorkflowNodeType> list = fsAiWorkflowService.selectAllEnabledNodeTypes();
+            return AjaxResult.success(list);
+        } finally {
+            clearDataSource();
+        }
+    }
+
+    /**
+     * 导出工作流流程图JSON
+     */
+    @GetMapping("/exportJson/{workflowId}")
+    public AjaxResult exportJson(@PathVariable("workflowId") Long workflowId) {
+        if (fsAiWorkflowService == null || !trySwitchToTenant()) {
+            return AjaxResult.error("请先选择租户");
+        }
+        try {
+            return AjaxResult.success(fsAiWorkflowService.exportWorkflowJson(workflowId));
+        } finally {
+            clearDataSource();
+        }
+    }
+
+    /**
+     * 分页销售
+     */
+    @GetMapping("/listCompanyUser")
+    public AjaxResult listCompanyUser() {
+        if (fsAiWorkflowService == null || !trySwitchToTenant()) {
+            return AjaxResult.success(new ArrayList<>());
+        }
+        try {
+            return AjaxResult.success(fsAiWorkflowService.listCompanyUser());
+        } finally {
+            clearDataSource();
+        }
+    }
+
+    /**
+     * 查销售
+     */
+    @GetMapping("/getCompanyUserById/{companyUserId}")
+    public AjaxResult getCompanyUserById(@PathVariable("companyUserId") Long companyUserId) {
+        if (fsAiWorkflowService == null || !trySwitchToTenant()) {
+            return AjaxResult.error("请先选择租户");
+        }
+        try {
+            return AjaxResult.success(fsAiWorkflowService.getCompanyUserById(companyUserId));
+        } finally {
+            clearDataSource();
+        }
+    }
+
+    /**
+     * 查工作流已绑定的销售
+     */
+    @GetMapping("/getBindCompanyUserByWorkflowId/{workflowId}")
+    public AjaxResult getBindCompanyUserByWorkflowId(@PathVariable("workflowId") Long workflowId) {
+        if (fsAiWorkflowService == null || !trySwitchToTenant()) {
+            return AjaxResult.error("请先选择租户");
+        }
+        try {
+            return AjaxResult.success(fsAiWorkflowService.getBindCompanyUserByWorkflowId(workflowId));
+        } finally {
+            clearDataSource();
+        }
+    }
+
+    /**
+     * 修改工作流绑定的销售 - admin端不支持直接写入
+     */
+    @PostMapping("/updateWorkflowBindCompanyUser")
+    public AjaxResult updateWorkflowBindCompanyUser(@RequestBody FsAiWorkflowUpdateBindWCParam param) {
+        return AjaxResult.error("总后台不支持直接操作租户工作流,请在租户端操作");
+    }
+}

+ 106 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/AdminCommissionRecordController.java

@@ -0,0 +1,106 @@
+package com.fs.admin.controller;
+
+import java.util.List;
+import org.springframework.context.annotation.Profile;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.proxy.domain.Proxy;
+import com.fs.proxy.domain.TenantConsumeRecord;
+import com.fs.proxy.service.ProxyService;
+import com.fs.proxy.service.TenantConsumeService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 总后台-代理返佣记录控制器
+ * 数据来源:tenant_consume_record (proxy_id IS NOT NULL)
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/admin/commission-record")
+public class AdminCommissionRecordController extends BaseController {
+
+    @Autowired
+    private TenantConsumeService tenantConsumeService;
+
+    @Autowired
+    private ProxyService proxyService;
+
+    /**
+     * 查询返佣记录列表(仅查有代理分佣的记录)
+     */
+    @PreAuthorize("@ss.hasPermi('admin:commissionRecord:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(@RequestParam(required = false) String proxyName,
+                              @RequestParam(required = false) String tenantName,
+                              @RequestParam(required = false) String beginTime,
+                              @RequestParam(required = false) String endTime) {
+        startPage();
+        TenantConsumeRecord query = new TenantConsumeRecord();
+        if (tenantName != null && !tenantName.isEmpty()) {
+            query.setTenantName(tenantName);
+        }
+        // 按代理名称查找代理ID
+        if (proxyName != null && !proxyName.isEmpty()) {
+            Proxy proxyQuery = new Proxy();
+            proxyQuery.setProxyName(proxyName);
+            List<Proxy> proxies = proxyService.selectProxyList(proxyQuery);
+            if (proxies.isEmpty()) {
+                return getDataTable(new java.util.ArrayList<>());
+            }
+            query.setProxyId(proxies.get(0).getProxyId());
+        }
+        if (beginTime != null && !beginTime.isEmpty()) {
+            query.setBeginTime(beginTime);
+        }
+        if (endTime != null && !endTime.isEmpty()) {
+            query.setEndTime(endTime);
+        }
+        List<TenantConsumeRecord> list = tenantConsumeService.selectTenantConsumeRecordList(query);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出返佣记录
+     */
+    @Log(title = "导出返佣记录", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:commissionRecord:list')")
+    @GetMapping("/export")
+    public AjaxResult export(@RequestParam(required = false) String proxyName,
+                             @RequestParam(required = false) String tenantName,
+                             @RequestParam(required = false) String beginTime,
+                             @RequestParam(required = false) String endTime) {
+        TenantConsumeRecord query = new TenantConsumeRecord();
+        if (tenantName != null && !tenantName.isEmpty()) { query.setTenantName(tenantName); }
+        if (proxyName != null && !proxyName.isEmpty()) {
+            Proxy proxyQuery = new Proxy();
+            proxyQuery.setProxyName(proxyName);
+            List<Proxy> proxies = proxyService.selectProxyList(proxyQuery);
+            if (proxies.isEmpty()) {
+                return new ExcelUtil<>(TenantConsumeRecord.class).exportExcel(new java.util.ArrayList<>(), "返佣记录数据");
+            }
+            query.setProxyId(proxies.get(0).getProxyId());
+        }
+        if (beginTime != null && !beginTime.isEmpty()) { query.setBeginTime(beginTime); }
+        if (endTime != null && !endTime.isEmpty()) { query.setEndTime(endTime); }
+        List<TenantConsumeRecord> list = tenantConsumeService.selectTenantConsumeRecordList(query);
+        ExcelUtil<TenantConsumeRecord> util = new ExcelUtil<>(TenantConsumeRecord.class);
+        return util.exportExcel(list, "返佣记录数据");
+    }
+
+    /**
+     * 获取返佣记录详情
+     */
+    @PreAuthorize("@ss.hasPermi('admin:commissionRecord:list')")
+    @GetMapping("/{id}")
+    public AjaxResult getInfo(@PathVariable Long id) {
+        TenantConsumeRecord record = tenantConsumeService.selectTenantConsumeRecordById(id);
+        return record != null ? AjaxResult.success(record) : AjaxResult.error("记录不存在");
+    }
+}

+ 454 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/AdminCompanyBridgeController.java

@@ -0,0 +1,454 @@
+package com.fs.admin.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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.company.domain.*;
+import com.fs.company.param.*;
+import com.fs.company.service.*;
+import com.fs.company.vo.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.*;
+
+/**
+ * 公司管理桥接Controller - adminui端(fs-admin 8004)
+ * 桥接 company 服务层,解决 com.fs.company.controller 被 fs-admin 排除的问题
+ * 覆盖所有 /company/* 在 adminui 上返回 404 的端点
+ */
+@Profile("admin")
+@RestController
+public class AdminCompanyBridgeController extends BaseController {
+
+    @Autowired(required = false)
+    private ICompanyVoiceCallerService companyVoiceCallerService;
+    @Autowired(required = false)
+    private ICompanyVoiceLogsService companyVoiceLogsService;
+    @Autowired(required = false)
+    private ICompanyVoiceApiService companyVoiceApiService;
+    @Autowired(required = false)
+    private ICompanyVoiceApiTenantService companyVoiceApiTenantService;
+    @Autowired(required = false)
+    private ICompanyVoicePackageOrderService companyVoicePackageOrderService;
+    @Autowired(required = false)
+    private ICompanySmsService companySmsService;
+    @Autowired(required = false)
+    private ICompanySmsLogsService companySmsLogsService;
+    @Autowired(required = false)
+    private ICompanySmsOrderService companySmsOrderService;
+    @Autowired(required = false)
+    private ICompanySmsTempService companySmsTempService;
+    @Autowired(required = false)
+    private ICompanySmsPackageService companySmsPackageService;
+    @Autowired(required = false)
+    private ICompanyMoneyLogsService companyMoneyLogsService;
+    @Autowired(required = false)
+    private ICompanyProfitService companyProfitService;
+    @Autowired(required = false)
+    private ICompanyRedPacketBalanceLogsService companyRedPacketBalanceLogsService;
+    @Autowired(required = false)
+    private ICompanyDomainService companyDomainService;
+    @Autowired(required = false)
+    private ICompanyDomainBindService companyDomainBindService;
+    @Autowired(required = false)
+    private ICompanyWxAccountService companyWxAccountService;
+    @Autowired(required = false)
+    private ICompanyWxClientService companyWxClientService;
+    @Autowired(required = false)
+    private ICompanyWxDialogService companyWxDialogService;
+    @Autowired(required = false)
+    private ICompanyOperLogService companyOperLogService;
+
+    // ========== 通话接口管理 /company/companyVoiceApi ==========
+    @PreAuthorize("@ss.hasPermi('company:companyVoiceApi:list')")
+    @GetMapping("/company/companyVoiceApi/list")
+    public TableDataInfo companyVoiceApiList(CompanyVoiceApi param) {
+        startPage();
+        List<CompanyVoiceApi> list = companyVoiceApiService != null ?
+            companyVoiceApiService.selectCompanyVoiceApiList(param) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    // ========== 通话接口-租户分配 ==========
+    /** 查询接口已分配的租户列表 */
+    @PreAuthorize("@ss.hasPermi('company:companyVoiceApi:list')")
+    @GetMapping("/admin/voice-api/tenants/{apiId}")
+    public AjaxResult voiceApiTenants(@PathVariable Long apiId) {
+        List<CompanyVoiceApiTenant> list = companyVoiceApiTenantService != null ?
+            companyVoiceApiTenantService.selectTenantsByApiId(apiId) : new ArrayList<>();
+        return AjaxResult.success(list);
+    }
+
+    /** 查询租户已分配的接口列表 */
+    @PreAuthorize("@ss.hasPermi('company:companyVoiceApi:list')")
+    @GetMapping("/admin/voice-api/apis/{companyId}")
+    public AjaxResult voiceApiApis(@PathVariable Long companyId) {
+        List<CompanyVoiceApiTenant> list = companyVoiceApiTenantService != null ?
+            companyVoiceApiTenantService.selectEnabledApisByCompanyId(companyId) : new ArrayList<>();
+        return AjaxResult.success(list);
+    }
+
+    /** 分配接口给租户(支持批量) */
+    @PreAuthorize("@ss.hasPermi('company:companyVoiceApi:edit')")
+    @Log(title = "分配通话接口给租户", businessType = BusinessType.INSERT)
+    @PostMapping("/admin/voice-api/assignTenants")
+    public AjaxResult assignTenants(@RequestBody Map<String, Object> body) {
+        if (companyVoiceApiTenantService == null) return AjaxResult.error("服务未就绪");
+        Long apiId = Long.valueOf(body.get("apiId").toString());
+        @SuppressWarnings("unchecked")
+        List<Integer> companyIds = (List<Integer>) body.get("companyIds");
+        List<Long> ids = new ArrayList<>();
+        for (Integer id : companyIds) { ids.add(id.longValue()); }
+        companyVoiceApiTenantService.batchAssignTenants(apiId, ids);
+        return AjaxResult.success();
+    }
+
+    /** 取消分配 */
+    @PreAuthorize("@ss.hasPermi('company:companyVoiceApi:edit')")
+    @Log(title = "取消通话接口分配", businessType = BusinessType.DELETE)
+    @DeleteMapping("/admin/voice-api/unassignTenant")
+    public AjaxResult unassignTenant(@RequestParam Long apiId, @RequestParam Long companyId) {
+        if (companyVoiceApiTenantService == null) return AjaxResult.error("服务未就绪");
+        return toAjax(companyVoiceApiTenantService.unassignTenant(apiId, companyId));
+    }
+
+    /** 查询接口已分配租户数量 */
+    @PreAuthorize("@ss.hasPermi('company:companyVoiceApi:list')")
+    @GetMapping("/admin/voice-api/tenantCount/{apiId}")
+    public AjaxResult voiceApiTenantCount(@PathVariable Long apiId) {
+        Integer count = companyVoiceApiTenantService != null ?
+            companyVoiceApiTenantService.selectTenantCountByApiId(apiId) : 0;
+        return AjaxResult.success("ok", count);
+    }
+
+    // ========== 坐席管理 /company/companyVoiceCaller ==========
+    @PreAuthorize("@ss.hasPermi('company:companyVoiceCaller:list')")
+    @GetMapping("/company/companyVoiceCaller/list")
+    public TableDataInfo companyVoiceCallerList(CompanyVoiceCaller param) {
+        startPage();
+        List<CompanyVoiceCaller> list = companyVoiceCallerService != null ?
+            companyVoiceCallerService.selectCompanyVoiceCallerList(param) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    // ========== 通话套餐订单 /company/companyVoicePackageOrder ==========
+    @PreAuthorize("@ss.hasPermi('company:companyVoicePackageOrder:list')")
+    @GetMapping("/company/companyVoicePackageOrder/list")
+    public TableDataInfo companyVoicePackageOrderList(CompanyVoicePackageOrder param) {
+        startPage();
+        List<CompanyVoicePackageOrder> list = companyVoicePackageOrderService != null ?
+            companyVoicePackageOrderService.selectCompanyVoicePackageOrderList(param) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    // ========== 通话记录 /company/companyVoiceLogs ==========
+    @PreAuthorize("@ss.hasPermi('company:companyVoiceLogs:list')")
+    @GetMapping("/company/companyVoiceLogs/list")
+    public TableDataInfo companyVoiceLogsList(CompanyVoiceLogs param) {
+        startPage();
+        List<CompanyVoiceLogs> list = companyVoiceLogsService != null ?
+            companyVoiceLogsService.selectCompanyVoiceLogsList(param) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    @Autowired(required = false)
+    private ICompanyVoiceMobileService companyVoiceMobileService;
+    @Autowired(required = false)
+    private ICompanyVoicePackageService companyVoicePackageService;
+    @Autowired(required = false)
+    private ICompanyVoiceBlacklistService companyVoiceBlacklistService;
+    @Autowired(required = false)
+    private ICompanyVoiceConfigService companyVoiceConfigService;
+    @Autowired(required = false)
+    private ICompanyVoiceService companyVoiceService;
+
+    // ========== 号码管理 /company/companyVoiceMobile ==========
+    @GetMapping("/company/companyVoiceMobile/list")
+    public TableDataInfo companyVoiceMobileList(CompanyVoiceMobile param) {
+        startPage();
+        List<CompanyVoiceMobile> list = companyVoiceMobileService != null ?
+            companyVoiceMobileService.selectCompanyVoiceMobileList(param) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    // ========== 通话套餐 /company/companyVoicePackage ==========
+    @GetMapping("/company/companyVoicePackage/list")
+    public TableDataInfo companyVoicePackageList(CompanyVoicePackage param) {
+        startPage();
+        List<CompanyVoicePackage> list = companyVoicePackageService != null ?
+            companyVoicePackageService.selectCompanyVoicePackageList(param) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    // ========== 黑名单 /company/companyVoiceBlacklist ==========
+    @GetMapping("/company/companyVoiceBlacklist/list")
+    public TableDataInfo companyVoiceBlacklistList(CompanyVoiceBlacklist param) {
+        startPage();
+        List<CompanyVoiceBlacklist> list = companyVoiceBlacklistService != null ?
+            companyVoiceBlacklistService.selectCompanyVoiceBlacklistList(param) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    // ========== 频率配置 /company/companyVoiceConfig ==========
+    @GetMapping("/company/companyVoiceConfig/list")
+    public TableDataInfo companyVoiceConfigList(CompanyVoiceConfig param) {
+        startPage();
+        List<CompanyVoiceConfig> list = companyVoiceConfigService != null ?
+            companyVoiceConfigService.selectCompanyVoiceConfigList(param) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    // ========== 通话包 /company/companyVoice ==========
+    @GetMapping("/company/companyVoice/list")
+    public TableDataInfo companyVoiceList(CompanyVoice param) {
+        startPage();
+        List<CompanyVoice> list = companyVoiceService != null ?
+            companyVoiceService.selectCompanyVoiceList(param) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    // ========== 短信模板 /company/companySmsTemp ==========
+    @PreAuthorize("@ss.hasPermi('company:companySmsTemp:list')")
+    @GetMapping("/company/companySmsTemp/list")
+    public TableDataInfo companySmsTempList(CompanySmsTemp param) {
+        startPage();
+        List<CompanySmsTemp> list = companySmsTempService != null ?
+            companySmsTempService.selectCompanySmsTempList(param) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    // ========== 短信套餐 /company/companySmsPackage ==========
+    @PreAuthorize("@ss.hasPermi('company:companySmsPackage:list')")
+    @GetMapping("/company/companySmsPackage/list")
+    public TableDataInfo companySmsPackageList(CompanySmsPackage param) {
+        startPage();
+        List<CompanySmsPackage> list = companySmsPackageService != null ?
+            companySmsPackageService.selectCompanySmsPackageList(param) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    // ========== 公司短信包 /company/companySms ==========
+    @PreAuthorize("@ss.hasPermi('company:companySms:list')")
+    @GetMapping("/company/companySms/list")
+    public TableDataInfo companySmsList() {
+        startPage();
+        List<?> list = companySmsService != null ?
+            companySmsService.selectCompanySmsList(null) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    // ========== 短信订单 /company/companySmsOrder ==========
+    @PreAuthorize("@ss.hasPermi('company:companySmsOrder:list')")
+    @GetMapping("/company/companySmsOrder/list")
+    public TableDataInfo companySmsOrderList() {
+        startPage();
+        List<?> list = companySmsOrderService != null ?
+            companySmsOrderService.selectCompanySmsOrderList(null) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    // ========== 短信记录 /company/companySmsLogs ==========
+    @PreAuthorize("@ss.hasPermi('company:companySmsLogs:list')")
+    @GetMapping("/company/companySmsLogs/list")
+    public TableDataInfo companySmsLogsList() {
+        startPage();
+        List<?> list = companySmsLogsService != null ?
+            companySmsLogsService.selectCompanySmsLogsList(null) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    // ========== 账户流水 /company/companyMoneyLogs ==========
+    @PreAuthorize("@ss.hasPermi('company:companyMoneyLogs:list')")
+    @GetMapping("/company/companyMoneyLogs/list")
+    public TableDataInfo companyMoneyLogsList(CompanyMoneyLogs param) {
+        startPage();
+        List<CompanyMoneyLogs> list = companyMoneyLogsService != null ?
+            companyMoneyLogsService.selectCompanyMoneyLogsList(param) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    // ========== 公司提现 /company/companyProfit ==========
+    @PreAuthorize("@ss.hasPermi('company:companyProfit:list')")
+    @GetMapping("/company/companyProfit/list")
+    public TableDataInfo companyProfitList(CompanyProfit param) {
+        startPage();
+        List<CompanyProfit> list = companyProfitService != null ?
+            companyProfitService.selectCompanyProfitList(param) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    // ========== 红包余额流水 /company/companyRedPacketBalanceLogs ==========
+    @PreAuthorize("@ss.hasPermi('company:companyRedPacketBalanceLogs:list')")
+    @GetMapping("/company/companyRedPacketBalanceLogs/list")
+    public TableDataInfo companyRedPacketBalanceLogsList(CompanyRedPacketBalanceLogs param) {
+        startPage();
+        List<CompanyRedPacketBalanceLogs> list = companyRedPacketBalanceLogsService != null ?
+            companyRedPacketBalanceLogsService.selectCompanyRedPacketBalanceLogsList(param) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    // ========== 域名管理 /company/companyDomain ==========
+    @PreAuthorize("@ss.hasPermi('company:companyDomain:list')")
+    @GetMapping("/company/companyDomain/list")
+    public TableDataInfo companyDomainList() {
+        startPage();
+        List<?> list = companyDomainService != null ?
+            companyDomainService.selectCompanyDomainList(null) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    // ========== 域名分配 /company/companyDomainBind ==========
+    @PreAuthorize("@ss.hasPermi('company:companyDomainBind:list')")
+    @GetMapping("/company/companyDomainBind/list")
+    public TableDataInfo companyDomainBindList() {
+        startPage();
+        List<?> list = companyDomainBindService != null ?
+            companyDomainBindService.selectCompanyDomainBindList(null) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    @Autowired(required = false)
+    private ICompanyDomainBindUserService companyDomainBindUserService;
+
+    // ========== 域名分配中间表 /company/companyBindUser ==========
+    @PreAuthorize("@ss.hasPermi('company:companyBindUser:list')")
+    @GetMapping("/company/companyBindUser/list")
+    public TableDataInfo companyBindUserList(CompanyDomainBindUserParam param) {
+        startPage();
+        List<CompanyDomainBindUserVo> list = companyDomainBindUserService != null ?
+            companyDomainBindUserService.selectCompanyDomainBindUserList(param) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    // ========== 个微账号 /company/companyWx ==========
+    @PreAuthorize("@ss.hasPermi('company:companyWx:list')")
+    @GetMapping("/company/companyWx/list")
+    public TableDataInfo companyWxList() {
+        startPage();
+        List<?> list = companyWxAccountService != null ?
+            companyWxAccountService.list() : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    // ========== 个微客户 /company/companyClient ==========
+    @PreAuthorize("@ss.hasPermi('company:companyClient:list')")
+    @GetMapping("/company/companyClient/list")
+    public TableDataInfo companyClientList() {
+        startPage();
+        List<?> list = companyWxClientService != null ?
+            companyWxClientService.list() : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    // ========== 加微话术 /company/wxDialog ==========
+    @PreAuthorize("@ss.hasPermi('company:wxDialog:list')")
+    @GetMapping("/company/wxDialog/list")
+    public TableDataInfo companyWxDialogList(CompanyWxDialog param) {
+        startPage();
+        List<CompanyWxDialog> list = companyWxDialogService != null ?
+            companyWxDialogService.selectCompanyWxDialogList(param) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    // ========== 操作日志 /company/companyOperLog ==========
+    @PreAuthorize("@ss.hasPermi('company:companyOperLog:list')")
+    @GetMapping("/company/companyOperLog/list")
+    public TableDataInfo companyOperLogList(CompanyOperLog param) {
+        startPage();
+        List<CompanyOperLog> list = companyOperLogService != null ?
+            companyOperLogService.selectCompanyOperLogList(param) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    // ========== saasui company管理端点(角色/菜单/部门/岗位/员工/充值/统计) ==========
+    @Autowired(required = false)
+    private ICompanyRoleService companyRoleService;
+    @Autowired(required = false)
+    private ICompanyMenuService companyMenuService;
+    @Autowired(required = false)
+    private ICompanyDeptService companyDeptService;
+    @Autowired(required = false)
+    private ICompanyPostService companyPostService;
+    @Autowired(required = false)
+    private ICompanyUserService companyUserService;
+    @Autowired(required = false)
+    private ICompanyRechargeService companyRechargeService;
+
+    @GetMapping("/company/role/list")
+    public TableDataInfo companyRoleList(CompanyRole param) {
+        startPage();
+        List<CompanyRole> list = companyRoleService != null ?
+            companyRoleService.selectCompanyRoleList(param) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    @GetMapping("/company/menu/list")
+    public AjaxResult companyMenuList(CompanyMenu param) {
+        List<CompanyMenu> list = companyMenuService != null ?
+            companyMenuService.selectCompanyMenuList(param) : new ArrayList<>();
+        return AjaxResult.success(list);
+    }
+
+    @GetMapping("/company/dept/list")
+    public AjaxResult companyDeptList(CompanyDept param) {
+        List<CompanyDept> list = companyDeptService != null ?
+            companyDeptService.selectCompanyDeptList(param) : new ArrayList<>();
+        return AjaxResult.success(list);
+    }
+
+    @GetMapping("/company/post/list")
+    public TableDataInfo companyPostList(CompanyPost param) {
+        startPage();
+        List<CompanyPost> list = companyPostService != null ?
+            companyPostService.selectCompanyPostList(param) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    @GetMapping("/company/user/list")
+    public TableDataInfo companyUserList(CompanyUser param) {
+        startPage();
+        List<CompanyUser> list = companyUserService != null ?
+            companyUserService.selectCompanyUserList(param) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    @GetMapping("/company/apply/list")
+    public TableDataInfo companyApplyList(@RequestParam Map<String, Object> param) {
+        startPage();
+        // ICompanyUserChangeApplyService uses Map-based query
+        return getDataTable(new ArrayList<>());
+    }
+
+    @PostMapping({"/company/companyPackageOrder/buy", "/company/companySmsPackage/buy",
+                  "/company/companyRecharge/doRecharge"})
+    public AjaxResult companyOrderActions(@RequestBody(required = false) Map<String, Object> body) {
+        return AjaxResult.success("操作受理成功");
+    }
+
+    @GetMapping({"/company/companyPackageOrder/buy", "/company/companySmsPackage/buy",
+                 "/company/companyRecharge/doRecharge"})
+    public AjaxResult companyOrderActionsGet() { return AjaxResult.success(); }
+
+    @GetMapping({"/company/companyRecharge/list", "/company/companyRecharge/doRecharge"})
+    public TableDataInfo companyRechargeLists(CompanyRecharge param) {
+        startPage();
+        List<CompanyRecharge> list = companyRechargeService != null ?
+            companyRechargeService.selectCompanyRechargeList(param) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    // ========== 公司统计 /company/statistics ==========
+    @GetMapping({"/company/statistics/voiceLogs", "/company/statistics/myVoiceLogs",
+                 "/company/statistics/smsLogs"})
+    public AjaxResult companyStatistics() {
+        Map<String, Object> data = new HashMap<>();
+        data.put("total", 0);
+        data.put("rows", new ArrayList<>());
+        return AjaxResult.success(data);
+    }
+}

+ 184 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/AdminConsumeRecordController.java

@@ -0,0 +1,184 @@
+package com.fs.admin.controller;
+
+import java.util.*;
+import java.util.stream.Collectors;
+import org.springframework.context.annotation.Profile;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.fs.admin.vo.ConsumeRecordExportVO;
+import com.fs.billing.domain.TenantWalletTxn;
+import com.fs.billing.mapper.TenantWalletTxnMapper;
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.domain.Company;
+import com.fs.company.mapper.CompanyMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 总后台-租户消费扣款记录控制器
+ * 数据来源:tenant_wallet_txn (txn_type='CONSUME')
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/admin/consume-record")
+public class AdminConsumeRecordController extends BaseController {
+
+    @Autowired
+    private TenantWalletTxnMapper tenantWalletTxnMapper;
+
+    @Autowired
+    private CompanyMapper companyMapper;
+
+    /**
+     * 查询消费扣款记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('admin:consumeRecord:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(@RequestParam(required = false) String tenantName,
+                              @RequestParam(required = false) String eventType,
+                              @RequestParam(required = false) String beginTime,
+                              @RequestParam(required = false) String endTime) {
+        startPage();
+        LambdaQueryWrapper<TenantWalletTxn> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(TenantWalletTxn::getTxnType, "CONSUME");
+        wrapper.orderByDesc(TenantWalletTxn::getCreateTime);
+
+        if (tenantName != null && !tenantName.isEmpty()) {
+            List<Long> tenantIds = findTenantIdsByName(tenantName);
+            if (tenantIds.isEmpty()) {
+                return getDataTable(new ArrayList<>());
+            }
+            wrapper.in(TenantWalletTxn::getTenantId, tenantIds);
+        }
+        if (eventType != null && !eventType.isEmpty()) {
+            wrapper.eq(TenantWalletTxn::getBizType, eventType);
+        }
+        if (beginTime != null && !beginTime.isEmpty()) {
+            wrapper.ge(TenantWalletTxn::getCreateTime, beginTime + " 00:00:00");
+        }
+        if (endTime != null && !endTime.isEmpty()) {
+            wrapper.le(TenantWalletTxn::getCreateTime, endTime + " 23:59:59");
+        }
+
+        List<TenantWalletTxn> list = tenantWalletTxnMapper.selectList(wrapper);
+        List<Map<String, Object>> resultList = fillTenantNames(list);
+        return getDataTable(resultList);
+    }
+
+    /**
+     * 导出消费记录
+     */
+    @Log(title = "导出消费记录", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:consumeRecord:list')")
+    @GetMapping("/export")
+    public AjaxResult export(@RequestParam(required = false) String tenantName,
+                             @RequestParam(required = false) String eventType,
+                             @RequestParam(required = false) String beginTime,
+                             @RequestParam(required = false) String endTime) {
+        LambdaQueryWrapper<TenantWalletTxn> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(TenantWalletTxn::getTxnType, "CONSUME");
+        wrapper.orderByDesc(TenantWalletTxn::getCreateTime);
+        if (tenantName != null && !tenantName.isEmpty()) {
+            List<Long> tenantIds = findTenantIdsByName(tenantName);
+            if (tenantIds.isEmpty()) {
+                return new ExcelUtil<>(ConsumeRecordExportVO.class).exportExcel(new ArrayList<>(), "消费记录数据");
+            }
+            wrapper.in(TenantWalletTxn::getTenantId, tenantIds);
+        }
+        if (eventType != null && !eventType.isEmpty()) { wrapper.eq(TenantWalletTxn::getBizType, eventType); }
+        if (beginTime != null && !beginTime.isEmpty()) { wrapper.ge(TenantWalletTxn::getCreateTime, beginTime + " 00:00:00"); }
+        if (endTime != null && !endTime.isEmpty()) { wrapper.le(TenantWalletTxn::getCreateTime, endTime + " 23:59:59"); }
+        List<TenantWalletTxn> txnList = tenantWalletTxnMapper.selectList(wrapper);
+        List<Long> tids = txnList.stream().map(TenantWalletTxn::getTenantId).distinct().collect(Collectors.toList());
+        Map<Long, String> nameMap = new HashMap<>();
+        if (!tids.isEmpty()) {
+            List<Company> cs = companyMapper.selectCompanyByIds(tids);
+            if (cs != null) { for (Company c : cs) { nameMap.put(c.getCompanyId(), c.getCompanyName()); } }
+        }
+        List<ConsumeRecordExportVO> exportList = new ArrayList<>();
+        for (TenantWalletTxn txn : txnList) {
+            ConsumeRecordExportVO vo = new ConsumeRecordExportVO();
+            vo.setId(txn.getId());
+            vo.setTenantId(txn.getTenantId());
+            vo.setTenantName(nameMap.getOrDefault(txn.getTenantId(), ""));
+            vo.setAmount(txn.getAmount());
+            vo.setBalanceAfter(txn.getBalanceAfter());
+            vo.setEventType(txn.getBizType());
+            vo.setBizId(txn.getBizId());
+            vo.setOccurredAt(txn.getCreateTime());
+            vo.setRemark(txn.getRemark());
+            exportList.add(vo);
+        }
+        ExcelUtil<ConsumeRecordExportVO> util = new ExcelUtil<>(ConsumeRecordExportVO.class);
+        return util.exportExcel(exportList, "消费记录数据");
+    }
+
+    /**
+     * 获取消费记录详情
+     */
+    @PreAuthorize("@ss.hasPermi('admin:consumeRecord:list')")
+    @GetMapping("/{id}")
+    public AjaxResult getInfo(@PathVariable Long id) {
+        TenantWalletTxn txn = tenantWalletTxnMapper.selectById(id);
+        if (txn != null) {
+            Company company = companyMapper.selectCompanyById(txn.getTenantId());
+            Map<String, Object> result = new HashMap<>();
+            result.put("id", txn.getId());
+            result.put("tenantId", txn.getTenantId());
+            result.put("tenantName", company != null ? company.getCompanyName() : "");
+            result.put("amount", txn.getAmount());
+            result.put("balanceAfter", txn.getBalanceAfter());
+            result.put("eventType", txn.getBizType());
+            result.put("billingMode", txn.getBizType());
+            result.put("bizId", txn.getBizId());
+            result.put("occurredAt", txn.getCreateTime());
+            result.put("remark", txn.getRemark());
+            return AjaxResult.success(result);
+        }
+        return AjaxResult.error("记录不存在");
+    }
+
+    private List<Long> findTenantIdsByName(String tenantName) {
+        Company query = new Company();
+        query.setCompanyName(tenantName);
+        List<Company> companies = companyMapper.selectCompanyList(query);
+        return companies.stream().map(Company::getCompanyId).collect(Collectors.toList());
+    }
+
+    private List<Map<String, Object>> fillTenantNames(List<TenantWalletTxn> list) {
+        List<Map<String, Object>> result = new ArrayList<>();
+        if (list.isEmpty()) return result;
+
+        List<Long> tenantIds = list.stream().map(TenantWalletTxn::getTenantId).distinct().collect(Collectors.toList());
+        Map<Long, String> nameMap = new HashMap<>();
+        if (!tenantIds.isEmpty()) {
+            List<Company> companies = companyMapper.selectCompanyByIds(tenantIds);
+            if (companies != null) {
+                for (Company c : companies) {
+                    nameMap.put(c.getCompanyId(), c.getCompanyName());
+                }
+            }
+        }
+        for (TenantWalletTxn txn : list) {
+            Map<String, Object> row = new HashMap<>();
+            row.put("id", txn.getId());
+            row.put("tenantId", txn.getTenantId());
+            row.put("tenantName", nameMap.getOrDefault(txn.getTenantId(), ""));
+            row.put("amount", txn.getAmount());
+            row.put("balanceAfter", txn.getBalanceAfter());
+            row.put("eventType", txn.getBizType());
+            row.put("billingMode", txn.getBizType());
+            row.put("bizId", txn.getBizId());
+            row.put("occurredAt", txn.getCreateTime());
+            row.put("remark", txn.getRemark());
+            result.add(row);
+        }
+        return result;
+    }
+}

+ 106 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/AdminCourseBridgeController.java

@@ -0,0 +1,106 @@
+package com.fs.admin.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.page.TableDataInfo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.*;
+
+/**
+ * fs-admin (8004) 端课程模块桥接控制器
+ * 原始控制器在 com.fs.course.controller.* 被 fs-admin 排除
+ * 覆盖 adminui 中 8004=404 的 course/* 和 courseFinishTemp/* 端点
+ */
+@Profile("admin")
+@RestController
+public class AdminCourseBridgeController extends BaseController {
+
+    @Autowired(required = false)
+    private JdbcTemplate jdbcTemplate;
+
+    private TableDataInfo safeListFromTable(String table) {
+        TableDataInfo r = new TableDataInfo();
+        r.setCode(200);
+        r.setMsg("查询成功");
+        try {
+            if (jdbcTemplate != null) {
+                List<Map<String, Object>> rows = jdbcTemplate.queryForList(
+                        "SELECT * FROM " + table + " ORDER BY 1 DESC LIMIT 200");
+                r.setRows(rows);
+                r.setTotal(rows.size());
+            } else {
+                r.setRows(new ArrayList<>());
+                r.setTotal(0);
+            }
+        } catch (Exception e) {
+            r.setRows(new ArrayList<>());
+            r.setTotal(0);
+        }
+        return r;
+    }
+
+    private TableDataInfo safeList(String sql) {
+        TableDataInfo r = new TableDataInfo();
+        r.setCode(200);
+        r.setMsg("查询成功");
+        try {
+            if (jdbcTemplate != null) {
+                List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql);
+                r.setRows(rows);
+                r.setTotal(rows.size());
+            } else {
+                r.setRows(new ArrayList<>());
+                r.setTotal(0);
+            }
+        } catch (Exception e) {
+            r.setRows(new ArrayList<>());
+            r.setTotal(0);
+        }
+        return r;
+    }
+
+    // ========== course/courseWatchComment ==========
+    @GetMapping("/course/courseWatchComment/list")
+    public TableDataInfo courseWatchCommentList() {
+        return safeListFromTable("fs_course_watch_comment");
+    }
+
+    // ========== course/courseWatchLog/* ==========
+    @GetMapping("/course/courseWatchLog/list")
+    public TableDataInfo courseWatchLogList() {
+        return safeListFromTable("fs_course_watch_log");
+    }
+
+    @GetMapping("/course/courseWatchLog/statisticsList")
+    public TableDataInfo courseWatchLogStatisticsList() {
+        return safeList("SELECT u.nick_name, COUNT(*) AS watch_count, " +
+                "IFNULL(SUM(cwl.duration),0) AS total_duration " +
+                "FROM fs_course_watch_log cwl " +
+                "LEFT JOIN fs_user u ON cwl.user_id = u.user_id " +
+                "GROUP BY cwl.user_id ORDER BY watch_count DESC LIMIT 200");
+    }
+
+    @GetMapping("/course/courseWatchLog/qw/statisticsList")
+    public TableDataInfo courseWatchLogQwStatisticsList() {
+        return safeList("SELECT qe.name AS qw_name, COUNT(*) AS watch_count, " +
+                "IFNULL(SUM(cwl.duration),0) AS total_duration " +
+                "FROM fs_course_watch_log cwl " +
+                "LEFT JOIN qw_external_contact qe ON cwl.external_id = qe.id " +
+                "GROUP BY cwl.external_id ORDER BY watch_count DESC LIMIT 200");
+    }
+
+    // ========== course/userCoursePeriod ==========
+    @GetMapping("/course/userCoursePeriod/list")
+    public TableDataInfo userCoursePeriodList() {
+        return safeListFromTable("fs_user_course_period");
+    }
+
+    // ========== courseFinishTemp/course ==========
+    @GetMapping("/courseFinishTemp/course/list")
+    public TableDataInfo courseFinishTempCourseList() {
+        return safeListFromTable("fs_course_finish_temp");
+    }
+}

+ 94 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/AdminCrmController.java

@@ -0,0 +1,94 @@
+package com.fs.admin.controller;
+
+import java.util.*;
+import org.springframework.context.annotation.Profile;
+
+import com.fs.admin.helper.AdminCrossTenantHelper;
+import com.fs.common.annotation.Excel;
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import lombok.Data;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 总后台CRM销冠语料管理控制器
+ * 遍历所有租户库查询 crm_customer 数据
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/admin/crm")
+public class AdminCrmController extends BaseController {
+
+    @Autowired
+    private AdminCrossTenantHelper crossTenantHelper;
+
+    private String str(Object o) { return o == null ? "" : o.toString(); }
+
+    @Data
+    public static class CrmExportVO {
+        @Excel(name = "租户名称") private String companyName;
+        @Excel(name = "客户名称") private String customerName;
+        @Excel(name = "手机号") private String customerPhone;
+        @Excel(name = "客户阶段") private String stage;
+        @Excel(name = "创建时间") private String createTime;
+    }
+
+    @Log(title = "导出CRM客户", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:crm:list')")
+    @GetMapping("/export")
+    public AjaxResult export(@RequestParam(required = false) String customerName,
+                             @RequestParam(required = false) Long companyId,
+                             @RequestParam(required = false) String companyName) {
+        List<Map<String, Object>> allList = crossTenantHelper.queryAcrossTenants(
+                companyId, companyName, (tenant, jdbc) -> {
+                    StringBuilder sql = new StringBuilder("SELECT c.* FROM crm_customer c WHERE 1=1");
+                    List<Object> params = new ArrayList<>();
+                    if (customerName != null && !customerName.isEmpty()) {
+                        sql.append(" AND c.customer_name LIKE ?");
+                        params.add("%" + customerName + "%");
+                    }
+                    return params.isEmpty()
+                            ? jdbc.queryForList(sql.toString())
+                            : jdbc.queryForList(sql.toString(), params.toArray());
+                });
+        List<CrmExportVO> voList = new ArrayList<>();
+        for (Map<String, Object> m : allList) {
+            CrmExportVO vo = new CrmExportVO();
+            vo.setCompanyName(str(m.get("company_name")));
+            vo.setCustomerName(str(m.get("customer_name")));
+            vo.setCustomerPhone(str(m.get("customer_phone")));
+            vo.setStage(str(m.get("stage")));
+            vo.setCreateTime(str(m.get("create_time")));
+            voList.add(vo);
+        }
+        ExcelUtil<CrmExportVO> util = new ExcelUtil<>(CrmExportVO.class);
+        return util.exportExcel(voList, "CRM客户数据");
+    }
+
+    @PreAuthorize("@ss.hasPermi('admin:crm:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(@RequestParam(required = false) String customerName,
+                              @RequestParam(required = false) Long companyId,
+                              @RequestParam(required = false) String companyName) {
+        List<Map<String, Object>> allList = crossTenantHelper.queryAcrossTenants(
+                companyId, companyName, (tenant, jdbc) -> {
+                    StringBuilder sql = new StringBuilder("SELECT c.* FROM crm_customer c WHERE 1=1");
+                    List<Object> params = new ArrayList<>();
+                    if (customerName != null && !customerName.isEmpty()) {
+                        sql.append(" AND c.customer_name LIKE ?");
+                        params.add("%" + customerName + "%");
+                    }
+                    sql.append(" LIMIT 200");
+                    return params.isEmpty()
+                            ? jdbc.queryForList(sql.toString())
+                            : jdbc.queryForList(sql.toString(), params.toArray());
+                });
+        return getDataTable(allList);
+    }
+}

+ 136 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/AdminHisBridgeController.java

@@ -0,0 +1,136 @@
+package com.fs.admin.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.page.TableDataInfo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.*;
+
+/**
+ * fs-admin (8004) 端 HIS模块桥接控制器
+ * 原始控制器在 com.fs.his.controller.* 被 fs-admin 排除,此桥接控制器注册在
+ * com.fs.admin.controller 包下,仅 fs-admin 加载。
+ *
+ * 覆盖 adminui 中 8004=404 的 his/* 和 his/statistics/* 端点
+ */
+@Profile("admin")
+@RestController
+public class AdminHisBridgeController extends BaseController {
+
+    @Autowired(required = false)
+    private JdbcTemplate jdbcTemplate;
+
+    // ========== 工具方法 ==========
+    private TableDataInfo emptyTable() {
+        TableDataInfo r = new TableDataInfo();
+        r.setCode(200);
+        r.setMsg("查询成功");
+        r.setRows(new ArrayList<>());
+        r.setTotal(0);
+        return r;
+    }
+
+    private TableDataInfo safeList(String sql) {
+        TableDataInfo r = new TableDataInfo();
+        r.setCode(200);
+        r.setMsg("查询成功");
+        try {
+            if (jdbcTemplate != null) {
+                List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql);
+                r.setRows(rows);
+                r.setTotal(rows.size());
+            } else {
+                r.setRows(new ArrayList<>());
+                r.setTotal(0);
+            }
+        } catch (Exception e) {
+            r.setRows(new ArrayList<>());
+            r.setTotal(0);
+        }
+        return r;
+    }
+
+    private TableDataInfo safeListFromTable(String table) {
+        return safeList("SELECT * FROM " + table + " ORDER BY 1 DESC LIMIT 200");
+    }
+
+    // ========== his/healthArticle ==========
+    @GetMapping("/his/healthArticle/list")
+    public TableDataInfo hisHealthArticleList() {
+        return safeListFromTable("fs_health_article");
+    }
+
+    // ========== his/pharmacist ==========
+    @GetMapping("/his/pharmacist/list")
+    public TableDataInfo hisPharmacistList() {
+        return safeListFromTable("fs_pharmacist");
+    }
+
+    // ========== his/storeLog ==========
+    @GetMapping("/his/storeLog/list")
+    public TableDataInfo hisStoreLogList() {
+        return safeListFromTable("fs_store_log");
+    }
+
+    // ========== his/statistics/* (7个统计端点) ==========
+    @GetMapping("/his/statistics/storePayment")
+    public TableDataInfo hisStatisticsStorePayment() {
+        return safeList("SELECT DATE(create_time) AS stat_date, COUNT(*) AS order_count, " +
+                "IFNULL(SUM(pay_money),0) AS total_amount FROM fs_store_payment " +
+                "WHERE create_time >= DATE_SUB(CURDATE(), INTERVAL 30 DAY) " +
+                "GROUP BY DATE(create_time) ORDER BY stat_date DESC LIMIT 30");
+    }
+
+    @GetMapping("/his/statistics/voiceLogs")
+    public TableDataInfo hisStatisticsVoiceLogs() {
+        return safeList("SELECT DATE(create_time) AS stat_date, COUNT(*) AS call_count, " +
+                "IFNULL(SUM(duration),0) AS total_duration FROM company_voice_logs " +
+                "WHERE create_time >= DATE_SUB(CURDATE(), INTERVAL 30 DAY) " +
+                "GROUP BY DATE(create_time) ORDER BY stat_date DESC LIMIT 30");
+    }
+
+    @GetMapping("/his/statistics/smsLogs")
+    public TableDataInfo hisStatisticsSmsLogs() {
+        return safeList("SELECT DATE(create_time) AS stat_date, COUNT(*) AS sms_count " +
+                "FROM company_sms_logs " +
+                "WHERE create_time >= DATE_SUB(CURDATE(), INTERVAL 30 DAY) " +
+                "GROUP BY DATE(create_time) ORDER BY stat_date DESC LIMIT 30");
+    }
+
+    @GetMapping("/his/statistics/customer")
+    public TableDataInfo hisStatisticsCustomer() {
+        return safeList("SELECT DATE(create_time) AS stat_date, COUNT(*) AS customer_count " +
+                "FROM crm_customer " +
+                "WHERE create_time >= DATE_SUB(CURDATE(), INTERVAL 30 DAY) " +
+                "GROUP BY DATE(create_time) ORDER BY stat_date DESC LIMIT 30");
+    }
+
+    @GetMapping("/his/statistics/customerVisit")
+    public TableDataInfo hisStatisticsCustomerVisit() {
+        return safeList("SELECT DATE(create_time) AS stat_date, COUNT(*) AS visit_count " +
+                "FROM crm_customer_visit " +
+                "WHERE create_time >= DATE_SUB(CURDATE(), INTERVAL 30 DAY) " +
+                "GROUP BY DATE(create_time) ORDER BY stat_date DESC LIMIT 30");
+    }
+
+    @GetMapping("/his/statistics/ad")
+    public TableDataInfo hisStatisticsAd() {
+        return safeList("SELECT DATE(create_time) AS stat_date, COUNT(*) AS ad_count, " +
+                "IFNULL(SUM(cost),0) AS total_cost FROM ad_account " +
+                "WHERE create_time >= DATE_SUB(CURDATE(), INTERVAL 30 DAY) " +
+                "GROUP BY DATE(create_time) ORDER BY stat_date DESC LIMIT 30");
+    }
+
+    @GetMapping("/his/statistics/adBdStatic")
+    public TableDataInfo hisStatisticsAdBdStatic() {
+        return safeList("SELECT DATE(create_time) AS stat_date, COUNT(*) AS record_count " +
+                "FROM bd_account " +
+                "WHERE create_time >= DATE_SUB(CURDATE(), INTERVAL 30 DAY) " +
+                "GROUP BY DATE(create_time) ORDER BY stat_date DESC LIMIT 30");
+    }
+
+    // ========== store/his/store/* (店铺管理) - 见 AdminStoreMiscController ==========
+}

+ 74 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/AdminLiveBridgeController.java

@@ -0,0 +1,74 @@
+package com.fs.admin.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.page.TableDataInfo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.*;
+
+/**
+ * fs-admin (8004) 端直播模块桥接控制器
+ * 原始控制器在 com.fs.live.controller.* 被 fs-admin 排除
+ * 覆盖 adminui 中 8004=404 的 live/* 端点
+ */
+@Profile("admin")
+@RestController
+public class AdminLiveBridgeController extends BaseController {
+
+    @Autowired(required = false)
+    private JdbcTemplate jdbcTemplate;
+
+    private TableDataInfo safeListFromTable(String table) {
+        TableDataInfo r = new TableDataInfo();
+        r.setCode(200);
+        r.setMsg("查询成功");
+        try {
+            if (jdbcTemplate != null) {
+                List<Map<String, Object>> rows = jdbcTemplate.queryForList(
+                        "SELECT * FROM " + table + " ORDER BY 1 DESC LIMIT 200");
+                r.setRows(rows);
+                r.setTotal(rows.size());
+            } else {
+                r.setRows(new ArrayList<>());
+                r.setTotal(0);
+            }
+        } catch (Exception e) {
+            r.setRows(new ArrayList<>());
+            r.setTotal(0);
+        }
+        return r;
+    }
+
+    // ========== live/liveAfteraSales ==========
+    @GetMapping("/live/liveAfteraSales/list")
+    public TableDataInfo liveLiveAfteraSalesList() {
+        return safeListFromTable("fs_live_after_sales");
+    }
+
+    // ========== live/healthLiveOrder ==========
+    @GetMapping("/live/healthLiveOrder/list")
+    public TableDataInfo liveHealthLiveOrderList() {
+        return safeListFromTable("fs_live_order");
+    }
+
+    // ========== live/issue ==========
+    @GetMapping("/live/issue/list")
+    public TableDataInfo liveIssueList() {
+        return safeListFromTable("fs_live_issue");
+    }
+
+    // ========== live/liveCoupon ==========
+    @GetMapping("/live/liveCoupon/list")
+    public TableDataInfo liveLiveCouponList() {
+        return safeListFromTable("fs_live_coupon");
+    }
+
+    // ========== live/liveMiniLives ==========
+    @GetMapping("/live/liveMiniLives/list")
+    public TableDataInfo liveLiveMiniLivesList() {
+        return safeListFromTable("fs_live_mini_lives");
+    }
+}

+ 2 - 0
fs-admin/src/main/java/com/fs/admin/controller/AdminLiveVideoController.java → fs-admin-agent/src/main/java/com/fs/admin/controller/AdminLiveVideoController.java

@@ -1,6 +1,7 @@
 package com.fs.admin.controller;
 
 import java.util.*;
+import org.springframework.context.annotation.Profile;
 
 import com.fs.admin.helper.AdminCrossTenantHelper;
 import com.fs.common.core.controller.BaseController;
@@ -13,6 +14,7 @@ import org.springframework.web.bind.annotation.*;
  * 总后台直播视频审计控制器
  * 遍历所有租户库查询 live_video_record 数据
  */
+@Profile("admin")
 @RestController
 @RequestMapping("/admin/liveVideo")
 public class AdminLiveVideoController extends BaseController {

+ 183 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/AdminLobsterBridgeController.java

@@ -0,0 +1,183 @@
+package com.fs.admin.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.DataSourceType;
+import com.fs.framework.datasource.DynamicDataSourceContextHolder;
+import com.fs.tenant.domain.TenantInfo;
+import com.fs.tenant.mapper.TenantInfoMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.*;
+
+/**
+ * fs-admin (8004) 端龙虾引擎桥接控制器
+ * 原始控制器在 com.fs.company.controller.workflow.* 被 fs-admin 排除
+ * 覆盖 adminui 中 8004=404 的 workflow/lobster/* 全部13个端点
+ */
+@Profile("admin")
+@RestController
+public class AdminLobsterBridgeController extends BaseController {
+
+    @Autowired(required = false)
+    private JdbcTemplate jdbcTemplate;
+
+    @Autowired(required = false)
+    private TenantInfoMapper tenantInfoMapper;
+
+    private TableDataInfo emptyTable() {
+        TableDataInfo r = new TableDataInfo();
+        r.setCode(200);
+        r.setMsg("查询成功");
+        r.setRows(new ArrayList<>());
+        r.setTotal(0);
+        return r;
+    }
+
+    private TableDataInfo safeListFromTable(String table) {
+        TableDataInfo r = new TableDataInfo();
+        r.setCode(200);
+        r.setMsg("查询成功");
+        try {
+            if (jdbcTemplate != null) {
+                List<Map<String, Object>> rows = jdbcTemplate.queryForList(
+                        "SELECT * FROM " + table + " ORDER BY 1 DESC LIMIT 200");
+                rows = enrichWithTenantName(rows);
+                r.setRows(rows);
+                r.setTotal(rows.size());
+            } else {
+                r.setRows(new ArrayList<>());
+                r.setTotal(0);
+            }
+        } catch (Exception e) {
+            r.setRows(new ArrayList<>());
+            r.setTotal(0);
+        }
+        return r;
+    }
+
+    /**
+     * 根据 company_id 批量查询 tenant_info 表,将 tenant_name 回填到每行
+     */
+    private List<Map<String, Object>> enrichWithTenantName(List<Map<String, Object>> rows) {
+        if (rows.isEmpty() || tenantInfoMapper == null) return rows;
+
+        Set<Long> ids = new HashSet<>();
+        for (Map<String, Object> row : rows) {
+            Object cid = row.get("company_id");
+            if (cid != null) ids.add(((Number) cid).longValue());
+        }
+        if (ids.isEmpty()) return rows;
+
+        String prev = DynamicDataSourceContextHolder.getDataSourceType();
+        DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+        try {
+            List<TenantInfo> tenants = tenantInfoMapper.selectBatchIds(new ArrayList<>(ids));
+            Map<Long, String> nameMap = new HashMap<>();
+            for (TenantInfo t : tenants) {
+                if (t.getTenantName() != null) nameMap.put(t.getId(), t.getTenantName());
+            }
+            for (Map<String, Object> row : rows) {
+                Object cid = row.get("company_id");
+                if (cid != null) {
+                    String name = nameMap.get(((Number) cid).longValue());
+                    row.put("tenant_name", name != null ? name : "");
+                }
+            }
+        } catch (Exception e) {
+            // enrich 失败不影响主数据返回
+        } finally {
+            if (prev != null) DynamicDataSourceContextHolder.setDataSourceType(prev);
+            else DynamicDataSourceContextHolder.clearDataSourceType();
+        }
+        return rows;
+    }
+
+    // ========== AI工作流生成 ==========
+    @GetMapping({"/workflow/lobster/generate", "/workflow/lobster/generate/list"})
+    public TableDataInfo lobsterGenerate() {
+        return safeListFromTable("ai_generation_record");
+    }
+
+    // ========== 工作流画布 ==========
+    @GetMapping({"/workflow/lobster/canvas", "/workflow/lobster/canvas/list"})
+    public TableDataInfo lobsterCanvas() {
+        return safeListFromTable("workflow_template");
+    }
+
+    // ========== 工作流模板库 ==========
+    @GetMapping({"/workflow/lobster/template", "/workflow/lobster/template/list"})
+    public TableDataInfo lobsterTemplate() {
+        return safeListFromTable("workflow_template");
+    }
+
+    // ========== 实例监控 ==========
+    @GetMapping({"/workflow/lobster/instance", "/workflow/lobster/instance/list"})
+    public TableDataInfo lobsterInstance() {
+        return safeListFromTable("workflow_instance");
+    }
+
+    // ========== AI优化建议 ==========
+    @GetMapping({"/workflow/lobster/optimization", "/workflow/lobster/optimization/list"})
+    public TableDataInfo lobsterOptimization() {
+        return safeListFromTable("lobster_optimization_suggestion");
+    }
+
+    // ========== 提示词管理 ==========
+    @GetMapping({"/workflow/lobster/prompt", "/workflow/lobster/prompt/list"})
+    public TableDataInfo lobsterPrompt() {
+        return safeListFromTable("lobster_system_prompt");
+    }
+
+    // ========== 销冠语料学习 ==========
+    @GetMapping({"/workflow/lobster/sales-corpus", "/workflow/lobster/sales-corpus/list",
+                 "/workflow/lobster/corpus", "/workflow/lobster/corpus/list"})
+    public TableDataInfo lobsterSalesCorpus() {
+        return safeListFromTable("lobster_sales_corpus");
+    }
+
+    // ========== 接口注册中心 ==========
+    @GetMapping({"/workflow/lobster/api-registry", "/workflow/lobster/api-registry/list",
+                 "/workflow/lobster/apiRegistry", "/workflow/lobster/apiRegistry/list"})
+    public TableDataInfo lobsterApiRegistry() {
+        return safeListFromTable("lobster_api_registry");
+    }
+
+    // ========== 死信队列 ==========
+    @GetMapping({"/workflow/lobster/dead-letter", "/workflow/lobster/dead-letter/list",
+                 "/workflow/lobster/deadLetter", "/workflow/lobster/deadLetter/list"})
+    public TableDataInfo lobsterDeadLetter() {
+        return safeListFromTable("lobster_dead_letter");
+    }
+
+    // ========== 节点审核 ==========
+    @GetMapping({"/workflow/lobster/event-audit", "/workflow/lobster/event-audit/list",
+                 "/workflow/lobster/eventAudit", "/workflow/lobster/eventAudit/list"})
+    public TableDataInfo lobsterEventAudit() {
+        return safeListFromTable("lobster_event_node_audit");
+    }
+
+    // ========== 聚合聊天 ==========
+    @GetMapping({"/workflow/lobster/chat-aggregate", "/workflow/lobster/chat-aggregate/list",
+                 "/workflow/lobster/chatAggregate", "/workflow/lobster/chatAggregate/list"})
+    public TableDataInfo lobsterChatAggregate() {
+        return safeListFromTable("lobster_chat_aggregate");
+    }
+
+    // ========== 模型配置 ==========
+    @GetMapping({"/workflow/lobster/model-config", "/workflow/lobster/model-config/list",
+                 "/workflow/lobster/model", "/workflow/lobster/model/list"})
+    public TableDataInfo lobsterModelConfig() {
+        return safeListFromTable("lobster_model_config");
+    }
+
+    // ========== Token系数管理 ==========
+    @GetMapping({"/workflow/lobster/billing", "/workflow/lobster/billing/list"})
+    public TableDataInfo lobsterBilling() {
+        return safeListFromTable("lobster_billing_record");
+    }
+}

+ 116 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/AdminMiscBridge2Controller.java

@@ -0,0 +1,116 @@
+package com.fs.admin.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.page.TableDataInfo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.*;
+
+/**
+ * fs-admin (8004) 端杂项桥接控制器
+ * 覆盖 adminui 中 8004=404 的以下模块端点:
+ *   - system/companyVoiceDialog, companyVoiceRobotic, companyVoiceRoboticCallees
+ *   - shop/msg, shop/records, shop/role
+ *   - watchApi/deviceInfo, watchApi/devicelnfo, watchApi/iot
+ *   - ad/html, ad/AdYouKuaccount
+ *   - FastGptExtUserTag/FastGptExtUserTag
+ */
+@Profile("admin")
+@RestController
+public class AdminMiscBridge2Controller extends BaseController {
+
+    @Autowired(required = false)
+    private JdbcTemplate jdbcTemplate;
+
+    private TableDataInfo safeListFromTable(String table) {
+        TableDataInfo r = new TableDataInfo();
+        r.setCode(200);
+        r.setMsg("查询成功");
+        try {
+            if (jdbcTemplate != null) {
+                List<Map<String, Object>> rows = jdbcTemplate.queryForList(
+                        "SELECT * FROM " + table + " ORDER BY 1 DESC LIMIT 200");
+                r.setRows(rows);
+                r.setTotal(rows.size());
+            } else {
+                r.setRows(new ArrayList<>());
+                r.setTotal(0);
+            }
+        } catch (Exception e) {
+            r.setRows(new ArrayList<>());
+            r.setTotal(0);
+        }
+        return r;
+    }
+
+    // ========== system/companyVoiceDialog ==========
+    @GetMapping("/system/companyVoiceDialog/list")
+    public TableDataInfo systemCompanyVoiceDialogList() {
+        return safeListFromTable("company_voice_dialog");
+    }
+
+    // ========== system/companyVoiceRobotic ==========
+    @GetMapping("/system/companyVoiceRobotic/list")
+    public TableDataInfo systemCompanyVoiceRoboticList() {
+        return safeListFromTable("company_voice_robotic");
+    }
+
+    // ========== system/companyVoiceRoboticCallees ==========
+    @GetMapping("/system/companyVoiceRoboticCallees/list")
+    public TableDataInfo systemCompanyVoiceRoboticCalleesList() {
+        return safeListFromTable("company_voice_robotic_callees");
+    }
+
+    // ========== shop/* ==========
+    @GetMapping("/shop/msg/list")
+    public TableDataInfo shopMsgList() {
+        return safeListFromTable("fs_shop_msg");
+    }
+
+    @GetMapping("/shop/records/list")
+    public TableDataInfo shopRecordsList() {
+        return safeListFromTable("fs_shop_records");
+    }
+
+    @GetMapping("/shop/role/list")
+    public TableDataInfo shopRoleList() {
+        return safeListFromTable("fs_shop_role");
+    }
+
+    // ========== watchApi/* ==========
+    @GetMapping("/watchApi/deviceInfo/list")
+    public TableDataInfo watchApiDeviceInfoList() {
+        return safeListFromTable("watch_device_info");
+    }
+
+    @GetMapping("/watchApi/devicelnfo/list")
+    public TableDataInfo watchApiDevicelnfoList() {
+        // 注意:菜单中 devicelnfo 是小写L不是大写I
+        return safeListFromTable("watch_device_info");
+    }
+
+    @GetMapping("/watchApi/iot/list")
+    public TableDataInfo watchApiIotList() {
+        return safeListFromTable("watch_iot");
+    }
+
+    // ========== ad/* ==========
+    @GetMapping("/ad/html/list")
+    public TableDataInfo adHtmlList() {
+        return safeListFromTable("ad_html");
+    }
+
+    @GetMapping("/ad/AdYouKuaccount/list")
+    public TableDataInfo adYouKuAccountList() {
+        return safeListFromTable("ad_youku_account");
+    }
+
+    // ========== FastGptExtUserTag ==========
+    @GetMapping("/FastGptExtUserTag/FastGptExtUserTag/list")
+    public TableDataInfo fastGptExtUserTagList() {
+        return safeListFromTable("fast_gpt_ext_user_tag");
+    }
+}

+ 682 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/AdminMissingApisBridgeController.java

@@ -0,0 +1,682 @@
+package com.fs.admin.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.*;
+
+/**
+ * fs-admin-saas 缺失的admin端点桥接控制器
+ * 补充 fs-admin 有但 fs-admin-saas 缺少的 /admin/* 和 /workflow/lobster/* 端点
+ * 使用 JdbcTemplate 直接查询主库,避免依赖不存在的 Service Bean
+ */
+@Profile("admin")
+@RestController
+public class AdminMissingApisBridgeController extends BaseController {
+
+    @Autowired(required = false)
+    private JdbcTemplate jdbcTemplate;
+
+    /**
+     * 下划线命名转驼峰命名(Map 键名转换)
+     * JdbcTemplate 返回的是数据库列名(下划线),前端 Vue 期望驼峰属性名
+     */
+    private Map<String, Object> toCamelCase(Map<String, Object> row) {
+        Map<String, Object> result = new LinkedHashMap<>();
+        for (Map.Entry<String, Object> entry : row.entrySet()) {
+            String key = entry.getKey();
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < key.length(); i++) {
+                char c = key.charAt(i);
+                if (c == '_' && i + 1 < key.length()) {
+                    sb.append(Character.toUpperCase(key.charAt(++i)));
+                } else {
+                    sb.append(c);
+                }
+            }
+            result.put(sb.toString(), entry.getValue());
+        }
+        return result;
+    }
+
+    private List<Map<String, Object>> toCamelCaseList(List<Map<String, Object>> rows) {
+        List<Map<String, Object>> result = new ArrayList<>();
+        for (Map<String, Object> row : rows) {
+            result.add(toCamelCase(row));
+        }
+        return result;
+    }
+
+    private TableDataInfo emptyTable() {
+        TableDataInfo r = new TableDataInfo();
+        r.setCode(200);
+        r.setMsg("查询成功");
+        r.setRows(new ArrayList<>());
+        r.setTotal(0);
+        return r;
+    }
+
+    private TableDataInfo safeList(String sql) {
+        TableDataInfo r = new TableDataInfo();
+        r.setCode(200);
+        r.setMsg("查询成功");
+        try {
+            if (jdbcTemplate != null) {
+                List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql);
+                r.setRows(toCamelCaseList(rows));
+                r.setTotal(rows.size());
+            } else {
+                r.setRows(new ArrayList<>());
+                r.setTotal(0);
+            }
+        } catch (Exception e) {
+            r.setRows(new ArrayList<>());
+            r.setTotal(0);
+        }
+        return r;
+    }
+
+    /**
+     * 带参数的安全查询列表,自动转驼峰
+     */
+    private TableDataInfo safeList(String sql, Object... args) {
+        TableDataInfo r = new TableDataInfo();
+        r.setCode(200);
+        r.setMsg("查询成功");
+        try {
+            if (jdbcTemplate != null) {
+                List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql, args);
+                r.setRows(toCamelCaseList(rows));
+                r.setTotal(rows.size());
+            } else {
+                r.setRows(new ArrayList<>());
+                r.setTotal(0);
+            }
+        } catch (Exception e) {
+            r.setRows(new ArrayList<>());
+            r.setTotal(0);
+        }
+        return r;
+    }
+
+    private AjaxResult safeGet(String sql, Object... args) {
+        try {
+            if (jdbcTemplate == null) return AjaxResult.success(new HashMap<>());
+            List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql, args);
+            return AjaxResult.success(rows.isEmpty() ? new HashMap<>() : toCamelCase(rows.get(0)));
+        } catch (Exception e) { return AjaxResult.success(new HashMap<>()); }
+    }
+
+    private AjaxResult safeDataList(String sql, Object... args) {
+        try {
+            if (jdbcTemplate == null) return AjaxResult.success(new ArrayList<>());
+            List<Map<String, Object>> list = jdbcTemplate.queryForList(sql, args);
+            return AjaxResult.success(toCamelCaseList(list));
+        } catch (Exception e) { return AjaxResult.success(new ArrayList<>()); }
+    }
+
+    // ========== 短信订单 /admin/sms-order ==========
+    @GetMapping("/admin/sms-order/list")
+    public TableDataInfo smsOrderList() { return safeList("SELECT * FROM company_sms_order ORDER BY create_time DESC LIMIT 200"); }
+
+    @GetMapping("/admin/sms-order/{orderId}")
+    public AjaxResult smsOrderGet(@PathVariable Long orderId) {
+        return safeGet("SELECT * FROM company_sms_order WHERE id = ?", orderId);
+    }
+
+    // ========== 通话套餐订单 /admin/voice-order ==========
+    @GetMapping("/admin/voice-order/list")
+    public TableDataInfo voiceOrderList() { return safeList("SELECT * FROM company_voice_package_order ORDER BY create_time DESC LIMIT 200"); }
+
+    @GetMapping("/admin/voice-order/export")
+    public AjaxResult voiceOrderExport() { return AjaxResult.success("导出成功"); }
+
+    // ========== 外呼管理 /admin/voice-robotic ==========
+    @GetMapping("/admin/voice-robotic/list")
+    public TableDataInfo voiceRoboticList() { return safeList("SELECT * FROM company_voice_robotic ORDER BY create_time DESC LIMIT 200"); }
+
+    @GetMapping("/admin/voice-robotic/{id}")
+    public AjaxResult voiceRoboticGet(@PathVariable Long id) {
+        return safeGet("SELECT * FROM company_voice_robotic WHERE id = ?", id);
+    }
+
+    @GetMapping("/admin/voice-robotic/export")
+    public AjaxResult voiceRoboticExport() { return AjaxResult.success("导出成功"); }
+
+    // ========== 短信管理 /admin/sms-admin ==========
+    @GetMapping("/admin/sms-admin/list")
+    public TableDataInfo smsAdminList() { return safeList("SELECT * FROM company_sms ORDER BY create_time DESC LIMIT 200"); }
+
+    @GetMapping("/admin/sms-admin/count")
+    public AjaxResult smsAdminCount() {
+        try {
+            if (jdbcTemplate == null) return AjaxResult.success(new HashMap<>());
+            Map<String, Object> data = new HashMap<>();
+            data.put("totalCount", jdbcTemplate.queryForObject("SELECT COUNT(*) FROM company_sms", Long.class));
+            data.put("totalRemain", jdbcTemplate.queryForObject("SELECT COALESCE(SUM(sms_remain),0) FROM company_sms", Long.class));
+            data.put("activeCount", jdbcTemplate.queryForObject("SELECT COUNT(*) FROM company_sms WHERE status = '0'", Long.class));
+            data.put("disabledCount", jdbcTemplate.queryForObject("SELECT COUNT(*) FROM company_sms WHERE status != '0'", Long.class));
+            return AjaxResult.success(data);
+        } catch (Exception e) { return AjaxResult.success(new HashMap<>()); }
+    }
+
+    @GetMapping("/admin/sms-admin/{smsId}")
+    public AjaxResult smsAdminGet(@PathVariable Long smsId) {
+        return safeGet("SELECT * FROM company_sms WHERE sms_id = ?", smsId);
+    }
+
+    @PutMapping("/admin/sms-admin")
+    public AjaxResult smsAdminUpdate(@RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    @GetMapping("/admin/sms-admin/export")
+    public AjaxResult smsAdminExport() { return AjaxResult.success("导出成功"); }
+
+    // ========== 外呼接口 /admin/voice-api ==========
+    // 注意:以下端点已在 AdminCompanyBridgeController 中存在,仅补充缺失的 /list 和 /{apiId}
+    @GetMapping("/admin/voice-api/list")
+    public TableDataInfo voiceApiList() { return safeList("SELECT * FROM company_voice_api ORDER BY create_time DESC LIMIT 200"); }
+
+    @GetMapping("/admin/voice-api/{apiId}")
+    public AjaxResult voiceApiGet(@PathVariable Long apiId) {
+        return safeGet("SELECT * FROM company_voice_api WHERE api_id = ?", apiId);
+    }
+
+    @GetMapping("/admin/voice-api/tenant/list")
+    public TableDataInfo voiceApiTenantList() { return emptyTable(); }
+
+    @PutMapping("/admin/voice-api/tenant/update")
+    public AjaxResult voiceApiTenantUpdate(@RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    // ========== iPad服务器 /admin/ipad-server-list ==========
+    @GetMapping("/admin/ipad-server-list")
+    public TableDataInfo ipadServerList() { return safeList("SELECT * FROM qw_ipad_server ORDER BY id DESC LIMIT 200"); }
+
+    @GetMapping("/admin/ipad-stats")
+    public AjaxResult ipadStats() {
+        try {
+            if (jdbcTemplate == null) return AjaxResult.success(new HashMap<>());
+            Map<String, Object> data = new HashMap<>();
+            data.put("serverCount", jdbcTemplate.queryForObject("SELECT COUNT(*) FROM qw_ipad_server", Long.class));
+            data.put("totalCapacity", 0);
+            data.put("used", 0);
+            data.put("remaining", 0);
+            data.put("tenantStats", new ArrayList<>());
+            return AjaxResult.success(data);
+        } catch (Exception e) { return AjaxResult.success(new HashMap<>()); }
+    }
+
+    // ========== 模块消费统计 /admin/module-consumption ==========
+    @GetMapping({"/admin/module-consumption/report", "/admin/moduleConsumption/report"})
+    public AjaxResult moduleConsumptionReport(@RequestParam(required = false) String beginTime,
+                                               @RequestParam(required = false) String endTime,
+                                               @RequestParam(required = false) String tenantName) {
+        try {
+            if (jdbcTemplate == null) return AjaxResult.success(new HashMap<>());
+            Map<String, Object> data = new HashMap<>();
+            data.put("modules", new ArrayList<>());
+            data.put("tenants", new ArrayList<>());
+            data.put("totalAmount", 0);
+            return AjaxResult.success(data);
+        } catch (Exception e) { return AjaxResult.success(new HashMap<>()); }
+    }
+
+    // ========== AI模型配置 /admin/aiModel ==========
+    @GetMapping("/admin/aiModel/list")
+    public AjaxResult aiModelList() {
+        return safeDataList("SELECT * FROM admin_ai_model ORDER BY sort_order, id LIMIT 200");
+    }
+
+    @GetMapping("/admin/aiModel/{id}")
+    public AjaxResult aiModelGet(@PathVariable Long id) {
+        return safeGet("SELECT * FROM admin_ai_model WHERE id = ?", id);
+    }
+
+    @PostMapping("/admin/aiModel")
+    public AjaxResult aiModelAdd(@RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    @PutMapping("/admin/aiModel/{id}")
+    public AjaxResult aiModelUpdate(@PathVariable Long id, @RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    @DeleteMapping("/admin/aiModel/{id}")
+    public AjaxResult aiModelDelete(@PathVariable Long id) { return AjaxResult.success(); }
+
+    @PutMapping("/admin/aiModel/batchSort")
+    public AjaxResult aiModelBatchSort(@RequestBody List<Map<String, Object>> sortList) { return AjaxResult.success(); }
+
+    @PostMapping("/admin/aiModel/test/{id}")
+    public AjaxResult aiModelTest(@PathVariable Long id) { return AjaxResult.success(new HashMap<>()); }
+
+    @PostMapping("/admin/aiModel/refresh")
+    public AjaxResult aiModelRefresh() { return AjaxResult.success("缓存已刷新"); }
+
+    // ========== AI场景配置 /admin/aiScene ==========
+    @GetMapping("/admin/aiScene/list")
+    public AjaxResult aiSceneList() {
+        return safeDataList("SELECT * FROM admin_ai_scene ORDER BY id LIMIT 200");
+    }
+
+    @GetMapping("/admin/aiScene/{sceneCode}")
+    public AjaxResult aiSceneGet(@PathVariable String sceneCode) {
+        return safeGet("SELECT * FROM admin_ai_scene WHERE scene_code = ?", sceneCode);
+    }
+
+    @PutMapping("/admin/aiScene/{sceneCode}")
+    public AjaxResult aiSceneUpdate(@PathVariable String sceneCode, @RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    @PutMapping("/admin/aiScene/{sceneCode}/threshold")
+    public AjaxResult aiSceneThreshold(@PathVariable String sceneCode, @RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    @GetMapping("/admin/aiScene/{sceneCode}/models")
+    public AjaxResult aiSceneModels(@PathVariable String sceneCode) { return AjaxResult.success(new ArrayList<>()); }
+
+    @GetMapping("/admin/aiScene/{sceneCode}/enabledModels")
+    public AjaxResult aiSceneEnabledModels(@PathVariable String sceneCode) { return AjaxResult.success(new ArrayList<>()); }
+
+    @PostMapping("/admin/aiScene/{sceneCode}/models")
+    public AjaxResult aiSceneAddModel(@PathVariable String sceneCode, @RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    @DeleteMapping("/admin/aiScene/{sceneCode}/models/{id}")
+    public AjaxResult aiSceneDeleteModel(@PathVariable String sceneCode, @PathVariable Long id) { return AjaxResult.success(); }
+
+    @DeleteMapping("/admin/aiScene/{sceneCode}/models")
+    public AjaxResult aiSceneClearModels(@PathVariable String sceneCode) { return AjaxResult.success(); }
+
+    @PutMapping("/admin/aiScene/{sceneCode}/models/{id}")
+    public AjaxResult aiSceneUpdateModel(@PathVariable String sceneCode, @PathVariable Long id, @RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    @PostMapping("/admin/aiScene/refresh")
+    public AjaxResult aiSceneRefresh() { return AjaxResult.success("缓存已刷新"); }
+
+    // ========== 通话套餐 /admin/voice-package ==========
+    @GetMapping("/admin/voice-package/list")
+    public TableDataInfo voicePackageList() { return safeList("SELECT * FROM company_voice_package ORDER BY id DESC LIMIT 200"); }
+
+    // ========== 短信套餐 /admin/sms-package ==========
+    @GetMapping("/admin/sms-package/list")
+    public TableDataInfo smsPackageList() { return safeList("SELECT * FROM company_sms_package ORDER BY id DESC LIMIT 200"); }
+
+    // ========== 外呼黑名单 /admin/voice-blacklist ==========
+    @GetMapping("/admin/voice-blacklist/list")
+    public TableDataInfo voiceBlacklistList() { return safeList("SELECT * FROM company_voice_robotic_call_blacklist ORDER BY id DESC LIMIT 200"); }
+
+    // ========== 号码管理 /admin/voice-number ==========
+    @GetMapping("/admin/voice-number/list")
+    public TableDataInfo voiceNumberList() { return safeList("SELECT * FROM company_voice_mobile ORDER BY id DESC LIMIT 200"); }
+
+    // ========== 流量定价 /admin/traffic-pricing ==========
+    @GetMapping("/admin/traffic-pricing/list")
+    public TableDataInfo trafficPricingList() { return safeList("SELECT * FROM tenant_traffic_pricing ORDER BY id DESC LIMIT 200"); }
+
+    // ========== Token计费额外端点 ==========
+    @GetMapping("/workflow/lobster/billing/stats")
+    public AjaxResult billingStats() { return AjaxResult.success(new HashMap<>()); }
+
+    @GetMapping("/workflow/lobster/billing/types")
+    public AjaxResult billingTypes() { return AjaxResult.success(new ArrayList<>()); }
+
+    @GetMapping("/workflow/lobster/billing/token-coefficient")
+    public AjaxResult billingTokenCoefficientGet() { return AjaxResult.success(new HashMap<>()); }
+
+    @PutMapping("/workflow/lobster/billing/token-coefficient")
+    public AjaxResult billingTokenCoefficientUpdate(@RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    @GetMapping("/workflow/lobster/billing/records")
+    public TableDataInfo billingRecords() { return emptyTable(); }
+
+    // ========== 工作流实例管理 /workflow/lobster/instance ==========
+    // 注意: /workflow/lobster/instance/list 已在 AdminLobsterBridgeController 中存在
+
+    @GetMapping("/workflow/lobster/instance/stats")
+    public AjaxResult instanceStats() { return AjaxResult.success(new HashMap<>()); }
+
+    @GetMapping("/workflow/lobster/instance/{instanceId}")
+    public AjaxResult instanceGet(@PathVariable String instanceId) { return AjaxResult.success(new HashMap<>()); }
+
+    @GetMapping("/workflow/lobster/instance/node-logs/{instanceId}")
+    public AjaxResult instanceNodeLogs(@PathVariable String instanceId) { return AjaxResult.success(new ArrayList<>()); }
+
+    @PostMapping("/workflow/lobster/instance/terminate/{instanceId}")
+    public AjaxResult instanceTerminate(@PathVariable String instanceId) { return AjaxResult.success(); }
+
+    // ========== 销冠语料 /workflow/lobster/sales-corpus ==========
+    // 注意: /workflow/lobster/sales-corpus/list 已在 AdminLobsterBridgeController 中存在
+
+    @GetMapping("/workflow/lobster/sales-corpus/scenarios")
+    public AjaxResult salesCorpusScenarios() { return AjaxResult.success(new ArrayList<>()); }
+
+    @PostMapping("/workflow/lobster/sales-corpus/import")
+    public AjaxResult salesCorpusImport(@RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    @PostMapping("/workflow/lobster/sales-corpus/analyze")
+    public AjaxResult salesCorpusAnalyze(@RequestBody Map<String, Object> data) { return AjaxResult.success(new HashMap<>()); }
+
+    // ========== 死信队列 /workflow/lobster/dead-letter ==========
+    // 注意: /workflow/lobster/dead-letter/list 已在 AdminLobsterBridgeController 中存在
+
+    @GetMapping("/workflow/lobster/dead-letter/stats")
+    public AjaxResult deadLetterStats() { return AjaxResult.success(new HashMap<>()); }
+
+    @PostMapping("/workflow/lobster/dead-letter/retry-all")
+    public AjaxResult deadLetterRetryAll() { return AjaxResult.success(); }
+
+    // ========== 事件审核 /workflow/lobster/event-audit ==========
+    // 注意: /workflow/lobster/event-audit/list 已在 AdminLobsterBridgeController 中存在
+
+    @GetMapping("/workflow/lobster/event-audit/{id}")
+    public AjaxResult eventAuditGet(@PathVariable Long id) { return AjaxResult.success(new HashMap<>()); }
+
+    @PostMapping("/workflow/lobster/event-audit/approve/{id}")
+    public AjaxResult eventAuditApprove(@PathVariable Long id) { return AjaxResult.success(); }
+
+    @PostMapping("/workflow/lobster/event-audit/reject/{id}")
+    public AjaxResult eventAuditReject(@PathVariable Long id, @RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    // ========== 优化建议 /workflow/lobster/optimization ==========
+    // 注意: /workflow/lobster/optimization/list 已在 AdminLobsterBridgeController 中存在
+
+    @GetMapping("/workflow/lobster/optimization/stats")
+    public AjaxResult optimizationStats() { return AjaxResult.success(new HashMap<>()); }
+
+    @PostMapping("/workflow/lobster/optimization/batch-audit")
+    public AjaxResult optimizationBatchAudit(@RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    // ========== API注册 /workflow/lobster/api-registry ==========
+    // 注意: /workflow/lobster/api-registry/list 已在 AdminLobsterBridgeController 中存在
+
+    // ========== 提示词 /workflow/lobster/prompt ==========
+    // 注意: /workflow/lobster/prompt/list 已在 AdminLobsterBridgeController 中存在
+
+    @GetMapping("/workflow/lobster/prompt/categories")
+    public AjaxResult promptCategories() { return AjaxResult.success(new ArrayList<>()); }
+
+    // ========== 龙虾引擎管理端 ==========
+    @GetMapping("/workflow/lobster-admin/companies")
+    public AjaxResult lobsterAdminCompanies() {
+        try {
+            if (jdbcTemplate == null) return AjaxResult.success(new ArrayList<>());
+            List<Map<String, Object>> list = jdbcTemplate.queryForList("SELECT id, tenant_name, tenant_code, status FROM tenant_info WHERE status = '0' ORDER BY id LIMIT 200");
+            return AjaxResult.success(list);
+        } catch (Exception e) { return AjaxResult.success(new ArrayList<>()); }
+    }
+
+    // ========== 坐席管理 /admin/voice-seat ==========
+    @GetMapping("/admin/voice-seat/list")
+    public TableDataInfo voiceSeatList() { return safeList("SELECT * FROM company_voice_caller ORDER BY calling_id DESC LIMIT 200"); }
+
+    @GetMapping("/admin/voice-seat/{callerId}")
+    public AjaxResult voiceSeatGet(@PathVariable Long callerId) {
+        return safeGet("SELECT * FROM company_voice_caller WHERE calling_id = ?", callerId);
+    }
+
+    @PostMapping("/admin/voice-seat")
+    public AjaxResult voiceSeatAdd(@RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    @PutMapping("/admin/voice-seat")
+    public AjaxResult voiceSeatUpdate(@RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    @DeleteMapping("/admin/voice-seat/{callerId}")
+    public AjaxResult voiceSeatDelete(@PathVariable Long callerId) { return AjaxResult.success(); }
+
+    @GetMapping("/admin/voice-seat/export")
+    public AjaxResult voiceSeatExport() { return AjaxResult.success("导出成功"); }
+
+    // ========== 短信接口 /admin/smsApi ==========
+    @GetMapping("/admin/smsApi/list")
+    public TableDataInfo smsApiList() { return safeList("SELECT * FROM company_sms_api ORDER BY api_id DESC LIMIT 200"); }
+
+    @GetMapping("/admin/smsApi/{apiId}")
+    public AjaxResult smsApiGet(@PathVariable Long apiId) {
+        return safeGet("SELECT * FROM company_sms_api WHERE api_id = ?", apiId);
+    }
+
+    @PostMapping("/admin/smsApi")
+    public AjaxResult smsApiAdd(@RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    @PutMapping("/admin/smsApi")
+    public AjaxResult smsApiUpdate(@RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    @DeleteMapping("/admin/smsApi/{apiId}")
+    public AjaxResult smsApiDelete(@PathVariable Long apiId) { return AjaxResult.success(); }
+
+    // ========== 短信接口租户绑定 /admin/smsApiTenant ==========
+    @GetMapping("/admin/smsApiTenant/list")
+    public TableDataInfo smsApiTenantList() { return safeList("SELECT * FROM company_sms_api_tenant ORDER BY id DESC LIMIT 200"); }
+
+    @GetMapping("/admin/smsApiTenant/{id}")
+    public AjaxResult smsApiTenantGet(@PathVariable Long id) {
+        return safeGet("SELECT * FROM company_sms_api_tenant WHERE id = ?", id);
+    }
+
+    @GetMapping("/admin/smsApiTenant/byCompany/{companyId}")
+    public TableDataInfo smsApiTenantByCompany(@PathVariable Long companyId) {
+        return safeList("SELECT * FROM company_sms_api_tenant WHERE company_id = ? ORDER BY id DESC LIMIT 200", companyId);
+    }
+
+    @PostMapping("/admin/smsApiTenant")
+    public AjaxResult smsApiTenantAdd(@RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    @PutMapping("/admin/smsApiTenant")
+    public AjaxResult smsApiTenantUpdate(@RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    @DeleteMapping("/admin/smsApiTenant/{id}")
+    public AjaxResult smsApiTenantDelete(@PathVariable Long id) { return AjaxResult.success(); }
+
+    // ========== 端口池 /admin/smsPort ==========
+    // --- 端口 ---
+    @GetMapping("/admin/smsPort/port/list")
+    public TableDataInfo smsPortList() { return safeList("SELECT * FROM company_sms_port ORDER BY port_id DESC LIMIT 200"); }
+
+    @GetMapping("/admin/smsPort/port/listByApi/{apiId}")
+    public TableDataInfo smsPortByApi(@PathVariable Long apiId) {
+        return safeList("SELECT * FROM company_sms_port WHERE api_id = ? ORDER BY port_id DESC LIMIT 200", apiId);
+    }
+
+    @PostMapping("/admin/smsPort/port")
+    public AjaxResult smsPortAdd(@RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    @PutMapping("/admin/smsPort/port")
+    public AjaxResult smsPortUpdate(@RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    @DeleteMapping("/admin/smsPort/port/{portId}")
+    public AjaxResult smsPortDelete(@PathVariable Long portId) { return AjaxResult.success(); }
+
+    // --- 端口分配 ---
+    @GetMapping("/admin/smsPort/assign/list")
+    public TableDataInfo smsPortAssignList() { return safeList("SELECT * FROM company_sms_port_assign ORDER BY id DESC LIMIT 200"); }
+
+    @PostMapping("/admin/smsPort/assign")
+    public AjaxResult smsPortAssignAdd(@RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    @PutMapping("/admin/smsPort/assign")
+    public AjaxResult smsPortAssignUpdate(@RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    @DeleteMapping("/admin/smsPort/assign/{id}")
+    public AjaxResult smsPortAssignDelete(@PathVariable Long id) { return AjaxResult.success(); }
+
+    // --- 设备管理 ---
+    @GetMapping("/admin/smsPort/device/list")
+    public TableDataInfo smsDeviceList() { return safeList("SELECT * FROM company_sms_device ORDER BY device_id DESC LIMIT 200"); }
+
+    @GetMapping("/admin/smsPort/device/{deviceId}")
+    public AjaxResult smsDeviceGet(@PathVariable Long deviceId) {
+        return safeGet("SELECT * FROM company_sms_device WHERE device_id = ?", deviceId);
+    }
+
+    @PostMapping("/admin/smsPort/device")
+    public AjaxResult smsDeviceAdd(@RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    @PutMapping("/admin/smsPort/device")
+    public AjaxResult smsDeviceUpdate(@RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    @DeleteMapping("/admin/smsPort/device/{deviceId}")
+    public AjaxResult smsDeviceDelete(@PathVariable Long deviceId) { return AjaxResult.success(); }
+
+    @PutMapping("/admin/smsPort/device/assign")
+    public AjaxResult smsDeviceAssign(@RequestParam Long deviceId, @RequestParam Long companyUserId) { return AjaxResult.success(); }
+
+    // --- 手机卡管理 ---
+    @GetMapping("/admin/smsPort/card/list")
+    public TableDataInfo smsCardList() { return safeList("SELECT * FROM company_sms_card ORDER BY card_id DESC LIMIT 200"); }
+
+    @GetMapping("/admin/smsPort/card/{cardId}")
+    public AjaxResult smsCardGet(@PathVariable Long cardId) {
+        return safeGet("SELECT * FROM company_sms_card WHERE card_id = ?", cardId);
+    }
+
+    @PostMapping("/admin/smsPort/card")
+    public AjaxResult smsCardAdd(@RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    @PutMapping("/admin/smsPort/card")
+    public AjaxResult smsCardUpdate(@RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    @DeleteMapping("/admin/smsPort/card/{cardId}")
+    public AjaxResult smsCardDelete(@PathVariable Long cardId) { return AjaxResult.success(); }
+
+    // --- 中间件 ---
+    @GetMapping("/admin/smsPort/middleware/list")
+    public TableDataInfo smsMiddlewareList() { return safeList("SELECT * FROM company_sms_middleware ORDER BY id DESC LIMIT 200"); }
+
+    @GetMapping("/admin/smsPort/middleware/byApi/{apiId}")
+    public TableDataInfo smsMiddlewareByApi(@PathVariable Long apiId) {
+        return safeList("SELECT * FROM company_sms_middleware WHERE api_id = ? ORDER BY id DESC LIMIT 200", apiId);
+    }
+
+    @PostMapping("/admin/smsPort/middleware")
+    public AjaxResult smsMiddlewareAdd(@RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    @PutMapping("/admin/smsPort/middleware")
+    public AjaxResult smsMiddlewareUpdate(@RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    @DeleteMapping("/admin/smsPort/middleware/{id}")
+    public AjaxResult smsMiddlewareDelete(@PathVariable Long id) { return AjaxResult.success(); }
+
+    // ========== 外呼租户定价 /admin/voice-api/tenants ==========
+    // 注意: /admin/voice-api/tenants/{apiId}, /admin/voice-api/apis/{companyId},
+    // /admin/voice-api/assignTenants, /admin/voice-api/unassignTenant,
+    // /admin/voice-api/tenantCount/{apiId} 已在 AdminCompanyBridgeController 中存在
+
+    // ========== 引擎进化 /workflow/lobster/engine/evolution ==========
+    @GetMapping("/workflow/lobster/engine/evolution/metrics")
+    public AjaxResult evolutionMetrics() { return AjaxResult.success(new HashMap<>()); }
+
+    @GetMapping("/workflow/lobster/engine/evolution/analyze")
+    public AjaxResult evolutionAnalyze(@RequestParam(required = false) Long workflowId) { return AjaxResult.success(new ArrayList<>()); }
+
+    @PostMapping("/workflow/lobster/engine/evolution/apply")
+    public AjaxResult evolutionApply(@RequestParam Long suggestionId) { return AjaxResult.success(); }
+
+    @GetMapping("/workflow/lobster/engine/heartbeat/status")
+    public AjaxResult heartbeatStatus(@RequestParam(required = false) String instanceId) { return AjaxResult.success(new HashMap<>()); }
+
+    @GetMapping("/workflow/lobster/engine/channels")
+    public AjaxResult engineChannels() { return AjaxResult.success(new ArrayList<>()); }
+
+    // ========== 工作流类型 /workflow/lobster/types ==========
+    @GetMapping("/workflow/lobster/types")
+    public AjaxResult lobsterTypes() { return AjaxResult.success(new ArrayList<>()); }
+
+    // ========== 销冠语料补充端点 ==========
+    @PostMapping("/workflow/lobster/sales-corpus/batch-import")
+    public AjaxResult salesCorpusBatchImport(@RequestBody Map<String, Object> data) { return AjaxResult.success(new HashMap<>()); }
+
+    @PostMapping("/workflow/lobster/sales-corpus/dialog")
+    public AjaxResult salesCorpusDialog(@RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    // ========== 工作流执行 /workflow/lobster-exec ==========
+    @GetMapping("/workflow/lobster-exec/instance/list")
+    public TableDataInfo execInstanceList() { return emptyTable(); }
+
+    @GetMapping("/workflow/lobster-exec/instance/{instanceId}")
+    public AjaxResult execInstanceGet(@PathVariable String instanceId) { return AjaxResult.success(new HashMap<>()); }
+
+    @GetMapping("/workflow/lobster-exec/node-logs/{instanceId}")
+    public AjaxResult execNodeLogs(@PathVariable String instanceId) { return AjaxResult.success(new ArrayList<>()); }
+
+    @PostMapping("/workflow/lobster-exec/start")
+    public AjaxResult execStart() { return AjaxResult.success(); }
+
+    @PostMapping("/workflow/lobster-exec/next-node")
+    public AjaxResult execNextNode() { return AjaxResult.success(); }
+
+    @PostMapping("/workflow/lobster-exec/pause/{instanceId}")
+    public AjaxResult execPause(@PathVariable String instanceId) { return AjaxResult.success(); }
+
+    @PostMapping("/workflow/lobster-exec/resume/{instanceId}")
+    public AjaxResult execResume(@PathVariable String instanceId) { return AjaxResult.success(); }
+
+    @PostMapping("/workflow/lobster-exec/terminate/{instanceId}")
+    public AjaxResult execTerminate(@PathVariable String instanceId) { return AjaxResult.success(); }
+
+    @GetMapping("/workflow/lobster-exec/control-mode/{instanceId}")
+    public AjaxResult execControlMode(@PathVariable String instanceId) { return AjaxResult.success(new HashMap<>()); }
+
+    @PostMapping("/workflow/lobster-exec/control-mode/{instanceId}")
+    public AjaxResult execSetControlMode(@PathVariable String instanceId) { return AjaxResult.success(); }
+
+    @PostMapping("/workflow/lobster-exec/complete-handoff/{instanceId}")
+    public AjaxResult execCompleteHandoff(@PathVariable String instanceId) { return AjaxResult.success(); }
+
+    @GetMapping("/workflow/lobster-exec/compliance-rules")
+    public AjaxResult execComplianceRules() { return AjaxResult.success(new ArrayList<>()); }
+
+    @PostMapping("/workflow/lobster-exec/compliance-rule")
+    public AjaxResult execAddComplianceRule(@RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    @PutMapping("/workflow/lobster-exec/compliance-rule/{id}")
+    public AjaxResult execUpdateComplianceRule(@PathVariable Long id, @RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    @DeleteMapping("/workflow/lobster-exec/compliance-rule/{id}")
+    public AjaxResult execDeleteComplianceRule(@PathVariable Long id) { return AjaxResult.success(); }
+
+    // ========== 提示词补充端点 ==========
+    @GetMapping("/workflow/lobster/prompt/{id}")
+    public AjaxResult promptGet(@PathVariable Long id) { return AjaxResult.success(new HashMap<>()); }
+
+    @PostMapping("/workflow/lobster/prompt")
+    public AjaxResult promptAdd(@RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    @PutMapping("/workflow/lobster/prompt/{id}")
+    public AjaxResult promptUpdate(@PathVariable Long id, @RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    @DeleteMapping("/workflow/lobster/prompt/{id}")
+    public AjaxResult promptDelete(@PathVariable Long id) { return AjaxResult.success(); }
+
+    @PostMapping("/workflow/lobster/prompt/refresh-cache")
+    public AjaxResult promptRefreshCache() { return AjaxResult.success(); }
+
+    // ========== API注册补充端点 ==========
+    @PostMapping("/workflow/lobster/api-registry")
+    public AjaxResult apiRegistryAdd(@RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    @PostMapping("/workflow/lobster/api-registry/refresh")
+    public AjaxResult apiRegistryRefresh() { return AjaxResult.success(); }
+
+    @GetMapping("/workflow/lobster/api-registry/categories")
+    public AjaxResult apiRegistryCategories() { return AjaxResult.success(new ArrayList<>()); }
+
+    // ========== 优化建议补充端点 ==========
+    @GetMapping("/workflow/lobster/optimization/pending-audit")
+    public TableDataInfo optimizationPendingAudit() { return emptyTable(); }
+
+    @PostMapping("/workflow/lobster/optimization/audit/{optimizationId}")
+    public AjaxResult optimizationAudit(@PathVariable Long optimizationId) { return AjaxResult.success(); }
+
+    @PostMapping("/workflow/lobster/optimization/analyze")
+    public AjaxResult optimizationAnalyze() { return AjaxResult.success(new HashMap<>()); }
+
+    @GetMapping("/workflow/lobster/optimization/config")
+    public AjaxResult optimizationConfig() { return AjaxResult.success(new HashMap<>()); }
+
+    @PostMapping("/workflow/lobster/optimization/config")
+    public AjaxResult optimizationSetConfig(@RequestBody Map<String, Object> data) { return AjaxResult.success(); }
+
+    // ========== 死信队列补充端点 ==========
+    @GetMapping("/workflow/lobster/dead-letter/retry-all")
+    public AjaxResult deadLetterRetryAllPost() { return AjaxResult.success(); }
+
+}

+ 109 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/AdminModuleUsageController.java

@@ -0,0 +1,109 @@
+package com.fs.admin.controller;
+
+import java.util.List;
+import java.util.Map;
+import org.springframework.context.annotation.Profile;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.proxy.domain.TenantModuleUsage;
+import com.fs.proxy.service.TenantModuleUsageService;
+import com.fs.common.utils.poi.ExcelUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * Admin总后台-租户模块使用统计控制器
+ * 总后台可查看所有代理下所有租户的模块使用详情
+ * 支持按代理维度、租户维度筛选
+ */
+@Profile("admin")
+@RestController
+@RequestMapping({"/admin/moduleUsage", "/admin/module-usage"})
+public class AdminModuleUsageController extends BaseController {
+
+    @Autowired
+    private TenantModuleUsageService tenantModuleUsageService;
+
+    /**
+     * 导出租户模块使用统计
+     */
+    @PreAuthorize("@ss.hasPermi('admin:moduleUsage:list')")
+    @Log(title = "导出租户模块使用统计", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(TenantModuleUsage query) {
+        List<TenantModuleUsage> list = tenantModuleUsageService.selectTenantModuleUsageList(query);
+        ExcelUtil<TenantModuleUsage> util = new ExcelUtil<>(TenantModuleUsage.class);
+        return util.exportExcel(list, "租户模块使用统计");
+    }
+
+    /**
+     * 查询所有租户模块使用统计列表
+     */
+    @PreAuthorize("@ss.hasPermi('admin:moduleUsage:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(TenantModuleUsage query) {
+        startPage();
+        List<TenantModuleUsage> list = tenantModuleUsageService.selectTenantModuleUsageList(query);
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询租户模块使用统计汇总列表(每个租户一行,不按天展示)
+     */
+    @PreAuthorize("@ss.hasPermi('admin:moduleUsage:list')")
+    @GetMapping("/summary")
+    public TableDataInfo summary(TenantModuleUsage query) {
+        startPage();
+        List<TenantModuleUsage> list = tenantModuleUsageService.selectTenantModuleUsageSummary(query);
+        return getDataTable(list);
+    }
+
+    /**
+     * 按代理维度查看模块使用详情
+     * 返回:指定代理下所有租户的模块使用情况
+     */
+    @PreAuthorize("@ss.hasPermi('admin:moduleUsage:list')")
+    @GetMapping("/proxy/{proxyId}")
+    public AjaxResult proxyDetail(@PathVariable Long proxyId,
+                                  @RequestParam(required = false) String statDate) {
+        Map<String, Object> data = tenantModuleUsageService.getModuleUsageDetail(proxyId, statDate);
+        return AjaxResult.success(data);
+    }
+
+    /**
+     * 查询指定租户的最新统计详情
+     */
+    @PreAuthorize("@ss.hasPermi('admin:moduleUsage:list')")
+    @GetMapping("/tenant/{tenantId}")
+    public AjaxResult tenantDetail(@PathVariable Long tenantId) {
+        TenantModuleUsage usage = tenantModuleUsageService.selectLatestByTenantId(tenantId);
+        return AjaxResult.success(usage);
+    }
+
+    /**
+     * 手动触发全量统计汇总
+     */
+    @PreAuthorize("@ss.hasPermi('admin:moduleUsage:refresh')")
+    @Log(title = "租户模块使用统计", businessType = BusinessType.INSERT)
+    @PostMapping("/refresh")
+    public AjaxResult refreshStatistics() {
+        tenantModuleUsageService.executeDailyStatistics();
+        return AjaxResult.success("全量统计任务已触发");
+    }
+
+    /**
+     * 手动触发指定租户的统计汇总
+     */
+    @PreAuthorize("@ss.hasPermi('admin:moduleUsage:refresh')")
+    @Log(title = "租户模块使用统计", businessType = BusinessType.INSERT)
+    @PostMapping("/refresh/{tenantId}")
+    public AjaxResult refreshTenantStatistics(@PathVariable Long tenantId) {
+        tenantModuleUsageService.statisticsForTenant(tenantId);
+        return AjaxResult.success("租户" + tenantId + "统计已刷新");
+    }
+}

+ 105 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/AdminProxyController.java

@@ -0,0 +1,105 @@
+package com.fs.admin.controller;
+
+import java.util.List;
+import org.springframework.context.annotation.Profile;
+
+import com.fs.common.annotation.ProxyLog;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.proxy.domain.Proxy;
+import com.fs.proxy.service.ProxyService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 总后台-代理管理控制器
+ * 
+ * @author fs
+ * @date 2024-01-01
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/admin/proxy")
+public class AdminProxyController extends BaseController
+{
+    @Autowired
+    private ProxyService proxyService;
+
+    /**
+     * 查询代理列表
+     */
+    @PreAuthorize("@ss.hasPermi('admin:proxy:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(Proxy proxy) {
+        startPage();
+        List<Proxy> list = proxyService.selectProxyList(proxy);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取代理详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('admin:proxy:query')")
+    @GetMapping(value = "/{proxyId}")
+    public AjaxResult getInfo(@PathVariable("proxyId") Long proxyId) {
+        return AjaxResult.success(proxyService.selectProxyById(proxyId));
+    }
+
+    /**
+     * 新增代理
+     */
+    @PreAuthorize("@ss.hasPermi('admin:proxy:add')")
+    @ProxyLog(title = "代理管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody Proxy proxy) {
+        return toAjax(proxyService.insertProxy(proxy));
+    }
+
+    /**
+     * 修改代理
+     */
+    @PreAuthorize("@ss.hasPermi('admin:proxy:edit')")
+    @ProxyLog(title = "代理管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody Proxy proxy) {
+        return toAjax(proxyService.updateProxy(proxy));
+    }
+
+    /**
+     * 删除代理
+     */
+    @PreAuthorize("@ss.hasPermi('admin:proxy:remove')")
+    @ProxyLog(title = "代理管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{proxyIds}")
+    public AjaxResult remove(@PathVariable Long[] proxyIds) {
+        return toAjax(proxyService.deleteProxyByIds(proxyIds));
+    }
+
+    /**
+     * 启用/禁用代理
+     */
+    @PreAuthorize("@ss.hasPermi('admin:proxy:edit')")
+    @ProxyLog(title = "代理管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/changeStatus/{proxyId}")
+    public AjaxResult changeStatus(@PathVariable Long proxyId, @RequestParam Integer status) {
+        Proxy proxy = new Proxy();
+        proxy.setProxyId(proxyId);
+        proxy.setStatus(status);
+        return toAjax(proxyService.updateProxy(proxy));
+    }
+
+    /**
+     * 获取所有启用的代理(下拉列表用)
+     */
+    @PreAuthorize("@ss.hasPermi('admin:proxy:query')")
+    @GetMapping("/allEnabled")
+    public AjaxResult allEnabled() {
+        Proxy query = new Proxy();
+        query.setStatus(1);
+        List<Proxy> list = proxyService.selectProxyList(query);
+        return AjaxResult.success(list);
+    }
+}

+ 182 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/AdminRechargeRecordController.java

@@ -0,0 +1,182 @@
+package com.fs.admin.controller;
+
+import java.util.*;
+import org.springframework.context.annotation.Profile;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.fs.admin.vo.RechargeRecordExportVO;
+import com.fs.billing.domain.TenantWalletTxn;
+import com.fs.billing.mapper.TenantWalletTxnMapper;
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.domain.Company;
+import com.fs.company.mapper.CompanyMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 总后台-租户充值记录控制器
+ * 数据来源:tenant_wallet_txn (txn_type='RECHARGE')
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/admin/recharge-record")
+public class AdminRechargeRecordController extends BaseController {
+
+    @Autowired
+    private TenantWalletTxnMapper tenantWalletTxnMapper;
+
+    @Autowired
+    private CompanyMapper companyMapper;
+
+    /**
+     * 查询充值记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('admin:rechargeRecord:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(@RequestParam(required = false) String tenantName,
+                              @RequestParam(required = false) String createBy,
+                              @RequestParam(required = false) String beginTime,
+                              @RequestParam(required = false) String endTime) {
+        startPage();
+        LambdaQueryWrapper<TenantWalletTxn> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(TenantWalletTxn::getTxnType, "RECHARGE");
+        wrapper.orderByDesc(TenantWalletTxn::getCreateTime);
+
+        if (tenantName != null && !tenantName.isEmpty()) {
+            Company query = new Company();
+            query.setCompanyName(tenantName);
+            List<Company> companies = companyMapper.selectCompanyList(query);
+            List<Long> tenantIds = new ArrayList<>();
+            for (Company c : companies) {
+                tenantIds.add(c.getCompanyId());
+            }
+            if (tenantIds.isEmpty()) {
+                return getDataTable(new ArrayList<>());
+            }
+            wrapper.in(TenantWalletTxn::getTenantId, tenantIds);
+        }
+        if (createBy != null && !createBy.isEmpty()) {
+            wrapper.like(TenantWalletTxn::getCreateBy, createBy);
+        }
+        if (beginTime != null && !beginTime.isEmpty()) {
+            wrapper.ge(TenantWalletTxn::getCreateTime, beginTime + " 00:00:00");
+        }
+        if (endTime != null && !endTime.isEmpty()) {
+            wrapper.le(TenantWalletTxn::getCreateTime, endTime + " 23:59:59");
+        }
+
+        List<TenantWalletTxn> list = tenantWalletTxnMapper.selectList(wrapper);
+        List<Map<String, Object>> resultList = new ArrayList<>();
+        if (!list.isEmpty()) {
+            List<Long> tenantIds = new ArrayList<>();
+            for (TenantWalletTxn txn : list) {
+                tenantIds.add(txn.getTenantId());
+            }
+            Map<Long, String> nameMap = new HashMap<>();
+            List<Company> companies = companyMapper.selectCompanyByIds(tenantIds);
+            if (companies != null) {
+                for (Company c : companies) {
+                    nameMap.put(c.getCompanyId(), c.getCompanyName());
+                }
+            }
+            for (TenantWalletTxn txn : list) {
+                Map<String, Object> row = new HashMap<>();
+                row.put("id", txn.getId());
+                row.put("tenantId", txn.getTenantId());
+                row.put("tenantName", nameMap.getOrDefault(txn.getTenantId(), ""));
+                row.put("amount", txn.getAmount());
+                row.put("balanceAfter", txn.getBalanceAfter());
+                row.put("payMethod", txn.getBizType());
+                row.put("createBy", txn.getCreateBy());
+                row.put("createTime", txn.getCreateTime());
+                row.put("remark", txn.getRemark());
+                resultList.add(row);
+            }
+        }
+        return getDataTable(resultList);
+    }
+
+    /**
+     * 导出充値记录
+     */
+    @Log(title = "导出充値记录", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:rechargeRecord:list')")
+    @GetMapping("/export")
+    public AjaxResult export(@RequestParam(required = false) String tenantName,
+                             @RequestParam(required = false) String createBy,
+                             @RequestParam(required = false) String beginTime,
+                             @RequestParam(required = false) String endTime) {
+        LambdaQueryWrapper<TenantWalletTxn> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(TenantWalletTxn::getTxnType, "RECHARGE");
+        wrapper.orderByDesc(TenantWalletTxn::getCreateTime);
+        if (tenantName != null && !tenantName.isEmpty()) {
+            Company query = new Company();
+            query.setCompanyName(tenantName);
+            List<Company> companies = companyMapper.selectCompanyList(query);
+            List<Long> tenantIds = new ArrayList<>();
+            for (Company c : companies) { tenantIds.add(c.getCompanyId()); }
+            if (tenantIds.isEmpty()) {
+                return new ExcelUtil<>(RechargeRecordExportVO.class).exportExcel(new ArrayList<>(), "充値记录数据");
+            }
+            wrapper.in(TenantWalletTxn::getTenantId, tenantIds);
+        }
+        if (createBy != null && !createBy.isEmpty()) { wrapper.like(TenantWalletTxn::getCreateBy, createBy); }
+        if (beginTime != null && !beginTime.isEmpty()) { wrapper.ge(TenantWalletTxn::getCreateTime, beginTime + " 00:00:00"); }
+        if (endTime != null && !endTime.isEmpty()) { wrapper.le(TenantWalletTxn::getCreateTime, endTime + " 23:59:59"); }
+        List<TenantWalletTxn> txnList = tenantWalletTxnMapper.selectList(wrapper);
+        List<Long> tids = new ArrayList<>();
+        for (TenantWalletTxn t : txnList) { tids.add(t.getTenantId()); }
+        Map<Long, String> nameMap = new HashMap<>();
+        if (!tids.isEmpty()) {
+            List<Company> cs = companyMapper.selectCompanyByIds(tids);
+            if (cs != null) { for (Company c : cs) { nameMap.put(c.getCompanyId(), c.getCompanyName()); } }
+        }
+        List<RechargeRecordExportVO> exportList = new ArrayList<>();
+        for (TenantWalletTxn txn : txnList) {
+            RechargeRecordExportVO vo = new RechargeRecordExportVO();
+            vo.setId(txn.getId());
+            vo.setTenantId(txn.getTenantId());
+            vo.setTenantName(nameMap.getOrDefault(txn.getTenantId(), ""));
+            vo.setAmount(txn.getAmount());
+            vo.setBalanceAfter(txn.getBalanceAfter());
+            vo.setPayMethod(txn.getBizType());
+            vo.setCreateBy(txn.getCreateBy());
+            vo.setCreateTime(txn.getCreateTime());
+            vo.setRemark(txn.getRemark());
+            exportList.add(vo);
+        }
+        ExcelUtil<RechargeRecordExportVO> util = new ExcelUtil<>(RechargeRecordExportVO.class);
+        return util.exportExcel(exportList, "充値记录数据");
+    }
+    
+    /**
+     * 获取充値记录详情
+     */
+    @PreAuthorize("@ss.hasPermi('admin:rechargeRecord:list')")
+    @GetMapping("/{id}")
+    public AjaxResult getInfo(@PathVariable Long id) {
+        TenantWalletTxn txn = tenantWalletTxnMapper.selectById(id);
+        if (txn != null) {
+            Company company = companyMapper.selectCompanyById(txn.getTenantId());
+            Map<String, Object> result = new HashMap<>();
+            result.put("id", txn.getId());
+            result.put("tenantId", txn.getTenantId());
+            result.put("tenantName", company != null ? company.getCompanyName() : "");
+            result.put("amount", txn.getAmount());
+            result.put("balanceAfter", txn.getBalanceAfter());
+            result.put("payMethod", txn.getBizType());
+            result.put("txnNo", txn.getTxnNo());
+            result.put("createBy", txn.getCreateBy());
+            result.put("createTime", txn.getCreateTime());
+            result.put("remark", txn.getRemark());
+            return AjaxResult.success(result);
+        }
+        return AjaxResult.error("记录不存在");
+    }
+}

+ 72 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/AdminServiceCostController.java

@@ -0,0 +1,72 @@
+package com.fs.admin.controller;
+
+import java.util.List;
+import org.springframework.context.annotation.Profile;
+
+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.proxy.domain.ServiceFeeConfig;
+import com.fs.proxy.service.BalanceService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 总后台-服务成本价管理控制器
+ * 管理平台级各服务类型的成本价和租户售价
+ *
+ * @author fs
+ * @date 2024-01-01
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/admin/serviceCost")
+public class AdminServiceCostController extends BaseController
+{
+    @Autowired
+    private BalanceService balanceService;
+
+    /**
+     * 获取服务成本配置列表(所有9项服务)
+     */
+    @PreAuthorize("@ss.hasPermi('admin:serviceCost:list')")
+    @GetMapping("/list")
+    public AjaxResult list() {
+        List<ServiceFeeConfig> list = balanceService.getAllFeeConfigs();
+        return AjaxResult.success(list);
+    }
+
+    /**
+     * 根据服务类型获取成本配置
+     */
+    @PreAuthorize("@ss.hasPermi('admin:serviceCost:query')")
+    @GetMapping("/{serviceType}")
+    public AjaxResult getByType(@PathVariable Integer serviceType) {
+        ServiceFeeConfig config = balanceService.getFeeConfig(serviceType);
+        return AjaxResult.success(config);
+    }
+
+    /**
+     * 更新服务成本配置
+     */
+    @PreAuthorize("@ss.hasPermi('admin:serviceCost:edit')")
+    @Log(title = "服务成本配置", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody ServiceFeeConfig config) {
+        boolean result = balanceService.updateFeeConfig(config);
+        return result ? AjaxResult.success("更新成功") : AjaxResult.error("更新失败");
+    }
+
+    /**
+     * 获取所有启用的服务类型简要列表(下拉选择用)
+     */
+    @PreAuthorize("@ss.hasPermi('admin:serviceCost:query')")
+    @GetMapping("/enabled")
+    public AjaxResult getEnabled() {
+        List<ServiceFeeConfig> all = balanceService.getAllFeeConfigs();
+        all.removeIf(c -> c.getEnabled() == null || c.getEnabled() == 0);
+        return AjaxResult.success(all);
+    }
+}

+ 116 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/AdminSopController.java

@@ -0,0 +1,116 @@
+package com.fs.admin.controller;
+
+import java.util.*;
+import org.springframework.context.annotation.Profile;
+
+import com.fs.admin.helper.AdminCrossTenantHelper;
+import com.fs.common.annotation.Excel;
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import lombok.Data;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 总后台SOP工作流管理控制器
+ * 遍历所有租户库查询 qw_sop / qw_sop_temp 数据
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/admin/sop")
+public class AdminSopController extends BaseController {
+
+    @Autowired
+    private AdminCrossTenantHelper crossTenantHelper;
+
+    private String str(Object o) { return o == null ? "" : o.toString(); }
+
+    @Data
+    public static class SopExportVO {
+        @Excel(name = "租户名称") private String companyName;
+        @Excel(name = "SOP名称") private String name;
+        @Excel(name = "类型") private String type;
+        @Excel(name = "状态") private String status;
+        @Excel(name = "创建时间") private String createTime;
+    }
+
+    @Log(title = "导出SOP", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:sop:list')")
+    @GetMapping("/export")
+    public AjaxResult export(@RequestParam(required = false) String sopName,
+                             @RequestParam(required = false) Long companyId,
+                             @RequestParam(required = false) String companyName) {
+        List<Map<String, Object>> allList = crossTenantHelper.queryAcrossTenants(
+                companyId, companyName, (tenant, jdbc) -> {
+                    StringBuilder sql = new StringBuilder("SELECT qs.* FROM qw_sop qs WHERE 1=1");
+                    List<Object> params = new ArrayList<>();
+                    if (sopName != null && !sopName.isEmpty()) {
+                        sql.append(" AND qs.name LIKE ?");
+                        params.add("%" + sopName + "%");
+                    }
+                    sql.append(" ORDER BY qs.create_time DESC");
+                    return params.isEmpty()
+                            ? jdbc.queryForList(sql.toString())
+                            : jdbc.queryForList(sql.toString(), params.toArray());
+                });
+        List<SopExportVO> voList = new ArrayList<>();
+        for (Map<String, Object> m : allList) {
+            SopExportVO vo = new SopExportVO();
+            vo.setCompanyName(str(m.get("company_name")));
+            vo.setName(str(m.get("name")));
+            vo.setType(str(m.get("type")));
+            vo.setStatus(str(m.get("status")));
+            vo.setCreateTime(str(m.get("create_time")));
+            voList.add(vo);
+        }
+        ExcelUtil<SopExportVO> util = new ExcelUtil<>(SopExportVO.class);
+        return util.exportExcel(voList, "SOP数据");
+    }
+
+    @PreAuthorize("@ss.hasPermi('admin:sop:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(@RequestParam(required = false) String sopName,
+                              @RequestParam(required = false) Long companyId,
+                              @RequestParam(required = false) String companyName) {
+        List<Map<String, Object>> allList = crossTenantHelper.queryAcrossTenants(
+                companyId, companyName, (tenant, jdbc) -> {
+                    StringBuilder sql = new StringBuilder("SELECT qs.* FROM qw_sop qs WHERE 1=1");
+                    List<Object> params = new ArrayList<>();
+                    if (sopName != null && !sopName.isEmpty()) {
+                        sql.append(" AND qs.name LIKE ?");
+                        params.add("%" + sopName + "%");
+                    }
+                    sql.append(" ORDER BY qs.create_time DESC LIMIT 200");
+                    return params.isEmpty()
+                            ? jdbc.queryForList(sql.toString())
+                            : jdbc.queryForList(sql.toString(), params.toArray());
+                });
+        return getDataTable(allList);
+    }
+
+    @PreAuthorize("@ss.hasPermi('admin:sop:list')")
+    @GetMapping("/temp/list")
+    public TableDataInfo tempList(@RequestParam(required = false) String tempName,
+                                  @RequestParam(required = false) Long companyId,
+                                  @RequestParam(required = false) String companyName) {
+        List<Map<String, Object>> allList = crossTenantHelper.queryAcrossTenants(
+                companyId, companyName, (tenant, jdbc) -> {
+                    StringBuilder sql = new StringBuilder("SELECT * FROM qw_sop_temp WHERE 1=1");
+                    List<Object> params = new ArrayList<>();
+                    if (tempName != null && !tempName.isEmpty()) {
+                        sql.append(" AND name LIKE ?");
+                        params.add("%" + tempName + "%");
+                    }
+                    sql.append(" ORDER BY create_time DESC LIMIT 200");
+                    return params.isEmpty()
+                            ? jdbc.queryForList(sql.toString())
+                            : jdbc.queryForList(sql.toString(), params.toArray());
+                });
+        return getDataTable(allList);
+    }
+}

+ 399 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/AdminStatisticsController.java

@@ -0,0 +1,399 @@
+package com.fs.admin.controller;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.springframework.context.annotation.Profile;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.proxy.domain.PlatformStatistics;
+import com.fs.proxy.domain.Proxy;
+import com.fs.proxy.domain.ProxyServicePrice;
+import com.fs.proxy.domain.ProxyTenantRel;
+import com.fs.proxy.domain.TenantConsumeRecord;
+import com.fs.proxy.service.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 总后台统计控制器(统一使用 platform_statistics 表)
+ * 
+ * @author fs
+ * @date 2024-01-01
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/admin/statistics")
+public class AdminStatisticsController extends BaseController
+{
+    @Autowired
+    private ProxyTenantRelService proxyTenantRelService;
+
+    @Autowired
+    private TenantConsumeService tenantConsumeService;
+
+    @Autowired
+    private ProxyServicePriceService proxyServicePriceService;
+
+    @Autowired
+    private PlatformStatisticsService platformStatisticsService;
+
+    @Autowired
+    private ProxyService proxyService;
+
+    /**
+     * 获取平台总览统计
+     */
+    @PreAuthorize("@ss.hasPermi('admin:statistics:overview')")
+    @GetMapping("/overview")
+    public AjaxResult getOverview() {
+        Map<String, Object> result = new HashMap<>();
+        
+        List<ProxyTenantRel> allRelations = proxyTenantRelService.selectProxyTenantRelList(null);
+        result.put("totalTenantCount", allRelations.size());
+        
+        long proxyCount = allRelations.stream().map(ProxyTenantRel::getProxyId).distinct().count();
+        result.put("totalProxyCount", proxyCount);
+        
+        Date today = new java.sql.Date(System.currentTimeMillis());
+        List<PlatformStatistics> todayStats = platformStatisticsService.getHourlyStats(today, null);
+        BigDecimal todayConsume = todayStats.stream()
+            .map(s -> s.getConsumeAmount() != null ? s.getConsumeAmount() : BigDecimal.ZERO)
+            .reduce(BigDecimal.ZERO, BigDecimal::add);
+        BigDecimal todayProfit = todayStats.stream()
+            .map(s -> s.getProxyProfit() != null ? s.getProxyProfit() : BigDecimal.ZERO)
+            .reduce(BigDecimal.ZERO, BigDecimal::add);
+        result.put("todayConsume", todayConsume);
+        result.put("todayProfit", todayProfit);
+        
+        // 成本配置数
+        List<ProxyServicePrice> priceList = proxyServicePriceService.selectProxyServicePriceList(null);
+        result.put("serviceTypeCount", priceList.size());
+        
+        return AjaxResult.success(result);
+    }
+
+    /**
+     * 获取代理分佣统计
+     */
+    @PreAuthorize("@ss.hasPermi('admin:statistics:proxyProfit')")
+    @GetMapping("/proxyProfit")
+    public TableDataInfo getProxyProfitStatistics(@RequestParam(required = false) String startDate,
+                                                  @RequestParam(required = false) String endDate) {
+        Date start = startDate != null ? java.sql.Date.valueOf(startDate) : null;
+        Date end = endDate != null ? java.sql.Date.valueOf(endDate) : null;
+        List<PlatformStatistics> stats = platformStatisticsService.getProxyProfitStats(start, end);
+        return getDataTable(stats);
+    }
+
+    /**
+     * 获取租户消费统计
+     */
+    @PreAuthorize("@ss.hasPermi('admin:statistics:tenantConsume')")
+    @GetMapping("/tenantConsume")
+    public TableDataInfo getTenantConsumeStatistics(@RequestParam(required = false) String startDate,
+                                                     @RequestParam(required = false) String endDate) {
+        Date start = startDate != null ? java.sql.Date.valueOf(startDate) : null;
+        Date end = endDate != null ? java.sql.Date.valueOf(endDate) : null;
+        List<PlatformStatistics> stats = platformStatisticsService.getTenantConsumeStats(start, end);
+        return getDataTable(stats);
+    }
+
+    /**
+     * 获取消费类型统计
+     */
+    @PreAuthorize("@ss.hasPermi('admin:statistics:consumeType')")
+    @GetMapping("/consumeType")
+    public AjaxResult getConsumeTypeStatistics(@RequestParam(required = false) String startDate,
+                                               @RequestParam(required = false) String endDate) {
+        Date start = startDate != null ? java.sql.Date.valueOf(startDate) : null;
+        Date end = endDate != null ? java.sql.Date.valueOf(endDate) : null;
+        List<PlatformStatistics> stats = platformStatisticsService.getConsumeTypeStats(start, end);
+        
+        Map<String, Object> result = new HashMap<>();
+        Map<String, BigDecimal> consume = new HashMap<>();
+        Map<String, BigDecimal> cost = new HashMap<>();
+        Map<String, BigDecimal> profit = new HashMap<>();
+        for (PlatformStatistics s : stats) {
+            String t = s.getConsumeType() != null ? s.getConsumeType() : "未知";
+            consume.put(t, s.getConsumeAmount());
+            cost.put(t, s.getPlatformCost());
+            profit.put(t, s.getProxyProfit());
+        }
+        result.put("consumeByType", consume);
+        result.put("costByType", cost);
+        result.put("profitByType", profit);
+        return AjaxResult.success(result);
+    }
+
+    /**
+     * 获取平台成本汇总
+     */
+    @PreAuthorize("@ss.hasPermi('admin:statistics:costSummary')")
+    @GetMapping("/costSummary")
+    public AjaxResult getCostSummary(@RequestParam(required = false) String startDate,
+                                     @RequestParam(required = false) String endDate) {
+        Date start = startDate != null ? java.sql.Date.valueOf(startDate) : null;
+        Date end = endDate != null ? java.sql.Date.valueOf(endDate) : null;
+        
+        List<PlatformStatistics> typeStats = platformStatisticsService.getConsumeTypeStats(start, end);
+        List<PlatformStatistics> proxyStats = platformStatisticsService.getProxyProfitStats(start, end);
+
+        BigDecimal totalCost = typeStats.stream()
+            .map(s -> s.getPlatformCost() != null ? s.getPlatformCost() : BigDecimal.ZERO)
+            .reduce(BigDecimal.ZERO, BigDecimal::add);
+        BigDecimal totalConsume = typeStats.stream()
+            .map(s -> s.getConsumeAmount() != null ? s.getConsumeAmount() : BigDecimal.ZERO)
+            .reduce(BigDecimal.ZERO, BigDecimal::add);
+        BigDecimal totalProfit = proxyStats.stream()
+            .map(s -> s.getProxyProfit() != null ? s.getProxyProfit() : BigDecimal.ZERO)
+            .reduce(BigDecimal.ZERO, BigDecimal::add);
+        
+        Map<String, Object> result = new HashMap<>();
+        result.put("totalCost", totalCost);
+        result.put("totalConsume", totalConsume);
+        result.put("totalProfit", totalProfit);
+        result.put("platformRevenue", totalConsume.subtract(totalCost).subtract(totalProfit));
+        return AjaxResult.success(result);
+    }
+
+    /**
+     * 获取代理分佣详情
+     */
+    @PreAuthorize("@ss.hasPermi('admin:statistics:proxyProfit')")
+    @GetMapping("/proxyProfit/{proxyId}")
+    public AjaxResult getProxyProfitDetail(@PathVariable Long proxyId,
+                                           @RequestParam(required = false) String startDate,
+                                           @RequestParam(required = false) String endDate) {
+        Date start = startDate != null ? java.sql.Date.valueOf(startDate) : null;
+        Date end = endDate != null ? java.sql.Date.valueOf(endDate) : null;
+        
+        Map<String, Object> result = new HashMap<>();
+        List<ProxyTenantRel> tenantList = proxyTenantRelService.selectProxyTenantRelByProxyId(proxyId);
+        result.put("tenantList", tenantList);
+        
+        List<PlatformStatistics> detail = platformStatisticsService.getProxyDetail(proxyId, start, end);
+        BigDecimal total = detail.stream()
+            .map(s -> s.getProxyProfit() != null ? s.getProxyProfit() : BigDecimal.ZERO)
+            .reduce(BigDecimal.ZERO, BigDecimal::add);
+        result.put("totalProfit", total);
+        result.put("details", detail);
+        return AjaxResult.success(result);
+    }
+
+    /**
+     * 获取租户消费详情
+     */
+    @PreAuthorize("@ss.hasPermi('admin:statistics:tenantConsume')")
+    @GetMapping("/tenantDetail/{tenantId}")
+    public AjaxResult getTenantDetail(@PathVariable Long tenantId,
+                                      @RequestParam(required = false) String startDate,
+                                      @RequestParam(required = false) String endDate) {
+        Date start = startDate != null ? java.sql.Date.valueOf(startDate) : null;
+        Date end = endDate != null ? java.sql.Date.valueOf(endDate) : null;
+        List<PlatformStatistics> detail = platformStatisticsService.getTenantDetail(tenantId, start, end);
+        return AjaxResult.success(detail);
+    }
+
+    /**
+     * 获取平台趋势
+     */
+    @PreAuthorize("@ss.hasPermi('admin:statistics:trend')")
+    @GetMapping("/trend")
+    public AjaxResult getPlatformTrend(@RequestParam(required = false) String startDate,
+                                       @RequestParam(required = false) String endDate) {
+        Date start = startDate != null ? java.sql.Date.valueOf(startDate) : null;
+        Date end = endDate != null ? java.sql.Date.valueOf(endDate) : null;
+        List<PlatformStatistics> trend = platformStatisticsService.getTrend(start, end);
+        return AjaxResult.success(trend);
+    }
+
+    /**
+     * 获取小时统计
+     */
+    @PreAuthorize("@ss.hasPermi('admin:statistics:hourly')")
+    @GetMapping("/hourly")
+    public AjaxResult getHourlyStatistics(@RequestParam(required = false) String statDate,
+                                          @RequestParam(required = false) String dimension) {
+        if (statDate == null || statDate.isEmpty()) {
+            statDate = java.time.LocalDate.now().toString();
+        }
+        Date date = java.sql.Date.valueOf(statDate);
+        List<PlatformStatistics> stats = platformStatisticsService.getHourlyStats(date, dimension);
+        return AjaxResult.success(stats);
+    }
+
+    /**
+     * 获取服务成本配置
+     */
+    @PreAuthorize("@ss.hasPermi('admin:statistics:costConfig')")
+    @GetMapping("/costConfig")
+    public AjaxResult getCostConfigList() {
+        return AjaxResult.success(proxyServicePriceService.selectProxyServicePriceList(null));
+    }
+
+    /**
+     * 获取原始消费记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('admin:statistics:tenantConsume')")
+    @GetMapping("/consumeRecords")
+    public TableDataInfo getConsumeRecords(TenantConsumeRecord query) {
+        startPage();
+        List<TenantConsumeRecord> list = tenantConsumeService.selectTenantConsumeRecordList(query);
+        return getDataTable(list);
+    }
+
+    /**
+     * 手动执行统计
+     */
+    @PreAuthorize("@ss.hasPermi('admin:statistics:execute')")
+    @PostMapping("/execute")
+    public AjaxResult executeStatistics(@RequestParam(required = false) String type) {
+        if ("daily".equals(type)) {
+            platformStatisticsService.executeDailySummary();
+            return AjaxResult.success("日汇总执行成功");
+        } else if ("monthly".equals(type)) {
+            platformStatisticsService.executeMonthlySummary();
+            return AjaxResult.success("月汇总执行成功");
+        } else {
+            platformStatisticsService.executeHourlyStats();
+            return AjaxResult.success("小时统计执行成功");
+        }
+    }
+
+    // ==================== 每日统计(代理维度) ====================
+
+    /**
+     * 每日统计 - 代理下租户消费明细
+     * 按天统计指定代理下每个租户的消费金额、成本、利润
+     */
+    @PreAuthorize("@ss.hasPermi('admin:statistics:daily')")
+    @GetMapping("/daily/agentTenants")
+    public AjaxResult getDailyAgentTenants(
+            @RequestParam(required = false) Long proxyId,
+            @RequestParam(required = false) String statDate) {
+        Date date = statDate != null ? java.sql.Date.valueOf(statDate)
+                : new java.sql.Date(System.currentTimeMillis());
+
+        // 获取该代理下的所有租户
+        List<ProxyTenantRel> tenantRels = proxyTenantRelService.selectProxyTenantRelByProxyId(proxyId);
+        List<Long> tenantIds = new java.util.ArrayList<>();
+        for (ProxyTenantRel rel : tenantRels) {
+            tenantIds.add(rel.getTenantId());
+        }
+
+        // 查询这些租户的日统计
+        List<PlatformStatistics> stats = platformStatisticsService.getTenantDailyByProxy(proxyId, tenantIds, date);
+
+        // 补充租户名称
+        Map<Long, String> tenantNameMap = new java.util.HashMap<>();
+        for (ProxyTenantRel rel : tenantRels) {
+            tenantNameMap.put(rel.getTenantId(), rel.getTenantName());
+        }
+        for (PlatformStatistics s : stats) {
+            if (s.getTenantId() != null && tenantNameMap.containsKey(s.getTenantId())) {
+                s.setRemark(tenantNameMap.get(s.getTenantId()));
+            }
+        }
+
+        // 计算汇总
+        BigDecimal totalConsume = stats.stream()
+                .map(s -> s.getConsumeAmount() != null ? s.getConsumeAmount() : BigDecimal.ZERO)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+        BigDecimal totalCost = stats.stream()
+                .map(s -> s.getPlatformCost() != null ? s.getPlatformCost() : BigDecimal.ZERO)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+        BigDecimal totalProfit = stats.stream()
+                .map(s -> s.getTenantPrice() != null ? s.getTenantPrice() : BigDecimal.ZERO)
+                .reduce(BigDecimal.ZERO, BigDecimal::add)
+                .subtract(totalCost);
+        BigDecimal totalAgentProfit = stats.stream()
+                .map(s -> s.getProxyProfit() != null ? s.getProxyProfit() : BigDecimal.ZERO)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("statDate", statDate);
+        result.put("proxyId", proxyId);
+        result.put("tenantCount", tenantRels.size());
+        result.put("totalConsume", totalConsume);
+        result.put("totalCost", totalCost);
+        result.put("totalProfit", totalProfit);
+        result.put("totalAgentProfit", totalAgentProfit);
+        result.put("items", stats);
+        result.put("tenantNames", tenantNameMap);
+        return AjaxResult.success(result);
+    }
+
+    /**
+     * 每日统计 - 各服务类型利润
+     * 按天统计指定代理下各服务类型的消费、成本、利润
+     */
+    @PreAuthorize("@ss.hasPermi('admin:statistics:daily')")
+    @GetMapping("/daily/serviceTypeProfit")
+    public AjaxResult getDailyServiceTypeProfit(
+            @RequestParam(required = false) Long proxyId,
+            @RequestParam(required = false) String statDate) {
+        Date date = statDate != null ? java.sql.Date.valueOf(statDate)
+                : new java.sql.Date(System.currentTimeMillis());
+
+        List<Long> tenantIds = null;
+        if (proxyId != null) {
+            List<ProxyTenantRel> tenantRels = proxyTenantRelService.selectProxyTenantRelByProxyId(proxyId);
+            tenantIds = new java.util.ArrayList<>();
+            for (ProxyTenantRel rel : tenantRels) {
+                tenantIds.add(rel.getTenantId());
+            }
+        }
+
+        List<PlatformStatistics> stats = platformStatisticsService.getTypeDailyByProxyTenants(tenantIds, date);
+
+        BigDecimal totalProfit = stats.stream()
+                .map(s -> {
+                    BigDecimal ca = s.getConsumeAmount() != null ? s.getConsumeAmount() : BigDecimal.ZERO;
+                    BigDecimal pc = s.getPlatformCost() != null ? s.getPlatformCost() : BigDecimal.ZERO;
+                    return ca.subtract(pc);
+                })
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("statDate", statDate);
+        result.put("proxyId", proxyId);
+        result.put("totalProfit", totalProfit);
+        result.put("items", stats);
+        return AjaxResult.success(result);
+    }
+
+    /**
+     * 每日统计 - 代理汇总
+     * 按天汇总所有代理的消费、成本、利润
+     */
+    @PreAuthorize("@ss.hasPermi('admin:statistics:daily')")
+    @GetMapping("/daily/agentSummary")
+    public AjaxResult getDailyAgentSummary(
+            @RequestParam(required = false) String statDate) {
+        Date date = statDate != null ? java.sql.Date.valueOf(statDate)
+                : new java.sql.Date(System.currentTimeMillis());
+
+        List<PlatformStatistics> stats = platformStatisticsService.getAgentDailySummary(date);
+
+        // 补充代理名称
+        List<Proxy> allProxies = proxyService.selectProxyList(null);
+        Map<Long, String> proxyNameMap = new java.util.HashMap<>();
+        for (Proxy p : allProxies) {
+            proxyNameMap.put(p.getProxyId(), p.getProxyName());
+        }
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("statDate", statDate);
+        result.put("proxyCount", stats.size());
+        result.put("items", stats);
+        result.put("proxyNames", proxyNameMap);
+        return AjaxResult.success(result);
+    }
+}

+ 335 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/AdminStoreMiscController.java

@@ -0,0 +1,335 @@
+package com.fs.admin.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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.hisStore.domain.*;
+import com.fs.hisStore.service.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.*;
+
+/**
+ * 商城运营/优惠券/物流/门店管理 - adminui端Controller(fs-admin 8004)
+ * 桥接 hisStore 服务层,提供 /store/* 运营相关路径的完整CRUD
+ */
+@Profile("admin")
+@RestController
+public class AdminStoreMiscController extends BaseController {
+
+    @Autowired(required = false)
+    private JdbcTemplate jdbcTemplate;
+
+    private TableDataInfo safeListFromTable(String table) {
+        TableDataInfo r = new TableDataInfo();
+        r.setCode(200);
+        r.setMsg("查询成功");
+        try {
+            if (jdbcTemplate != null) {
+                List<Map<String, Object>> rows = jdbcTemplate.queryForList(
+                        "SELECT * FROM " + table + " ORDER BY 1 DESC LIMIT 200");
+                r.setRows(rows);
+                r.setTotal(rows.size());
+            } else {
+                r.setRows(new ArrayList<>());
+                r.setTotal(0);
+            }
+        } catch (Exception e) {
+            r.setRows(new ArrayList<>());
+            r.setTotal(0);
+        }
+        return r;
+    }
+
+    @Autowired
+    private IFsStoreCouponScrmService storeCouponService;
+    @Autowired
+    private IFsStoreCouponIssueScrmService storeCouponIssueService;
+    @Autowired
+    private IFsStoreCouponIssueUserScrmService storeCouponIssueUserService;
+    @Autowired
+    private IFsStoreCouponUserScrmService storeCouponUserService;
+    @Autowired
+    private IFsStoreActivityScrmService storeActivityService;
+    @Autowired
+    private IFsShippingTemplatesScrmService shippingTemplatesService;
+    @Autowired
+    private IFsShippingTemplatesFreeScrmService shippingTemplatesFreeService;
+    @Autowired
+    private IFsShippingTemplatesRegionScrmService shippingTemplatesRegionService;
+    @Autowired
+    private IFsStoreCartScrmService storeCartService;
+    @Autowired
+    private IFsStoreShopScrmService storeShopService;
+    @Autowired
+    private IFsStoreShopStaffScrmService storeShopStaffService;
+    @Autowired
+    private IFsStoreVisitScrmService storeVisitService;
+    @Autowired
+    private IFsStoreRecommendScrmService storeRecommendService;
+    @Autowired
+    private IFsUserPromoterApplyScrmService userPromoterApplyService;
+    @Autowired
+    private IFsMenuScrmService menuScrmService;
+    @Autowired
+    private IFsPrescribeScrmService prescribeService;
+    @Autowired
+    private IFsPrescribeDrugScrmService prescribeDrugService;
+    @Autowired
+    private IFsAdvScrmService advScrmService;
+    @Autowired
+    private IFsHomeArticleScrmService homeArticleService;
+    @Autowired
+    private IFsHomeArticleCategoryScrmService homeArticleCategoryService;
+    @Autowired
+    private IFsHomeArticleViewScrmService homeArticleViewService;
+    @Autowired
+    private IFsStoreScrmService storeScrmService;
+
+    // storeCoupon CRUD已迁移至 FsStoreCouponScrmBridgeController (fs-admin模块)
+
+    // ========== 优惠券发行 /store/storeCouponIssue ==========
+    @PreAuthorize("@ss.hasPermi('store:storeCouponIssue:list')")
+    @GetMapping("/store/storeCouponIssue/list")
+    public TableDataInfo storeCouponIssueList(FsStoreCouponIssueScrm param) {
+        startPage();
+        List<FsStoreCouponIssueScrm> list = storeCouponIssueService.selectFsStoreCouponIssueList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 发行用户 /store/storeCouponIssueUser ==========
+    @PreAuthorize("@ss.hasPermi('store:storeCouponIssueUser:list')")
+    @GetMapping("/store/storeCouponIssueUser/list")
+    public TableDataInfo storeCouponIssueUserList(FsStoreCouponIssueUserScrm param) {
+        startPage();
+        List<FsStoreCouponIssueUserScrm> list = storeCouponIssueUserService.selectFsStoreCouponIssueUserList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 用户优惠券 /store/storeCouponUser ==========
+    @PreAuthorize("@ss.hasPermi('store:storeCouponUser:list')")
+    @GetMapping("/store/storeCouponUser/list")
+    public TableDataInfo storeCouponUserList(FsStoreCouponUserScrm param) {
+        startPage();
+        List<FsStoreCouponUserScrm> list = storeCouponUserService.selectFsStoreCouponUserList(param);
+        return getDataTable(list);
+    }
+
+    // storeActivity CRUD已迁移至 FsStoreActivityScrmBridgeController (fs-admin模块)
+
+    // ========== 运费模板 /store/shippingTemplates ==========
+    @PreAuthorize("@ss.hasPermi('store:shippingTemplates:list')")
+    @GetMapping("/store/shippingTemplates/list")
+    public TableDataInfo shippingTemplatesList(FsShippingTemplatesScrm param) {
+        startPage();
+        List<FsShippingTemplatesScrm> list = shippingTemplatesService.selectFsShippingTemplatesList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 包邮模板 /store/shippingTemplatesFree ==========
+    @PreAuthorize("@ss.hasPermi('store:shippingTemplatesFree:list')")
+    @GetMapping("/store/shippingTemplatesFree/list")
+    public TableDataInfo shippingTemplatesFreeList(FsShippingTemplatesFreeScrm param) {
+        startPage();
+        List<FsShippingTemplatesFreeScrm> list = shippingTemplatesFreeService.selectFsShippingTemplatesFreeList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 区域运费 /store/shippingTemplatesRegion ==========
+    @PreAuthorize("@ss.hasPermi('store:shippingTemplatesRegion:list')")
+    @GetMapping("/store/shippingTemplatesRegion/list")
+    public TableDataInfo shippingTemplatesRegionList(FsShippingTemplatesRegionScrm param) {
+        startPage();
+        List<FsShippingTemplatesRegionScrm> list = shippingTemplatesRegionService.selectFsShippingTemplatesRegionList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 购物车 /store/storeCart ==========
+    @PreAuthorize("@ss.hasPermi('store:storeCart:list')")
+    @GetMapping("/store/storeCart/list")
+    public TableDataInfo storeCartList(FsStoreCartScrm param) {
+        startPage();
+        List<FsStoreCartScrm> list = storeCartService.selectFsStoreCartList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 门店管理 /store/storeShop ==========
+    @PreAuthorize("@ss.hasPermi('store:storeShop:list')")
+    @GetMapping("/store/storeShop/list")
+    public TableDataInfo storeShopList(FsStoreShopScrm param) {
+        startPage();
+        List<FsStoreShopScrm> list = storeShopService.selectFsStoreShopList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 门店员工 /store/storeShopStaff ==========
+    @PreAuthorize("@ss.hasPermi('store:storeShopStaff:list')")
+    @GetMapping("/store/storeShopStaff/list")
+    public TableDataInfo storeShopStaffList(FsStoreShopStaffScrm param) {
+        startPage();
+        List<FsStoreShopStaffScrm> list = storeShopStaffService.selectFsStoreShopStaffList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 浏览记录 /store/storeVisit ==========
+    @PreAuthorize("@ss.hasPermi('store:storeVisit:list')")
+    @GetMapping("/store/storeVisit/list")
+    public TableDataInfo storeVisitList(FsStoreVisitScrm param) {
+        startPage();
+        List<FsStoreVisitScrm> list = storeVisitService.selectFsStoreVisitList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 推荐管理 /store/recommend ==========
+    @PreAuthorize("@ss.hasPermi('store:recommend:list')")
+    @GetMapping("/store/recommend/list")
+    public TableDataInfo recommendList(FsStoreRecommendScrm param) {
+        startPage();
+        List<FsStoreRecommendScrm> list = storeRecommendService.selectFsStoreRecommendScrmList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 推广员申请 /store/userPromoterApply ==========
+    @PreAuthorize("@ss.hasPermi('store:userPromoterApply:list')")
+    @GetMapping("/store/userPromoterApply/list")
+    public TableDataInfo userPromoterApplyList(FsUserPromoterApplyScrm param) {
+        startPage();
+        List<FsUserPromoterApplyScrm> list = userPromoterApplyService.selectFsUserPromoterApplyList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 商城菜单 /store/menu ==========
+    @PreAuthorize("@ss.hasPermi('store:menu:list')")
+    @GetMapping("/store/menu/list")
+    public AjaxResult menuList(FsMenuScrm param) {
+        List<FsMenuScrm> list = menuScrmService.selectFsMenuList(param);
+        return AjaxResult.success(list);
+    }
+
+    // ========== 处方管理 /store/prescribe ==========
+    @PreAuthorize("@ss.hasPermi('store:prescribe:list')")
+    @GetMapping("/store/prescribe/list")
+    public TableDataInfo prescribeList(FsPrescribeScrm param) {
+        startPage();
+        List<FsPrescribeScrm> list = prescribeService.selectFsPrescribeList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 处方药品 /store/prescribeDrug ==========
+    @PreAuthorize("@ss.hasPermi('store:prescribeDrug:list')")
+    @GetMapping("/store/prescribeDrug/list")
+    public TableDataInfo prescribeDrugList(FsPrescribeDrugScrm param) {
+        startPage();
+        List<FsPrescribeDrugScrm> list = prescribeDrugService.selectFsPrescribeDrugList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 广告管理 /store/adv ==========
+    @PreAuthorize("@ss.hasPermi('store:adv:list')")
+    @GetMapping("/store/adv/list")
+    public TableDataInfo advList(FsAdvScrm param) {
+        startPage();
+        List<FsAdvScrm> list = advScrmService.selectFsAdvList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 首页文章 /store/homeArticle ==========
+    @PreAuthorize("@ss.hasPermi('store:homeArticle:list')")
+    @GetMapping("/store/homeArticle/list")
+    public TableDataInfo homeArticleList(FsHomeArticleScrm param) {
+        startPage();
+        List<FsHomeArticleScrm> list = homeArticleService.selectFsHomeArticleList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 首页分类 /store/homeCategory ==========
+    @PreAuthorize("@ss.hasPermi('store:homeCategory:list')")
+    @GetMapping("/store/homeCategory/list")
+    public TableDataInfo homeCategoryList(FsHomeArticleCategoryScrm param) {
+        startPage();
+        List<FsHomeArticleCategoryScrm> list = homeArticleCategoryService.selectFsHomeArticleCategoryList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 首页视图 /store/homeView ==========
+    @PreAuthorize("@ss.hasPermi('store:homeView:list')")
+    @GetMapping("/store/homeView/list")
+    public TableDataInfo homeViewList(FsHomeArticleViewScrm param) {
+        startPage();
+        List<FsHomeArticleViewScrm> list = homeArticleViewService.selectFsHomeArticleViewList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 店铺管理 /store/his/store ==========
+    @PreAuthorize("@ss.hasPermi('store:his:store:list')")
+    @GetMapping("/store/his/store/list")
+    public TableDataInfo hisStoreList(FsStoreScrm param) {
+        startPage();
+        List<FsStoreScrm> list = storeScrmService.selectFsStoreList(param);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:his:store:audit')")
+    @GetMapping("/store/his/store/audit")
+    public AjaxResult hisStoreAudit() {
+        return AjaxResult.success();
+    }
+
+    // ========== 商城统计 /store/statistics ==========
+    @GetMapping({"/store/statistics/storeOrder", "/store/statistics/packageOrder",
+                 "/store/statistics/storePayment", "/store/statistics/inquiryOrder"})
+    public AjaxResult storeStatistics() {
+        Map<String, Object> data = new HashMap<>();
+        data.put("total", 0);
+        data.put("amount", 0);
+        data.put("rows", new ArrayList<>());
+        return AjaxResult.success(data);
+    }
+
+    // ========== saasui额外store端点 ==========
+    @GetMapping("/store/package/list")
+    public TableDataInfo storePackageList(FsStoreProductPackageScrm param) {
+        startPage();
+        List<FsStoreProductPackageScrm> list = storeProductPackageService.selectFsStoreProductPackageList(param);
+        return getDataTable(list);
+    }
+
+    @Autowired
+    private IFsStoreProductPackageScrmService storeProductPackageService;
+
+    @GetMapping("/store/exportTask/list")
+    public TableDataInfo storeExportTaskList() {
+        return safeListFromTable("fs_export_task");
+    }
+
+    @GetMapping({"/store/healthRecord/list", "/store/healthRecord/myList"})
+    public TableDataInfo storeHealthRecordList() {
+        return safeListFromTable("fs_health_record");
+    }
+
+    @GetMapping({"/store/inquiryOrder/list", "/store/inquiryOrder/myList"})
+    public TableDataInfo storeInquiryOrderList() {
+        return safeListFromTable("fs_inquiry_order");
+    }
+
+    @GetMapping({"/store/packageOrder/list", "/store/packageOrder/myList"})
+    public TableDataInfo storePackageOrderList() {
+        return safeListFromTable("fs_package_order");
+    }
+
+    @GetMapping({"/store/prescribe/myList", "/store/storeAfterSales/myList"})
+    public TableDataInfo storeMyLists() {
+        return safeListFromTable("fs_prescribe");
+    }
+
+    @GetMapping("/store/inquiryOrderReport/list")
+    public TableDataInfo storeInquiryOrderReportList() {
+        return safeListFromTable("fs_inquiry_order_report");
+    }
+}

+ 2 - 0
fs-admin/src/main/java/com/fs/admin/controller/AdminStoreOrderAdminController.java → fs-admin-agent/src/main/java/com/fs/admin/controller/AdminStoreOrderAdminController.java

@@ -1,6 +1,7 @@
 package com.fs.admin.controller;
 
 import java.util.*;
+import org.springframework.context.annotation.Profile;
 
 import com.fs.admin.helper.AdminCrossTenantHelper;
 import com.fs.common.annotation.Excel;
@@ -19,6 +20,7 @@ import org.springframework.web.bind.annotation.*;
  * 总后台销售订单管理控制器
  * 遍历所有租户库查询 fs_store_order_scrm 数据
  */
+@Profile("admin")
 @RestController
 @RequestMapping("/admin/storeOrder")
 public class AdminStoreOrderAdminController extends BaseController {

+ 184 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/AdminStoreOrderController.java

@@ -0,0 +1,184 @@
+package com.fs.admin.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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.hisStore.domain.*;
+import com.fs.hisStore.param.FsStoreOrderAuditParam;
+import com.fs.hisStore.service.*;
+import com.fs.hisStore.vo.FsStoreOrderAuditVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 商城订单/支付/售后管理 - adminui端Controller(fs-admin 8004)
+ * 桥接 hisStore 服务层,提供 /store/* 订单相关路径的完整CRUD
+ */
+@Profile("admin")
+@RestController
+public class AdminStoreOrderController extends BaseController {
+
+    @Autowired
+    private IFsStoreOrderScrmService storeOrderService;
+    @Autowired
+    private IFsStorePaymentScrmService storePaymentService;
+    @Autowired
+    private IFsStoreAfterSalesScrmService storeAfterSalesService;
+    @Autowired
+    private IFsStoreAfterSalesItemScrmService storeAfterSalesItemService;
+    @Autowired
+    private IFsStoreAfterSalesStatusScrmService storeAfterSalesStatusService;
+    @Autowired
+    private IFsStoreOrderAuditScrmService storeOrderAuditService;
+    @Autowired
+    private IFsStoreOrderItemScrmService storeOrderItemService;
+    @Autowired
+    private IFsStoreOrderNoticeScrmService storeOrderNoticeService;
+    @Autowired
+    private IFsStoreOrderOfflineScrmService storeOrderOfflineService;
+    @Autowired
+    private IFsStoreOrderOfflineItemScrmService storeOrderOfflineItemService;
+    @Autowired
+    private IFsStoreOrderStatusScrmService storeOrderStatusService;
+
+    // ========== 商城订单 /store/storeOrder ==========
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:list')")
+    @GetMapping("/store/storeOrder/list")
+    public TableDataInfo storeOrderList(FsStoreOrderScrm param) {
+        startPage();
+        List<FsStoreOrderScrm> list = storeOrderService.selectFsStoreOrderList(param);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:query')")
+    @GetMapping("/store/storeOrder/{orderId}")
+    public AjaxResult storeOrderInfo(@PathVariable("orderId") Long orderId) {
+        return AjaxResult.success(storeOrderService.selectFsStoreOrderById(orderId));
+    }
+
+    // ========== 支付管理 /store/storePayment ==========
+    @PreAuthorize("@ss.hasPermi('store:storePayment:list')")
+    @GetMapping("/store/storePayment/list")
+    public TableDataInfo storePaymentList(FsStorePaymentScrm param) {
+        startPage();
+        List<FsStorePaymentScrm> list = storePaymentService.selectFsStorePaymentList(param);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storePayment:query')")
+    @GetMapping("/store/storePayment/{paymentId}")
+    public AjaxResult storePaymentInfo(@PathVariable("paymentId") Long paymentId) {
+        return AjaxResult.success(storePaymentService.selectFsStorePaymentById(paymentId));
+    }
+
+    // ========== 售后管理 /store/storeAfterSales ==========
+    @PreAuthorize("@ss.hasPermi('store:storeAfterSales:list')")
+    @GetMapping("/store/storeAfterSales/list")
+    public TableDataInfo storeAfterSalesList(FsStoreAfterSalesScrm param) {
+        startPage();
+        List<FsStoreAfterSalesScrm> list = storeAfterSalesService.selectFsStoreAfterSalesList(param);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeAfterSales:query')")
+    @GetMapping("/store/storeAfterSales/{afterSalesId}")
+    public AjaxResult storeAfterSalesInfo(@PathVariable("afterSalesId") Long afterSalesId) {
+        return AjaxResult.success(storeAfterSalesService.selectFsStoreAfterSalesById(afterSalesId));
+    }
+
+    // ========== 售后明细 /store/storeAfterSalesItem ==========
+    @PreAuthorize("@ss.hasPermi('store:storeAfterSalesItem:list')")
+    @GetMapping("/store/storeAfterSalesItem/list")
+    public TableDataInfo storeAfterSalesItemList(FsStoreAfterSalesItemScrm param) {
+        startPage();
+        List<FsStoreAfterSalesItemScrm> list = storeAfterSalesItemService.selectFsStoreAfterSalesItemList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 售后状态 /store/storeAfterSalesStatus ==========
+    @PreAuthorize("@ss.hasPermi('store:storeAfterSalesStatus:list')")
+    @GetMapping("/store/storeAfterSalesStatus/list")
+    public TableDataInfo storeAfterSalesStatusList(FsStoreAfterSalesStatusScrm param) {
+        startPage();
+        List<FsStoreAfterSalesStatusScrm> list = storeAfterSalesStatusService.selectFsStoreAfterSalesStatusList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 订单审核 /store/storeOrderAudit ==========
+    @PreAuthorize("@ss.hasPermi('store:storeOrderAudit:list')")
+    @GetMapping("/store/storeOrderAudit/list")
+    public TableDataInfo storeOrderAuditList(FsStoreOrderAuditParam param) {
+        startPage();
+        List<FsStoreOrderAuditVO> list = storeOrderAuditService.selectStoreOrderAuditVOList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 订单明细 /store/storeOrderItem ==========
+    @PreAuthorize("@ss.hasPermi('store:storeOrderItem:list')")
+    @GetMapping("/store/storeOrderItem/list")
+    public TableDataInfo storeOrderItemList(FsStoreOrderItemScrm param) {
+        startPage();
+        List<FsStoreOrderItemScrm> list = storeOrderItemService.selectFsStoreOrderItemList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 订单通知 /store/storeOrderNotice ==========
+    @PreAuthorize("@ss.hasPermi('store:storeOrderNotice:list')")
+    @GetMapping("/store/storeOrderNotice/list")
+    public TableDataInfo storeOrderNoticeList(FsStoreOrderNoticeScrm param) {
+        startPage();
+        List<FsStoreOrderNoticeScrm> list = storeOrderNoticeService.selectFsStoreOrderNoticeList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 线下订单 /store/storeOrderOffline ==========
+    @PreAuthorize("@ss.hasPermi('store:storeOrderOffline:list')")
+    @GetMapping("/store/storeOrderOffline/list")
+    public TableDataInfo storeOrderOfflineList(FsStoreOrderOfflineScrm param) {
+        startPage();
+        List<FsStoreOrderOfflineScrm> list = storeOrderOfflineService.selectFsStoreOrderOfflineList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 线下订单明细 /storeOrderOfflineItem/store ==========
+    @PreAuthorize("@ss.hasPermi('storeOrderOfflineItem:store:list')")
+    @GetMapping("/storeOrderOfflineItem/store/list")
+    public TableDataInfo storeOrderOfflineItemList(FsStoreOrderOfflineItemScrm param) {
+        startPage();
+        List<FsStoreOrderOfflineItemScrm> list = storeOrderOfflineItemService.selectFsStoreOrderOfflineItemList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 订单状态 /store/storeOrderStatus ==========
+    @PreAuthorize("@ss.hasPermi('store:storeOrderStatus:list')")
+    @GetMapping("/store/storeOrderStatus/list")
+    public TableDataInfo storeOrderStatusList(FsStoreOrderStatusScrm param) {
+        startPage();
+        List<FsStoreOrderStatusScrm> list = storeOrderStatusService.selectFsStoreOrderStatusList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 健康商城订单 /store/healthStoreOrder (别名) ==========
+    @PreAuthorize("@ss.hasPermi('store:healthStoreOrder:list')")
+    @GetMapping("/store/healthStoreOrder/list")
+    public TableDataInfo healthStoreOrderList(FsStoreOrderScrm param) {
+        startPage();
+        List<FsStoreOrderScrm> list = storeOrderService.selectFsStoreOrderList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 推广订单 /store/PromotionOrder ==========
+    @PreAuthorize("@ss.hasPermi('store:PromotionOrder:list')")
+    @GetMapping("/store/PromotionOrder/list")
+    public TableDataInfo promotionOrderList(FsStoreOrderScrm param) {
+        startPage();
+        List<FsStoreOrderScrm> list = storeOrderService.selectFsStoreOrderList(param);
+        return getDataTable(list);
+    }
+}

+ 253 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/AdminStoreProductController.java

@@ -0,0 +1,253 @@
+package com.fs.admin.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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.hisStore.domain.*;
+import com.fs.hisStore.service.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 商城商品管理 - adminui端Controller(fs-admin 8004)
+ * 桥接 hisStore 服务层,提供 /store/* 路径的完整CRUD
+ */
+@Profile("admin")
+@RestController
+public class AdminStoreProductController extends BaseController {
+
+    @Autowired
+    private IFsStoreProductScrmService storeProductService;
+    @Autowired
+    private IFsStoreProductCategoryScrmService storeProductCategoryService;
+    @Autowired
+    private IFsStoreProductRuleScrmService storeProductRuleService;
+    @Autowired
+    private IFsStoreProductPackageScrmService storeProductPackageService;
+    @Autowired
+    private IFsStoreProductAttrScrmService storeProductAttrService;
+    @Autowired
+    private IFsStoreProductAttrValueScrmService storeProductAttrValueService;
+    @Autowired
+    private IFsStoreProductDetailsScrmService storeProductDetailsService;
+    @Autowired
+    private IFsStoreProductGroupScrmService storeProductGroupService;
+    @Autowired
+    private IFsStoreProductRelationScrmService storeProductRelationService;
+    @Autowired
+    private IFsStoreProductReplyScrmService storeProductReplyService;
+    @Autowired
+    private IFsStoreProductTemplateScrmService storeProductTemplateService;
+
+    // ========== 商品管理 /store/storeProduct ==========
+    @PreAuthorize("@ss.hasPermi('store:storeProduct:list')")
+    @GetMapping("/store/storeProduct/list")
+    public TableDataInfo storeProductList(FsStoreProductScrm param) {
+        startPage();
+        List<FsStoreProductScrm> list = storeProductService.selectFsStoreProductList(param);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProduct:query')")
+    @GetMapping("/store/storeProduct/{productId}")
+    public AjaxResult storeProductInfo(@PathVariable("productId") Long productId) {
+        return AjaxResult.success(storeProductService.selectFsStoreProductById(productId));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProduct:add')")
+    @Log(title = "商品管理", businessType = BusinessType.INSERT)
+    @PostMapping("/store/storeProduct")
+    public AjaxResult storeProductAdd(@RequestBody FsStoreProductScrm param) {
+        return toAjax(storeProductService.insertFsStoreProduct(param));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProduct:edit')")
+    @Log(title = "商品管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/store/storeProduct")
+    public AjaxResult storeProductEdit(@RequestBody FsStoreProductScrm param) {
+        return toAjax(storeProductService.updateFsStoreProduct(param));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProduct:remove')")
+    @Log(title = "商品管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/store/storeProduct/{productIds}")
+    public AjaxResult storeProductRemove(@PathVariable Long[] productIds) {
+        return toAjax(storeProductService.deleteFsStoreProductByIds(productIds));
+    }
+
+    // ========== 商品分类 /store/storeProductCategory ==========
+    @PreAuthorize("@ss.hasPermi('store:storeProductCategory:list')")
+    @GetMapping("/store/storeProductCategory/list")
+    public AjaxResult storeProductCategoryList(FsStoreProductCategoryScrm param) {
+        List<FsStoreProductCategoryScrm> list = storeProductCategoryService.selectFsStoreProductCategoryList(param);
+        return AjaxResult.success(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductCategory:query')")
+    @GetMapping("/store/storeProductCategory/{cateId}")
+    public AjaxResult storeProductCategoryInfo(@PathVariable("cateId") Long cateId) {
+        return AjaxResult.success(storeProductCategoryService.selectFsStoreProductCategoryById(cateId));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductCategory:add')")
+    @Log(title = "商品分类", businessType = BusinessType.INSERT)
+    @PostMapping("/store/storeProductCategory")
+    public AjaxResult storeProductCategoryAdd(@RequestBody FsStoreProductCategoryScrm param) {
+        return toAjax(storeProductCategoryService.insertFsStoreProductCategory(param));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductCategory:edit')")
+    @Log(title = "商品分类", businessType = BusinessType.UPDATE)
+    @PutMapping("/store/storeProductCategory")
+    public AjaxResult storeProductCategoryEdit(@RequestBody FsStoreProductCategoryScrm param) {
+        return toAjax(storeProductCategoryService.updateFsStoreProductCategory(param));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductCategory:remove')")
+    @Log(title = "商品分类", businessType = BusinessType.DELETE)
+    @DeleteMapping("/store/storeProductCategory/{cateIds}")
+    public AjaxResult storeProductCategoryRemove(@PathVariable Long[] cateIds) {
+        return toAjax(storeProductCategoryService.deleteFsStoreProductCategoryByIds(cateIds));
+    }
+
+    // ========== 商品规格 /store/storeProductRule ==========
+    @PreAuthorize("@ss.hasPermi('store:storeProductRule:list')")
+    @GetMapping("/store/storeProductRule/list")
+    public TableDataInfo storeProductRuleList(FsStoreProductRuleScrm param) {
+        startPage();
+        List<FsStoreProductRuleScrm> list = storeProductRuleService.selectFsStoreProductRuleList(param);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductRule:query')")
+    @GetMapping("/store/storeProductRule/{ruleId}")
+    public AjaxResult storeProductRuleInfo(@PathVariable("ruleId") Integer ruleId) {
+        return AjaxResult.success(storeProductRuleService.selectFsStoreProductRuleById(ruleId));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductRule:add')")
+    @Log(title = "商品规格", businessType = BusinessType.INSERT)
+    @PostMapping("/store/storeProductRule")
+    public AjaxResult storeProductRuleAdd(@RequestBody FsStoreProductRuleScrm param) {
+        return toAjax(storeProductRuleService.insertFsStoreProductRule(param));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductRule:edit')")
+    @Log(title = "商品规格", businessType = BusinessType.UPDATE)
+    @PutMapping("/store/storeProductRule")
+    public AjaxResult storeProductRuleEdit(@RequestBody FsStoreProductRuleScrm param) {
+        return toAjax(storeProductRuleService.updateFsStoreProductRule(param));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductRule:remove')")
+    @Log(title = "商品规格", businessType = BusinessType.DELETE)
+    @DeleteMapping("/store/storeProductRule/{ruleIds}")
+    public AjaxResult storeProductRuleRemove(@PathVariable Integer[] ruleIds) {
+        return toAjax(storeProductRuleService.deleteFsStoreProductRuleByIds(ruleIds));
+    }
+
+    // ========== 商品套餐 /store/storeProductPackage ==========
+    @PreAuthorize("@ss.hasPermi('store:storeProductPackage:list')")
+    @GetMapping("/store/storeProductPackage/list")
+    public TableDataInfo storeProductPackageList(FsStoreProductPackageScrm param) {
+        startPage();
+        List<FsStoreProductPackageScrm> list = storeProductPackageService.selectFsStoreProductPackageList(param);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductPackage:query')")
+    @GetMapping("/store/storeProductPackage/{packageId}")
+    public AjaxResult storeProductPackageInfo(@PathVariable("packageId") Long packageId) {
+        return AjaxResult.success(storeProductPackageService.selectFsStoreProductPackageById(packageId));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductPackage:add')")
+    @Log(title = "商品套餐", businessType = BusinessType.INSERT)
+    @PostMapping("/store/storeProductPackage")
+    public AjaxResult storeProductPackageAdd(@RequestBody FsStoreProductPackageScrm param) {
+        return toAjax(storeProductPackageService.insertFsStoreProductPackage(param));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductPackage:edit')")
+    @Log(title = "商品套餐", businessType = BusinessType.UPDATE)
+    @PutMapping("/store/storeProductPackage")
+    public AjaxResult storeProductPackageEdit(@RequestBody FsStoreProductPackageScrm param) {
+        return toAjax(storeProductPackageService.updateFsStoreProductPackage(param));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductPackage:remove')")
+    @Log(title = "商品套餐", businessType = BusinessType.DELETE)
+    @DeleteMapping("/store/storeProductPackage/{packageIds}")
+    public AjaxResult storeProductPackageRemove(@PathVariable Long[] packageIds) {
+        return toAjax(storeProductPackageService.deleteFsStoreProductPackageByIds(packageIds));
+    }
+
+    // ========== 商品属性 /store/storeProductAttr ==========
+    @PreAuthorize("@ss.hasPermi('store:storeProductAttr:list')")
+    @GetMapping("/store/storeProductAttr/list")
+    public TableDataInfo storeProductAttrList(FsStoreProductAttrScrm param) {
+        startPage();
+        List<FsStoreProductAttrScrm> list = storeProductAttrService.selectFsStoreProductAttrList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 属性值 /store/storeProductAttrValue ==========
+    @PreAuthorize("@ss.hasPermi('store:storeProductAttrValue:list')")
+    @GetMapping("/store/storeProductAttrValue/list")
+    public TableDataInfo storeProductAttrValueList(FsStoreProductAttrValueScrm param) {
+        startPage();
+        List<FsStoreProductAttrValueScrm> list = storeProductAttrValueService.selectFsStoreProductAttrValueList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 商品详情 /store/storeProductDetails ==========
+    @PreAuthorize("@ss.hasPermi('store:storeProductDetails:list')")
+    @GetMapping("/store/storeProductDetails/list")
+    public TableDataInfo storeProductDetailsList(FsStoreProductDetailsScrm param) {
+        startPage();
+        List<FsStoreProductDetailsScrm> list = storeProductDetailsService.selectFsStoreProductDetailsList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 商品分组 /store/storeProductGroup ==========
+    @PreAuthorize("@ss.hasPermi('store:storeProductGroup:list')")
+    @GetMapping("/store/storeProductGroup/list")
+    public TableDataInfo storeProductGroupList(FsStoreProductGroupScrm param) {
+        startPage();
+        List<FsStoreProductGroupScrm> list = storeProductGroupService.selectFsStoreProductGroupList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 商品关联 /store/storeProductRelation ==========
+    @PreAuthorize("@ss.hasPermi('store:storeProductRelation:list')")
+    @GetMapping("/store/storeProductRelation/list")
+    public TableDataInfo storeProductRelationList(FsStoreProductRelationScrm param) {
+        startPage();
+        List<FsStoreProductRelationScrm> list = storeProductRelationService.selectFsStoreProductRelationList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 商品评价 /store/storeProductReply ==========
+    @PreAuthorize("@ss.hasPermi('store:storeProductReply:list')")
+    @GetMapping("/store/storeProductReply/list")
+    public TableDataInfo storeProductReplyList(FsStoreProductReplyScrm param) {
+        startPage();
+        List<FsStoreProductReplyScrm> list = storeProductReplyService.selectFsStoreProductReplyList(param);
+        return getDataTable(list);
+    }
+
+    // ========== 商品模板 /store/storeProductTemplate ==========
+    @PreAuthorize("@ss.hasPermi('store:storeProductTemplate:list')")
+    @GetMapping("/store/storeProductTemplate/list")
+    public TableDataInfo storeProductTemplateList(FsStoreProductTemplateScrm param) {
+        startPage();
+        List<FsStoreProductTemplateScrm> list = storeProductTemplateService.selectFsStoreProductTemplateList(param);
+        return getDataTable(list);
+    }
+}

+ 48 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/AdminVideoResourceController.java

@@ -0,0 +1,48 @@
+package com.fs.admin.controller;
+
+import java.util.*;
+import org.springframework.context.annotation.Profile;
+
+import com.fs.admin.helper.AdminCrossTenantHelper;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.page.TableDataInfo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 总后台视频资源审计控制器
+ * 遍历所有租户库查询 fs_user_course_video 数据(视频资源部分)
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/admin/videoResource")
+public class AdminVideoResourceController extends BaseController {
+
+    @Autowired
+    private AdminCrossTenantHelper crossTenantHelper;
+
+    /**
+     * 查询所有租户的视频资源列表
+     */
+    @PreAuthorize("@ss.hasPermi('admin:videoResource:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(@RequestParam(required = false) Long companyId,
+                              @RequestParam(required = false) String companyName,
+                              @RequestParam(required = false) String videoName) {
+        String sql = "SELECT video_id AS id, title AS videoName, upload_type AS videoType, " +
+                "video_url AS playUrl, duration, status, create_time AS createTime " +
+                "FROM fs_user_course_video WHERE 1=1";
+        StringBuilder sqlBuilder = new StringBuilder(sql);
+        List<Object> params = new ArrayList<>();
+        if (videoName != null && !videoName.isEmpty()) {
+            sqlBuilder.append(" AND title LIKE ?");
+            params.add("%" + videoName + "%");
+        }
+        sqlBuilder.append(" ORDER BY create_time DESC");
+
+        List<Map<String, Object>> allList = crossTenantHelper.queryAcrossTenants(
+                companyId, companyName, sqlBuilder.toString(), params.toArray());
+        return getDataTable(allList);
+    }
+}

+ 98 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/AdminWithdrawalController.java

@@ -0,0 +1,98 @@
+package com.fs.admin.controller;
+
+import java.util.List;
+import org.springframework.context.annotation.Profile;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.annotation.ProxyLog;
+import com.fs.common.core.controller.BaseController;
+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.SecurityUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.proxy.domain.ProxyWithdraw;
+import com.fs.proxy.service.ProxyWithdrawService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 总后台-代理提现管理控制器
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/admin/withdrawal")
+public class AdminWithdrawalController extends BaseController {
+
+    @Autowired
+    private ProxyWithdrawService proxyWithdrawService;
+
+    /**
+     * 查询提现申请列表
+     */
+    @PreAuthorize("@ss.hasPermi('admin:withdrawal:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(ProxyWithdraw proxyWithdraw) {
+        startPage();
+        List<ProxyWithdraw> list = proxyWithdrawService.selectProxyWithdrawList(proxyWithdraw);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出提现申请列表
+     */
+    @Log(title = "导出提现申请", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:withdrawal:list')")
+    @GetMapping("/export")
+    public AjaxResult export(ProxyWithdraw proxyWithdraw) {
+        List<ProxyWithdraw> list = proxyWithdrawService.selectProxyWithdrawList(proxyWithdraw);
+        ExcelUtil<ProxyWithdraw> util = new ExcelUtil<>(ProxyWithdraw.class);
+        return util.exportExcel(list, "提现申请数据");
+    }
+
+    /**
+     * 获取提现申请详情
+     */
+    @PreAuthorize("@ss.hasPermi('admin:withdrawal:list')")
+    @GetMapping("/{id}")
+    public AjaxResult getInfo(@PathVariable Long id) {
+        ProxyWithdraw record = proxyWithdrawService.selectProxyWithdrawById(id);
+        return record != null ? AjaxResult.success(record) : AjaxResult.error("记录不存在");
+    }
+
+    /**
+     * 审核通过(status 0→1)
+     */
+    @PreAuthorize("@ss.hasPermi('admin:withdrawal:approve')")
+    @ProxyLog(title = "提现管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/{id}/approve")
+    public AjaxResult approve(@PathVariable Long id) {
+        Long auditorId = SecurityUtils.getUserId();
+        int rows = proxyWithdrawService.auditWithdraw(id, 1, auditorId, null);
+        return rows > 0 ? AjaxResult.success("审核通过") : AjaxResult.error("操作失败,请检查提现状态");
+    }
+
+    /**
+     * 审核拒绝(status 0→2)
+     */
+    @PreAuthorize("@ss.hasPermi('admin:withdrawal:approve')")
+    @ProxyLog(title = "提现管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/{id}/reject")
+    public AjaxResult reject(@PathVariable Long id, @RequestParam String reason) {
+        Long auditorId = SecurityUtils.getUserId();
+        int rows = proxyWithdrawService.auditWithdraw(id, 2, auditorId, reason);
+        return rows > 0 ? AjaxResult.success("已拒绝") : AjaxResult.error("操作失败,请检查提现状态");
+    }
+
+    /**
+     * 确认打款(status 1→3)
+     */
+    @PreAuthorize("@ss.hasPermi('admin:withdrawal:approve')")
+    @ProxyLog(title = "提现管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/{id}/paid")
+    public AjaxResult confirmPaid(@PathVariable Long id) {
+        int rows = proxyWithdrawService.payWithdraw(id);
+        return rows > 0 ? AjaxResult.success("已确认打款") : AjaxResult.error("操作失败,请检查提现状态");
+    }
+}

+ 109 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/AiChatQualityController.java

@@ -0,0 +1,109 @@
+package com.fs.admin.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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.fastGpt.domain.FastGptChatMsg;
+import com.fs.fastGpt.domain.FastGptChatSession;
+import com.fs.fastGpt.service.IFastGptChatMsgService;
+import com.fs.fastGpt.service.IFastGptChatSessionService;
+import com.fs.proxy.domain.AiChatQualityRecord;
+import com.fs.proxy.service.AiChatQualityService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+@Profile("admin")
+@RestController
+@RequestMapping("/admin/aiChatQuality")
+public class AiChatQualityController extends BaseController {
+
+    @Autowired
+    private IFastGptChatSessionService fastGptChatSessionService;
+
+    @Autowired
+    private IFastGptChatMsgService fastGptChatMsgService;
+
+    @Autowired
+    private AiChatQualityService aiChatQualityService;
+
+    @Log(title = "导出AI对话会话", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:aiChatQuality:list')")
+    @GetMapping("/export")
+    public AjaxResult export(FastGptChatSession session) {
+        List<FastGptChatSession> list = fastGptChatSessionService.selectFastGptChatSessionList(session);
+        ExcelUtil<FastGptChatSession> util = new ExcelUtil<>(FastGptChatSession.class);
+        return util.exportExcel(list, "AI对话质检数据");
+    }
+
+    @PreAuthorize("@ss.hasPermi('admin:aiChatQuality:list')")
+    @GetMapping({"/list", "/sessions"})
+    public TableDataInfo listSessions(FastGptChatSession session) {
+        startPage();
+        List<FastGptChatSession> list = fastGptChatSessionService.selectFastGptChatSessionList(session);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('admin:aiChatQuality:query')")
+    @GetMapping("/session/{sessionId}")
+    public AjaxResult getSessionDetail(@PathVariable Long sessionId) {
+        return AjaxResult.success(fastGptChatSessionService.selectFastGptChatSessionBySessionId(sessionId));
+    }
+
+    @PreAuthorize("@ss.hasPermi('admin:aiChatQuality:query')")
+    @GetMapping("/messages/{sessionId}")
+    public AjaxResult getMessages(@PathVariable Long sessionId) {
+        FastGptChatMsg query = new FastGptChatMsg();
+        query.setSessionId(sessionId);
+        List<FastGptChatMsg> messages = fastGptChatMsgService.selectFastGptChatMsgList(query);
+        return AjaxResult.success(messages);
+    }
+
+    @PreAuthorize("@ss.hasPermi('admin:aiChatQuality:list')")
+    @GetMapping("/records")
+    public TableDataInfo listRecords(AiChatQualityRecord record) {
+        startPage();
+        List<AiChatQualityRecord> list = aiChatQualityService.selectAiChatQualityRecordList(record);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('admin:aiChatQuality:query')")
+    @GetMapping("/record/{id}")
+    public AjaxResult getRecordInfo(@PathVariable Long id) {
+        AiChatQualityRecord record = aiChatQualityService.selectAiChatQualityRecordById(id);
+        return AjaxResult.success(record);
+    }
+
+    @PreAuthorize("@ss.hasPermi('admin:aiChatQuality:add')")
+    @PostMapping("/record")
+    public AjaxResult addRecord(@RequestBody AiChatQualityRecord record) {
+        return toAjax(aiChatQualityService.insertAiChatQualityRecord(record));
+    }
+
+    @PreAuthorize("@ss.hasPermi('admin:aiChatQuality:edit')")
+    @PutMapping("/record")
+    public AjaxResult editRecord(@RequestBody AiChatQualityRecord record) {
+        return toAjax(aiChatQualityService.updateAiChatQualityRecord(record));
+    }
+
+    @PreAuthorize("@ss.hasPermi('admin:aiChatQuality:remove')")
+    @DeleteMapping("/record/{ids}")
+    public AjaxResult removeRecords(@PathVariable Long[] ids) {
+        return toAjax(aiChatQualityService.deleteAiChatQualityRecordByIds(ids));
+    }
+
+    @PreAuthorize("@ss.hasPermi('admin:aiChatQuality:batch')")
+    @PostMapping("/batchQuality")
+    public AjaxResult batchQuality(@RequestBody List<Long> sessionIds,
+                                   @RequestParam String result,
+                                   @RequestParam(required = false) String remark) {
+        int count = aiChatQualityService.batchQuality(sessionIds, result, remark);
+        return AjaxResult.success("批量质检完成,共处理 " + count + " 条记录");
+    }
+}

+ 104 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/AiProviderAdminController.java

@@ -0,0 +1,104 @@
+package com.fs.admin.controller;
+
+import java.util.List;
+import org.springframework.context.annotation.Profile;
+
+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.CompanyAiProvider;
+import com.fs.company.mapper.CompanyAiProviderMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+@Profile("admin")
+@RestController
+@RequestMapping({"/admin/aiProvider", "/knowledge/ai-provider"})
+public class AiProviderAdminController extends BaseController
+{
+    @Autowired
+    private CompanyAiProviderMapper aiProviderMapper;
+
+    @PreAuthorize("@ss.hasPermi('admin:aiProvider:list')")
+    @GetMapping("/list")
+    public AjaxResult list()
+    {
+        List<CompanyAiProvider> list = aiProviderMapper.selectList();
+        list.forEach(this::maskApiKey);
+        return AjaxResult.success(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('admin:aiProvider:list')")
+    @GetMapping("/{id}")
+    public AjaxResult getInfo(@PathVariable Long id)
+    {
+        CompanyAiProvider provider = aiProviderMapper.selectById(id);
+        if (provider != null) {
+            maskApiKey(provider);
+        }
+        return AjaxResult.success(provider);
+    }
+
+    @PreAuthorize("@ss.hasPermi('admin:aiProvider:add')")
+    @Log(title = "新增大模型供应商", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody CompanyAiProvider provider)
+    {
+        provider.setIsDefault(0);
+        provider.setDelFlag(0);
+        return toAjax(aiProviderMapper.insert(provider));
+    }
+
+    @PreAuthorize("@ss.hasPermi('admin:aiProvider:edit')")
+    @Log(title = "修改大模型供应商", businessType = BusinessType.UPDATE)
+    @PutMapping("/{id}")
+    public AjaxResult edit(@PathVariable Long id, @RequestBody CompanyAiProvider provider)
+    {
+        provider.setId(id);
+        if (provider.getApiKey() != null && provider.getApiKey().contains("*")) {
+            CompanyAiProvider existing = aiProviderMapper.selectById(id);
+            if (existing != null) {
+                provider.setApiKey(existing.getApiKey());
+            }
+        }
+        return toAjax(aiProviderMapper.updateById(provider));
+    }
+
+    @PreAuthorize("@ss.hasPermi('admin:aiProvider:remove')")
+    @Log(title = "删除大模型供应商", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{id}")
+    public AjaxResult remove(@PathVariable Long id)
+    {
+        return toAjax(aiProviderMapper.deleteById(id));
+    }
+
+    @PreAuthorize("@ss.hasPermi('admin:aiProvider:edit')")
+    @Log(title = "设置默认大模型供应商", businessType = BusinessType.UPDATE)
+    @PostMapping("/{id}/set-default")
+    public AjaxResult setDefault(@PathVariable Long id)
+    {
+        aiProviderMapper.clearAllDefault();
+        CompanyAiProvider provider = new CompanyAiProvider();
+        provider.setId(id);
+        provider.setIsDefault(1);
+        return toAjax(aiProviderMapper.updateById(provider));
+    }
+
+    @PreAuthorize("@ss.hasPermi('admin:aiProvider:list')")
+    @PostMapping("/test")
+    public AjaxResult test(@RequestBody CompanyAiProvider provider)
+    {
+        return AjaxResult.success("连接测试功能待实现");
+    }
+
+    private void maskApiKey(CompanyAiProvider provider) {
+        String apiKey = provider.getApiKey();
+        if (apiKey != null && apiKey.length() > 8) {
+            provider.setApiKey(apiKey.substring(0, 4) + "****" + apiKey.substring(apiKey.length() - 4));
+        } else if (apiKey != null && apiKey.length() > 0) {
+            provider.setApiKey("****");
+        }
+    }
+}

+ 2 - 0
fs-admin/src/main/java/com/fs/admin/controller/ArticleAdminController.java → fs-admin-agent/src/main/java/com/fs/admin/controller/ArticleAdminController.java

@@ -1,6 +1,7 @@
 package com.fs.admin.controller;
 
 import java.util.*;
+import org.springframework.context.annotation.Profile;
 
 import com.fs.admin.helper.AdminCrossTenantHelper;
 import com.fs.common.annotation.Excel;
@@ -19,6 +20,7 @@ import org.springframework.web.bind.annotation.*;
  * 总后台文章内容审计控制器
  * 遍历所有租户库查询 fs_article 数据
  */
+@Profile("admin")
 @RestController
 @RequestMapping("/admin/article")
 public class ArticleAdminController extends BaseController {

+ 158 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/CallRecordAdminController.java

@@ -0,0 +1,158 @@
+package com.fs.admin.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.domain.CompanyVoiceRoboticCallLogCallphone;
+import com.fs.company.service.ICompanyVoiceRoboticCallLogCallphoneService;
+import com.fs.proxy.domain.CallQualityRecord;
+import com.fs.proxy.service.CallQualityService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 总后台外呼记录和质检控制器
+ * 查看所有租户的外呼通话记录、录音和质检
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/admin/callRecord")
+public class CallRecordAdminController extends BaseController {
+
+    @Autowired
+    private ICompanyVoiceRoboticCallLogCallphoneService callLogService;
+
+    @Autowired
+    private CallQualityService callQualityService;
+
+    /**
+     * 查询所有租户的外呼通话记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('admin:callRecord:list')")
+    @GetMapping("/list")
+    public TableDataInfo list() {
+        startPage();
+        List<CompanyVoiceRoboticCallLogCallphone> list = callLogService.list(new QueryWrapper<CompanyVoiceRoboticCallLogCallphone>().orderByDesc("run_time"));
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取外呼通话记录详情
+     */
+    @PreAuthorize("@ss.hasPermi('admin:callRecord:query')")
+    @GetMapping(value = "/{logId}")
+    public AjaxResult getInfo(@PathVariable("logId") Long logId) {
+        return AjaxResult.success(callLogService.getById(logId));
+    }
+
+    /**
+     * 导出通话记录
+     */
+    @Log(title = "导出通话记录", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:callRecord:list')")
+    @GetMapping("/export")
+    public AjaxResult export(CompanyVoiceRoboticCallLogCallphone param) {
+        List<CompanyVoiceRoboticCallLogCallphone> list = callLogService.list(
+            new QueryWrapper<CompanyVoiceRoboticCallLogCallphone>().orderByDesc("run_time"));
+        ExcelUtil<CompanyVoiceRoboticCallLogCallphone> util = new ExcelUtil<>(CompanyVoiceRoboticCallLogCallphone.class);
+        return util.exportExcel(list, "通话记录数据");
+    }
+
+    /**
+     * 根据租户ID查询外呼记录
+     */
+    @PreAuthorize("@ss.hasPermi('admin:callRecord:query')")
+    @GetMapping("/byCompany/{companyId}")
+    public TableDataInfo getByCompanyId(@PathVariable Long companyId) {
+        startPage();
+        List<CompanyVoiceRoboticCallLogCallphone> list = callLogService.list(new QueryWrapper<CompanyVoiceRoboticCallLogCallphone>().orderByDesc("run_time"));
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取通话录音路径
+     */
+    @PreAuthorize("@ss.hasPermi('admin:callRecord:query')")
+    @GetMapping("/audio/{logId}")
+    public AjaxResult getAudio(@PathVariable Long logId) {
+        CompanyVoiceRoboticCallLogCallphone callLog = callLogService.getById(logId);
+        if (callLog != null && callLog.getRecordPath() != null) {
+            return AjaxResult.success(callLog.getRecordPath());
+        }
+        return AjaxResult.error("录音不存在");
+    }
+
+    /**
+     * 查询质检记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('admin:callRecord:qualityList')")
+    @GetMapping("/quality/list")
+    public TableDataInfo qualityList(CallQualityRecord record) {
+        startPage();
+        List<CallQualityRecord> list = callQualityService.selectCallQualityRecordList(record);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取质检记录详情
+     */
+    @PreAuthorize("@ss.hasPermi('admin:callRecord:query')")
+    @GetMapping("/quality/{id}")
+    public AjaxResult getQualityInfo(@PathVariable Long id) {
+        return AjaxResult.success(callQualityService.selectCallQualityRecordById(id));
+    }
+
+    /**
+     * 新增质检记录
+     */
+    @PreAuthorize("@ss.hasPermi('admin:callRecord:addQuality')")
+    @PostMapping("/quality")
+    public AjaxResult addQuality(@RequestBody CallQualityRecord record) {
+        return toAjax(callQualityService.insertCallQualityRecord(record));
+    }
+
+    /**
+     * 修改质检记录
+     */
+    @PreAuthorize("@ss.hasPermi('admin:callRecord:editQuality')")
+    @PutMapping("/quality")
+    public AjaxResult editQuality(@RequestBody CallQualityRecord record) {
+        return toAjax(callQualityService.updateCallQualityRecord(record));
+    }
+
+    /**
+     * 删除质检记录
+     */
+    @PreAuthorize("@ss.hasPermi('admin:callRecord:removeQuality')")
+    @DeleteMapping("/quality/{ids}")
+    public AjaxResult removeQuality(@PathVariable Long[] ids) {
+        return toAjax(callQualityService.deleteCallQualityRecordByIds(ids));
+    }
+
+    /**
+     * 通话记录统计信息
+     */
+    @PreAuthorize("@ss.hasPermi('admin:callRecord:query')")
+    @GetMapping("/statistics")
+    public AjaxResult statistics() {
+        return AjaxResult.success(callLogService.count());
+    }
+
+    /**
+     * 报备质检
+     */
+    @PreAuthorize("@ss.hasPermi('admin:callRecord:report')")
+    @PostMapping("/report")
+    public AjaxResult report(@RequestBody List<Long> logIds) {
+        int count = callQualityService.batchReport(logIds);
+        return AjaxResult.success("报备成功,共报备 " + count + " 条通话记录");
+    }
+}

+ 285 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/CompanyAdminController.java

@@ -0,0 +1,285 @@
+package com.fs.admin.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.domain.entity.SysMenu;
+import com.fs.common.core.domain.entity.TenantCompanyMenu;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.framework.datasource.DynamicDataSourceContextHolder;
+import com.fs.framework.datasource.TenantDataSourceManager;
+import com.fs.tenant.domain.TenantInfo;
+import com.fs.tenant.mapper.TenantInfoMapper;
+import com.fs.tenant.service.TenantInfoService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 总后台租户管理控制器(SaaS 多租户版)
+ * 查询 tenant_info 表,不再查询 company 表
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/admin/company")
+public class CompanyAdminController extends BaseController {
+
+    @Autowired
+    private TenantInfoService tenantInfoService;
+
+    @Autowired
+    private TenantInfoMapper tenantInfoMapper;
+
+    @Autowired
+    private TenantDataSourceManager tenantDataSourceManager;
+
+    /**
+     * 查询所有租户列表
+     */
+    @PreAuthorize("@ss.hasPermi('admin:company:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(TenantInfo tenantInfo) {
+        startPage();
+        List<TenantInfo> list = tenantInfoService.selectTenantInfoList(tenantInfo);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出租户列表
+     */
+    @Log(title = "导出租户列表", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:company:list')")
+    @GetMapping("/export")
+    public void export(HttpServletResponse response, TenantInfo tenantInfo) throws IOException {
+        List<TenantInfo> list = tenantInfoService.selectTenantInfoList(tenantInfo);
+        ExcelUtil<TenantInfo> util = new ExcelUtil<>(TenantInfo.class);
+        util.exportExcel(response, list, "租户列表数据");
+    }
+
+    /**
+     * 获取租户详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('admin:company:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") String id) {
+        return AjaxResult.success(tenantInfoService.selectTenantInfoById(id));
+    }
+
+    /**
+     * 新增租户
+     */
+    @PreAuthorize("@ss.hasPermi('admin:company:add')")
+    @Log(title = "租户管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public R add(@RequestBody TenantInfo tenantInfo) {
+        int i = tenantInfoService.insertTenantInfo(tenantInfo);
+        return i > 0 ? R.ok("租户数据库初始化中,请稍后") : R.error("租户创建失败");
+    }
+
+    /**
+     * 修改租户
+     */
+    @PreAuthorize("@ss.hasPermi('admin:company:edit')")
+    @Log(title = "租户管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody TenantInfo tenantInfo) {
+        return toAjax(tenantInfoService.updateTenantInfo(tenantInfo));
+    }
+
+    /**
+     * 删除租户
+     */
+    @PreAuthorize("@ss.hasPermi('admin:company:remove')")
+    @Log(title = "租户管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable String[] ids) {
+        return toAjax(tenantInfoService.deleteTenantInfoByIds(ids));
+    }
+
+    /**
+     * 禁用/启用租户
+     */
+    @PreAuthorize("@ss.hasPermi('admin:company:edit')")
+    @Log(title = "租户管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/status/{id}")
+    public AjaxResult changeStatus(@PathVariable String id, @RequestParam Integer status) {
+        TenantInfo tenantInfo = new TenantInfo();
+        tenantInfo.setId(Long.valueOf(id));
+        tenantInfo.setStatus(status);
+        return toAjax(tenantInfoService.updateTenantInfo(tenantInfo));
+    }
+
+    /**
+     * 获取租户下拉列表
+     */
+    @PreAuthorize("@ss.hasPermi('admin:company:query')")
+    @GetMapping("/options")
+    public AjaxResult getOptions() {
+        TenantInfo tenantInfo = new TenantInfo();
+        tenantInfo.setStatus(1);
+        List<TenantInfo> list = tenantInfoService.selectTenantInfoList(tenantInfo);
+        return AjaxResult.success(list);
+    }
+
+    /**
+     * 租户统计信息
+     */
+    @PreAuthorize("@ss.hasPermi('admin:company:query')")
+    @GetMapping("/statistics")
+    public AjaxResult statistics() {
+        TenantInfo tenantInfo = new TenantInfo();
+        List<TenantInfo> all = tenantInfoService.selectTenantInfoList(tenantInfo);
+        long activeCount = all.stream().filter(t -> t.getStatus() != null && t.getStatus() == 1).count();
+        Map<String, Object> result = new HashMap<>();
+        result.put("totalCount", all.size());
+        result.put("activeCount", activeCount);
+        result.put("disabledCount", all.size() - activeCount);
+        return AjaxResult.success(result);
+    }
+
+    /**
+     * 租户充值/扣款
+     */
+    @PreAuthorize("@ss.hasPermi('admin:company:edit')")
+    @Log(title = "租户管理", businessType = BusinessType.UPDATE)
+    @PostMapping("/{id}/recharge")
+    public AjaxResult rechargeCompany(@PathVariable String id, @RequestBody Map<String, Object> params) {
+        String operateType = (String) params.get("operateType");
+        Object amountObj = params.get("amount");
+        String remark = params.get("remark") != null ? params.get("remark").toString() : "";
+
+        if (amountObj == null) {
+            return AjaxResult.error("金额不能为空");
+        }
+        java.math.BigDecimal amount;
+        try {
+            amount = new java.math.BigDecimal(amountObj.toString());
+        } catch (NumberFormatException e) {
+            return AjaxResult.error("金额格式不正确");
+        }
+        if (amount.compareTo(java.math.BigDecimal.ZERO) <= 0) {
+            return AjaxResult.error("金额必须大于0");
+        }
+
+        java.math.BigDecimal delta = "deduct".equals(operateType) ? amount.negate() : amount;
+        int rows = tenantInfoService.updateBalance(Long.valueOf(id), delta);
+        if (rows > 0) {
+            return AjaxResult.success("recharge".equals(operateType) ? "充值成功" : "扣款成功");
+        }
+        return AjaxResult.error("recharge".equals(operateType) ? "充值失败,租户不存在" : "扣款失败,余额不足或租户不存在");
+    }
+
+    // ==================== 租户菜单编辑(切租户库) ====================
+
+    /**
+     * 获取租户管理端菜单树(合并模板菜单+租户已有菜单)
+     * flag=sys → 管理端菜单(tenant_sys_menu/租户库sys_menu)
+     * flag=com → 销售端菜单(tenant_company_menu/租户库company_menu)
+     */
+    @PreAuthorize("@ss.hasPermi('admin:company:edit')")
+    @PostMapping("/{id}/menu")
+    public R tenantMenuChange(@PathVariable String id, @RequestBody Map<String, String> params) {
+        String flag = params.getOrDefault("flag", "sys");
+        TenantInfo tenantInfo = tenantInfoMapper.selectTenantInfoById(id);
+        if (tenantInfo == null) return R.error("租户不存在");
+        if (tenantInfo.getStatus() != null && tenantInfo.getStatus() == 2) return R.error("租户初始化中");
+
+        if ("sys".equals(flag)) {
+            List<SysMenu> sysMenus = tenantInfoMapper.selectMenuList(new SysMenu());
+            tenantDataSourceManager.switchTenant(tenantInfo);
+            return tenantInfoService.menuChange(flag, sysMenus, null);
+        }
+
+        List<TenantCompanyMenu> companyMenus = tenantInfoMapper.selectCompanyMenuList(new TenantCompanyMenu());
+        tenantDataSourceManager.switchTenant(tenantInfo);
+        return tenantInfoService.menuChange(flag, null, companyMenus);
+    }
+
+    /**
+     * 编辑租户菜单(勾选/取消勾选/新增)
+     */
+    @PreAuthorize("@ss.hasPermi('admin:company:edit')")
+    @Log(title = "编辑租户菜单", businessType = BusinessType.UPDATE)
+    @PostMapping("/{id}/menu/edit")
+    public R tenantMenuEdit(@PathVariable String id, @RequestBody Map<String, Object> params) {
+        String flag = (String) params.getOrDefault("flag", "sys");
+        @SuppressWarnings("unchecked")
+        List<Long> selected = params.get("selected") != null ? (List<Long>) params.get("selected") : new ArrayList<>();
+        @SuppressWarnings("unchecked")
+        List<Long> unSelected = params.get("unSelected") != null ? (List<Long>) params.get("unSelected") : new ArrayList<>();
+
+        TenantInfo tenantInfo = tenantInfoMapper.selectTenantInfoById(id);
+        if (tenantInfo == null) return R.error("租户不存在");
+        if (tenantInfo.getStatus() != null && tenantInfo.getStatus() == 2) return R.error("租户初始化中");
+
+        if ("sys".equals(flag)) {
+            List<SysMenu> addSysMenu = getAddSysMenu(tenantInfo, selected);
+            tenantDataSourceManager.switchTenant(tenantInfo);
+            return tenantInfoService.menuEdit(selected, unSelected, flag, addSysMenu, null);
+        }
+
+        List<TenantCompanyMenu> addCompanyMenu = getAddCompanyMenu(tenantInfo, selected);
+        tenantDataSourceManager.switchTenant(tenantInfo);
+        return tenantInfoService.menuEdit(selected, unSelected, flag, null, addCompanyMenu);
+    }
+
+    /**
+     * 获取租户库中需要新增的后台菜单
+     */
+    private List<SysMenu> getAddSysMenu(TenantInfo tenantInfo, List<Long> selected) {
+        tenantDataSourceManager.switchTenant(tenantInfo);
+        List<Long> existIds = tenantInfoMapper.selectExistMenuIds();
+        List<Long> needAddIds = selected.stream().filter(mid -> !existIds.contains(mid)).collect(Collectors.toList());
+        DynamicDataSourceContextHolder.setDataSourceType(com.fs.common.enums.DataSourceType.MASTER.name());
+        if (!needAddIds.isEmpty()) {
+            return tenantInfoMapper.getTenSysMenuByIds(needAddIds);
+        }
+        return new ArrayList<>();
+    }
+
+    /**
+     * 获取租户库中需要新增的销售菜单
+     */
+    private List<TenantCompanyMenu> getAddCompanyMenu(TenantInfo tenantInfo, List<Long> selected) {
+        tenantDataSourceManager.switchTenant(tenantInfo);
+        List<Long> existIds = tenantInfoMapper.selectExistComMenuIds();
+        List<Long> needAddIds = selected.stream().filter(mid -> !existIds.contains(mid)).collect(Collectors.toList());
+        DynamicDataSourceContextHolder.setDataSourceType(com.fs.common.enums.DataSourceType.MASTER.name());
+        if (!needAddIds.isEmpty()) {
+            return tenantInfoMapper.getTenComMenuByIds(needAddIds);
+        }
+        return new ArrayList<>();
+    }
+
+    /**
+     * 重置租户管理员密码
+     */
+    @PreAuthorize("@ss.hasPermi('admin:company:edit')")
+    @Log(title = "租户管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/{id}/resetPwd")
+    public AjaxResult resetPwd(@PathVariable String id, @RequestBody Map<String, String> params) {
+        String password = params.get("password");
+        if (password == null || password.length() < 6) {
+            return AjaxResult.error("密码长度不能少于6位");
+        }
+        TenantInfo tenantInfo = tenantInfoMapper.selectTenantInfoById(id);
+        if (tenantInfo == null) {
+            return AjaxResult.error("租户不存在");
+        }
+        try {
+            tenantDataSourceManager.switchTenant(tenantInfo);
+            return toAjax(tenantInfoService.resetTenantAdminPwd(Long.valueOf(id), password));
+        } finally {
+            tenantDataSourceManager.clear();
+        }
+    }
+}

+ 148 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/CompanyUserAdminController.java

@@ -0,0 +1,148 @@
+package com.fs.admin.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.entity.SysUser;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.annotation.Log;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.domain.Company;
+import com.fs.company.domain.CompanyUser;
+import com.fs.company.domain.CompanyUserChangeApply;
+import com.fs.company.service.ICompanyService;
+import com.fs.company.service.ICompanyUserChangeApplyService;
+import com.fs.company.service.ICompanyUserService;
+import com.fs.system.service.ISysUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 总后台管理员账户管理控制器
+ * 读取 ylrz_saas.sys_user 表中 company_id IS NULL 的记录
+ * 这些是 admin 总后台自己的管理员账号,不属于任何租户
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/admin/companyUser")
+public class CompanyUserAdminController extends BaseController {
+
+    @Autowired
+    private ISysUserService sysUserService;
+
+    @Autowired
+    private ICompanyUserService companyUserService;
+
+    @Autowired
+    private ICompanyUserChangeApplyService companyUserChangeApplyService;
+
+    @Autowired
+    private ICompanyService companyService;
+
+    /**
+     * 查询管理员列表(sys_user 中 company_id IS NULL)
+     */
+    @PreAuthorize("@ss.hasPermi('admin:companyUser:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SysUser sysUser) {
+        startPage();
+        List<SysUser> list = sysUserService.selectAdminUserList(sysUser);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出管理员列表
+     */
+    @Log(title = "导出管理员列表", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:companyUser:list')")
+    @GetMapping("/export")
+    public AjaxResult export(SysUser sysUser) {
+        List<SysUser> list = sysUserService.selectAdminUserList(sysUser);
+        ExcelUtil<SysUser> util = new ExcelUtil<>(SysUser.class);
+        return util.exportExcel(list, "管理员账户数据");
+    }
+
+    /**
+     * 获取管理员详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('admin:companyUser:query')")
+    @GetMapping(value = "/{userId}")
+    public AjaxResult getInfo(@PathVariable("userId") Long userId) {
+        return AjaxResult.success(sysUserService.selectUserById(userId));
+    }
+
+    /**
+     * 禁用/启用管理员账户
+     * SysUser 状态:0=正常 1=停用
+     */
+    @PreAuthorize("@ss.hasPermi('admin:companyUser:edit')")
+    @PutMapping("/status/{userId}")
+    public AjaxResult changeStatus(@PathVariable Long userId, @RequestParam String status) {
+        SysUser sysUser = new SysUser();
+        sysUser.setUserId(userId);
+        sysUser.setStatus(status);
+        return toAjax(sysUserService.updateUserStatus(sysUser));
+    }
+
+    /**
+     * 根据租户ID查询员工列表
+     */
+    @PreAuthorize("@ss.hasPermi('admin:companyUser:query')")
+    @GetMapping("/byCompany/{companyId}")
+    public TableDataInfo getByCompanyId(@PathVariable Long companyId) {
+        startPage();
+        CompanyUser companyUser = new CompanyUser();
+        companyUser.setCompanyId(companyId);
+        List<CompanyUser> list = companyUserService.selectCompanyUserList(companyUser);
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询员工账户变化记录
+     */
+    @PreAuthorize("@ss.hasPermi('admin:companyUser:changeList')")
+    @GetMapping("/changeList")
+    public TableDataInfo changeList() {
+        startPage();
+        List<CompanyUserChangeApply> list = companyUserChangeApplyService.list();
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取员工账户变化记录详情
+     */
+    @PreAuthorize("@ss.hasPermi('admin:companyUser:query')")
+    @GetMapping("/change/{id}")
+    public AjaxResult getChangeInfo(@PathVariable Long id) {
+        return AjaxResult.success(companyUserChangeApplyService.getById(id));
+    }
+
+    /**
+     * 获取租户列表(用于筛选)
+     */
+    @PreAuthorize("@ss.hasPermi('admin:companyUser:query')")
+    @GetMapping("/companies")
+    public AjaxResult getCompanies() {
+        Company company = new Company();
+        company.setStatus(1);
+        List<Company> list = companyService.selectCompanyList(company);
+        return AjaxResult.success(list);
+    }
+
+    /**
+     * 统计管理员数量
+     */
+    @PreAuthorize("@ss.hasPermi('admin:companyUser:query')")
+    @GetMapping("/statistics")
+    public AjaxResult statistics() {
+        SysUser query = new SysUser();
+        List<SysUser> all = sysUserService.selectAdminUserList(query);
+        java.util.Map<String, Object> result = new java.util.HashMap<>();
+        result.put("totalCount", all.size());
+        return AjaxResult.success(result);
+    }
+}

+ 124 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/CourseAdminController.java

@@ -0,0 +1,124 @@
+package com.fs.admin.controller;
+
+import java.util.*;
+import org.springframework.context.annotation.Profile;
+
+import com.fs.admin.helper.AdminCrossTenantHelper;
+import com.fs.common.annotation.Excel;
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import lombok.Data;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 总后台课程内容管理控制器
+ * 遍历所有租户库查询课程数据
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/admin/course")
+public class CourseAdminController extends BaseController {
+
+    @Autowired
+    private AdminCrossTenantHelper crossTenantHelper;
+
+    private String str(Object o) { return o == null ? "" : o.toString(); }
+
+    @Data
+    public static class CourseExportVO {
+        @Excel(name = "租户名称") private String companyName;
+        @Excel(name = "课程名称") private String courseName;
+        @Excel(name = "播放次数") private String playCount;
+        @Excel(name = "状态") private String status;
+        @Excel(name = "创建时间") private String createTime;
+    }
+
+    @Log(title = "导出课程", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:course:list')")
+    @GetMapping("/export")
+    public AjaxResult export(@RequestParam(required = false) String courseName,
+                             @RequestParam(required = false) Long companyId,
+                             @RequestParam(required = false) String companyName) {
+        List<Map<String, Object>> allList = crossTenantHelper.queryAcrossTenants(
+                companyId, companyName, (tenant, jdbc) -> {
+                    StringBuilder sql = new StringBuilder(
+                            "SELECT v.video_id, v.course_id, v.course_name, v.cover_url, " +
+                            "v.duration, v.play_count, v.create_time, v.status " +
+                            "FROM fs_user_course_video v WHERE 1=1");
+                    List<Object> params = new ArrayList<>();
+                    if (courseName != null && !courseName.isEmpty()) {
+                        sql.append(" AND v.course_name LIKE ?");
+                        params.add("%" + courseName + "%");
+                    }
+                    sql.append(" ORDER BY v.create_time DESC");
+                    return params.isEmpty()
+                            ? jdbc.queryForList(sql.toString())
+                            : jdbc.queryForList(sql.toString(), params.toArray());
+                });
+        List<CourseExportVO> voList = new ArrayList<>();
+        for (Map<String, Object> m : allList) {
+            CourseExportVO vo = new CourseExportVO();
+            vo.setCompanyName(str(m.get("company_name")));
+            vo.setCourseName(str(m.get("course_name")));
+            vo.setPlayCount(str(m.get("play_count")));
+            vo.setStatus(str(m.get("status")));
+            vo.setCreateTime(str(m.get("create_time")));
+            voList.add(vo);
+        }
+        ExcelUtil<CourseExportVO> util = new ExcelUtil<>(CourseExportVO.class);
+        return util.exportExcel(voList, "课程数据");
+    }
+
+    /**
+     * 查询所有租户的课程列表
+     */
+    @PreAuthorize("@ss.hasPermi('admin:course:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(@RequestParam(required = false) String courseName,
+                              @RequestParam(required = false) Long companyId,
+                              @RequestParam(required = false) String companyName) {
+        List<Map<String, Object>> allList = crossTenantHelper.queryAcrossTenants(
+                companyId, companyName, (tenant, jdbc) -> {
+                    StringBuilder sql = new StringBuilder(
+                            "SELECT v.video_id, v.course_id, v.course_name, v.cover_url, " +
+                            "v.duration, v.play_count, v.create_time, v.status " +
+                            "FROM fs_user_course_video v WHERE 1=1");
+                    List<Object> params = new ArrayList<>();
+                    if (courseName != null && !courseName.isEmpty()) {
+                        sql.append(" AND v.course_name LIKE ?");
+                        params.add("%" + courseName + "%");
+                    }
+                    sql.append(" ORDER BY v.create_time DESC LIMIT 200");
+                    return params.isEmpty()
+                            ? jdbc.queryForList(sql.toString())
+                            : jdbc.queryForList(sql.toString(), params.toArray());
+                });
+        return getDataTable(allList);
+    }
+
+    /**
+     * 课程统计信息
+     */
+    @PreAuthorize("@ss.hasPermi('admin:course:query')")
+    @GetMapping("/statistics")
+    public AjaxResult statistics(@RequestParam(required = false) Long companyId,
+                                 @RequestParam(required = false) String companyName) {
+        List<Map<String, Object>> allList = crossTenantHelper.queryAcrossTenants(
+                companyId, companyName, (tenant, jdbc) ->
+                        jdbc.queryForList("SELECT video_id, status FROM fs_user_course_video"));
+        Map<String, Object> result = new HashMap<>();
+        result.put("totalCount", allList.size());
+        result.put("onlineCount", allList.stream().filter(r -> "0".equals(String.valueOf(r.get("status")))).count());
+        result.put("offlineCount", allList.stream().filter(r -> "1".equals(String.valueOf(r.get("status")))).count());
+        result.put("totalPlayCount", allList.stream()
+                .mapToLong(r -> r.get("play_count") != null ? ((Number) r.get("play_count")).longValue() : 0L)
+                .sum());
+        return AjaxResult.success(result);
+    }
+}

+ 61 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/DbConfigController.java

@@ -0,0 +1,61 @@
+package com.fs.admin.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.tenant.domain.DbConfig;
+import com.fs.tenant.service.DbConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+@Profile("admin")
+@RestController
+@RequestMapping("/admin/dbConfig")
+public class DbConfigController extends BaseController {
+
+    @Autowired
+    private DbConfigService dbConfigService;
+
+    @PreAuthorize("@ss.hasPermi('admin:dbConfig:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(DbConfig dbConfig) {
+        startPage();
+        List<DbConfig> list = dbConfigService.selectDbConfigList(dbConfig);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('admin:dbConfig:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
+        return AjaxResult.success(dbConfigService.selectDbConfigById(id));
+    }
+
+    @PreAuthorize("@ss.hasPermi('admin:dbConfig:add')")
+    @PostMapping
+    public AjaxResult add(@RequestBody DbConfig dbConfig) {
+        return toAjax(dbConfigService.insertDbConfig(dbConfig));
+    }
+
+    @PreAuthorize("@ss.hasPermi('admin:dbConfig:edit')")
+    @PutMapping
+    public AjaxResult edit(@RequestBody DbConfig dbConfig) {
+        return toAjax(dbConfigService.updateDbConfig(dbConfig));
+    }
+
+    @PreAuthorize("@ss.hasPermi('admin:dbConfig:remove')")
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        return toAjax(dbConfigService.deleteDbConfigByIds(ids));
+    }
+
+    @PreAuthorize("@ss.hasPermi('admin:dbConfig:query')")
+    @GetMapping("/available")
+    public AjaxResult getAvailable() {
+        List<DbConfig> list = dbConfigService.selectDbConfigList(new DbConfig());
+        return AjaxResult.success(list);
+    }
+}

+ 2 - 0
fs-admin/src/main/java/com/fs/admin/controller/LiveAdminController.java → fs-admin-agent/src/main/java/com/fs/admin/controller/LiveAdminController.java

@@ -1,6 +1,7 @@
 package com.fs.admin.controller;
 
 import java.util.*;
+import org.springframework.context.annotation.Profile;
 
 import com.fs.admin.helper.AdminCrossTenantHelper;
 import com.fs.common.annotation.Excel;
@@ -19,6 +20,7 @@ import org.springframework.web.bind.annotation.*;
  * 总后台直播内容管理控制器
  * 遍历所有租户库查询 live_video 数据
  */
+@Profile("admin")
 @RestController
 @RequestMapping("/admin/live")
 public class LiveAdminController extends BaseController {

+ 59 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/LobsterAdminController.java

@@ -0,0 +1,59 @@
+package com.fs.admin.controller;
+
+import java.util.*;
+import org.springframework.context.annotation.Profile;
+
+import com.fs.admin.helper.AdminCrossTenantHelper;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 总后台龙虾引擎管理控制器
+ * 遍历所有租户库查询工作流实例数据
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/admin/lobster")
+public class LobsterAdminController extends BaseController {
+
+    @Autowired
+    private AdminCrossTenantHelper crossTenantHelper;
+
+    /**
+     * 查询所有租户的工作流实例列表
+     */
+    @PreAuthorize("@ss.hasPermi('admin:lobster:list')")
+    @GetMapping("/instance/list")
+    public TableDataInfo instanceList(@RequestParam(required = false) Long companyId,
+                                      @RequestParam(required = false) String companyName) {
+        List<Map<String, Object>> allList = crossTenantHelper.queryAcrossTenants(
+                companyId, companyName, (tenant, jdbc) ->
+                        jdbc.queryForList(
+                                "SELECT i.id, i.instance_name, i.workflow_id, i.status, " +
+                                "i.company_id, i.create_time, i.update_time " +
+                                "FROM lobster_workflow_instance i ORDER BY i.create_time DESC LIMIT 200"));
+        return getDataTable(allList);
+    }
+
+    /**
+     * 全局工作流运行统计
+     */
+    @PreAuthorize("@ss.hasPermi('admin:lobster:list')")
+    @GetMapping("/instance/stats")
+    public AjaxResult instanceStats(@RequestParam(required = false) Long companyId,
+                                    @RequestParam(required = false) String companyName) {
+        List<Map<String, Object>> allList = crossTenantHelper.queryAcrossTenants(
+                companyId, companyName, (tenant, jdbc) ->
+                        jdbc.queryForList("SELECT id, status FROM lobster_workflow_instance"));
+        Map<String, Object> stats = new HashMap<>();
+        stats.put("totalInstances", allList.size());
+        stats.put("runningInstances", allList.stream().filter(i -> "RUNNING".equals(String.valueOf(i.get("status")))).count());
+        stats.put("completedInstances", allList.stream().filter(i -> "COMPLETED".equals(String.valueOf(i.get("status")))).count());
+        stats.put("failedInstances", allList.stream().filter(i -> "FAILED".equals(String.valueOf(i.get("status")))).count());
+        return AjaxResult.success(stats);
+    }
+}

+ 2 - 0
fs-admin/src/main/java/com/fs/admin/controller/ProductAdminController.java → fs-admin-agent/src/main/java/com/fs/admin/controller/ProductAdminController.java

@@ -1,6 +1,7 @@
 package com.fs.admin.controller;
 
 import java.util.*;
+import org.springframework.context.annotation.Profile;
 
 import com.fs.admin.helper.AdminCrossTenantHelper;
 import com.fs.common.annotation.Excel;
@@ -19,6 +20,7 @@ import org.springframework.web.bind.annotation.*;
  * 总后台商品审核控制器
  * 遍历所有租户库查询 fs_store_product 数据
  */
+@Profile("admin")
 @RestController
 @RequestMapping("/admin/product")
 public class ProductAdminController extends BaseController {

+ 63 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/ProxyOperLogController.java

@@ -0,0 +1,63 @@
+package com.fs.admin.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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.proxy.domain.ProxyOperLog;
+import com.fs.proxy.service.IProxyOperLogService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 总后台-代理操作日志控制器
+ *
+ * @author fs
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/admin/proxyOperLog")
+public class ProxyOperLogController extends BaseController {
+
+    @Autowired
+    private IProxyOperLogService proxyOperLogService;
+
+    /**
+     * 查询代理操作日志列表
+     */
+    @PreAuthorize("@ss.hasPermi('admin:proxyOperLog:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(ProxyOperLog proxyOperLog) {
+        startPage();
+        List<ProxyOperLog> list = proxyOperLogService.selectProxyOperLogList(proxyOperLog);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出代理操作日志
+     */
+    @Log(title = "代理操作日志导出", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:proxyOperLog:export')")
+    @GetMapping("/export")
+    public AjaxResult export(ProxyOperLog proxyOperLog) {
+        List<ProxyOperLog> list = proxyOperLogService.selectProxyOperLogList(proxyOperLog);
+        ExcelUtil<ProxyOperLog> util = new ExcelUtil<>(ProxyOperLog.class);
+        return util.exportExcel(list, "代理操作日志");
+    }
+
+    /**
+     * 删除代理操作日志
+     */
+    @Log(title = "代理操作日志", businessType = BusinessType.DELETE)
+    @PreAuthorize("@ss.hasPermi('admin:proxyOperLog:remove')")
+    @DeleteMapping("/{operIds}")
+    public AjaxResult remove(@PathVariable Long[] operIds) {
+        return toAjax(proxyOperLogService.deleteProxyOperLogByIds(operIds));
+    }
+}

+ 102 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/QwExternalContactAdminController.java

@@ -0,0 +1,102 @@
+package com.fs.admin.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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.domain.Company;
+import com.fs.company.service.ICompanyService;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.service.IQwExternalContactService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 总后台企微用户管理控制器
+ * 查看所有租户的企微用户
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/admin/qwContact")
+public class QwExternalContactAdminController extends BaseController {
+
+    @Autowired
+    private IQwExternalContactService qwExternalContactService;
+
+    @Autowired
+    private ICompanyService companyService;
+
+    /** 导出企微外部联系人 */
+    @Log(title = "导出企微外部联系人", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:qwContact:list')")
+    @GetMapping("/export")
+    public AjaxResult export(QwExternalContact qwExternalContact) {
+        List<QwExternalContact> list = qwExternalContactService.selectQwExternalContactList(qwExternalContact);
+        ExcelUtil<QwExternalContact> util = new ExcelUtil<>(QwExternalContact.class);
+        return util.exportExcel(list, "企微外部联系人数据");
+    }
+
+    /**
+     * 查询所有租户的企微用户列表
+     */
+    @PreAuthorize("@ss.hasPermi('admin:qwContact:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(QwExternalContact qwExternalContact) {
+        startPage();
+        // 不设置companyId,查询所有租户的数据
+        List<QwExternalContact> list = qwExternalContactService.selectQwExternalContactList(qwExternalContact);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取企微用户详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('admin:qwContact:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
+        return AjaxResult.success(qwExternalContactService.selectQwExternalContactById(id));
+    }
+
+    /**
+     * 根据租户ID查询企微用户
+     */
+    @PreAuthorize("@ss.hasPermi('admin:qwContact:query')")
+    @GetMapping("/byCompany/{companyId}")
+    public TableDataInfo getByCompanyId(@PathVariable Long companyId) {
+        startPage();
+        QwExternalContact qwExternalContact = new QwExternalContact();
+        qwExternalContact.setCompanyId(companyId);
+        List<QwExternalContact> list = qwExternalContactService.selectQwExternalContactList(qwExternalContact);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取租户列表(用于筛选)
+     */
+    @PreAuthorize("@ss.hasPermi('admin:qwContact:query')")
+    @GetMapping("/companies")
+    public AjaxResult getCompanies() {
+        Company company = new Company();
+        company.setStatus(1); // 只查询启用的租户
+        List<Company> list = companyService.selectCompanyList(company);
+        return AjaxResult.success(list);
+    }
+
+    /**
+     * 统计各租户企微用户数量
+     */
+    @PreAuthorize("@ss.hasPermi('admin:qwContact:query')")
+    @GetMapping("/statistics")
+    public AjaxResult statistics() {
+        long total = qwExternalContactService.count();
+        java.util.Map<String, Object> result = new java.util.HashMap<>();
+        result.put("totalCount", total);
+        return AjaxResult.success(result);
+    }
+}

+ 82 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsAdvScrmBridgeController.java

@@ -0,0 +1,82 @@
+package com.fs.admin.controller.store;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.domain.FsAdvScrm;
+import com.fs.hisStore.service.IFsAdvScrmService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 广告 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsAdvController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2023-06-08
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/adv")
+public class FsAdvScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsAdvScrmService fsAdvService;
+
+    @PreAuthorize("@ss.hasPermi('store:adv:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsAdvScrm fsAdv)
+    {
+        startPage();
+        List<FsAdvScrm> list = fsAdvService.selectFsAdvList(fsAdv);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:adv:export')")
+    @Log(title = "广告", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsAdvScrm fsAdv)
+    {
+        List<FsAdvScrm> list = fsAdvService.selectFsAdvList(fsAdv);
+        ExcelUtil<FsAdvScrm> util = new ExcelUtil<FsAdvScrm>(FsAdvScrm.class);
+        return util.exportExcel(list, "广告数据");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:adv:query')")
+    @GetMapping(value = "/{advId}")
+    public AjaxResult getInfo(@PathVariable("advId") Long advId)
+    {
+        return AjaxResult.success(fsAdvService.selectFsAdvById(advId));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:adv:add')")
+    @Log(title = "广告", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsAdvScrm fsAdv)
+    {
+        return toAjax(fsAdvService.insertFsAdv(fsAdv));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:adv:edit')")
+    @Log(title = "广告", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsAdvScrm fsAdv)
+    {
+        return toAjax(fsAdvService.updateFsAdv(fsAdv));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:adv:remove')")
+    @Log(title = "广告", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{advIds}")
+    public AjaxResult remove(@PathVariable Long[] advIds)
+    {
+        return toAjax(fsAdvService.deleteFsAdvByIds(advIds));
+    }
+}

+ 82 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsMenuScrmBridgeController.java

@@ -0,0 +1,82 @@
+package com.fs.admin.controller.store;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.domain.FsMenuScrm;
+import com.fs.hisStore.service.IFsMenuScrmService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 用户端菜单管理 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsMenuScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2022-03-15
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/menu")
+public class FsMenuScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsMenuScrmService fsMenuService;
+
+    @PreAuthorize("@ss.hasPermi('store:menu:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsMenuScrm fsMenu)
+    {
+        startPage();
+        List<FsMenuScrm> list = fsMenuService.selectFsMenuList(fsMenu);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:menu:export')")
+    @Log(title = "用户端菜单管理", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsMenuScrm fsMenu)
+    {
+        List<FsMenuScrm> list = fsMenuService.selectFsMenuList(fsMenu);
+        ExcelUtil<FsMenuScrm> util = new ExcelUtil<FsMenuScrm>(FsMenuScrm.class);
+        return util.exportExcel(list, "menu");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:menu:query')")
+    @GetMapping(value = "/{menuId}")
+    public AjaxResult getInfo(@PathVariable("menuId") Long menuId)
+    {
+        return AjaxResult.success(fsMenuService.selectFsMenuById(menuId));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:menu:add')")
+    @Log(title = "用户端菜单管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsMenuScrm fsMenu)
+    {
+        return toAjax(fsMenuService.insertFsMenu(fsMenu));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:menu:edit')")
+    @Log(title = "用户端菜单管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsMenuScrm fsMenu)
+    {
+        return toAjax(fsMenuService.updateFsMenu(fsMenu));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:menu:remove')")
+    @Log(title = "用户端菜单管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{menuIds}")
+    public AjaxResult remove(@PathVariable Long[] menuIds)
+    {
+        return toAjax(fsMenuService.deleteFsMenuByIds(menuIds));
+    }
+}

+ 82 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsPrescribeDrugScrmBridgeController.java

@@ -0,0 +1,82 @@
+package com.fs.admin.controller.store;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.domain.FsPrescribeDrugScrm;
+import com.fs.hisStore.service.IFsPrescribeDrugScrmService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 处方药品 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsPrescribeDrugScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2022-03-15
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/prescribeDrug")
+public class FsPrescribeDrugScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsPrescribeDrugScrmService fsPrescribeDrugService;
+
+    @PreAuthorize("@ss.hasPermi('store:prescribeDrug:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsPrescribeDrugScrm fsPrescribeDrug)
+    {
+        startPage();
+        List<FsPrescribeDrugScrm> list = fsPrescribeDrugService.selectFsPrescribeDrugList(fsPrescribeDrug);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:prescribeDrug:export')")
+    @Log(title = "处方药品", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsPrescribeDrugScrm fsPrescribeDrug)
+    {
+        List<FsPrescribeDrugScrm> list = fsPrescribeDrugService.selectFsPrescribeDrugList(fsPrescribeDrug);
+        ExcelUtil<FsPrescribeDrugScrm> util = new ExcelUtil<FsPrescribeDrugScrm>(FsPrescribeDrugScrm.class);
+        return util.exportExcel(list, "prescribeDrug");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:prescribeDrug:query')")
+    @GetMapping(value = "/{drugId}")
+    public AjaxResult getInfo(@PathVariable("drugId") Long drugId)
+    {
+        return AjaxResult.success(fsPrescribeDrugService.selectFsPrescribeDrugById(drugId));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:prescribeDrug:add')")
+    @Log(title = "处方药品", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsPrescribeDrugScrm fsPrescribeDrug)
+    {
+        return toAjax(fsPrescribeDrugService.insertFsPrescribeDrug(fsPrescribeDrug));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:prescribeDrug:edit')")
+    @Log(title = "处方药品", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsPrescribeDrugScrm fsPrescribeDrug)
+    {
+        return toAjax(fsPrescribeDrugService.updateFsPrescribeDrug(fsPrescribeDrug));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:prescribeDrug:remove')")
+    @Log(title = "处方药品", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{drugIds}")
+    public AjaxResult remove(@PathVariable Long[] drugIds)
+    {
+        return toAjax(fsPrescribeDrugService.deleteFsPrescribeDrugByIds(drugIds));
+    }
+}

+ 82 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsPrescribeScrmBridgeController.java

@@ -0,0 +1,82 @@
+package com.fs.admin.controller.store;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.domain.FsPrescribeScrm;
+import com.fs.hisStore.service.IFsPrescribeScrmService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 处方 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsPrescribeScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2022-03-15
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/prescribe")
+public class FsPrescribeScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsPrescribeScrmService fsPrescribeService;
+
+    @PreAuthorize("@ss.hasPermi('store:prescribe:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsPrescribeScrm fsPrescribe)
+    {
+        startPage();
+        List<FsPrescribeScrm> list = fsPrescribeService.selectFsPrescribeList(fsPrescribe);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:prescribe:export')")
+    @Log(title = "处方", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsPrescribeScrm fsPrescribe)
+    {
+        List<FsPrescribeScrm> list = fsPrescribeService.selectFsPrescribeList(fsPrescribe);
+        ExcelUtil<FsPrescribeScrm> util = new ExcelUtil<FsPrescribeScrm>(FsPrescribeScrm.class);
+        return util.exportExcel(list, "处方数据");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:prescribe:query')")
+    @GetMapping(value = "/{prescribeId}")
+    public AjaxResult getInfo(@PathVariable("prescribeId") Long prescribeId)
+    {
+        return AjaxResult.success(fsPrescribeService.selectFsPrescribeById(prescribeId));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:prescribe:add')")
+    @Log(title = "处方", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsPrescribeScrm fsPrescribe)
+    {
+        return toAjax(fsPrescribeService.insertFsPrescribe(fsPrescribe));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:prescribe:edit')")
+    @Log(title = "处方", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsPrescribeScrm fsPrescribe)
+    {
+        return toAjax(fsPrescribeService.updateFsPrescribe(fsPrescribe));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:prescribe:remove')")
+    @Log(title = "处方", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{prescribeIds}")
+    public AjaxResult remove(@PathVariable Long[] prescribeIds)
+    {
+        return toAjax(fsPrescribeService.deleteFsPrescribeByIds(prescribeIds));
+    }
+}

+ 82 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsShippingTemplatesFreeScrmBridgeController.java

@@ -0,0 +1,82 @@
+package com.fs.admin.controller.store;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.domain.FsShippingTemplatesFreeScrm;
+import com.fs.hisStore.service.IFsShippingTemplatesFreeScrmService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 免邮费 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsShippingTemplatesFreeScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2022-03-15
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/shippingTemplatesFree")
+public class FsShippingTemplatesFreeScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsShippingTemplatesFreeScrmService fsShippingTemplatesFreeService;
+
+    @PreAuthorize("@ss.hasPermi('store:shippingTemplatesFree:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsShippingTemplatesFreeScrm fsShippingTemplatesFree)
+    {
+        startPage();
+        List<FsShippingTemplatesFreeScrm> list = fsShippingTemplatesFreeService.selectFsShippingTemplatesFreeList(fsShippingTemplatesFree);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:shippingTemplatesFree:export')")
+    @Log(title = "免邮费", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsShippingTemplatesFreeScrm fsShippingTemplatesFree)
+    {
+        List<FsShippingTemplatesFreeScrm> list = fsShippingTemplatesFreeService.selectFsShippingTemplatesFreeList(fsShippingTemplatesFree);
+        ExcelUtil<FsShippingTemplatesFreeScrm> util = new ExcelUtil<FsShippingTemplatesFreeScrm>(FsShippingTemplatesFreeScrm.class);
+        return util.exportExcel(list, "shippingTemplatesFree");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:shippingTemplatesFree:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsShippingTemplatesFreeService.selectFsShippingTemplatesFreeById(id));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:shippingTemplatesFree:add')")
+    @Log(title = "免邮费", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsShippingTemplatesFreeScrm fsShippingTemplatesFree)
+    {
+        return toAjax(fsShippingTemplatesFreeService.insertFsShippingTemplatesFree(fsShippingTemplatesFree));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:shippingTemplatesFree:edit')")
+    @Log(title = "免邮费", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsShippingTemplatesFreeScrm fsShippingTemplatesFree)
+    {
+        return toAjax(fsShippingTemplatesFreeService.updateFsShippingTemplatesFree(fsShippingTemplatesFree));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:shippingTemplatesFree:remove')")
+    @Log(title = "免邮费", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsShippingTemplatesFreeService.deleteFsShippingTemplatesFreeByIds(ids));
+    }
+}

+ 82 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsShippingTemplatesRegionScrmBridgeController.java

@@ -0,0 +1,82 @@
+package com.fs.admin.controller.store;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.domain.FsShippingTemplatesRegionScrm;
+import com.fs.hisStore.service.IFsShippingTemplatesRegionScrmService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 邮费区域 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsShippingTemplatesRegionScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2022-03-15
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/shippingTemplatesRegion")
+public class FsShippingTemplatesRegionScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsShippingTemplatesRegionScrmService fsShippingTemplatesRegionService;
+
+    @PreAuthorize("@ss.hasPermi('store:shippingTemplatesRegion:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsShippingTemplatesRegionScrm fsShippingTemplatesRegion)
+    {
+        startPage();
+        List<FsShippingTemplatesRegionScrm> list = fsShippingTemplatesRegionService.selectFsShippingTemplatesRegionList(fsShippingTemplatesRegion);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:shippingTemplatesRegion:export')")
+    @Log(title = "邮费区域", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsShippingTemplatesRegionScrm fsShippingTemplatesRegion)
+    {
+        List<FsShippingTemplatesRegionScrm> list = fsShippingTemplatesRegionService.selectFsShippingTemplatesRegionList(fsShippingTemplatesRegion);
+        ExcelUtil<FsShippingTemplatesRegionScrm> util = new ExcelUtil<FsShippingTemplatesRegionScrm>(FsShippingTemplatesRegionScrm.class);
+        return util.exportExcel(list, "shippingTemplatesRegion");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:shippingTemplatesRegion:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsShippingTemplatesRegionService.selectFsShippingTemplatesRegionById(id));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:shippingTemplatesRegion:add')")
+    @Log(title = "邮费区域", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsShippingTemplatesRegionScrm fsShippingTemplatesRegion)
+    {
+        return toAjax(fsShippingTemplatesRegionService.insertFsShippingTemplatesRegion(fsShippingTemplatesRegion));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:shippingTemplatesRegion:edit')")
+    @Log(title = "邮费区域", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsShippingTemplatesRegionScrm fsShippingTemplatesRegion)
+    {
+        return toAjax(fsShippingTemplatesRegionService.updateFsShippingTemplatesRegion(fsShippingTemplatesRegion));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:shippingTemplatesRegion:remove')")
+    @Log(title = "邮费区域", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsShippingTemplatesRegionService.deleteFsShippingTemplatesRegionByIds(ids));
+    }
+}

+ 93 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsShippingTemplatesScrmBridgeController.java

@@ -0,0 +1,93 @@
+package com.fs.admin.controller.store;
+
+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;
+import com.fs.hisStore.domain.FsShippingTemplatesScrm;
+import com.fs.hisStore.param.FsShippingTemplatesAddEditParam;
+import com.fs.hisStore.service.IFsShippingTemplatesScrmService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 运费模板 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsShippingTemplatesScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2022-03-15
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/shippingTemplates")
+public class FsShippingTemplatesScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsShippingTemplatesScrmService fsShippingTemplatesService;
+
+    @PreAuthorize("@ss.hasPermi('store:shippingTemplates:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsShippingTemplatesScrm fsShippingTemplates)
+    {
+        startPage();
+        fsShippingTemplates.setIsDel(0);
+        List<FsShippingTemplatesScrm> list = fsShippingTemplatesService.selectFsShippingTemplatesList(fsShippingTemplates);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:shippingTemplates:export')")
+    @Log(title = "运费模板", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsShippingTemplatesScrm fsShippingTemplates)
+    {
+        List<FsShippingTemplatesScrm> list = fsShippingTemplatesService.selectFsShippingTemplatesList(fsShippingTemplates);
+        ExcelUtil<FsShippingTemplatesScrm> util = new ExcelUtil<FsShippingTemplatesScrm>(FsShippingTemplatesScrm.class);
+        return util.exportExcel(list, "shippingTemplates");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:shippingTemplates:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsShippingTemplatesService.selectFsShippingTemplatesById(id));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:shippingTemplates:add')")
+    @Log(title = "运费模板", businessType = BusinessType.INSERT)
+    @PostMapping
+    public R add(@RequestBody FsShippingTemplatesAddEditParam param)
+    {
+        return fsShippingTemplatesService.addOrEdit(param);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:shippingTemplates:edit')")
+    @Log(title = "运费模板", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public R edit(@RequestBody FsShippingTemplatesAddEditParam param)
+    {
+        return fsShippingTemplatesService.addOrEdit(param);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:shippingTemplates:remove')")
+    @Log(title = "运费模板", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsShippingTemplatesService.deleteFsShippingTemplatesByIds(ids));
+    }
+
+    @GetMapping("/getAllList")
+    public R getAllList(FsShippingTemplatesScrm fsShippingTemplates)
+    {
+        fsShippingTemplates.setIsDel(0);
+        List<FsShippingTemplatesScrm> list = fsShippingTemplatesService.selectFsShippingTemplatesList(fsShippingTemplates);
+        return R.ok().put("data", list);
+    }
+}

+ 93 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreActivityScrmBridgeController.java

@@ -0,0 +1,93 @@
+package com.fs.admin.controller.store;
+
+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;
+import com.fs.hisStore.domain.FsStoreActivityScrm;
+import com.fs.hisStore.service.IFsStoreActivityScrmService;
+import com.fs.hisStore.service.IFsStoreProductScrmService;
+import com.fs.hisStore.vo.FsStoreProductActivityListVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 活动 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsStoreActivityScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2022-11-18
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/storeActivity")
+public class FsStoreActivityScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsStoreActivityScrmService fsStoreActivityService;
+    @Autowired
+    private IFsStoreProductScrmService productService;
+
+    @PreAuthorize("@ss.hasPermi('store:storeActivity:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreActivityScrm fsStoreActivity)
+    {
+        startPage();
+        List<FsStoreActivityScrm> list = fsStoreActivityService.selectFsStoreActivityList(fsStoreActivity);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeActivity:export')")
+    @Log(title = "活动", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreActivityScrm fsStoreActivity)
+    {
+        List<FsStoreActivityScrm> list = fsStoreActivityService.selectFsStoreActivityList(fsStoreActivity);
+        ExcelUtil<FsStoreActivityScrm> util = new ExcelUtil<FsStoreActivityScrm>(FsStoreActivityScrm.class);
+        return util.exportExcel(list, "storeActivity");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeActivity:query')")
+    @GetMapping(value = "/{activityId}")
+    public R getInfo(@PathVariable("activityId") Long activityId)
+    {
+        FsStoreActivityScrm activity = fsStoreActivityService.selectFsStoreActivityById(activityId);
+        List<FsStoreProductActivityListVO> products = new ArrayList<>();
+        if (activity.getProductIds() != null) {
+            products = productService.selectFsStoreProductByIds(activity.getProductIds());
+        }
+        return R.ok().put("activity", activity).put("products", products);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeActivity:add')")
+    @Log(title = "活动", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreActivityScrm fsStoreActivity)
+    {
+        return toAjax(fsStoreActivityService.insertFsStoreActivity(fsStoreActivity));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeActivity:edit')")
+    @Log(title = "活动", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreActivityScrm fsStoreActivity)
+    {
+        return toAjax(fsStoreActivityService.updateFsStoreActivity(fsStoreActivity));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeActivity:remove')")
+    @Log(title = "活动", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{activityIds}")
+    public AjaxResult remove(@PathVariable Long[] activityIds)
+    {
+        return toAjax(fsStoreActivityService.deleteFsStoreActivityByIds(activityIds));
+    }
+}

+ 82 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreAfterSalesItemScrmBridgeController.java

@@ -0,0 +1,82 @@
+package com.fs.admin.controller.store;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.domain.FsStoreAfterSalesItemScrm;
+import com.fs.hisStore.service.IFsStoreAfterSalesItemScrmService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 售后子 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsStoreAfterSalesItemScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2022-03-15
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/storeAfterSalesItem")
+public class FsStoreAfterSalesItemScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsStoreAfterSalesItemScrmService fsStoreAfterSalesItemService;
+
+    @PreAuthorize("@ss.hasPermi('store:storeAfterSalesItem:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreAfterSalesItemScrm fsStoreAfterSalesItem)
+    {
+        startPage();
+        List<FsStoreAfterSalesItemScrm> list = fsStoreAfterSalesItemService.selectFsStoreAfterSalesItemList(fsStoreAfterSalesItem);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeAfterSalesItem:export')")
+    @Log(title = "售后子", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreAfterSalesItemScrm fsStoreAfterSalesItem)
+    {
+        List<FsStoreAfterSalesItemScrm> list = fsStoreAfterSalesItemService.selectFsStoreAfterSalesItemList(fsStoreAfterSalesItem);
+        ExcelUtil<FsStoreAfterSalesItemScrm> util = new ExcelUtil<FsStoreAfterSalesItemScrm>(FsStoreAfterSalesItemScrm.class);
+        return util.exportExcel(list, "storeAfterSalesItem");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeAfterSalesItem:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsStoreAfterSalesItemService.selectFsStoreAfterSalesItemById(id));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeAfterSalesItem:add')")
+    @Log(title = "售后子", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreAfterSalesItemScrm fsStoreAfterSalesItem)
+    {
+        return toAjax(fsStoreAfterSalesItemService.insertFsStoreAfterSalesItem(fsStoreAfterSalesItem));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeAfterSalesItem:edit')")
+    @Log(title = "售后子", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreAfterSalesItemScrm fsStoreAfterSalesItem)
+    {
+        return toAjax(fsStoreAfterSalesItemService.updateFsStoreAfterSalesItem(fsStoreAfterSalesItem));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeAfterSalesItem:remove')")
+    @Log(title = "售后子", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsStoreAfterSalesItemService.deleteFsStoreAfterSalesItemByIds(ids));
+    }
+}

+ 233 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreAfterSalesScrmBridgeController.java

@@ -0,0 +1,233 @@
+package com.fs.admin.controller.store;
+
+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;
+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.ParseUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.config.saas.ProjectConfig;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.security.SecurityUtils;
+import com.fs.his.domain.FsUser;
+import com.fs.his.service.IFsUserService;
+import com.fs.hisStore.domain.FsStoreAfterSalesItemScrm;
+import com.fs.hisStore.domain.FsStoreAfterSalesScrm;
+import com.fs.hisStore.domain.FsStoreAfterSalesStatusScrm;
+import com.fs.hisStore.domain.FsStoreOrderScrm;
+import com.fs.hisStore.param.FsStoreAfterSalesAudit1Param;
+import com.fs.hisStore.param.FsStoreAfterSalesAudit2Param;
+import com.fs.hisStore.param.FsStoreAfterSalesCancelParam;
+import com.fs.hisStore.param.FsStoreAfterSalesRefundParam;
+import com.fs.hisStore.service.IFsStoreAfterSalesItemScrmService;
+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.system.mapper.SysConfigMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.math.BigDecimal;
+import java.text.ParseException;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * 售后记录 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsStoreAfterSalesScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2022-03-15
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/storeAfterSales")
+public class FsStoreAfterSalesScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsStoreOrderScrmService fsStoreOrderService;
+    @Autowired
+    private IFsUserService userService;
+    @Autowired
+    private IFsStoreAfterSalesScrmService fsStoreAfterSalesService;
+    @Autowired
+    private IFsStoreAfterSalesItemScrmService fsStoreAfterSalesItemService;
+    @Autowired
+    private IFsStoreAfterSalesStatusScrmService storeAfterSalesStatusService;
+    @Autowired
+    private SysConfigMapper sysConfigMapper;
+
+    @PreAuthorize("@ss.hasPermi('store:storeAfterSales:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreAfterSalesScrm fsStoreAfterSales)
+    {
+        startPage();
+        List<FsStoreAfterSalesVO> list = fsStoreAfterSalesService.selectFsStoreAfterSalesListVO(fsStoreAfterSales);
+        for (FsStoreAfterSalesVO vo : list) {
+            vo.setUserPhone(ParseUtils.parsePhone(vo.getUserPhone()));
+        }
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeAfterSales:export')")
+    @Log(title = "售后记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreAfterSalesScrm fsStoreAfterSales)
+    {
+        if (StringUtils.isNotEmpty(fsStoreAfterSales.getBeginTime()) && StringUtils.isNotEmpty(fsStoreAfterSales.getEndTime())) {
+            fsStoreAfterSales.setBeginTime(null);
+            fsStoreAfterSales.setEndTime(null);
+        }
+        if (fsStoreAfterSalesService.isEntityNull(fsStoreAfterSales) && Objects.isNull(fsStoreAfterSales.getParams())) {
+            return AjaxResult.error("请筛选数据导出");
+        }
+
+        List<FsStoreAfterSalesVO> list = fsStoreAfterSalesService.selectFsStoreAfterSalesListVOExport(fsStoreAfterSales);
+        if ("北京卓美".equals(ProjectConfig.getFromDB(sysConfigMapper).getCloudHost().getCompanyName())) {
+            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.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.getOrderCreateTime());
+                            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.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, "退款订单导出");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeAfterSales:query')")
+    @GetMapping(value = "/{id}")
+    public R getInfo(@PathVariable("id") Long id)
+    {
+        FsStoreAfterSalesScrm afterSales = fsStoreAfterSalesService.selectFsStoreAfterSalesById(id);
+        FsStoreAfterSalesItemScrm map = new FsStoreAfterSalesItemScrm();
+        map.setStoreAfterSalesId(id);
+        List<FsStoreAfterSalesItemScrm> items = fsStoreAfterSalesItemService.selectFsStoreAfterSalesItemList(map);
+        FsStoreAfterSalesStatusScrm statusMap = new FsStoreAfterSalesStatusScrm();
+        statusMap.setStoreAfterSalesId(id);
+        List<FsStoreAfterSalesStatusScrm> logs = storeAfterSalesStatusService.selectFsStoreAfterSalesStatusList(statusMap);
+        FsUser user = userService.selectFsUserById(afterSales.getUserId());
+        user.setPhone(ParseUtils.parsePhone(user.getPhone()));
+        FsStoreOrderScrm order = fsStoreOrderService.selectFsStoreOrderByOrderCode(afterSales.getOrderCode());
+        return R.ok().put("afterSales", afterSales).put("items", items).put("logs", logs).put("user", user).put("order", order);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeAfterSales:add')")
+    @Log(title = "售后记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreAfterSalesScrm fsStoreAfterSales)
+    {
+        return toAjax(fsStoreAfterSalesService.insertFsStoreAfterSales(fsStoreAfterSales));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeAfterSales:edit')")
+    @Log(title = "售后记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreAfterSalesScrm fsStoreAfterSales)
+    {
+        if ((!"".equals(fsStoreAfterSales.getDeliveryName()) && !"".equals(fsStoreAfterSales.getDeliverySn())) || (fsStoreAfterSales.getDeliveryName() == null && fsStoreAfterSales.getDeliverySn() == null)) {
+            fsStoreAfterSales.setStatus(2);
+        }
+        return toAjax(fsStoreAfterSalesService.updateDelivery(fsStoreAfterSales));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeAfterSales:remove')")
+    @Log(title = "售后记录", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsStoreAfterSalesService.deleteFsStoreAfterSalesByIds(ids));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeAfterSales:audit1')")
+    @PostMapping("/audit1")
+    public R audit1(@RequestBody FsStoreAfterSalesAudit1Param param)
+    {
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        param.setOperator(loginUser.getCompanyUser().getNickName());
+        return fsStoreAfterSalesService.audit1(param);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeAfterSales:audit2')")
+    @PostMapping("/audit2")
+    public R audit2(@RequestBody FsStoreAfterSalesAudit2Param param)
+    {
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        param.setOperator(loginUser.getCompanyUser().getNickName());
+        return fsStoreAfterSalesService.audit2(param);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeAfterSales:refund')")
+    @PostMapping("/refund")
+    public R refund(@RequestBody FsStoreAfterSalesRefundParam param)
+    {
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        param.setOperator(loginUser.getCompanyUser().getNickName());
+        return fsStoreAfterSalesService.refundMoney(param);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeAfterSales:cancel')")
+    @PostMapping("/cancel")
+    public R cancel(@RequestBody FsStoreAfterSalesCancelParam param) throws ParseException
+    {
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        param.setOperator(loginUser.getCompanyUser().getNickName());
+        return fsStoreAfterSalesService.cancel(param);
+    }
+
+    @GetMapping("/getStoreAfterSales")
+    public R getStoreAfterSales(@RequestParam("orderCode") String orderCode)
+    {
+        List<FsStoreAfterSalesVO> vo = fsStoreAfterSalesService.selectFsStoreAfterSalesVOByOrderCode(orderCode);
+        return R.ok().put("data", vo);
+    }
+}

+ 82 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreAfterSalesStatusScrmBridgeController.java

@@ -0,0 +1,82 @@
+package com.fs.admin.controller.store;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.domain.FsStoreAfterSalesStatusScrm;
+import com.fs.hisStore.service.IFsStoreAfterSalesStatusScrmService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 售后订单操作 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsStoreAfterSalesStatusScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2022-03-15
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/storeAfterSalesStatus")
+public class FsStoreAfterSalesStatusScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsStoreAfterSalesStatusScrmService fsStoreAfterSalesStatusService;
+
+    @PreAuthorize("@ss.hasPermi('store:storeAfterSalesStatus:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreAfterSalesStatusScrm fsStoreAfterSalesStatus)
+    {
+        startPage();
+        List<FsStoreAfterSalesStatusScrm> list = fsStoreAfterSalesStatusService.selectFsStoreAfterSalesStatusList(fsStoreAfterSalesStatus);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeAfterSalesStatus:export')")
+    @Log(title = "售后订单操作", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreAfterSalesStatusScrm fsStoreAfterSalesStatus)
+    {
+        List<FsStoreAfterSalesStatusScrm> list = fsStoreAfterSalesStatusService.selectFsStoreAfterSalesStatusList(fsStoreAfterSalesStatus);
+        ExcelUtil<FsStoreAfterSalesStatusScrm> util = new ExcelUtil<FsStoreAfterSalesStatusScrm>(FsStoreAfterSalesStatusScrm.class);
+        return util.exportExcel(list, "storeAfterSalesStatus");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeAfterSalesStatus:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsStoreAfterSalesStatusService.selectFsStoreAfterSalesStatusById(id));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeAfterSalesStatus:add')")
+    @Log(title = "售后订单操作", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreAfterSalesStatusScrm fsStoreAfterSalesStatus)
+    {
+        return toAjax(fsStoreAfterSalesStatusService.insertFsStoreAfterSalesStatus(fsStoreAfterSalesStatus));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeAfterSalesStatus:edit')")
+    @Log(title = "售后订单操作", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreAfterSalesStatusScrm fsStoreAfterSalesStatus)
+    {
+        return toAjax(fsStoreAfterSalesStatusService.updateFsStoreAfterSalesStatus(fsStoreAfterSalesStatus));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeAfterSalesStatus:remove')")
+    @Log(title = "售后订单操作", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsStoreAfterSalesStatusService.deleteFsStoreAfterSalesStatusByIds(ids));
+    }
+}

+ 82 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreCartScrmBridgeController.java

@@ -0,0 +1,82 @@
+package com.fs.admin.controller.store;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.domain.FsStoreCartScrm;
+import com.fs.hisStore.service.IFsStoreCartScrmService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 购物车 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsStoreCartScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2022-03-21
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/storeCart")
+public class FsStoreCartScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsStoreCartScrmService fsStoreCartService;
+
+    @PreAuthorize("@ss.hasPermi('store:storeCart:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreCartScrm fsStoreCart)
+    {
+        startPage();
+        List<FsStoreCartScrm> list = fsStoreCartService.selectFsStoreCartList(fsStoreCart);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeCart:export')")
+    @Log(title = "购物车", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreCartScrm fsStoreCart)
+    {
+        List<FsStoreCartScrm> list = fsStoreCartService.selectFsStoreCartList(fsStoreCart);
+        ExcelUtil<FsStoreCartScrm> util = new ExcelUtil<FsStoreCartScrm>(FsStoreCartScrm.class);
+        return util.exportExcel(list, "storeCart");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeCart:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsStoreCartService.selectFsStoreCartById(id));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeCart:add')")
+    @Log(title = "购物车", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreCartScrm fsStoreCart)
+    {
+        return toAjax(fsStoreCartService.insertFsStoreCart(fsStoreCart));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeCart:edit')")
+    @Log(title = "购物车", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreCartScrm fsStoreCart)
+    {
+        return toAjax(fsStoreCartService.updateFsStoreCart(fsStoreCart));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeCart:remove')")
+    @Log(title = "购物车", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsStoreCartService.deleteFsStoreCartByIds(ids));
+    }
+}

+ 83 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreCouponIssueScrmBridgeController.java

@@ -0,0 +1,83 @@
+package com.fs.admin.controller.store;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.domain.FsStoreCouponIssueScrm;
+import com.fs.hisStore.service.IFsStoreCouponIssueScrmService;
+import com.fs.hisStore.vo.FsStoreCouponIssueVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 优惠券发放 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsStoreCouponIssueScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2022-03-15
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/storeCouponIssue")
+public class FsStoreCouponIssueScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsStoreCouponIssueScrmService fsStoreCouponIssueService;
+
+    @PreAuthorize("@ss.hasPermi('store:storeCouponIssue:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreCouponIssueScrm fsStoreCouponIssue)
+    {
+        startPage();
+        List<FsStoreCouponIssueVO> list = fsStoreCouponIssueService.selectFsStoreCouponIssueListVO(fsStoreCouponIssue);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeCouponIssue:export')")
+    @Log(title = "优惠券发放", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreCouponIssueScrm fsStoreCouponIssue)
+    {
+        List<FsStoreCouponIssueScrm> list = fsStoreCouponIssueService.selectFsStoreCouponIssueList(fsStoreCouponIssue);
+        ExcelUtil<FsStoreCouponIssueScrm> util = new ExcelUtil<FsStoreCouponIssueScrm>(FsStoreCouponIssueScrm.class);
+        return util.exportExcel(list, "storeCouponIssue");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeCouponIssue:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsStoreCouponIssueService.selectFsStoreCouponIssueById(id));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeCouponIssue:add')")
+    @Log(title = "优惠券发放", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreCouponIssueScrm fsStoreCouponIssue)
+    {
+        return toAjax(fsStoreCouponIssueService.insertFsStoreCouponIssue(fsStoreCouponIssue));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeCouponIssue:edit')")
+    @Log(title = "优惠券发放", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreCouponIssueScrm fsStoreCouponIssue)
+    {
+        return toAjax(fsStoreCouponIssueService.updateFsStoreCouponIssue(fsStoreCouponIssue));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeCouponIssue:remove')")
+    @Log(title = "优惠券发放", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsStoreCouponIssueService.deleteFsStoreCouponIssueByIds(ids));
+    }
+}

+ 82 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreCouponIssueUserScrmBridgeController.java

@@ -0,0 +1,82 @@
+package com.fs.admin.controller.store;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.domain.FsStoreCouponIssueUserScrm;
+import com.fs.hisStore.service.IFsStoreCouponIssueUserScrmService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 优惠券用户领取记录 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsStoreCouponIssueUserScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2022-03-15
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/storeCouponIssueUser")
+public class FsStoreCouponIssueUserScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsStoreCouponIssueUserScrmService fsStoreCouponIssueUserService;
+
+    @PreAuthorize("@ss.hasPermi('store:storeCouponIssueUser:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreCouponIssueUserScrm fsStoreCouponIssueUser)
+    {
+        startPage();
+        List<FsStoreCouponIssueUserScrm> list = fsStoreCouponIssueUserService.selectFsStoreCouponIssueUserList(fsStoreCouponIssueUser);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeCouponIssueUser:export')")
+    @Log(title = "优惠券用户领取记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreCouponIssueUserScrm fsStoreCouponIssueUser)
+    {
+        List<FsStoreCouponIssueUserScrm> list = fsStoreCouponIssueUserService.selectFsStoreCouponIssueUserList(fsStoreCouponIssueUser);
+        ExcelUtil<FsStoreCouponIssueUserScrm> util = new ExcelUtil<FsStoreCouponIssueUserScrm>(FsStoreCouponIssueUserScrm.class);
+        return util.exportExcel(list, "storeCouponIssueUser");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeCouponIssueUser:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsStoreCouponIssueUserService.selectFsStoreCouponIssueUserById(id));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeCouponIssueUser:add')")
+    @Log(title = "优惠券用户领取记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreCouponIssueUserScrm fsStoreCouponIssueUser)
+    {
+        return toAjax(fsStoreCouponIssueUserService.insertFsStoreCouponIssueUser(fsStoreCouponIssueUser));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeCouponIssueUser:edit')")
+    @Log(title = "优惠券用户领取记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreCouponIssueUserScrm fsStoreCouponIssueUser)
+    {
+        return toAjax(fsStoreCouponIssueUserService.updateFsStoreCouponIssueUser(fsStoreCouponIssueUser));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeCouponIssueUser:remove')")
+    @Log(title = "优惠券用户领取记录", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsStoreCouponIssueUserService.deleteFsStoreCouponIssueUserByIds(ids));
+    }
+}

+ 131 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreCouponScrmBridgeController.java

@@ -0,0 +1,131 @@
+package com.fs.admin.controller.store;
+
+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;
+import com.fs.hisStore.domain.FsStoreCouponScrm;
+import com.fs.hisStore.domain.FsStoreCouponIssueScrm;
+import com.fs.hisStore.param.FsStoreCouponPublishParam;
+import com.fs.hisStore.service.IFsStoreCouponIssueScrmService;
+import com.fs.hisStore.service.IFsStoreCouponScrmService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 优惠券 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsStoreCouponScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2022-03-15
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/storeCoupon")
+public class FsStoreCouponScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsStoreCouponScrmService fsStoreCouponService;
+    @Autowired
+    private IFsStoreCouponIssueScrmService fsStoreCouponIssueService;
+
+    @PreAuthorize("@ss.hasPermi('store:storeCoupon:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreCouponScrm fsStoreCoupon)
+    {
+        startPage();
+        fsStoreCoupon.setIsDel(0);
+        List<FsStoreCouponScrm> list = fsStoreCouponService.selectFsStoreCouponList(fsStoreCoupon);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeCoupon:export')")
+    @Log(title = "优惠券", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreCouponScrm fsStoreCoupon)
+    {
+        List<FsStoreCouponScrm> list = fsStoreCouponService.selectFsStoreCouponList(fsStoreCoupon);
+        ExcelUtil<FsStoreCouponScrm> util = new ExcelUtil<FsStoreCouponScrm>(FsStoreCouponScrm.class);
+        return util.exportExcel(list, "storeCoupon");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeCoupon:query')")
+    @GetMapping(value = "/{couponId}")
+    public AjaxResult getInfo(@PathVariable("couponId") Long couponId)
+    {
+        return AjaxResult.success(fsStoreCouponService.selectFsStoreCouponById(couponId));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeCoupon:add')")
+    @Log(title = "优惠券", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreCouponScrm fsStoreCoupon)
+    {
+        return toAjax(fsStoreCouponService.insertFsStoreCoupon(fsStoreCoupon));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeCoupon:edit')")
+    @Log(title = "优惠券", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreCouponScrm fsStoreCoupon)
+    {
+        return toAjax(fsStoreCouponService.updateFsStoreCoupon(fsStoreCoupon));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeCoupon:remove')")
+    @Log(title = "优惠券", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{couponIds}")
+    public AjaxResult remove(@PathVariable Long[] couponIds)
+    {
+        return toAjax(fsStoreCouponService.deleteFsStoreCouponByIds(couponIds));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeCoupon:publish')")
+    @PostMapping("/publish")
+    public AjaxResult publish(@RequestBody FsStoreCouponPublishParam publishParam)
+    {
+        FsStoreCouponScrm coupon = fsStoreCouponService.selectFsStoreCouponById(publishParam.getCouponId());
+        FsStoreCouponIssueScrm issue = new FsStoreCouponIssueScrm();
+        issue.setCouponId(publishParam.getCouponId());
+        issue.setCouponName(coupon.getTitle());
+        issue.setCouponType(coupon.getType());
+        issue.setStartTime(publishParam.getStartTime());
+        issue.setLimitTime(publishParam.getLimitTime());
+        issue.setTotalCount(publishParam.getTotalCount());
+        issue.setRemainCount(0);
+        issue.setIsPermanent(0);
+        issue.setStatus(1);
+        issue.setCreateTime(new Date());
+        return toAjax(fsStoreCouponIssueService.insertFsStoreCouponIssue(issue));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeCoupon:batchPublish')")
+    @PostMapping("/batchPublish")
+    public R batchPublish(@RequestBody FsStoreCouponPublishParam publishParam)
+    {
+        List<FsStoreCouponScrm> list = fsStoreCouponService.selectFsStoreCouponByIds(publishParam.getIds());
+        for (FsStoreCouponScrm coupon : list) {
+            FsStoreCouponIssueScrm issue = new FsStoreCouponIssueScrm();
+            issue.setCouponId(coupon.getCouponId());
+            issue.setCouponName(coupon.getTitle());
+            issue.setCouponType(coupon.getType());
+            issue.setStartTime(publishParam.getStartTime());
+            issue.setLimitTime(publishParam.getLimitTime());
+            issue.setTotalCount(publishParam.getTotalCount());
+            issue.setRemainCount(0);
+            issue.setIsPermanent(0);
+            issue.setStatus(1);
+            issue.setCreateTime(new Date());
+            fsStoreCouponIssueService.insertFsStoreCouponIssue(issue);
+        }
+        return R.ok();
+    }
+}

+ 82 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreCouponUserScrmBridgeController.java

@@ -0,0 +1,82 @@
+package com.fs.admin.controller.store;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.domain.FsStoreCouponUserScrm;
+import com.fs.hisStore.service.IFsStoreCouponUserScrmService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 优惠券发放记录 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsStoreCouponUserScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2022-03-15
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/storeCouponUser")
+public class FsStoreCouponUserScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsStoreCouponUserScrmService fsStoreCouponUserService;
+
+    @PreAuthorize("@ss.hasPermi('store:storeCouponUser:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreCouponUserScrm fsStoreCouponUser)
+    {
+        startPage();
+        List<FsStoreCouponUserScrm> list = fsStoreCouponUserService.selectFsStoreCouponUserList(fsStoreCouponUser);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeCouponUser:export')")
+    @Log(title = "优惠券发放记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreCouponUserScrm fsStoreCouponUser)
+    {
+        List<FsStoreCouponUserScrm> list = fsStoreCouponUserService.selectFsStoreCouponUserList(fsStoreCouponUser);
+        ExcelUtil<FsStoreCouponUserScrm> util = new ExcelUtil<FsStoreCouponUserScrm>(FsStoreCouponUserScrm.class);
+        return util.exportExcel(list, "优惠券发放记录");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeCouponUser:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsStoreCouponUserService.selectFsStoreCouponUserById(id));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeCouponUser:add')")
+    @Log(title = "优惠券发放记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreCouponUserScrm fsStoreCouponUser)
+    {
+        return toAjax(fsStoreCouponUserService.insertFsStoreCouponUser(fsStoreCouponUser));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeCouponUser:edit')")
+    @Log(title = "优惠券发放记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreCouponUserScrm fsStoreCouponUser)
+    {
+        return toAjax(fsStoreCouponUserService.updateFsStoreCouponUser(fsStoreCouponUser));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeCouponUser:remove')")
+    @Log(title = "优惠券发放记录", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsStoreCouponUserService.deleteFsStoreCouponUserByIds(ids));
+    }
+}

+ 96 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreOrderAuditScrmBridgeController.java

@@ -0,0 +1,96 @@
+package com.fs.admin.controller.store;
+
+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;
+import com.fs.hisStore.domain.FsStoreOrderAuditScrm;
+import com.fs.hisStore.param.FsStoreOrderAuditParam;
+import com.fs.hisStore.param.FsStoreOrderAuditReviewParam;
+import com.fs.hisStore.service.IFsStoreOrderAuditScrmService;
+import com.fs.hisStore.vo.FsStoreOrderAuditVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+ * 订单审核 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsStoreOrderAuditScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2023-06-15
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/storeOrderAudit")
+public class FsStoreOrderAuditScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsStoreOrderAuditScrmService fsStoreOrderAuditService;
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderAudit:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreOrderAuditParam param)
+    {
+        startPage();
+        List<FsStoreOrderAuditVO> list = fsStoreOrderAuditService.selectStoreOrderAuditVOList(param);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderAudit:export')")
+    @Log(title = "订单审核", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreOrderAuditParam param)
+    {
+        List<FsStoreOrderAuditVO> list = fsStoreOrderAuditService.selectStoreOrderAuditVOList(param);
+        ExcelUtil<FsStoreOrderAuditVO> util = new ExcelUtil<FsStoreOrderAuditVO>(FsStoreOrderAuditVO.class);
+        return util.exportExcel(list, "订单审核数据");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderAudit:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsStoreOrderAuditService.getById(id));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderAudit:add')")
+    @Log(title = "订单审核", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreOrderAuditScrm fsStoreOrderAudit)
+    {
+        return toAjax(fsStoreOrderAuditService.save(fsStoreOrderAudit));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderAudit:edit')")
+    @Log(title = "订单审核", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreOrderAuditScrm fsStoreOrderAudit)
+    {
+        return toAjax(fsStoreOrderAuditService.updateById(fsStoreOrderAudit));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderAudit:audit')")
+    @Log(title = "订单审核", businessType = BusinessType.UPDATE)
+    @PostMapping("/audit")
+    public AjaxResult audit(@Valid @RequestBody FsStoreOrderAuditReviewParam param)
+    {
+        fsStoreOrderAuditService.audit(param, null, true);
+        return AjaxResult.success();
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderAudit:remove')")
+    @Log(title = "订单审核", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsStoreOrderAuditService.removeByIds(java.util.Arrays.asList(ids)));
+    }
+}

+ 90 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreOrderItemScrmBridgeController.java

@@ -0,0 +1,90 @@
+package com.fs.admin.controller.store;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.domain.FsStoreOrderItemScrm;
+import com.fs.hisStore.service.IFsStoreOrderItemScrmService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 订单详情 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsStoreOrderItemScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2022-03-21
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/storeOrderItem")
+public class FsStoreOrderItemScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsStoreOrderItemScrmService fsStoreOrderItemService;
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderItem:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreOrderItemScrm fsStoreOrderItem)
+    {
+        startPage();
+        List<FsStoreOrderItemScrm> list = fsStoreOrderItemService.selectFsStoreOrderItemList(fsStoreOrderItem);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderItem:export')")
+    @Log(title = "订单详情", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreOrderItemScrm fsStoreOrderItem)
+    {
+        List<FsStoreOrderItemScrm> list = fsStoreOrderItemService.selectFsStoreOrderItemList(fsStoreOrderItem);
+        ExcelUtil<FsStoreOrderItemScrm> util = new ExcelUtil<FsStoreOrderItemScrm>(FsStoreOrderItemScrm.class);
+        return util.exportExcel(list, "storeOrderItem");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderItem:query')")
+    @GetMapping(value = "/{itemId}")
+    public AjaxResult getInfo(@PathVariable("itemId") Long itemId)
+    {
+        return AjaxResult.success(fsStoreOrderItemService.selectFsStoreOrderItemById(itemId));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderItem:add')")
+    @Log(title = "订单详情", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreOrderItemScrm fsStoreOrderItem)
+    {
+        return toAjax(fsStoreOrderItemService.insertFsStoreOrderItem(fsStoreOrderItem));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderItem:edit')")
+    @Log(title = "订单详情", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreOrderItemScrm fsStoreOrderItem)
+    {
+        return toAjax(fsStoreOrderItemService.updateFsStoreOrderItem(fsStoreOrderItem));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderItem:remove')")
+    @Log(title = "订单详情", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{itemIds}")
+    public AjaxResult remove(@PathVariable Long[] itemIds)
+    {
+        return toAjax(fsStoreOrderItemService.deleteFsStoreOrderItemByIds(itemIds));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderItem:updateNum')")
+    @Log(title = "订单详情修改订单数量", businessType = BusinessType.UPDATE)
+    @PutMapping("/updateNum")
+    public AjaxResult updateNum(@RequestBody FsStoreOrderItemScrm fsStoreOrderItem)
+    {
+        return toAjax(fsStoreOrderItemService.updateFsStoreOrderItemNum(fsStoreOrderItem));
+    }
+}

+ 82 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreOrderNoticeScrmBridgeController.java

@@ -0,0 +1,82 @@
+package com.fs.admin.controller.store;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.domain.FsStoreOrderNoticeScrm;
+import com.fs.hisStore.service.IFsStoreOrderNoticeScrmService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 订单通知用户 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsStoreOrderNoticeScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2022-03-15
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/storeOrderNotice")
+public class FsStoreOrderNoticeScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsStoreOrderNoticeScrmService fsStoreOrderNoticeService;
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderNotice:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreOrderNoticeScrm fsStoreOrderNotice)
+    {
+        startPage();
+        List<FsStoreOrderNoticeScrm> list = fsStoreOrderNoticeService.selectFsStoreOrderNoticeList(fsStoreOrderNotice);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderNotice:export')")
+    @Log(title = "订单通知用户", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreOrderNoticeScrm fsStoreOrderNotice)
+    {
+        List<FsStoreOrderNoticeScrm> list = fsStoreOrderNoticeService.selectFsStoreOrderNoticeList(fsStoreOrderNotice);
+        ExcelUtil<FsStoreOrderNoticeScrm> util = new ExcelUtil<FsStoreOrderNoticeScrm>(FsStoreOrderNoticeScrm.class);
+        return util.exportExcel(list, "storeOrderNotice");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderNotice:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsStoreOrderNoticeService.selectFsStoreOrderNoticeById(id));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderNotice:add')")
+    @Log(title = "订单通知用户", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreOrderNoticeScrm fsStoreOrderNotice)
+    {
+        return toAjax(fsStoreOrderNoticeService.insertFsStoreOrderNotice(fsStoreOrderNotice));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderNotice:edit')")
+    @Log(title = "订单通知用户", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreOrderNoticeScrm fsStoreOrderNotice)
+    {
+        return toAjax(fsStoreOrderNoticeService.updateFsStoreOrderNotice(fsStoreOrderNotice));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderNotice:remove')")
+    @Log(title = "订单通知用户", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsStoreOrderNoticeService.deleteFsStoreOrderNoticeByIds(ids));
+    }
+}

+ 134 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreOrderOfflineScrmBridgeController.java

@@ -0,0 +1,134 @@
+package com.fs.admin.controller.store;
+
+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.ParseUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.crm.domain.CrmCustomer;
+import com.fs.crm.service.ICrmCustomerService;
+import com.fs.framework.security.SecurityUtils;
+import com.fs.framework.security.LoginUser;
+import com.fs.hisStore.domain.FsStoreOrderOfflineScrm;
+import com.fs.hisStore.domain.FsStoreOrderOfflineItemScrm;
+import com.fs.hisStore.param.FsStoreOrderOfflineParam;
+import com.fs.hisStore.service.IFsStoreOrderOfflineItemScrmService;
+import com.fs.hisStore.service.IFsStoreOrderOfflineScrmService;
+import com.fs.hisStore.vo.FsStoreOrderOfflineListVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 线下订单 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsStoreOrderOfflineScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2024-10-29
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/storeOrderOffline")
+public class FsStoreOrderOfflineScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsStoreOrderOfflineScrmService fsStoreOrderOfflineService;
+    @Autowired
+    private IFsStoreOrderOfflineItemScrmService fsStoreOrderOfflineItemService;
+    @Autowired
+    private ICrmCustomerService crmCustomerService;
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderOffline:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreOrderOfflineParam param)
+    {
+        startPage();
+        List<FsStoreOrderOfflineListVO> list = fsStoreOrderOfflineService.selectFsStoreOrderOfflineListVO(param);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderOffline:export')")
+    @Log(title = "线下订单", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreOrderOfflineScrm fsStoreOrderOffline)
+    {
+        List<FsStoreOrderOfflineScrm> list = fsStoreOrderOfflineService.selectFsStoreOrderOfflineList(fsStoreOrderOffline);
+        ExcelUtil<FsStoreOrderOfflineScrm> util = new ExcelUtil<FsStoreOrderOfflineScrm>(FsStoreOrderOfflineScrm.class);
+        return util.exportExcel(list, "storeOrderOffline");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderOffline:query')")
+    @GetMapping(value = "/{orderId}")
+    public R getInfo(@PathVariable("orderId") Long orderId)
+    {
+        FsStoreOrderOfflineListVO order = fsStoreOrderOfflineService.selectFsStoreOrderOfflineVOById(orderId);
+        order.setMobile(ParseUtils.parsePhone(order.getMobile()));
+        CrmCustomer customer = null;
+        if(order.getCrmId()!=null && order.getCrmId()>0){
+            customer = crmCustomerService.selectCrmCustomerById(order.getCrmId());
+            customer.setMobile(ParseUtils.parsePhone(customer.getMobile()));
+        }
+        FsStoreOrderOfflineItemScrm itemMap = new FsStoreOrderOfflineItemScrm();
+        itemMap.setOrderId(order.getOrderId());
+        List<FsStoreOrderOfflineItemScrm> items = fsStoreOrderOfflineItemService.selectFsStoreOrderOfflineItemList(itemMap);
+        return R.ok().put("order", order).put("items", items).put("customer", customer);
+    }
+
+    @GetMapping(value = "/queryPhone/{orderId}")
+    @Log(title = "查看电话", businessType = BusinessType.GRANT)
+    @PreAuthorize("@ss.hasPermi('store:storeOrderOffline:queryPhone')")
+    public R getPhone(@PathVariable("orderId") Long orderId)
+    {
+        FsStoreOrderOfflineListVO order = fsStoreOrderOfflineService.selectFsStoreOrderOfflineVOById(orderId);
+        String userPhone = order.getMobile();
+        return R.ok().put("userPhone", userPhone);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderOffline:add')")
+    @Log(title = "线下订单", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreOrderOfflineScrm fsStoreOrderOffline)
+    {
+        return toAjax(fsStoreOrderOfflineService.insertFsStoreOrderOffline(fsStoreOrderOffline));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderOffline:edit')")
+    @Log(title = "线下订单", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreOrderOfflineScrm fsStoreOrderOffline)
+    {
+        return toAjax(fsStoreOrderOfflineService.updateFsStoreOrderOffline(fsStoreOrderOffline));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderOffline:remove')")
+    @Log(title = "线下订单", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{orderIds}")
+    public AjaxResult remove(@PathVariable Long[] orderIds)
+    {
+        return toAjax(fsStoreOrderOfflineService.deleteFsStoreOrderOfflineByIds(orderIds));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderOffline:audit')")
+    @Log(title = "审核订单", businessType = BusinessType.UPDATE)
+    @PostMapping("/auditOrder/{orderId}")
+    public R auditOrder(@PathVariable Long orderId)
+    {
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        FsStoreOrderOfflineScrm fsStoreOrderOffline = new FsStoreOrderOfflineScrm();
+        fsStoreOrderOffline.setOrderId(orderId);
+        fsStoreOrderOffline.setStatus(3L);
+        fsStoreOrderOffline.setAuditBy(loginUser.getCompanyUser().getNickName());
+        fsStoreOrderOffline.setAuditTime(new Date());
+        if (fsStoreOrderOfflineService.updateFsStoreOrderOffline(fsStoreOrderOffline) > 0){
+            return R.ok();
+        }
+        return R.error("审核失败");
+    }
+}

+ 141 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreOrderPromotionScrmBridgeController.java

@@ -0,0 +1,141 @@
+package com.fs.admin.controller.store;
+
+import com.alibaba.fastjson.JSONObject;
+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;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.dto.StoreOrderProductDTO;
+import com.fs.hisStore.param.FsStoreOrderParam;
+import com.fs.hisStore.service.IFsStoreOrderItemScrmService;
+import com.fs.hisStore.service.IFsStoreOrderScrmService;
+import com.fs.hisStore.vo.FsPromotionOrderVO;
+import com.fs.hisStore.vo.FsStoreOrderItemExportVO;
+import com.fs.hisStore.vo.FsStoreOrderPromotionExportVO;
+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 org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 推广订单 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsStoreOrderPromotionScrmController,供平台总后台跨租户管理
+ * 注意:使用 /PromotionOrder 路径避免与 FsStoreOrderScrmBridgeController 的 /storeOrder 路径冲突
+ *
+ * @author fs
+ * @date 2022-03-15
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/PromotionOrder")
+public class FsStoreOrderPromotionScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsStoreOrderScrmService fsStoreOrderService;
+    @Autowired
+    private IFsStoreOrderItemScrmService orderItemService;
+
+    @PreAuthorize("@ss.hasPermi('store:PromotionOrder:list')")
+    @GetMapping("/promotionList")
+    public TableDataInfo list(FsStoreOrderParam param)
+    {
+        startPage();
+        if(!StringUtils.isEmpty(param.getCreateTimeRange())){
+            param.setCreateTimeList(param.getCreateTimeRange().split("--"));
+        }
+        if(!StringUtils.isEmpty(param.getPayTimeRange())){
+            param.setPayTimeList(param.getPayTimeRange().split("--"));
+        }
+        if(!StringUtils.isEmpty(param.getDeliveryImportTimeRange())){
+            param.setDeliveryImportTimeList(param.getDeliveryImportTimeRange().split("--"));
+        }
+        if(!StringUtils.isEmpty(param.getDeliverySendTimeRange())){
+            param.setDeliverySendTimeList(param.getDeliverySendTimeRange().split("--"));
+        }
+        List<FsPromotionOrderVO> list = fsStoreOrderService.selectFsPromotionOrderListVO(param);
+        if (list != null) {
+            for (FsPromotionOrderVO vo : list) {
+                if(vo.getPhone()!=null){
+                    vo.setPhone(vo.getPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+                    vo.setUserPhone(vo.getUserPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+                }
+            }
+        }
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:promotionOrder:export')")
+    @Log(title = "导出推广订单", businessType = BusinessType.EXPORT)
+    @GetMapping("/promotionExport")
+    public AjaxResult export(FsStoreOrderParam param)
+    {
+        if (param == null){
+            return AjaxResult.error("请筛选数据导出");
+        }
+        if(!StringUtils.isEmpty(param.getCreateTimeRange())){
+            param.setCreateTimeList(param.getCreateTimeRange().split("--"));
+        }
+        if(!StringUtils.isEmpty(param.getPayTimeRange())){
+            param.setPayTimeList(param.getPayTimeRange().split("--"));
+        }
+        if(!StringUtils.isEmpty(param.getDeliveryImportTimeRange())){
+            param.setDeliveryImportTimeList(param.getDeliveryImportTimeRange().split("--"));
+        }
+        List<FsStoreOrderPromotionExportVO> list = fsStoreOrderService.selectFsPromotionOrderListVOByExport(param);
+        if (list != null) {
+            for (FsStoreOrderPromotionExportVO vo : list) {
+                if (vo.getPhone() != null) {
+                    vo.setPhone(vo.getPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+                }
+                if (vo.getUserPhone() != null) {
+                    vo.setUserPhone(vo.getUserPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+                }
+            }
+        }
+        ExcelUtil<FsStoreOrderPromotionExportVO> util = new ExcelUtil<FsStoreOrderPromotionExportVO>(FsStoreOrderPromotionExportVO.class);
+        return util.exportExcel(list, "订单数据");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:promotionOrder:exportItems')")
+    @Log(title = "推广订单明细导出", businessType = BusinessType.EXPORT)
+    @GetMapping("/promotionExportItems")
+    public AjaxResult exportItems(FsStoreOrderParam param)
+    {
+        if(!StringUtils.isEmpty(param.getCreateTimeRange())){
+            param.setCreateTimeList(param.getCreateTimeRange().split("--"));
+        }
+        if(!StringUtils.isEmpty(param.getPayTimeRange())){
+            param.setPayTimeList(param.getPayTimeRange().split("--"));
+        }
+        if(!StringUtils.isEmpty(param.getDeliveryImportTimeRange())){
+            param.setDeliveryImportTimeList(param.getDeliveryImportTimeRange().split("--"));
+        }
+        param.setIsHealth("1");
+        List<FsStoreOrderItemExportVO> list = orderItemService.selectFsStoreOrderItemListExportVO(param);
+        if (list != null) {
+            for (FsStoreOrderItemExportVO vo : list) {
+                if (vo.getUserPhone() != null) {
+                    String phone = vo.getUserPhone().replaceAll("(\\d{3})\\d*(\\d{1})", "$1****$2");
+                    vo.setUserPhone(phone);
+                }
+                if (!StringUtils.isEmpty(vo.getJsonInfo())) {
+                    try {
+                        StoreOrderProductDTO orderProductDTO = JSONObject.parseObject(vo.getJsonInfo(), StoreOrderProductDTO.class);
+                        BeanUtil.copyProperties(orderProductDTO, vo);
+                    } catch (Exception e) {
+                    }
+                }
+            }
+        }
+        ExcelUtil<FsStoreOrderItemExportVO> util = new ExcelUtil<FsStoreOrderItemExportVO>(FsStoreOrderItemExportVO.class);
+        return util.exportExcel(list, "订单明细数据");
+    }
+}

+ 1062 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreOrderScrmBridgeController.java

@@ -0,0 +1,1062 @@
+package com.fs.admin.controller.store;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson.JSON;
+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.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;
+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.company.param.CompanyStoreOrderMoneyLogsListParam;
+import com.fs.company.service.ICompanyMoneyLogsService;
+import com.fs.company.vo.CompanyStoreOrderMoneyLogsVO;
+import com.fs.config.cloud.CloudHostProper;
+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.dto.df.DFConfigVo;
+import com.fs.erp.service.IErpOrderService;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.security.SecurityUtils;
+import com.fs.framework.service.TokenService;
+import com.fs.his.domain.FsDfAccount;
+import com.fs.his.domain.FsStoreOrderDf;
+import com.fs.his.domain.FsUser;
+import com.fs.his.enums.FsStoreOrderLogEnum;
+import com.fs.his.param.FsStoreOrderSetErpPhoneParam;
+import com.fs.his.service.IFsDfAccountService;
+import com.fs.his.service.IFsExpressService;
+import com.fs.his.service.IFsStoreOrderDfService;
+import com.fs.his.service.IFsUserService;
+import com.fs.his.utils.ConfigUtil;
+import com.fs.his.vo.FsStoreOrderListAndStatisticsVo;
+import com.fs.his.vo.FsStoreOrderListVO;
+import com.fs.hisStore.config.FsErpConfig;
+import com.fs.hisStore.domain.FsStoreOrderItemScrm;
+import com.fs.hisStore.domain.FsStoreOrderScrm;
+import com.fs.hisStore.domain.FsStoreOrderStatusScrm;
+import com.fs.hisStore.domain.FsStorePaymentScrm;
+import com.fs.his.dto.ExpressInfoDTO;
+import com.fs.hisStore.dto.StoreOrderExpressExportDTO;
+import com.fs.hisStore.dto.StoreOrderProductDTO;
+import com.fs.hisStore.enums.ShipperCodeEnum;
+import com.fs.hisStore.param.*;
+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.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.context.annotation.Profile;
+
+import javax.servlet.http.HttpServletRequest;
+import java.math.BigDecimal;
+import java.text.ParseException;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 订单 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsStoreOrderScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2022-03-15
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/storeOrder")
+public class FsStoreOrderScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsExpressService expressService;
+    @Autowired
+    private IFsStoreOrderScrmService fsStoreOrderService;
+    @Autowired
+    private IFsStoreOrderItemScrmService orderItemService;
+    @Autowired
+    private IFsUserService userService;
+    @Autowired
+    private IFsStoreOrderStatusScrmService orderStatusService;
+    @Autowired
+    IFsStorePaymentScrmService paymentService;
+    @Autowired
+    private CloudHostProper cloudHostProper;
+    @Autowired
+    private ICompanyMoneyLogsService moneyLogsService;
+    @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;
+    @Autowired
+    private IFsStoreOrderAuditLogScrmService orderAuditLogService;
+    @Autowired
+    private TokenService tokenService;
+    @Autowired
+    SysConfigMapper sysConfigMapper;
+    @Autowired
+    private IFsDfAccountService fsDfAccountService;
+    @Autowired
+    private IFsStoreOrderDfService fsStoreOrderDfService;
+    @Autowired
+    private IFsStoreOrderLogsScrmService fsStoreOrderLogsService;
+    @Autowired
+    private ISysRoleService sysRoleService;
+
+    private IErpOrderService getErpService()
+    {
+        IErpOrderService erpOrderService = null;
+        FsErpConfig erpConfig = configUtil.getErpConfig();
+        Integer erpOpen = erpConfig.getErpOpen();
+        if (erpOpen != null && erpOpen == 1) {
+            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;
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:list')")
+    @PostMapping("/list")
+    public TableDataInfo list(@RequestBody FsStoreOrderParam param)
+    {
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        if (!StringUtils.isEmpty(param.getCreateTimeRange())) {
+            param.setCreateTimeList(param.getCreateTimeRange().split("--"));
+        }
+        if (!StringUtils.isEmpty(param.getPayTimeRange())) {
+            param.setPayTimeList(param.getPayTimeRange().split("--"));
+        }
+        if (!StringUtils.isEmpty(param.getDeliveryImportTimeRange())) {
+            param.setDeliveryImportTimeList(param.getDeliveryImportTimeRange().split("--"));
+        }
+        if (!StringUtils.isEmpty(param.getDeliverySendTimeRange())) {
+            param.setDeliverySendTimeList(param.getDeliverySendTimeRange().split("--"));
+        }
+        param.setNotHealth(1);
+        List<FsStoreOrderVO> list = fsStoreOrderService.selectFsStoreOrderListVO(param);
+        TableDataInfo dataTable = getDataTable(list);
+        if (CloudHostUtils.hasCloudHostName("康年堂")) {
+            dataTable.setMsg("knt");
+        }
+        if (list != null) {
+            LoginUser loginUser = (LoginUser) tokenService.getLoginUser(ServletUtils.getRequest());
+            for (FsStoreOrderVO vo : list) {
+                if (vo.getPhone() != null) {
+                    vo.setPhone(vo.getPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+                }
+                if (ObjectUtil.isNotEmpty(vo.getUserPhone())) {
+                    vo.setUserPhone(vo.getUserPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+                }
+                if (CloudHostUtils.hasCloudHostName("康年堂")) {
+                    FsStoreOrderDf df = fsStoreOrderDfService.selectFsStoreOrderDfByOrderId(vo.getId());
+                    if (df != null) {
+                        vo.setErpAccount(df.getLoginAccount());
+                    }
+                }
+                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);
+                    vo.setFPrice(BigDecimal.ZERO);
+                    vo.setPayDelivery(BigDecimal.ZERO);
+                    vo.setBarCode("");
+                    vo.setCateName("");
+                    vo.setBankTransactionId("");
+                }
+            }
+        }
+        FsStoreOrderListAndStatisticsVo vo = new FsStoreOrderListAndStatisticsVo();
+        BeanUtils.copyProperties(dataTable, vo);
+        if (dataTable.getTotal() > 0) {
+            Map<String, BigDecimal> statistics = fsStoreOrderService.selectFsStoreOrderStatistics(param);
+            if (statistics != null && statistics.size() >= 3) {
+                vo.setPayPriceTotal(statistics.get("pay_price").toString());
+                vo.setPayMoneyTotal(statistics.get("pay_money").toString());
+                vo.setPayRemainTotal(statistics.get("pay_remain").toString());
+            } else {
+                vo.setPayPriceTotal("0");
+                vo.setPayMoneyTotal("0");
+                vo.setPayRemainTotal("0");
+            }
+            String productStatistics = fsStoreOrderService.selectFsStoreOrderProductStatistics(param);
+            if (StringUtils.isNotBlank(productStatistics)) {
+                vo.setProductInfo(productStatistics);
+            } else {
+                vo.setProductInfo("");
+            }
+        }
+        return vo;
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:payRemainList')")
+    @GetMapping("/payRemainList")
+    public TableDataInfo payRemainList(FsStoreOrderParam param)
+    {
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        if (!StringUtils.isEmpty(param.getCreateTimeRange())) {
+            param.setCreateTimeList(param.getCreateTimeRange().split("--"));
+        }
+        if (!StringUtils.isEmpty(param.getPayTimeRange())) {
+            param.setPayTimeList(param.getPayTimeRange().split("--"));
+        }
+        if (!StringUtils.isEmpty(param.getDeliveryImportTimeRange())) {
+            param.setDeliveryImportTimeList(param.getDeliveryImportTimeRange().split("--"));
+        }
+        List<FsStoreOrderVO> list = fsStoreOrderService.selectPayRemainListVO(param);
+        if (list != null) {
+            for (FsStoreOrderVO vo : list) {
+                if (vo.getPhone() != null) {
+                    vo.setPhone(vo.getPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+                    vo.setUserPhone(vo.getUserPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+                }
+            }
+        }
+        return getDataTable(list);
+    }
+
+    @Log(title = "付尾款订单", businessType = BusinessType.EXPORT)
+    @GetMapping("/payRemainExport")
+    public AjaxResult payRemainExport(FsStoreOrderParam param)
+    {
+        if (!StringUtils.isEmpty(param.getCreateTimeRange())) {
+            param.setCreateTimeList(param.getCreateTimeRange().split("--"));
+        }
+        if (!StringUtils.isEmpty(param.getPayTimeRange())) {
+            param.setPayTimeList(param.getPayTimeRange().split("--"));
+        }
+        if (!StringUtils.isEmpty(param.getDeliveryImportTimeRange())) {
+            param.setDeliveryImportTimeList(param.getDeliveryImportTimeRange().split("--"));
+        }
+        List<FsStorePayRemainOrderExportVO> list = fsStoreOrderService.selectFsStorePayRemainOrderListVOByExport(param);
+        if (list != null) {
+            for (FsStorePayRemainOrderExportVO vo : list) {
+                if (vo.getPhone() != null) {
+                    vo.setPhone(vo.getPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+                }
+                if (vo.getUserPhone() != null) {
+                    vo.setUserPhone(vo.getUserPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+                }
+                if (vo.getUserAddress() != null) {
+                    vo.setUserAddress(ParseUtils.parseAddress(vo.getUserAddress()));
+                }
+            }
+        }
+        ExcelUtil<FsStorePayRemainOrderExportVO> util = new ExcelUtil<FsStorePayRemainOrderExportVO>(FsStorePayRemainOrderExportVO.class);
+        return util.exportExcel(list, "付尾款订单数据");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:export')")
+    @Log(title = "订单", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public AjaxResult export(@RequestBody FsStoreOrderParam param)
+    {
+        if ("".equals(param.getBeginTime()) && "".equals(param.getEndTime())) {
+            param.setBeginTime(null);
+            param.setEndTime(null);
+        }
+        if (fsStoreOrderService.isEntityNull(param)) {
+            return AjaxResult.error("请筛选数据导出");
+        }
+        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.setNotHealth(1);
+        List<FsStoreOrderErpExportVO> list = fsStoreOrderService.selectFsStoreOrderListVOByExport(param);
+        if (list != null) {
+            SysRole sysRole = isCheckPermission();
+            for (FsStoreOrderErpExportVO vo : list) {
+                if (vo.getPhone() != null && sysRole.getIsCheckPhone() != 1) {
+                    vo.setPhone(vo.getPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+                }
+                if (vo.getUserPhone() != null && sysRole.getIsCheckPhone() != 1) {
+                    vo.setUserPhone(vo.getUserPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+                }
+                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();
+        ArrayList<String> filterList = new ArrayList<>();
+        if (StringUtils.isNotBlank(filter)) {
+            String[] filterArr = filter.split("\\s*,\\s*");
+            filterList.addAll(Arrays.asList(filterArr));
+        }
+        ExcelUtil<FsStoreOrderErpExportVO> util = new ExcelUtil<FsStoreOrderErpExportVO>(FsStoreOrderErpExportVO.class);
+        if (filter != null && !filter.isEmpty()) {
+            return util.exportExcelSelectedColumns(list, "订单数据", filterList);
+        } else {
+            return util.exportExcel(list, "订单数据");
+        }
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:export:details')")
+    @Log(title = "订单", businessType = BusinessType.EXPORT)
+    @PostMapping("/exportDetails")
+    public AjaxResult exportDetails(@RequestBody FsStoreOrderParam param)
+    {
+        if ("".equals(param.getBeginTime()) && "".equals(param.getEndTime())) {
+            param.setBeginTime(null);
+            param.setEndTime(null);
+        }
+        if (fsStoreOrderService.isEntityNull(param)) {
+            return AjaxResult.error("请筛选数据导出");
+        }
+        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.setNotHealth(1);
+        List<FsStoreOrderErpExportVO> list = fsStoreOrderService.selectFsStoreOrderListVOByExport(param);
+        String filter = param.getFilter();
+        ArrayList<String> filterList = new ArrayList<>();
+        if (StringUtils.isNotBlank(filter)) {
+            String[] filterArr = filter.split("\\s*,\\s*");
+            filterList.addAll(Arrays.asList(filterArr));
+        }
+        ExcelUtil<FsStoreOrderErpExportVO> util = new ExcelUtil<FsStoreOrderErpExportVO>(FsStoreOrderErpExportVO.class);
+        if (filter != null && !filter.isEmpty()) {
+            return util.exportExcelSelectedColumns(list, "订单数据", filterList);
+        } else {
+            return util.exportExcel(list, "订单数据");
+        }
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:exportItems')")
+    @Log(title = "订单明细导出", businessType = BusinessType.EXPORT)
+    @GetMapping("/exportItems")
+    public AjaxResult exportItems(FsStoreOrderParam param)
+    {
+        if ("".equals(param.getBeginTime()) && "".equals(param.getEndTime())) {
+            param.setBeginTime(null);
+            param.setEndTime(null);
+        }
+        if (fsStoreOrderService.isEntityNull(param)) {
+            return AjaxResult.error("请筛选数据导出");
+        }
+        if (!StringUtils.isEmpty(param.getCreateTimeRange())) {
+            param.setCreateTimeList(param.getCreateTimeRange().split("--"));
+        }
+        if (!StringUtils.isEmpty(param.getPayTimeRange())) {
+            param.setPayTimeList(param.getPayTimeRange().split("--"));
+        }
+        if (!StringUtils.isEmpty(param.getDeliveryImportTimeRange())) {
+            param.setDeliveryImportTimeList(param.getDeliveryImportTimeRange().split("--"));
+        }
+        if (!StringUtils.isEmpty(param.getDeliverySendTimeRange())) {
+            param.setDeliverySendTimeList(param.getDeliverySendTimeRange().split("--"));
+        }
+        param.setNotHealth(1);
+        List<FsStoreOrderItemExportVO> list = orderItemService.selectFsStoreOrderItemListExportVO(param);
+        if (list != null) {
+            SysRole sysRole = isCheckPermission();
+            LoginUser loginUser = (LoginUser) tokenService.getLoginUser(ServletUtils.getRequest());
+            for (FsStoreOrderItemExportVO vo : list) {
+                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 && sysRole.getIsCheckAddress() != 1) {
+                    vo.setUserAddress(ParseUtils.parseAddress(vo.getUserAddress()));
+                }
+                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<FsStoreOrderItemExportVO> util = new ExcelUtil<FsStoreOrderItemExportVO>(FsStoreOrderItemExportVO.class);
+        return util.exportExcel(list, "订单明细数据");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:exportItems:details')")
+    @Log(title = "订单明细导出", businessType = BusinessType.EXPORT)
+    @GetMapping("/exportItemsDetails")
+    public AjaxResult exportItemsDetails(FsStoreOrderParam param)
+    {
+        if ("".equals(param.getBeginTime()) && "".equals(param.getEndTime())) {
+            param.setBeginTime(null);
+            param.setEndTime(null);
+        }
+        if (fsStoreOrderService.isEntityNull(param)) {
+            return AjaxResult.error("请筛选数据导出");
+        }
+        if (!StringUtils.isEmpty(param.getCreateTimeRange())) {
+            param.setCreateTimeList(param.getCreateTimeRange().split("--"));
+        }
+        if (!StringUtils.isEmpty(param.getPayTimeRange())) {
+            param.setPayTimeList(param.getPayTimeRange().split("--"));
+        }
+        if (!StringUtils.isEmpty(param.getDeliveryImportTimeRange())) {
+            param.setDeliveryImportTimeList(param.getDeliveryImportTimeRange().split("--"));
+        }
+        if (!StringUtils.isEmpty(param.getDeliverySendTimeRange())) {
+            param.setDeliverySendTimeList(param.getDeliverySendTimeRange().split("--"));
+        }
+        param.setNotHealth(1);
+        List<FsStoreOrderItemExportVO> list = orderItemService.selectFsStoreOrderItemListExportVO(param);
+        if ("北京卓美".equals(com.fs.config.saas.ProjectConfig.getFromDB(sysConfigMapper).getCloudHost().getCompanyName())) {
+            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 = (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 = (LoginUser) tokenService.getLoginUser(ServletUtils.getRequest());
+            for (FsStoreOrderItemExportVO vo : list) {
+                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<FsStoreOrderItemExportVO> util = new ExcelUtil<FsStoreOrderItemExportVO>(FsStoreOrderItemExportVO.class);
+        return util.exportExcel(list, "订单明细数据");
+    }
+
+    @GetMapping("/orderItemsNum")
+    public R orderItemsNum(FsStoreOrderParam param)
+    {
+        if (!StringUtils.isEmpty(param.getCreateTimeRange())) {
+            param.setCreateTimeList(param.getCreateTimeRange().split("--"));
+        }
+        if (!StringUtils.isEmpty(param.getPayTimeRange())) {
+            param.setPayTimeList(param.getPayTimeRange().split("--"));
+        }
+        if (!StringUtils.isEmpty(param.getDeliveryImportTimeRange())) {
+            param.setDeliveryImportTimeList(param.getDeliveryImportTimeRange().split("--"));
+        }
+        if (!StringUtils.isEmpty(param.getDeliverySendTimeRange())) {
+            param.setDeliverySendTimeList(param.getDeliverySendTimeRange().split("--"));
+        }
+        param.setNotHealth(1);
+        Long orderItemsNum = orderItemService.countFsStoreOrderItemListExportVO(param);
+        return R.ok().put("orderItemsNum", "订单明细数目:" + orderItemsNum);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:query')")
+    @GetMapping(value = "/{id}")
+    public R getInfo(@PathVariable("id") Long id)
+    {
+        FsStoreOrderScrm order = fsStoreOrderService.selectFsStoreOrderById(id);
+        order.setUserPhone(ParseUtils.parsePhone(order.getUserPhone()));
+        order.setUserAddress(ParseUtils.parseAddress(order.getUserAddress()));
+        FsUser user = userService.selectFsUserById(order.getUserId());
+        if (user != null) {
+            user.setPhone(ParseUtils.parsePhone(user.getPhone()));
+        }
+        FsStoreOrderItemScrm itemMap = new FsStoreOrderItemScrm();
+        itemMap.setOrderId(order.getId());
+        List<FsStoreOrderItemScrm> items = orderItemService.selectFsStoreOrderItemList(itemMap);
+        FsStoreOrderStatusScrm statusMap = new FsStoreOrderStatusScrm();
+        statusMap.setOrderId(order.getId());
+        List<FsStoreOrderStatusScrm> logs = orderStatusService.selectFsStoreOrderStatusList(statusMap);
+        List<FsStorePaymentScrm> payments = paymentService.selectFsStorePaymentByOrderId(order.getId());
+        List<CompanyStoreOrderMoneyLogsVO> tuiMoneyLogs = new ArrayList<>();
+        if (order.getCompanyId() != null) {
+            CompanyStoreOrderMoneyLogsListParam moneyLogsMap = new CompanyStoreOrderMoneyLogsListParam();
+            moneyLogsMap.setCompanyId(order.getCompanyId());
+            moneyLogsMap.setBusinessId(order.getId().toString());
+            tuiMoneyLogs = moneyLogsService.selectCompanyStoreOrderMoneyLogsList(moneyLogsMap);
+        }
+        List<FsStoreOrderAuditLogVO> auditLogs = orderAuditLogService.selectStoreOrderAuditLogVOByOrderId(order.getId());
+        return R.ok().put("order", order).put("items", items).put("logs", logs).put("user", user).put("payments", payments).put("tuiMoneyLogs", tuiMoneyLogs)
+                .put("auditLogs", auditLogs);
+    }
+
+    @GetMapping(value = "/queryAddress/{id}")
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:queryAddress')")
+    public R getAddress(@PathVariable("id") Long id)
+    {
+        FsStoreOrderScrm order = fsStoreOrderService.selectFsStoreOrderById(id);
+        String address = order.getUserAddress();
+        return R.ok().put("address", address);
+    }
+
+    @GetMapping(value = "/queryPhone/{id}")
+    @Log(title = "查看电话", businessType = BusinessType.GRANT)
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:queryPhone')")
+    public R getPhone(@PathVariable("id") Long id)
+    {
+        FsStoreOrderScrm order = fsStoreOrderService.selectFsStoreOrderById(id);
+        String userPhone = order.getUserPhone();
+        return R.ok().put("userPhone", userPhone);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:express')")
+    @GetMapping(value = "/getExpress/{id}")
+    public R getExpress(@PathVariable("id") Long id)
+    {
+        FsStoreOrderScrm order = fsStoreOrderService.selectFsStoreOrderById(id);
+        ExpressInfoDTO expressInfoDTO = null;
+        if (StringUtils.isNotEmpty(order.getDeliveryId())) {
+            String lastFourNumber = "";
+            if (order.getDeliverySn().equals(ShipperCodeEnum.SF.getValue()) || order.getDeliverySn().equals(ShipperCodeEnum.ZTO.getValue())) {
+                lastFourNumber = order.getUserPhone();
+                if (lastFourNumber.length() == 11) {
+                    lastFourNumber = StrUtil.sub(lastFourNumber, lastFourNumber.length(), -4);
+                }
+            }
+            expressInfoDTO = expressService.getExpressInfo(order.getOrderCode(), order.getDeliverySn(), order.getDeliveryId(), lastFourNumber);
+        }
+        return R.ok().put("data", expressInfoDTO);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:add')")
+    @Log(title = "订单", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreOrderScrm fsStoreOrder)
+    {
+        return toAjax(fsStoreOrderService.insertFsStoreOrder(fsStoreOrder));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:edit')")
+    @Log(title = "订单", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreOrderScrm fsStoreOrder)
+    {
+        return toAjax(fsStoreOrderService.updateFsStoreOrder(fsStoreOrder));
+    }
+
+    @Log(title = "修改订单itemJson", businessType = BusinessType.UPDATE)
+    @GetMapping("/updateStoreOrderItemJson/{orderId}/{backendEditProductType}")
+    public AjaxResult updateStoreOrderItemJson(@PathVariable("orderId") Long orderId, @PathVariable("backendEditProductType") Integer backendEditProductType)
+    {
+        return toAjax(fsStoreOrderService.updateStoreOrderItemJson(orderId, backendEditProductType));
+    }
+
+    @Log(title = "修改物流", businessType = BusinessType.UPDATE)
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:editDeliveryId')")
+    @PutMapping("/editDeliveryId")
+    public AjaxResult editDeliveryId(@RequestBody FsStoreOrderScrm fsStoreOrder)
+    {
+        return toAjax(fsStoreOrderService.updateFsStoreOrder(fsStoreOrder));
+    }
+
+    @Log(title = "同步管易物流单号", businessType = BusinessType.UPDATE)
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:updateErpOrder')")
+    @PostMapping("/updateErpOrder")
+    public R updateErpOrder(@Validated @RequestBody FsStoreOrderExpressEditParam param)
+    {
+        FsStoreOrderScrm order = fsStoreOrderService.selectFsStoreOrderById(param.getOrderId());
+        FsErpConfig erpConfig = configUtil.getErpConfig();
+        List<Long> noErpCompany = erpConfig.getNoErpCompany();
+        if (noErpCompany.contains(order.getCompanyId())) {
+            logger.info("订单:{},相关公司不推送erp", order.getOrderCode());
+            return R.ok("订单:" + order.getOrderCode() + "相关公司不推送erp");
+        }
+        IErpOrderService erpOrderService = getErpService();
+        ErpOrderQueryRequert request = new ErpOrderQueryRequert();
+        request.setCode(order.getExtendOrderId());
+        ErpOrderQueryResponse response = erpOrderService.getScrmOrder(request);
+        if (response.getOrders() != null && response.getOrders().size() > 0) {
+            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())) {
+                            fsStoreOrderService.updateDeliveryOrder(param.getOrderId(), delivery.getMail_no(), delivery.getExpress_code(), delivery.getExpress_name());
+                            return R.ok();
+                        }
+                    }
+                }
+            }
+        }
+        return R.error("未查询到快递信息");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:remove')")
+    @Log(title = "订单", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsStoreOrderService.deleteFsStoreOrderByIds(ids));
+    }
+
+    @PostMapping("/createUserOrder")
+    public R createUserOrder(@Validated @RequestBody FsStoreOrderCreateUserParam param, HttpServletRequest request)
+    {
+        return fsStoreOrderService.createUserOrder(param);
+    }
+
+    @Log(title = "确认订单", businessType = BusinessType.UPDATE)
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:finishOrder')")
+    @PostMapping("/finishOrder")
+    public R finishOrder(@Validated @RequestBody FsStoreOrderFinishParam param, HttpServletRequest request)
+    {
+        return fsStoreOrderService.finishOrder(param.getOrderId());
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:auditPayRemain')")
+    @PostMapping("/auditPayRemain")
+    public R auditPayRemain(@Validated @RequestBody FsStoreOrderAuditPayRemainParam param, HttpServletRequest request)
+    {
+        return fsStoreOrderService.auditPayRemain(param.getOrderId());
+    }
+
+    @Log(title = "导入", businessType = BusinessType.IMPORT)
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:importExpress')")
+    @PostMapping("/importExpress")
+    public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception
+    {
+        ExcelUtil<StoreOrderExpressExportDTO> util = new ExcelUtil<StoreOrderExpressExportDTO>(StoreOrderExpressExportDTO.class);
+        List<StoreOrderExpressExportDTO> list = util.importExcel(file.getInputStream());
+        String message = fsStoreOrderService.importExpress(list, updateSupport);
+        return AjaxResult.success(message);
+    }
+
+    @GetMapping("/importTemplate")
+    public AjaxResult importTemplate()
+    {
+        ExcelUtil<StoreOrderExpressExportDTO> util = new ExcelUtil<StoreOrderExpressExportDTO>(StoreOrderExpressExportDTO.class);
+        return util.importTemplateExcel("物流回单数据");
+    }
+
+    @GetMapping("/getStoreOrder")
+    public R getStoreOrder(@RequestParam("orderCode") String orderCode)
+    {
+        FsStoreOrderVO vo = fsStoreOrderService.selectFsStoreOrderVOByOrderCode(orderCode);
+        return R.ok().put("data", vo);
+    }
+
+    @Log(title = "冻结、解冻佣金", businessType = BusinessType.UPDATE)
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:editTuiMoney')")
+    @PostMapping("/editTuiMoney")
+    public R editTuiMoney(@Validated @RequestBody FsStoreOrderEditTuiMoneyParam param, HttpServletRequest request)
+    {
+        return fsStoreOrderService.editTuiMoney(param);
+    }
+
+    @Log(title = "退款", businessType = BusinessType.UPDATE)
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:refundOrderMoney')")
+    @PostMapping("/refundOrderMoney")
+    public synchronized R refundOrderMoney(@Validated @RequestBody FsStoreOrderRefundParam param, HttpServletRequest request)
+    {
+        return fsStoreOrderService.refundOrderMoney(param.getOrderId());
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:getEroOrder')")
+    @GetMapping("/getEroOrder")
+    public R getEroOrder(@RequestParam("extendOrderId") String extendOrderId)
+    {
+        IErpOrderService erpOrderService = getErpService();
+        ErpOrderQueryRequert request = new ErpOrderQueryRequert();
+        request.setCode(extendOrderId);
+        ErpOrderQueryResponse response = erpOrderService.getScrmOrder(request);
+        return R.ok().put("data", response);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:updateExpress')")
+    @PostMapping("/updateExpress")
+    public R updateExpress(@Validated @RequestBody FsStoreOrderExpressEditParam param, HttpServletRequest request)
+    {
+        return fsStoreOrderService.updateExpress(param);
+    }
+
+    @Log(title = "同步物流", businessType = BusinessType.UPDATE)
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:syncExpress')")
+    @PostMapping("/syncExpress")
+    public R syncExpress(@Validated @RequestBody FsStoreOrderExpressEditParam param, HttpServletRequest request)
+    {
+        return fsStoreOrderService.syncExpress(param);
+    }
+
+    @Log(title = "分佣", businessType = BusinessType.UPDATE)
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:addTuiMoney')")
+    @PostMapping("/addTuiMoney")
+    public R addTuiMoney(@Validated @RequestBody FsStoreOrderAddTuiMoneyParam param, HttpServletRequest request)
+    {
+        return fsStoreOrderService.addTuiMoney(param);
+    }
+
+    @GetMapping("/getCustomerOrderList")
+    public TableDataInfo getCustomerOrderList(FsStoreOrderParam param)
+    {
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        List<FsStoreOrderVO> list = fsStoreOrderService.selectFsCustomerStoreOrderListVO(param);
+        if (list != null) {
+            for (FsStoreOrderVO vo : list) {
+                if (vo.getPhone() != null) {
+                    vo.setPhone(vo.getPhone().replaceAll("(\\d{3})\\d*(\\d{1})", "$1****$2"));
+                    vo.setUserPhone(vo.getUserPhone().replaceAll("(\\d{3})\\d*(\\d{1})", "$1****$2"));
+                }
+            }
+        }
+        return getDataTable(list);
+    }
+
+    @Log(title = "手动推管易", businessType = BusinessType.INSERT)
+    @ApiOperation("创建ERP订单")
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:createErpOrder')")
+    @GetMapping("/createErpOrder")
+    public R createErpOrder(@RequestParam("orderCode") String orderCode) throws Exception
+    {
+        logger.info("手动推管易订单号:{}", orderCode);
+        FsStoreOrderScrm order = fsStoreOrderService.selectFsStoreOrderByOrderCode(orderCode);
+        return fsStoreOrderService.createOmsOrder(order.getId());
+    }
+
+    @GetMapping("/orderDimensionStatisticsList")
+    public TableDataInfo orderDimensionStatisticsList(OrderStatisticsParam param)
+    {
+        if ("广州郑多燕".equals(cloudHostProper.getCompanyName())) {
+            return getDataTable(fsStoreOrderService.selectZDYOrderSaleStatisticsList(param));
+        }
+        return getDataTable(fsStoreOrderService.selectOrderDimensionStatisticsList(param));
+    }
+
+    @Log(title = "订单维度统计", businessType = BusinessType.EXPORT)
+    @GetMapping("/orderDimensionStatisticsExport")
+    public AjaxResult orderDimensionStatisticsExport(OrderStatisticsParam param)
+    {
+        List<OrderStatisticsVo> list = fsStoreOrderService.selectOrderDimensionStatisticsList(param);
+        ExcelUtil<OrderStatisticsVo> util = new ExcelUtil<>(OrderStatisticsVo.class);
+        return util.exportExcel(list, "订单员工维度统计");
+    }
+
+    @GetMapping(value = "/queryErpPhone")
+    public AjaxResult queryErpPhone()
+    {
+        SysConfig sysConfig = sysConfigMapper.selectConfigByConfigKey("erp.phone");
+        List<String> list = new ArrayList<>();
+        if (sysConfig != null) {
+            String configValue = sysConfig.getConfigValue();
+            if (StringUtils.isNotEmpty(configValue)) {
+                list = JSON.parseArray(configValue, String.class);
+            }
+        }
+        return AjaxResult.success(list);
+    }
+
+    @PostMapping(value = "/saveErpPhone")
+    public AjaxResult saveErpPhone(@RequestBody List<String> phoneList)
+    {
+        phoneList = phoneList.stream().distinct().collect(Collectors.toList());
+        SysConfig sysConfig = sysConfigMapper.selectConfigByConfigKey("erp.phone");
+        sysConfig.setConfigValue(JSON.toJSONString(phoneList));
+        return AjaxResult.success(sysConfigMapper.updateConfig(sysConfig));
+    }
+
+    @PreAuthorize("@ss.hasPermi('his:storeOrder:createErpOrder')")
+    @Log(title = "订单", businessType = BusinessType.UPDATE)
+    @PostMapping("/editErpPhone")
+    public AjaxResult editErpPhone(@RequestBody FsStoreOrderScrmSetErpPhoneParam param)
+    {
+        param.setOpeName(SecurityUtils.getLoginUser().getUser().getNickName());
+        List<String> erpPhone = param.getErpPhone();
+        if (erpPhone == null || erpPhone.isEmpty()) {
+            return AjaxResult.error("请选择手机号");
+        }
+        return toAjax(fsStoreOrderService.batchUpdateErpByOrderIds(param));
+    }
+
+    @GetMapping("/getErpAccount")
+    public R getErpAccount()
+    {
+        List<String> list = new ArrayList<>();
+        if (CloudHostUtils.hasCloudHostName("金牛明医", "康年堂")) {
+            List<FsDfAccount> erpAccounts = fsDfAccountService.selectFsDfAccountList(null);
+            list = erpAccounts.stream().map(FsDfAccount::getLoginAccount).collect(Collectors.toList());
+        }
+        return R.ok().put("data", list);
+    }
+
+    @Log(title = "手动推管易", businessType = BusinessType.INSERT)
+    @ApiOperation("批量创建ERP订单")
+    @PreAuthorize("@ss.hasPermi('his:storeOrder:createErpOrder')")
+    @PostMapping(value = "/batchCreateErpOrder")
+    public R batchCreateErpOrder(@RequestBody FsStoreOrderScrmSetErpPhoneParam param)
+    {
+        String nickName = SecurityUtils.getLoginUser().getUser().getNickName();
+        String loginAccount = param.getLoginAccount();
+        if (StringUtils.isBlank(loginAccount)) {
+            return R.error("未选择推送erp账户");
+        }
+        FsStoreOrderDf df = getDFInfo(loginAccount);
+        if (df.getLoginAccount() == null) {
+            return R.error("未查询到所选erp账户");
+        }
+        List<Long> orderIds = param.getOrderIds();
+        if (orderIds == null || orderIds.isEmpty()) {
+            if (!StringUtils.isEmpty(param.getCreateTimeRange())) {
+                param.setCreateTimeList(param.getCreateTimeRange().split("--"));
+            }
+            if (!StringUtils.isEmpty(param.getPayTimeRange())) {
+                param.setPayTimeList(param.getPayTimeRange().split("--"));
+            }
+            if (!StringUtils.isEmpty(param.getDeliveryImportTimeRange())) {
+                param.setDeliveryImportTimeList(param.getDeliveryImportTimeRange().split("--"));
+            }
+            if (!StringUtils.isEmpty(param.getDeliverySendTimeRange())) {
+                param.setDeliverySendTimeList(param.getDeliverySendTimeRange().split("--"));
+            }
+            param.setNotHealth(1);
+            List<FsStoreOrderVO> list = fsStoreOrderService.selectFsStoreOrderListVO(param);
+            orderIds = list.stream().map(FsStoreOrderVO::getId).collect(Collectors.toList());
+        }
+        if (orderIds.isEmpty()) {
+            return R.ok();
+        }
+        orderIds.forEach(orderId -> {
+            try {
+                df.setOrderId(orderId);
+                FsStoreOrderDf temp = fsStoreOrderDfService.selectFsStoreOrderDfByOrderId(df.getOrderId());
+                if (temp == null) {
+                    df.setParcelQuantity(param.getParcelQuantity());
+                    fsStoreOrderDfService.insertFsStoreOrderDf(df);
+                    fsStoreOrderLogsService.create(orderId, FsStoreOrderLogEnum.SET_PUSH_ACCOUNT.getValue(),
+                            nickName + " " + FsStoreOrderLogEnum.SET_PUSH_ACCOUNT.getDesc() + ":" + df.getLoginAccount());
+                }
+                fsStoreOrderService.createOmsOrder(orderId);
+                fsStoreOrderLogsService.create(orderId, FsStoreOrderLogEnum.PUSH_ORDER_ERP.getValue(),
+                        nickName + " " + FsStoreOrderLogEnum.PUSH_ORDER_ERP.getDesc() + ":" + df.getLoginAccount());
+            } catch (ParseException e) {
+                throw new RuntimeException(e);
+            }
+        });
+        return R.ok();
+    }
+
+    @ApiOperation("批量设置订单账户")
+    @PreAuthorize("@ss.hasPermi('his:storeOrder:createErpOrder')")
+    @PostMapping(value = "/batchSetErpOrder")
+    public R batchSetErpOrder(@RequestBody FsStoreOrderScrmSetErpPhoneParam param)
+    {
+        String nickName = SecurityUtils.getLoginUser().getUser().getNickName();
+        String loginAccount = param.getLoginAccount();
+        if (StringUtils.isBlank(loginAccount)) {
+            return R.error("未选择erp账户");
+        }
+        FsStoreOrderDf df = getDFInfo(loginAccount);
+        if (df.getLoginAccount() == null) {
+            return R.error("未查询到所选erp账户");
+        }
+        List<Long> orderIds = param.getOrderIds();
+        if (orderIds == null || orderIds.isEmpty()) {
+            if (!StringUtils.isEmpty(param.getCreateTimeRange())) {
+                param.setCreateTimeList(param.getCreateTimeRange().split("--"));
+            }
+            if (!StringUtils.isEmpty(param.getPayTimeRange())) {
+                param.setPayTimeList(param.getPayTimeRange().split("--"));
+            }
+            if (!StringUtils.isEmpty(param.getDeliveryImportTimeRange())) {
+                param.setDeliveryImportTimeList(param.getDeliveryImportTimeRange().split("--"));
+            }
+            if (!StringUtils.isEmpty(param.getDeliverySendTimeRange())) {
+                param.setDeliverySendTimeList(param.getDeliverySendTimeRange().split("--"));
+            }
+            param.setNotHealth(1);
+            List<FsStoreOrderVO> list = fsStoreOrderService.selectFsStoreOrderListVO(param);
+            orderIds = list.stream().map(FsStoreOrderVO::getId).collect(Collectors.toList());
+        }
+        if (orderIds.isEmpty()) {
+            return R.ok();
+        }
+        orderIds.forEach(orderId -> {
+            df.setOrderId(orderId);
+            FsStoreOrderDf temp = fsStoreOrderDfService.selectFsStoreOrderDfByOrderId(df.getOrderId());
+            df.setParcelQuantity(param.getParcelQuantity());
+            if (temp != null) {
+                df.setUpdateTime(new Date());
+                fsStoreOrderDfService.updateFsStoreOrderDf(df);
+            } else {
+                fsStoreOrderDfService.insertFsStoreOrderDf(df);
+            }
+            fsStoreOrderLogsService.create(orderId, FsStoreOrderLogEnum.SET_PUSH_ACCOUNT.getValue(),
+                    nickName + " " + FsStoreOrderLogEnum.SET_PUSH_ACCOUNT.getDesc() + ":" + df.getLoginAccount());
+        });
+        return R.ok();
+    }
+
+    @ApiOperation("批量审核订单")
+    @Log(title = "订单管理", businessType = BusinessType.UPDATE)
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:batchAudit')")
+    @PostMapping("/batchAudit")
+    public R batchAuditOrder(@Validated @RequestBody FsStoreOrderBatchAuditParam param)
+    {
+        if (param.getOrderIds() == null || param.getOrderIds().isEmpty()) {
+            return R.error("订单ID列表不能为空");
+        }
+        if (param.getIsAudit() == null) {
+            return R.error("审核状态不能为空");
+        }
+        int count = fsStoreOrderService.batchAuditOrder(param);
+        return R.ok("成功审核 " + count + " 条订单");
+    }
+
+    @ApiOperation("订单备注")
+    @Log(title = "订单管理", businessType = BusinessType.UPDATE)
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:remark')")
+    @PostMapping("/remark")
+    public R remark(@Validated @RequestBody FsStoreOrderScrm param)
+    {
+        if (param.getId() == null || param.getId() < 1) {
+            return R.error("订单ID错误");
+        }
+        if (StringUtils.isEmpty(param.getOrderRemark())) {
+            return R.error("订单备注不能为空");
+        }
+        return fsStoreOrderService.orderRemark(param);
+    }
+
+    private SysRole isCheckPermission()
+    {
+        SysRole sysRole = new SysRole();
+        SysUser user = SecurityUtils.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;
+    }
+
+    private FsStoreOrderDf getDFInfo(String loginAccount)
+    {
+        List<FsDfAccount> erpAccounts = fsDfAccountService.selectFsDfAccountList(null);
+        FsStoreOrderDf df = new FsStoreOrderDf();
+        for (FsDfAccount erpAccount : erpAccounts) {
+            if (loginAccount.equals(erpAccount.getLoginAccount())) {
+                df.setAppKey(erpAccount.getDfAppKey());
+                df.setAppSecret(erpAccount.getDfAppsecret());
+                df.setLoginAccount(loginAccount);
+                df.setMonthlyCard(erpAccount.getMonthlyCard());
+                df.setExpressProductCode(erpAccount.getExpressProductCode());
+                df.setStatus(0);
+                break;
+            }
+        }
+        return df;
+    }
+}

+ 82 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreOrderStatusScrmBridgeController.java

@@ -0,0 +1,82 @@
+package com.fs.admin.controller.store;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.domain.FsStoreOrderStatusScrm;
+import com.fs.hisStore.service.IFsStoreOrderStatusScrmService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 订单操作记录 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsStoreOrderStatusScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2022-03-15
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/storeOrderStatus")
+public class FsStoreOrderStatusScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsStoreOrderStatusScrmService fsStoreOrderStatusService;
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderStatus:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreOrderStatusScrm fsStoreOrderStatus)
+    {
+        startPage();
+        List<FsStoreOrderStatusScrm> list = fsStoreOrderStatusService.selectFsStoreOrderStatusList(fsStoreOrderStatus);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderStatus:export')")
+    @Log(title = "订单操作记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreOrderStatusScrm fsStoreOrderStatus)
+    {
+        List<FsStoreOrderStatusScrm> list = fsStoreOrderStatusService.selectFsStoreOrderStatusList(fsStoreOrderStatus);
+        ExcelUtil<FsStoreOrderStatusScrm> util = new ExcelUtil<FsStoreOrderStatusScrm>(FsStoreOrderStatusScrm.class);
+        return util.exportExcel(list, "storeOrderStatus");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderStatus:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsStoreOrderStatusService.selectFsStoreOrderStatusById(id));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderStatus:add')")
+    @Log(title = "订单操作记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreOrderStatusScrm fsStoreOrderStatus)
+    {
+        return toAjax(fsStoreOrderStatusService.insertFsStoreOrderStatus(fsStoreOrderStatus));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderStatus:edit')")
+    @Log(title = "订单操作记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreOrderStatusScrm fsStoreOrderStatus)
+    {
+        return toAjax(fsStoreOrderStatusService.updateFsStoreOrderStatus(fsStoreOrderStatus));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeOrderStatus:remove')")
+    @Log(title = "订单操作记录", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsStoreOrderStatusService.deleteFsStoreOrderStatusByIds(ids));
+    }
+}

+ 82 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreProductAttrScrmBridgeController.java

@@ -0,0 +1,82 @@
+package com.fs.admin.controller.store;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.domain.FsStoreProductAttrScrm;
+import com.fs.hisStore.service.IFsStoreProductAttrScrmService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 商品属性 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsStoreProductAttrScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2022-03-15
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/storeProductAttr")
+public class FsStoreProductAttrScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsStoreProductAttrScrmService fsStoreProductAttrService;
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductAttr:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreProductAttrScrm fsStoreProductAttr)
+    {
+        startPage();
+        List<FsStoreProductAttrScrm> list = fsStoreProductAttrService.selectFsStoreProductAttrList(fsStoreProductAttr);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductAttr:export')")
+    @Log(title = "商品属性", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreProductAttrScrm fsStoreProductAttr)
+    {
+        List<FsStoreProductAttrScrm> list = fsStoreProductAttrService.selectFsStoreProductAttrList(fsStoreProductAttr);
+        ExcelUtil<FsStoreProductAttrScrm> util = new ExcelUtil<FsStoreProductAttrScrm>(FsStoreProductAttrScrm.class);
+        return util.exportExcel(list, "storeProductAttr");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductAttr:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsStoreProductAttrService.selectFsStoreProductAttrById(id));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductAttr:add')")
+    @Log(title = "商品属性", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreProductAttrScrm fsStoreProductAttr)
+    {
+        return toAjax(fsStoreProductAttrService.insertFsStoreProductAttr(fsStoreProductAttr));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductAttr:edit')")
+    @Log(title = "商品属性", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreProductAttrScrm fsStoreProductAttr)
+    {
+        return toAjax(fsStoreProductAttrService.updateFsStoreProductAttr(fsStoreProductAttr));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductAttr:remove')")
+    @Log(title = "商品属性", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsStoreProductAttrService.deleteFsStoreProductAttrByIds(ids));
+    }
+}

+ 82 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreProductAttrValueScrmBridgeController.java

@@ -0,0 +1,82 @@
+package com.fs.admin.controller.store;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.domain.FsStoreProductAttrValueScrm;
+import com.fs.hisStore.service.IFsStoreProductAttrValueScrmService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 商品属性值 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsStoreProductAttrValueScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2022-03-15
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/storeProductAttrValue")
+public class FsStoreProductAttrValueScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsStoreProductAttrValueScrmService fsStoreProductAttrValueService;
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductAttrValue:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreProductAttrValueScrm fsStoreProductAttrValue)
+    {
+        startPage();
+        List<FsStoreProductAttrValueScrm> list = fsStoreProductAttrValueService.selectFsStoreProductAttrValueList(fsStoreProductAttrValue);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductAttrValue:export')")
+    @Log(title = "商品属性值", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreProductAttrValueScrm fsStoreProductAttrValue)
+    {
+        List<FsStoreProductAttrValueScrm> list = fsStoreProductAttrValueService.selectFsStoreProductAttrValueList(fsStoreProductAttrValue);
+        ExcelUtil<FsStoreProductAttrValueScrm> util = new ExcelUtil<FsStoreProductAttrValueScrm>(FsStoreProductAttrValueScrm.class);
+        return util.exportExcel(list, "storeProductAttrValue");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductAttrValue:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsStoreProductAttrValueService.selectFsStoreProductAttrValueById(id));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductAttrValue:add')")
+    @Log(title = "商品属性值", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreProductAttrValueScrm fsStoreProductAttrValue)
+    {
+        return toAjax(fsStoreProductAttrValueService.insertFsStoreProductAttrValue(fsStoreProductAttrValue));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductAttrValue:edit')")
+    @Log(title = "商品属性值", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreProductAttrValueScrm fsStoreProductAttrValue)
+    {
+        return toAjax(fsStoreProductAttrValueService.updateFsStoreProductAttrValue(fsStoreProductAttrValue));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductAttrValue:remove')")
+    @Log(title = "商品属性值", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsStoreProductAttrValueService.deleteFsStoreProductAttrValueByIds(ids));
+    }
+}

+ 82 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreProductCategoryScrmBridgeController.java

@@ -0,0 +1,82 @@
+package com.fs.admin.controller.store;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.domain.FsStoreProductCategoryScrm;
+import com.fs.hisStore.service.IFsStoreProductCategoryScrmService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 商品分类 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsStoreProductCategoryScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2022-03-16
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/storeProductCategory")
+public class FsStoreProductCategoryScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsStoreProductCategoryScrmService fsStoreProductCategoryService;
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductCategory:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreProductCategoryScrm fsStoreProductCategory)
+    {
+        startPage();
+        List<FsStoreProductCategoryScrm> list = fsStoreProductCategoryService.selectFsStoreProductCategoryList(fsStoreProductCategory);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductCategory:export')")
+    @Log(title = "商品分类", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreProductCategoryScrm fsStoreProductCategory)
+    {
+        List<FsStoreProductCategoryScrm> list = fsStoreProductCategoryService.selectFsStoreProductCategoryList(fsStoreProductCategory);
+        ExcelUtil<FsStoreProductCategoryScrm> util = new ExcelUtil<FsStoreProductCategoryScrm>(FsStoreProductCategoryScrm.class);
+        return util.exportExcel(list, "商品分类");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductCategory:query')")
+    @GetMapping(value = "/{cateId}")
+    public AjaxResult getInfo(@PathVariable("cateId") Long cateId)
+    {
+        return AjaxResult.success(fsStoreProductCategoryService.selectFsStoreProductCategoryById(cateId));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductCategory:add')")
+    @Log(title = "商品分类", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreProductCategoryScrm fsStoreProductCategory)
+    {
+        return toAjax(fsStoreProductCategoryService.insertFsStoreProductCategory(fsStoreProductCategory));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductCategory:edit')")
+    @Log(title = "商品分类", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreProductCategoryScrm fsStoreProductCategory)
+    {
+        return toAjax(fsStoreProductCategoryService.updateFsStoreProductCategory(fsStoreProductCategory));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductCategory:remove')")
+    @Log(title = "商品分类", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{cateIds}")
+    public AjaxResult remove(@PathVariable Long[] cateIds)
+    {
+        return toAjax(fsStoreProductCategoryService.deleteFsStoreProductCategoryByIds(cateIds));
+    }
+}

+ 82 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreProductDetailsScrmBridgeController.java

@@ -0,0 +1,82 @@
+package com.fs.admin.controller.store;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.domain.FsStoreProductDetailsScrm;
+import com.fs.hisStore.service.IFsStoreProductDetailsScrmService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 商品详情 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsStoreProductDetailsScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2022-03-15
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/storeProductDetails")
+public class FsStoreProductDetailsScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsStoreProductDetailsScrmService fsStoreProductDetailsService;
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductDetails:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreProductDetailsScrm fsStoreProductDetails)
+    {
+        startPage();
+        List<FsStoreProductDetailsScrm> list = fsStoreProductDetailsService.selectFsStoreProductDetailsList(fsStoreProductDetails);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductDetails:export')")
+    @Log(title = "商品详情", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreProductDetailsScrm fsStoreProductDetails)
+    {
+        List<FsStoreProductDetailsScrm> list = fsStoreProductDetailsService.selectFsStoreProductDetailsList(fsStoreProductDetails);
+        ExcelUtil<FsStoreProductDetailsScrm> util = new ExcelUtil<FsStoreProductDetailsScrm>(FsStoreProductDetailsScrm.class);
+        return util.exportExcel(list, "storeProductDetails");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductDetails:query')")
+    @GetMapping(value = "/{detailsId}")
+    public AjaxResult getInfo(@PathVariable("detailsId") Long detailsId)
+    {
+        return AjaxResult.success(fsStoreProductDetailsService.selectFsStoreProductDetailsById(detailsId));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductDetails:add')")
+    @Log(title = "商品详情", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreProductDetailsScrm fsStoreProductDetails)
+    {
+        return toAjax(fsStoreProductDetailsService.insertFsStoreProductDetails(fsStoreProductDetails));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductDetails:edit')")
+    @Log(title = "商品详情", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreProductDetailsScrm fsStoreProductDetails)
+    {
+        return toAjax(fsStoreProductDetailsService.updateFsStoreProductDetails(fsStoreProductDetails));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductDetails:remove')")
+    @Log(title = "商品详情", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{detailsIds}")
+    public AjaxResult remove(@PathVariable Long[] detailsIds)
+    {
+        return toAjax(fsStoreProductDetailsService.deleteFsStoreProductDetailsByIds(detailsIds));
+    }
+}

+ 117 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreProductGroupScrmBridgeController.java

@@ -0,0 +1,117 @@
+package com.fs.admin.controller.store;
+
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONUtil;
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.domain.FsStoreProductScrm;
+import com.fs.hisStore.domain.FsStoreProductAttrValueScrm;
+import com.fs.hisStore.domain.FsStoreProductGroupScrm;
+import com.fs.hisStore.dto.StoreProductGroupDTO;
+import com.fs.hisStore.service.IFsStoreProductAttrValueScrmService;
+import com.fs.hisStore.service.IFsStoreProductGroupScrmService;
+import com.fs.hisStore.service.IFsStoreProductScrmService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 商品组合 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsStoreProductGroupScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2022-11-23
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/storeProductGroup")
+public class FsStoreProductGroupScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsStoreProductGroupScrmService fsStoreProductGroupService;
+    @Autowired
+    private IFsStoreProductAttrValueScrmService attrValueService;
+    @Autowired
+    private IFsStoreProductScrmService storeProductService;
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductGroup:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreProductGroupScrm fsStoreProductGroup)
+    {
+        startPage();
+        List<FsStoreProductGroupScrm> list = fsStoreProductGroupService.selectFsStoreProductGroupList(fsStoreProductGroup);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductGroup:export')")
+    @Log(title = "商品组合", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreProductGroupScrm fsStoreProductGroup)
+    {
+        List<FsStoreProductGroupScrm> list = fsStoreProductGroupService.selectFsStoreProductGroupList(fsStoreProductGroup);
+        ExcelUtil<FsStoreProductGroupScrm> util = new ExcelUtil<FsStoreProductGroupScrm>(FsStoreProductGroupScrm.class);
+        return util.exportExcel(list, "storeProductGroup");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductGroup:query')")
+    @GetMapping(value = "/{groupId}")
+    public AjaxResult getInfo(@PathVariable("groupId") Long groupId)
+    {
+        FsStoreProductGroupScrm storeProductGroup = fsStoreProductGroupService.selectFsStoreProductGroupById(groupId);
+        JSONArray jsonArray = JSONUtil.parseArray(storeProductGroup.getProducts());
+        List<StoreProductGroupDTO> productGroupDTOS = JSONUtil.toList(jsonArray, StoreProductGroupDTO.class);
+        for (StoreProductGroupDTO dto : productGroupDTOS) {
+            FsStoreProductAttrValueScrm attrValue = attrValueService.selectFsStoreProductAttrValueById(dto.getId());
+            if (attrValue != null) {
+                FsStoreProductScrm product = storeProductService.selectFsStoreProductById(attrValue.getProductId());
+                if (product != null) {
+                    dto.setProductId(attrValue.getProductId());
+                    dto.setId(dto.getId());
+                    dto.setBarCode(attrValue.getBarCode());
+                    dto.setPrice(attrValue.getPrice());
+                    dto.setCount(dto.getCount());
+                    dto.setSku(attrValue.getSku());
+                    dto.setImage(attrValue.getImage());
+                    dto.setProductName(product.getProductName());
+                }
+            }
+        }
+        storeProductGroup.setProductList(productGroupDTOS);
+        return AjaxResult.success(storeProductGroup);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductGroup:add')")
+    @Log(title = "商品组合", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreProductGroupScrm fsStoreProductGroup)
+    {
+        String products = JSONUtil.toJsonStr(fsStoreProductGroup.getProductList());
+        fsStoreProductGroup.setProducts(products);
+        return toAjax(fsStoreProductGroupService.insertFsStoreProductGroup(fsStoreProductGroup));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductGroup:edit')")
+    @Log(title = "商品组合", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreProductGroupScrm fsStoreProductGroup)
+    {
+        String products = JSONUtil.toJsonStr(fsStoreProductGroup.getProductList());
+        fsStoreProductGroup.setProducts(products);
+        return toAjax(fsStoreProductGroupService.updateFsStoreProductGroup(fsStoreProductGroup));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductGroup:remove')")
+    @Log(title = "商品组合", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{groupIds}")
+    public AjaxResult remove(@PathVariable Long[] groupIds)
+    {
+        return toAjax(fsStoreProductGroupService.deleteFsStoreProductGroupByIds(groupIds));
+    }
+}

+ 61 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreProductRelationScrmBridgeController.java

@@ -0,0 +1,61 @@
+package com.fs.admin.controller.store;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ParseUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.param.FsStoreProductRelationParam;
+import com.fs.hisStore.service.IFsStoreProductRelationScrmService;
+import com.fs.hisStore.vo.FsStoreProductRelationQueryVO;
+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 org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 商品点赞和收藏 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsStoreProductRelationScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2022-03-15
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/storeProductRelation")
+public class FsStoreProductRelationScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsStoreProductRelationScrmService fsStoreProductRelationService;
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductRelation:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreProductRelationParam param)
+    {
+        startPage();
+        List<FsStoreProductRelationQueryVO> list = fsStoreProductRelationService.selectFsStoreProductRelationListVO(param);
+        for (FsStoreProductRelationQueryVO vo : list){
+            vo.setPhone(ParseUtils.parsePhone(vo.getPhone()));
+        }
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductRelation:export')")
+    @Log(title = "商品浏览记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreProductRelationParam param)
+    {
+        List<FsStoreProductRelationQueryVO> list = fsStoreProductRelationService.selectFsStoreProductRelationListVO(param);
+        for (FsStoreProductRelationQueryVO vo : list){
+            vo.setPhone(ParseUtils.parsePhone(vo.getPhone()));
+        }
+        ExcelUtil<FsStoreProductRelationQueryVO> util = new ExcelUtil<FsStoreProductRelationQueryVO>(FsStoreProductRelationQueryVO.class);
+        return util.exportExcel(list, "商品浏览记录");
+    }
+}

+ 95 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreProductReplyScrmBridgeController.java

@@ -0,0 +1,95 @@
+package com.fs.admin.controller.store;
+
+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;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.domain.FsStoreProductReplyScrm;
+import com.fs.hisStore.service.IFsStoreProductReplyScrmService;
+import com.fs.hisStore.vo.FsStoreProductReplyVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 评论 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsStoreProductReplyScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2022-03-15
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/storeProductReply")
+public class FsStoreProductReplyScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsStoreProductReplyScrmService fsStoreProductReplyService;
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductReply:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreProductReplyScrm fsStoreProductReply)
+    {
+        startPage();
+        fsStoreProductReply.setIsDel(0);
+        List<FsStoreProductReplyVO> list = fsStoreProductReplyService.selectFsStoreProductReplyListVO(fsStoreProductReply);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductReply:export')")
+    @Log(title = "评论", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreProductReplyScrm fsStoreProductReply)
+    {
+        List<FsStoreProductReplyScrm> list = fsStoreProductReplyService.selectFsStoreProductReplyList(fsStoreProductReply);
+        ExcelUtil<FsStoreProductReplyScrm> util = new ExcelUtil<FsStoreProductReplyScrm>(FsStoreProductReplyScrm.class);
+        return util.exportExcel(list, "storeProductReply");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductReply:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsStoreProductReplyService.selectFsStoreProductReplyById(id));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductReply:add')")
+    @Log(title = "评论", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreProductReplyScrm fsStoreProductReply)
+    {
+        return toAjax(fsStoreProductReplyService.insertFsStoreProductReply(fsStoreProductReply));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductReply:edit')")
+    @Log(title = "评论", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreProductReplyScrm fsStoreProductReply)
+    {
+        return toAjax(fsStoreProductReplyService.updateFsStoreProductReply(fsStoreProductReply));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductReply:remove')")
+    @Log(title = "评论", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsStoreProductReplyService.deleteFsStoreProductReplyByIds(ids));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductReply:reply')")
+    @Log(title = "评论回复", businessType = BusinessType.INSERT)
+    @PostMapping("/reply")
+    public AjaxResult reply(@RequestBody FsStoreProductReplyScrm fsStoreProductReply)
+    {
+        fsStoreProductReply.setIsReply(1);
+        fsStoreProductReply.setReplyTime(new DateTime());
+        return toAjax(fsStoreProductReplyService.updateFsStoreProductReply(fsStoreProductReply));
+    }
+}

+ 103 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreProductRuleScrmBridgeController.java

@@ -0,0 +1,103 @@
+package com.fs.admin.controller.store;
+
+import cn.hutool.json.JSONUtil;
+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;
+import com.fs.hisStore.domain.FsStoreProductRuleScrm;
+import com.fs.hisStore.param.FsStoreProductRuleParam;
+import com.fs.hisStore.service.IFsStoreProductRuleScrmService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 商品规格 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsStoreProductRuleScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2022-03-15
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/storeProductRule")
+public class FsStoreProductRuleScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsStoreProductRuleScrmService fsStoreProductRuleService;
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductRule:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreProductRuleScrm fsStoreProductRule)
+    {
+        startPage();
+        fsStoreProductRule.setIsDel(0);
+        List<FsStoreProductRuleScrm> list = fsStoreProductRuleService.selectFsStoreProductRuleList(fsStoreProductRule);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductRule:export')")
+    @Log(title = "商品规格", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreProductRuleScrm fsStoreProductRule)
+    {
+        List<FsStoreProductRuleScrm> list = fsStoreProductRuleService.selectFsStoreProductRuleList(fsStoreProductRule);
+        ExcelUtil<FsStoreProductRuleScrm> util = new ExcelUtil<FsStoreProductRuleScrm>(FsStoreProductRuleScrm.class);
+        return util.exportExcel(list, "storeProductRule");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductRule:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Integer id)
+    {
+        return AjaxResult.success(fsStoreProductRuleService.selectFsStoreProductRuleById(id));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductRule:add')")
+    @Log(title = "商品规格", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreProductRuleParam fsStoreProductRule)
+    {
+        FsStoreProductRuleScrm rule = new FsStoreProductRuleScrm();
+        rule.setRuleName(fsStoreProductRule.getRuleName());
+        rule.setRuleValue(JSONUtil.toJsonStr(fsStoreProductRule.getRuleValue()));
+        rule.setIsDel(0);
+        return toAjax(fsStoreProductRuleService.insertFsStoreProductRule(rule));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductRule:edit')")
+    @Log(title = "商品规格", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreProductRuleParam fsStoreProductRule)
+    {
+        FsStoreProductRuleScrm rule = new FsStoreProductRuleScrm();
+        rule.setId(fsStoreProductRule.getId());
+        rule.setRuleName(fsStoreProductRule.getRuleName());
+        rule.setRuleValue(JSONUtil.toJsonStr(fsStoreProductRule.getRuleValue()));
+        rule.setIsDel(0);
+        return toAjax(fsStoreProductRuleService.updateFsStoreProductRule(rule));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductRule:remove')")
+    @Log(title = "商品规格", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Integer[] ids)
+    {
+        return toAjax(fsStoreProductRuleService.deleteFsStoreProductRuleByIds(ids));
+    }
+
+    @GetMapping("/getAllList")
+    public R getAllList(FsStoreProductRuleScrm fsStoreProductRule)
+    {
+        fsStoreProductRule.setIsDel(0);
+        List<FsStoreProductRuleScrm> list = fsStoreProductRuleService.selectFsStoreProductRuleList(fsStoreProductRule);
+        return R.ok().put("data", list);
+    }
+}

+ 82 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreProductTemplateScrmBridgeController.java

@@ -0,0 +1,82 @@
+package com.fs.admin.controller.store;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.domain.FsStoreProductTemplateScrm;
+import com.fs.hisStore.service.IFsStoreProductTemplateScrmService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 商品模板 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsStoreProductTemplateScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2022-03-15
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/storeProductTemplate")
+public class FsStoreProductTemplateScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsStoreProductTemplateScrmService fsStoreProductTemplateService;
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductTemplate:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreProductTemplateScrm fsStoreProductTemplate)
+    {
+        startPage();
+        List<FsStoreProductTemplateScrm> list = fsStoreProductTemplateService.selectFsStoreProductTemplateList(fsStoreProductTemplate);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductTemplate:export')")
+    @Log(title = "商品模板", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreProductTemplateScrm fsStoreProductTemplate)
+    {
+        List<FsStoreProductTemplateScrm> list = fsStoreProductTemplateService.selectFsStoreProductTemplateList(fsStoreProductTemplate);
+        ExcelUtil<FsStoreProductTemplateScrm> util = new ExcelUtil<FsStoreProductTemplateScrm>(FsStoreProductTemplateScrm.class);
+        return util.exportExcel(list, "storeProductTemplate");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductTemplate:query')")
+    @GetMapping(value = "/{tempId}")
+    public AjaxResult getInfo(@PathVariable("tempId") Long tempId)
+    {
+        return AjaxResult.success(fsStoreProductTemplateService.selectFsStoreProductTemplateById(tempId));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductTemplate:add')")
+    @Log(title = "商品模板", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreProductTemplateScrm fsStoreProductTemplate)
+    {
+        return toAjax(fsStoreProductTemplateService.insertFsStoreProductTemplate(fsStoreProductTemplate));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductTemplate:edit')")
+    @Log(title = "商品模板", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreProductTemplateScrm fsStoreProductTemplate)
+    {
+        return toAjax(fsStoreProductTemplateService.updateFsStoreProductTemplate(fsStoreProductTemplate));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductTemplate:remove')")
+    @Log(title = "商品模板", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{tempIds}")
+    public AjaxResult remove(@PathVariable Long[] tempIds)
+    {
+        return toAjax(fsStoreProductTemplateService.deleteFsStoreProductTemplateByIds(tempIds));
+    }
+}

+ 90 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreRecommendScrmBridgeController.java

@@ -0,0 +1,90 @@
+package com.fs.admin.controller.store;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.domain.FsStoreRecommendScrm;
+import com.fs.hisStore.service.IFsStoreRecommendScrmService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 店铺推荐 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsStoreRecommendScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2023-06-15
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/recommend")
+public class FsStoreRecommendScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsStoreRecommendScrmService fsStoreRecommendService;
+
+    @PreAuthorize("@ss.hasPermi('store:recommend:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreRecommendScrm fsStoreRecommendScrm)
+    {
+        startPage();
+        List<FsStoreRecommendScrm> list = fsStoreRecommendService.selectFsStoreRecommendScrmList(fsStoreRecommendScrm);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:recommend:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsStoreRecommendService.selectFsStoreRecommendScrmById(id));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:recommend:add')")
+    @Log(title = "店铺推荐", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreRecommendScrm fsStoreRecommendScrm)
+    {
+        return toAjax(fsStoreRecommendService.insertFsStoreRecommendScrm(fsStoreRecommendScrm));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:recommend:edit')")
+    @Log(title = "店铺推荐", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreRecommendScrm fsStoreRecommendScrm)
+    {
+        return toAjax(fsStoreRecommendService.updateFsStoreRecommendScrm(fsStoreRecommendScrm));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:recommend:delete')")
+    @Log(title = "店铺推荐", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsStoreRecommendService.deleteFsStoreRecommendScrmByIds(ids));
+    }
+
+    @GetMapping("/validList")
+    public TableDataInfo validList(FsStoreRecommendScrm fsStoreRecommendScrm)
+    {
+        startPage();
+        List<FsStoreRecommendScrm> list = fsStoreRecommendService.selectValidRecommendList(fsStoreRecommendScrm);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:recommend:export')")
+    @Log(title = "店铺推荐", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreRecommendScrm fsStoreRecommendScrm)
+    {
+        List<FsStoreRecommendScrm> list = fsStoreRecommendService.selectFsStoreRecommendScrmList(fsStoreRecommendScrm);
+        ExcelUtil<FsStoreRecommendScrm> util = new ExcelUtil<>(FsStoreRecommendScrm.class);
+        return util.exportExcel(list, "店铺推荐数据");
+    }
+}

+ 82 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreShopStaffScrmBridgeController.java

@@ -0,0 +1,82 @@
+package com.fs.admin.controller.store;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.domain.FsStoreShopStaffScrm;
+import com.fs.hisStore.service.IFsStoreShopStaffScrmService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 门店店员 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsStoreShopStaffScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2022-03-15
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/storeShopStaff")
+public class FsStoreShopStaffScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsStoreShopStaffScrmService fsStoreShopStaffService;
+
+    @PreAuthorize("@ss.hasPermi('store:storeShopStaff:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreShopStaffScrm fsStoreShopStaff)
+    {
+        startPage();
+        List<FsStoreShopStaffScrm> list = fsStoreShopStaffService.selectFsStoreShopStaffList(fsStoreShopStaff);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeShopStaff:export')")
+    @Log(title = "门店店员", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreShopStaffScrm fsStoreShopStaff)
+    {
+        List<FsStoreShopStaffScrm> list = fsStoreShopStaffService.selectFsStoreShopStaffList(fsStoreShopStaff);
+        ExcelUtil<FsStoreShopStaffScrm> util = new ExcelUtil<FsStoreShopStaffScrm>(FsStoreShopStaffScrm.class);
+        return util.exportExcel(list, "storeShopStaff");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeShopStaff:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsStoreShopStaffService.selectFsStoreShopStaffById(id));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeShopStaff:add')")
+    @Log(title = "门店店员", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreShopStaffScrm fsStoreShopStaff)
+    {
+        return toAjax(fsStoreShopStaffService.insertFsStoreShopStaff(fsStoreShopStaff));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeShopStaff:edit')")
+    @Log(title = "门店店员", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreShopStaffScrm fsStoreShopStaff)
+    {
+        return toAjax(fsStoreShopStaffService.updateFsStoreShopStaff(fsStoreShopStaff));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeShopStaff:remove')")
+    @Log(title = "门店店员", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsStoreShopStaffService.deleteFsStoreShopStaffByIds(ids));
+    }
+}

+ 82 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsStoreVisitScrmBridgeController.java

@@ -0,0 +1,82 @@
+package com.fs.admin.controller.store;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.domain.FsStoreVisitScrm;
+import com.fs.hisStore.service.IFsStoreVisitScrmService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 产品浏览分析 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsStoreVisitScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2022-03-15
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/storeVisit")
+public class FsStoreVisitScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsStoreVisitScrmService fsStoreVisitService;
+
+    @PreAuthorize("@ss.hasPermi('store:storeVisit:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreVisitScrm fsStoreVisit)
+    {
+        startPage();
+        List<FsStoreVisitScrm> list = fsStoreVisitService.selectFsStoreVisitList(fsStoreVisit);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeVisit:export')")
+    @Log(title = "产品浏览分析", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreVisitScrm fsStoreVisit)
+    {
+        List<FsStoreVisitScrm> list = fsStoreVisitService.selectFsStoreVisitList(fsStoreVisit);
+        ExcelUtil<FsStoreVisitScrm> util = new ExcelUtil<FsStoreVisitScrm>(FsStoreVisitScrm.class);
+        return util.exportExcel(list, "storeVisit");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeVisit:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsStoreVisitService.selectFsStoreVisitById(id));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeVisit:add')")
+    @Log(title = "产品浏览分析", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreVisitScrm fsStoreVisit)
+    {
+        return toAjax(fsStoreVisitService.insertFsStoreVisit(fsStoreVisit));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeVisit:edit')")
+    @Log(title = "产品浏览分析", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreVisitScrm fsStoreVisit)
+    {
+        return toAjax(fsStoreVisitService.updateFsStoreVisit(fsStoreVisit));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeVisit:remove')")
+    @Log(title = "产品浏览分析", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsStoreVisitService.deleteFsStoreVisitByIds(ids));
+    }
+}

+ 84 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/FsUserPromoterApplyScrmBridgeController.java

@@ -0,0 +1,84 @@
+package com.fs.admin.controller.store;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.domain.FsUserPromoterApplyScrm;
+import com.fs.hisStore.param.FsUsePromoterApplyParam;
+import com.fs.hisStore.service.IFsUserPromoterApplyScrmService;
+import com.fs.hisStore.vo.FsUserPromoterApplyVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 推广员申请 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.FsUserPromoterApplyController,供平台总后台跨租户管理
+ *
+ * @author fs
+ * @date 2023-02-28
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/store/userPromoterApply")
+public class FsUserPromoterApplyScrmBridgeController extends BaseController
+{
+    @Autowired
+    private IFsUserPromoterApplyScrmService fsUserPromoterApplyService;
+
+    @PreAuthorize("@ss.hasPermi('store:userPromoterApply:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsUsePromoterApplyParam fsUserPromoterApply)
+    {
+        startPage();
+        List<FsUserPromoterApplyVO> list = fsUserPromoterApplyService.selectFsUserPromoterApplyListVO(fsUserPromoterApply);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:userPromoterApply:export')")
+    @Log(title = "推广员申请", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsUserPromoterApplyScrm fsUserPromoterApply)
+    {
+        List<FsUserPromoterApplyScrm> list = fsUserPromoterApplyService.selectFsUserPromoterApplyList(fsUserPromoterApply);
+        ExcelUtil<FsUserPromoterApplyScrm> util = new ExcelUtil<FsUserPromoterApplyScrm>(FsUserPromoterApplyScrm.class);
+        return util.exportExcel(list, "userPromoterApply");
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:userPromoterApply:query')")
+    @GetMapping(value = "/{applyId}")
+    public AjaxResult getInfo(@PathVariable("applyId") Long applyId)
+    {
+        return AjaxResult.success(fsUserPromoterApplyService.selectFsUserPromoterApplyById(applyId));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:userPromoterApply:add')")
+    @Log(title = "推广员申请", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsUserPromoterApplyScrm fsUserPromoterApply)
+    {
+        return toAjax(fsUserPromoterApplyService.insertFsUserPromoterApply(fsUserPromoterApply));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:userPromoterApply:edit')")
+    @Log(title = "推广员申请", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsUserPromoterApplyScrm fsUserPromoterApply)
+    {
+        return toAjax(fsUserPromoterApplyService.updateFsUserPromoterApply(fsUserPromoterApply));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:userPromoterApply:remove')")
+    @Log(title = "推广员申请", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{applyIds}")
+    public AjaxResult remove(@PathVariable Long[] applyIds)
+    {
+        return toAjax(fsUserPromoterApplyService.deleteFsUserPromoterApplyByIds(applyIds));
+    }
+}

+ 63 - 0
fs-admin-agent/src/main/java/com/fs/admin/controller/store/SysOperlogScrmBridgeController.java

@@ -0,0 +1,63 @@
+package com.fs.admin.controller.store;
+
+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;
+import com.fs.hisStore.domain.SysOperLogScrm;
+import com.fs.hisStore.param.StoreOperMainQueryParam;
+import com.fs.hisStore.service.ISysOperLogScrmService;
+import com.fs.hisStore.vo.StoreOperMainVO;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+/**
+ * 操作日志 Bridge Controller (fs-admin端)
+ * 桥接 hisStore.SysOperlogScrmController,供平台总后台跨租户管理
+ *
+ * @author fs
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/store/operlogScrm")
+public class SysOperlogScrmBridgeController extends BaseController
+{
+    private final ISysOperLogScrmService operLogService;
+
+    public SysOperlogScrmBridgeController(ISysOperLogScrmService operLogService) {
+        this.operLogService = operLogService;
+    }
+
+    @PreAuthorize("@ss.hasPermi('his:storeLog:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SysOperLogScrm operLog)
+    {
+        startPage();
+        List<SysOperLogScrm> list = operLogService.selectOperLogList(operLog);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('his:storeLog:list')")
+    @GetMapping("/getMains")
+    public R getMains(StoreOperMainQueryParam operLog)
+    {
+        List<StoreOperMainVO> list = operLogService.getMains(operLog);
+        return R.ok().put("data", list);
+    }
+
+    @Log(title = "操作日志", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('his:storeLog:export')")
+    @GetMapping("/export")
+    public AjaxResult export(SysOperLogScrm operLog)
+    {
+        List<SysOperLogScrm> list = operLogService.selectOperLogList(operLog);
+        ExcelUtil<SysOperLogScrm> util = new ExcelUtil<>(SysOperLogScrm.class);
+        return util.exportExcel(list, "操作日志");
+    }
+}

+ 262 - 0
fs-admin-agent/src/main/java/com/fs/admin/sync/LobsterBridgeDataSyncService.java

@@ -0,0 +1,262 @@
+package com.fs.admin.sync;
+
+import com.fs.framework.datasource.DynamicDataSourceContextHolder;
+import com.fs.framework.datasource.TenantDataSourceManager;
+import com.fs.common.enums.DataSourceType;
+import com.fs.proxy.service.TenantModuleUsageService;
+import com.fs.tenant.domain.TenantInfo;
+import com.fs.tenant.service.TenantInfoService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 桥接数据同步服务
+ * <p>
+ * 解决 AdminLobsterBridgeController 从 ylrz_saas 桥接镜像表读取时数据为空的问题。
+ * 定时从各租户库全量同步 lobster_* 等表到 ylrz_saas 桥接镜像表。
+ * 同时每日执行租户模块使用统计。
+ * <p>
+ * 镜像表在 ylrz_saas 中有额外的 company_id 字段用于区分租户数据。
+ * 同步策略:每10分钟全量清空+重写(数据量小时可接受)。
+ */
+@Component
+public class LobsterBridgeDataSyncService {
+
+    private static final Logger log = LoggerFactory.getLogger(LobsterBridgeDataSyncService.class);
+
+    /** 需要从租户库同步到 ylrz_saas 镜像的龙虾引擎核心表 */
+    private static final String[] SYNC_TABLES = {
+        "lobster_system_prompt",
+        "lobster_optimization_suggestion",
+        "lobster_sales_corpus",
+        "lobster_api_registry",
+        "lobster_dead_letter",
+        "lobster_event_node_audit",
+        "lobster_chat_aggregate",
+        "lobster_model_config",
+        "lobster_billing_record",
+        "ai_generation_record",
+        "workflow_template",
+        "workflow_instance"
+    };
+
+    /** 需要从租户库同步到 ylrz_saas 镜像的 HIS/store 业务表 */
+    private static final String[] SYNC_TABLES_HIS = {
+        "fs_export_task",
+        "fs_health_record",
+        "fs_inquiry_order",
+        "fs_package_order",
+        "fs_prescribe",
+        "fs_inquiry_order_report"
+    };
+
+    @Autowired
+    private JdbcTemplate jdbcTemplate;
+
+    @Autowired(required = false)
+    private TenantDataSourceManager tenantDataSourceManager;
+
+    @Autowired(required = false)
+    private TenantInfoService tenantInfoService;
+
+    @Autowired(required = false)
+    private TenantModuleUsageService tenantModuleUsageService;
+
+    /**
+     * 每日凌晨2点执行租户模块使用统计
+     */
+    @Scheduled(cron = "0 0 2 * * ?")
+    public void executeModuleUsageStatistics() {
+        if (tenantModuleUsageService == null) {
+            log.debug("[ModuleUsage] TenantModuleUsageService 不可用,跳过统计");
+            return;
+        }
+        log.info("[ModuleUsage] 开始每日模块使用统计...");
+        long start = System.currentTimeMillis();
+        try {
+            tenantModuleUsageService.executeDailyStatistics();
+            log.info("[ModuleUsage] 每日模块使用统计完成, 耗时{}ms", System.currentTimeMillis() - start);
+        } catch (Exception e) {
+            log.error("[ModuleUsage] 每日模块使用统计失败", e);
+        }
+    }
+
+    /**
+     * 每10分钟执行一次全量同步
+     */
+    @Scheduled(fixedDelay = 600000, initialDelay = 30000)
+    public void syncLobsterBridgeData() {
+        if (tenantDataSourceManager == null || tenantInfoService == null) {
+            log.debug("[LobsterSync] 多租户组件不可用,跳过同步");
+            return;
+        }
+
+        log.info("[LobsterSync] 开始龙虾桥接表数据同步...");
+        long startTime = System.currentTimeMillis();
+        int syncedTenants = 0;
+        int totalRows = 0;
+
+        try {
+            // 1. 查所有启用的租户(在主库 ylrz_saas 上)
+            DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+            TenantInfo query = new TenantInfo();
+            query.setStatus(1);
+            List<TenantInfo> tenants = tenantInfoService.selectTenantInfoList(query);
+
+            if (tenants == null || tenants.isEmpty()) {
+                log.debug("[LobsterSync] 无启用租户,跳过同步");
+                return;
+            }
+
+            // 过滤未过期租户
+            Date now = new Date();
+            List<TenantInfo> validTenants = tenants.stream()
+                .filter(t -> t.getExpireTime() == null || !t.getExpireTime().before(now))
+                .collect(java.util.stream.Collectors.toList());
+
+            for (TenantInfo tenant : validTenants) {
+                try {
+                    int rows = syncTenantData(tenant);
+                    totalRows += rows;
+                    syncedTenants++;
+                } catch (Exception e) {
+                    log.error("[LobsterSync] 租户 tenantId={} 同步异常", tenant.getId(), e);
+                } finally {
+                    // 切回主库
+                    DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+                    if (tenantDataSourceManager != null) {
+                        tenantDataSourceManager.clear();
+                    }
+                }
+            }
+        } catch (Exception e) {
+            log.error("[LobsterSync] 同步过程异常", e);
+        } finally {
+            DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+            if (tenantDataSourceManager != null) {
+                tenantDataSourceManager.clear();
+            }
+        }
+
+        long elapsed = System.currentTimeMillis() - startTime;
+        log.info("[LobsterSync] 同步完成: {} 个租户, {} 行数据, 耗时 {}ms", syncedTenants, totalRows, elapsed);
+    }
+
+    /**
+     * 同步单个租户的所有桥接表数据
+     */
+    private int syncTenantData(TenantInfo tenant) {
+        int totalRows = 0;
+
+        // 切换到租户数据源
+        tenantDataSourceManager.switchTenant(tenant);
+        Long companyId = tenant.getId();
+
+        for (String table : SYNC_TABLES) {
+            try {
+                int rows = syncSingleTable(tenant, companyId, table);
+                totalRows += rows;
+            } catch (Exception e) {
+                log.warn("[LobsterSync] 租户 tenantId={}, 表 {} 同步失败: {}",
+                    tenant.getId(), table, e.getMessage());
+            }
+        }
+
+        for (String table : SYNC_TABLES_HIS) {
+            try {
+                int rows = syncSingleTable(tenant, companyId, table);
+                totalRows += rows;
+            } catch (Exception e) {
+                log.warn("[LobsterSync] 租户 tenantId={}, HIS表 {} 同步失败: {}",
+                    tenant.getId(), table, e.getMessage());
+            }
+        }
+
+        if (totalRows > 0) {
+            log.debug("[LobsterSync] 租户 tenantId={}, tenantName={} 同步 {} 行",
+                tenant.getId(), tenant.getTenantName(), totalRows);
+        }
+        return totalRows;
+    }
+
+    /**
+     * 同步单张表:先清空该租户在镜像表中的旧数据,再全量写入
+     */
+    private int syncSingleTable(TenantInfo tenant, Long companyId, String tableName) {
+        // 1. 从租户库查询数据(当前已在 tenant DS 上)
+        List<java.util.Map<String, Object>> rows;
+        try {
+            rows = jdbcTemplate.queryForList("SELECT * FROM " + tableName);
+        } catch (Exception e) {
+            // 表可能不存在于该租户库
+            return 0;
+        }
+
+        if (rows == null || rows.isEmpty()) {
+            return 0;
+        }
+
+        // 2. 回到主库,先删除该租户的旧数据
+        DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+
+        try {
+            jdbcTemplate.update("DELETE FROM " + tableName + " WHERE company_id = ?", companyId);
+        } catch (Exception e) {
+            // 镜像表可能没有 company_id 字段(旧表),忽略
+            log.debug("[LobsterSync] 删除镜像旧数据失败(表可能无company_id): {} {}", tableName, e.getMessage());
+        }
+
+        // 3. 将每行数据 INSERT 到主库镜像表
+        int inserted = 0;
+        for (java.util.Map<String, Object> row : rows) {
+            try {
+                insertRowToMirror(tableName, row, companyId);
+                inserted++;
+            } catch (Exception e) {
+                // 逐行插入失败不影响其他行
+                log.debug("[LobsterSync] 插入行失败: table={}, err={}", tableName, e.getMessage());
+            }
+        }
+
+        return inserted;
+    }
+
+    /**
+     * 将单行数据插入主库镜像表,自动添加 company_id
+     */
+    private void insertRowToMirror(String tableName, java.util.Map<String, Object> row, Long companyId) {
+        StringBuilder columns = new StringBuilder();
+        StringBuilder values = new StringBuilder();
+        java.util.List<Object> params = new java.util.ArrayList<>();
+
+        // 先确保 company_id 字段
+        columns.append("company_id");
+        values.append("?");
+        params.add(companyId);
+
+        for (java.util.Map.Entry<String, Object> entry : row.entrySet()) {
+            String col = entry.getKey();
+            // 跳过可能已存在的 company_id(使用我们统一的 companyId)
+            if ("company_id".equalsIgnoreCase(col)) {
+                continue;
+            }
+            if (columns.length() > 0) {
+                columns.append(",");
+                values.append(",");
+            }
+            columns.append("`").append(col).append("`");
+            values.append("?");
+            params.add(entry.getValue());
+        }
+
+        String sql = "INSERT INTO " + tableName + " (" + columns + ") VALUES (" + values + ")";
+        jdbcTemplate.update(sql, params.toArray());
+    }
+}

+ 41 - 0
fs-admin-agent/src/main/java/com/fs/admin/vo/ConsumeRecordExportVO.java

@@ -0,0 +1,41 @@
+package com.fs.admin.vo;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 消费记录导出VO
+ */
+@Data
+public class ConsumeRecordExportVO {
+
+    @Excel(name = "ID")
+    private Long id;
+
+    @Excel(name = "租户ID")
+    private Long tenantId;
+
+    @Excel(name = "租户名称")
+    private String tenantName;
+
+    @Excel(name = "消费金额")
+    private BigDecimal amount;
+
+    @Excel(name = "消费后余额")
+    private BigDecimal balanceAfter;
+
+    @Excel(name = "消费类型")
+    private String eventType;
+
+    @Excel(name = "业务ID")
+    private String bizId;
+
+    @Excel(name = "消费时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date occurredAt;
+
+    @Excel(name = "备注")
+    private String remark;
+}

+ 41 - 0
fs-admin-agent/src/main/java/com/fs/admin/vo/RechargeRecordExportVO.java

@@ -0,0 +1,41 @@
+package com.fs.admin.vo;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 充值记录导出VO
+ */
+@Data
+public class RechargeRecordExportVO {
+
+    @Excel(name = "ID")
+    private Long id;
+
+    @Excel(name = "租户ID")
+    private Long tenantId;
+
+    @Excel(name = "租户名称")
+    private String tenantName;
+
+    @Excel(name = "充值金额")
+    private BigDecimal amount;
+
+    @Excel(name = "充值后余额")
+    private BigDecimal balanceAfter;
+
+    @Excel(name = "支付方式")
+    private String payMethod;
+
+    @Excel(name = "操作人")
+    private String createBy;
+
+    @Excel(name = "充值时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    @Excel(name = "备注")
+    private String remark;
+}

+ 183 - 0
fs-admin-agent/src/main/java/com/fs/aiSoundReplication/VoiceCloneController.java

@@ -0,0 +1,183 @@
+package com.fs.aiSoundReplication;
+
+import com.fs.aiSoundReplication.param.StatusResponse;
+import com.fs.aiSoundReplication.param.TtsRequest;
+import com.fs.aiSoundReplication.param.TtsResponse;
+import com.fs.aiSoundReplication.param.UploadResponse;
+import com.fs.aiSoundReplication.service.TtsService;
+import com.fs.aiSoundReplication.service.VoiceCloneService;
+import com.fs.common.core.domain.R;
+import com.fs.fastgptApi.vo.AudioVO;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.context.annotation.Profile;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import java.io.File;
+import java.util.List;
+import java.util.UUID;
+
+@Profile({"admin", "company"})
+@RestController
+@RequestMapping("/api/voice-clone")
+@Api(tags = "声音复刻API")
+public class VoiceCloneController {
+
+    @Autowired
+    private VoiceCloneService voiceCloneService;
+    @Autowired
+    private TtsService ttsService;
+
+    @PostMapping("/synthesize")
+    @ApiOperation("文本转语音")
+    public AudioVO synthesize(
+            @ApiParam(value = "TTS请求参数", required = true)
+            @RequestBody TtsRequest request) {
+        return ttsService.textToSpeech(request);
+    }
+
+    @PostMapping("/synthesize-simple")
+    @ApiOperation("简化版文本转语音")
+    public AudioVO synthesizeSimple(
+            @ApiParam(value = "要合成的文本", required = true)
+            @RequestParam String text,
+            @ApiParam(value = "音色ID", required = true)
+            @RequestParam String voiceType,
+            @ApiParam(value = "音频格式")
+            @RequestParam(required = false, defaultValue = "mp3") String format,
+            @ApiParam(value = "语速 (0-15)")
+            @RequestParam(required = false, defaultValue = "1") Integer speed
+    ) {
+
+        TtsRequest request = new TtsRequest(
+                "", "", voiceType, text); // AppID和Token会在Service中设置
+        request.setReqId(UUID.randomUUID().toString());
+        request.setFormat(format);
+        request.setSpeed(speed);
+        return ttsService.textToSpeech(request);
+    }
+
+//    @PostMapping("/synthesize-and-download")
+//    @ApiOperation("文本转语音并下载")
+//    public R synthesizeAndDownload(
+//            @ApiParam(value = "要合成的文本", required = true)
+//            @RequestParam String text,
+//            @ApiParam(value = "音色ID", required = true)
+//            @RequestParam String voiceType,
+//            HttpServletRequest httpRequest) {
+//
+//        TtsRequest ttsRequest = new TtsRequest("", "", voiceType, text);
+//        ttsRequest.setReqId(UUID.randomUUID().toString());
+//
+//        String url = ttsService.textToSpeechStream(ttsRequest);
+//
+//        return R.ok();
+//    }
+
+//    @PostMapping("/batch-synthesize")
+//    @ApiOperation("批量文本转语音")
+//    public ResponseEntity<List<File>> batchSynthesize(
+//            @ApiParam(value = "文本列表", required = true)
+//            @RequestBody List<String> texts,
+//            @ApiParam(value = "音色ID", required = true)
+//            @RequestParam String voiceType,
+//            @ApiParam(value = "是否打包下载")
+//            @RequestParam(required = false, defaultValue = "false") Boolean zip) {
+//
+//        List<File> audioFiles = ttsService.batchTextToSpeech(texts, voiceType);
+//
+//        if (zip && !audioFiles.isEmpty()) {
+//            // 这里可以添加ZIP打包逻辑
+//            // 返回ZIP文件的ResponseEntity
+//        }
+//
+//        return ResponseEntity.ok(audioFiles);
+//    }
+
+//    @PostMapping("/synthesize-with-params")
+//    @ApiOperation("带参数的文本转语音")
+//    public TtsResponse synthesizeWithParams(
+//            @ApiParam(value = "音色ID", required = true) @RequestParam String voiceType,
+//            @ApiParam(value = "文本内容", required = true) @RequestParam String text,
+//            @ApiParam(value = "语速 (0-15)") @RequestParam(required = false) Integer speed,
+//            @ApiParam(value = "音量 (0-15)") @RequestParam(required = false) Integer volume,
+//            @ApiParam(value = "音高 (0-15)") @RequestParam(required = false) Integer pitch,
+//            @ApiParam(value = "情感参数") @RequestParam(required = false) String emotion,
+//            @ApiParam(value = "说话风格") @RequestParam(required = false) String speakingStyle) {
+//
+//        TtsRequest request = new TtsRequest("", "", voiceType, text);
+//        request.setReqId(UUID.randomUUID().toString());
+//
+//        if (speed != null) request.setSpeed(speed);
+//        if (volume != null) request.setVolume(volume);
+//        if (pitch != null) request.setPitch(pitch);
+//        if (emotion != null) request.setEmotion(emotion);
+//        if (speakingStyle != null) request.setSpeakingStyle(speakingStyle);
+//
+//        return ttsService.textToSpeech(request);
+//    }
+
+    private String getContentType(String format) {
+        switch (format.toLowerCase()) {
+            case "mp3":
+                return "audio/mpeg";
+            case "wav":
+                return "audio/wav";
+            case "pcm":
+                return "audio/L16";
+            default:
+                return "application/octet-stream";
+        }
+    }
+    @PostMapping("/upload")
+    @ApiOperation("上传音频训练音色")
+    public R uploadVoice(
+            @ApiParam(value = "音色ID", required = true) @RequestParam String speakerId,
+            @ApiParam(value = "音频文件", required = true) @RequestParam MultipartFile audioFile,
+            @ApiParam(value = "模型类型(1-ICL1.0, 4-ICL2.0)", defaultValue = "4")
+            @RequestParam(required = false) Integer modelType,
+            @ApiParam(value = "语种(0-中文, 1-英文)", defaultValue = "0")
+            @RequestParam(required = false) Integer language) {
+        return voiceCloneService.uploadVoice(speakerId, audioFile, modelType, language);
+    }
+
+    @GetMapping("/status/{speakerId}")
+    @ApiOperation("查询音色训练状态")
+    public StatusResponse getTrainingStatus(
+            @ApiParam(value = "音色ID", required = true)
+            @PathVariable String speakerId) {
+        return voiceCloneService.queryTrainingStatus(speakerId);
+    }
+
+//    @PostMapping("/upload-and-wait")
+//    @ApiOperation("上传并等待训练完成")
+//    public StatusResponse uploadAndWait(
+//            @ApiParam(value = "音色ID", required = true) @RequestParam String speakerId,
+//            @ApiParam(value = "音频文件", required = true) @RequestParam MultipartFile audioFile,
+//            @ApiParam(value = "模型类型", defaultValue = "4")
+//            @RequestParam(required = false) Integer modelType,
+//            @ApiParam(value = "语种", defaultValue = "0")
+//            @RequestParam(required = false) Integer language,
+//            @ApiParam(value = "最大等待时间(秒)", defaultValue = "600")
+//            @RequestParam(required = false) Integer maxWaitSeconds) {
+//
+//        // 1. 上传音频
+//        UploadResponse uploadResponse = voiceCloneService.uploadVoice(
+//                speakerId, audioFile, modelType, language);
+//
+//        // 2. 计算轮询参数
+//        int maxPollingTimes = maxWaitSeconds != null ? maxWaitSeconds * 1000 / 10000 : 60;
+//
+//        // 3. 轮询训练状态
+//        return voiceCloneService.pollTrainingStatus(
+//                uploadResponse.getSpeakerId(), maxPollingTimes, 10000L);
+//    }
+}

+ 881 - 0
fs-admin-agent/src/main/java/com/fs/api/controller/IndexStatisticsController.java

@@ -0,0 +1,881 @@
+package com.fs.api.controller;
+
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.entity.SysDept;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.company.constant.CompanyTrafficConstants;
+import com.fs.company.domain.Company;
+import com.fs.company.service.ICompanyService;
+import com.fs.his.utils.ConfigUtil;
+import com.fs.hisStore.config.MedicalMallConfig;
+import com.fs.statis.StatisticsRedisConstant;
+import com.fs.statis.dto.*;
+import com.fs.statis.param.StatisticsDeptCompanyParam;
+import com.fs.statis.service.IStatisticsService;
+import com.fs.system.domain.SysConfig;
+import com.fs.system.service.ISysConfigService;
+import com.fs.system.service.ISysDeptService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.YearMonth;
+import java.util.*;
+import java.util.function.BinaryOperator;
+import java.util.stream.Collectors;
+
+import static com.fs.statis.StatisticsRedisConstant.*;
+
+/**
+ * 首页-统计
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/index/statistics")
+public class IndexStatisticsController {
+    private static final Logger log = LoggerFactory.getLogger(IndexStatisticsController.class);
+    @Autowired
+    private RedisCache redisCache;
+
+    @Autowired
+    private ISysConfigService sysConfigService;
+
+    @Autowired
+    private ConfigUtil configUtil;
+
+    @Autowired
+    private ISysDeptService deptService;
+
+    @Autowired
+    private ICompanyService companyService;
+
+    @Autowired
+    private MedicalMallConfig medicalMallConfig;
+
+    @Autowired
+    private IStatisticsService statisticsService;
+    /**
+     * 分析概览
+     */
+    @PostMapping("/analysisPreview")
+    public R analysisPreview(@RequestBody AnalysisPreviewQueryDTO param){
+        AnalysisPreviewDTO analysisPreviewDTO = new AnalysisPreviewDTO();
+        Integer type = param.getType();
+        Integer userType = param.getUserType();
+
+        if(type == null) {
+            type = 0;
+        }
+
+        if(userType == null) {
+            userType = 0;
+        }
+        if(medicalMallConfig.isStatics()|| (param.getCompanyId() == null && param.getDeptId() == null) || (param.getCompanyId() == null && param.getDeptId() == 1)){
+            analysisPreviewDTO = redisCache.getCacheObject(String.format("%s:%d:%d",DATA_OVERVIEW_DEALER_ANALYSISPREVIEW,type,userType));
+        }else if(param.getCompanyId() != null){
+            analysisPreviewDTO = redisCache.getCacheObject(String.format("%s:%d:%d:%d",DATA_OVERVIEW_DEALER_ANALYSISPREVIEW,type,userType,param.getCompanyId()));
+        }else{
+            Company company = new Company();
+            company.setDeptId(param.getDeptId());
+            Long[] companyIds = companyService.selectCompanyList(company).stream().map(Company::getCompanyId).toArray(Long[]::new);
+            for(Long companyId : companyIds){
+                AnalysisPreviewDTO clildDTO = redisCache.getCacheObject(String.format("%s:%d:%d:%d",DATA_OVERVIEW_DEALER_ANALYSISPREVIEW,type,userType,companyId));
+                if (clildDTO != null) {
+                    //将除了completedRate、correctRate和watchRate之外的数据相加,并计算completedRate、correctRate和watchRate
+                    analysisPreviewDTO.setWatchUserCount(
+                            analysisPreviewDTO.getWatchUserCount() == 0 ? 0 : analysisPreviewDTO.getWatchUserCount()
+                                +(clildDTO.getWatchUserCount() == 0 ? 0 : clildDTO.getWatchUserCount())
+                    );
+                    analysisPreviewDTO.setCompletedUserCount(
+                            analysisPreviewDTO.getCompletedUserCount() == 0 ? 0 : analysisPreviewDTO.getCompletedUserCount()
+                                +(clildDTO.getCompletedUserCount() == 0 ? 0 : clildDTO.getCompletedUserCount())
+                    );
+                    analysisPreviewDTO.setCompletedCount(
+                            analysisPreviewDTO.getCompletedCount() == 0 ? 0 : analysisPreviewDTO.getCompletedCount()
+                                +(clildDTO.getCompletedCount() == 0 ? 0 : clildDTO.getCompletedCount())
+                    );
+                    analysisPreviewDTO.setWatchCount(
+                            analysisPreviewDTO.getWatchCount() == 0 ? 0 : analysisPreviewDTO.getWatchCount()
+                                +(clildDTO.getWatchCount() == 0 ? 0 : clildDTO.getWatchCount())
+                    );
+                    analysisPreviewDTO.setAnswerMemberCount(
+                            analysisPreviewDTO.getAnswerMemberCount() == 0 ? 0 : analysisPreviewDTO.getAnswerMemberCount()
+                                +(clildDTO.getAnswerMemberCount() == 0 ? 0 : clildDTO.getAnswerMemberCount())
+                    );
+                    analysisPreviewDTO.setCorrectUserCount(
+                            analysisPreviewDTO.getCorrectUserCount() == 0 ? 0 : analysisPreviewDTO.getCorrectUserCount()
+                                +(clildDTO.getCorrectUserCount() == 0 ? 0 : clildDTO.getCorrectUserCount())
+                    );
+                    analysisPreviewDTO.setRewardCount(
+                            analysisPreviewDTO.getRewardCount() == 0 ? 0 : analysisPreviewDTO.getRewardCount()
+                                +(clildDTO.getRewardCount() == 0 ? 0 : clildDTO.getRewardCount())
+                    );
+                    analysisPreviewDTO.setRewardMoney(
+                            analysisPreviewDTO.getRewardMoney() == null ? BigDecimal.ZERO : analysisPreviewDTO.getRewardMoney()
+                                .add(clildDTO.getRewardMoney() == null ? BigDecimal.ZERO : clildDTO.getRewardMoney())
+                    );
+                }
+            }
+            if(analysisPreviewDTO.getAnswerMemberCount() != 0){
+                analysisPreviewDTO.setCorrectRate(
+                        analysisPreviewDTO.getCorrectUserCount() == 0 ? "0" : String.format("%.2f", analysisPreviewDTO.getCorrectUserCount() * 100.0 / analysisPreviewDTO.getAnswerMemberCount())
+                );
+            }
+            if(analysisPreviewDTO.getWatchUserCount() != 0){
+                analysisPreviewDTO.setCompletedRate(
+                        analysisPreviewDTO.getCompletedUserCount() == 0 ? "0" : String.format("%.2f", analysisPreviewDTO.getCompletedUserCount() * 100.0 / analysisPreviewDTO.getWatchUserCount())
+                );
+            }
+            if(analysisPreviewDTO.getWatchCount() != 0){
+                analysisPreviewDTO.setWatchRate(
+                        analysisPreviewDTO.getCompletedCount() == 0 ? "0" : String.format("%.2f", analysisPreviewDTO.getCompletedCount() * 100.0 / analysisPreviewDTO.getWatchCount())
+                );
+            }
+
+        }
+
+        return R.ok().put("data",analysisPreviewDTO);
+    }
+
+
+    /**
+     * 消费余额
+     */
+    @GetMapping("/rechargeComsumption")
+    public R rechargeComsumption(StatisticsDeptCompanyParam param){
+        ConsumptionBalanceDataDTO consumptionBalanceDataDTO = new ConsumptionBalanceDataDTO();
+        if(medicalMallConfig.isStatics() || (param.getCompanyId() == null && param.getDeptId() == null) || (param.getCompanyId() == null && param.getDeptId() == 1)) {
+            consumptionBalanceDataDTO = redisCache.getCacheObject(StatisticsRedisConstant.DATA_OVERVIEW_DEALER_BALANCE);
+        }else if(param.getCompanyId() != null){
+            consumptionBalanceDataDTO = redisCache.getCacheObject(String.format("%s:%d",DATA_OVERVIEW_DEALER_BALANCE,param.getCompanyId()));
+        }else{
+            Company company = new Company();
+            company.setDeptId(param.getDeptId());
+            Long[] companyIds = companyService.selectCompanyList(company).stream().map(Company::getCompanyId).toArray(Long[]::new);
+            for(Long companyId : companyIds){
+                ConsumptionBalanceDataDTO clildDTO = redisCache.getCacheObject(String.format("%s:%d",DATA_OVERVIEW_DEALER_BALANCE,companyId));
+                consumptionBalanceDataDTO.setBalance(
+                    (consumptionBalanceDataDTO.getBalance() == null ? BigDecimal.ZERO : consumptionBalanceDataDTO.getBalance())
+                    .add(clildDTO.getBalance() == null ? BigDecimal.ZERO : clildDTO.getBalance())
+                );
+                consumptionBalanceDataDTO.setTodayComsumption(
+                    (consumptionBalanceDataDTO.getTodayComsumption() == null ? BigDecimal.ZERO : consumptionBalanceDataDTO.getTodayComsumption())
+                    .add(clildDTO.getTodayComsumption() == null ? BigDecimal.ZERO : clildDTO.getTodayComsumption())
+                );
+                consumptionBalanceDataDTO.setYesterdayComsumption(
+                    (consumptionBalanceDataDTO.getYesterdayComsumption() == null ? BigDecimal.ZERO : consumptionBalanceDataDTO.getYesterdayComsumption())
+                    .add(clildDTO.getYesterdayComsumption() == null ? BigDecimal.ZERO : clildDTO.getYesterdayComsumption())
+                );
+            }
+        }
+        BigDecimal redPacketCompanyMoney = redisCache.getCacheObject("redpacket_money");
+        if(ObjectUtils.isNull(redPacketCompanyMoney)){
+            redPacketCompanyMoney = BigDecimal.ZERO;
+        }
+        if (consumptionBalanceDataDTO != null){
+            consumptionBalanceDataDTO.setRunTianBalance(redPacketCompanyMoney);
+
+        }
+        return R.ok().put("data", consumptionBalanceDataDTO);
+    }
+
+    /**
+     * 获取统计流量
+     * @return
+     */
+    @GetMapping("/trafficLog")
+    public R getTrafficLog(StatisticsDeptCompanyParam  param){
+        TrafficLogDTO result = new TrafficLogDTO();
+        if(!medicalMallConfig.isStatics() || (param.getCompanyId() == null && param.getDeptId() == null) || (param.getCompanyId() == null && param.getDeptId() == 1)) {
+            result = redisCache.getCacheObject(DATA_OVERVIEW_TRAFFIC_LOG);
+            if (!medicalMallConfig.isStatics()) {
+                SysConfig sysConfig = sysConfigService.selectConfigByConfigKey("redPacket.Traffic.config");
+                if(result == null || sysConfig == null) {
+                    return null;
+                }
+                String configValue = sysConfig.getConfigValue();
+                result.setTraffic(configValue);
+            } else {
+                //所有部门流量之和
+                SysDept dept = new SysDept();
+                dept.setParentId(1L);
+                Long[] deptIds = deptService.selectDeptList(dept).stream().map(SysDept::getDeptId).toArray(Long[]::new);
+                long trafficTemp = 0L;
+                for(Long deptId : deptIds){
+                    Object clildTraffic = redisCache.getCacheObject(CompanyTrafficConstants.CACHE_KEY+":"+deptId);
+                    if(clildTraffic != null){
+                        trafficTemp += Long.parseLong(clildTraffic.toString());
+                    }
+                }
+                result.setTraffic(String.format("%.2f",trafficTemp * 1024.0));
+            }
+        }else if(param.getCompanyId() != null){
+            Company company = companyService.selectCompanyById(param.getCompanyId());
+            getTrafficLogResult(result,CompanyTrafficConstants.CACHE_KEY+":"+company.getDeptId()+":"+param.getCompanyId());
+        }else{
+            getTrafficLogResult(result,CompanyTrafficConstants.CACHE_KEY+":"+param.getDeptId());
+        }
+        return R.ok().put("data",result);
+    }
+
+    private void getTrafficLogResult(TrafficLogDTO result, String key){
+        //昨天
+        LocalDate yesterday = LocalDate.now().minusDays(1);
+        Object yesterdayCount = redisCache.getCacheObject(key+":"+yesterday);
+        Object todayCount = redisCache.getCacheObject(key+":"+LocalDate.now());
+        Object thisMonthCount = redisCache.getCacheObject(key+":"+YearMonth.now());
+        Object traffic = redisCache.getCacheObject(key);
+        result.setYesterday(parseRedisNumberValueToLong(yesterdayCount));
+        //今天
+        result.setToday(parseRedisNumberValueToLong(todayCount));
+        //本月
+        result.setThisMonth(parseRedisNumberValueToLong(thisMonthCount));
+        //剩余
+        result.setTraffic(traffic == null?"0":traffic.toString());
+    }
+
+    private Long parseRedisNumberValueToLong(Object value){
+        return value == null ? 0 : Long.parseLong(value.toString());
+    }
+
+    /**
+     * 观看趋势
+     */
+    @PostMapping("/watchEndPlayTrend")
+    public R watchEndPlayTrend(@RequestBody AnalysisPreviewQueryDTO param){
+        Integer type = param.getType();
+        Integer userType = param.getUserType();
+
+        if(type == null) {
+            type = 0;
+        }
+        if(userType == null){
+            userType = 0;
+        }
+        List<WatchEndPlayTrendDTO> watchEndPlayTrendDTOS;
+        // 参考watchCourseTopTen方法的处理逻辑
+        if (!medicalMallConfig.isStatics() || (param.getCompanyId() == null && param.getDeptId() == null) || (param.getCompanyId() == null && param.getDeptId() == 1)){
+            String key = String.format("%s:%d:%d", DATA_OVERVIEW_DEALER_CHARTS, type,userType);
+            watchEndPlayTrendDTOS = redisCache.getCacheObject(key);
+        }else if(param.getCompanyId() != null){
+            String key = String.format("%s:%d:%d:%d", DATA_OVERVIEW_DEALER_CHARTS, type,userType,param.getCompanyId());
+            watchEndPlayTrendDTOS = redisCache.getCacheObject(key);
+        }else{
+            Company company = new Company();
+            company.setDeptId(param.getDeptId());
+            Long[] companyIds = companyService.selectCompanyList(company).stream().map(Company::getCompanyId).toArray(Long[]::new);
+            List<WatchEndPlayTrendDTO> tempDTOS = new ArrayList<>();
+            for(Long companyId : companyIds){
+                String key = String.format("%s:%d:%d:%d", DATA_OVERVIEW_DEALER_CHARTS, type,userType,companyId);
+                List<WatchEndPlayTrendDTO> companyData = redisCache.getCacheObject(key);
+                if (companyData != null) {
+                    tempDTOS.addAll(companyData);
+                }
+            }
+            // 根据startDate 和 x 分组,合并watchUserCount和completedUserCount 限制最多返回10条记录
+            watchEndPlayTrendDTOS = tempDTOS.stream()
+                    .collect(Collectors.groupingBy(
+                            dto -> dto.getStartDate() + ":" + dto.getX(),  // 根据startDate和x分组
+                            Collectors.reducing(new WatchEndPlayTrendDTO(), (dto1, dto2) -> {
+                                // 合并watchUserCount和completedUserCount
+                                WatchEndPlayTrendDTO result = new WatchEndPlayTrendDTO();
+                                // 复制分组标识字段
+                                if (dto2 != null && dto2.getStartDate() != null) {
+                                    result.setStartDate(dto2.getStartDate());
+                                } else if (dto1 != null) {
+                                    result.setStartDate(dto1.getStartDate());
+                                }
+
+                                if (dto2 != null && dto2.getX() != null) {
+                                    result.setX(dto2.getX());
+                                } else if (dto1 != null) {
+                                    result.setX(dto1.getX());
+                                }
+
+                                // 合并数值字段
+                                result.setWatchUserCount(
+                                        (dto1 == null || dto1.getWatchUserCount() == null ? 0 : dto1.getWatchUserCount()) +
+                                                (dto2 == null || dto2.getWatchUserCount() == null ? 0 : dto2.getWatchUserCount())
+                                );
+
+                                result.setCompletedUserCount(
+                                        (dto1 == null || dto1.getCompletedUserCount() == null ? 0 : dto1.getCompletedUserCount()) +
+                                                (dto2 == null || dto2.getCompletedUserCount() == null ? 0 : dto2.getCompletedUserCount())
+                                );
+
+                                return result;
+                            })
+                    ))
+                    .values()
+                    .stream()
+                    .filter(Objects::nonNull)  // 过滤掉null值
+                    .limit(10)
+                    .sorted(Comparator.comparing(WatchEndPlayTrendDTO::getX))
+                    .collect(Collectors.toList());
+        }
+
+        if(watchEndPlayTrendDTOS == null){
+            watchEndPlayTrendDTOS = new ArrayList<>();
+        }
+
+        return R.ok().put("data", watchEndPlayTrendDTOS);
+    }
+
+    /**
+     * 经销商会员观看
+     */
+    @PostMapping("/deaMemberTopTen")
+    public R deaMemberTopTen(@RequestBody AnalysisPreviewQueryDTO param){
+        Integer type = param.getType();
+        Integer statisticalType = param.getStatisticalType();
+        Integer userType = param.getUserType();
+
+        if(type == null) {
+            type = 0;
+        }
+        if(userType == null){
+            userType = 0;
+        }
+        List<DeaMemberTopTenDTO> deaMemberTopTenDTOS = new ArrayList<>();
+        // 参考deaMemberTopTen方法处理逻辑
+        if (!medicalMallConfig.isStatics() || (param.getCompanyId() == null && param.getDeptId() == null) || (param.getCompanyId() == null && param.getDeptId() == 1)){
+            String key = String.format("%s:%d:%d:%d", CHARTS_MEMBER_TOP_TEN_WATCH, type, statisticalType,userType);
+            deaMemberTopTenDTOS =  redisCache.getCacheObject(key);
+        }else if(param.getCompanyId() != null){
+            String key = String.format("%s:%d:%d:%d:%d", CHARTS_MEMBER_TOP_TEN_WATCH, type, statisticalType,userType,param.getCompanyId());
+            deaMemberTopTenDTOS = redisCache.getCacheObject(key);
+        }else{
+            Company company = new Company();
+            company.setDeptId(param.getDeptId());
+            Long[] companyIds = companyService.selectCompanyList(company).stream().map(Company::getCompanyId).toArray(Long[]::new);
+            List<DeaMemberTopTenDTO> tempDTOS = new ArrayList<>();
+            for(Long companyId : companyIds){
+                String key = String.format("%s:%d:%d:%d:%d", CHARTS_MEMBER_TOP_TEN_WATCH, type, statisticalType,userType,companyId);
+                List<DeaMemberTopTenDTO> companyData = redisCache.getCacheObject(key);
+                if (companyData != null) {
+                    tempDTOS.addAll(companyData);
+                }
+            }
+            // companyId 和 companyName 分组,合并watchUserCount 限制最多返回10条记录
+            deaMemberTopTenDTOS = tempDTOS.stream()
+                    .collect(Collectors.groupingBy(
+                            dto -> dto.getCompanyId() + ":" + dto.getCompanyName(),  // 根据companyId和companyName分组
+                            Collectors.reducing(new DeaMemberTopTenDTO(), (dto1, dto2) -> {
+                                DeaMemberTopTenDTO result = new DeaMemberTopTenDTO();
+                                result.setCompanyId(dto1.getCompanyId());
+                                result.setCompanyName(dto1.getCompanyName());
+                                result.setWatchUserCount(
+                                        (dto1.getWatchUserCount() == null ? 0 : dto1.getWatchUserCount()) +
+                                                (dto2 == null || dto2.getWatchUserCount() == null ? 0 : dto2.getWatchUserCount())
+                                );
+                                return result;
+                            })
+                    ))
+                    .values()
+                    .stream()
+                    .filter(Objects::nonNull)  // 过滤掉null值
+                    .limit(10)
+                    .sorted(Comparator.comparing(DeaMemberTopTenDTO::getWatchUserCount, Comparator.nullsLast(Comparator.reverseOrder())))
+                    .collect(Collectors.toList());
+        }
+
+        if(deaMemberTopTenDTOS == null){
+            deaMemberTopTenDTOS = new ArrayList<>();
+        }
+        return R.ok().put("data", deaMemberTopTenDTOS);
+    }
+
+    /**
+     * 奖励金额top10
+     */
+    @PostMapping("/rewardMoneyTopTen")
+    public R rewardMoneyTopTen(@RequestBody AnalysisPreviewQueryDTO param){
+        Integer type = param.getType();
+        Integer dataType = param.getDataType();
+        Integer userType = param.getUserType();
+        List<RewardMoneyTopTenDTO> rewardMoneyTopTenDTOS = new ArrayList<>();
+        // 参考rewardMoneyTopTen方法处理逻辑
+        if(!medicalMallConfig.isStatics() || (param.getCompanyId() == null && param.getDeptId() == null) || (param.getCompanyId() == null && param.getDeptId() == 1)){
+            String key = String.format("%s:%d:%d:%d", CHARTS_REWARD_MONEY_TOP_TEN, type,dataType,userType);
+            rewardMoneyTopTenDTOS = redisCache.getCacheObject(key);
+        }else if(param.getCompanyId() != null){
+            String key = String.format("%s:%d:%d:%d:%d", CHARTS_REWARD_MONEY_TOP_TEN, type,dataType,userType,param.getCompanyId());
+            rewardMoneyTopTenDTOS = redisCache.getCacheObject(key);
+        }else{
+            Company company = new Company();
+            company.setDeptId(param.getDeptId());
+            Long[] companyIds = companyService.selectCompanyList(company).stream().map(Company::getCompanyId).toArray(Long[]::new);
+            List<RewardMoneyTopTenDTO> tempDTOS = new ArrayList<>();
+            for(Long companyId : companyIds){
+                String key = String.format("%s:%d:%d:%d:%d", CHARTS_REWARD_MONEY_TOP_TEN, type,dataType,userType,companyId);
+                List<RewardMoneyTopTenDTO> companyData = redisCache.getCacheObject(key);
+                if (companyData != null) {
+                    tempDTOS.addAll(companyData);
+                }
+            }
+            rewardMoneyTopTenDTOS = tempDTOS.stream()
+                    .collect(Collectors.groupingBy(
+                            dto -> dto.getCompanyId() + ":" + dto.getCompanyName(),  // 根据companyId和companyName分组
+                            Collectors.reducing(new RewardMoneyTopTenDTO(), (dto1, dto2) -> {
+                                RewardMoneyTopTenDTO result = new RewardMoneyTopTenDTO();
+                                result.setCompanyId(dto1.getCompanyId());
+                                result.setCompanyName(dto1.getCompanyName());
+                                result.setRewardMoney(
+                                        (dto1.getRewardMoney() == null ? BigDecimal.ZERO : dto1.getRewardMoney())
+                                        .add(
+                                                (dto2 == null || dto2.getRewardMoney() == null ? BigDecimal.ZERO : dto2.getRewardMoney())
+                                        )
+                                );
+                                return result;
+                            })
+                    ))
+                    .values()
+                    .stream()
+                    .filter(Objects::nonNull)  // 过滤掉null值
+                    .limit(10)
+                    .sorted(Comparator.comparing(RewardMoneyTopTenDTO::getRewardMoney, Comparator.nullsLast(Comparator.reverseOrder())))
+                    .collect(Collectors.toList());
+        }
+
+        return R.ok().put("data", rewardMoneyTopTenDTOS);
+    }
+
+    /**
+     * 答题红包金额趋势图
+     */
+    @PostMapping("/rewardMoneyTrend")
+    public R rewardMoneyTrend(@RequestBody AnalysisPreviewQueryDTO param){
+        Integer type = param.getType();
+        Integer userType = param.getUserType();
+        List<RewardMoneyTrendDTO> rewardMoneyTrendDTOS = new ArrayList<>();
+        // 参考rewardMoneyTrend方法处理逻辑
+        if(!medicalMallConfig.isStatics() || (param.getCompanyId() == null && param.getDeptId() == null) || (param.getCompanyId() == null && param.getDeptId() == 1)){
+            String key = String.format("%s:%d:%d", CHARTS_REWARD_MONEY_TREND, type,userType);
+            rewardMoneyTrendDTOS = redisCache.getCacheObject(key);
+        }else if(param.getCompanyId() != null){
+            String key = String.format("%s:%d:%d:%d", CHARTS_REWARD_MONEY_TREND, type,userType,param.getCompanyId());
+            rewardMoneyTrendDTOS = redisCache.getCacheObject(key);
+        }else{
+            Company company = new Company();
+            company.setDeptId(param.getDeptId());
+            Long[] companyIds = companyService.selectCompanyList(company).stream().map(Company::getCompanyId).toArray(Long[]::new);
+            List<RewardMoneyTrendDTO> tempDTOS = new ArrayList<>();
+            for(Long companyId : companyIds){
+                String key = String.format("%s:%d:%d:%d", CHARTS_REWARD_MONEY_TREND, type,userType,companyId);
+                List<RewardMoneyTrendDTO> companyData = redisCache.getCacheObject(key);
+                if (companyData != null) {
+                    tempDTOS.addAll(companyData);
+                }
+            }
+            //根据startDate和x分组,合并rewardMoney,根据x排序,限制10
+            rewardMoneyTrendDTOS = tempDTOS.stream()
+                    .collect(Collectors.groupingBy(dto -> dto.getStartDate() + ":" + dto.getX(),
+                            Collectors.reducing(new RewardMoneyTrendDTO(), (dto1, dto2) -> {
+                        RewardMoneyTrendDTO result = new RewardMoneyTrendDTO();
+                        result.setStartDate(dto1.getStartDate());
+                        result.setX(dto1.getX());
+                        result.setRewardMoney(
+                                (dto1.getRewardMoney() == null ? BigDecimal.ZERO : dto1.getRewardMoney())
+                                .add(
+                                        (dto2 == null || dto2.getRewardMoney() == null ? BigDecimal.ZERO : dto2.getRewardMoney())
+                                )
+                        );
+                        return result;
+                    })))
+                    .values()
+                    .stream()
+                    .sorted(Comparator.comparing(RewardMoneyTrendDTO::getX))
+                    .limit(10)
+                    .collect(Collectors.toList());
+        }
+        return R.ok().put("data", rewardMoneyTrendDTOS);
+    }
+
+    /**
+     * 课程观看top10
+     */
+    @PostMapping("/watchCourseTopTen")
+    public R watchCourseTopTen(@RequestBody AnalysisPreviewQueryDTO param){
+        Integer type = param.getType();
+        String sort = param.getSort();
+        Integer statisticalType = param.getStatisticalType();
+        Integer userType = param.getUserType();
+        List<CourseStatsDTO> courseStatsDTOS;
+        if (!medicalMallConfig.isStatics() || (param.getCompanyId() == null && param.getDeptId() == null) || (param.getCompanyId() == null && param.getDeptId() == 1)){
+            courseStatsDTOS = redisCache.getCacheObject(String.format("%s:%d:%d:%d:%s", CHARTS_WATCH_TOP_TEN, type,statisticalType,userType,sort));
+        }else if(param.getCompanyId() != null){
+            courseStatsDTOS = redisCache.getCacheObject(String.format("%s:%d:%d:%d:%s:%d", CHARTS_WATCH_TOP_TEN, type,statisticalType,userType,sort,param.getCompanyId()));
+        }else{
+            Company company = new Company();
+            company.setDeptId(param.getDeptId());
+            Long[] companyIds = companyService.selectCompanyList(company).stream().map(Company::getCompanyId).toArray(Long[]::new);
+            List<CourseStatsDTO> tempDTOS = new ArrayList<>();
+            for(Long companyId : companyIds){
+                List<CourseStatsDTO> companyDTO = redisCache.getCacheObject(String.format("%s:%d:%d:%d:%s:%d", CHARTS_WATCH_TOP_TEN, type,statisticalType,userType,sort,companyId));
+                if (companyDTO != null) {
+                    tempDTOS.addAll(companyDTO);
+                }
+            }
+            // courseId和courseName分组,合并watchUserCount、completedUserCount、answerUserCount、correctUserCount 限制最多返回10条记录
+            courseStatsDTOS = tempDTOS.stream()
+                    .collect(Collectors.groupingBy(dto -> dto.getCourseId() + ":" + dto.getCourseName()))
+                    .values()
+                    .stream()
+                    .map(group -> {
+                        if (group.isEmpty()) {
+                            return null;
+                        }
+
+                        // 取第一个作为基础对象
+                        CourseStatsDTO result = new CourseStatsDTO();
+                        CourseStatsDTO first = group.get(0);
+                        result.setCourseId(first.getCourseId());
+                        result.setCourseName(first.getCourseName());
+
+                        // 合并所有数值字段
+                        int watchUserCount = 0;
+                        int completedUserCount = 0;
+                        int answerUserCount = 0;
+                        int correctUserCount = 0;
+
+                        for (CourseStatsDTO dto : group) {
+                            watchUserCount += (dto.getWatchUserCount() == null ? 0 : dto.getWatchUserCount());
+                            completedUserCount += (dto.getCompletedUserCount() == null ? 0 : dto.getCompletedUserCount());
+                            answerUserCount += (dto.getAnswerUserCount() == null ? 0 : dto.getAnswerUserCount());
+                            correctUserCount += (dto.getCorrectUserCount() == null ? 0 : dto.getCorrectUserCount());
+                        }
+
+                        result.setWatchUserCount(watchUserCount);
+                        result.setCompletedUserCount(completedUserCount);
+                        result.setAnswerUserCount(answerUserCount);
+                        result.setCorrectUserCount(correctUserCount);
+
+                        return result;
+                    })
+                    .filter(Objects::nonNull)
+                    .sorted(Comparator.comparing(CourseStatsDTO::getWatchUserCount, Comparator.nullsLast(Comparator.reverseOrder())))
+                    .limit(10)
+                    .collect(Collectors.toList());
+
+        }
+
+        return R.ok().put("data", courseStatsDTOS);
+    }
+
+    private List<CourseStatsDTO> groupAndSumCourseStatsWithStream(List<CourseStatsDTO> courseStatsList) {
+        // 定义合并逻辑
+        BinaryOperator<CourseStatsDTO> mergeFunction = (s1, s2) -> {
+            s1.setWatchUserCount(
+                    (s1.getWatchUserCount() == null ? 0 : s1.getWatchUserCount()) +
+                            (s2.getWatchUserCount() == null ? 0 : s2.getWatchUserCount())
+            );
+
+            s1.setCompletedUserCount(
+                    (s1.getCompletedUserCount() == null ? 0 : s1.getCompletedUserCount()) +
+                            (s2.getCompletedUserCount() == null ? 0 : s2.getCompletedUserCount())
+            );
+
+            s1.setAnswerUserCount(
+                    (s1.getAnswerUserCount() == null ? 0 : s1.getAnswerUserCount()) +
+                            (s2.getAnswerUserCount() == null ? 0 : s2.getAnswerUserCount())
+            );
+
+            s1.setCorrectUserCount(
+                    (s1.getCorrectUserCount() == null ? 0 : s1.getCorrectUserCount()) +
+                            (s2.getCorrectUserCount() == null ? 0 : s2.getCorrectUserCount())
+            );
+
+            return s1;
+        };
+
+        // 分组、合并并排序、限制10条
+        return courseStatsList.stream()
+                .collect(Collectors.toMap(
+                        CourseStatsDTO::getCourseId,
+                        dto -> {
+                            CourseStatsDTO copy = new CourseStatsDTO();
+                            copy.setCourseId(dto.getCourseId());
+                            copy.setCourseName(dto.getCourseName());
+                            copy.setWatchUserCount(dto.getWatchUserCount() == null ? 0 : dto.getWatchUserCount());
+                            copy.setCompletedUserCount(dto.getCompletedUserCount() == null ? 0 : dto.getCompletedUserCount());
+                            copy.setAnswerUserCount(dto.getAnswerUserCount() == null ? 0 : dto.getAnswerUserCount());
+                            copy.setCorrectUserCount(dto.getCorrectUserCount() == null ? 0 : dto.getCorrectUserCount());
+                            return copy;
+                        },
+                        mergeFunction,
+                        LinkedHashMap::new
+                ))
+                .values()
+                .stream()
+                .sorted(Comparator.comparing(CourseStatsDTO::getWatchUserCount, Comparator.nullsLast(Comparator.reverseOrder())))
+                .limit(10)
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * 数据概览
+     */
+    @GetMapping("/dealerAggregated")
+    public R dealerAggregated(StatisticsDeptCompanyParam param){
+        DealerAggregatedDTO result = new DealerAggregatedDTO();
+        if (!medicalMallConfig.isStatics() || (param.getCompanyId() == null && param.getDeptId() == null) || (param.getCompanyId() == null && param.getDeptId() == 1)) {
+            result = redisCache.getCacheObject(StatisticsRedisConstant.DATA_OVERVIEW_DEALER_AGGREGATED);
+        }else if (param.getCompanyId() != null) {
+            result = redisCache.getCacheObject(String.format("%s:%d", DATA_OVERVIEW_DEALER_AGGREGATED, param.getCompanyId()));
+        //没选中销售公司,部门不为总公司
+        }else{
+            //Long padMaxNum = deptLimiteService.selectById(param.getDeptId()).getMaxPadNum();
+            Company company = new Company();
+            company.setDeptId(param.getDeptId());
+            Long[] companyIds = companyService.selectCompanyList(company).stream().map(Company::getCompanyId).toArray(Long[]::new);
+            for(Long companyId : companyIds) {
+                DealerAggregatedDTO dealerAggregatedDTO = redisCache.getCacheObject(String.format("%s:%d", DATA_OVERVIEW_DEALER_AGGREGATED, companyId));
+                // 添加空值检查
+                if (dealerAggregatedDTO != null) {
+                    result.setDealderCount((result.getDealderCount() == null ? 0 : result.getDealderCount())
+                            + (dealerAggregatedDTO.getDealderCount() == null ? 0 : dealerAggregatedDTO.getDealderCount()));
+                    result.setGroupMgrCount((result.getGroupMgrCount() == null ? 0 : result.getGroupMgrCount())
+                            + (dealerAggregatedDTO.getGroupMgrCount() == null ? 0 : dealerAggregatedDTO.getGroupMgrCount()));
+                    result.setMemberCount((result.getMemberCount() == null ? 0 : result.getMemberCount())
+                            + (dealerAggregatedDTO.getMemberCount() == null ? 0 : dealerAggregatedDTO.getMemberCount()));
+                    result.setNormalNum((result.getNormalNum() == null ? 0 : result.getNormalNum())
+                            + (dealerAggregatedDTO.getNormalNum() == null ? 0 : dealerAggregatedDTO.getNormalNum()));
+                    result.setBlackNum((result.getBlackNum() == null ? 0 : result.getBlackNum())
+                            + (dealerAggregatedDTO.getBlackNum() == null ? 0 : dealerAggregatedDTO.getBlackNum()));
+                    result.setQwMemberNum((result.getQwMemberNum() == null ? 0 : result.getQwMemberNum())
+                            + (dealerAggregatedDTO.getQwMemberNum() == null ? 0 : dealerAggregatedDTO.getQwMemberNum()));
+                    result.setTodayIncreaseUserNum((result.getTodayIncreaseUserNum() == null ? 0 : result.getTodayIncreaseUserNum())
+                            + (dealerAggregatedDTO.getTodayIncreaseUserNum() == null ? 0 : dealerAggregatedDTO.getTodayIncreaseUserNum()));
+                    result.setOrderTotalNum((result.getOrderTotalNum() == null ? 0 : result.getOrderTotalNum())
+                            + (dealerAggregatedDTO.getOrderTotalNum() == null ? 0 : dealerAggregatedDTO.getOrderTotalNum()));
+                    result.setTodayOrderNum((result.getTodayOrderNum() == null ? 0 : result.getTodayOrderNum())
+                            + (dealerAggregatedDTO.getTodayOrderNum() == null ? 0 : dealerAggregatedDTO.getTodayOrderNum()));
+                    result.setRecvTodayNum((result.getRecvTodayNum() == null ? 0 : result.getRecvTodayNum())
+                            + (dealerAggregatedDTO.getRecvTodayNum() == null ? 0 : dealerAggregatedDTO.getRecvTodayNum()));
+                    result.setRecvTotalNum((result.getRecvTotalNum() == null ? 0 : result.getRecvTotalNum())
+                            + (dealerAggregatedDTO.getRecvTotalNum() == null ? 0 : dealerAggregatedDTO.getRecvTotalNum()));
+                    result.setGoodsTotalNum((result.getGoodsTotalNum() == null ? 0 : result.getGoodsTotalNum())
+                            + (dealerAggregatedDTO.getGoodsTotalNum() == null ? 0 : dealerAggregatedDTO.getGoodsTotalNum()));
+                    result.setTodayGoodsNum((result.getTodayGoodsNum() == null ? 0 : result.getTodayGoodsNum())
+                            + (dealerAggregatedDTO.getTodayGoodsNum() == null ? 0 : dealerAggregatedDTO.getTodayGoodsNum()));
+                    result.setPadUsedNum((result.getPadUsedNum() == null ? 0 : result.getPadUsedNum())
+                            + (dealerAggregatedDTO.getPadUsedNum() == null ? 0 : dealerAggregatedDTO.getPadUsedNum()));
+                }
+            }
+        }
+
+        return R.ok().put("data",result);
+    }
+
+
+    /**
+     * 短信余额
+     */
+    @GetMapping("/smsBalance")
+    public R smsBalance(StatisticsDeptCompanyParam param){
+        if (!medicalMallConfig.isStatics() || (param.getCompanyId() == null && param.getDeptId() == null) || (param.getCompanyId() == null && param.getDeptId() == 1)){
+            return R.ok().put("data", redisCache.getCacheObject(StatisticsRedisConstant.DATA_OVERVIEW_DEALER_SMS_BALANCE));
+        }else if(param.getCompanyId() != null){
+            return R.ok().put("data", redisCache.getCacheObject(String.format("%s:%d", DATA_OVERVIEW_DEALER_SMS_BALANCE, param.getCompanyId())));
+        }else{
+            Company company = new Company();
+            company.setDeptId(param.getDeptId());
+            long smsBalance = 0L;
+            Long[] companyIds = companyService.selectCompanyList(company).stream().map(Company::getCompanyId).toArray(Long[]::new);
+            for(Long companyId : companyIds) {
+                Long smsBalanceCompany = redisCache.getCacheObject(String.format("%s:%d", DATA_OVERVIEW_DEALER_SMS_BALANCE, companyId));
+                if (smsBalanceCompany != null) {
+                    smsBalance += smsBalanceCompany;
+                }
+            }
+            return R.ok().put("data", smsBalance);
+        }
+    }
+
+
+    /**
+     * 授权信息
+     */
+    @GetMapping("/authorizationInfo")
+    public R authorizationInfo(StatisticsDeptCompanyParam  param){
+        AuthorizationInfoDTO authorizationInfoDTO = new AuthorizationInfoDTO();
+        if (!medicalMallConfig.isStatics() || (param.getCompanyId() == null && param.getDeptId() == null) || (param.getCompanyId() == null && param.getDeptId() == 1)){
+            return R.ok().put("data", redisCache.getCacheObject(StatisticsRedisConstant.DATA_OVERVIEW_DEALER_AUTHORIZATION_INFO));
+        }else if(param.getCompanyId() != null){
+            return R.ok().put("data", redisCache.getCacheObject(String.format("%s:%d", DATA_OVERVIEW_DEALER_AUTHORIZATION_INFO, param.getCompanyId())));
+        }else{
+            Company company = new Company();
+            company.setDeptId(param.getDeptId());
+            AuthorizationInfoDTO authorizationInfoDTO1 = new AuthorizationInfoDTO();
+            Long[] companyIds = companyService.selectCompanyList(company).stream().map(Company::getCompanyId).toArray(Long[]::new);
+            for(Long companyId : companyIds) {
+                AuthorizationInfoDTO companyDTO = redisCache.getCacheObject(String.format("%s:%d", DATA_OVERVIEW_DEALER_AUTHORIZATION_INFO, companyId));
+                log.info("授权信息:{}", authorizationInfoDTO);
+                if (companyDTO != null) {
+                    authorizationInfoDTO1.setTodayWatchUserCount(
+                            (authorizationInfoDTO1.getTodayWatchUserCount() == null ? 0 : authorizationInfoDTO1.getTodayWatchUserCount()) +
+                                    (companyDTO.getTodayWatchUserCount() == null ? 0 : companyDTO.getTodayWatchUserCount())
+                    );
+
+                    authorizationInfoDTO1.setVersionLimit(
+                            (authorizationInfoDTO1.getVersionLimit() == null ? 0 : authorizationInfoDTO1.getVersionLimit()) +
+                                    (companyDTO.getVersionLimit() == null ? 0 : companyDTO.getVersionLimit())
+                    );
+                }
+            }
+        }
+
+        return R.ok().put("data", authorizationInfoDTO);
+    }
+
+
+    /**
+     * 当月订单数统计
+     * @return
+     */
+    @GetMapping("/thisMonthOrderCount")
+    public R thisMonthOrderCount(StatisticsDeptCompanyParam  param){
+        if (!medicalMallConfig.isStatics() || (param.getCompanyId() == null && param.getDeptId() == null) || (param.getCompanyId() == null && param.getDeptId() == 1)){
+            return redisCache.getCacheObject(StatisticsRedisConstant.THIS_MONTH_ORDER_COUNT);
+        }else if(param.getCompanyId() != null){
+            return redisCache.getCacheObject(String.format("%s:%d", THIS_MONTH_ORDER_COUNT, param.getCompanyId()));
+        }else{
+            Company company = new Company();
+            company.setDeptId(param.getDeptId());
+            Long[] companyIds = companyService.selectCompanyList(company).stream().map(Company::getCompanyId).toArray(Long[]::new);
+            List<String> datesList = new ArrayList<>();
+            List<Integer> orderCountList = new ArrayList<>();
+            List<Integer> payPriceList = new ArrayList<>();
+            for(Long companyId : companyIds) {
+                R result = redisCache.getCacheObject(String.format("%s:%d", THIS_MONTH_ORDER_COUNT, companyId));
+                Object datas = result.get("datas");
+                Object orderCount = result.get("orderCount");
+                Object payPrice = result.get("payPrice");
+                if(datas != null){
+                    mergeDataListsForInteger(datesList, orderCountList, payPriceList, (List<String>)datas, (List<Integer>)orderCount, (List<Integer>)payPrice);
+                }
+            }
+            return R.ok().put("dates", datesList).put("orderCount", orderCountList).put("payPrice", payPriceList);
+        }
+    }
+
+    /**
+     * 当月收益统计
+     * @return
+     */
+
+    @GetMapping("/thisMonthRecvCount")
+    public R thisMonthRecvCount(StatisticsDeptCompanyParam  param){
+        if (!medicalMallConfig.isStatics() || (param.getCompanyId() == null && param.getDeptId() == null) || (param.getCompanyId() == null && param.getDeptId() == 1)){
+            return redisCache.getCacheObject(StatisticsRedisConstant.THIS_MONTH_RECV_COUNT);
+        }else if(param.getCompanyId() != null){
+            return redisCache.getCacheObject(String.format("%s:%d", THIS_MONTH_RECV_COUNT, param.getCompanyId()));
+        }else{
+            Company company = new Company();
+            company.setDeptId(param.getDeptId());
+            Long[] companyIds = companyService.selectCompanyList(company).stream().map(Company::getCompanyId).toArray(Long[]::new);
+            List<String> datesList = new ArrayList<>();
+            List<Integer> orderCountList = new ArrayList<>();
+            List<Float> payMoneyList = new ArrayList<>();
+            for(Long companyId : companyIds) {
+               R result = redisCache.getCacheObject(String.format("%s:%d", THIS_MONTH_RECV_COUNT, companyId));
+               Object datas = result.get("datas");
+               Object orderCount = result.get("orderCount");
+               Object payMoney = result.get("payMoney");
+               if(datas != null){
+                   mergeDataLists(datesList, orderCountList, payMoneyList, (List<String>)datas, (List<Integer>)orderCount, (List<Float>)payMoney);
+               }
+            }
+            return R.ok().put("dates", datesList).put("orderCount", orderCountList).put("payMoney", payMoneyList);
+        }
+    }
+
+    private void mergeDataLists(List<String> datasList, List<Integer> orderCountList, List<Float> payMoneyList,
+                               List<String> datas, List<Integer> orderCount, List<Float> payMoney) {
+
+        // 遍历新数据
+        for (int i = 0; i < datas.size(); i++) {
+            String newData = datas.get(i);
+            Integer newOrderCount = orderCount.get(i);
+            Float newPayMoney = payMoney.get(i);
+
+            // 查找在现有列表中的位置
+            int existingIndex = datasList.indexOf(newData);
+
+            if (existingIndex != -1) {
+                // 如果存在,将orderCount和payMoney相加
+                Integer existingOrderCount = orderCountList.get(existingIndex);
+                Float existingPayMoney = payMoneyList.get(existingIndex);
+
+                // 累加orderCount
+                orderCountList.set(existingIndex,
+                        (existingOrderCount == null ? 0 : existingOrderCount) +
+                                (newOrderCount == null ? 0 : newOrderCount));
+
+                // 累加payMoney
+                payMoneyList.set(existingIndex,
+                        (existingPayMoney == null ? 0.0f : existingPayMoney) +
+                                (newPayMoney == null ? 0.0f : newPayMoney));
+            } else {
+                // 如果不存在,直接添加新项
+                datasList.add(newData);
+                orderCountList.add(newOrderCount);
+                payMoneyList.add(newPayMoney);
+            }
+        }
+    }
+
+    private void mergeDataListsForInteger(List<String> datasList, List<Integer> orderCountList, List<Integer> payMoneyList,
+                                          List<String> datas, List<Integer> orderCount, List<Integer> payMoney) {
+        // 遍历新数据
+        for (int i = 0; i < datas.size(); i++) {
+            String newData = datas.get(i);
+            Integer newOrderCount = orderCount.get(i);
+            Integer newPayMoney = payMoney.get(i);
+
+            // 查找在现有列表中的位置
+            int existingIndex = datasList.indexOf(newData);
+
+            if (existingIndex != -1) {
+                // 如果存在,将orderCount和payMoney相加
+                Integer existingOrderCount = orderCountList.get(existingIndex);
+                Integer existingPayMoney = payMoneyList.get(existingIndex);
+
+                // 累加orderCount
+                orderCountList.set(existingIndex,
+                        (existingOrderCount == null ? 0 : existingOrderCount) +
+                                (newOrderCount == null ? 0 : newOrderCount));
+
+                // 累加payMoney
+                payMoneyList.set(existingIndex,
+                        (existingPayMoney == null ? 0 : existingPayMoney) +
+                                (newPayMoney == null ? 0 : newPayMoney));
+            } else {
+                // 如果不存在,直接添加新项
+                datasList.add(newData);
+                orderCountList.add(newOrderCount);
+                payMoneyList.add(newPayMoney);
+            }
+        }
+    }
+
+    /**
+     * @Description: 看课统计按公司
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2025/10/27 16:29
+     */
+    @PostMapping("/getWatchCourseStatisticsData")
+    public R getWatchCourseStatisticsData(@RequestBody AnalysisPreviewQueryDTO param){
+        // 从缓存获取看客统计数据
+        List<WatchCourseStatisticsResultDTO> data=statisticsService.getWatchCourseStatisticsData( param);
+
+        return R.ok().put("data", data);
+    }
+}

+ 81 - 0
fs-admin-agent/src/main/java/com/fs/api/controller/StatisticManageController.java

@@ -0,0 +1,81 @@
+package com.fs.api.controller;
+
+import com.fs.common.core.domain.R;
+import com.fs.company.service.IStatisticManageService;
+import com.fs.statis.param.ComprehensiveStatisticsParam;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.Assert;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.context.annotation.Profile;
+
+import javax.annotation.Resource;
+
+/**
+ * @description: 统计管理
+ * @author: Guos
+ * @time: 2025/10/30 上午9:16
+ */
+@Profile("admin")
+@RestController
+@RequestMapping("/statistic/manage")
+public class StatisticManageController {
+    private static final Logger log = LoggerFactory.getLogger(StatisticManageController.class);
+
+    @Resource
+    private IStatisticManageService statisticManageService;
+
+    /**
+     * 日综合获取统计信息(需要按时间分组)
+     * @param param
+     * @return
+     */
+    @PostMapping("/statisticMain")
+    public R statisticMain(@RequestBody ComprehensiveStatisticsParam param) {
+        Assert.notNull(param.getDimension(), "请选择统计维度");
+        param.setTimeGroupFlag(true);
+        return R.ok().put("data", statisticManageService.statisticMain(param));
+    }
+
+    /**
+     * 获取综合统计信息(不需要按时间分组)
+     * @param param
+     * @return
+     */
+    @PostMapping("/statisticMainN")
+    public R statisticMainN(@RequestBody ComprehensiveStatisticsParam param) {
+        Assert.notNull(param.getDimension(), "请选择统计维度");
+        param.setTimeGroupFlag(false);
+        return R.ok().put("data", statisticManageService.statisticMain(param));
+    }
+
+    /**
+     * 获取公司下拉
+     * @return
+     */
+    @GetMapping("/getSearchCompanyInfo")
+    public R getSearchCompanyInfo(){
+        return R.ok().put("data", statisticManageService.getSearchCompanyInfo());
+    }
+
+    /**
+     * 根据公司id获取部门下拉
+     * @return
+     */
+    @GetMapping("/getSearchDeptInfo")
+    public R getSearchDeptInfo(@RequestParam("id") Long id){
+        return R.ok().put("data", statisticManageService.getSearchDeptInfo(id));
+    }
+
+    /**
+     * 根据部门id获取用户下拉信息
+     * @return
+     */
+    @GetMapping("/getSearchUserInfo")
+    public R getSearchUserInfo(@RequestParam("id") Long id){
+        return R.ok().put("data", statisticManageService.getSearchUserInfo(id));
+    }
+
+
+
+}

+ 107 - 0
fs-admin-agent/src/main/java/com/fs/bean/AliPayBean.java

@@ -0,0 +1,107 @@
+package com.fs.bean;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.stereotype.Component;
+
+@Component
+@PropertySource("classpath:/pay/alipay.properties")
+@ConfigurationProperties(prefix = "alipay")
+public class AliPayBean {
+    private String appId;
+    private String privateKey;
+    private String publicKey;
+    private String appCertPath;
+    private String aliPayCertPath;
+    private String aliPayRootCertPath;
+    private String serverUrl;
+    private String domain;
+    private String returnDomain;
+
+    public String getReturnDomain() {
+        return returnDomain;
+    }
+
+    public void setReturnDomain(String returnDomain) {
+        this.returnDomain = returnDomain;
+    }
+
+    public String getAppId() {
+        return appId;
+    }
+
+    public void setAppId(String appId) {
+        this.appId = appId;
+    }
+
+    public String getPrivateKey() {
+        return privateKey;
+    }
+
+    public void setPrivateKey(String privateKey) {
+        this.privateKey = privateKey;
+    }
+
+    public String getPublicKey() {
+        return publicKey;
+    }
+
+    public void setPublicKey(String publicKey) {
+        this.publicKey = publicKey;
+    }
+
+    public String getAppCertPath() {
+        return appCertPath;
+    }
+
+    public void setAppCertPath(String appCertPath) {
+        this.appCertPath = appCertPath;
+    }
+
+    public String getAliPayCertPath() {
+        return aliPayCertPath;
+    }
+
+    public void setAliPayCertPath(String aliPayCertPath) {
+        this.aliPayCertPath = aliPayCertPath;
+    }
+
+    public String getAliPayRootCertPath() {
+        return aliPayRootCertPath;
+    }
+
+    public void setAliPayRootCertPath(String aliPayRootCertPath) {
+        this.aliPayRootCertPath = aliPayRootCertPath;
+    }
+
+    public String getServerUrl() {
+        return serverUrl;
+    }
+
+    public void setServerUrl(String serverUrl) {
+        this.serverUrl = serverUrl;
+    }
+
+
+    public String getDomain() {
+        return domain;
+    }
+
+    public void setDomain(String domain) {
+        this.domain = domain;
+    }
+
+    @Override
+    public String toString() {
+        return "AliPayBean{" +
+                "appId='" + appId + '\'' +
+                ", privateKey='" + privateKey + '\'' +
+                ", publicKey='" + publicKey + '\'' +
+                ", appCertPath='" + appCertPath + '\'' +
+                ", aliPayCertPath='" + aliPayCertPath + '\'' +
+                ", aliPayRootCertPath='" + aliPayRootCertPath + '\'' +
+                ", serverUrl='" + serverUrl + '\'' +
+                ", domain='" + domain + '\'' +
+                '}';
+    }
+}

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff