xdd hace 1 mes
commit
81e2adf8c0
Se han modificado 100 ficheros con 10141 adiciones y 0 borrados
  1. 3 0
      .gitignore
  2. 0 0
      README.en.md
  3. 0 0
      README.md
  4. 149 0
      deploy.sh
  5. 208 0
      fs-admin/pom.xml
  6. 66 0
      fs-admin/proguard.cfg
  7. 25 0
      fs-admin/src/main/java/com/fs/FSApplication.java
  8. 14 0
      fs-admin/src/main/java/com/fs/FSServletInitializer.java
  9. 120 0
      fs-admin/src/main/java/com/fs/audit/controller/AuditTempController.java
  10. 125 0
      fs-admin/src/main/java/com/fs/city/controller/FsCityController.java
  11. 113 0
      fs-admin/src/main/java/com/fs/common/SysFileController.java
  12. 170 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyController.java
  13. 103 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyDeptController.java
  14. 97 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyLogininforController.java
  15. 97 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyMenuController.java
  16. 97 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyOperLogController.java
  17. 97 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyPostController.java
  18. 97 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyProductController.java
  19. 97 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyRoleController.java
  20. 97 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyRoleDeptController.java
  21. 97 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyRoleMenuController.java
  22. 138 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyUserController.java
  23. 97 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyUserPostController.java
  24. 97 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyUserRoleController.java
  25. 97 0
      fs-admin/src/main/java/com/fs/delivery/controller/CtArticleController.java
  26. 103 0
      fs-admin/src/main/java/com/fs/delivery/controller/CtCloudClassController.java
  27. 103 0
      fs-admin/src/main/java/com/fs/delivery/controller/CtLongVideoController.java
  28. 103 0
      fs-admin/src/main/java/com/fs/delivery/controller/CtShortVideoController.java
  29. 103 0
      fs-admin/src/main/java/com/fs/delivery/controller/TagController.java
  30. 103 0
      fs-admin/src/main/java/com/fs/delivery/controller/TagGroupController.java
  31. 103 0
      fs-admin/src/main/java/com/fs/doctor/controller/DoctorBalanceLogController.java
  32. 142 0
      fs-admin/src/main/java/com/fs/doctor/controller/DoctorCompanyUserController.java
  33. 144 0
      fs-admin/src/main/java/com/fs/doctor/controller/DoctorController.java
  34. 112 0
      fs-admin/src/main/java/com/fs/doctor/controller/DoctorLevelApplyController.java
  35. 97 0
      fs-admin/src/main/java/com/fs/doctor/controller/DoctorLevelController.java
  36. 98 0
      fs-admin/src/main/java/com/fs/doctor/controller/DoctorLevelDataController.java
  37. 97 0
      fs-admin/src/main/java/com/fs/doctor/controller/DoctorLevelMaterialController.java
  38. 124 0
      fs-admin/src/main/java/com/fs/doctor/controller/WithdrawRecordsController.java
  39. 182 0
      fs-admin/src/main/java/com/fs/framework/aspectj/DataScopeAspect.java
  40. 73 0
      fs-admin/src/main/java/com/fs/framework/aspectj/DataSourceAspect.java
  41. 244 0
      fs-admin/src/main/java/com/fs/framework/aspectj/LogAspect.java
  42. 117 0
      fs-admin/src/main/java/com/fs/framework/aspectj/RateLimiterAspect.java
  43. 81 0
      fs-admin/src/main/java/com/fs/framework/config/AdminLoginContextCallback.java
  44. 62 0
      fs-admin/src/main/java/com/fs/framework/config/ApplicationConfig.java
  45. 85 0
      fs-admin/src/main/java/com/fs/framework/config/CaptchaConfig.java
  46. 123 0
      fs-admin/src/main/java/com/fs/framework/config/DruidConfig.java
  47. 52 0
      fs-admin/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  48. 59 0
      fs-admin/src/main/java/com/fs/framework/config/FilterConfig.java
  49. 76 0
      fs-admin/src/main/java/com/fs/framework/config/KaptchaTextCreator.java
  50. 128 0
      fs-admin/src/main/java/com/fs/framework/config/MyBatisConfig.java
  51. 74 0
      fs-admin/src/main/java/com/fs/framework/config/RedisConfig.java
  52. 66 0
      fs-admin/src/main/java/com/fs/framework/config/ResourcesConfig.java
  53. 168 0
      fs-admin/src/main/java/com/fs/framework/config/SecurityConfig.java
  54. 33 0
      fs-admin/src/main/java/com/fs/framework/config/ServerConfig.java
  55. 124 0
      fs-admin/src/main/java/com/fs/framework/config/SwaggerConfig.java
  56. 63 0
      fs-admin/src/main/java/com/fs/framework/config/ThreadPoolConfig.java
  57. 77 0
      fs-admin/src/main/java/com/fs/framework/config/properties/DruidProperties.java
  58. 27 0
      fs-admin/src/main/java/com/fs/framework/datasource/DynamicDataSource.java
  59. 45 0
      fs-admin/src/main/java/com/fs/framework/datasource/DynamicDataSourceContextHolder.java
  60. 56 0
      fs-admin/src/main/java/com/fs/framework/interceptor/RepeatSubmitInterceptor.java
  61. 126 0
      fs-admin/src/main/java/com/fs/framework/interceptor/impl/SameUrlDataInterceptor.java
  62. 56 0
      fs-admin/src/main/java/com/fs/framework/manager/AsyncManager.java
  63. 40 0
      fs-admin/src/main/java/com/fs/framework/manager/ShutdownManager.java
  64. 103 0
      fs-admin/src/main/java/com/fs/framework/manager/factory/AsyncFactory.java
  65. 56 0
      fs-admin/src/main/java/com/fs/framework/security/filter/JwtAuthenticationTokenFilter.java
  66. 35 0
      fs-admin/src/main/java/com/fs/framework/security/handle/AuthenticationEntryPointImpl.java
  67. 54 0
      fs-admin/src/main/java/com/fs/framework/security/handle/LogoutSuccessHandlerImpl.java
  68. 237 0
      fs-admin/src/main/java/com/fs/framework/web/domain/Server.java
  69. 101 0
      fs-admin/src/main/java/com/fs/framework/web/domain/server/Cpu.java
  70. 123 0
      fs-admin/src/main/java/com/fs/framework/web/domain/server/Jvm.java
  71. 61 0
      fs-admin/src/main/java/com/fs/framework/web/domain/server/Mem.java
  72. 84 0
      fs-admin/src/main/java/com/fs/framework/web/domain/server/Sys.java
  73. 114 0
      fs-admin/src/main/java/com/fs/framework/web/domain/server/SysFile.java
  74. 115 0
      fs-admin/src/main/java/com/fs/framework/web/exception/GlobalExceptionHandler.java
  75. 166 0
      fs-admin/src/main/java/com/fs/framework/web/service/PermissionService.java
  76. 135 0
      fs-admin/src/main/java/com/fs/framework/web/service/SysLoginService.java
  77. 67 0
      fs-admin/src/main/java/com/fs/framework/web/service/SysPermissionService.java
  78. 115 0
      fs-admin/src/main/java/com/fs/framework/web/service/SysRegisterService.java
  79. 227 0
      fs-admin/src/main/java/com/fs/framework/web/service/TokenService.java
  80. 60 0
      fs-admin/src/main/java/com/fs/framework/web/service/UserDetailsServiceImpl.java
  81. 97 0
      fs-admin/src/main/java/com/fs/project/controller/ProjectContractController.java
  82. 113 0
      fs-admin/src/main/java/com/fs/project/controller/ProjectController.java
  83. 97 0
      fs-admin/src/main/java/com/fs/project/controller/ProjectPermissionController.java
  84. 143 0
      fs-admin/src/main/java/com/fs/project/controller/ProjectSettingsController.java
  85. 108 0
      fs-admin/src/main/java/com/fs/project/controller/ProjectStandardController.java
  86. 109 0
      fs-admin/src/main/java/com/fs/project/controller/ProjectTaskTypeController.java
  87. 128 0
      fs-admin/src/main/java/com/fs/provider/controller/ServiceGenConfigController.java
  88. 125 0
      fs-admin/src/main/java/com/fs/provider/controller/ServiceOrderController.java
  89. 97 0
      fs-admin/src/main/java/com/fs/provider/controller/ServiceOrderTaskRelController.java
  90. 108 0
      fs-admin/src/main/java/com/fs/survey/DrugResearchController.java
  91. 199 0
      fs-admin/src/main/java/com/fs/survey/SurveyDataController.java
  92. 94 0
      fs-admin/src/main/java/com/fs/survey/SurveyFieldConfigController.java
  93. 127 0
      fs-admin/src/main/java/com/fs/survey/SurveyQuestionnaireController.java
  94. 119 0
      fs-admin/src/main/java/com/fs/survey/SurveyTaskController.java
  95. 103 0
      fs-admin/src/main/java/com/fs/survey/SurveyTaskPeriodController.java
  96. 103 0
      fs-admin/src/main/java/com/fs/survey/SurveyVersionController.java
  97. 102 0
      fs-admin/src/main/java/com/fs/task/controller/TaskDeliveryInfoController.java
  98. 147 0
      fs-admin/src/main/java/com/fs/task/controller/TaskInfoController.java
  99. 103 0
      fs-admin/src/main/java/com/fs/task/controller/TaskMaterialInfoController.java
  100. 96 0
      fs-admin/src/main/java/com/fs/web/controller/common/CaptchaController.java

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+/.idea/
+/*/target
+/target/*

+ 0 - 0
README.en.md


+ 0 - 0
README.md


+ 149 - 0
deploy.sh

@@ -0,0 +1,149 @@
+#!/bin/bash
+
+# 各服务对应的远程服务器配置
+declare -A SERVER_CONFIG=(
+    # 服务名:IP地址
+    ["fs-admin"]="132.232.83.221"
+    ["fs-company"]="132.232.83.221"
+    ["fs-user-app"]="132.232.83.221"
+    ["fs-company-app"]="132.232.83.221"
+)
+
+# 通用配置(所有服务器相同)
+REMOTE_USER="root"
+REMOTE_BASE_DIR="/home/software"
+
+# 本地 JAR 包路径
+LOCAL_FS_ADMIN_JAR="./fs-admin/target/fs-admin.jar"
+LOCAL_FS_COMPANY_JAR="./fs-company/target/fs-company.jar"
+LOCAL_FS_USER_APP_JAR="./fs-user-app/target/fs-user-app.jar"
+LOCAL_FS_COMPANY_APP_JAR="./fs-company-app/target/fs-company-app.jar"
+
+# 函数:检查本地文件是否存在
+check_local_file() {
+    if [ ! -f "$1" ]; then
+        echo "错误: 本地文件 $1 不存在。"
+        exit 1
+    fi
+}
+
+# 停止远程服务器上可能正在运行的旧版本
+stop_remote_app() {
+    local remote_user=$1
+    local remote_host=$2
+    local app_name=$3
+
+    echo "正在停止 $remote_host 上的 $app_name 服务..."
+    ssh "$remote_user@$remote_host" "pkill -f $app_name || echo '没有找到运行中的进程'"
+}
+
+# 检查服务器连通性
+check_server_connectivity() {
+    local remote_user=$1
+    local remote_host=$2
+
+    if ! ssh -o ConnectTimeout=5 "$remote_user@$remote_host" "echo '连接成功'" &>/dev/null; then
+        echo "错误: 无法连接到服务器 $remote_host"
+        return 1
+    fi
+    return 0
+}
+
+# 部署单个 JAR 包到指定服务器
+deploy_jar() {
+    local local_jar=$1
+    local service_name=$2  # 服务名,用于确定目标服务器
+    local remote_dir=$3     # 远程目录名
+
+    # 获取服务对应的服务器IP
+    local remote_host="${SERVER_CONFIG[$service_name]}"
+    if [ -z "$remote_host" ]; then
+        echo "错误: 未找到服务 $service_name 的服务器配置"
+        return 1
+    fi
+
+    local app_name=$(basename "$local_jar" .jar)
+
+    echo "========================================"
+    echo "开始部署 $service_name 到服务器 $remote_host"
+    echo "本地文件: $local_jar"
+    echo "远程目录: $REMOTE_BASE_DIR/$remote_dir"
+    echo "========================================"
+
+    # 检查本地文件
+    check_local_file "$local_jar"
+
+    # 检查服务器连通性
+    if ! check_server_connectivity "$REMOTE_USER" "$remote_host"; then
+        return 1
+    fi
+
+    # 创建远程目录(如果不存在)
+    echo "创建远程目录..."
+    ssh "$REMOTE_USER@$remote_host" "mkdir -p $REMOTE_BASE_DIR/$remote_dir"
+
+    # 停止旧版本
+    stop_remote_app "$REMOTE_USER" "$remote_host" "$app_name"
+
+    # 上传 JAR 包
+    echo "上传 JAR 包..."
+    scp "$local_jar" "$REMOTE_USER@$remote_host:$REMOTE_BASE_DIR/$remote_dir/"
+
+    # 在后台启动 JAR 包
+    echo "启动服务..."
+    ssh -f "$REMOTE_USER@$remote_host" \
+    "cd $REMOTE_BASE_DIR/$remote_dir && \
+     nohup java -jar -Dfile.encoding=UTF-8 $app_name.jar \
+     > $app_name.log 2>&1 &"
+
+    # 检查进程是否启动成功
+    if ssh "$REMOTE_USER@$remote_host" "pgrep -f $app_name" &>/dev/null; then
+        echo "✓ $service_name 部署成功到 $remote_host"
+    else
+        echo "✗ $service_name 启动失败,请检查日志: $REMOTE_BASE_DIR/$remote_dir/$app_name.log"
+        return 1
+    fi
+
+    echo ""
+}
+
+# 主要部署流程
+echo "开始多服务器分布式部署..."
+echo "部署配置:"
+for service in "${!SERVER_CONFIG[@]}"; do
+    echo "  $service -> ${SERVER_CONFIG[$service]}"
+done
+echo ""
+
+# 部署 fs-admin
+deploy_jar "$LOCAL_FS_ADMIN_JAR" "fs-admin" "fs-admin"
+
+# 部署 fs-company
+deploy_jar "$LOCAL_FS_COMPANY_JAR" "fs-company" "fs-company"
+
+# 部署 fs-user-app
+#deploy_jar "$LOCAL_FS_USER_APP_JAR" "fs-user-app" "fs-user-app"
+
+# 部署 fs-api
+#deploy_jar "$LOCAL_FS_COMPANY_APP_JAR" "fs-company-app" "fs-company-app"
+
+echo "========================================"
+echo "分布式部署完成!"
+echo "各服务部署状态:"
+for service in "${!SERVER_CONFIG[@]}"; do
+    remote_host="${SERVER_CONFIG[$service]}"
+    echo "  $service -> $remote_host"
+done
+echo "========================================"
+
+# 可选:显示各服务进程状态
+echo "服务进程状态检查:"
+for service in "${!SERVER_CONFIG[@]}"; do
+    remote_host="${SERVER_CONFIG[$service]}"
+    app_name="$service"  # 简化处理,实际可能需要根据JAR文件名调整
+    if ssh "$REMOTE_USER@$remote_host" "pgrep -f $app_name" &>/dev/null; then
+        echo "  ✓ $service 在 $remote_host 上运行正常"
+    else
+        echo "  ✗ $service 在 $remote_host 上未运行"
+    fi
+done

+ 208 - 0
fs-admin/pom.xml

@@ -0,0 +1,208 @@
+<?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</artifactId>
+
+    <description>
+        web服务入口
+    </description>
+
+    <dependencies>
+        <!-- SpringBoot Web容器 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <!-- SpringBoot 拦截器 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+
+        <!-- 阿里数据库连接池 -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid-spring-boot-starter</artifactId>
+        </dependency>
+
+        <!-- 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.github.oshi</groupId>
+            <artifactId>oshi-core</artifactId>
+        </dependency>
+        <!-- 核心模块-->
+        <dependency>
+            <groupId>com.fs</groupId>
+            <artifactId>fs-service</artifactId>
+        </dependency>
+
+        <!-- 定时任务-->
+        <dependency>
+            <groupId>com.fs</groupId>
+            <artifactId>fs-quartz</artifactId>
+        </dependency>
+
+        <!-- 代码生成-->
+        <dependency>
+            <groupId>com.fs</groupId>
+            <artifactId>fs-generator</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.clickhouse</groupId>
+            <artifactId>clickhouse-jdbc</artifactId>
+            <version>0.4.6</version>
+        </dependency>
+
+
+        <!-- swagger2-->
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger2</artifactId>
+        </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>
+        </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>
+        </dependency>
+        <!-- 验证码 -->
+        <dependency>
+            <groupId>com.github.penggle</groupId>
+            <artifactId>kaptcha</artifactId>
+            <exclusions>
+                <exclusion>
+                    <artifactId>javax.servlet-api</artifactId>
+                    <groupId>javax.servlet</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>2.1.1.RELEASE</version>
+                <configuration>
+                    <fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-war-plugin</artifactId>
+                <version>3.1.0</version>
+                <configuration>
+                    <failOnMissingWebXml>false</failOnMissingWebXml>
+                    <warName>${project.artifactId}</warName>
+                </configuration>
+           </plugin>
+<!--            <plugin>-->
+<!--                <groupId>com.github.wvengen</groupId>-->
+<!--                <artifactId>proguard-maven-plugin</artifactId>-->
+<!--                <version>2.6.0</version>-->
+<!--                <executions>-->
+<!--                    &lt;!&ndash; 以下配置说明执行mvn的package命令时候,会执行proguard&ndash;&gt;-->
+<!--                    <execution>-->
+<!--                        <phase>package</phase>-->
+<!--                        <goals>-->
+<!--                            <goal>proguard</goal>-->
+<!--                        </goals>-->
+<!--                    </execution>-->
+<!--                </executions>-->
+<!--                <configuration>-->
+<!--                    &lt;!&ndash; 防止本地编译的时候路径太长编译失败&ndash;&gt;-->
+<!--                    <putLibraryJarsInTempDir>true</putLibraryJarsInTempDir>-->
+<!--                    &lt;!&ndash; 就是输入Jar的名称,我们要知道,代码混淆其实是将一个原始的jar,生成一个混淆后的jar,那么就会有输入输出。 &ndash;&gt;-->
+<!--                    <injar>${project.build.finalName}.jar</injar>-->
+<!--                    &lt;!&ndash; 输出jar名称,输入输出jar同名的时候就是覆盖,也是比较常用的配置。 &ndash;&gt;-->
+<!--                    <outjar>${project.build.finalName}.jar</outjar>-->
+<!--                    &lt;!&ndash; 是否混淆 默认是true &ndash;&gt;-->
+<!--                    <obfuscate>true</obfuscate>-->
+<!--                    &lt;!&ndash; 配置一个文件,通常叫做proguard.cfg,该文件主要是配置options选项,也就是说使用proguard.cfg那么options下的所有内容都可以移到proguard.cfg中 &ndash;&gt;-->
+<!--                    <proguardInclude>${project.basedir}/proguard.cfg</proguardInclude>-->
+<!--                    &lt;!&ndash; 额外的jar包,通常是项目编译所需要的jar &ndash;&gt;-->
+<!--                    <libs>-->
+<!--                        <lib>${java.home}/lib/rt.jar</lib>-->
+<!--                        <lib>${java.home}/lib/jce.jar</lib>-->
+<!--                        <lib>${java.home}/lib/jsse.jar</lib>-->
+<!--                    </libs>-->
+<!--                    &lt;!&ndash; 对输入jar进行过滤比如,如下配置就是对META-INFO文件不处理。 &ndash;&gt;-->
+<!--                    &lt;!&ndash;                    <inLibsFilter>!META-INF/**,!META-INF/versions/9/**.class</inLibsFilter>&ndash;&gt;-->
+<!--                    &lt;!&ndash; 这是输出路径配置,但是要注意这个路径必须要包括injar标签填写的jar &ndash;&gt;-->
+<!--                    <outputDirectory>${project.basedir}/target</outputDirectory>-->
+<!--                    &lt;!&ndash;这里特别重要,此处主要是配置混淆的一些细节选项,比如哪些类不需要混淆,哪些需要混淆&ndash;&gt;-->
+<!--                    <options>-->
+<!--                        &lt;!&ndash; 可以在这里写option标签配置,不过我上面使用了proguardInclude,所以可以在proguard.cfg中配置 &ndash;&gt;-->
+<!--                    </options>-->
+<!--                    &lt;!&ndash;                    子模块&ndash;&gt;-->
+<!--                    <assembly>-->
+<!--                        <inclusions>-->
+<!--                            <inclusion>-->
+<!--                                <groupId>com.fs</groupId>-->
+<!--                                <artifactId>fs-admin</artifactId>-->
+<!--                            </inclusion>-->
+
+<!--                        </inclusions>-->
+<!--                    </assembly>-->
+<!--                </configuration>-->
+<!--            </plugin>-->
+
+        </plugins>
+        <finalName>${project.artifactId}</finalName>
+    </build>
+
+</project>

+ 66 - 0
fs-admin/proguard.cfg

@@ -0,0 +1,66 @@
+#指定Java的版本
+-target 1.8
+#proguard会对代码进行优化压缩,他会删除从未使用的类或者类成员变量等
+-dontshrink
+#是否关闭字节码级别的优化,如果不开启则设置如下配置  不做优化(变更代码实现逻辑)
+-dontoptimize
+#混淆时不生成大小写混合的类名,默认是可以大小写混合
+-dontusemixedcaseclassnames
+# 对于类成员的命名的混淆采取唯一策略
+-useuniqueclassmembernames
+#混淆类名之后,对使用Class.forName('className')之类的地方进行相应替代
+-adaptclassstrings
+#保持目录结构
+-keepdirectories
+#对异常、注解信息予以保留
+-keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod,Qualifier
+# 此选项将保存接口中的所有原始名称(不混淆)-->
+#-keepnames interface ** { *; }
+
+# 此选项将保存所有软件包中的所有原始接口文件(不进行混淆)
+#-keep interface * extends * { *; }
+#保留参数名,因为控制器,或者Mybatis等接口的参数如果混淆会导致无法接受参数,xml文件找不到参数
+-keepparameternames
+# 保留枚举成员及方法
+#-keepclassmembers enum * { *; }
+# 不混淆所有类,保存原始定义的注释-
+-keepclassmembers class * {
+                        @org.springframework.context.annotation.Bean *;
+                        @org.springframework.context.annotation.Bean *;
+                        @org.springframework.beans.factory.annotation.Autowired *;
+                        @org.springframework.beans.factory.annotation.Value *;
+                        @org.springframework.stereotype.Service *;
+                        @org.springframework.stereotype.Component *;
+                        @org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration *;
+                        @org.springframework.boot.context.properties.ConfigurationProperties *;
+                        @org.springframework.web.bind.annotation.RestController *;
+                        @org.springframework.beans.factory.annotation.Qualifier *;
+                        @io.swagger.annotations.ApiParam *;
+                        @org.springframework.validation.annotation.Validated *;
+                        @io.swagger.annotations.ApiModelProperty *;
+                        @javax.validation.constraints.NotNull *;
+                        @javax.validation.constraints.Size *;
+                        @javax.validation.constraints.NotBlank *;
+                        @javax.validation.constraints.Pattern *;
+
+                        }
+-keep class org.springframework.** {*;}
+-keep public class ch.qos.logback.**{*;}
+-keep class com.fasterxml.jackson.** { *; }
+
+#忽略warn消息
+-ignorewarnings
+#打印配置信息
+-printconfiguration
+
+#入口程序类不能混淆,混淆会导致springboot启动不了
+-keep public class com.fs.FSApplication { *;}
+
+
+# 全放开
+#-keep class com.ugdsec.** {*;}
+#保留Serializable序列化的类不被混淆
+# controller 层映射前台参数的类、后端返回的 bean 属性类等,不能混淆类的成员属性(如变成 string a;)
+#-keepclassmembers  class * implements java.io.Serializable {*;}
+-dontwarn
+

+ 25 - 0
fs-admin/src/main/java/com/fs/FSApplication.java

@@ -0,0 +1,25 @@
+package com.fs;
+
+import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * 启动程序
+ */
+@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
+@Transactional
+@EnableAsync
+@EnableSwaggerBootstrapUI
+public class FSApplication
+{
+    public static void main(String[] args)
+    {
+        // System.setProperty("spring.devtools.restart.enabled", "false");
+        SpringApplication.run(FSApplication.class, args);
+        System.out.println("admin启动成功");
+    }
+}

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

+ 120 - 0
fs-admin/src/main/java/com/fs/audit/controller/AuditTempController.java

@@ -0,0 +1,120 @@
+package com.fs.audit.controller;
+
+import java.util.List;
+
+import com.fs.audit.domain.AuditTemp;
+import com.fs.audit.service.AuditTempService;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 审核模板Controller
+ *
+ * @author fs
+ * @date 2026-01-06
+ */
+@RestController
+@RequestMapping("/audit/temp")
+public class AuditTempController extends BaseController
+{
+    @Autowired
+    private AuditTempService auditTempService;
+
+    /**
+     * 查询审核模板列表
+     */
+    @PreAuthorize("@ss.hasPermi('shop:temp:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(AuditTemp auditTemp)
+    {
+        startPage();
+        List<AuditTemp> list = auditTempService.selectAuditTempList(auditTemp);
+        return getDataTable(list);
+    }
+
+
+
+
+
+    /**
+     * 导出审核模板列表
+     */
+    @PreAuthorize("@ss.hasPermi('shop:temp:export')")
+    @Log(title = "审核模板", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(AuditTemp auditTemp)
+    {
+        List<AuditTemp> list = auditTempService.selectAuditTempList(auditTemp);
+        ExcelUtil<AuditTemp> util = new ExcelUtil<AuditTemp>(AuditTemp.class);
+        return util.exportExcel(list, "审核模板数据");
+    }
+
+    /**
+     * 获取审核模板详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('shop:temp:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(auditTempService.getTemplate(id));
+    }
+
+    /**
+     * 新增审核模板
+     */
+    @PreAuthorize("@ss.hasPermi('shop:temp:add')")
+    @Log(title = "审核模板", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody AuditTemp auditTemp)
+    {
+        return toAjax(auditTempService.insertAuditTemp(auditTemp));
+    }
+
+    /**
+     * 新增审核模板
+     */
+    @PreAuthorize("@ss.hasPermi('shop:temp:add')")
+    @Log(title = "审核模板", businessType = BusinessType.INSERT)
+    @PostMapping("/initAuditTemp")
+    public AjaxResult initAuditTemp(@RequestBody  Long companyId)
+    {
+        return toAjax(auditTempService.initAuditTemp(companyId));
+    }
+
+
+    /**
+     * 修改审核模板
+     */
+    @PreAuthorize("@ss.hasPermi('shop:temp:edit')")
+    @Log(title = "审核模板", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody AuditTemp auditTemp)
+    {
+        return toAjax(auditTempService.updateAuditTemp(auditTemp));
+    }
+
+    /**
+     * 删除审核模板
+     */
+    @PreAuthorize("@ss.hasPermi('shop:temp:remove')")
+    @Log(title = "审核模板", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(auditTempService.deleteAuditTempByIds(ids));
+    }
+}

+ 125 - 0
fs-admin/src/main/java/com/fs/city/controller/FsCityController.java

@@ -0,0 +1,125 @@
+package com.fs.city.controller;
+
+import com.fs.city.domain.FsCity;
+import com.fs.city.service.IFsCityService;
+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 java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 城市Controller
+ * 
+ * @author fs
+ * @date 2026-01-14
+ */
+@RestController
+@RequestMapping("/city/city")
+public class FsCityController extends BaseController
+{
+    @Autowired
+    private IFsCityService fsCityService;
+
+    /**
+     * 查询城市列表
+     */
+    @PreAuthorize("@ss.hasPermi('city:city:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsCity fsCity)
+    {
+        startPage();
+        List<FsCity> list = fsCityService.selectFsCityList(fsCity);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出城市列表
+     */
+    @Log(title = "城市", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsCity fsCity)
+    {
+        List<FsCity> list = fsCityService.selectFsCityList(fsCity);
+        ExcelUtil<FsCity> util = new ExcelUtil<FsCity>(FsCity.class);
+        return util.exportExcel(list, "城市数据");
+    }
+
+    /**
+     * 获取城市详细信息
+     */
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsCityService.selectFsCityById(id));
+    }
+
+    /**
+     * 新增城市
+     */
+    @PreAuthorize("@ss.hasPermi('city:city:add')")
+    @Log(title = "城市", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsCity fsCity)
+    {
+        return toAjax(fsCityService.insertFsCity(fsCity));
+    }
+
+    /**
+     * 修改城市
+     */
+    @Log(title = "城市", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsCity fsCity)
+    {
+        return toAjax(fsCityService.updateFsCity(fsCity));
+    }
+
+    /**
+     * 删除城市
+     */
+    @Log(title = "城市", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsCityService.deleteFsCityByIds(ids));
+    }
+
+
+    /**
+     * 级联:按父级编码查询下一级
+     * parentId 为空 -> 默认取全国下的省(parent_id = "0" 或 level=1,按你数据定)
+     */
+    @GetMapping("/cascaderChildren")
+    public AjaxResult cascaderChildren(@RequestParam(required = false) String parentId) {
+        List<FsCity> list = fsCityService.selectChildren(parentId);
+        // 组装成 el-cascader 需要的结构:value/label/leaf
+        List<Map<String, Object>> result = list.stream().map(c -> {
+            Map<String, Object> m = new HashMap<>();
+            m.put("value", c.getCityId());     // 用 city_id 作为value(强烈推荐)
+            m.put("label", c.getCityName());
+            // leaf:是否叶子(这里按 level 判断:省/市/区县...你想停在区县就 level>=3 leaf=true)
+            boolean leaf = c.getLevel() != null && c.getLevel() >= 3;
+            m.put("leaf", leaf);
+            return m;
+        }).collect(Collectors.toList());
+
+        return AjaxResult.success(result);
+    }
+
+
+    @GetMapping("/path")
+    public AjaxResult path(@RequestParam String cityId) {
+        return AjaxResult.success(fsCityService.selectPath(cityId));
+    }
+}
+

+ 113 - 0
fs-admin/src/main/java/com/fs/common/SysFileController.java

@@ -0,0 +1,113 @@
+package com.fs.common;
+
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.file.domain.SysUploadFile;
+import com.fs.file.service.ISysFileService;
+import com.fs.file.vo.FileUploadVO;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+
+/**
+ * 通用文件上传
+ */
+@Api(tags = "文件管理")
+@RestController
+@RequestMapping("/system/file")
+@RequiredArgsConstructor
+public class SysFileController {
+
+    private final ISysFileService sysFileService;
+
+    /**
+     * 通用文件上传
+     */
+    @ApiOperation("通用文件上传")
+    @PostMapping("/upload")
+    public AjaxResult upload(@RequestParam("file") MultipartFile file,
+                             @RequestParam("bizType") String bizType,
+                             @RequestParam(value = "bizId", required = false) String bizId) {
+        FileUploadVO vo = sysFileService.upload(file, bizType, bizId);
+        return AjaxResult.success(vo);
+    }
+
+    /**
+     * 批量文件上传
+     */
+    @ApiOperation("批量文件上传")
+    @PostMapping("/upload/batch")
+    public AjaxResult uploadBatch(@RequestParam("files") List<MultipartFile> files,
+                                  @RequestParam("bizType") String bizType,
+                                  @RequestParam(value = "bizId", required = false) String bizId) {
+        List<FileUploadVO> voList = sysFileService.uploadBatch(files, bizType, bizId);
+        return AjaxResult.success(voList);
+    }
+
+    /**
+     * 秒传检查
+     */
+    @ApiOperation("秒传检查")
+    @GetMapping("/fast-upload/check")
+    public AjaxResult checkFastUpload(@RequestParam("md5") String md5,
+                                      @RequestParam("bizType") String bizType,
+                                      @RequestParam(value = "bizId", required = false) String bizId) {
+        FileUploadVO vo = sysFileService.checkFastUpload(md5, bizType, bizId);
+        if (vo != null) {
+            return AjaxResult.success("秒传成功", vo);
+        }
+        return AjaxResult.error("文件不存在,需要上传");
+    }
+
+    /**
+     * 绑定业务ID
+     */
+    @ApiOperation("绑定业务ID")
+    @PostMapping("/bind")
+    public AjaxResult bindBizId(@RequestParam("fileId") Long fileId,
+                                @RequestParam("bizId") String bizId) {
+        return AjaxResult.success(sysFileService.bindBizId(fileId, bizId));
+    }
+
+    /**
+     * 批量绑定业务ID
+     */
+    @ApiOperation("批量绑定业务ID")
+    @PostMapping("/bind/batch")
+    public AjaxResult bindBizIdBatch(@RequestParam("fileIds") List<Long> fileIds,
+                                     @RequestParam("bizId") String bizId) {
+        return AjaxResult.success(sysFileService.bindBizIdBatch(fileIds, bizId));
+    }
+
+    /**
+     * 根据业务查询文件列表
+     */
+    @ApiOperation("根据业务查询文件列表")
+    @GetMapping("/list")
+    public AjaxResult listByBiz(@RequestParam("bizType") String bizType,
+                                @RequestParam("bizId") String bizId) {
+        List<SysUploadFile> list = sysFileService.listByBiz(bizType, bizId);
+        return AjaxResult.success(list);
+    }
+
+    /**
+     * 删除文件
+     */
+    @ApiOperation("删除文件")
+    @DeleteMapping("/{id}")
+    public AjaxResult delete(@PathVariable Long id) {
+        return AjaxResult.success(sysFileService.deleteById(id));
+    }
+
+    /**
+     * 批量删除文件
+     */
+    @ApiOperation("批量删除文件")
+    @DeleteMapping("/batch")
+    public AjaxResult deleteBatch(@RequestParam("ids") List<Long> ids) {
+        return AjaxResult.success(sysFileService.deleteByIds(ids));
+    }
+}

+ 170 - 0
fs-admin/src/main/java/com/fs/company/controller/CompanyController.java

@@ -0,0 +1,170 @@
+package com.fs.company.controller;
+
+import cn.hutool.core.util.IdUtil;
+import com.fs.common.annotation.Log;
+import com.fs.common.annotation.RepeatSubmit;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.SecurityUtils;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.utils.sign.Md5Utils;
+import com.fs.company.domain.*;
+import com.fs.company.param.CompanyParam;
+import com.fs.company.service.*;
+import com.fs.company.vo.CompanyOptionRespVO;
+import com.fs.company.vo.CompanyVO;
+import com.fs.core.utils.OrderCodeUtils;
+import com.fs.framework.web.service.TokenService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 企业Controller
+ *
+ * @author fs
+ * @date 2021-10-04
+ */
+@RestController
+@RequestMapping("/company/company")
+public class CompanyController extends BaseController
+{
+
+    @Autowired
+    private ICompanyService companyService;
+    @Autowired
+    private ICompanyUserService userService;
+    /**
+     * 查询企业列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:company:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CompanyParam param)
+    {
+        startPage();
+        List<CompanyVO> list = companyService.selectCompanyVOList(param);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出企业列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:company:export')")
+    @Log(title = "企业", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CompanyParam company)
+    {
+        List<CompanyVO> list = companyService.selectCompanyVOList(company);
+        ExcelUtil<CompanyVO> util = new ExcelUtil<CompanyVO>(CompanyVO.class);
+        return util.exportExcel(list, "company");
+    }
+
+    /**
+     * 获取企业详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:company:query')")
+    @GetMapping(value = "/{companyId}")
+    public AjaxResult getInfo(@PathVariable("companyId") Long companyId)
+    {
+        return AjaxResult.success(companyService.selectCompanyById(companyId));
+    }
+
+    /**
+     * 新增企业
+     */
+    @PreAuthorize("@ss.hasPermi('company:company:add')")
+    @Log(title = "企业", businessType = BusinessType.INSERT)
+    @PostMapping
+    public R add(@RequestBody Company company)
+    {
+        company.setPassword(SecurityUtils.encryptPassword(company.getPassword()));
+        company.setAppId(Md5Utils.hash(company.getUserName()));
+        company.setAppKey(Md5Utils.hash(company.getPassword()));
+        return companyService.insertCompany(company);
+    }
+
+    /**
+     * 修改企业
+     */
+    @PreAuthorize("@ss.hasPermi('company:company:edit')")
+    @Log(title = "企业", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody Company company)
+    {
+        CompanyUser companyUser = new CompanyUser();
+        if (company.getStatus()==0){
+
+        }
+        return toAjax(companyService.updateCompany(company));
+    }
+
+
+
+    /**
+     * 删除企业
+     */
+    @PreAuthorize("@ss.hasPermi('company:company:remove')")
+    @Log(title = "企业", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{companyIds}")
+    public AjaxResult remove(@PathVariable Long[] companyIds)
+    {
+        return toAjax(companyService.deleteCompanyByIds(companyIds));
+    }
+
+    @GetMapping("/getCompanyList")
+    public R getCompanyList()
+    {
+        Company map=new Company();
+        map.setIsDel(0);
+        List<Company> list = companyService.selectCompanyList(map);
+        return R.ok().put("data",list);
+    }
+
+
+
+
+
+    @PreAuthorize("@ss.hasPermi('company:company:resetPwd')")
+    @PostMapping("/resetPwd/{companyId}")
+    public AjaxResult resetPwd(@PathVariable Long companyId)
+    {
+        Company company=companyService.selectCompanyById(companyId);
+        return toAjax(userService.resetUserPwdByUserId(company.getUserId(),SecurityUtils.encryptPassword("123456")));
+    }
+
+
+    /**
+     * 公司下拉选项(模糊搜索)
+     */
+    @GetMapping("/option")
+    public TableDataInfo option(@RequestParam(required = false) String companyName) {
+        List<CompanyOptionRespVO> companyOptionRespVOS = companyService.selectCompanyOptions(companyName);
+        return getDataTable(companyOptionRespVOS);
+    }
+
+
+    /**
+     * 查询所有企业列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:company:list')")
+    @GetMapping("/listAll")
+    public AjaxResult listAll()
+    {
+        List<CompanyVO> list = companyService.selectCompanyAll();
+        return AjaxResult.success(list);
+    }
+
+
+
+
+
+}

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

@@ -0,0 +1,103 @@
+package com.fs.company.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.CompanyDept;
+import com.fs.company.service.ICompanyDeptService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+
+@RestController
+@RequestMapping("/company/companyDept")
+public class CompanyDeptController extends BaseController
+{
+    @Autowired
+    private ICompanyDeptService companyDeptService;
+
+    /**
+     * 查询部门列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyDept:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CompanyDept companyDept)
+    {
+        startPage();
+        List<CompanyDept> list = companyDeptService.selectCompanyDeptList(companyDept);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出部门列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyDept:export')")
+    @Log(title = "部门", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CompanyDept companyDept)
+    {
+        List<CompanyDept> list = companyDeptService.selectCompanyDeptList(companyDept);
+        ExcelUtil<CompanyDept> util = new ExcelUtil<CompanyDept>(CompanyDept.class);
+        return util.exportExcel(list, "companyDept");
+    }
+
+    /**
+     * 获取部门详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyDept:query')")
+    @GetMapping(value = "/{deptId}")
+    public AjaxResult getInfo(@PathVariable("deptId") Long deptId)
+    {
+        return AjaxResult.success(companyDeptService.selectCompanyDeptById(deptId));
+    }
+
+    /**
+     * 新增部门
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyDept:add')")
+    @Log(title = "部门", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody CompanyDept companyDept)
+    {
+        return toAjax(companyDeptService.insertCompanyDept(companyDept));
+    }
+
+    /**
+     * 修改部门
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyDept:edit')")
+    @Log(title = "部门", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody CompanyDept companyDept)
+    {
+        return toAjax(companyDeptService.updateCompanyDept(companyDept));
+    }
+
+    /**
+     * 删除部门
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyDept:remove')")
+    @Log(title = "部门", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{deptIds}")
+    public AjaxResult remove(@PathVariable Long[] deptIds)
+    {
+        return toAjax(companyDeptService.deleteCompanyDeptByIds(deptIds));
+    }
+
+    /**
+     * 获取部门下拉树列表
+     */
+    @GetMapping("/treeselect")
+    public AjaxResult treeselect(CompanyDept dept)
+    {
+        dept.setStatus("0");
+        List<CompanyDept> depts = companyDeptService.selectCompanyDeptList(dept);
+        return AjaxResult.success(companyDeptService.buildDeptTreeSelect(depts));
+    }
+}

+ 97 - 0
fs-admin/src/main/java/com/fs/company/controller/CompanyLogininforController.java

@@ -0,0 +1,97 @@
+package com.fs.company.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.CompanyLogininfor;
+import com.fs.company.service.ICompanyLogininforService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 系统访问记录Controller
+ *
+ * @author fs
+ * @date 2021-10-04
+ */
+@RestController
+@RequestMapping("/company/companyLogininfor")
+public class CompanyLogininforController extends BaseController
+{
+    @Autowired
+    private ICompanyLogininforService companyLogininforService;
+
+    /**
+     * 查询系统访问记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyLogininfor:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CompanyLogininfor companyLogininfor)
+    {
+        startPage();
+        List<CompanyLogininfor> list = companyLogininforService.selectCompanyLogininforList(companyLogininfor);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出系统访问记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyLogininfor:export')")
+    @Log(title = "系统访问记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CompanyLogininfor companyLogininfor)
+    {
+        List<CompanyLogininfor> list = companyLogininforService.selectCompanyLogininforList(companyLogininfor);
+        ExcelUtil<CompanyLogininfor> util = new ExcelUtil<CompanyLogininfor>(CompanyLogininfor.class);
+        return util.exportExcel(list, "companyLogininfor");
+    }
+
+    /**
+     * 获取系统访问记录详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyLogininfor:query')")
+    @GetMapping(value = "/{infoId}")
+    public AjaxResult getInfo(@PathVariable("infoId") Long infoId)
+    {
+        return AjaxResult.success(companyLogininforService.selectCompanyLogininforById(infoId));
+    }
+
+    /**
+     * 新增系统访问记录
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyLogininfor:add')")
+    @Log(title = "系统访问记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody CompanyLogininfor companyLogininfor)
+    {
+        return toAjax(companyLogininforService.insertCompanyLogininfor(companyLogininfor));
+    }
+
+    /**
+     * 修改系统访问记录
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyLogininfor:edit')")
+    @Log(title = "系统访问记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody CompanyLogininfor companyLogininfor)
+    {
+        return toAjax(companyLogininforService.updateCompanyLogininfor(companyLogininfor));
+    }
+
+    /**
+     * 删除系统访问记录
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyLogininfor:remove')")
+    @Log(title = "系统访问记录", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{infoIds}")
+    public AjaxResult remove(@PathVariable Long[] infoIds)
+    {
+        return toAjax(companyLogininforService.deleteCompanyLogininforByIds(infoIds));
+    }
+}

+ 97 - 0
fs-admin/src/main/java/com/fs/company/controller/CompanyMenuController.java

@@ -0,0 +1,97 @@
+package com.fs.company.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.CompanyMenu;
+import com.fs.company.service.ICompanyMenuService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 菜单权限Controller
+ *
+ * @author fs
+ * @date 2021-10-04
+ */
+@RestController
+@RequestMapping("/company/companyMenu")
+public class CompanyMenuController extends BaseController
+{
+    @Autowired
+    private ICompanyMenuService companyMenuService;
+
+    /**
+     * 查询菜单权限列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyMenu:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CompanyMenu companyMenu)
+    {
+        startPage();
+        List<CompanyMenu> list = companyMenuService.selectCompanyMenuList(companyMenu);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出菜单权限列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyMenu:export')")
+    @Log(title = "菜单权限", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CompanyMenu companyMenu)
+    {
+        List<CompanyMenu> list = companyMenuService.selectCompanyMenuList(companyMenu);
+        ExcelUtil<CompanyMenu> util = new ExcelUtil<CompanyMenu>(CompanyMenu.class);
+        return util.exportExcel(list, "companyMenu");
+    }
+
+    /**
+     * 获取菜单权限详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyMenu:query')")
+    @GetMapping(value = "/{menuId}")
+    public AjaxResult getInfo(@PathVariable("menuId") Long menuId)
+    {
+        return AjaxResult.success(companyMenuService.selectCompanyMenuById(menuId));
+    }
+
+    /**
+     * 新增菜单权限
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyMenu:add')")
+    @Log(title = "菜单权限", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody CompanyMenu companyMenu)
+    {
+        return toAjax(companyMenuService.insertCompanyMenu(companyMenu));
+    }
+
+    /**
+     * 修改菜单权限
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyMenu:edit')")
+    @Log(title = "菜单权限", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody CompanyMenu companyMenu)
+    {
+        return toAjax(companyMenuService.updateCompanyMenu(companyMenu));
+    }
+
+    /**
+     * 删除菜单权限
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyMenu:remove')")
+    @Log(title = "菜单权限", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{menuIds}")
+    public AjaxResult remove(@PathVariable Long[] menuIds)
+    {
+        return toAjax(companyMenuService.deleteCompanyMenuByIds(menuIds));
+    }
+}

+ 97 - 0
fs-admin/src/main/java/com/fs/company/controller/CompanyOperLogController.java

@@ -0,0 +1,97 @@
+package com.fs.company.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.CompanyOperLog;
+import com.fs.company.service.ICompanyOperLogService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 操作日志记录Controller
+ *
+ * @author fs
+ * @date 2021-10-04
+ */
+@RestController
+@RequestMapping("/company/companyOperLog")
+public class CompanyOperLogController extends BaseController
+{
+    @Autowired
+    private ICompanyOperLogService companyOperLogService;
+
+    /**
+     * 查询操作日志记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyOperLog:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CompanyOperLog companyOperLog)
+    {
+        startPage();
+        List<CompanyOperLog> list = companyOperLogService.selectCompanyOperLogList(companyOperLog);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出操作日志记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyOperLog:export')")
+    @Log(title = "操作日志记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CompanyOperLog companyOperLog)
+    {
+        List<CompanyOperLog> list = companyOperLogService.selectCompanyOperLogList(companyOperLog);
+        ExcelUtil<CompanyOperLog> util = new ExcelUtil<CompanyOperLog>(CompanyOperLog.class);
+        return util.exportExcel(list, "companyOperLog");
+    }
+
+    /**
+     * 获取操作日志记录详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyOperLog:query')")
+    @GetMapping(value = "/{operId}")
+    public AjaxResult getInfo(@PathVariable("operId") Long operId)
+    {
+        return AjaxResult.success(companyOperLogService.selectCompanyOperLogById(operId));
+    }
+
+    /**
+     * 新增操作日志记录
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyOperLog:add')")
+    @Log(title = "操作日志记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody CompanyOperLog companyOperLog)
+    {
+        return toAjax(companyOperLogService.insertCompanyOperLog(companyOperLog));
+    }
+
+    /**
+     * 修改操作日志记录
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyOperLog:edit')")
+    @Log(title = "操作日志记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody CompanyOperLog companyOperLog)
+    {
+        return toAjax(companyOperLogService.updateCompanyOperLog(companyOperLog));
+    }
+
+    /**
+     * 删除操作日志记录
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyOperLog:remove')")
+    @Log(title = "操作日志记录", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{operIds}")
+    public AjaxResult remove(@PathVariable Long[] operIds)
+    {
+        return toAjax(companyOperLogService.deleteCompanyOperLogByIds(operIds));
+    }
+}

+ 97 - 0
fs-admin/src/main/java/com/fs/company/controller/CompanyPostController.java

@@ -0,0 +1,97 @@
+package com.fs.company.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.CompanyPost;
+import com.fs.company.service.ICompanyPostService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 岗位信息Controller
+ *
+ * @author fs
+ * @date 2021-10-04
+ */
+@RestController
+@RequestMapping("/company/companyPost")
+public class CompanyPostController extends BaseController
+{
+    @Autowired
+    private ICompanyPostService companyPostService;
+
+    /**
+     * 查询岗位信息列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyPost:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CompanyPost companyPost)
+    {
+        startPage();
+        List<CompanyPost> list = companyPostService.selectCompanyPostList(companyPost);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出岗位信息列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyPost:export')")
+    @Log(title = "岗位信息", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CompanyPost companyPost)
+    {
+        List<CompanyPost> list = companyPostService.selectCompanyPostList(companyPost);
+        ExcelUtil<CompanyPost> util = new ExcelUtil<CompanyPost>(CompanyPost.class);
+        return util.exportExcel(list, "companyPost");
+    }
+
+    /**
+     * 获取岗位信息详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyPost:query')")
+    @GetMapping(value = "/{postId}")
+    public AjaxResult getInfo(@PathVariable("postId") Long postId)
+    {
+        return AjaxResult.success(companyPostService.selectCompanyPostById(postId));
+    }
+
+    /**
+     * 新增岗位信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyPost:add')")
+    @Log(title = "岗位信息", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody CompanyPost companyPost)
+    {
+        return toAjax(companyPostService.insertCompanyPost(companyPost));
+    }
+
+    /**
+     * 修改岗位信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyPost:edit')")
+    @Log(title = "岗位信息", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody CompanyPost companyPost)
+    {
+        return toAjax(companyPostService.updateCompanyPost(companyPost));
+    }
+
+    /**
+     * 删除岗位信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyPost:remove')")
+    @Log(title = "岗位信息", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{postIds}")
+    public AjaxResult remove(@PathVariable Long[] postIds)
+    {
+        return toAjax(companyPostService.deleteCompanyPostByIds(postIds));
+    }
+}

+ 97 - 0
fs-admin/src/main/java/com/fs/company/controller/CompanyProductController.java

@@ -0,0 +1,97 @@
+package com.fs.company.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.CompanyProduct;
+import com.fs.company.service.ICompanyProductService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 产品Controller
+ * 
+ * @author fs
+ * @date 2025-07-01
+ */
+@RestController
+@RequestMapping("/company/product")
+public class CompanyProductController extends BaseController
+{
+    @Autowired
+    private ICompanyProductService companyProductService;
+
+    /**
+     * 查询产品列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyProduct:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CompanyProduct companyProduct)
+    {
+        startPage();
+        List<CompanyProduct> list = companyProductService.selectCompanyProductList(companyProduct);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出产品列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyProduct:export')")
+    @Log(title = "产品", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CompanyProduct companyProduct)
+    {
+        List<CompanyProduct> list = companyProductService.selectCompanyProductList(companyProduct);
+        ExcelUtil<CompanyProduct> util = new ExcelUtil<CompanyProduct>(CompanyProduct.class);
+        return util.exportExcel(list, "产品数据");
+    }
+
+    /**
+     * 获取产品详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyProduct:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(companyProductService.selectCompanyProductById(id));
+    }
+
+    /**
+     * 新增产品
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyProduct:add')")
+    @Log(title = "产品", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody CompanyProduct companyProduct)
+    {
+        return toAjax(companyProductService.insertCompanyProduct(companyProduct));
+    }
+
+    /**
+     * 修改产品
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyProduct:edit')")
+    @Log(title = "产品", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody CompanyProduct companyProduct)
+    {
+        return toAjax(companyProductService.updateCompanyProduct(companyProduct));
+    }
+
+    /**
+     * 删除产品
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyProduct:remove')")
+    @Log(title = "产品", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(companyProductService.deleteCompanyProductByIds(ids));
+    }
+}

+ 97 - 0
fs-admin/src/main/java/com/fs/company/controller/CompanyRoleController.java

@@ -0,0 +1,97 @@
+package com.fs.company.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.CompanyRole;
+import com.fs.company.service.ICompanyRoleService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 角色信息Controller
+ *
+ * @author fs
+ * @date 2021-10-04
+ */
+@RestController
+@RequestMapping("/company/companyRole")
+public class CompanyRoleController extends BaseController
+{
+    @Autowired
+    private ICompanyRoleService companyRoleService;
+
+    /**
+     * 查询角色信息列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRole:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CompanyRole companyRole)
+    {
+        startPage();
+        List<CompanyRole> list = companyRoleService.selectCompanyRoleList(companyRole);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出角色信息列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRole:export')")
+    @Log(title = "角色信息", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CompanyRole companyRole)
+    {
+        List<CompanyRole> list = companyRoleService.selectCompanyRoleList(companyRole);
+        ExcelUtil<CompanyRole> util = new ExcelUtil<CompanyRole>(CompanyRole.class);
+        return util.exportExcel(list, "companyRole");
+    }
+
+    /**
+     * 获取角色信息详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRole:query')")
+    @GetMapping(value = "/{roleId}")
+    public AjaxResult getInfo(@PathVariable("roleId") Long roleId)
+    {
+        return AjaxResult.success(companyRoleService.selectCompanyRoleById(roleId));
+    }
+
+    /**
+     * 新增角色信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRole:add')")
+    @Log(title = "角色信息", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody CompanyRole companyRole)
+    {
+        return toAjax(companyRoleService.insertCompanyRole(companyRole));
+    }
+
+    /**
+     * 修改角色信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRole:edit')")
+    @Log(title = "角色信息", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody CompanyRole companyRole)
+    {
+        return toAjax(companyRoleService.updateCompanyRole(companyRole));
+    }
+
+    /**
+     * 删除角色信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRole:remove')")
+    @Log(title = "角色信息", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{roleIds}")
+    public AjaxResult remove(@PathVariable Long[] roleIds)
+    {
+        return toAjax(companyRoleService.deleteCompanyRoleByIds(roleIds));
+    }
+}

+ 97 - 0
fs-admin/src/main/java/com/fs/company/controller/CompanyRoleDeptController.java

@@ -0,0 +1,97 @@
+package com.fs.company.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.CompanyRoleDept;
+import com.fs.company.service.ICompanyRoleDeptService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 角色和部门关联Controller
+ *
+ * @author fs
+ * @date 2021-10-04
+ */
+@RestController
+@RequestMapping("/company/companyRoleDept")
+public class CompanyRoleDeptController extends BaseController
+{
+    @Autowired
+    private ICompanyRoleDeptService companyRoleDeptService;
+
+    /**
+     * 查询角色和部门关联列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRoleDept:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CompanyRoleDept companyRoleDept)
+    {
+        startPage();
+        List<CompanyRoleDept> list = companyRoleDeptService.selectCompanyRoleDeptList(companyRoleDept);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出角色和部门关联列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRoleDept:export')")
+    @Log(title = "角色和部门关联", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CompanyRoleDept companyRoleDept)
+    {
+        List<CompanyRoleDept> list = companyRoleDeptService.selectCompanyRoleDeptList(companyRoleDept);
+        ExcelUtil<CompanyRoleDept> util = new ExcelUtil<CompanyRoleDept>(CompanyRoleDept.class);
+        return util.exportExcel(list, "companyRoleDept");
+    }
+
+    /**
+     * 获取角色和部门关联详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRoleDept:query')")
+    @GetMapping(value = "/{roleId}")
+    public AjaxResult getInfo(@PathVariable("roleId") Long roleId)
+    {
+        return AjaxResult.success(companyRoleDeptService.selectCompanyRoleDeptById(roleId));
+    }
+
+    /**
+     * 新增角色和部门关联
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRoleDept:add')")
+    @Log(title = "角色和部门关联", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody CompanyRoleDept companyRoleDept)
+    {
+        return toAjax(companyRoleDeptService.insertCompanyRoleDept(companyRoleDept));
+    }
+
+    /**
+     * 修改角色和部门关联
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRoleDept:edit')")
+    @Log(title = "角色和部门关联", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody CompanyRoleDept companyRoleDept)
+    {
+        return toAjax(companyRoleDeptService.updateCompanyRoleDept(companyRoleDept));
+    }
+
+    /**
+     * 删除角色和部门关联
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRoleDept:remove')")
+    @Log(title = "角色和部门关联", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{roleIds}")
+    public AjaxResult remove(@PathVariable Long[] roleIds)
+    {
+        return toAjax(companyRoleDeptService.deleteCompanyRoleDeptByIds(roleIds));
+    }
+}

+ 97 - 0
fs-admin/src/main/java/com/fs/company/controller/CompanyRoleMenuController.java

@@ -0,0 +1,97 @@
+package com.fs.company.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.CompanyRoleMenu;
+import com.fs.company.service.ICompanyRoleMenuService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 角色和菜单关联Controller
+ *
+ * @author fs
+ * @date 2021-10-04
+ */
+@RestController
+@RequestMapping("/company/companyRoleMenu")
+public class CompanyRoleMenuController extends BaseController
+{
+    @Autowired
+    private ICompanyRoleMenuService companyRoleMenuService;
+
+    /**
+     * 查询角色和菜单关联列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRoleMenu:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CompanyRoleMenu companyRoleMenu)
+    {
+        startPage();
+        List<CompanyRoleMenu> list = companyRoleMenuService.selectCompanyRoleMenuList(companyRoleMenu);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出角色和菜单关联列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRoleMenu:export')")
+    @Log(title = "角色和菜单关联", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CompanyRoleMenu companyRoleMenu)
+    {
+        List<CompanyRoleMenu> list = companyRoleMenuService.selectCompanyRoleMenuList(companyRoleMenu);
+        ExcelUtil<CompanyRoleMenu> util = new ExcelUtil<CompanyRoleMenu>(CompanyRoleMenu.class);
+        return util.exportExcel(list, "companyRoleMenu");
+    }
+
+    /**
+     * 获取角色和菜单关联详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRoleMenu:query')")
+    @GetMapping(value = "/{roleId}")
+    public AjaxResult getInfo(@PathVariable("roleId") Long roleId)
+    {
+        return AjaxResult.success(companyRoleMenuService.selectCompanyRoleMenuById(roleId));
+    }
+
+    /**
+     * 新增角色和菜单关联
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRoleMenu:add')")
+    @Log(title = "角色和菜单关联", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody CompanyRoleMenu companyRoleMenu)
+    {
+        return toAjax(companyRoleMenuService.insertCompanyRoleMenu(companyRoleMenu));
+    }
+
+    /**
+     * 修改角色和菜单关联
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRoleMenu:edit')")
+    @Log(title = "角色和菜单关联", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody CompanyRoleMenu companyRoleMenu)
+    {
+        return toAjax(companyRoleMenuService.updateCompanyRoleMenu(companyRoleMenu));
+    }
+
+    /**
+     * 删除角色和菜单关联
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRoleMenu:remove')")
+    @Log(title = "角色和菜单关联", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{roleIds}")
+    public AjaxResult remove(@PathVariable Long[] roleIds)
+    {
+        return toAjax(companyRoleMenuService.deleteCompanyRoleMenuByIds(roleIds));
+    }
+}

+ 138 - 0
fs-admin/src/main/java/com/fs/company/controller/CompanyUserController.java

@@ -0,0 +1,138 @@
+package com.fs.company.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.constant.UserConstants;
+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.company.domain.CompanyUser;
+import com.fs.company.service.ICompanyUserService;
+import com.fs.company.vo.CompanyUserOptionRespVO;
+import org.apache.http.util.Asserts;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 企业员工信息Controller
+ *
+ * @author fs
+ * @date 2021-10-04
+ */
+@RestController
+@RequestMapping("/company/companyUser")
+public class CompanyUserController extends BaseController {
+    @Autowired
+    private ICompanyUserService companyUserService;
+
+    /**
+     * 查询企业员工信息列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyUser:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CompanyUser companyUser) {
+        startPage();
+        List<CompanyUser> list = companyUserService.selectCompanyUserList(companyUser);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出企业员工信息列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyUser:export')")
+    @Log(title = "企业员工信息", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CompanyUser companyUser) {
+        List<CompanyUser> list = companyUserService.selectCompanyUserList(companyUser);
+        ExcelUtil<CompanyUser> util = new ExcelUtil<CompanyUser>(CompanyUser.class);
+        return util.exportExcel(list, "companyUser");
+    }
+
+    /**
+     * 获取企业员工信息详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyUser:query')")
+    @GetMapping(value = "/{userId}")
+    public AjaxResult getInfo(@PathVariable("userId") Long userId) {
+        return AjaxResult.success(companyUserService.selectCompanyUserById(userId));
+    }
+
+    /**
+     * 新增企业员工信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyUser:add')")
+    @Log(title = "企业员工信息", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody CompanyUser companyUser) {
+        if (UserConstants.NOT_UNIQUE.equals(companyUserService.checkUserNameUnique(companyUser.getUserName()))) {
+            return AjaxResult.error("新增用户'" + companyUser.getUserName() + "'失败,登录账号已存在");
+        }
+        return toAjax(companyUserService.insertCompanyUser(companyUser));
+    }
+
+    /**
+     * 修改企业员工信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyUser:edit')")
+    @Log(title = "企业员工信息", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody CompanyUser companyUser) {
+        return toAjax(companyUserService.updateCompanyUser(companyUser));
+    }
+
+    /**
+     * 删除企业员工信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyUser:remove')")
+    @Log(title = "企业员工信息", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{userIds}")
+    public AjaxResult remove(@PathVariable Long[] userIds) {
+        return toAjax(companyUserService.deleteCompanyUserByIds(userIds));
+    }
+
+    @GetMapping("/getAllUserlist")
+    public R getAllUserlist(@RequestParam Long companyId) {
+        CompanyUser map = new CompanyUser();
+        map.setCompanyId(companyId);
+        List<CompanyUser> list = companyUserService.selectCompanyUserList(map);
+        return R.ok().put("data", list);
+    }
+
+
+    @GetMapping("/getUserOptionVOList")
+    public R getCompanyUserOptionVOList(@RequestParam(value = "companyNickName") String companyNickName,
+                                        @RequestParam(required = false,value = "companyId") Long companyId) {
+        List<CompanyUserOptionRespVO> list = companyUserService.getUserOptionVOList(companyNickName,companyId);
+        return R.ok().put("data", list);
+    }
+
+
+    /**
+     * 获取业务员的产品
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:user:query')")
+    @GetMapping(value = "getCompanyUserProductList")
+    public AjaxResult getCompanyUserProductList(@RequestParam("id") Long id) {
+        return AjaxResult.success(companyUserService.getCompanyUserProductList(id));
+    }
+
+    /**
+     * 查询当前公司的所有客服
+     * @param companyId 公司ID
+     * @return 客服列表
+     */
+    @GetMapping("/queryAllCompanyUser")
+    public R queryAllCompanyUser(@RequestParam("companyId") Long companyId) {
+        Asserts.notNull(companyId,"公司ID不能为空!");
+
+        List<CompanyUser> companyUsers = companyUserService.queryAllCompanyUser(companyId);
+        return R.ok().put("data", companyUsers);
+    }
+
+
+}

+ 97 - 0
fs-admin/src/main/java/com/fs/company/controller/CompanyUserPostController.java

@@ -0,0 +1,97 @@
+package com.fs.company.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.CompanyUserPost;
+import com.fs.company.service.ICompanyUserPostService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 用户与岗位关联Controller
+ *
+ * @author fs
+ * @date 2021-10-04
+ */
+@RestController
+@RequestMapping("/company/companyUserPost")
+public class CompanyUserPostController extends BaseController
+{
+    @Autowired
+    private ICompanyUserPostService companyUserPostService;
+
+    /**
+     * 查询用户与岗位关联列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyUserPost:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CompanyUserPost companyUserPost)
+    {
+        startPage();
+        List<CompanyUserPost> list = companyUserPostService.selectCompanyUserPostList(companyUserPost);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出用户与岗位关联列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyUserPost:export')")
+    @Log(title = "用户与岗位关联", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CompanyUserPost companyUserPost)
+    {
+        List<CompanyUserPost> list = companyUserPostService.selectCompanyUserPostList(companyUserPost);
+        ExcelUtil<CompanyUserPost> util = new ExcelUtil<CompanyUserPost>(CompanyUserPost.class);
+        return util.exportExcel(list, "companyUserPost");
+    }
+
+    /**
+     * 获取用户与岗位关联详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyUserPost:query')")
+    @GetMapping(value = "/{userId}")
+    public AjaxResult getInfo(@PathVariable("userId") Long userId)
+    {
+        return AjaxResult.success(companyUserPostService.selectCompanyUserPostById(userId));
+    }
+
+    /**
+     * 新增用户与岗位关联
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyUserPost:add')")
+    @Log(title = "用户与岗位关联", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody CompanyUserPost companyUserPost)
+    {
+        return toAjax(companyUserPostService.insertCompanyUserPost(companyUserPost));
+    }
+
+    /**
+     * 修改用户与岗位关联
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyUserPost:edit')")
+    @Log(title = "用户与岗位关联", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody CompanyUserPost companyUserPost)
+    {
+        return toAjax(companyUserPostService.updateCompanyUserPost(companyUserPost));
+    }
+
+    /**
+     * 删除用户与岗位关联
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyUserPost:remove')")
+    @Log(title = "用户与岗位关联", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{userIds}")
+    public AjaxResult remove(@PathVariable Long[] userIds)
+    {
+        return toAjax(companyUserPostService.deleteCompanyUserPostByIds(userIds));
+    }
+}

+ 97 - 0
fs-admin/src/main/java/com/fs/company/controller/CompanyUserRoleController.java

@@ -0,0 +1,97 @@
+package com.fs.company.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.CompanyUserRole;
+import com.fs.company.service.ICompanyUserRoleService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 用户和角色关联Controller
+ *
+ * @author fs
+ * @date 2021-10-04
+ */
+@RestController
+@RequestMapping("/company/companyUserRole")
+public class CompanyUserRoleController extends BaseController
+{
+    @Autowired
+    private ICompanyUserRoleService companyUserRoleService;
+
+    /**
+     * 查询用户和角色关联列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyUserRole:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CompanyUserRole companyUserRole)
+    {
+        startPage();
+        List<CompanyUserRole> list = companyUserRoleService.selectCompanyUserRoleList(companyUserRole);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出用户和角色关联列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyUserRole:export')")
+    @Log(title = "用户和角色关联", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CompanyUserRole companyUserRole)
+    {
+        List<CompanyUserRole> list = companyUserRoleService.selectCompanyUserRoleList(companyUserRole);
+        ExcelUtil<CompanyUserRole> util = new ExcelUtil<CompanyUserRole>(CompanyUserRole.class);
+        return util.exportExcel(list, "companyUserRole");
+    }
+
+    /**
+     * 获取用户和角色关联详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyUserRole:query')")
+    @GetMapping(value = "/{userId}")
+    public AjaxResult getInfo(@PathVariable("userId") Long userId)
+    {
+        return AjaxResult.success(companyUserRoleService.selectCompanyUserRoleById(userId));
+    }
+
+    /**
+     * 新增用户和角色关联
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyUserRole:add')")
+    @Log(title = "用户和角色关联", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody CompanyUserRole companyUserRole)
+    {
+        return toAjax(companyUserRoleService.insertCompanyUserRole(companyUserRole));
+    }
+
+    /**
+     * 修改用户和角色关联
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyUserRole:edit')")
+    @Log(title = "用户和角色关联", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody CompanyUserRole companyUserRole)
+    {
+        return toAjax(companyUserRoleService.updateCompanyUserRole(companyUserRole));
+    }
+
+    /**
+     * 删除用户和角色关联
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyUserRole:remove')")
+    @Log(title = "用户和角色关联", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{userIds}")
+    public AjaxResult remove(@PathVariable Long[] userIds)
+    {
+        return toAjax(companyUserRoleService.deleteCompanyUserRoleByIds(userIds));
+    }
+}

+ 97 - 0
fs-admin/src/main/java/com/fs/delivery/controller/CtArticleController.java

@@ -0,0 +1,97 @@
+package com.fs.delivery.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.delivery.domain.CtArticle;
+import com.fs.delivery.service.ICtArticleService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 文章Controller
+ * 
+ * @author fs
+ * @date 2026-01-20
+ */
+@RestController
+@RequestMapping("/delivery/article")
+public class CtArticleController extends BaseController
+{
+    @Autowired
+    private ICtArticleService ctArticleService;
+
+    /**
+     * 查询文章列表
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:article:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CtArticle ctArticle)
+    {
+        startPage();
+        List<CtArticle> list = ctArticleService.selectCtArticleList(ctArticle);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出文章列表
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:article:export')")
+    @Log(title = "文章", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CtArticle ctArticle)
+    {
+        List<CtArticle> list = ctArticleService.selectCtArticleList(ctArticle);
+        ExcelUtil<CtArticle> util = new ExcelUtil<CtArticle>(CtArticle.class);
+        return util.exportExcel(list, "文章数据");
+    }
+
+    /**
+     * 获取文章详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:article:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(ctArticleService.selectCtArticleById(id));
+    }
+
+    /**
+     * 新增文章
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:article:add')")
+    @Log(title = "文章", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody CtArticle ctArticle)
+    {
+        return toAjax(ctArticleService.insertCtArticle(ctArticle));
+    }
+
+    /**
+     * 修改文章
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:article:edit')")
+    @Log(title = "文章", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody CtArticle ctArticle)
+    {
+        return toAjax(ctArticleService.updateCtArticle(ctArticle));
+    }
+
+    /**
+     * 删除文章
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:article:remove')")
+    @Log(title = "文章", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(ctArticleService.deleteCtArticleByIds(ids));
+    }
+}

+ 103 - 0
fs-admin/src/main/java/com/fs/delivery/controller/CtCloudClassController.java

@@ -0,0 +1,103 @@
+package com.fs.delivery.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.delivery.domain.CtCloudClass;
+import com.fs.delivery.service.ICtCloudClassService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 空中课堂内容维护Controller
+ * 
+ * @author fs
+ * @date 2026-02-13
+ */
+@RestController
+@RequestMapping("/delivery/class")
+public class CtCloudClassController extends BaseController
+{
+    @Autowired
+    private ICtCloudClassService ctCloudClassService;
+
+    /**
+     * 查询空中课堂内容维护列表
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:class:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CtCloudClass ctCloudClass)
+    {
+        startPage();
+        List<CtCloudClass> list = ctCloudClassService.selectCtCloudClassList(ctCloudClass);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出空中课堂内容维护列表
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:class:export')")
+    @Log(title = "空中课堂内容维护", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CtCloudClass ctCloudClass)
+    {
+        List<CtCloudClass> list = ctCloudClassService.selectCtCloudClassList(ctCloudClass);
+        ExcelUtil<CtCloudClass> util = new ExcelUtil<CtCloudClass>(CtCloudClass.class);
+        return util.exportExcel(list, "空中课堂内容维护数据");
+    }
+
+    /**
+     * 获取空中课堂内容维护详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:class:query')")
+    @GetMapping(value = "/{cloudClassId}")
+    public AjaxResult getInfo(@PathVariable("cloudClassId") Long cloudClassId)
+    {
+        return AjaxResult.success(ctCloudClassService.selectCtCloudClassByCloudClassId(cloudClassId));
+    }
+
+    /**
+     * 新增空中课堂内容维护
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:class:add')")
+    @Log(title = "空中课堂内容维护", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody CtCloudClass ctCloudClass)
+    {
+        return toAjax(ctCloudClassService.insertCtCloudClass(ctCloudClass));
+    }
+
+    /**
+     * 修改空中课堂内容维护
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:class:edit')")
+    @Log(title = "空中课堂内容维护", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody CtCloudClass ctCloudClass)
+    {
+        return toAjax(ctCloudClassService.updateCtCloudClass(ctCloudClass));
+    }
+
+    /**
+     * 删除空中课堂内容维护
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:class:remove')")
+    @Log(title = "空中课堂内容维护", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{cloudClassIds}")
+    public AjaxResult remove(@PathVariable Long[] cloudClassIds)
+    {
+        return toAjax(ctCloudClassService.deleteCtCloudClassByCloudClassIds(cloudClassIds));
+    }
+}

+ 103 - 0
fs-admin/src/main/java/com/fs/delivery/controller/CtLongVideoController.java

@@ -0,0 +1,103 @@
+package com.fs.delivery.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.delivery.domain.CtLongVideo;
+import com.fs.delivery.service.ICtLongVideoService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 长视频Controller
+ * 
+ * @author fs
+ * @date 2026-01-22
+ */
+@RestController
+@RequestMapping("/delivery/video")
+public class CtLongVideoController extends BaseController
+{
+    @Autowired
+    private ICtLongVideoService ctLongVideoService;
+
+    /**
+     * 查询长视频列表
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:video:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CtLongVideo ctLongVideo)
+    {
+        startPage();
+        List<CtLongVideo> list = ctLongVideoService.selectCtLongVideoList(ctLongVideo);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出长视频列表
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:video:export')")
+    @Log(title = "长视频", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CtLongVideo ctLongVideo)
+    {
+        List<CtLongVideo> list = ctLongVideoService.selectCtLongVideoList(ctLongVideo);
+        ExcelUtil<CtLongVideo> util = new ExcelUtil<CtLongVideo>(CtLongVideo.class);
+        return util.exportExcel(list, "长视频数据");
+    }
+
+    /**
+     * 获取长视频详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:video:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(ctLongVideoService.selectCtLongVideoById(id));
+    }
+
+    /**
+     * 新增长视频
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:video:add')")
+    @Log(title = "长视频", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody CtLongVideo ctLongVideo)
+    {
+        return toAjax(ctLongVideoService.insertCtLongVideo(ctLongVideo));
+    }
+
+    /**
+     * 修改长视频
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:video:edit')")
+    @Log(title = "长视频", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody CtLongVideo ctLongVideo)
+    {
+        return toAjax(ctLongVideoService.updateCtLongVideo(ctLongVideo));
+    }
+
+    /**
+     * 删除长视频
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:video:remove')")
+    @Log(title = "长视频", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(ctLongVideoService.deleteCtLongVideoByIds(ids));
+    }
+}

+ 103 - 0
fs-admin/src/main/java/com/fs/delivery/controller/CtShortVideoController.java

@@ -0,0 +1,103 @@
+package com.fs.delivery.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.delivery.domain.CtShortVideo;
+import com.fs.delivery.service.ICtShortVideoService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 长视频Controller
+ * 
+ * @author fs
+ * @date 2026-01-22
+ */
+@RestController
+@RequestMapping("/delivery/shortVideo")
+public class CtShortVideoController extends BaseController
+{
+    @Autowired
+    private ICtShortVideoService ctShortVideoService;
+
+    /**
+     * 查询长视频列表
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:shortVideo:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CtShortVideo ctShortVideo)
+    {
+        startPage();
+        List<CtShortVideo> list = ctShortVideoService.selectCtShortVideoList(ctShortVideo);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出长视频列表
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:shortVideo:export')")
+    @Log(title = "长视频", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CtShortVideo ctShortVideo)
+    {
+        List<CtShortVideo> list = ctShortVideoService.selectCtShortVideoList(ctShortVideo);
+        ExcelUtil<CtShortVideo> util = new ExcelUtil<CtShortVideo>(CtShortVideo.class);
+        return util.exportExcel(list, "长视频数据");
+    }
+
+    /**
+     * 获取长视频详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:shortVideo:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(ctShortVideoService.selectCtShortVideoById(id));
+    }
+
+    /**
+     * 新增长视频
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:shortVideo:add')")
+    @Log(title = "长视频", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody CtShortVideo ctShortVideo)
+    {
+        return toAjax(ctShortVideoService.insertCtShortVideo(ctShortVideo));
+    }
+
+    /**
+     * 修改长视频
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:shortVideo:edit')")
+    @Log(title = "长视频", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody CtShortVideo ctShortVideo)
+    {
+        return toAjax(ctShortVideoService.updateCtShortVideo(ctShortVideo));
+    }
+
+    /**
+     * 删除长视频
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:shortVideo:remove')")
+    @Log(title = "长视频", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(ctShortVideoService.deleteCtShortVideoByIds(ids));
+    }
+}

+ 103 - 0
fs-admin/src/main/java/com/fs/delivery/controller/TagController.java

@@ -0,0 +1,103 @@
+package com.fs.delivery.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.delivery.domain.Tag;
+import com.fs.delivery.service.ITagService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 标签Controller
+ * 
+ * @author fs
+ * @date 2026-01-20
+ */
+@RestController
+@RequestMapping("/delivery/tag")
+public class TagController extends BaseController
+{
+    @Autowired
+    private ITagService tagService;
+
+    /**
+     * 查询标签列表
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:tag:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(Tag tag)
+    {
+        startPage();
+        List<Tag> list = tagService.selectTagList(tag);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出标签列表
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:tag:export')")
+    @Log(title = "标签", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(Tag tag)
+    {
+        List<Tag> list = tagService.selectTagList(tag);
+        ExcelUtil<Tag> util = new ExcelUtil<Tag>(Tag.class);
+        return util.exportExcel(list, "标签数据");
+    }
+
+    /**
+     * 获取标签详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:tag:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(tagService.selectTagById(id));
+    }
+
+    /**
+     * 新增标签
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:tag:add')")
+    @Log(title = "标签", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody Tag tag)
+    {
+        return toAjax(tagService.insertTag(tag));
+    }
+
+    /**
+     * 修改标签
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:tag:edit')")
+    @Log(title = "标签", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody Tag tag)
+    {
+        return toAjax(tagService.updateTag(tag));
+    }
+
+    /**
+     * 删除标签
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:tag:remove')")
+    @Log(title = "标签", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(tagService.deleteTagByIds(ids));
+    }
+}

+ 103 - 0
fs-admin/src/main/java/com/fs/delivery/controller/TagGroupController.java

@@ -0,0 +1,103 @@
+package com.fs.delivery.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.delivery.domain.TagGroup;
+import com.fs.delivery.service.ITagGroupService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 标签分组Controller
+ * 
+ * @author fs
+ * @date 2026-01-20
+ */
+@RestController
+@RequestMapping("/delivery/group")
+public class TagGroupController extends BaseController
+{
+    @Autowired
+    private ITagGroupService tagGroupService;
+
+    /**
+     * 查询标签分组列表
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:group:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(TagGroup tagGroup)
+    {
+        startPage();
+        List<TagGroup> list = tagGroupService.selectTagGroupList(tagGroup);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出标签分组列表
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:group:export')")
+    @Log(title = "标签分组", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(TagGroup tagGroup)
+    {
+        List<TagGroup> list = tagGroupService.selectTagGroupList(tagGroup);
+        ExcelUtil<TagGroup> util = new ExcelUtil<TagGroup>(TagGroup.class);
+        return util.exportExcel(list, "标签分组数据");
+    }
+
+    /**
+     * 获取标签分组详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:group:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(tagGroupService.selectTagGroupById(id));
+    }
+
+    /**
+     * 新增标签分组
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:group:add')")
+    @Log(title = "标签分组", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody TagGroup tagGroup)
+    {
+        return toAjax(tagGroupService.insertTagGroup(tagGroup));
+    }
+
+    /**
+     * 修改标签分组
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:group:edit')")
+    @Log(title = "标签分组", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody TagGroup tagGroup)
+    {
+        return toAjax(tagGroupService.updateTagGroup(tagGroup));
+    }
+
+    /**
+     * 删除标签分组
+     */
+    @PreAuthorize("@ss.hasPermi('delivery:group:remove')")
+    @Log(title = "标签分组", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(tagGroupService.deleteTagGroupByIds(ids));
+    }
+}

+ 103 - 0
fs-admin/src/main/java/com/fs/doctor/controller/DoctorBalanceLogController.java

@@ -0,0 +1,103 @@
+package com.fs.doctor.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.doctor.domain.DoctorBalanceLog;
+import com.fs.doctor.service.IDoctorBalanceLogService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 医生积分变动记录Controller
+ * 
+ * @author fs
+ * @date 2026-01-26
+ */
+@RestController
+@RequestMapping("/doctor/log")
+public class DoctorBalanceLogController extends BaseController
+{
+    @Autowired
+    private IDoctorBalanceLogService doctorBalanceLogService;
+
+    /**
+     * 查询医生积分变动记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:log:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(DoctorBalanceLog doctorBalanceLog)
+    {
+        startPage();
+        List<DoctorBalanceLog> list = doctorBalanceLogService.selectDoctorBalanceLogList(doctorBalanceLog);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出医生积分变动记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:log:export')")
+    @Log(title = "医生积分变动记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(DoctorBalanceLog doctorBalanceLog)
+    {
+        List<DoctorBalanceLog> list = doctorBalanceLogService.selectDoctorBalanceLogList(doctorBalanceLog);
+        ExcelUtil<DoctorBalanceLog> util = new ExcelUtil<DoctorBalanceLog>(DoctorBalanceLog.class);
+        return util.exportExcel(list, "医生积分变动记录数据");
+    }
+
+    /**
+     * 获取医生积分变动记录详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:log:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") String id)
+    {
+        return AjaxResult.success(doctorBalanceLogService.selectDoctorBalanceLogById(id));
+    }
+
+    /**
+     * 新增医生积分变动记录
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:log:add')")
+    @Log(title = "医生积分变动记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody DoctorBalanceLog doctorBalanceLog)
+    {
+        return toAjax(doctorBalanceLogService.insertDoctorBalanceLog(doctorBalanceLog));
+    }
+
+    /**
+     * 修改医生积分变动记录
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:log:edit')")
+    @Log(title = "医生积分变动记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody DoctorBalanceLog doctorBalanceLog)
+    {
+        return toAjax(doctorBalanceLogService.updateDoctorBalanceLog(doctorBalanceLog));
+    }
+
+    /**
+     * 删除医生积分变动记录
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:log:remove')")
+    @Log(title = "医生积分变动记录", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable String[] ids)
+    {
+        return toAjax(doctorBalanceLogService.deleteDoctorBalanceLogByIds(ids));
+    }
+}

+ 142 - 0
fs-admin/src/main/java/com/fs/doctor/controller/DoctorCompanyUserController.java

@@ -0,0 +1,142 @@
+package com.fs.doctor.controller;
+
+import com.fs.common.BeanCopyUtils;
+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.vo.DoctorCompanyUserVO;
+import com.fs.doctor.domain.DoctorCompanyUser;
+import com.fs.doctor.service.IDoctorCompanyUserService;
+import com.fs.doctor.vo.ProductGroupReqVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 讲者业务员绑定Controller
+ * 
+ * @author fs
+ * @date 2025-12-30
+ */
+@RestController
+@RequestMapping("/doctor/user")
+public class DoctorCompanyUserController extends BaseController
+{
+    @Autowired
+    private IDoctorCompanyUserService doctorCompanyUserService;
+
+    /**
+     * 查询讲者业务员绑定列表
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:user:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(DoctorCompanyUser doctorCompanyUser)
+    {
+        startPage();
+        List<DoctorCompanyUserVO> doctorCompanyUserVOS = doctorCompanyUserService.selectDoctorCompanyUserList(doctorCompanyUser);
+        return getDataTable(doctorCompanyUserVOS);
+    }
+
+    /**
+     * 导出讲者业务员绑定列表
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:user:export')")
+    @Log(title = "讲者业务员绑定", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(DoctorCompanyUser doctorCompanyUser)
+    {
+        List<DoctorCompanyUserVO> doctorCompanyUserVOS = doctorCompanyUserService.selectDoctorCompanyUserList(doctorCompanyUser);
+        ExcelUtil<DoctorCompanyUserVO> util = new ExcelUtil<DoctorCompanyUserVO>(DoctorCompanyUserVO.class);
+        return util.exportExcel(doctorCompanyUserVOS, "讲者业务员绑定数据");
+    }
+
+    /**
+     * 获取讲者业务员绑定详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:user:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(doctorCompanyUserService.selectDoctorCompanyUserById(id));
+    }
+
+    /**
+     * 新增讲者业务员绑定
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:user:add')")
+    @Log(title = "讲者业务员绑定", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody DoctorCompanyUser doctorCompanyUser)
+    {
+        return toAjax(doctorCompanyUserService.insertDoctorCompanyUser(doctorCompanyUser));
+    }
+
+    /**
+     * 修改讲者业务员绑定
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:user:edit')")
+    @Log(title = "讲者业务员绑定", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody DoctorCompanyUser doctorCompanyUser)
+    {
+        return toAjax(doctorCompanyUserService.updateDoctorCompanyUser(doctorCompanyUser));
+    }
+
+    /**
+     * 删除讲者业务员绑定
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:user:remove')")
+    @Log(title = "讲者业务员绑定", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        return toAjax(doctorCompanyUserService.deleteDoctorCompanyUserByIds(ids));
+    }
+
+    @GetMapping("/getProductGroup")
+    @Log(title = "获取产品组")
+    public AjaxResult getProductGroup(ProductGroupReqVO req) {
+
+        String pg = doctorCompanyUserService.queryProductGroup(req.getCompanyId(), req.getDoctorId());
+        return AjaxResult.success(pg);
+    }
+
+
+    /**
+     * 按公司+业务员联想查询讲者(只返回该业务员已绑定的讲者,且讲者审核通过)
+     * 参数:
+     *  companyId(必填)
+     *  companyUserId(必填)
+     *  keyword(可选:讲者姓名/手机号模糊)
+     */
+    @GetMapping("/options/optionsByCompanyUser")
+    public AjaxResult doctorOptionsByCompanyUser(
+            @RequestParam Long companyId,
+            @RequestParam Long companyUserId,
+            @RequestParam(required = false) String productCode,
+            @RequestParam(required = false) String keyword
+    ) {
+        return AjaxResult.success(
+                doctorCompanyUserService.getDoctorOptionsByCompanyUser(companyId, companyUserId,productCode, keyword)
+        );
+    }
+
+
+
+    @GetMapping("/options/productoptionsByCompanyUser")
+    public AjaxResult productOptionsByCompanyUser(
+            @RequestParam Long companyId,
+            @RequestParam Long companyUserId,
+            @RequestParam(required = false) String keyword
+    ) {
+        return AjaxResult.success(
+                doctorCompanyUserService.getProductOptionsByCompanyUser(companyId, companyUserId, keyword)
+        );
+    }
+
+
+}

+ 144 - 0
fs-admin/src/main/java/com/fs/doctor/controller/DoctorController.java

@@ -0,0 +1,144 @@
+package com.fs.doctor.controller;
+
+import com.fs.common.BeanCopyUtils;
+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.doctor.domain.Doctor;
+import com.fs.doctor.service.IDoctorService;
+import com.fs.doctor.vo.*;
+import com.fs.task.vo.TaskFinishAuditReqVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 讲者Controller
+ *
+ * @author fs
+ * @date 2025-12-30
+ */
+@RestController
+@RequestMapping("/doctor/doctor")
+public class DoctorController extends BaseController
+{
+    @Autowired
+    private IDoctorService doctorService;
+
+    /**
+     * 查询讲者列表
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:doctor:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(Doctor doctor)
+    {
+        startPage();
+        List<Doctor> list = doctorService.selectDoctorList(doctor);
+        List<DoctorVO> doctorVOS = BeanCopyUtils.copyList(list, DoctorVO.class);
+
+        return getDataTable(doctorVOS);
+    }
+
+    /**
+     * 导出讲者列表
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:doctor:export')")
+    @Log(title = "讲者", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(Doctor doctor)
+    {
+        List<Doctor> list = doctorService.selectDoctorList(doctor);
+        ExcelUtil<Doctor> util = new ExcelUtil<Doctor>(Doctor.class);
+        return util.exportExcel(list, "讲者数据");
+    }
+
+    /**
+     * 获取讲者详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:doctor:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(doctorService.selectDoctorById(id));
+    }
+
+    /**
+     * 新增讲者
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:doctor:add')")
+    @Log(title = "讲者", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody DoctorVO doctorVO)
+    {
+       return toAjax( doctorService.add(doctorVO));
+    }
+
+    /**
+     * 修改讲者
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:doctor:edit')")
+    @Log(title = "讲者", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody Doctor doctor)
+    {
+        return toAjax(doctorService.updateDoctor(doctor));
+    }
+
+    /**
+     * 删除讲者
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:doctor:remove')")
+    @Log(title = "讲者", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(doctorService.deleteDoctorByIds(ids));
+    }
+
+
+        @PreAuthorize("@ss.hasPermi('doctor:doctor:list')")
+        @GetMapping("/getDoctorOptionVOList")
+        public AjaxResult getDoctorOptionVOList(DoctorOptionReqVO doctorOptionReqVO)
+        {
+            List<DoctorOptionRespVO> list = doctorService.getDoctorOptionVOList(doctorOptionReqVO);
+            return AjaxResult.success(list);
+        }
+
+
+    /**
+     * 查询讲者审核页面信息
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:doctor:audit')")
+    @GetMapping("/audit/{id}")
+    public AjaxResult getAuditInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(doctorService.selectDoctorAuditInfo(id));
+    }
+
+
+    @PreAuthorize("@ss.hasPermi('doctor:doctor:audit')")
+    @Log(title = "完成审核处理", businessType = BusinessType.UPDATE)
+    @PostMapping("doAudit")
+    public void doAudit(@RequestBody DoctorDoAuditReqVO doctorDoAuditReqVO){
+        doctorService.doAudit(doctorDoAuditReqVO);
+    }
+
+
+
+    /**
+     * 修改讲者密码
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:doctor:edit')")
+    @Log(title = "讲者-修改密码", businessType = BusinessType.UPDATE)
+    @PostMapping("/resetPwd")
+    public AjaxResult resetPwd(@RequestBody DoctorResetPwdReqVO req) {
+        return toAjax(doctorService.resetPwd(req));
+    }
+
+
+}

+ 112 - 0
fs-admin/src/main/java/com/fs/doctor/controller/DoctorLevelApplyController.java

@@ -0,0 +1,112 @@
+package com.fs.doctor.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.doctor.domain.DoctorLevelApply;
+import com.fs.doctor.service.IDoctorLevelApplyService;
+import com.fs.doctor.vo.DoctorApplyAuditReqVO;
+import com.fs.doctor.vo.DoctorLevelApplyVO;
+import org.apache.ibatis.annotations.Update;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 讲者定级申请Controller
+ * 
+ * @author fs
+ * @date 2025-12-30
+ */
+@RestController
+@RequestMapping("/doctor/apply")
+public class DoctorLevelApplyController extends BaseController
+{
+    @Autowired
+    private IDoctorLevelApplyService doctorLevelApplyService;
+
+    /**
+     * 查询讲者定级申请列表
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:apply:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(DoctorLevelApply doctorLevelApply)
+    {
+        startPage();
+        List<DoctorLevelApply> list = doctorLevelApplyService.selectDoctorLevelApplyList(doctorLevelApply);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出讲者定级申请列表
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:apply:export')")
+    @Log(title = "讲者定级申请", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(DoctorLevelApply doctorLevelApply)
+    {
+        List<DoctorLevelApply> list = doctorLevelApplyService.selectDoctorLevelApplyList(doctorLevelApply);
+        ExcelUtil<DoctorLevelApply> util = new ExcelUtil<DoctorLevelApply>(DoctorLevelApply.class);
+        return util.exportExcel(list, "讲者定级申请数据");
+    }
+
+    /**
+     * 获取讲者定级申请详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:apply:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(doctorLevelApplyService.selectDoctorLevelApplyById(id));
+    }
+
+    /**
+     * 新增讲者定级申请
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:apply:add')")
+    @Log(title = "讲者定级申请", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody DoctorLevelApplyVO doctorLevelApplyVO)
+    {
+        return toAjax(doctorLevelApplyService.insertDoctorLevelApply(doctorLevelApplyVO));
+    }
+
+    /**
+     * 修改讲者定级申请
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:apply:edit')")
+    @Log(title = "讲者定级申请", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody DoctorLevelApplyVO doctorLevelApplyVO)
+    {
+        return toAjax(doctorLevelApplyService.updateDoctorLevelApply(doctorLevelApplyVO));
+    }
+
+    /**
+     * 讲者定级申请
+     */
+    @Log(title = "讲者定级申请", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        return toAjax(doctorLevelApplyService.deleteDoctorLevelApplyByIds(ids));
+    }
+
+
+//    @Log(title = "讲者定级申请审核与查看页面信息")
+    @GetMapping(value = "/getDoctorApplyDetailInfoById")
+    public AjaxResult getDoctorApplyDetailInfoById(@RequestParam("id") Long id){
+
+        return AjaxResult.success(doctorLevelApplyService.getAdminDoctorApplyDetailInfoById(id));
+    }
+
+    @Log(title = "讲者定级申请审核", businessType = BusinessType.UPDATE)
+    @PostMapping(value = "/doAudit")
+    public void doAudit(@RequestBody DoctorApplyAuditReqVO doctorApplyAuditReqVO){
+        doctorLevelApplyService.doAudit(doctorApplyAuditReqVO);
+    }
+}

+ 97 - 0
fs-admin/src/main/java/com/fs/doctor/controller/DoctorLevelController.java

@@ -0,0 +1,97 @@
+package com.fs.doctor.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.doctor.domain.DoctorLevel;
+import com.fs.doctor.service.IDoctorLevelService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 讲者级别信息Controller
+ * 
+ * @author fs
+ * @date 2025-12-30
+ */
+@RestController
+@RequestMapping("/doctor/level")
+public class DoctorLevelController extends BaseController
+{
+    @Autowired
+    private IDoctorLevelService doctorLevelService;
+
+    /**
+     * 查询讲者级别信息列表
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:level:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(DoctorLevel doctorLevel)
+    {
+        startPage();
+        List<DoctorLevel> list = doctorLevelService.selectDoctorLevelList(doctorLevel);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出讲者级别信息列表
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:level:export')")
+    @Log(title = "讲者级别信息", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(DoctorLevel doctorLevel)
+    {
+        List<DoctorLevel> list = doctorLevelService.selectDoctorLevelList(doctorLevel);
+        ExcelUtil<DoctorLevel> util = new ExcelUtil<DoctorLevel>(DoctorLevel.class);
+        return util.exportExcel(list, "讲者级别信息数据");
+    }
+
+    /**
+     * 获取讲者级别信息详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:level:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(doctorLevelService.selectDoctorLevelById(id));
+    }
+
+    /**
+     * 新增讲者级别信息
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:level:add')")
+    @Log(title = "讲者级别信息", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody DoctorLevel doctorLevel)
+    {
+        return toAjax(doctorLevelService.insertDoctorLevel(doctorLevel));
+    }
+
+    /**
+     * 修改讲者级别信息
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:level:edit')")
+    @Log(title = "讲者级别信息", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody DoctorLevel doctorLevel)
+    {
+        return toAjax(doctorLevelService.updateDoctorLevel(doctorLevel));
+    }
+
+    /**
+     * 删除讲者级别信息
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:level:remove')")
+    @Log(title = "讲者级别信息", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(doctorLevelService.deleteDoctorLevelByIds(ids));
+    }
+}

+ 98 - 0
fs-admin/src/main/java/com/fs/doctor/controller/DoctorLevelDataController.java

@@ -0,0 +1,98 @@
+package com.fs.doctor.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.config.LoginContextManager;
+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.doctor.domain.DoctorLevelData;
+import com.fs.doctor.service.IDoctorLevelDataService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 医生级别Controller
+ * 
+ * @author fs
+ * @date 2026-02-12
+ */
+@RestController
+@RequestMapping("/doctor/data")
+public class DoctorLevelDataController extends BaseController
+{
+    @Autowired
+    private IDoctorLevelDataService doctorLevelDataService;
+
+    /**
+     * 查询医生级别列表
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:data:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(DoctorLevelData doctorLevelData)
+    {
+        startPage();
+        List<DoctorLevelData> list = doctorLevelDataService.selectDoctorLevelDataList(doctorLevelData);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出医生级别列表
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:data:export')")
+    @Log(title = "医生级别", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(DoctorLevelData doctorLevelData)
+    {
+        List<DoctorLevelData> list = doctorLevelDataService.selectDoctorLevelDataList(doctorLevelData);
+        ExcelUtil<DoctorLevelData> util = new ExcelUtil<DoctorLevelData>(DoctorLevelData.class);
+        return util.exportExcel(list, "医生级别数据");
+    }
+
+    /**
+     * 获取医生级别详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:data:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(doctorLevelDataService.selectDoctorLevelDataById(id));
+    }
+
+    /**
+     * 新增医生级别
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:data:add')")
+    @Log(title = "医生级别", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody DoctorLevelData doctorLevelData)
+    {
+        return toAjax(doctorLevelDataService.insertDoctorLevelData(doctorLevelData));
+    }
+
+    /**
+     * 修改医生级别
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:data:edit')")
+    @Log(title = "医生级别", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody DoctorLevelData doctorLevelData)
+    {
+        return toAjax(doctorLevelDataService.updateDoctorLevelData(doctorLevelData));
+    }
+
+    /**
+     * 删除医生级别
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:data:remove')")
+    @Log(title = "医生级别", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(doctorLevelDataService.deleteDoctorLevelDataByIds(ids));
+    }
+}

+ 97 - 0
fs-admin/src/main/java/com/fs/doctor/controller/DoctorLevelMaterialController.java

@@ -0,0 +1,97 @@
+package com.fs.doctor.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.doctor.domain.DoctorLevelMaterial;
+import com.fs.doctor.service.IDoctorLevelMaterialService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 定级证明材料Controller
+ * 
+ * @author fs
+ * @date 2025-12-30
+ */
+@RestController
+@RequestMapping("/doctor/material")
+public class DoctorLevelMaterialController extends BaseController
+{
+    @Autowired
+    private IDoctorLevelMaterialService doctorLevelMaterialService;
+
+    /**
+     * 查询定级证明材料列表
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:material:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(DoctorLevelMaterial doctorLevelMaterial)
+    {
+        startPage();
+        List<DoctorLevelMaterial> list = doctorLevelMaterialService.selectDoctorLevelMaterialList(doctorLevelMaterial);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出定级证明材料列表
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:material:export')")
+    @Log(title = "定级证明材料", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(DoctorLevelMaterial doctorLevelMaterial)
+    {
+        List<DoctorLevelMaterial> list = doctorLevelMaterialService.selectDoctorLevelMaterialList(doctorLevelMaterial);
+        ExcelUtil<DoctorLevelMaterial> util = new ExcelUtil<DoctorLevelMaterial>(DoctorLevelMaterial.class);
+        return util.exportExcel(list, "定级证明材料数据");
+    }
+
+    /**
+     * 获取定级证明材料详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:material:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(doctorLevelMaterialService.selectDoctorLevelMaterialById(id));
+    }
+
+    /**
+     * 新增定级证明材料
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:material:add')")
+    @Log(title = "定级证明材料", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody DoctorLevelMaterial doctorLevelMaterial)
+    {
+        return toAjax(doctorLevelMaterialService.insertDoctorLevelMaterial(doctorLevelMaterial));
+    }
+
+    /**
+     * 修改定级证明材料
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:material:edit')")
+    @Log(title = "定级证明材料", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody DoctorLevelMaterial doctorLevelMaterial)
+    {
+        return toAjax(doctorLevelMaterialService.updateDoctorLevelMaterial(doctorLevelMaterial));
+    }
+
+    /**
+     * 删除定级证明材料
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:material:remove')")
+    @Log(title = "定级证明材料", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(doctorLevelMaterialService.deleteDoctorLevelMaterialByIds(ids));
+    }
+}

+ 124 - 0
fs-admin/src/main/java/com/fs/doctor/controller/WithdrawRecordsController.java

@@ -0,0 +1,124 @@
+package com.fs.doctor.controller;
+
+import java.util.List;
+
+import com.fs.common.config.LoginContextManager;
+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.doctor.domain.WithdrawRecords;
+import com.fs.doctor.service.IWithdrawRecordsService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 医生提现记录Controller
+ *
+ * @author fs
+ * @date 2026-01-27
+ */
+@RestController
+@RequestMapping("/doctor/records")
+public class WithdrawRecordsController extends BaseController
+{
+    @Autowired
+    private IWithdrawRecordsService withdrawRecordsService;
+
+    /**
+     * 查询医生提现记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:records:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(WithdrawRecords withdrawRecords)
+    {
+        startPage();
+        List<WithdrawRecords> list = withdrawRecordsService.selectWithdrawRecordsList(withdrawRecords);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出医生提现记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:records:export')")
+    @Log(title = "医生提现记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(WithdrawRecords withdrawRecords)
+    {
+        List<WithdrawRecords> list = withdrawRecordsService.selectWithdrawRecordsList(withdrawRecords);
+        ExcelUtil<WithdrawRecords> util = new ExcelUtil<WithdrawRecords>(WithdrawRecords.class);
+        return util.exportExcel(list, "医生提现记录数据");
+    }
+
+    /**
+     * 获取医生提现记录详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:records:query')")
+    @GetMapping(value = "/{withdrawId}")
+    public AjaxResult getInfo(@PathVariable("withdrawId") Long withdrawId)
+    {
+        return AjaxResult.success(withdrawRecordsService.selectWithdrawRecordsByWithdrawId(withdrawId));
+    }
+
+    /**
+     * 新增医生提现记录
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:records:add')")
+    @Log(title = "医生提现记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody WithdrawRecords withdrawRecords)
+    {
+        return toAjax(withdrawRecordsService.insertWithdrawRecords(withdrawRecords));
+    }
+
+    /**
+     * 修改医生提现记录
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:records:edit')")
+    @Log(title = "医生提现记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody WithdrawRecords withdrawRecords)
+    {
+        return toAjax(withdrawRecordsService.updateWithdrawRecords(withdrawRecords));
+    }
+
+    /**
+     * 删除医生提现记录
+     */
+    @PreAuthorize("@ss.hasPermi('doctor:records:remove')")
+    @Log(title = "医生提现记录", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{withdrawIds}")
+    public AjaxResult remove(@PathVariable String[] withdrawIds)
+    {
+        return toAjax(withdrawRecordsService.deleteWithdrawRecordsByWithdrawIds(withdrawIds));
+    }
+
+
+    @PreAuthorize("@ss.hasPermi('doctor:records:audit')")
+    @Log(title = "医生提现记录", businessType = BusinessType.UPDATE)
+    @PostMapping("/audit")
+    public AjaxResult audit(@RequestBody WithdrawRecords withdrawRecords)
+    {
+        return toAjax(withdrawRecordsService.auditWithdraw(withdrawRecords));
+    }
+
+    @PreAuthorize("@ss.hasPermi('doctor:records:query')")
+    @Log(title = "提现审核详情", businessType = BusinessType.OTHER)
+    @GetMapping("/auditInfo/{withdrawId}")
+    public AjaxResult auditInfo(@PathVariable Long withdrawId) {
+        Long currentCompanyId = LoginContextManager.getCurrentCompanyId();
+
+        return AjaxResult.success(withdrawRecordsService.getRecordAuditInfo(withdrawId));
+    }
+
+}

+ 182 - 0
fs-admin/src/main/java/com/fs/framework/aspectj/DataScopeAspect.java

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

+ 73 - 0
fs-admin/src/main/java/com/fs/framework/aspectj/DataSourceAspect.java

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

+ 244 - 0
fs-admin/src/main/java/com/fs/framework/aspectj/LogAspect.java

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

+ 117 - 0
fs-admin/src/main/java/com/fs/framework/aspectj/RateLimiterAspect.java

@@ -0,0 +1,117 @@
+package com.fs.framework.aspectj;
+
+import com.fs.common.annotation.RateLimiter;
+import com.fs.common.enums.LimitType;
+import com.fs.common.exception.ServiceException;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.ip.IpUtils;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.Signature;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.RedisScript;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 限流处理
+ *
+
+ */
+@Aspect
+@Component
+public class RateLimiterAspect
+{
+    private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class);
+
+    private RedisTemplate<Object, Object> redisTemplate;
+
+    private RedisScript<Long> limitScript;
+
+    @Autowired
+    public void setRedisTemplate1(RedisTemplate<Object, Object> redisTemplate)
+    {
+        this.redisTemplate = redisTemplate;
+    }
+
+    @Autowired
+    public void setLimitScript(RedisScript<Long> limitScript)
+    {
+        this.limitScript = limitScript;
+    }
+
+    // 配置织入点
+    @Pointcut("@annotation(com.fs.common.annotation.RateLimiter)")
+    public void rateLimiterPointCut()
+    {
+    }
+
+    @Before("rateLimiterPointCut()")
+    public void doBefore(JoinPoint point) throws Throwable
+    {
+        RateLimiter rateLimiter = getAnnotationRateLimiter(point);
+        String key = rateLimiter.key();
+        int time = rateLimiter.time();
+        int count = rateLimiter.count();
+
+        String combineKey = getCombineKey(rateLimiter, point);
+        List<Object> keys = Collections.singletonList(combineKey);
+        try
+        {
+            Long number = redisTemplate.execute(limitScript, keys, count, time);
+            if (StringUtils.isNull(number) || number.intValue() > count)
+            {
+                throw new ServiceException("访问过于频繁,请稍后再试");
+            }
+            log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), key);
+        }
+        catch (ServiceException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException("服务器限流异常,请稍后再试");
+        }
+    }
+
+    /**
+     * 是否存在注解,如果存在就获取
+     */
+    private RateLimiter getAnnotationRateLimiter(JoinPoint joinPoint)
+    {
+        Signature signature = joinPoint.getSignature();
+        MethodSignature methodSignature = (MethodSignature) signature;
+        Method method = methodSignature.getMethod();
+
+        if (method != null)
+        {
+            return method.getAnnotation(RateLimiter.class);
+        }
+        return null;
+    }
+
+    public String getCombineKey(RateLimiter rateLimiter, JoinPoint point)
+    {
+        StringBuffer stringBuffer = new StringBuffer(rateLimiter.key());
+        if (rateLimiter.limitType() == LimitType.IP)
+        {
+            stringBuffer.append(IpUtils.getIpAddr(ServletUtils.getRequest()));
+        }
+        MethodSignature signature = (MethodSignature) point.getSignature();
+        Method method = signature.getMethod();
+        Class<?> targetClass = method.getDeclaringClass();
+        stringBuffer.append("-").append(targetClass.getName()).append("- ").append(method.getName());
+        return stringBuffer.toString();
+    }
+}

+ 81 - 0
fs-admin/src/main/java/com/fs/framework/config/AdminLoginContextCallback.java

@@ -0,0 +1,81 @@
+// fs-admin模块
+package com.fs.framework.config;
+
+import com.fs.common.config.LoginContextCallback;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.enums.DataScopeEnum;
+import com.fs.common.enums.PlatEnum;
+import com.fs.common.utils.SecurityUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * Admin端登录上下文回调实现
+ */
+@Component
+public class AdminLoginContextCallback implements LoginContextCallback, ApplicationContextAware {
+
+    private static ApplicationContext applicationContext;
+
+    @Override
+    public Long getUserId() {
+        try {
+            return SecurityUtils.getUserId();
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    @Override
+    public Long getCompanyUserId() {
+        // Admin端没有companyUserId概念
+        return null;
+    }
+
+    @Override
+    public String getUsername() {
+        try {
+            return SecurityUtils.getUsername();
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    @Override
+    public String getCompanyUserName() {
+        // Admin端没有companyUserName概念
+        return null;
+    }
+
+    @Override
+    public Long getCompanyId() {
+        // Admin端没有companyId概念,返回null
+        return null;
+    }
+
+    @Override
+    public Long getDeptId() {
+        try {
+            return SecurityUtils.getDeptId();
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    @Override
+    public DataScopeEnum getDataScope() {
+        return DataScopeEnum.ALL;
+    }
+
+    @Override
+    public PlatEnum getNowPlatEnum() {
+        return PlatEnum.ADMIN;
+    }
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+        AdminLoginContextCallback.applicationContext = applicationContext;
+    }
+}

+ 62 - 0
fs-admin/src/main/java/com/fs/framework/config/ApplicationConfig.java

@@ -0,0 +1,62 @@
+package com.fs.framework.config;
+
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.util.TimeZone;
+
+/**
+ * 程序注解配置
+ *
+
+ */
+@Configuration
+// 表示通过aop框架暴露该代理对象,AopContext能够访问
+@EnableAspectJAutoProxy(exposeProxy = true)
+// 指定要扫描的Mapper类的包的路径
+@MapperScan("com.fs.**.mapper")
+public class ApplicationConfig
+{
+    /**
+     * 时区配置和LocalDateTime序列化配置
+     */
+    @Bean
+    public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization()
+    {
+        return jacksonObjectMapperBuilder -> {
+            // 设置时区
+            jacksonObjectMapperBuilder.timeZone(TimeZone.getTimeZone("GMT+8"));
+
+            // LocalDateTime 序列化和反序列化
+            jacksonObjectMapperBuilder.serializerByType(LocalDateTime.class,
+                    new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
+            jacksonObjectMapperBuilder.deserializerByType(LocalDateTime.class,
+                    new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
+
+            // LocalDate 序列化和反序列化
+            jacksonObjectMapperBuilder.serializerByType(LocalDate.class,
+                    new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
+            jacksonObjectMapperBuilder.deserializerByType(LocalDate.class,
+                    new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
+
+            // LocalTime 序列化和反序列化
+            jacksonObjectMapperBuilder.serializerByType(LocalTime.class,
+                    new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
+            jacksonObjectMapperBuilder.deserializerByType(LocalTime.class,
+                    new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
+        };
+    }
+}

+ 85 - 0
fs-admin/src/main/java/com/fs/framework/config/CaptchaConfig.java

@@ -0,0 +1,85 @@
+package com.fs.framework.config;
+
+import com.google.code.kaptcha.impl.DefaultKaptcha;
+import com.google.code.kaptcha.util.Config;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.Properties;
+
+import static com.google.code.kaptcha.Constants.*;
+
+/**
+ * 验证码配置
+ *
+
+ */
+@Configuration
+public class CaptchaConfig
+{
+    @Bean(name = "captchaProducer")
+    public DefaultKaptcha getKaptchaBean()
+    {
+        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
+        Properties properties = new Properties();
+        // 是否有边框 默认为true 我们可以自己设置yes,no
+        properties.setProperty(KAPTCHA_BORDER, "yes");
+        // 验证码文本字符颜色 默认为Color.BLACK
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black");
+        // 验证码图片宽度 默认为200
+        properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
+        // 验证码图片高度 默认为50
+        properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
+        // 验证码文本字符大小 默认为40
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38");
+        // KAPTCHA_SESSION_KEY
+        properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode");
+        // 验证码文本字符长度 默认为5
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
+        // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
+        // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
+        properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
+        Config config = new Config(properties);
+        defaultKaptcha.setConfig(config);
+        return defaultKaptcha;
+    }
+
+    @Bean(name = "captchaProducerMath")
+    public DefaultKaptcha getKaptchaBeanMath()
+    {
+        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
+        Properties properties = new Properties();
+        // 是否有边框 默认为true 我们可以自己设置yes,no
+        properties.setProperty(KAPTCHA_BORDER, "yes");
+        // 边框颜色 默认为Color.BLACK
+        properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90");
+        // 验证码文本字符颜色 默认为Color.BLACK
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
+        // 验证码图片宽度 默认为200
+        properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
+        // 验证码图片高度 默认为50
+        properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
+        // 验证码文本字符大小 默认为40
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35");
+        // KAPTCHA_SESSION_KEY
+        properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath");
+        // 验证码文本生成器
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.fs.framework.config.KaptchaTextCreator");
+        // 验证码文本字符间距 默认为2
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3");
+        // 验证码文本字符长度 默认为5
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6");
+        // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
+        // 验证码噪点颜色 默认为Color.BLACK
+        properties.setProperty(KAPTCHA_NOISE_COLOR, "white");
+        // 干扰实现类
+        properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");
+        // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
+        properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
+        Config config = new Config(properties);
+        defaultKaptcha.setConfig(config);
+        return defaultKaptcha;
+    }
+}

+ 123 - 0
fs-admin/src/main/java/com/fs/framework/config/DruidConfig.java

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

+ 52 - 0
fs-admin/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -0,0 +1,52 @@
+package com.fs.framework.config;
+
+import java.nio.charset.Charset;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.data.redis.serializer.SerializationException;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
+import com.alibaba.fastjson2.filter.Filter;
+import com.fs.common.constant.Constants;
+
+/**
+ * Redis使用FastJson序列化
+ *
+
+ */
+public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
+{
+    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
+
+    static final Filter AUTO_TYPE_FILTER = JSONReader.autoTypeFilter(Constants.JSON_WHITELIST_STR);
+
+    private Class<T> clazz;
+
+    public FastJson2JsonRedisSerializer(Class<T> clazz)
+    {
+        super();
+        this.clazz = clazz;
+    }
+
+    @Override
+    public byte[] serialize(T t) throws SerializationException
+    {
+        if (t == null)
+        {
+            return new byte[0];
+        }
+        return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
+    }
+
+    @Override
+    public T deserialize(byte[] bytes) throws SerializationException
+    {
+        if (bytes == null || bytes.length <= 0)
+        {
+            return null;
+        }
+        String str = new String(bytes, DEFAULT_CHARSET);
+
+        return JSON.parseObject(str, clazz, AUTO_TYPE_FILTER);
+    }
+}

+ 59 - 0
fs-admin/src/main/java/com/fs/framework/config/FilterConfig.java

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

+ 76 - 0
fs-admin/src/main/java/com/fs/framework/config/KaptchaTextCreator.java

@@ -0,0 +1,76 @@
+package com.fs.framework.config;
+
+import com.google.code.kaptcha.text.impl.DefaultTextCreator;
+
+import java.util.Random;
+
+/**
+ * 验证码文本生成器
+ *
+
+ */
+public class KaptchaTextCreator extends DefaultTextCreator
+{
+    private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(",");
+
+    @Override
+    public String getText()
+    {
+        Integer result = 0;
+        Random random = new Random();
+        int x = random.nextInt(10);
+        int y = random.nextInt(10);
+        StringBuilder suChinese = new StringBuilder();
+        int randomoperands = (int) Math.round(Math.random() * 2);
+        if (randomoperands == 0)
+        {
+            result = x * y;
+            suChinese.append(CNUMBERS[x]);
+            suChinese.append("*");
+            suChinese.append(CNUMBERS[y]);
+        }
+        else if (randomoperands == 1)
+        {
+            if (!(x == 0) && y % x == 0)
+            {
+                result = y / x;
+                suChinese.append(CNUMBERS[y]);
+                suChinese.append("/");
+                suChinese.append(CNUMBERS[x]);
+            }
+            else
+            {
+                result = x + y;
+                suChinese.append(CNUMBERS[x]);
+                suChinese.append("+");
+                suChinese.append(CNUMBERS[y]);
+            }
+        }
+        else if (randomoperands == 2)
+        {
+            if (x >= y)
+            {
+                result = x - y;
+                suChinese.append(CNUMBERS[x]);
+                suChinese.append("-");
+                suChinese.append(CNUMBERS[y]);
+            }
+            else
+            {
+                result = y - x;
+                suChinese.append(CNUMBERS[y]);
+                suChinese.append("-");
+                suChinese.append(CNUMBERS[x]);
+            }
+        }
+        else
+        {
+            result = x + y;
+            suChinese.append(CNUMBERS[x]);
+            suChinese.append("+");
+            suChinese.append(CNUMBERS[y]);
+        }
+        suChinese.append("=?@" + result);
+        return suChinese.toString();
+    }
+}

+ 128 - 0
fs-admin/src/main/java/com/fs/framework/config/MyBatisConfig.java

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

+ 74 - 0
fs-admin/src/main/java/com/fs/framework/config/RedisConfig.java

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

+ 66 - 0
fs-admin/src/main/java/com/fs/framework/config/ResourcesConfig.java

@@ -0,0 +1,66 @@
+package com.fs.framework.config;
+
+import com.fs.common.config.FSConfig;
+import com.fs.common.constant.Constants;
+import com.fs.framework.interceptor.RepeatSubmitInterceptor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.filter.CorsFilter;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * 通用配置
+ *
+
+ */
+@Configuration
+public class ResourcesConfig implements WebMvcConfigurer
+{
+    @Autowired
+    private RepeatSubmitInterceptor repeatSubmitInterceptor;
+
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry)
+    {
+        /** 本地文件上传路径 */
+        registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**").addResourceLocations("file:" + FSConfig.getProfile() + "/");
+
+        /** swagger配置 */
+        registry.addResourceHandler("/swagger-ui/**").addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/");
+
+    }
+
+    /**
+     * 自定义拦截规则
+     */
+    @Override
+    public void addInterceptors(InterceptorRegistry registry)
+    {
+        registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
+    }
+
+    /**
+     * 跨域配置
+     */
+    @Bean
+    public CorsFilter corsFilter()
+    {
+        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+        CorsConfiguration config = new CorsConfiguration();
+        config.setAllowCredentials(true);
+        // 设置访问源地址
+        config.addAllowedOrigin("*");
+        // 设置访问源请求头
+        config.addAllowedHeader("*");
+        // 设置访问源请求方法
+        config.addAllowedMethod("*");
+        // 对接口配置跨域设置
+        source.registerCorsConfiguration("/**", config);
+        return new CorsFilter(source);
+    }
+}

+ 168 - 0
fs-admin/src/main/java/com/fs/framework/config/SecurityConfig.java

@@ -0,0 +1,168 @@
+package com.fs.framework.config;
+
+import com.fs.framework.security.filter.JwtAuthenticationTokenFilter;
+import com.fs.framework.security.handle.AuthenticationEntryPointImpl;
+import com.fs.framework.security.handle.LogoutSuccessHandlerImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.security.web.authentication.logout.LogoutFilter;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.filter.CorsFilter;
+
+import java.util.Arrays;
+
+/**
+ * spring security配置
+ *
+
+ */
+@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
+public class SecurityConfig extends WebSecurityConfigurerAdapter
+{
+    /**
+     * 自定义用户认证逻辑
+     */
+    @Autowired
+    private UserDetailsService userDetailsService;
+
+    /**
+     * 认证失败处理类
+     */
+    @Autowired
+    private AuthenticationEntryPointImpl unauthorizedHandler;
+
+    /**
+     * 退出处理类
+     */
+    @Autowired
+    private LogoutSuccessHandlerImpl logoutSuccessHandler;
+
+    /**
+     * token认证过滤器
+     */
+    @Autowired
+    private JwtAuthenticationTokenFilter authenticationTokenFilter;
+
+    /**
+     * 跨域过滤器
+     */
+    @Autowired
+    private CorsFilter corsFilter;
+
+    /**
+     * 解决 无法直接注入 AuthenticationManager
+     *
+     * @return
+     * @throws Exception
+     */
+    @Bean
+    @Override
+    public AuthenticationManager authenticationManagerBean() throws Exception
+    {
+        return super.authenticationManagerBean();
+    }
+
+    /**
+     * anyRequest          |   匹配所有请求路径
+     * access              |   SpringEl表达式结果为true时可以访问
+     * anonymous           |   匿名可以访问
+     * denyAll             |   用户不能访问
+     * fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)
+     * hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问
+     * hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问
+     * hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问
+     * hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
+     * hasRole             |   如果有参数,参数表示角色,则其角色可以访问
+     * permitAll           |   用户可以任意访问
+     * rememberMe          |   允许通过remember-me登录的用户访问
+     * authenticated       |   用户登录后可访问
+     */
+    @Override
+    protected void configure(HttpSecurity httpSecurity) throws Exception
+    {
+        httpSecurity.cors().configurationSource(request -> {
+            CorsConfiguration config = new CorsConfiguration();
+            // 使用域名模式(支持通配符)
+            config.setAllowedOriginPatterns(Arrays.asList(
+//                    "https://*.your-domain.com",  // 所有子域名
+                    "http://localhost:[*]",        // 所有端口的localhost
+                    "http://132.232.83.221:[*]"
+            ));
+            config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
+            config.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));
+            config.setAllowCredentials(true);
+            return config;
+        });
+        httpSecurity
+                // CSRF禁用,因为不使用session
+                .csrf().disable()
+                // 认证失败处理类
+                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
+                // 基于token,所以不需要session
+                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
+                // 过滤请求
+                .authorizeRequests()
+                // 对于登录login 注册register 验证码captchaImage 允许匿名访问
+                .antMatchers("/login", "/register", "/captchaImage").anonymous()
+                .antMatchers(
+                        HttpMethod.GET,
+                        "/",
+                        "/*.html",
+                        "/**/*.html",
+                        "/**/*.css",
+                        "/**/*.js",
+                        "/profile/**"
+                ).permitAll()
+
+
+                .antMatchers("/common/getId**").permitAll()
+                .antMatchers("/common/uploadOSS**").permitAll()
+                .antMatchers("/common/uploadWang**").permitAll()
+                .antMatchers("/common/download**").permitAll()
+                .antMatchers("/common/download/resource**").permitAll()
+                .antMatchers("/swagger-ui.html").permitAll()
+                .antMatchers("/swagger-resources/**").permitAll()
+                .antMatchers("/webjars/**").permitAll()
+                .antMatchers("/*/api-docs").permitAll()
+                .antMatchers("/druid/**").permitAll()
+//                .antMatchers("/system/file/**").anonymous()
+                // 除上面外的所有请求全部需要鉴权认证
+                .anyRequest().authenticated()
+                .and()
+                .headers().frameOptions().disable();
+        httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
+        // 添加JWT filter
+        httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
+        // 添加CORS filter
+        httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
+        httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
+    }
+
+    /**
+     * 强散列哈希加密实现
+     */
+    @Bean
+    public BCryptPasswordEncoder bCryptPasswordEncoder()
+    {
+        return new BCryptPasswordEncoder();
+    }
+
+    /**
+     * 身份认证接口
+     */
+    @Override
+    protected void configure(AuthenticationManagerBuilder auth) throws Exception
+    {
+        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
+    }
+}

+ 33 - 0
fs-admin/src/main/java/com/fs/framework/config/ServerConfig.java

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

+ 124 - 0
fs-admin/src/main/java/com/fs/framework/config/SwaggerConfig.java

@@ -0,0 +1,124 @@
+package com.fs.framework.config;
+
+import com.fs.common.config.FSConfig;
+import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.*;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spi.service.contexts.SecurityContext;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Swagger2的接口配置
+ *
+
+ */
+@Configuration
+@EnableSwagger2
+@EnableSwaggerBootstrapUI
+public class SwaggerConfig
+{
+    /** 系统基础配置 */
+    @Autowired
+    private FSConfig fsConfig;
+
+    /** 是否开启swagger */
+    @Value("${swagger.enabled}")
+    private boolean enabled;
+
+    /** 设置请求的统一前缀 */
+    @Value("${swagger.pathMapping}")
+    private String pathMapping;
+
+    /**
+     * 创建API
+     */
+    @Bean
+    public Docket createRestApi()
+    {
+        return new Docket(DocumentationType.SWAGGER_2)
+                // 是否启用Swagger
+                .enable(enabled)
+                // 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息)
+                .apiInfo(apiInfo())
+                // 设置哪些接口暴露给Swagger展示
+                .select()
+                // 扫描所有有注解的api,用这种方式更灵活
+                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
+                // 扫描指定包中的swagger注解
+                // .apis(RequestHandlerSelectors.basePackage("com.fs.project.tool.swagger"))
+                // 扫描所有 .apis(RequestHandlerSelectors.any())
+                .paths(PathSelectors.any())
+                .build()
+                /* 设置安全模式,swagger可以设置访问token */
+                .securitySchemes(securitySchemes())
+                .securityContexts(securityContexts())
+                .pathMapping(pathMapping);
+    }
+
+    /**
+     * 安全模式,这里指定token通过Authorization头请求头传递
+     */
+    private List<ApiKey> securitySchemes()
+    {
+        List<ApiKey> apiKeyList = new ArrayList<ApiKey>();
+        apiKeyList.add(new ApiKey("Authorization", "Authorization", "header"));
+        return apiKeyList;
+    }
+
+    /**
+     * 安全上下文
+     */
+    private List<SecurityContext> securityContexts()
+    {
+        List<SecurityContext> securityContexts = new ArrayList<>();
+        securityContexts.add(
+                SecurityContext.builder()
+                        .securityReferences(defaultAuth())
+                        .forPaths(PathSelectors.regex("^(?!auth).*$"))
+                        .build());
+        return securityContexts;
+    }
+
+    /**
+     * 默认的安全上引用
+     */
+    private List<SecurityReference> defaultAuth()
+    {
+        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
+        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
+        authorizationScopes[0] = authorizationScope;
+        List<SecurityReference> securityReferences = new ArrayList<>();
+        securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
+        return securityReferences;
+    }
+
+    /**
+     * 添加摘要信息
+     */
+    private ApiInfo apiInfo()
+    {
+        // 用ApiInfoBuilder进行定制
+        return new ApiInfoBuilder()
+                // 设置标题
+                .title("标题:FS管理系统_接口文档")
+                // 描述
+                .description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...")
+                // 作者信息
+                .contact(new Contact(fsConfig.getName(), null, null))
+                // 版本
+                .version("版本号:" + fsConfig.getVersion())
+                .build();
+    }
+}

+ 63 - 0
fs-admin/src/main/java/com/fs/framework/config/ThreadPoolConfig.java

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

+ 77 - 0
fs-admin/src/main/java/com/fs/framework/config/properties/DruidProperties.java

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

+ 27 - 0
fs-admin/src/main/java/com/fs/framework/datasource/DynamicDataSource.java

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

+ 45 - 0
fs-admin/src/main/java/com/fs/framework/datasource/DynamicDataSourceContextHolder.java

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

+ 56 - 0
fs-admin/src/main/java/com/fs/framework/interceptor/RepeatSubmitInterceptor.java

@@ -0,0 +1,56 @@
+package com.fs.framework.interceptor;
+
+import com.alibaba.fastjson.JSONObject;
+import com.fs.common.annotation.RepeatSubmit;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.utils.ServletUtils;
+import org.springframework.stereotype.Component;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.lang.reflect.Method;
+
+/**
+ * 防止重复提交拦截器
+ *
+
+ */
+@Component
+public abstract class RepeatSubmitInterceptor extends HandlerInterceptorAdapter
+{
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
+    {
+        if (handler instanceof HandlerMethod)
+        {
+            HandlerMethod handlerMethod = (HandlerMethod) handler;
+            Method method = handlerMethod.getMethod();
+            RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
+            if (annotation != null)
+            {
+                if (this.isRepeatSubmit(request))
+                {
+                    AjaxResult ajaxResult = AjaxResult.error("不允许重复提交,请稍后再试");
+                    ServletUtils.renderString(response, JSONObject.toJSONString(ajaxResult));
+                    return false;
+                }
+            }
+            return true;
+        }
+        else
+        {
+            return super.preHandle(request, response, handler);
+        }
+    }
+
+    /**
+     * 验证是否重复提交由子类实现具体的防重复提交的规则
+     *
+     * @param request
+     * @return
+     * @throws Exception
+     */
+    public abstract boolean isRepeatSubmit(HttpServletRequest request);
+}

+ 126 - 0
fs-admin/src/main/java/com/fs/framework/interceptor/impl/SameUrlDataInterceptor.java

@@ -0,0 +1,126 @@
+package com.fs.framework.interceptor.impl;
+
+import com.alibaba.fastjson.JSONObject;
+import com.fs.common.constant.Constants;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.common.filter.RepeatedlyRequestWrapper;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.http.HttpHelper;
+import com.fs.framework.interceptor.RepeatSubmitInterceptor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 判断请求url和数据是否和上一次相同,
+ * 如果和上次相同,则是重复提交表单。 有效时间为10秒内。
+ *
+
+ */
+@Component
+public class SameUrlDataInterceptor extends RepeatSubmitInterceptor
+{
+    public final String REPEAT_PARAMS = "repeatParams";
+
+    public final String REPEAT_TIME = "repeatTime";
+
+    // 令牌自定义标识
+    @Value("${token.header}")
+    private String header;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    /**
+     * 间隔时间,单位:秒 默认10秒
+     *
+     * 两次相同参数的请求,如果间隔时间大于该参数,系统不会认定为重复提交的数据
+     */
+    private int intervalTime = 10;
+
+    public void setIntervalTime(int intervalTime)
+    {
+        this.intervalTime = intervalTime;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public boolean isRepeatSubmit(HttpServletRequest request)
+    {
+        String nowParams = "";
+        if (request instanceof RepeatedlyRequestWrapper)
+        {
+            RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request;
+            nowParams = HttpHelper.getBodyString(repeatedlyRequest);
+        }
+
+        // body参数为空,获取Parameter的数据
+        if (StringUtils.isEmpty(nowParams))
+        {
+            nowParams = JSONObject.toJSONString(request.getParameterMap());
+        }
+        Map<String, Object> nowDataMap = new HashMap<String, Object>();
+        nowDataMap.put(REPEAT_PARAMS, nowParams);
+        nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());
+
+        // 请求地址(作为存放cache的key值)
+        String url = request.getRequestURI();
+
+        // 唯一值(没有消息头则使用请求地址)
+        String submitKey = request.getHeader(header);
+        if (StringUtils.isEmpty(submitKey))
+        {
+            submitKey = url;
+        }
+
+        // 唯一标识(指定key + 消息头)
+        String cacheRepeatKey = Constants.REPEAT_SUBMIT_KEY + submitKey;
+
+        Object sessionObj = redisCache.getCacheObject(cacheRepeatKey);
+        if (sessionObj != null)
+        {
+            Map<String, Object> sessionMap = (Map<String, Object>) sessionObj;
+            if (sessionMap.containsKey(url))
+            {
+                Map<String, Object> preDataMap = (Map<String, Object>) sessionMap.get(url);
+                if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap))
+                {
+                    return true;
+                }
+            }
+        }
+        Map<String, Object> cacheMap = new HashMap<String, Object>();
+        cacheMap.put(url, nowDataMap);
+        redisCache.setCacheObject(cacheRepeatKey, cacheMap, intervalTime, TimeUnit.SECONDS);
+        return false;
+    }
+
+    /**
+     * 判断参数是否相同
+     */
+    private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap)
+    {
+        String nowParams = (String) nowMap.get(REPEAT_PARAMS);
+        String preParams = (String) preMap.get(REPEAT_PARAMS);
+        return nowParams.equals(preParams);
+    }
+
+    /**
+     * 判断两次间隔时间
+     */
+    private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap)
+    {
+        long time1 = (Long) nowMap.get(REPEAT_TIME);
+        long time2 = (Long) preMap.get(REPEAT_TIME);
+        if ((time1 - time2) < (this.intervalTime * 1000))
+        {
+            return true;
+        }
+        return false;
+    }
+}

+ 56 - 0
fs-admin/src/main/java/com/fs/framework/manager/AsyncManager.java

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

+ 40 - 0
fs-admin/src/main/java/com/fs/framework/manager/ShutdownManager.java

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

+ 103 - 0
fs-admin/src/main/java/com/fs/framework/manager/factory/AsyncFactory.java

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

+ 56 - 0
fs-admin/src/main/java/com/fs/framework/security/filter/JwtAuthenticationTokenFilter.java

@@ -0,0 +1,56 @@
+package com.fs.framework.security.filter;
+
+import com.fs.common.config.LoginContextManager;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.utils.SecurityUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.framework.config.AdminLoginContextCallback;
+import com.fs.framework.web.service.TokenService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * token过滤器 验证token有效性
+ *
+
+ */
+@Component
+public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
+{
+    @Autowired
+    private TokenService tokenService;
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
+            throws ServletException, IOException
+    {
+        LoginUser loginUser = tokenService.getLoginUser(request);
+        if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication()))
+        {
+            tokenService.verifyToken(loginUser);
+            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
+            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
+
+            // 设置上下文回调
+            AdminLoginContextCallback callback = new AdminLoginContextCallback();
+            LoginContextManager.setContextCallback(callback);
+        }
+        try {
+            chain.doFilter(request, response);
+        } finally {
+            // 清理上下文,避免内存泄漏
+            LoginContextManager.clearContext();
+        }
+    }
+}

+ 35 - 0
fs-admin/src/main/java/com/fs/framework/security/handle/AuthenticationEntryPointImpl.java

@@ -0,0 +1,35 @@
+package com.fs.framework.security.handle;
+
+import com.alibaba.fastjson.JSON;
+import com.fs.common.constant.HttpStatus;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.Serializable;
+
+/**
+ * 认证失败处理类 返回未授权
+ *
+
+ */
+@Component
+public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable
+{
+    private static final long serialVersionUID = -8970718410437077606L;
+
+    @Override
+    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
+            throws IOException
+    {
+        int code = HttpStatus.UNAUTHORIZED;
+        String msg = StringUtils.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI());
+        ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg)));
+    }
+}

+ 54 - 0
fs-admin/src/main/java/com/fs/framework/security/handle/LogoutSuccessHandlerImpl.java

@@ -0,0 +1,54 @@
+package com.fs.framework.security.handle;
+
+import com.alibaba.fastjson.JSON;
+import com.fs.common.constant.Constants;
+import com.fs.common.constant.HttpStatus;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.framework.manager.AsyncManager;
+import com.fs.framework.manager.factory.AsyncFactory;
+import com.fs.framework.web.service.TokenService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * 自定义退出处理类 返回成功
+ *
+
+ */
+@Configuration
+public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
+{
+    @Autowired
+    private TokenService tokenService;
+
+    /**
+     * 退出处理
+     *
+     * @return
+     */
+    @Override
+    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
+            throws IOException, ServletException
+    {
+        LoginUser loginUser = tokenService.getLoginUser(request);
+        if (StringUtils.isNotNull(loginUser))
+        {
+            String userName = loginUser.getUsername();
+            // 删除用户缓存记录
+            tokenService.delLoginUser(loginUser.getToken());
+            // 记录用户退出日志
+            AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, "退出成功"));
+        }
+        ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(HttpStatus.SUCCESS, "退出成功")));
+    }
+}

+ 237 - 0
fs-admin/src/main/java/com/fs/framework/web/domain/Server.java

@@ -0,0 +1,237 @@
+package com.fs.framework.web.domain;
+
+import com.fs.common.utils.Arith;
+import com.fs.common.utils.ip.IpUtils;
+import com.fs.framework.web.domain.server.*;
+import oshi.SystemInfo;
+import oshi.hardware.CentralProcessor;
+import oshi.hardware.CentralProcessor.TickType;
+import oshi.hardware.GlobalMemory;
+import oshi.hardware.HardwareAbstractionLayer;
+import oshi.software.os.FileSystem;
+import oshi.software.os.OSFileStore;
+import oshi.software.os.OperatingSystem;
+import oshi.util.Util;
+
+import java.net.UnknownHostException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Properties;
+
+/**
+ * 服务器相关信息
+ *
+
+ */
+public class Server
+{
+    private static final int OSHI_WAIT_SECOND = 1000;
+
+    /**
+     * CPU相关信息
+     */
+    private Cpu cpu = new Cpu();
+
+    /**
+     * 內存相关信息
+     */
+    private Mem mem = new Mem();
+
+    /**
+     * JVM相关信息
+     */
+    private Jvm jvm = new Jvm();
+
+    /**
+     * 服务器相关信息
+     */
+    private Sys sys = new Sys();
+
+    /**
+     * 磁盘相关信息
+     */
+    private List<SysFile> sysFiles = new LinkedList<SysFile>();
+
+    public Cpu getCpu()
+    {
+        return cpu;
+    }
+
+    public void setCpu(Cpu cpu)
+    {
+        this.cpu = cpu;
+    }
+
+    public Mem getMem()
+    {
+        return mem;
+    }
+
+    public void setMem(Mem mem)
+    {
+        this.mem = mem;
+    }
+
+    public Jvm getJvm()
+    {
+        return jvm;
+    }
+
+    public void setJvm(Jvm jvm)
+    {
+        this.jvm = jvm;
+    }
+
+    public Sys getSys()
+    {
+        return sys;
+    }
+
+    public void setSys(Sys sys)
+    {
+        this.sys = sys;
+    }
+
+    public List<SysFile> getSysFiles()
+    {
+        return sysFiles;
+    }
+
+    public void setSysFiles(List<SysFile> sysFiles)
+    {
+        this.sysFiles = sysFiles;
+    }
+
+    public void copyTo() throws Exception
+    {
+        SystemInfo si = new SystemInfo();
+        HardwareAbstractionLayer hal = si.getHardware();
+
+        setCpuInfo(hal.getProcessor());
+
+        setMemInfo(hal.getMemory());
+
+        setSysInfo();
+
+        setJvmInfo();
+
+        setSysFiles(si.getOperatingSystem());
+    }
+
+    /**
+     * 设置CPU信息
+     */
+    private void setCpuInfo(CentralProcessor processor)
+    {
+        // CPU信息
+        long[] prevTicks = processor.getSystemCpuLoadTicks();
+        Util.sleep(OSHI_WAIT_SECOND);
+        long[] ticks = processor.getSystemCpuLoadTicks();
+        long nice = ticks[TickType.NICE.getIndex()] - prevTicks[TickType.NICE.getIndex()];
+        long irq = ticks[TickType.IRQ.getIndex()] - prevTicks[TickType.IRQ.getIndex()];
+        long softirq = ticks[TickType.SOFTIRQ.getIndex()] - prevTicks[TickType.SOFTIRQ.getIndex()];
+        long steal = ticks[TickType.STEAL.getIndex()] - prevTicks[TickType.STEAL.getIndex()];
+        long cSys = ticks[TickType.SYSTEM.getIndex()] - prevTicks[TickType.SYSTEM.getIndex()];
+        long user = ticks[TickType.USER.getIndex()] - prevTicks[TickType.USER.getIndex()];
+        long iowait = ticks[TickType.IOWAIT.getIndex()] - prevTicks[TickType.IOWAIT.getIndex()];
+        long idle = ticks[TickType.IDLE.getIndex()] - prevTicks[TickType.IDLE.getIndex()];
+        long totalCpu = user + nice + cSys + idle + iowait + irq + softirq + steal;
+        cpu.setCpuNum(processor.getLogicalProcessorCount());
+        cpu.setTotal(totalCpu);
+        cpu.setSys(cSys);
+        cpu.setUsed(user);
+        cpu.setWait(iowait);
+        cpu.setFree(idle);
+    }
+
+    /**
+     * 设置内存信息
+     */
+    private void setMemInfo(GlobalMemory memory)
+    {
+        mem.setTotal(memory.getTotal());
+        mem.setUsed(memory.getTotal() - memory.getAvailable());
+        mem.setFree(memory.getAvailable());
+    }
+
+    /**
+     * 设置服务器信息
+     */
+    private void setSysInfo()
+    {
+        Properties props = System.getProperties();
+        sys.setComputerName(IpUtils.getHostName());
+        sys.setComputerIp(IpUtils.getHostIp());
+        sys.setOsName(props.getProperty("os.name"));
+        sys.setOsArch(props.getProperty("os.arch"));
+        sys.setUserDir(props.getProperty("user.dir"));
+    }
+
+    /**
+     * 设置Java虚拟机
+     */
+    private void setJvmInfo() throws UnknownHostException
+    {
+        Properties props = System.getProperties();
+        jvm.setTotal(Runtime.getRuntime().totalMemory());
+        jvm.setMax(Runtime.getRuntime().maxMemory());
+        jvm.setFree(Runtime.getRuntime().freeMemory());
+        jvm.setVersion(props.getProperty("java.version"));
+        jvm.setHome(props.getProperty("java.home"));
+    }
+
+    /**
+     * 设置磁盘信息
+     */
+    private void setSysFiles(OperatingSystem os)
+    {
+        FileSystem fileSystem = os.getFileSystem();
+        List<OSFileStore> fsArray = fileSystem.getFileStores();
+        for (OSFileStore fs : fsArray)
+        {
+            long free = fs.getUsableSpace();
+            long total = fs.getTotalSpace();
+            long used = total - free;
+            SysFile sysFile = new SysFile();
+            sysFile.setDirName(fs.getMount());
+            sysFile.setSysTypeName(fs.getType());
+            sysFile.setTypeName(fs.getName());
+            sysFile.setTotal(convertFileSize(total));
+            sysFile.setFree(convertFileSize(free));
+            sysFile.setUsed(convertFileSize(used));
+            sysFile.setUsage(Arith.mul(Arith.div(used, total, 4), 100));
+            sysFiles.add(sysFile);
+        }
+    }
+
+    /**
+     * 字节转换
+     *
+     * @param size 字节大小
+     * @return 转换后值
+     */
+    public String convertFileSize(long size)
+    {
+        long kb = 1024;
+        long mb = kb * 1024;
+        long gb = mb * 1024;
+        if (size >= gb)
+        {
+            return String.format("%.1f GB", (float) size / gb);
+        }
+        else if (size >= mb)
+        {
+            float f = (float) size / mb;
+            return String.format(f > 100 ? "%.0f MB" : "%.1f MB", f);
+        }
+        else if (size >= kb)
+        {
+            float f = (float) size / kb;
+            return String.format(f > 100 ? "%.0f KB" : "%.1f KB", f);
+        }
+        else
+        {
+            return String.format("%d B", size);
+        }
+    }
+}

+ 101 - 0
fs-admin/src/main/java/com/fs/framework/web/domain/server/Cpu.java

@@ -0,0 +1,101 @@
+package com.fs.framework.web.domain.server;
+
+import com.fs.common.utils.Arith;
+
+/**
+ * CPU相关信息
+ * 
+
+ */
+public class Cpu
+{
+    /**
+     * 核心数
+     */
+    private int cpuNum;
+
+    /**
+     * CPU总的使用率
+     */
+    private double total;
+
+    /**
+     * CPU系统使用率
+     */
+    private double sys;
+
+    /**
+     * CPU用户使用率
+     */
+    private double used;
+
+    /**
+     * CPU当前等待率
+     */
+    private double wait;
+
+    /**
+     * CPU当前空闲率
+     */
+    private double free;
+
+    public int getCpuNum()
+    {
+        return cpuNum;
+    }
+
+    public void setCpuNum(int cpuNum)
+    {
+        this.cpuNum = cpuNum;
+    }
+
+    public double getTotal()
+    {
+        return Arith.round(Arith.mul(total, 100), 2);
+    }
+
+    public void setTotal(double total)
+    {
+        this.total = total;
+    }
+
+    public double getSys()
+    {
+        return Arith.round(Arith.mul(sys / total, 100), 2);
+    }
+
+    public void setSys(double sys)
+    {
+        this.sys = sys;
+    }
+
+    public double getUsed()
+    {
+        return Arith.round(Arith.mul(used / total, 100), 2);
+    }
+
+    public void setUsed(double used)
+    {
+        this.used = used;
+    }
+
+    public double getWait()
+    {
+        return Arith.round(Arith.mul(wait / total, 100), 2);
+    }
+
+    public void setWait(double wait)
+    {
+        this.wait = wait;
+    }
+
+    public double getFree()
+    {
+        return Arith.round(Arith.mul(free / total, 100), 2);
+    }
+
+    public void setFree(double free)
+    {
+        this.free = free;
+    }
+}

+ 123 - 0
fs-admin/src/main/java/com/fs/framework/web/domain/server/Jvm.java

@@ -0,0 +1,123 @@
+package com.fs.framework.web.domain.server;
+
+import com.fs.common.utils.Arith;
+import com.fs.common.utils.DateUtils;
+
+import java.lang.management.ManagementFactory;
+
+/**
+ * JVM相关信息
+ *
+
+ */
+public class Jvm
+{
+    /**
+     * 当前JVM占用的内存总数(M)
+     */
+    private double total;
+
+    /**
+     * JVM最大可用内存总数(M)
+     */
+    private double max;
+
+    /**
+     * JVM空闲内存(M)
+     */
+    private double free;
+
+    /**
+     * JDK版本
+     */
+    private String version;
+
+    /**
+     * JDK路径
+     */
+    private String home;
+
+    public double getTotal()
+    {
+        return Arith.div(total, (1024 * 1024), 2);
+    }
+
+    public void setTotal(double total)
+    {
+        this.total = total;
+    }
+
+    public double getMax()
+    {
+        return Arith.div(max, (1024 * 1024), 2);
+    }
+
+    public void setMax(double max)
+    {
+        this.max = max;
+    }
+
+    public double getFree()
+    {
+        return Arith.div(free, (1024 * 1024), 2);
+    }
+
+    public void setFree(double free)
+    {
+        this.free = free;
+    }
+
+    public double getUsed()
+    {
+        return Arith.div(total - free, (1024 * 1024), 2);
+    }
+
+    public double getUsage()
+    {
+        return Arith.mul(Arith.div(total - free, total, 4), 100);
+    }
+
+    /**
+     * 获取JDK名称
+     */
+    public String getName()
+    {
+        return ManagementFactory.getRuntimeMXBean().getVmName();
+    }
+
+    public String getVersion()
+    {
+        return version;
+    }
+
+    public void setVersion(String version)
+    {
+        this.version = version;
+    }
+
+    public String getHome()
+    {
+        return home;
+    }
+
+    public void setHome(String home)
+    {
+        this.home = home;
+    }
+
+    /**
+     * JDK启动时间
+     */
+    public String getStartTime()
+    {
+        return DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, DateUtils.getServerStartDate());
+    }
+
+    /**
+     * JDK运行时间
+     */
+    public String getRunTime()
+    {
+        return DateUtils.getDatePoor(DateUtils.getNowDate(), DateUtils.getServerStartDate());
+    }
+}

+ 61 - 0
fs-admin/src/main/java/com/fs/framework/web/domain/server/Mem.java

@@ -0,0 +1,61 @@
+package com.fs.framework.web.domain.server;
+
+import com.fs.common.utils.Arith;
+
+/**
+ * 內存相关信息
+ * 
+
+ */
+public class Mem
+{
+    /**
+     * 内存总量
+     */
+    private double total;
+
+    /**
+     * 已用内存
+     */
+    private double used;
+
+    /**
+     * 剩余内存
+     */
+    private double free;
+
+    public double getTotal()
+    {
+        return Arith.div(total, (1024 * 1024 * 1024), 2);
+    }
+
+    public void setTotal(long total)
+    {
+        this.total = total;
+    }
+
+    public double getUsed()
+    {
+        return Arith.div(used, (1024 * 1024 * 1024), 2);
+    }
+
+    public void setUsed(long used)
+    {
+        this.used = used;
+    }
+
+    public double getFree()
+    {
+        return Arith.div(free, (1024 * 1024 * 1024), 2);
+    }
+
+    public void setFree(long free)
+    {
+        this.free = free;
+    }
+
+    public double getUsage()
+    {
+        return Arith.mul(Arith.div(used, total, 4), 100);
+    }
+}

+ 84 - 0
fs-admin/src/main/java/com/fs/framework/web/domain/server/Sys.java

@@ -0,0 +1,84 @@
+package com.fs.framework.web.domain.server;
+
+/**
+ * 系统相关信息
+ * 
+
+ */
+public class Sys
+{
+    /**
+     * 服务器名称
+     */
+    private String computerName;
+
+    /**
+     * 服务器Ip
+     */
+    private String computerIp;
+
+    /**
+     * 项目路径
+     */
+    private String userDir;
+
+    /**
+     * 操作系统
+     */
+    private String osName;
+
+    /**
+     * 系统架构
+     */
+    private String osArch;
+
+    public String getComputerName()
+    {
+        return computerName;
+    }
+
+    public void setComputerName(String computerName)
+    {
+        this.computerName = computerName;
+    }
+
+    public String getComputerIp()
+    {
+        return computerIp;
+    }
+
+    public void setComputerIp(String computerIp)
+    {
+        this.computerIp = computerIp;
+    }
+
+    public String getUserDir()
+    {
+        return userDir;
+    }
+
+    public void setUserDir(String userDir)
+    {
+        this.userDir = userDir;
+    }
+
+    public String getOsName()
+    {
+        return osName;
+    }
+
+    public void setOsName(String osName)
+    {
+        this.osName = osName;
+    }
+
+    public String getOsArch()
+    {
+        return osArch;
+    }
+
+    public void setOsArch(String osArch)
+    {
+        this.osArch = osArch;
+    }
+}

+ 114 - 0
fs-admin/src/main/java/com/fs/framework/web/domain/server/SysFile.java

@@ -0,0 +1,114 @@
+package com.fs.framework.web.domain.server;
+
+/**
+ * 系统文件相关信息
+ * 
+
+ */
+public class SysFile
+{
+    /**
+     * 盘符路径
+     */
+    private String dirName;
+
+    /**
+     * 盘符类型
+     */
+    private String sysTypeName;
+
+    /**
+     * 文件类型
+     */
+    private String typeName;
+
+    /**
+     * 总大小
+     */
+    private String total;
+
+    /**
+     * 剩余大小
+     */
+    private String free;
+
+    /**
+     * 已经使用量
+     */
+    private String used;
+
+    /**
+     * 资源的使用率
+     */
+    private double usage;
+
+    public String getDirName()
+    {
+        return dirName;
+    }
+
+    public void setDirName(String dirName)
+    {
+        this.dirName = dirName;
+    }
+
+    public String getSysTypeName()
+    {
+        return sysTypeName;
+    }
+
+    public void setSysTypeName(String sysTypeName)
+    {
+        this.sysTypeName = sysTypeName;
+    }
+
+    public String getTypeName()
+    {
+        return typeName;
+    }
+
+    public void setTypeName(String typeName)
+    {
+        this.typeName = typeName;
+    }
+
+    public String getTotal()
+    {
+        return total;
+    }
+
+    public void setTotal(String total)
+    {
+        this.total = total;
+    }
+
+    public String getFree()
+    {
+        return free;
+    }
+
+    public void setFree(String free)
+    {
+        this.free = free;
+    }
+
+    public String getUsed()
+    {
+        return used;
+    }
+
+    public void setUsed(String used)
+    {
+        this.used = used;
+    }
+
+    public double getUsage()
+    {
+        return usage;
+    }
+
+    public void setUsage(double usage)
+    {
+        this.usage = usage;
+    }
+}

+ 115 - 0
fs-admin/src/main/java/com/fs/framework/web/exception/GlobalExceptionHandler.java

@@ -0,0 +1,115 @@
+package com.fs.framework.web.exception;
+
+import com.fs.common.constant.HttpStatus;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.exception.DemoModeException;
+import com.fs.common.exception.ServiceException;
+import com.fs.common.utils.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.validation.BindException;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 全局异常处理器
+ *
+
+ */
+@RestControllerAdvice
+public class GlobalExceptionHandler
+{
+    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
+
+    /**
+     * 权限校验异常
+     */
+    @ExceptionHandler(AccessDeniedException.class)
+    public AjaxResult handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request)
+    {
+        String requestURI = request.getRequestURI();
+        log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage());
+        return AjaxResult.error(HttpStatus.FORBIDDEN, "没有权限,请联系管理员授权");
+    }
+
+    /**
+     * 请求方式不支持
+     */
+    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
+    public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
+            HttpServletRequest request)
+    {
+        String requestURI = request.getRequestURI();
+        log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
+        return AjaxResult.error(e.getMessage());
+    }
+
+    /**
+     * 业务异常
+     */
+    @ExceptionHandler(ServiceException.class)
+    public AjaxResult handleServiceException(ServiceException e, HttpServletRequest request)
+    {
+        log.error(e.getMessage(), e);
+        Integer code = e.getCode();
+        return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage());
+    }
+
+    /**
+     * 拦截未知的运行时异常
+     */
+    @ExceptionHandler(RuntimeException.class)
+    public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request)
+    {
+        String requestURI = request.getRequestURI();
+        log.error("请求地址'{}',发生未知异常.", requestURI, e);
+        return AjaxResult.error(e.getMessage());
+    }
+
+    /**
+     * 系统异常
+     */
+    @ExceptionHandler(Exception.class)
+    public AjaxResult handleException(Exception e, HttpServletRequest request)
+    {
+        String requestURI = request.getRequestURI();
+        log.error("请求地址'{}',发生系统异常.", requestURI, e);
+        return AjaxResult.error(e.getMessage());
+    }
+
+    /**
+     * 自定义验证异常
+     */
+    @ExceptionHandler(BindException.class)
+    public AjaxResult handleBindException(BindException e)
+    {
+        log.error(e.getMessage(), e);
+        String message = e.getAllErrors().get(0).getDefaultMessage();
+        return AjaxResult.error(message);
+    }
+
+    /**
+     * 自定义验证异常
+     */
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e)
+    {
+        log.error(e.getMessage(), e);
+        String message = e.getBindingResult().getFieldError().getDefaultMessage();
+        return AjaxResult.error(message);
+    }
+
+    /**
+     * 演示模式异常
+     */
+    @ExceptionHandler(DemoModeException.class)
+    public AjaxResult handleDemoModeException(DemoModeException e)
+    {
+        return AjaxResult.error("演示模式,不允许操作");
+    }
+}

+ 166 - 0
fs-admin/src/main/java/com/fs/framework/web/service/PermissionService.java

@@ -0,0 +1,166 @@
+package com.fs.framework.web.service;
+
+import com.fs.common.core.domain.entity.SysRole;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.utils.SecurityUtils;
+import com.fs.common.utils.StringUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import java.util.Set;
+
+/**
+ *  自定义权限实现,ss取自SpringSecurity首字母
+ *
+
+ */
+@Service("ss")
+public class PermissionService
+{
+    /** 所有权限标识 */
+    private static final String ALL_PERMISSION = "*:*:*";
+
+    /** 管理员角色权限标识 */
+    private static final String SUPER_ADMIN = "admin";
+
+    private static final String ROLE_DELIMETER = ",";
+
+    private static final String PERMISSION_DELIMETER = ",";
+
+    /**
+     * 验证用户是否具备某权限
+     *
+     * @param permission 权限字符串
+     * @return 用户是否具备某权限
+     */
+    public boolean hasPermi(String permission)
+    {
+        if (StringUtils.isEmpty(permission))
+        {
+            return false;
+        }
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions()))
+        {
+            return false;
+        }
+        return hasPermissions(loginUser.getPermissions(), permission);
+    }
+
+    /**
+     * 验证用户是否不具备某权限,与 hasPermi逻辑相反
+     *
+     * @param permission 权限字符串
+     * @return 用户是否不具备某权限
+     */
+    public boolean lacksPermi(String permission)
+    {
+        return hasPermi(permission) != true;
+    }
+
+    /**
+     * 验证用户是否具有以下任意一个权限
+     *
+     * @param permissions 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表
+     * @return 用户是否具有以下任意一个权限
+     */
+    public boolean hasAnyPermi(String permissions)
+    {
+        if (StringUtils.isEmpty(permissions))
+        {
+            return false;
+        }
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions()))
+        {
+            return false;
+        }
+        Set<String> authorities = loginUser.getPermissions();
+        for (String permission : permissions.split(PERMISSION_DELIMETER))
+        {
+            if (permission != null && hasPermissions(authorities, permission))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 判断用户是否拥有某个角色
+     *
+     * @param role 角色字符串
+     * @return 用户是否具备某角色
+     */
+    public boolean hasRole(String role)
+    {
+        if (StringUtils.isEmpty(role))
+        {
+            return false;
+        }
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles()))
+        {
+            return false;
+        }
+        for (SysRole sysRole : loginUser.getUser().getRoles())
+        {
+            String roleKey = sysRole.getRoleKey();
+            if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role)))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 验证用户是否不具备某角色,与 isRole逻辑相反。
+     *
+     * @param role 角色名称
+     * @return 用户是否不具备某角色
+     */
+    public boolean lacksRole(String role)
+    {
+        return hasRole(role) != true;
+    }
+
+    /**
+     * 验证用户是否具有以下任意一个角色
+     *
+     * @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表
+     * @return 用户是否具有以下任意一个角色
+     */
+    public boolean hasAnyRoles(String roles)
+    {
+        if (StringUtils.isEmpty(roles))
+        {
+            return false;
+        }
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles()))
+        {
+            return false;
+        }
+        for (String role : roles.split(ROLE_DELIMETER))
+        {
+            if (hasRole(role))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 判断是否包含权限
+     *
+     * @param permissions 权限列表
+     * @param permission 权限字符串
+     * @return 用户是否具备某权限
+     */
+    private boolean hasPermissions(Set<String> permissions, String permission)
+    {
+        return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));
+    }
+}

+ 135 - 0
fs-admin/src/main/java/com/fs/framework/web/service/SysLoginService.java

@@ -0,0 +1,135 @@
+package com.fs.framework.web.service;
+
+import com.fs.common.constant.Constants;
+import com.fs.common.core.domain.entity.SysUser;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.common.exception.ServiceException;
+import com.fs.common.exception.user.CaptchaException;
+import com.fs.common.exception.user.CaptchaExpireException;
+import com.fs.common.exception.user.UserPasswordNotMatchException;
+import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.MessageUtils;
+import com.fs.common.utils.SecurityUtils;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.ip.IpUtils;
+import com.fs.company.service.ICompanyUserService;
+import com.fs.framework.manager.AsyncManager;
+import com.fs.framework.manager.factory.AsyncFactory;
+import com.fs.system.service.ISysConfigService;
+import com.fs.system.service.ISysUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * 登录校验方法
+ *
+
+ */
+@Component
+public class SysLoginService
+{
+    @Autowired
+    private TokenService tokenService;
+
+    @Resource
+    private AuthenticationManager authenticationManager;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    @Autowired
+    private ISysUserService userService;
+
+    @Autowired
+    private ISysConfigService configService;
+
+    /**
+     * 登录验证
+     *
+     * @param username 用户名
+     * @param password 密码
+     * @param code 验证码
+     * @param uuid 唯一标识
+     * @return 结果
+     */
+    public String login(String username, String password, String code, String uuid)
+    {
+
+
+
+        boolean captchaOnOff = configService.selectCaptchaOnOff();
+        // 验证码开关
+        if (captchaOnOff)
+        {
+            validateCaptcha(username, code, uuid);
+        }
+        // 用户验证
+        Authentication authentication = null;
+        try
+        {
+            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
+            authentication = authenticationManager
+                    .authenticate(new UsernamePasswordAuthenticationToken(username, password));
+        }
+        catch (Exception e)
+        {
+            if (e instanceof BadCredentialsException)
+            {
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
+                throw new UserPasswordNotMatchException();
+            }
+            else
+            {
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
+                throw new ServiceException(e.getMessage());
+            }
+        }
+        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
+        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
+        recordLoginInfo(loginUser.getUser());
+        // 生成token
+        return tokenService.createToken(loginUser);
+    }
+
+    /**
+     * 校验验证码
+     *
+     * @param username 用户名
+     * @param code 验证码
+     * @param uuid 唯一标识
+     * @return 结果
+     */
+    public void validateCaptcha(String username, String code, String uuid)
+    {
+        String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
+        String captcha = redisCache.getCacheObject(verifyKey);
+        redisCache.deleteObject(verifyKey);
+        if (captcha == null)
+        {
+            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
+            throw new CaptchaExpireException();
+        }
+        if (!code.equalsIgnoreCase(captcha))
+        {
+            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
+            throw new CaptchaException();
+        }
+    }
+
+    /**
+     * 记录登录信息
+     */
+    public void recordLoginInfo(SysUser user)
+    {
+        user.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
+        user.setLoginDate(DateUtils.getNowDate());
+        userService.updateUserProfile(user);
+    }
+}

+ 67 - 0
fs-admin/src/main/java/com/fs/framework/web/service/SysPermissionService.java

@@ -0,0 +1,67 @@
+package com.fs.framework.web.service;
+
+import com.fs.common.core.domain.entity.SysUser;
+import com.fs.system.service.ISysMenuService;
+import com.fs.system.service.ISysRoleService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * 用户权限处理
+ *
+
+ */
+@Component
+public class SysPermissionService
+{
+    @Autowired
+    private ISysRoleService roleService;
+
+    @Autowired
+    private ISysMenuService menuService;
+
+    /**
+     * 获取角色数据权限
+     *
+     * @param user 用户信息
+     * @return 角色权限信息
+     */
+    public Set<String> getRolePermission(SysUser user)
+    {
+        Set<String> roles = new HashSet<String>();
+        // 管理员拥有所有权限
+        if (user.isAdmin())
+        {
+            roles.add("admin");
+        }
+        else
+        {
+            roles.addAll(roleService.selectRolePermissionByUserId(user.getUserId()));
+        }
+        return roles;
+    }
+
+    /**
+     * 获取菜单数据权限
+     *
+     * @param user 用户信息
+     * @return 菜单权限信息
+     */
+    public Set<String> getMenuPermission(SysUser user)
+    {
+        Set<String> perms = new HashSet<String>();
+        // 管理员拥有所有权限
+        if (user.isAdmin())
+        {
+            perms.add("*:*:*");
+        }
+        else
+        {
+            perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId()));
+        }
+        return perms;
+    }
+}

+ 115 - 0
fs-admin/src/main/java/com/fs/framework/web/service/SysRegisterService.java

@@ -0,0 +1,115 @@
+package com.fs.framework.web.service;
+
+import com.fs.common.constant.Constants;
+import com.fs.common.constant.UserConstants;
+import com.fs.common.core.domain.entity.SysUser;
+import com.fs.common.core.domain.model.RegisterBody;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.common.exception.user.CaptchaException;
+import com.fs.common.exception.user.CaptchaExpireException;
+import com.fs.common.utils.MessageUtils;
+import com.fs.common.utils.SecurityUtils;
+import com.fs.framework.manager.AsyncManager;
+import com.fs.framework.manager.factory.AsyncFactory;
+import com.fs.system.service.ISysConfigService;
+import com.fs.system.service.ISysUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+/**
+ * 注册校验方法
+ *
+
+ */
+@Component
+public class SysRegisterService
+{
+    @Autowired
+    private ISysUserService userService;
+
+    @Autowired
+    private ISysConfigService configService;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    /**
+     * 注册
+     */
+    public String register(RegisterBody registerBody)
+    {
+        String msg = "", username = registerBody.getUsername(), password = registerBody.getPassword();
+
+        boolean captchaOnOff = configService.selectCaptchaOnOff();
+        // 验证码开关
+        if (captchaOnOff)
+        {
+            validateCaptcha(username, registerBody.getCode(), registerBody.getUuid());
+        }
+
+        if (StringUtils.isEmpty(username))
+        {
+            msg = "用户名不能为空";
+        }
+        else if (StringUtils.isEmpty(password))
+        {
+            msg = "用户密码不能为空";
+        }
+        else if (username.length() < UserConstants.USERNAME_MIN_LENGTH
+                || username.length() > UserConstants.USERNAME_MAX_LENGTH)
+        {
+            msg = "账户长度必须在2到20个字符之间";
+        }
+        else if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
+                || password.length() > UserConstants.PASSWORD_MAX_LENGTH)
+        {
+            msg = "密码长度必须在5到20个字符之间";
+        }
+        else if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(username)))
+        {
+            msg = "保存用户'" + username + "'失败,注册账号已存在";
+        }
+        else
+        {
+            SysUser sysUser = new SysUser();
+            sysUser.setUserName(username);
+            sysUser.setNickName(username);
+            sysUser.setPassword(SecurityUtils.encryptPassword(registerBody.getPassword()));
+            boolean regFlag = userService.registerUser(sysUser);
+            if (!regFlag)
+            {
+                msg = "注册失败,请联系系统管理人员";
+            }
+            else
+            {
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.REGISTER,
+                        MessageUtils.message("user.register.success")));
+            }
+        }
+        return msg;
+    }
+
+    /**
+     * 校验验证码
+     *
+     * @param username 用户名
+     * @param code 验证码
+     * @param uuid 唯一标识
+     * @return 结果
+     */
+    public void validateCaptcha(String username, String code, String uuid)
+    {
+        String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
+        String captcha = redisCache.getCacheObject(verifyKey);
+        redisCache.deleteObject(verifyKey);
+        if (captcha == null)
+        {
+            throw new CaptchaExpireException();
+        }
+        if (!code.equalsIgnoreCase(captcha))
+        {
+            throw new CaptchaException();
+        }
+    }
+}

+ 227 - 0
fs-admin/src/main/java/com/fs/framework/web/service/TokenService.java

@@ -0,0 +1,227 @@
+package com.fs.framework.web.service;
+
+import com.fs.common.constant.Constants;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.ip.AddressUtils;
+import com.fs.common.utils.ip.IpUtils;
+import com.fs.common.utils.uuid.IdUtils;
+import eu.bitwalker.useragentutils.UserAgent;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * token验证处理
+ *
+
+ */
+@Component
+public class TokenService
+{
+    // 令牌自定义标识
+    @Value("${token.header}")
+    private String header;
+
+    // 令牌秘钥
+    @Value("${token.secret}")
+    private String secret;
+
+    // 令牌有效期(默认30分钟)
+    @Value("${token.expireTime}")
+    private int expireTime;
+
+    protected static final long MILLIS_SECOND = 1000;
+
+    protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
+
+    private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    /**
+     * 获取用户身份信息
+     *
+     * @return 用户信息
+     */
+    public LoginUser getLoginUser(HttpServletRequest request)
+    {
+        // 获取请求携带的令牌
+        String token = getToken(request);
+        if (StringUtils.isNotEmpty(token))
+        {
+            try
+            {
+                Claims claims = parseToken(token);
+                // 解析对应的权限以及用户信息
+                String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
+                String userKey = getTokenKey(uuid);
+                LoginUser user = redisCache.getCacheObject(userKey);
+                return user;
+            }
+            catch (Exception e)
+            {
+                System.out.println(e.getLocalizedMessage());
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 设置用户身份信息
+     */
+    public void setLoginUser(LoginUser loginUser)
+    {
+        if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken()))
+        {
+            refreshToken(loginUser);
+        }
+    }
+
+    /**
+     * 删除用户身份信息
+     */
+    public void delLoginUser(String token)
+    {
+        if (StringUtils.isNotEmpty(token))
+        {
+            String userKey = getTokenKey(token);
+            redisCache.deleteObject(userKey);
+        }
+    }
+
+    /**
+     * 创建令牌
+     *
+     * @param loginUser 用户信息
+     * @return 令牌
+     */
+    public String createToken(LoginUser loginUser)
+    {
+        String token = IdUtils.fastUUID();
+        loginUser.setToken(token);
+        setUserAgent(loginUser);
+        refreshToken(loginUser);
+
+        Map<String, Object> claims = new HashMap<>();
+        claims.put(Constants.LOGIN_USER_KEY, token);
+        return createToken(claims);
+    }
+
+    /**
+     * 验证令牌有效期,相差不足20分钟,自动刷新缓存
+     *
+     * @param loginUser
+     * @return 令牌
+     */
+    public void verifyToken(LoginUser loginUser)
+    {
+        long expireTime = loginUser.getExpireTime();
+        long currentTime = System.currentTimeMillis();
+        if (expireTime - currentTime <= MILLIS_MINUTE_TEN)
+        {
+            refreshToken(loginUser);
+        }
+    }
+
+    /**
+     * 刷新令牌有效期
+     *
+     * @param loginUser 登录信息
+     */
+    public void refreshToken(LoginUser loginUser)
+    {
+        loginUser.setLoginTime(System.currentTimeMillis());
+        loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
+        // 根据uuid将loginUser缓存
+        String userKey = getTokenKey(loginUser.getToken());
+        redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
+    }
+
+    /**
+     * 设置用户代理信息
+     *
+     * @param loginUser 登录信息
+     */
+    public void setUserAgent(LoginUser loginUser)
+    {
+        UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
+        String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
+        loginUser.setIpaddr(ip);
+        loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
+        loginUser.setBrowser(userAgent.getBrowser().getName());
+        loginUser.setOs(userAgent.getOperatingSystem().getName());
+    }
+
+    /**
+     * 从数据声明生成令牌
+     *
+     * @param claims 数据声明
+     * @return 令牌
+     */
+    private String createToken(Map<String, Object> claims)
+    {
+        String token = Jwts.builder()
+                .setClaims(claims)
+                .signWith(SignatureAlgorithm.HS512, secret).compact();
+        return token;
+    }
+
+    /**
+     * 从令牌中获取数据声明
+     *
+     * @param token 令牌
+     * @return 数据声明
+     */
+    private Claims parseToken(String token)
+    {
+        return Jwts.parser()
+                .setSigningKey(secret)
+                .parseClaimsJws(token)
+                .getBody();
+    }
+
+    /**
+     * 从令牌中获取用户名
+     *
+     * @param token 令牌
+     * @return 用户名
+     */
+    public String getUsernameFromToken(String token)
+    {
+        Claims claims = parseToken(token);
+        return claims.getSubject();
+    }
+
+    /**
+     * 获取请求token
+     *
+     * @param request
+     * @return token
+     */
+    private String getToken(HttpServletRequest request)
+    {
+        String token = request.getHeader(header);
+        if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX))
+        {
+            token = token.replace(Constants.TOKEN_PREFIX, "");
+        }
+        return token;
+    }
+
+    private String getTokenKey(String uuid)
+    {
+        return Constants.LOGIN_TOKEN_KEY + uuid;
+    }
+}

+ 60 - 0
fs-admin/src/main/java/com/fs/framework/web/service/UserDetailsServiceImpl.java

@@ -0,0 +1,60 @@
+package com.fs.framework.web.service;
+
+import com.fs.common.core.domain.entity.SysUser;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.enums.UserStatus;
+import com.fs.common.exception.ServiceException;
+import com.fs.common.utils.StringUtils;
+import com.fs.system.service.ISysUserService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+
+/**
+ * 用户验证处理
+ *
+
+ */
+@Service
+public class UserDetailsServiceImpl implements UserDetailsService
+{
+    private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
+
+    @Autowired
+    private ISysUserService userService;
+
+    @Autowired
+    private SysPermissionService permissionService;
+
+    @Override
+    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
+    {
+        SysUser user = userService.selectUserByUserName(username);
+        if (StringUtils.isNull(user))
+        {
+            log.info("登录用户:{} 不存在.", username);
+            throw new ServiceException("登录用户:" + username + " 不存在");
+        }
+        else if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
+        {
+            log.info("登录用户:{} 已被删除.", username);
+            throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
+        }
+        else if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
+        {
+            log.info("登录用户:{} 已被停用.", username);
+            throw new ServiceException("对不起,您的账号:" + username + " 已停用");
+        }
+
+        return createLoginUser(user);
+    }
+
+    public UserDetails createLoginUser(SysUser user)
+    {
+        return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
+    }
+}

+ 97 - 0
fs-admin/src/main/java/com/fs/project/controller/ProjectContractController.java

@@ -0,0 +1,97 @@
+package com.fs.project.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.project.domain.ProjectContract;
+import com.fs.project.service.IProjectContractService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 项目合同信息Controller
+ * 
+ * @author fs
+ * @date 2025-12-30
+ */
+@RestController
+@RequestMapping("/project/contract")
+public class ProjectContractController extends BaseController
+{
+    @Autowired
+    private IProjectContractService projectContractService;
+
+    /**
+     * 查询项目合同信息列表
+     */
+    @PreAuthorize("@ss.hasPermi('project:contract:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(ProjectContract projectContract)
+    {
+        startPage();
+        List<ProjectContract> list = projectContractService.selectProjectContractList(projectContract);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出项目合同信息列表
+     */
+    @PreAuthorize("@ss.hasPermi('project:contract:export')")
+    @Log(title = "项目合同信息", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(ProjectContract projectContract)
+    {
+        List<ProjectContract> list = projectContractService.selectProjectContractList(projectContract);
+        ExcelUtil<ProjectContract> util = new ExcelUtil<ProjectContract>(ProjectContract.class);
+        return util.exportExcel(list, "项目合同信息数据");
+    }
+
+    /**
+     * 获取项目合同信息详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('project:contract:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(projectContractService.selectProjectContractById(id));
+    }
+
+    /**
+     * 新增项目合同信息
+     */
+    @PreAuthorize("@ss.hasPermi('project:contract:add')")
+    @Log(title = "项目合同信息", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody ProjectContract projectContract)
+    {
+        return toAjax(projectContractService.insertProjectContract(projectContract));
+    }
+
+    /**
+     * 修改项目合同信息
+     */
+    @PreAuthorize("@ss.hasPermi('project:contract:edit')")
+    @Log(title = "项目合同信息", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody ProjectContract projectContract)
+    {
+        return toAjax(projectContractService.updateProjectContract(projectContract));
+    }
+
+    /**
+     * 删除项目合同信息
+     */
+    @PreAuthorize("@ss.hasPermi('project:contract:remove')")
+    @Log(title = "项目合同信息", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(projectContractService.deleteProjectContractByIds(ids));
+    }
+}

+ 113 - 0
fs-admin/src/main/java/com/fs/project/controller/ProjectController.java

@@ -0,0 +1,113 @@
+package com.fs.project.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.project.domain.Project;
+import com.fs.project.service.IProjectService;
+import com.fs.project.vo.AddProjectReqVO;
+import com.fs.project.vo.ProjectOptionVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 项目Controller
+ *
+ * @author fs
+ * @date 2025-12-30
+ */
+@RestController
+@RequestMapping("/project/project")
+public class ProjectController extends BaseController {
+    @Autowired
+    private IProjectService projectService;
+
+    /**
+     * 查询项目列表
+     */
+    @PreAuthorize("@ss.hasPermi('project:project:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(Project project) {
+        startPage();
+        List<Project> list = projectService.selectProjectList(project);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出项目列表
+     */
+    @PreAuthorize("@ss.hasPermi('project:project:export')")
+    @Log(title = "项目", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(Project project) {
+        List<Project> list = projectService.selectProjectList(project);
+        ExcelUtil<Project> util = new ExcelUtil<Project>(Project.class);
+        return util.exportExcel(list, "项目数据");
+    }
+
+    /**
+     * 获取项目详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('project:project:query')")
+    @GetMapping("/{id:\\d+}")
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
+        return AjaxResult.success(projectService.selectProjectDetailById(id));
+    }
+
+    /**
+     * 新增项目
+     */
+    @PreAuthorize("@ss.hasPermi('project:project:add')")
+    @Log(title = "项目", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody AddProjectReqVO addProjectReqVO) {
+        return toAjax(projectService.add(addProjectReqVO));
+    }
+
+    /**
+     * 修改项目
+     */
+    @PreAuthorize("@ss.hasPermi('project:project:edit')")
+    @Log(title = "项目", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody AddProjectReqVO addProjectReqVO) {
+        return toAjax(projectService.edit(addProjectReqVO));
+    }
+
+    /**
+     * 删除项目
+     */
+    @PreAuthorize("@ss.hasPermi('project:project:remove')")
+    @Log(title = "项目", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        return toAjax(projectService.deleteProjectByIds(ids));
+    }
+
+
+    @PreAuthorize("@ss.hasPermi('project:project:list')")
+    @GetMapping("/options")
+    public AjaxResult options(@RequestParam(required = false) String projectName) {
+        List<ProjectOptionVO> list = projectService.selectProjectOptions(projectName);
+        return AjaxResult.success(list);
+    }
+
+
+    /**
+     * 修改项目启用状态
+     */
+    @PreAuthorize("@ss.hasPermi('project:project:edit')")
+    @Log(title = "项目", businessType = BusinessType.UPDATE)
+    @PutMapping("/changeStatus")
+    public AjaxResult changeStatus(@RequestBody Project project) {
+        // 只需要 id 和 status
+        return toAjax(projectService.changeStatus(project.getId(), project.getStatus()));
+    }
+
+}

+ 97 - 0
fs-admin/src/main/java/com/fs/project/controller/ProjectPermissionController.java

@@ -0,0 +1,97 @@
+package com.fs.project.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.project.domain.ProjectPermission;
+import com.fs.project.service.IProjectPermissionService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 项目权限Controller
+ * 
+ * @author fs
+ * @date 2025-12-30
+ */
+@RestController
+@RequestMapping("/project/permission")
+public class ProjectPermissionController extends BaseController
+{
+    @Autowired
+    private IProjectPermissionService projectPermissionService;
+
+    /**
+     * 查询项目权限列表
+     */
+    @PreAuthorize("@ss.hasPermi('project:permission:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(ProjectPermission projectPermission)
+    {
+        startPage();
+         List<ProjectPermission> list = projectPermissionService.selectProjectPermissionList(projectPermission);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出项目权限列表
+     */
+    @PreAuthorize("@ss.hasPermi('project:permission:export')")
+    @Log(title = "项目权限", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(ProjectPermission projectPermission)
+    {
+        List<ProjectPermission> list = projectPermissionService.selectProjectPermissionList(projectPermission);
+        ExcelUtil<ProjectPermission> util = new ExcelUtil<ProjectPermission>(ProjectPermission.class);
+        return util.exportExcel(list, "项目权限数据");
+    }
+
+    /**
+     * 获取项目权限详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('project:permission:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(projectPermissionService.selectProjectPermissionById(id));
+    }
+
+    /**
+     * 新增项目权限
+     */
+    @PreAuthorize("@ss.hasPermi('project:permission:add')")
+    @Log(title = "项目权限", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody ProjectPermission projectPermission)
+    {
+        return toAjax(projectPermissionService.insertProjectPermission(projectPermission));
+    }
+
+    /**
+     * 修改项目权限
+     */
+    @PreAuthorize("@ss.hasPermi('project:permission:edit')")
+    @Log(title = "项目权限", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody ProjectPermission projectPermission)
+    {
+        return toAjax(projectPermissionService.updateProjectPermission(projectPermission));
+    }
+
+    /**
+     * 删除项目权限
+     */
+    @PreAuthorize("@ss.hasPermi('project:permission:remove')")
+    @Log(title = "项目权限", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(projectPermissionService.deleteProjectPermissionByIds(ids));
+    }
+}

+ 143 - 0
fs-admin/src/main/java/com/fs/project/controller/ProjectSettingsController.java

@@ -0,0 +1,143 @@
+package com.fs.project.controller;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import com.fs.common.config.LoginContextManager;
+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.project.domain.ProjectSettings;
+import com.fs.project.service.IProjectSettingsService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 项目设置Controller
+ *
+ * @author fs
+ * @date 2026-01-12
+ */
+@RestController
+@RequestMapping("/project/settings")
+public class ProjectSettingsController extends BaseController
+{
+    @Autowired
+    private IProjectSettingsService projectSettingsService;
+
+    /**
+     * 查询项目设置列表
+     */
+    @PreAuthorize("@ss.hasPermi('project:settings:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(ProjectSettings projectSettings)
+    {
+        startPage();
+        List<ProjectSettings> list = projectSettingsService.selectProjectSettingsList(projectSettings);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出项目设置列表
+     */
+    @PreAuthorize("@ss.hasPermi('project:settings:export')")
+    @Log(title = "项目设置", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(ProjectSettings projectSettings)
+    {
+        List<ProjectSettings> list = projectSettingsService.selectProjectSettingsList(projectSettings);
+        ExcelUtil<ProjectSettings> util = new ExcelUtil<ProjectSettings>(ProjectSettings.class);
+        return util.exportExcel(list, "项目设置数据");
+    }
+
+    /**
+     * 获取项目设置详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('project:settings:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(projectSettingsService.selectProjectSettingsById(id));
+    }
+
+    /**
+     * 新增项目设置
+     */
+    @PreAuthorize("@ss.hasPermi('project:settings:add')")
+    @Log(title = "项目设置", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody ProjectSettings projectSettings)
+    {
+        return toAjax(projectSettingsService.insertProjectSettings(projectSettings));
+    }
+
+    /**
+     * 修改项目设置
+     */
+    @PreAuthorize("@ss.hasPermi('project:settings:edit')")
+    @Log(title = "项目设置", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody ProjectSettings projectSettings)
+    {
+        return toAjax(projectSettingsService.updateProjectSettings(projectSettings));
+    }
+
+    /**
+     * 删除项目设置
+     */
+    @PreAuthorize("@ss.hasPermi('project:settings:remove')")
+    @Log(title = "项目设置", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(projectSettingsService.deleteProjectSettingsByIds(ids));
+    }
+
+    /**
+     * 查询指定公司下的项目设置列表
+     */
+    @GetMapping("/company/{companyId}")
+    public AjaxResult listByCompanyId(@PathVariable String companyId)
+    {
+        return AjaxResult.success(projectSettingsService.selectProjectSettingsByCompanyId(companyId));
+    }
+
+
+
+    /**
+     * 查询项目设置具体配置
+     */
+    @PreAuthorize("@ss.hasPermi('project:settings:list')")
+    @GetMapping("/getSettingTaskTypeList")
+    public AjaxResult getprojectSetting(ProjectSettings projectSettings) throws Exception {
+        startPage();
+        Long currentCompanyId = LoginContextManager.getCurrentCompanyId();
+        projectSettings.setCompanyId(currentCompanyId);
+        List<ProjectSettings> list = projectSettingsService.selectProjectSettingsList(projectSettings);
+        ProjectSettings projectSettings1 = list.get(0);
+        if(Objects.isNull(projectSettings1)){
+            throw new Exception("当前部门下未配置项目设置,请先配置");
+        }
+        //逗号分割的任务类型
+        String inHisTaskTypes = projectSettings1.getInHisTaskTypes();
+        List<Integer> taskTypeIdList = Arrays.stream(inHisTaskTypes.split(","))
+                .map(String::trim)
+                .filter(s -> !s.isEmpty())
+                .map(Integer::parseInt)
+                .collect(Collectors.toList());
+        return AjaxResult.success(taskTypeIdList);
+    }
+}

+ 108 - 0
fs-admin/src/main/java/com/fs/project/controller/ProjectStandardController.java

@@ -0,0 +1,108 @@
+package com.fs.project.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.project.domain.ProjectStandard;
+import com.fs.project.service.IProjectStandardService;
+import com.fs.project.vo.ProjectStandardDTO;
+import com.fs.project.vo.ProjectStandardVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Set;
+
+@RestController
+@RequestMapping("/project/standard")
+public class ProjectStandardController extends BaseController {
+
+    @Autowired
+    private IProjectStandardService projectStandardService;
+
+    /**
+     * 查询列表 - 返回 VO(自动转换 levelConfigList)
+     */
+    @PreAuthorize("@ss.hasPermi('project:standard:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(ProjectStandard projectStandard) {
+        startPage();
+        List<ProjectStandardVO> voList = projectStandardService.listVo(projectStandard);
+        return getDataTable(voList);
+    }
+
+    /**
+     * 导出(暂保持原逻辑,导出字符串 JSON)
+     */
+    @PreAuthorize("@ss.hasPermi('project:standard:export')")
+    @Log(title = "项目标准配置", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(ProjectStandard projectStandard) {
+        List<ProjectStandard> list = projectStandardService.selectProjectStandardList(projectStandard);
+        ExcelUtil<ProjectStandard> util = new ExcelUtil<>(ProjectStandard.class);
+        return util.exportExcel(list, "项目标准配置数据");
+    }
+
+    /**
+     * 获取详情 - 返回 VO
+     */
+    @PreAuthorize("@ss.hasPermi('project:standard:query')")
+    @GetMapping("/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
+        ProjectStandardVO vo = projectStandardService.getDetailVo(id);
+        return AjaxResult.success(vo);
+    }
+
+    /**
+     * 新增 - 接收 DTO
+     */
+    @PreAuthorize("@ss.hasPermi('project:standard:add')")
+    @Log(title = "项目标准配置", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody ProjectStandardDTO dto) {
+        // 校验唯一性
+        projectStandardService.checkParseTaskTypes(dto);
+        projectStandardService.saveOrUpdate(dto);
+        return AjaxResult.success();
+    }
+
+    /**
+     * 修改 - 接收 DTO
+     */
+    @PreAuthorize("@ss.hasPermi('project:standard:edit')")
+    @Log(title = "项目标准配置", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody ProjectStandardDTO dto) {
+        projectStandardService.checkEditParseTaskTypes(dto);
+        projectStandardService.saveOrUpdate(dto);
+        return AjaxResult.success();
+    }
+
+    /**
+     * 删除(逻辑删除)
+     */
+    @PreAuthorize("@ss.hasPermi('project:standard:remove')")
+    @Log(title = "项目标准配置", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        return toAjax(projectStandardService.deleteProjectStandardByIds(ids));
+    }
+
+
+    /**
+     * 获取项目允许的最大积分额度
+     *
+     * @param projectId 项目ID(必填)
+     * @return { "maxIntegral": 500.00 },若未配置则返回 null
+     */
+    @PreAuthorize("@ss.hasPermi('project:standard:query')")
+    @GetMapping("/checkIntegralLimit")
+    public AjaxResult checkIntegralLimit(@RequestParam Long projectId, @RequestParam Long doctorId, @RequestParam Integer pointNum,Long taskType) {
+        return AjaxResult.success(projectStandardService.getCompanyIntegralLimit(projectId, doctorId, pointNum,taskType));
+
+    }
+}

+ 109 - 0
fs-admin/src/main/java/com/fs/project/controller/ProjectTaskTypeController.java

@@ -0,0 +1,109 @@
+package com.fs.project.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.project.domain.ProjectTaskType;
+import com.fs.project.service.IProjectTaskTypeService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 项目任务类型Controller
+ * 
+ * @author fs
+ * @date 2025-12-30
+ */
+@RestController
+@RequestMapping("/project/type")
+public class ProjectTaskTypeController extends BaseController
+{
+    @Autowired
+    private IProjectTaskTypeService projectTaskTypeService;
+
+    /**
+     * 查询项目任务类型列表
+     */
+    @PreAuthorize("@ss.hasPermi('project:type:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(ProjectTaskType projectTaskType)
+    {
+        startPage();
+        List<ProjectTaskType> list = projectTaskTypeService.selectProjectTaskTypeList(projectTaskType);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出项目任务类型列表
+     */
+    @PreAuthorize("@ss.hasPermi('project:type:export')")
+    @Log(title = "项目任务类型", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(ProjectTaskType projectTaskType)
+    {
+        List<ProjectTaskType> list = projectTaskTypeService.selectProjectTaskTypeList(projectTaskType);
+        ExcelUtil<ProjectTaskType> util = new ExcelUtil<ProjectTaskType>(ProjectTaskType.class);
+        return util.exportExcel(list, "项目任务类型数据");
+    }
+
+    /**
+     * 获取项目任务类型详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('project:type:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(projectTaskTypeService.selectProjectTaskTypeById(id));
+    }
+
+    /**
+     * 新增项目任务类型
+     */
+    @PreAuthorize("@ss.hasPermi('project:type:add')")
+    @Log(title = "项目任务类型", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody ProjectTaskType projectTaskType)
+    {
+        return toAjax(projectTaskTypeService.insertProjectTaskType(projectTaskType));
+    }
+
+    /**
+     * 修改项目任务类型
+     */
+    @PreAuthorize("@ss.hasPermi('project:type:edit')")
+    @Log(title = "项目任务类型", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody ProjectTaskType projectTaskType)
+    {
+        return toAjax(projectTaskTypeService.updateProjectTaskType(projectTaskType));
+    }
+
+    /**
+     * 删除项目任务类型
+     */
+    @PreAuthorize("@ss.hasPermi('project:type:remove')")
+    @Log(title = "项目任务类型", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(projectTaskTypeService.deleteProjectTaskTypeByIds(ids));
+    }
+
+
+
+
+    @GetMapping("/selectProjectOptionsByDept")
+    public AjaxResult optionsByDept(@RequestParam Long deptId,
+                                    @RequestParam(required = false) String projectName) {
+        return AjaxResult.success(projectTaskTypeService.selectProjectOptionsByDept(deptId, projectName));
+    }
+
+
+
+}

+ 128 - 0
fs-admin/src/main/java/com/fs/provider/controller/ServiceGenConfigController.java

@@ -0,0 +1,128 @@
+package com.fs.provider.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.config.LoginContextManager;
+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.provider.domain.ServiceGenConfig;
+import com.fs.provider.service.IServiceGenConfigService;
+import com.fs.provider.vo.ServiceGenConfigAuditReqVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 任务生成配置Controller
+ *
+ * @author fs
+ * @date 2026-01-27
+ */
+@RestController
+@RequestMapping("/service/config")
+public class ServiceGenConfigController extends BaseController {
+    @Autowired
+    private IServiceGenConfigService serviceGenConfigService;
+
+    /**
+     * 查询任务生成配置列表
+     */
+    @PreAuthorize("@ss.hasPermi('service:config:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(ServiceGenConfig serviceGenConfig) {
+        startPage();
+
+        List<ServiceGenConfig> list = serviceGenConfigService.selectServiceGenConfigList(serviceGenConfig);
+        return getDataTable(list);
+    }
+
+
+    /**
+     * 导出任务生成配置列表
+     */
+    @PreAuthorize("@ss.hasPermi('service:config:export')")
+    @Log(title = "任务生成配置", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(ServiceGenConfig serviceGenConfig) {
+        List<ServiceGenConfig> list = serviceGenConfigService.selectServiceGenConfigList(serviceGenConfig);
+        ExcelUtil<ServiceGenConfig> util = new ExcelUtil<ServiceGenConfig>(ServiceGenConfig.class);
+        return util.exportExcel(list, "任务生成配置数据");
+    }
+
+    /**
+     * 获取任务生成配置详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('service:config:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
+        return AjaxResult.success(serviceGenConfigService.selectServiceGenConfigById(id));
+    }
+
+
+    /**
+     * 获取任务生成配置详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('service:config:query')")
+    @GetMapping("/audit/{id}")
+    public AjaxResult getAuditInfoById(@PathVariable Long id) {
+        return AjaxResult.success(serviceGenConfigService.getAuditInfoById(id));
+    }
+
+    /**
+     * 新增任务生成配置
+     */
+    @PreAuthorize("@ss.hasPermi('service:config:add')")
+    @Log(title = "任务生成配置", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody ServiceGenConfig serviceGenConfig) {
+        return toAjax(serviceGenConfigService.insertServiceGenConfig(serviceGenConfig));
+    }
+
+    /**
+     * 修改任务生成配置
+     */
+    @PreAuthorize("@ss.hasPermi('service:config:edit')")
+    @Log(title = "任务生成配置", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody ServiceGenConfig serviceGenConfig) {
+        return toAjax(serviceGenConfigService.updateServiceGenConfig(serviceGenConfig));
+    }
+
+    /**
+     * 删除任务生成配置
+     */
+    @PreAuthorize("@ss.hasPermi('service:config:remove')")
+    @Log(title = "任务生成配置", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        return toAjax(serviceGenConfigService.deleteServiceGenConfigByIds(ids));
+    }
+
+
+    /**
+     * 审核任务生成配置
+     * <p>
+     * 前端请求示例:
+     * POST /service/config/audit
+     * {
+     * "id": 1,
+     * "auditStatus": 1,        // 1-通过,2-不通过
+     * "auditRemark": "xxx",    // 审核描述
+     * "auditInstanceId": 123   // 可选
+     * }
+     */
+    @PreAuthorize("@ss.hasPermi('service:config:edit')")
+    @Log(title = "任务生成配置审核", businessType = BusinessType.UPDATE)
+    @PostMapping("/audit")
+    public AjaxResult audit(@RequestBody ServiceGenConfigAuditReqVO serviceGenConfigAuditReqVO) {
+        // 这里只做壳子:保证能进来即可
+        // 你可以在 Service 里校验 needAudit / auditStatus 状态机 / 幂等 等逻辑
+        return toAjax(serviceGenConfigService.audit(serviceGenConfigAuditReqVO));
+    }
+
+
+}

+ 125 - 0
fs-admin/src/main/java/com/fs/provider/controller/ServiceOrderController.java

@@ -0,0 +1,125 @@
+package com.fs.provider.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.provider.domain.ServiceOrder;
+import com.fs.provider.service.IServiceOrderService;
+import com.fs.provider.vo.GenerateServiceOrderReqVO;
+import com.fs.provider.vo.ServiceOrderAuditReqVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 服务单信息Controller
+ * 
+ * @author fs
+ * @date 2026-01-26
+ */
+@RestController
+@RequestMapping("/service/order")
+public class ServiceOrderController extends BaseController
+{
+    @Autowired
+    private IServiceOrderService serviceOrderService;
+
+    /**
+     * 查询服务单信息列表
+     */
+    @PreAuthorize("@ss.hasPermi('service:order:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(ServiceOrder serviceOrder)
+    {
+        startPage();
+        List<ServiceOrder> list = serviceOrderService.selectServiceOrderList(serviceOrder);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出服务单信息列表
+     */
+    @PreAuthorize("@ss.hasPermi('service:order:export')")
+    @Log(title = "服务单信息", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(ServiceOrder serviceOrder)
+    {
+        List<ServiceOrder> list = serviceOrderService.selectServiceOrderList(serviceOrder);
+        ExcelUtil<ServiceOrder> util = new ExcelUtil<ServiceOrder>(ServiceOrder.class);
+        return util.exportExcel(list, "服务单信息数据");
+    }
+
+    /**
+     * 获取服务单信息详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('service:order:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(serviceOrderService.selectServiceOrderById(id));
+    }
+
+    /**
+     * 新增服务单信息
+     */
+    @PreAuthorize("@ss.hasPermi('service:order:add')")
+    @Log(title = "服务单信息", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody ServiceOrder serviceOrder)
+    {
+        return toAjax(serviceOrderService.insertServiceOrder(serviceOrder));
+    }
+
+    /**
+     * 修改服务单信息
+     */
+    @PreAuthorize("@ss.hasPermi('service:order:edit')")
+    @Log(title = "服务单信息", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody ServiceOrder serviceOrder)
+    {
+        return toAjax(serviceOrderService.updateServiceOrder(serviceOrder));
+    }
+
+    /**
+     * 删除服务单信息
+     */
+    @PreAuthorize("@ss.hasPermi('service:order:remove')")
+    @Log(title = "服务单信息", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(serviceOrderService.deleteServiceOrderByIds(ids));
+    }
+
+
+    /**
+     * 根据任务ID生成服务单
+     */
+    @PostMapping("/generateByTask")
+    @PreAuthorize("@ss.hasPermi('service:order:add')")
+    public AjaxResult generateServiceOrderByTask(@RequestBody GenerateServiceOrderReqVO reqVO) {
+        if (reqVO == null || reqVO.getTaskId() == null) {
+            return AjaxResult.error("任务ID不能为空");
+        }
+        return AjaxResult.success(serviceOrderService.generateByTaskId(reqVO.getTaskId()));
+    }
+
+
+
+    /**
+     * 获取服务单信息详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('service:order:query')")
+    @GetMapping(value = "getAuditInfo")
+    public AjaxResult getAuditInfo(ServiceOrderAuditReqVO serviceOrderAuditReqVO){
+        return AjaxResult.success(serviceOrderService.getAuditInfo(serviceOrderAuditReqVO));
+    }
+
+
+}

+ 97 - 0
fs-admin/src/main/java/com/fs/provider/controller/ServiceOrderTaskRelController.java

@@ -0,0 +1,97 @@
+package com.fs.provider.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.provider.domain.ServiceOrderTaskRel;
+import com.fs.provider.service.IServiceOrderTaskRelService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 服务单-任务关联Controller
+ * 
+ * @author fs
+ * @date 2026-01-26
+ */
+@RestController
+@RequestMapping("/service/rel")
+public class ServiceOrderTaskRelController extends BaseController
+{
+    @Autowired
+    private IServiceOrderTaskRelService serviceOrderTaskRelService;
+
+    /**
+     * 查询服务单-任务关联列表
+     */
+    @PreAuthorize("@ss.hasPermi('service:rel:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(ServiceOrderTaskRel serviceOrderTaskRel)
+    {
+        startPage();
+        List<ServiceOrderTaskRel> list = serviceOrderTaskRelService.selectServiceOrderTaskRelList(serviceOrderTaskRel);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出服务单-任务关联列表
+     */
+    @PreAuthorize("@ss.hasPermi('service:rel:export')")
+    @Log(title = "服务单-任务关联", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(ServiceOrderTaskRel serviceOrderTaskRel)
+    {
+        List<ServiceOrderTaskRel> list = serviceOrderTaskRelService.selectServiceOrderTaskRelList(serviceOrderTaskRel);
+        ExcelUtil<ServiceOrderTaskRel> util = new ExcelUtil<ServiceOrderTaskRel>(ServiceOrderTaskRel.class);
+        return util.exportExcel(list, "服务单-任务关联数据");
+    }
+
+    /**
+     * 获取服务单-任务关联详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('service:rel:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(serviceOrderTaskRelService.selectServiceOrderTaskRelById(id));
+    }
+
+    /**
+     * 新增服务单-任务关联
+     */
+    @PreAuthorize("@ss.hasPermi('service:rel:add')")
+    @Log(title = "服务单-任务关联", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody ServiceOrderTaskRel serviceOrderTaskRel)
+    {
+        return toAjax(serviceOrderTaskRelService.insertServiceOrderTaskRel(serviceOrderTaskRel));
+    }
+
+    /**
+     * 修改服务单-任务关联
+     */
+    @PreAuthorize("@ss.hasPermi('service:rel:edit')")
+    @Log(title = "服务单-任务关联", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody ServiceOrderTaskRel serviceOrderTaskRel)
+    {
+        return toAjax(serviceOrderTaskRelService.updateServiceOrderTaskRel(serviceOrderTaskRel));
+    }
+
+    /**
+     * 删除服务单-任务关联
+     */
+    @PreAuthorize("@ss.hasPermi('service:rel:remove')")
+    @Log(title = "服务单-任务关联", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(serviceOrderTaskRelService.deleteServiceOrderTaskRelByIds(ids));
+    }
+}

+ 108 - 0
fs-admin/src/main/java/com/fs/survey/DrugResearchController.java

@@ -0,0 +1,108 @@
+package com.fs.survey;
+
+import java.util.Date;
+import java.util.List;
+
+import com.fs.common.config.LoginContextManager;
+import com.fs.common.utils.SecurityUtils;
+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.survey.domain.DrugResearch;
+import com.fs.survey.service.IDrugResearchService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 用药调研Controller
+ * 
+ * @author fs
+ * @date 2026-02-06
+ */
+@RestController
+@RequestMapping("/survey/drugResearch")
+public class DrugResearchController extends BaseController
+{
+    @Autowired
+    private IDrugResearchService drugResearchService;
+
+    /**
+     * 查询用药调研列表
+     */
+    @PreAuthorize("@ss.hasPermi('survey:drugResearch:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(DrugResearch drugResearch)
+    {
+        startPage();
+        List<DrugResearch> list = drugResearchService.selectDrugResearchList(drugResearch);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出用药调研列表
+     */
+    @PreAuthorize("@ss.hasPermi('survey:drugResearch:export')")
+    @Log(title = "用药调研", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(DrugResearch drugResearch)
+    {
+        List<DrugResearch> list = drugResearchService.selectDrugResearchList(drugResearch);
+        ExcelUtil<DrugResearch> util = new ExcelUtil<DrugResearch>(DrugResearch.class);
+        return util.exportExcel(list, "用药调研数据");
+    }
+
+    /**
+     * 获取用药调研详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('survey:drugResearch:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(drugResearchService.selectDrugResearchById(id));
+    }
+
+    /**
+     * 新增用药调研
+     */
+    @PreAuthorize("@ss.hasPermi('survey:drugResearch:add')")
+    @Log(title = "用药调研", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody DrugResearch drugResearch)
+    {
+
+        return toAjax(drugResearchService.insertDrugResearch(drugResearch));
+    }
+
+    /**
+     * 修改用药调研
+     */
+    @PreAuthorize("@ss.hasPermi('survey:drugResearch:edit')")
+    @Log(title = "用药调研", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody DrugResearch drugResearch)
+    {
+        return toAjax(drugResearchService.updateDrugResearch(drugResearch));
+    }
+
+    /**
+     * 删除用药调研
+     */
+    @PreAuthorize("@ss.hasPermi('survey:drugResearch:remove')")
+    @Log(title = "用药调研", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(drugResearchService.deleteDrugResearchByIds(ids));
+    }
+}

+ 199 - 0
fs-admin/src/main/java/com/fs/survey/SurveyDataController.java

@@ -0,0 +1,199 @@
+package com.fs.survey;
+
+import com.alibaba.fastjson2.JSONArray;
+import com.alibaba.fastjson2.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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.survey.domain.SurveyFieldConfig;
+import com.fs.survey.mapper.SurveyFieldConfigMapper;
+import com.fs.survey.service.ISurveyDataService;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.xssf.streaming.SXSSFWorkbook;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 问卷数据Controller
+ *
+ */
+@RestController
+@RequestMapping("/survey/data")
+public class SurveyDataController extends BaseController {
+
+    @Autowired
+    private ISurveyDataService surveyDataService;
+
+    @Autowired
+    private SurveyFieldConfigMapper fieldConfigMapper;
+
+    /**
+     * 查询问卷数据列表
+     */
+    @PreAuthorize("@ss.hasPermi('survey:data:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(@RequestParam Map<String, Object> params) {
+        startPage();
+        List<Map<String, Object>> list = surveyDataService.selectSurveyDataList(params);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取问卷数据详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('survey:data:query')")
+    @GetMapping(value = "/{versionId}/{id}")
+    public AjaxResult getInfo(@PathVariable("versionId") Long versionId, @PathVariable("id") Long id) {
+        return AjaxResult.success(surveyDataService.selectSurveyDataById(versionId, id));
+    }
+
+    /**
+     * 提交问卷数据
+     */
+    @Log(title = "提交问卷", businessType = BusinessType.INSERT)
+    @PostMapping("/submit")
+    public AjaxResult submit(@RequestBody Map<String, Object> data) {
+        return toAjax(surveyDataService.submitSurveyData(data));
+    }
+
+    /**
+     * 修改问卷数据
+     */
+    @PreAuthorize("@ss.hasPermi('survey:data:edit')")
+    @Log(title = "问卷数据", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody Map<String, Object> data) {
+        return toAjax(surveyDataService.updateSurveyData(data));
+    }
+
+    /**
+     * 删除问卷数据
+     */
+    @PreAuthorize("@ss.hasPermi('survey:data:remove')")
+    @Log(title = "问卷数据", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{versionId}/{ids}")
+    public AjaxResult remove(@PathVariable Long versionId, @PathVariable Long[] ids) {
+        return toAjax(surveyDataService.deleteSurveyDataByIds(versionId, ids));
+    }
+
+    /**
+     * 导出问卷数据
+     */
+    @PreAuthorize("@ss.hasPermi('survey:data:export')")
+    @Log(title = "问卷数据", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, @RequestParam Map<String, Object> params) throws IOException {
+        Long versionId = Long.parseLong(params.get("versionId").toString());
+
+        // 获取字段配置(isExport=1 的列)
+        List<SurveyFieldConfig> fieldConfigs = fieldConfigMapper.selectFieldConfigByVersionId(versionId);
+        fieldConfigs.removeIf(f -> f.getIsExport() == null || f.getIsExport() != 1);
+
+        // 查询全量数据(不分页)
+        List<Map<String, Object>> dataList = surveyDataService.selectSurveyDataList(params);
+
+        // 构建 Excel
+        SXSSFWorkbook wb = new SXSSFWorkbook(500);
+        Sheet sheet = wb.createSheet("问卷数据");
+
+        // 表头样式
+        CellStyle headerStyle = wb.createCellStyle();
+        Font headerFont = wb.createFont();
+        headerFont.setBold(true);
+        headerStyle.setFont(headerFont);
+        headerStyle.setAlignment(HorizontalAlignment.CENTER);
+        headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
+        headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+
+        // 写表头
+        Row headerRow = sheet.createRow(0);
+        int colIdx = 0;
+        for (SurveyFieldConfig fc : fieldConfigs) {
+            Cell cell = headerRow.createCell(colIdx++);
+            cell.setCellValue(fc.getFieldLabel());
+            cell.setCellStyle(headerStyle);
+        }
+        headerRow.createCell(colIdx++).setCellValue("提交时间");
+        headerRow.createCell(colIdx).setCellValue("提交人");
+
+        // 写数据行
+        for (int i = 0; i < dataList.size(); i++) {
+            Map<String, Object> row = dataList.get(i);
+            Row dataRow = sheet.createRow(i + 1);
+            int ci = 0;
+            for (SurveyFieldConfig fc : fieldConfigs) {
+                Object val = row.get(fc.getFieldKey());
+                String displayVal = formatExportValue(val, fc);
+                dataRow.createCell(ci++).setCellValue(displayVal);
+            }
+            Object submitTime = row.get("submit_time");
+            dataRow.createCell(ci++).setCellValue(submitTime == null ? "" : submitTime.toString());
+            Object userName = row.get("user_name");
+            dataRow.createCell(ci).setCellValue(userName == null ? "" : userName.toString());
+        }
+
+        // 输出
+        String fileName = URLEncoder.encode("问卷数据", "UTF-8");
+        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+        response.setCharacterEncoding("utf-8");
+        response.setHeader("Content-Disposition", "attachment; filename*=utf-8''" + fileName + ".xlsx");
+        wb.write(response.getOutputStream());
+        wb.dispose();
+    }
+
+    /** 导出时将字段值转换为可读文本 */
+    private String formatExportValue(Object val, SurveyFieldConfig fc) {
+        if (val == null) return "";
+        String strVal = val.toString();
+        if (strVal.isEmpty()) return "";
+
+        // 有选项配置的字段,转换 value -> label
+        if (fc.getOptionsJson() != null && !fc.getOptionsJson().isEmpty()) {
+            try {
+                JSONArray options = JSONArray.parseArray(fc.getOptionsJson());
+                // 尝试多选(JSON数组)
+                if (strVal.startsWith("[")) {
+                    JSONArray selected = JSONArray.parseArray(strVal);
+                    StringBuilder sb = new StringBuilder();
+                    for (int i = 0; i < selected.size(); i++) {
+                        String sv = selected.get(i).toString();
+                        String label = findLabel(options, sv);
+                        if (i > 0) sb.append(", ");
+                        sb.append(label);
+                    }
+                    return sb.toString();
+                }
+                return findLabel(options, strVal);
+            } catch (Exception ignored) {}
+        }
+        return strVal;
+    }
+
+    private String findLabel(JSONArray options, String value) {
+        for (int i = 0; i < options.size(); i++) {
+            JSONObject opt = options.getJSONObject(i);
+            if (value.equals(opt.getString("value"))) {
+                return opt.getString("label");
+            }
+        }
+        return value;
+    }
+
+    /**
+     * 数据统计
+     */
+    @PreAuthorize("@ss.hasPermi('survey:data:statistics')")
+    @GetMapping("/statistics/{versionId}")
+    public AjaxResult statistics(@PathVariable Long versionId) {
+        return AjaxResult.success(surveyDataService.getStatistics(versionId));
+    }
+}

+ 94 - 0
fs-admin/src/main/java/com/fs/survey/SurveyFieldConfigController.java

@@ -0,0 +1,94 @@
+package com.fs.survey;
+
+import java.util.List;
+
+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.survey.domain.SurveyFieldConfig;
+import com.fs.survey.service.ISurveyFieldConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 字段配置Controller
+ *
+ * 管理问卷字段的增删改查操作
+ *
+ */
+@RestController
+@RequestMapping("/survey/fieldConfig")
+public class SurveyFieldConfigController extends BaseController {
+
+    @Autowired
+    private ISurveyFieldConfigService fieldConfigService;
+
+    /**
+     * 查询字段配置列表
+     */
+    @GetMapping("/list")
+    public AjaxResult list(SurveyFieldConfig fieldConfig) {
+        List<SurveyFieldConfig> list = fieldConfigService.selectSurveyFieldConfigList(fieldConfig);
+        return AjaxResult.success(list);
+    }
+
+    /**
+     * 根据ID获取字段配置详细信息
+     */
+    @GetMapping("/{id}")
+    public AjaxResult getInfo(@PathVariable Long id) {
+        return AjaxResult.success(fieldConfigService.selectSurveyFieldConfigById(id));
+    }
+
+    /**
+     * 根据问卷版本ID查询字段配置信息列表
+     */
+    @GetMapping("/byVersion/{versionId}")
+    public AjaxResult getByVersionId(@PathVariable Long versionId) {
+        List<SurveyFieldConfig> list = fieldConfigService.selectFieldConfigByVersionId(versionId);
+        return AjaxResult.success(list);
+    }
+
+    /**
+     * 新增字段配置
+     */
+    @Log(title = "字段配置", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody SurveyFieldConfig fieldConfig) {
+        int row = fieldConfigService.insertSurveyFieldConfig(fieldConfig);
+        return toAjax(row);
+    }
+
+    /**
+     * 修改字段配置
+     */
+    @Log(title = "字段配置", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody SurveyFieldConfig fieldConfig) {
+        int row = fieldConfigService.updateSurveyFieldConfig(fieldConfig);
+        return toAjax(row);
+    }
+
+    /**
+     * 删除字段配置,支持批量ID
+     */
+    @Log(title = "字段配置", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        int row = fieldConfigService.deleteSurveyFieldConfigByIds(ids);
+        return toAjax(row);
+    }
+
+    /**
+     * 根据版本ID批量删除字段配置
+     */
+    @Log(title = "字段配置", businessType = BusinessType.DELETE)
+    @DeleteMapping("/byVersion/{versionId}")
+    public AjaxResult removeByVersionId(@PathVariable Long versionId) {
+        int row = fieldConfigService.deleteSurveyFieldConfigByVersionId(versionId);
+        return toAjax(row);
+    }
+}

+ 127 - 0
fs-admin/src/main/java/com/fs/survey/SurveyQuestionnaireController.java

@@ -0,0 +1,127 @@
+package com.fs.survey;
+
+import java.io.IOException;
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+
+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.survey.domain.SurveyQuestionnaire;
+import com.fs.survey.service.ISurveyQuestionnaireService;
+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;
+
+/**
+ * 问卷Controller
+ *
+ */
+@RestController
+@RequestMapping("/survey/questionnaire")
+public class SurveyQuestionnaireController extends BaseController {
+    @Autowired
+    private ISurveyQuestionnaireService surveyQuestionnaireService;
+
+    /**
+     * 查询问卷列表
+     */
+    @PreAuthorize("@ss.hasPermi('survey:questionnaire:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SurveyQuestionnaire surveyQuestionnaire) {
+        startPage();
+        List<SurveyQuestionnaire> list = surveyQuestionnaireService.selectSurveyQuestionnaireList(surveyQuestionnaire);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出问卷列表
+     */
+    @PreAuthorize("@ss.hasPermi('survey:questionnaire:export')")
+    @Log(title = "问卷", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, SurveyQuestionnaire surveyQuestionnaire) throws IOException {
+        List<SurveyQuestionnaire> list = surveyQuestionnaireService.selectSurveyQuestionnaireList(surveyQuestionnaire);
+        ExcelUtil<SurveyQuestionnaire> util = new ExcelUtil<SurveyQuestionnaire>(SurveyQuestionnaire.class);
+        util.exportExcel(response, list, "问卷数据");
+    }
+
+    /**
+     * 获取问卷详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('survey:questionnaire:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
+        return AjaxResult.success(surveyQuestionnaireService.selectSurveyQuestionnaireById(id));
+    }
+
+    /**
+     * 新增问卷
+     */
+    @PreAuthorize("@ss.hasPermi('survey:questionnaire:add')")
+    @Log(title = "问卷", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody SurveyQuestionnaire surveyQuestionnaire) {
+        return toAjax(surveyQuestionnaireService.insertSurveyQuestionnaire(surveyQuestionnaire));
+    }
+
+    /**
+     * 修改问卷
+     */
+    @PreAuthorize("@ss.hasPermi('survey:questionnaire:edit')")
+    @Log(title = "问卷", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody SurveyQuestionnaire surveyQuestionnaire) {
+        return toAjax(surveyQuestionnaireService.updateSurveyQuestionnaire(surveyQuestionnaire));
+    }
+
+    /**
+     * 删除问卷
+     */
+    @PreAuthorize("@ss.hasPermi('survey:questionnaire:remove')")
+    @Log(title = "问卷", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        return toAjax(surveyQuestionnaireService.deleteSurveyQuestionnaireByIds(ids));
+    }
+
+    /**
+     * 发布问卷
+     */
+    @PreAuthorize("@ss.hasPermi('survey:questionnaire:publish')")
+    @Log(title = "发布问卷", businessType = BusinessType.UPDATE)
+    @PutMapping("/publish/{id}/{versionId}")
+    public AjaxResult publish(@PathVariable Long id, @PathVariable Long versionId) {
+        return toAjax(surveyQuestionnaireService.publishQuestionnaire(id, versionId));
+    }
+
+    /**
+     * 停用问卷
+     */
+    @PreAuthorize("@ss.hasPermi('survey:questionnaire:disable')")
+    @Log(title = "停用问卷", businessType = BusinessType.UPDATE)
+    @PutMapping("/disable/{id}")
+    public AjaxResult disable(@PathVariable Long id) {
+        return toAjax(surveyQuestionnaireService.disableQuestionnaire(id));
+    }
+
+
+    /**
+     * 获取公司定级申请问卷(不需要参数)
+     */
+    @GetMapping("/getCompanyDoctorLevelApplyQuestionnaire")
+    public AjaxResult getCompanyDoctorLevelApplyQuestionnaire(Long companyId) {
+        return AjaxResult.success(surveyQuestionnaireService.getAdminDoctorLevelApplyQuestionnaire(companyId));
+
+    }
+}

+ 119 - 0
fs-admin/src/main/java/com/fs/survey/SurveyTaskController.java

@@ -0,0 +1,119 @@
+package com.fs.survey;
+
+import java.util.Date;
+import java.util.List;
+
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.utils.SecurityUtils;
+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.survey.domain.SurveyTask;
+import com.fs.survey.service.ISurveyTaskService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 问卷任务Controller
+ *
+ * @author fs
+ * @date 2026-02-06
+ */
+@RestController
+@RequestMapping("/survey/task")
+public class SurveyTaskController extends BaseController
+{
+    @Autowired
+    private ISurveyTaskService surveyTaskService;
+
+    /**
+     * 查询问卷任务列表
+     */
+    @PreAuthorize("@ss.hasPermi('survey:task:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SurveyTask surveyTask)
+    {
+        startPage();
+        List<SurveyTask> list = surveyTaskService.selectSurveyTaskList(surveyTask);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出问卷任务列表
+     */
+    @PreAuthorize("@ss.hasPermi('survey:task:export')")
+    @Log(title = "问卷任务", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(SurveyTask surveyTask)
+    {
+        List<SurveyTask> list = surveyTaskService.selectSurveyTaskList(surveyTask);
+        ExcelUtil<SurveyTask> util = new ExcelUtil<SurveyTask>(SurveyTask.class);
+        return util.exportExcel(list, "问卷任务数据");
+    }
+
+    /**
+     * 获取问卷任务详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('survey:task:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(surveyTaskService.selectSurveyTaskById(id));
+    }
+
+    /**
+     * 新增问卷任务
+     */
+    @PreAuthorize("@ss.hasPermi('survey:task:add')")
+    @Log(title = "问卷任务", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody SurveyTask surveyTask)
+    {
+        //删除标记
+        surveyTask.setDeleted(0l);
+        //时间标记
+        surveyTask.setCreateTime(new Date());
+        surveyTask.setUpdateTime(new Date());
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+
+        surveyTask.setCreateBy(loginUser.getUserId()+"");
+        surveyTask.setUpdateBy(loginUser.getUserId()+"");
+        return toAjax(surveyTaskService.insertSurveyTask(surveyTask));
+    }
+
+    /**
+     * 修改问卷任务
+     */
+    @PreAuthorize("@ss.hasPermi('survey:task:edit')")
+    @Log(title = "问卷任务", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody SurveyTask surveyTask)
+    {
+        surveyTask.setUpdateTime(new Date());
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        surveyTask.setUpdateBy(loginUser.getUserId()+"");
+        return toAjax(surveyTaskService.updateSurveyTaskBaseInfo(surveyTask));
+    }
+
+    /**
+     * 删除问卷任务
+     */
+    @PreAuthorize("@ss.hasPermi('survey:task:remove')")
+    @Log(title = "问卷任务", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(surveyTaskService.deleteSurveyTaskByIds(ids));
+    }
+}

+ 103 - 0
fs-admin/src/main/java/com/fs/survey/SurveyTaskPeriodController.java

@@ -0,0 +1,103 @@
+package com.fs.survey;
+
+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.survey.domain.SurveyTaskPeriod;
+import com.fs.survey.service.ISurveyTaskPeriodService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 问卷任务期次Controller
+ * 
+ * @author fs
+ * @date 2026-02-06
+ */
+@RestController
+@RequestMapping("/survey/period")
+public class SurveyTaskPeriodController extends BaseController
+{
+    @Autowired
+    private ISurveyTaskPeriodService surveyTaskPeriodService;
+
+    /**
+     * 查询问卷任务期次列表
+     */
+    @PreAuthorize("@ss.hasPermi('survey:period:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SurveyTaskPeriod surveyTaskPeriod)
+    {
+        startPage();
+        List<SurveyTaskPeriod> list = surveyTaskPeriodService.selectSurveyTaskPeriodList(surveyTaskPeriod);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出问卷任务期次列表
+     */
+    @PreAuthorize("@ss.hasPermi('survey:period:export')")
+    @Log(title = "问卷任务期次", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(SurveyTaskPeriod surveyTaskPeriod)
+    {
+        List<SurveyTaskPeriod> list = surveyTaskPeriodService.selectSurveyTaskPeriodList(surveyTaskPeriod);
+        ExcelUtil<SurveyTaskPeriod> util = new ExcelUtil<SurveyTaskPeriod>(SurveyTaskPeriod.class);
+        return util.exportExcel(list, "问卷任务期次数据");
+    }
+
+    /**
+     * 获取问卷任务期次详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('survey:period:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(surveyTaskPeriodService.selectSurveyTaskPeriodById(id));
+    }
+
+    /**
+     * 新增问卷任务期次
+     */
+    @PreAuthorize("@ss.hasPermi('survey:period:add')")
+    @Log(title = "问卷任务期次", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody SurveyTaskPeriod surveyTaskPeriod)
+    {
+        return toAjax(surveyTaskPeriodService.insertSurveyTaskPeriod(surveyTaskPeriod));
+    }
+
+    /**
+     * 修改问卷任务期次
+     */
+    @PreAuthorize("@ss.hasPermi('survey:period:edit')")
+    @Log(title = "问卷任务期次", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody SurveyTaskPeriod surveyTaskPeriod)
+    {
+        return toAjax(surveyTaskPeriodService.updateSurveyTaskPeriod(surveyTaskPeriod));
+    }
+
+    /**
+     * 删除问卷任务期次
+     */
+    @PreAuthorize("@ss.hasPermi('survey:period:remove')")
+    @Log(title = "问卷任务期次", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(surveyTaskPeriodService.deleteSurveyTaskPeriodByIds(ids));
+    }
+}

+ 103 - 0
fs-admin/src/main/java/com/fs/survey/SurveyVersionController.java

@@ -0,0 +1,103 @@
+package com.fs.survey;
+
+import java.util.List;
+
+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.survey.domain.SurveyQuestionnaireVersion;
+import com.fs.survey.service.ISurveyQuestionnaireVersionService;
+import org.apache.ibatis.annotations.Param;
+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;
+
+/**
+ * 问卷版本Controller
+ *
+ */
+@RestController
+@RequestMapping("/survey/version")
+public class SurveyVersionController extends BaseController {
+    @Autowired
+    private ISurveyQuestionnaireVersionService versionService;
+
+    /**
+     * 查询问卷版本列表
+     */
+    @PreAuthorize("@ss.hasPermi('survey:version:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SurveyQuestionnaireVersion version) {
+        startPage();
+        List<SurveyQuestionnaireVersion> list = versionService.selectSurveyQuestionnaireVersionList(version);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取问卷版本详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('survey:version:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
+        return AjaxResult.success(versionService.selectSurveyQuestionnaireVersionById(id));
+    }
+
+    /**
+     * 获取当前最新问卷版本
+     * @param id 问卷ID
+     * @return 最新问卷版本
+     */
+    @GetMapping(value = "/getLatestInfo")
+    public AjaxResult getLatestInfo(@Param("id") Long id) {
+        return AjaxResult.success(versionService.getLatestInfo(id));
+    }
+
+    /**
+     * 新增/保存问卷版本
+     */
+    @PreAuthorize("@ss.hasPermi('survey:version:add')")
+    @Log(title = "问卷版本", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody SurveyQuestionnaireVersion version) {
+        return AjaxResult.success(versionService.saveVersion(version));
+    }
+
+    /**
+     * 修改问卷版本
+     */
+    @PreAuthorize("@ss.hasPermi('survey:version:edit')")
+    @Log(title = "问卷版本", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody SurveyQuestionnaireVersion version) {
+        return toAjax(versionService.updateSurveyQuestionnaireVersion(version));
+    }
+
+    /**
+     * 发布版本
+     */
+    @PreAuthorize("@ss.hasPermi('survey:version:publish')")
+    @Log(title = "发布版本", businessType = BusinessType.UPDATE)
+    @PutMapping("/publish/{id}")
+    public AjaxResult publish(@PathVariable Long id) {
+        return toAjax(versionService.publishVersion(id));
+    }
+
+    /**
+     * 删除问卷版本
+     */
+    @PreAuthorize("@ss.hasPermi('survey:version:remove')")
+    @Log(title = "问卷版本", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        return toAjax(versionService.deleteSurveyQuestionnaireVersionByIds(ids));
+    }
+}

+ 102 - 0
fs-admin/src/main/java/com/fs/task/controller/TaskDeliveryInfoController.java

@@ -0,0 +1,102 @@
+package com.fs.task.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.task.domain.TaskDeliveryInfo;
+import com.fs.task.service.ITaskDeliveryInfoService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 交付物信息Controller
+ *
+ * @author fs
+ * @date 2026-01-04
+ */
+@RestController
+@RequestMapping("/task/delivery")
+public class TaskDeliveryInfoController extends BaseController {
+    @Autowired
+    private ITaskDeliveryInfoService taskDeliveryInfoService;
+
+    /**
+     * 查询交付物信息列表
+     */
+    @PreAuthorize("@ss.hasPermi('task:delivery:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(TaskDeliveryInfo taskDeliveryInfo) {
+        startPage();
+        List<TaskDeliveryInfo> list = taskDeliveryInfoService.selectTaskDeliveryInfoList(taskDeliveryInfo);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出交付物信息列表
+     */
+    @PreAuthorize("@ss.hasPermi('task:delivery:export')")
+    @Log(title = "交付物信息", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(TaskDeliveryInfo taskDeliveryInfo) {
+        List<TaskDeliveryInfo> list = taskDeliveryInfoService.selectTaskDeliveryInfoList(taskDeliveryInfo);
+        ExcelUtil<TaskDeliveryInfo> util = new ExcelUtil<TaskDeliveryInfo>(TaskDeliveryInfo.class);
+        return util.exportExcel(list, "交付物信息数据");
+    }
+
+    /**
+     * 获取交付物信息详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('task:delivery:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
+        return AjaxResult.success(taskDeliveryInfoService.selectTaskDeliveryInfoById(id));
+    }
+
+    /**
+     * 新增交付物信息
+     */
+    @PreAuthorize("@ss.hasPermi('task:delivery:add')")
+    @Log(title = "交付物信息", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody TaskDeliveryInfo taskDeliveryInfo) {
+        return toAjax(taskDeliveryInfoService.insertTaskDeliveryInfo(taskDeliveryInfo));
+    }
+
+    /**
+     * 修改交付物信息
+     */
+    @PreAuthorize("@ss.hasPermi('task:delivery:edit')")
+    @Log(title = "交付物信息", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody TaskDeliveryInfo taskDeliveryInfo) {
+        return toAjax(taskDeliveryInfoService.updateTaskDeliveryInfo(taskDeliveryInfo));
+    }
+
+    /**
+     * 删除交付物信息
+     */
+    @PreAuthorize("@ss.hasPermi('task:delivery:remove')")
+    @Log(title = "交付物信息", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        return toAjax(taskDeliveryInfoService.deleteTaskDeliveryInfoByIds(ids));
+    }
+
+
+
+    /**
+     * 通过任务类别与交付物编号查询出交付物信息
+     */
+    @PreAuthorize("@ss.hasPermi('task:delivery:query')")
+    @GetMapping("/getDeliveryInfoByTaskTypeAndDeliveryNo")
+    public AjaxResult getDeliveryInfoByTaskTypeAndDeliveryNo(TaskDeliveryInfo taskDeliveryInfo) {
+        return AjaxResult.success(taskDeliveryInfoService.getDeliveryInfoByTaskTypeAndDeliveryNo(taskDeliveryInfo)
+        );
+    }
+}

+ 147 - 0
fs-admin/src/main/java/com/fs/task/controller/TaskInfoController.java

@@ -0,0 +1,147 @@
+package com.fs.task.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.task.domain.TaskInfo;
+import com.fs.task.service.ITaskInfoService;
+import com.fs.task.vo.TaskCreateAuditReqVO;
+import com.fs.task.vo.TaskFinishAuditReqVO;
+import com.fs.task.vo.TaskInfoCreateVO;
+import com.fs.task.vo.TaskSpotCheckUpdateReqVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+ * 任务信息Controller
+ *
+ * @author fs
+ * @date 2026-01-04
+ */
+@RestController
+@RequestMapping("/task/info")
+public class TaskInfoController extends BaseController
+{
+    @Autowired
+    private ITaskInfoService taskInfoService;
+
+
+
+    /**
+     * 查询任务信息列表
+     */
+    @PreAuthorize("@ss.hasPermi('task:info:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(TaskInfo taskInfo)
+    {
+        startPage();
+        List<TaskInfo> list = taskInfoService.selectTaskInfoList(taskInfo);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出任务信息列表
+     */
+    @PreAuthorize("@ss.hasPermi('task:info:export')")
+    @Log(title = "任务信息", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(TaskInfo taskInfo)
+    {
+        List<TaskInfo> list = taskInfoService.selectTaskInfoList(taskInfo);
+        ExcelUtil<TaskInfo> util = new ExcelUtil<TaskInfo>(TaskInfo.class);
+        return util.exportExcel(list, "任务信息数据");
+    }
+
+    /**
+     * 获取任务信息详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('task:info:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(taskInfoService.selectTaskInfoById(id));
+    }
+
+    /**
+     * 新增任务信息
+     */
+    @PreAuthorize("@ss.hasPermi('task:info:add')")
+    @Log(title = "任务信息", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody TaskInfoCreateVO vo)
+    {
+        return toAjax(taskInfoService.insertTaskInfo(vo));
+    }
+
+    /**
+     * 修改任务信息
+     */
+    @PreAuthorize("@ss.hasPermi('task:info:edit')")
+    @Log(title = "任务信息", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody TaskInfo taskInfo)
+    {
+        return toAjax(taskInfoService.updateTaskInfo(taskInfo));
+    }
+
+    /**
+     * 删除任务信息
+     */
+    @PreAuthorize("@ss.hasPermi('task:info:remove')")
+    @Log(title = "任务信息", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(taskInfoService.deleteTaskInfoByIds(ids));
+    }
+
+
+    @Log(title = "创建审核处理", businessType = BusinessType.UPDATE)
+    @PostMapping("doCreateAudit")
+    @PreAuthorize("@ss.hasPermi('task:info:edit')")
+    public void doCreateAudit(@RequestBody TaskCreateAuditReqVO taskCreateAuditReqVO){
+        taskInfoService.doCreateAudit(taskCreateAuditReqVO);
+    }
+
+
+    @Log(title = "完成审核处理", businessType = BusinessType.UPDATE)
+    @PostMapping("dofinishAudit")
+    public void dofinishAudit(@RequestBody TaskFinishAuditReqVO taskFinishAuditReqVO){
+        taskInfoService.dofinishAudit(taskFinishAuditReqVO);
+    }
+
+    @Log(title = "获取创建审核任务信息", businessType = BusinessType.UPDATE)
+    @GetMapping("getTaskCreateAuditInfo")
+    @PreAuthorize("@ss.hasPermi('task:info:query')")
+    public AjaxResult getTaskCreateAuditInfo(@RequestParam Long taskId){
+       return AjaxResult.success( taskInfoService.getTaskCreateAuditInfo(taskId));
+    }
+
+
+    @Log(title = "获取完成审核任务信息", businessType = BusinessType.UPDATE)
+    @GetMapping("getTaskFinishAuditInfo")
+    @PreAuthorize("@ss.hasPermi('task:info:query')")
+    public AjaxResult getTaskFinishAuditInfo(@RequestParam Long taskId){
+        return AjaxResult.success( taskInfoService.getTaskFinishAuditInfo(taskId));
+    }
+
+
+
+    @PutMapping("/spotCheck")
+    public AjaxResult updateSpotCheck(@Valid @RequestBody TaskSpotCheckUpdateReqVO req) {
+
+        int rows = taskInfoService.updateSpotCheck(req);
+        return rows > 0 ? AjaxResult.success() : AjaxResult.error("更新失败");
+    }
+
+
+
+
+    }

+ 103 - 0
fs-admin/src/main/java/com/fs/task/controller/TaskMaterialInfoController.java

@@ -0,0 +1,103 @@
+package com.fs.task.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.task.domain.TaskMaterialInfo;
+import com.fs.task.service.ITaskMaterialInfoService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 材料信息Controller
+ * 
+ * @author fs
+ * @date 2026-01-04
+ */
+@RestController
+@RequestMapping("/task/material")
+public class TaskMaterialInfoController extends BaseController
+{
+    @Autowired
+    private ITaskMaterialInfoService taskMaterialInfoService;
+
+    /**
+     * 查询材料信息列表
+     */
+    @PreAuthorize("@ss.hasPermi('task:material:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(TaskMaterialInfo taskMaterialInfo)
+    {
+        startPage();
+        List<TaskMaterialInfo> list = taskMaterialInfoService.selectTaskMaterialInfoList(taskMaterialInfo);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出材料信息列表
+     */
+    @PreAuthorize("@ss.hasPermi('task:material:export')")
+    @Log(title = "材料信息", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(TaskMaterialInfo taskMaterialInfo)
+    {
+        List<TaskMaterialInfo> list = taskMaterialInfoService.selectTaskMaterialInfoList(taskMaterialInfo);
+        ExcelUtil<TaskMaterialInfo> util = new ExcelUtil<TaskMaterialInfo>(TaskMaterialInfo.class);
+        return util.exportExcel(list, "材料信息数据");
+    }
+
+    /**
+     * 获取材料信息详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('task:material:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(taskMaterialInfoService.selectTaskMaterialInfoById(id));
+    }
+
+    /**
+     * 新增材料信息
+     */
+    @PreAuthorize("@ss.hasPermi('task:material:add')")
+    @Log(title = "材料信息", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody TaskMaterialInfo taskMaterialInfo)
+    {
+        return toAjax(taskMaterialInfoService.insertTaskMaterialInfo(taskMaterialInfo));
+    }
+
+    /**
+     * 修改材料信息
+     */
+    @PreAuthorize("@ss.hasPermi('task:material:edit')")
+    @Log(title = "材料信息", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody TaskMaterialInfo taskMaterialInfo)
+    {
+        return toAjax(taskMaterialInfoService.updateTaskMaterialInfo(taskMaterialInfo));
+    }
+
+    /**
+     * 删除材料信息
+     */
+    @PreAuthorize("@ss.hasPermi('task:material:remove')")
+    @Log(title = "材料信息", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(taskMaterialInfoService.deleteTaskMaterialInfoByIds(ids));
+    }
+}

+ 96 - 0
fs-admin/src/main/java/com/fs/web/controller/common/CaptchaController.java

@@ -0,0 +1,96 @@
+package com.fs.web.controller.common;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Resource;
+import javax.imageio.ImageIO;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.util.FastByteArrayOutputStream;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.google.code.kaptcha.Producer;
+import com.fs.common.constant.Constants;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.sign.Base64;
+import com.fs.common.utils.uuid.IdUtils;
+import com.fs.system.service.ISysConfigService;
+
+/**
+ * 验证码操作处理
+ * 
+
+ */
+@RestController
+public class CaptchaController
+{
+    @Resource(name = "captchaProducer")
+    private Producer captchaProducer;
+
+    @Resource(name = "captchaProducerMath")
+    private Producer captchaProducerMath;
+
+    @Autowired
+    private RedisCache redisCache;
+    
+    // 验证码类型
+    @Value("${fs.captchaType}")
+    private String captchaType;
+    
+    @Autowired
+    private ISysConfigService configService;
+    /**
+     * 生成验证码
+     */
+    @GetMapping("/captchaImage")
+    public AjaxResult getCode(HttpServletResponse response) throws IOException
+    {
+        AjaxResult ajax = AjaxResult.success();
+        boolean captchaOnOff = configService.selectCaptchaOnOff();
+        ajax.put("captchaOnOff", captchaOnOff);
+        if (!captchaOnOff)
+        {
+            return ajax;
+        }
+
+        // 保存验证码信息
+        String uuid = IdUtils.simpleUUID();
+        String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
+
+        String capStr = null, code = null;
+        BufferedImage image = null;
+
+        // 生成验证码
+        if ("math".equals(captchaType))
+        {
+            String capText = captchaProducerMath.createText();
+            capStr = capText.substring(0, capText.lastIndexOf("@"));
+            code = capText.substring(capText.lastIndexOf("@") + 1);
+            image = captchaProducerMath.createImage(capStr);
+        }
+        else if ("char".equals(captchaType))
+        {
+            capStr = code = captchaProducer.createText();
+            image = captchaProducer.createImage(capStr);
+        }
+
+        redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
+        // 转换流信息写出
+        FastByteArrayOutputStream os = new FastByteArrayOutputStream();
+        try
+        {
+            ImageIO.write(image, "jpg", os);
+        }
+        catch (IOException e)
+        {
+            return AjaxResult.error(e.getMessage());
+        }
+
+        ajax.put("uuid", uuid);
+        ajax.put("img", Base64.encode(os.toByteArray()));
+        return ajax;
+    }
+}

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