Jelajahi Sumber

Merge remote-tracking branch 'origin/dev_20260131' into dev_20260131

donglang 4 bulan lalu
induk
melakukan
710485f901
65 mengubah file dengan 3737 tambahan dan 20 penghapusan
  1. 1 0
      .gitignore
  2. 10 1
      sckw-auth/pom.xml
  3. 94 0
      sckw-auth/src/main/java/com/sckw/auth/config/OpenApiConfig.java
  4. 34 0
      sckw-auth/src/main/java/com/sckw/auth/controller/AuthController.java
  5. 10 0
      sckw-auth/src/main/java/com/sckw/auth/model/vo/req/LoginBase.java
  6. 18 0
      sckw-auth/src/main/java/com/sckw/auth/model/vo/req/LoginReqVo.java
  7. 45 0
      sckw-auth/src/main/java/com/sckw/auth/model/vo/req/RefreshTokenReqVo.java
  8. 45 0
      sckw-auth/src/main/java/com/sckw/auth/model/vo/req/SwitchAccountReqVo.java
  9. 29 1
      sckw-auth/src/main/java/com/sckw/auth/model/vo/res/LoginResVo1.java
  10. 22 0
      sckw-auth/src/main/java/com/sckw/auth/service/IAuthService.java
  11. 327 1
      sckw-auth/src/main/java/com/sckw/auth/service/impl/AuthServiceImpl.java
  12. 13 0
      sckw-auth/src/main/resources/bootstrap.yml
  13. 23 0
      sckw-common/sckw-common-core/src/main/java/com/sckw/core/model/constant/Global.java
  14. 5 0
      sckw-common/sckw-common-core/src/main/java/com/sckw/core/web/response/BaseResult.java
  15. 36 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/forkliftapp/AppVersionsController.java
  16. 68 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/forkliftapp/FeedbacksController.java
  17. 111 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/forkliftapp/LoadingRecordsController.java
  18. 65 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/forkliftapp/MaterialsController.java
  19. 60 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/forkliftapp/SysArticlesController.java
  20. 104 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/forkliftapp/UsersController.java
  21. 21 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/dao/AppVersionsDao.java
  22. 21 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/dao/FeedbacksDao.java
  23. 21 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/dao/LoadingRecordsDao.java
  24. 21 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/dao/LoginLogsDao.java
  25. 21 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/dao/MaterialsDao.java
  26. 21 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/dao/SysArticlesDao.java
  27. 76 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/AppVersions.java
  28. 74 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/Feedbacks.java
  29. 88 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/LoadingRecords.java
  30. 68 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/LoginLogs.java
  31. 53 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/Materials.java
  32. 68 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/SysArticles.java
  33. 45 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/FeedbackListReqVo.java
  34. 46 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/LoginReqVo.java
  35. 34 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/LogoutReqVo.java
  36. 54 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/SubmitFeedbackReqVo.java
  37. 49 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/SwitchAccountReqVo.java
  38. 18 14
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/UpdatePasswordReqVo.java
  39. 32 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/UserInfoReqVo.java
  40. 95 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/UsersReq.java
  41. 81 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/AppVersionsResVo.java
  42. 87 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/FeedbackResVo.java
  43. 93 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/LoadingRecordsResVo.java
  44. 96 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/LoginResVo.java
  45. 55 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/MaterialsResVo.java
  46. 99 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/SwitchAccountResVo.java
  47. 70 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/SysArticlesResVo.java
  48. 75 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/UserInfoResVo.java
  49. 21 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/repository/AppVersionsService.java
  50. 14 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/repository/FeedbacksService.java
  51. 14 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/repository/LoadingRecordsService.java
  52. 14 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/repository/LoginLogsService.java
  53. 14 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/repository/MaterialsService.java
  54. 21 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/repository/SysArticlesService.java
  55. 27 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/repository/impl/AppVersionsServiceImpl.java
  56. 18 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/repository/impl/FeedbacksServiceImpl.java
  57. 18 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/repository/impl/LoadingRecordsServiceImpl.java
  58. 18 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/repository/impl/LoginLogsServiceImpl.java
  59. 18 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/repository/impl/MaterialsServiceImpl.java
  60. 25 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/repository/impl/SysArticlesServiceImpl.java
  61. 41 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/service/KwsAppVersionsService.java
  62. 155 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/service/KwsFeedbackService.java
  63. 69 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/service/KwsSysArticlesService.java
  64. 479 3
      sckw-modules/sckw-system/src/main/java/com/sckw/system/service/KwsUserService.java
  65. 69 0
      sql/2026/01/2026_01_07_chenxiaofei_create.sql

+ 1 - 0
.gitignore

@@ -32,3 +32,4 @@ logs
 **/rebel.xml
 **/rebel.xml
 *.rebel.xml
 *.rebel.xml
 **/*.rebel.xml
 **/*.rebel.xml
+/effective-pom-abaf4996-b861-4224-acd2-bc9566fd9b7b.log

+ 10 - 1
sckw-auth/pom.xml

@@ -65,7 +65,16 @@
             <artifactId>sckw-fleet-api</artifactId>
             <artifactId>sckw-fleet-api</artifactId>
             <version>${basic.version}</version>
             <version>${basic.version}</version>
         </dependency>
         </dependency>
-
+        <dependency>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
+            <version>2.6.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
+            <version>4.5.0</version>
+        </dependency>
 
 
 
 
     </dependencies>
     </dependencies>

+ 94 - 0
sckw-auth/src/main/java/com/sckw/auth/config/OpenApiConfig.java

@@ -0,0 +1,94 @@
+package com.sckw.auth.config;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Contact;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.info.License;
+import io.swagger.v3.oas.models.servers.Server;
+import org.springdoc.core.models.GroupedOpenApi;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import java.util.List;
+
+/**
+ * @author PC
+ */
+@Configuration
+public class OpenApiConfig implements WebMvcConfigurer {
+
+    @Value("${spring.application.name:Application}")
+    private String applicationName;
+
+    @Bean
+    public OpenAPI customOpenAPI() {
+        return new OpenAPI()
+                .info(new Info()
+                        .title(applicationName + " API")
+                        .version("1.0.0")
+                        .description("物流登录DOC文档 - 提供登录管理以及相关API接口")
+                        .contact(new Contact()
+                                .name("开物登录开发团队")
+                                .email("iot-dev@example.com")
+                                .url("https://iot-platform.com"))
+                        .license(new License()
+                                .name("Apache 2.0")
+                                .url("https://www.apache.org/licenses/LICENSE-2.0")))
+                .servers(List.of(
+                        new Server()
+                                .url("http://localhost:10160")
+                                .description("开发环境"),
+                        new Server()
+                                .url("http://10.10.10.224:10160/")
+                                .description("测试环境"),
+                        new Server()
+                                .url("https://api.auth.example.com")
+                                .description("生产环境")
+                ));
+    }
+
+    @Bean
+    public GroupedOpenApi authApi() {
+        return GroupedOpenApi.builder()
+                .group("auth")
+                .packagesToScan("com.sckw.auth.controller")
+                .build();
+    }
+
+    @Bean
+    public GroupedOpenApi authAppApi() {
+        return GroupedOpenApi.builder()
+                .group("auth-app")
+                .packagesToScan("com.sckw.auth.controller.app")
+                .build();
+    }
+
+    @Bean
+    public GroupedOpenApi authOperateApi() {
+        return GroupedOpenApi.builder()
+                .group("auth-operate")
+                .packagesToScan("com.sckw.auth.controller.operate")
+                .build();
+    }
+
+    @Bean
+    public GroupedOpenApi authAllApi() {
+        return GroupedOpenApi.builder()
+                .group("auth-all")
+                .packagesToScan("com.sckw.auth.controller",
+                        "com.sckw.auth.controller.app",
+                        "com.sckw.auth.controller.operate")
+                .build();
+    }
+
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry) {
+        registry.addResourceHandler("/doc.html")
+                .addResourceLocations("classpath:/META-INF/resources/");
+        registry.addResourceHandler("/webjars/**")
+                .addResourceLocations("classpath:/META-INF/resources/webjars/");
+    }
+}

+ 34 - 0
sckw-auth/src/main/java/com/sckw/auth/controller/AuthController.java

@@ -5,6 +5,8 @@ import com.sckw.auth.service.IAuthService;
 import com.sckw.core.model.enums.LoginMethodEnum;
 import com.sckw.core.model.enums.LoginMethodEnum;
 import com.sckw.core.web.constant.HttpStatus;
 import com.sckw.core.web.constant.HttpStatus;
 import com.sckw.core.web.response.HttpResult;
 import com.sckw.core.web.response.HttpResult;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.validation.Valid;
 import jakarta.validation.Valid;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -17,6 +19,7 @@ import org.springframework.web.bind.annotation.*;
  */
  */
 @RestController
 @RestController
 @RequestMapping("/auth")
 @RequestMapping("/auth")
+@Tag(name = "登录相关接口")
 public class AuthController {
 public class AuthController {
 
 
     @Autowired
     @Autowired
@@ -30,6 +33,7 @@ public class AuthController {
      * @date: 2023/6/16
      * @date: 2023/6/16
      */
      */
     @PostMapping("/login")
     @PostMapping("/login")
+    @Operation(summary = "用户登录")
     public HttpResult login(@Valid @RequestBody LoginReqVo reqVo, HttpServletRequest request) {
     public HttpResult login(@Valid @RequestBody LoginReqVo reqVo, HttpServletRequest request) {
         int systemType = request.getIntHeader("System-Type");
         int systemType = request.getIntHeader("System-Type");
         String clientType = request.getHeader("Client-Type");
         String clientType = request.getHeader("Client-Type");
@@ -44,9 +48,39 @@ public class AuthController {
         loginBase.setSystemType(systemType);
         loginBase.setSystemType(systemType);
         loginBase.setClientType(clientType);
         loginBase.setClientType(clientType);
         loginBase.setLoginMethod(LoginMethodEnum.ORDINARY.getValue());
         loginBase.setLoginMethod(LoginMethodEnum.ORDINARY.getValue());
+        loginBase.setRememberMe(reqVo.getRememberMe());
+        loginBase.setDeviceId(reqVo.getDeviceId());
         return authService.login(loginBase);
         return authService.login(loginBase);
     }
     }
 
 
+    /**
+     * @param reqVo 刷新Token入参
+     * @return HttpResult
+     * @desc: 刷新Token(记住密码时使用,支持多设备)
+     * @date: 2026/1/14
+     */
+    @PostMapping("/refresh")
+    @Operation(summary = "刷新Token")
+    public HttpResult refreshToken(@Valid @RequestBody RefreshTokenReqVo reqVo, HttpServletRequest request) {
+        int systemType = request.getIntHeader("System-Type");
+        String clientType = request.getHeader("Client-Type");
+        return authService.refreshToken(reqVo.getRefreshToken(), clientType, systemType, reqVo.getDeviceId());
+    }
+
+    /**
+     * @param reqVo 切换账号入参
+     * @return HttpResult
+     * @desc: 切换账号(使用目标账号的refreshToken快速登录,无需重新输入密码)
+     * @date: 2026/1/14
+     */
+    @Operation(summary = "切换账号")
+    @PostMapping("/switchAccount")
+    public HttpResult switchAccount(@Valid @RequestBody SwitchAccountReqVo reqVo, HttpServletRequest request) {
+        int systemType = request.getIntHeader("System-Type");
+        String clientType = request.getHeader("Client-Type");
+        return authService.switchAccount(reqVo.getTargetRefreshToken(), clientType, systemType, reqVo.getDeviceId());
+    }
+
     /**
     /**
      * 切换角色
      * 切换角色
      *
      *

+ 10 - 0
sckw-auth/src/main/java/com/sckw/auth/model/vo/req/LoginBase.java

@@ -44,4 +44,14 @@ public class LoginBase {
      * 登录方式1 账号密码登录/2账号短信登录/3单账号登录
      * 登录方式1 账号密码登录/2账号短信登录/3单账号登录
      */
      */
     private int loginMethod;
     private int loginMethod;
+
+    /**
+     * 是否记住密码(用于生成refreshToken)
+     */
+    private Boolean rememberMe = false;
+
+    /**
+     * 设备唯一标识(用于多设备记住密码)
+     */
+    private String deviceId;
 }
 }

+ 18 - 0
sckw-auth/src/main/java/com/sckw/auth/model/vo/req/LoginReqVo.java

@@ -1,5 +1,6 @@
 package com.sckw.auth.model.vo.req;
 package com.sckw.auth.model.vo.req;
 
 
+import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.constraints.NotBlank;
 import jakarta.validation.constraints.NotBlank;
 import jakarta.validation.constraints.NotNull;
 import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 import lombok.Data;
@@ -23,28 +24,45 @@ public class LoginReqVo implements Serializable {
      * 账号
      * 账号
      */
      */
     @NotBlank(message = "账号不能为空")
     @NotBlank(message = "账号不能为空")
+    @Schema(description = "账号")
     private String account;
     private String account;
 
 
     /**
     /**
      * 设备类型
      * 设备类型
      */
      */
 //    @NotBlank(message = "设备类型不能为空")
 //    @NotBlank(message = "设备类型不能为空")
+    @Schema(description = "设备类型")
     private String clientType;
     private String clientType;
 
 
     /**
     /**
      * 系统类型
      * 系统类型
      */
      */
 //    @NotNull(message = "系统类型不能为空")
 //    @NotNull(message = "系统类型不能为空")
+    @Schema(description = "系统类型")
     private Integer systemType;
     private Integer systemType;
 
 
     /**
     /**
      * 密码
      * 密码
      */
      */
+    @Schema(description = "密码")
     private String password;
     private String password;
 
 
     /**
     /**
      * 验证码
      * 验证码
      */
      */
+    @Schema(description = "验证码")
     private String captcha;
     private String captcha;
 
 
+    /**
+     * 是否记住密码
+     */
+    @Schema(description = "是否记住密码")
+    private Boolean rememberMe = false;
+
+    /**
+     * 设备唯一标识(用于多设备记住密码)
+     */
+    @Schema(description = "设备唯一标识")
+    private String deviceId;
+
 }
 }

+ 45 - 0
sckw-auth/src/main/java/com/sckw/auth/model/vo/req/RefreshTokenReqVo.java

@@ -0,0 +1,45 @@
+package com.sckw.auth.model.vo.req;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * @author cxf
+ * @desc 刷新Token请求入参
+ * @date 2026/1/14
+ */
+@Data
+public class RefreshTokenReqVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 刷新令牌
+     */
+    @NotBlank(message = "刷新令牌不能为空")
+    @Schema(description = "刷新令牌")
+    private String refreshToken;
+
+    /**
+     * 设备类型
+     */
+    @Schema(description = "设备类型")
+    private String clientType;
+
+    /**
+     * 系统类型
+     */
+    @Schema(description = "系统类型")
+    private Integer systemType;
+
+    /**
+     * 设备唯一标识
+     */
+    @Schema(description = "设备唯一标识")
+    private String deviceId;
+}

+ 45 - 0
sckw-auth/src/main/java/com/sckw/auth/model/vo/req/SwitchAccountReqVo.java

@@ -0,0 +1,45 @@
+package com.sckw.auth.model.vo.req;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * @author cxf
+ * @desc 切换账号请求入参
+ * @date 2026/1/14
+ */
+@Data
+public class SwitchAccountReqVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 目标账号的刷新令牌
+     */
+    @NotBlank(message = "目标账号的刷新令牌不能为空")
+    @Schema(description = "目标账号的刷新令牌")
+    private String targetRefreshToken;
+
+    /**
+     * 设备类型
+     */
+    @Schema(description = "设备类型")
+    private String clientType;
+
+    /**
+     * 系统类型
+     */
+    @Schema(description = "系统类型")
+    private Integer systemType;
+
+    /**
+     * 设备唯一标识
+     */
+    @Schema(description = "设备唯一标识")
+    private String deviceId;
+}

+ 29 - 1
sckw-auth/src/main/java/com/sckw/auth/model/vo/res/LoginResVo1.java

@@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import com.sckw.core.utils.LongToStringUtils;
 import com.sckw.core.utils.LongToStringUtils;
 import com.sckw.system.api.model.dto.res.KwsRoleResDto;
 import com.sckw.system.api.model.dto.res.KwsRoleResDto;
 import com.sckw.system.api.model.dto.res.RoleInfoDto;
 import com.sckw.system.api.model.dto.res.RoleInfoDto;
+import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import lombok.Data;
 
 
 import javax.management.relation.RoleInfo;
 import javax.management.relation.RoleInfo;
@@ -29,105 +30,132 @@ public class LoginResVo1 implements Serializable {
     /**
     /**
      * 姓名
      * 姓名
      */
      */
+    @Schema(description = "姓名")
     private String name;
     private String name;
 
 
     /**
     /**
      * 账号
      * 账号
      */
      */
+    @Schema(description = "账号")
     private String account;
     private String account;
 
 
     /**
     /**
      * 电话
      * 电话
      */
      */
+    @Schema(description = "电话")
     private String phone;
     private String phone;
 
 
     /**
     /**
      * 头像
      * 头像
      */
      */
+    @Schema(description = "头像")
     private String photo;
     private String photo;
 
 
     /**
     /**
      * 角色名称
      * 角色名称
      */
      */
+    @Schema(description = "角色名称")
     private String roleName;
     private String roleName;
 
 
     /**
     /**
      * 机构名称
      * 机构名称
      */
      */
+    @Schema(description = "机构名称")
     private String deptName;
     private String deptName;
 
 
     /**
     /**
      * 是否是企业管理(0是 1否)
      * 是否是企业管理(0是 1否)
      */
      */
+    @Schema(description = "是否是企业管理(0是 1否)")
     private Integer isMain;
     private Integer isMain;
 
 
     /**
     /**
      * 状态
      * 状态
      */
      */
+    @Schema(description = "状态")
     private Integer status;
     private Integer status;
 
 
     /**
     /**
      * 企业Id
      * 企业Id
      */
      */
     @JsonSerialize(using = LongToStringUtils.class)
     @JsonSerialize(using = LongToStringUtils.class)
+    @Schema(description = "企业Id")
     private Long entId;
     private Long entId;
 
 
     /**
     /**
      * 企业名称
      * 企业名称
      */
      */
+    @Schema(description = "企业名称")
     private String firmName;
     private String firmName;
 
 
     /**
     /**
      * 资料审批状态(0未审批、1通过、2未通过、3审批中)
      * 资料审批状态(0未审批、1通过、2未通过、3审批中)
      */
      */
+    @Schema(description = "资料审批状态(0未审批、1通过、2未通过、3审批中)")
     private Integer approval;
     private Integer approval;
 
 
     /**
     /**
      * 是否有效
      * 是否有效
      */
      */
+    @Schema(description = "是否有效")
     private Boolean valid;
     private Boolean valid;
 
 
 
 
     /**
     /**
      * 企业类型(名称)
      * 企业类型(名称)
      */
      */
+    @Schema(description = "企业类型(名称)")
     private String entTypeNames;
     private String entTypeNames;
 
 
     /**
     /**
      * 企业属性 (1供应商,2采购商,34PL物流,43PL物流)
      * 企业属性 (1供应商,2采购商,34PL物流,43PL物流)
      */
      */
+    @Schema(description = "企业属性 (1供应商,2采购商,34PL物流,43PL物流)")
     private String entTypes;
     private String entTypes;
 
 
     /**
     /**
      * 推送设备id
      * 推送设备id
      */
      */
+    @Schema(description = "推送设备id")
     private String clientId;
     private String clientId;
 
 
     /**
     /**
      * 设备类型
      * 设备类型
      */
      */
+    @Schema(description = "设备类型")
     private String clientType;
     private String clientType;
 
 
     /**
     /**
      * 系统类型(1运营端、2企业开户、3司机端)
      * 系统类型(1运营端、2企业开户、3司机端)
      */
      */
+    @Schema(description = "系统类型(1运营端、2企业开户、3司机端)")
     private Integer systemType;
     private Integer systemType;
 
 
     /**
     /**
-     * 设备类型
+     * 访问令牌
      */
      */
+    @Schema(description = "访问令牌")
     private String token;
     private String token;
 
 
+    /**
+     * 刷新令牌(记住密码时返回)
+     */
+    @Schema(description = "刷新令牌(记住密码时返回)")
+    private String refreshToken;
+
     /**
     /**
      * 机构id
      * 机构id
      */
      */
+    @Schema(description = "机构id")
     private Long deptId;
     private Long deptId;
 
 
     /**
     /**
      * 岗位角色id
      * 岗位角色id
      */
      */
+    @Schema(description = "岗位角色id")
     private Long roleId;
     private Long roleId;
 
 
+    @Schema(description = "岗位角色列表")
     private List<RoleInfoDto> roleList;
     private List<RoleInfoDto> roleList;
 
 
 }
 }

+ 22 - 0
sckw-auth/src/main/java/com/sckw/auth/service/IAuthService.java

@@ -53,4 +53,26 @@ public interface IAuthService {
     LoginResVo1 getLoginResByToken(String clientType, String token);
     LoginResVo1 getLoginResByToken(String clientType, String token);
 
 
     HttpResult changeRole(RoleChangeVo reqVo);
     HttpResult changeRole(RoleChangeVo reqVo);
+
+    /**
+     * @param refreshToken 刷新令牌
+     * @param clientType   客户端类型
+     * @param systemType   系统类型
+     * @param deviceId     设备标识
+     * @return HttpResult
+     * @desc: 刷新Token(记住密码时使用,支持多设备)
+     * @date: 2026/1/14
+     */
+    HttpResult refreshToken(String refreshToken, String clientType, Integer systemType, String deviceId);
+
+    /**
+     * @param targetRefreshToken 目标账号的刷新令牌
+     * @param clientType         客户端类型
+     * @param systemType         系统类型
+     * @param deviceId           设备标识
+     * @return HttpResult
+     * @desc: 切换账号(使用目标账号的refreshToken快速登录)
+     * @date: 2026/1/14
+     */
+    HttpResult switchAccount(String targetRefreshToken, String clientType, Integer systemType, String deviceId);
 }
 }

+ 327 - 1
sckw-auth/src/main/java/com/sckw/auth/service/impl/AuthServiceImpl.java

@@ -142,6 +142,12 @@ public class AuthServiceImpl implements IAuthService {
         /*缓存信息**/
         /*缓存信息**/
         AsyncFactory.execute(new AsyncProcess(loginBase, null, driver, enterprise, remoteUserService));
         AsyncFactory.execute(new AsyncProcess(loginBase, null, driver, enterprise, remoteUserService));
 
 
+        /*生成refreshToken(记住密码时)**/
+        String refreshToken = null;
+        if (Boolean.TRUE.equals(loginBase.getRememberMe())) {
+            refreshToken = generateRefreshToken(loginBase, driver.getId());
+        }
+
         /*数据组装**/
         /*数据组装**/
         LoginResVo1 loginRes = new LoginResVo1();
         LoginResVo1 loginRes = new LoginResVo1();
         loginRes.setId(driver.getId());
         loginRes.setId(driver.getId());
@@ -156,6 +162,7 @@ public class AuthServiceImpl implements IAuthService {
         loginRes.setClientType(loginBase.getClientType());
         loginRes.setClientType(loginBase.getClientType());
         loginRes.setSystemType(loginBase.getSystemType());
         loginRes.setSystemType(loginBase.getSystemType());
         loginRes.setToken(token);
         loginRes.setToken(token);
+        loginRes.setRefreshToken(refreshToken);
         return HttpResult.ok(loginRes);
         return HttpResult.ok(loginRes);
     }
     }
 
 
@@ -211,8 +218,14 @@ public class AuthServiceImpl implements IAuthService {
         /*缓存信息**/
         /*缓存信息**/
 //        AsyncFactory.execute(new AsyncProcess(loginBase, user, null, enterprise, remoteUserService));
 //        AsyncFactory.execute(new AsyncProcess(loginBase, user, null, enterprise, remoteUserService));
         new AsyncProcess(loginBase, user, null, enterprise, remoteUserService).run();
         new AsyncProcess(loginBase, user, null, enterprise, remoteUserService).run();
-        /*数据组装**/
 
 
+        /*生成refreshToken(记住密码时)**/
+        String refreshToken = null;
+        if (Boolean.TRUE.equals(loginBase.getRememberMe())) {
+            refreshToken = generateRefreshToken(loginBase, user.getId());
+        }
+
+        /*数据组装**/
         LoginResVo1 loginRes = new LoginResVo1();
         LoginResVo1 loginRes = new LoginResVo1();
         loginRes.setId(user.getId());
         loginRes.setId(user.getId());
         loginRes.setName(user.getName());
         loginRes.setName(user.getName());
@@ -231,6 +244,7 @@ public class AuthServiceImpl implements IAuthService {
         loginRes.setClientType(loginBase.getClientType());
         loginRes.setClientType(loginBase.getClientType());
         loginRes.setSystemType(user.getSystemType());
         loginRes.setSystemType(user.getSystemType());
         loginRes.setToken(token);
         loginRes.setToken(token);
+        loginRes.setRefreshToken(refreshToken);
         loginRes.setDeptId(user.getDeptId());
         loginRes.setDeptId(user.getDeptId());
         loginRes.setRoleId(user.getRoleId());
         loginRes.setRoleId(user.getRoleId());
         loginRes.setRoleName(user.getRoleName());
         loginRes.setRoleName(user.getRoleName());
@@ -459,6 +473,318 @@ public class AuthServiceImpl implements IAuthService {
         return token;
         return token;
     }
     }
 
 
+    /**
+     * @param loginBase 登录参数
+     * @param userId    用户ID
+     * @desc 生成refreshToken(记住密码时使用,支持多设备)
+     * @author assistant
+     * @date 2026/1/14
+     **/
+    private String generateRefreshToken(LoginBase loginBase, Long userId) {
+        String account = loginBase.getAccount();
+        String clientType = loginBase.getClientType();
+        Integer systemType = loginBase.getSystemType();
+        String deviceId = loginBase.getDeviceId();
+        Map<String, Object> info = new HashMap<>(Global.NUMERICAL_SIXTEEN);
+        info.put("userId", userId);
+        info.put("account", account);
+        info.put("clientType", clientType);
+        info.put("systemType", systemType);
+        info.put("deviceId", deviceId);
+        info.put("type", "refresh");
+        String key = Global.getFullRefreshTokenKey(clientType, userId, deviceId);
+        String refreshToken = EncryUtil.encryV1(Global.PRI_KEY, JSON.toJSONString(info));
+        RedissonUtils.putString(key, refreshToken, Global.REFRESH_TOKEN_EXPIRE);
+        return refreshToken;
+    }
+
+    @Override
+    public HttpResult refreshToken(String refreshToken, String clientType, Integer systemType, String deviceId) {
+        log.info("刷新token,refreshToken:{},clientType:{},systemType:{},deviceId:{}", refreshToken, clientType, systemType, deviceId);
+        
+        // 验证refreshToken参数
+        HttpResult validateResult = validateRefreshTokenParam(refreshToken);
+        if (validateResult != null) {
+            return validateResult;
+        }
+        
+        // 解析refreshToken
+        Map<String, Object> tokenInfoMap = parseRefreshToken(refreshToken);
+        if (tokenInfoMap == null) {
+            return HttpResult.error(HttpStatus.TOKEN_INVALID_CODE, "刷新令牌无效!");
+        }
+        
+        // 验证refreshToken是否有效
+        HttpResult verifyResult = verifyRefreshToken(tokenInfoMap, refreshToken);
+        if (verifyResult != null) {
+            return verifyResult;
+        }
+        
+        // 构建LoginBase对象
+        LoginBase loginBase = buildLoginBaseFromTokenInfo(tokenInfoMap);
+        
+        // 根据系统类型处理刷新逻辑
+        Integer tokenSystemType = Integer.valueOf(tokenInfoMap.get("systemType").toString());
+        if (Objects.equals(tokenSystemType, SystemTypeEnum.DRIVER.getCode())) {
+            return handleDriverRefresh(loginBase, tokenInfoMap);
+        } else {
+            return handleCommonRefresh(loginBase, tokenInfoMap);
+        }
+    }
+    
+    @Override
+    public HttpResult switchAccount(String targetRefreshToken, String clientType, Integer systemType, String deviceId) {
+        log.info("切换账号,targetRefreshToken:{},clientType:{},systemType:{},deviceId:{}", targetRefreshToken, clientType, systemType, deviceId);
+        
+        // 验证targetRefreshToken参数
+        HttpResult validateResult = validateRefreshTokenParam(targetRefreshToken);
+        if (validateResult != null) {
+            return validateResult;
+        }
+        
+        // 解析目标账号的refreshToken
+        Map<String, Object> targetTokenInfoMap = parseRefreshToken(targetRefreshToken);
+        if (targetTokenInfoMap == null) {
+            return HttpResult.error(HttpStatus.TOKEN_INVALID_CODE, "目标账号的刷新令牌无效!");
+        }
+        
+        // 验证目标账号的refreshToken是否有效
+        HttpResult verifyResult = verifyRefreshToken(targetTokenInfoMap, targetRefreshToken);
+        if (verifyResult != null) {
+            return HttpResult.error(HttpStatus.TOKEN_INVALID_CODE, "目标账号的刷新令牌已过期或无效!");
+        }
+        
+        // 清除当前登录用户的token缓存(如果存在)
+        clearCurrentUserToken(clientType, deviceId);
+        
+        // 构建LoginBase对象(使用新的deviceId)
+        LoginBase loginBase = buildLoginBaseFromTokenInfo(targetTokenInfoMap);
+        // 如果传入了新的deviceId,则使用新的deviceId
+        if (StringUtils.isNotBlank(deviceId)) {
+            loginBase.setDeviceId(deviceId);
+        }
+        
+        // 根据系统类型处理切换逻辑
+        Integer targetSystemType = Integer.valueOf(targetTokenInfoMap.get("systemType").toString());
+        if (Objects.equals(SystemTypeEnum.DRIVER.getCode(), targetSystemType)) {
+            return handleDriverRefresh(loginBase, targetTokenInfoMap);
+        } else {
+            return handleCommonRefresh(loginBase, targetTokenInfoMap);
+        }
+    }
+    
+    /**
+     * 清除当前登录用户的token缓存
+     */
+    private void clearCurrentUserToken(String clientType, String deviceId) {
+        try {
+            LoginUserInfo currentUser = LoginUserHolder.get();
+            if (currentUser != null && currentUser.getId() != null) {
+                // 清除当前用户的token
+                String tokenKey = Global.getFullUserTokenKey(clientType, currentUser.getId());
+                RedissonUtils.delete(tokenKey);
+                
+                // 清除当前用户的登录信息缓存
+                String loginKey = Global.getFullUserLoginKey(currentUser.getSystemType(), currentUser.getId());
+                RedissonUtils.delete(loginKey);
+                
+                log.info("已清除当前用户token缓存,userId:{}", currentUser.getId());
+            }
+        } catch (Exception e) {
+            log.warn("清除当前用户token缓存失败", e);
+        }
+    }
+    
+    /**
+     * 验证refreshToken参数
+     */
+    private HttpResult validateRefreshTokenParam(String refreshToken) {
+        if (StringUtils.isBlank(refreshToken)) {
+            return HttpResult.error(HttpStatus.PARAMETERS_MISSING_CODE, "刷新令牌不能为空!");
+        }
+        return null;
+    }
+    
+    /**
+     * 解析refreshToken
+     */
+    private Map<String, Object> parseRefreshToken(String refreshToken) {
+        String tokenInfo;
+        try {
+            tokenInfo = EncryUtil.descry(Global.PRI_KEY, refreshToken);
+            return JSON.parseObject(tokenInfo, Map.class);
+        } catch (Exception e) {
+            log.error("解析refreshToken失败", e);
+            return null;
+        }
+    }
+    
+    /**
+     * 验证refreshToken是否有效
+     */
+    private HttpResult verifyRefreshToken(Map<String, Object> tokenInfoMap, String refreshToken) {
+        Long userId = Long.valueOf(tokenInfoMap.get("userId").toString());
+        String tokenClientType = tokenInfoMap.get("clientType").toString();
+        String tokenDeviceId = tokenInfoMap.get("deviceId") != null ? tokenInfoMap.get("deviceId").toString() : null;
+        
+        String key = Global.getFullRefreshTokenKey(tokenClientType, userId, tokenDeviceId);
+        String storedRefreshToken = RedissonUtils.getString(key);
+        if (StringUtils.isBlank(storedRefreshToken) || !storedRefreshToken.equals(refreshToken)) {
+            return HttpResult.error(HttpStatus.TOKEN_INVALID_CODE, "刷新令牌已过期或无效,请重新登录!");
+        }
+        return null;
+    }
+    
+    /**
+     * 从token信息构建LoginBase对象
+     */
+    private LoginBase buildLoginBaseFromTokenInfo(Map<String, Object> tokenInfoMap) {
+        String account = tokenInfoMap.get("account").toString();
+        String tokenClientType = tokenInfoMap.get("clientType").toString();
+        Integer tokenSystemType = Integer.valueOf(tokenInfoMap.get("systemType").toString());
+        String tokenDeviceId = tokenInfoMap.get("deviceId") != null ? tokenInfoMap.get("deviceId").toString() : null;
+        
+        LoginBase loginBase = new LoginBase();
+        loginBase.setAccount(account);
+        loginBase.setClientType(tokenClientType);
+        loginBase.setSystemType(tokenSystemType);
+        loginBase.setDeviceId(tokenDeviceId);
+        loginBase.setRememberMe(true);
+        
+        return loginBase;
+    }
+    
+    /**
+     * 处理司机端token刷新
+     */
+    private HttpResult handleDriverRefresh(LoginBase loginBase, Map<String, Object> tokenInfoMap) {
+        String account = tokenInfoMap.get("account").toString();
+        String tokenClientType = tokenInfoMap.get("clientType").toString();
+        Integer tokenSystemType = Integer.valueOf(tokenInfoMap.get("systemType").toString());
+        
+        // 查询司机信息
+        RDriverDetailVo driver = fleetService.findDriverDetai(account);
+        if (driver == null) {
+            return HttpResult.error(HttpStatus.QUERY_FAIL_CODE, "账号不存在!");
+        }
+        if (driver.getStatus() == Global.YES) {
+            return HttpResult.error(HttpStatus.CODE_10301, "您的账号已冻结!");
+        }
+
+        EntCacheResDto enterprise = systemService.queryEntDetails(driver.getEntId());
+
+        // 生成新token
+        String newToken = generateToken(loginBase, driver.getId());
+        // 生成新的refreshToken
+        String newRefreshToken = generateRefreshToken(loginBase, driver.getId());
+
+        // 更新缓存
+        new AsyncProcess(loginBase, null, driver, enterprise, remoteUserService).run();
+
+        // 构建返回结果
+        LoginResVo1 loginRes = buildDriverLoginRes(driver, enterprise, tokenClientType, tokenSystemType, newToken, newRefreshToken);
+        return HttpResult.ok(loginRes);
+    }
+    
+    /**
+     * 处理运营端/企业端token刷新
+     */
+    private HttpResult handleCommonRefresh(LoginBase loginBase, Map<String, Object> tokenInfoMap) {
+        String account = tokenInfoMap.get("account").toString();
+        String tokenClientType = tokenInfoMap.get("clientType").toString();
+        Integer tokenSystemType = Integer.valueOf(tokenInfoMap.get("systemType").toString());
+        
+        // 查询用户信息
+        KwsUserResDto user = systemService.queryUserDetails(account, tokenSystemType);
+        if (user == null) {
+            return HttpResult.error(HttpStatus.QUERY_FAIL_CODE, "账号不存在!");
+        }
+        if (user.getStatus() == Global.YES) {
+            return HttpResult.error(HttpStatus.CODE_10301, "您的账号已冻结!");
+        }
+
+        EntCacheResDto enterprise = systemService.queryEntDetails(user.getEntId());
+        if (tokenSystemType == SystemTypeEnum.COMPANY.getCode()) {
+            if (enterprise == null) {
+                return HttpResult.error(HttpStatus.QUERY_FAIL_CODE, "账号没有归属企业!");
+            }
+        }
+        if (enterprise != null && enterprise.getStatus() == Global.YES) {
+            return HttpResult.error(HttpStatus.QUERY_FAIL_CODE, "企业已冻结!");
+        }
+
+        // 生成新token
+        String newToken = generateToken(loginBase, user.getId());
+        String newRefreshToken = generateRefreshToken(loginBase, user.getId());
+
+        // 更新缓存
+        new AsyncProcess(loginBase, user, null, enterprise, remoteUserService).run();
+
+        // 构建返回结果
+        LoginResVo1 loginRes = buildCommonLoginRes(user, enterprise, tokenClientType, tokenSystemType, newToken, newRefreshToken);
+        return HttpResult.ok(loginRes);
+    }
+    
+    /**
+     * 构建司机端登录返回结果
+     */
+    private LoginResVo1 buildDriverLoginRes(RDriverDetailVo driver, EntCacheResDto enterprise, 
+                                            String tokenClientType, Integer tokenSystemType, 
+                                            String newToken, String newRefreshToken) {
+        LoginResVo1 loginRes = new LoginResVo1();
+        loginRes.setId(driver.getId());
+        loginRes.setName(driver.getName());
+        loginRes.setAccount(driver.getPhone());
+        loginRes.setPhone(driver.getPhone());
+        loginRes.setStatus(driver.getStatus());
+        loginRes.setEntId(driver.getEntId());
+        loginRes.setFirmName(enterprise != null ? enterprise.getFirmName() : null);
+        loginRes.setApproval(enterprise != null ? enterprise.getApproval() : null);
+        loginRes.setEntTypeNames(enterprise != null ? enterprise.getEntTypeNames() : null);
+        loginRes.setClientType(tokenClientType);
+        loginRes.setSystemType(tokenSystemType);
+        loginRes.setToken(newToken);
+        loginRes.setRefreshToken(newRefreshToken);
+        return loginRes;
+    }
+    
+    /**
+     * 构建运营端/企业端登录返回结果
+     */
+    private LoginResVo1 buildCommonLoginRes(KwsUserResDto user, EntCacheResDto enterprise, 
+                                           String tokenClientType, Integer tokenSystemType, 
+                                           String newToken, String newRefreshToken) {
+        LoginResVo1 loginRes = new LoginResVo1();
+        loginRes.setId(user.getId());
+        loginRes.setName(user.getName());
+        loginRes.setAccount(user.getAccount());
+        loginRes.setPhone(user.getPhone());
+        loginRes.setPhoto(user.getPhoto());
+        loginRes.setIsMain(user.getIsMain());
+        loginRes.setStatus(user.getStatus());
+        loginRes.setDeptName(user.getDeptName());
+        loginRes.setClientId(user.getClientId());
+        loginRes.setEntId(user.getEntId());
+        loginRes.setFirmName(enterprise != null ? enterprise.getFirmName() : null);
+        loginRes.setApproval(enterprise != null ? enterprise.getApproval() : null);
+        loginRes.setEntTypes(enterprise != null ? enterprise.getEntTypes() : null);
+        loginRes.setEntTypeNames(enterprise != null ? enterprise.getEntTypeNames() : null);
+        loginRes.setClientType(tokenClientType);
+        loginRes.setSystemType(user.getSystemType());
+        loginRes.setToken(newToken);
+        loginRes.setRefreshToken(newRefreshToken);
+        loginRes.setDeptId(user.getDeptId());
+        loginRes.setRoleId(user.getRoleId());
+        loginRes.setRoleName(user.getRoleName());
+        loginRes.setRoleList(user.getRoleInfoDto());
+        if (user.getSystemType().equals(SystemTypeEnum.MANAGE.getCode())) {
+            loginRes.setValid(true);
+        } else {
+            loginRes.setValid(!Objects.isNull(enterprise) && enterprise.getValid());
+        }
+        return loginRes;
+    }
+
     static class AsyncProcess implements Runnable {
     static class AsyncProcess implements Runnable {
         private final LoginBase loginBase;
         private final LoginBase loginBase;
 
 

+ 13 - 0
sckw-auth/src/main/resources/bootstrap.yml

@@ -12,3 +12,16 @@ spring:
 logging:
 logging:
   level:
   level:
     root: info
     root: info
+
+
+# SpringDoc OpenAPI 配置
+springdoc:
+  swagger-ui:
+    path: /swagger-ui.html
+    tags-sorter: alpha
+    operations-sorter: alpha
+  api-docs:
+    path: /v3/api-docs
+  show-actuator: true
+  cache:
+    disabled: true

+ 23 - 0
sckw-common/sckw-common-core/src/main/java/com/sckw/core/model/constant/Global.java

@@ -147,9 +147,15 @@ public class Global {
     public static final String REDIS_USER_LOGIN_PREFIX = "userLoginInfo:";
     public static final String REDIS_USER_LOGIN_PREFIX = "userLoginInfo:";
     public static final String REDIS_USER_PREFIX = "userInfo:";
     public static final String REDIS_USER_PREFIX = "userInfo:";
     public static final String REDIS_USER_TOKEN_PREFIX = "userToken:";
     public static final String REDIS_USER_TOKEN_PREFIX = "userToken:";
+    public static final String REDIS_USER_REFRESH_TOKEN_PREFIX = "userRefreshToken:";
     //客户经理redisKey
     //客户经理redisKey
     public static final String REDIS_CUSTOMER_MANAGER_USER_LOGIN_PREFIX = "customerManagerUserLoginInfo:";
     public static final String REDIS_CUSTOMER_MANAGER_USER_LOGIN_PREFIX = "customerManagerUserLoginInfo:";
 
 
+    /**
+     * refresh token有效期为30天
+     */
+    public static final int REFRESH_TOKEN_EXPIRE = 30 * 24 * 60 * 60;
+
     /**
     /**
      * redis企业信息前缀
      * redis企业信息前缀
      */
      */
@@ -452,6 +458,23 @@ public class Global {
         return builder;
         return builder;
     }
     }
 
 
+    /**
+     * 完整的用户刷新tokenkey(支持多设备)
+     */
+    public static String getFullRefreshTokenKey(String clientType, Long userId, String deviceId) {
+        StringBuilder builder = new StringBuilder(REDIS_USER_REFRESH_TOKEN_PREFIX);
+        if (StringUtils.isNotNull(clientType)) {
+            builder.append(clientType).append(COLON);
+        }
+        if (Objects.nonNull(userId)) {
+            builder.append(userId);
+        }
+        if (StringUtils.isNotNull(deviceId)) {
+            builder.append(COLON).append(deviceId);
+        }
+        return builder.toString();
+    }
+
     /**
     /**
      * 完整的用户企业信息key
      * 完整的用户企业信息key
      */
      */

+ 5 - 0
sckw-common/sckw-common-core/src/main/java/com/sckw/core/web/response/BaseResult.java

@@ -30,6 +30,11 @@ public class BaseResult<T> implements Serializable {
         result.setData(data);
         result.setData(data);
         return result;
         return result;
     }
     }
+    public static <T> BaseResult<T> success(String data) {
+        BaseResult<T> result = new BaseResult<>();
+        result.setMessage(data);
+        return result;
+    }
 
 
     /**
     /**
      * 失败响应 - 自定义消息
      * 失败响应 - 自定义消息

+ 36 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/forkliftapp/AppVersionsController.java

@@ -0,0 +1,36 @@
+package com.sckw.system.controller.forkliftapp;
+
+import com.sckw.core.web.response.BaseResult;
+import com.sckw.system.model.vo.res.AppVersionsResVo;
+import com.sckw.system.service.KwsAppVersionsService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 系统版本管理Controller
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+@RestController
+@RequestMapping("/appVersions")
+@Tag(name = "版本管理相关接口")
+@RequiredArgsConstructor
+public class AppVersionsController {
+
+    private final KwsAppVersionsService kwsAppVersionsService;
+
+    /**
+     * 检查更新
+     * @param platform 平台类型: 1-Android, 2-iOS
+     * @return BaseResult
+     */
+    @GetMapping("/checkUpdate")
+    @Operation(summary = "检查更新", description = "根据平台类型获取最新版本信息")
+    public BaseResult<AppVersionsResVo> checkUpdate(@RequestParam Integer platform) {
+        return BaseResult.success(kwsAppVersionsService.getLatestVersion(platform));
+    }
+
+}
+

+ 68 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/forkliftapp/FeedbacksController.java

@@ -0,0 +1,68 @@
+package com.sckw.system.controller.forkliftapp;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.sckw.core.web.response.BaseResult;
+import com.sckw.system.model.vo.req.FeedbackListReqVo;
+import com.sckw.system.model.vo.req.SubmitFeedbackReqVo;
+import com.sckw.system.model.vo.res.FeedbackResVo;
+import com.sckw.system.service.KwsFeedbackService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 意见反馈Controller
+ * @author chenxiaofei
+ * @date 2026-01-12
+ */
+@RestController
+@RequestMapping("/feedback")
+@Tag(name = "意见反馈相关接口")
+@RequiredArgsConstructor
+public class FeedbacksController {
+
+    private final KwsFeedbackService kwsFeedbackService;
+
+    /**
+     * 提交意见反馈
+     * @param reqVo 提交反馈请求对象
+     * @return BaseResult<FeedbackResVo>
+     */
+    @PostMapping("/submit")
+    @Operation(summary = "提交意见反馈", description = "用户提交意见反馈,可上传图片")
+    public BaseResult<FeedbackResVo> submitFeedback(@RequestBody @Valid SubmitFeedbackReqVo reqVo) {
+        FeedbackResVo resVo = kwsFeedbackService.submitFeedback(reqVo);
+        return BaseResult.success(resVo);
+    }
+
+    /**
+     * 查询用户反馈列表(分页)
+     * @param reqVo 反馈列表查询请求对象
+     * @return BaseResult<Page<FeedbackResVo>>
+     */
+    @PostMapping("/list")
+    @Operation(summary = "查询反馈列表", description = "分页查询当前用户的反馈列表")
+    public BaseResult<Page<FeedbackResVo>> getFeedbackList(@RequestBody @Valid FeedbackListReqVo reqVo) {
+        Page<FeedbackResVo> page = kwsFeedbackService.getFeedbackList(reqVo);
+        return BaseResult.success(page);
+    }
+
+    /**
+     * 查询反馈详情
+     * @param feedbackId 反馈ID
+     * @return BaseResult<FeedbackResVo>
+     */
+    @GetMapping("/detail/{feedbackId}")
+    @Operation(summary = "查询反馈详情", description = "根据反馈ID查询详细信息")
+    public BaseResult<FeedbackResVo> getFeedbackDetail(@PathVariable("feedbackId") Long feedbackId) {
+        FeedbackResVo resVo = kwsFeedbackService.getFeedbackDetail(feedbackId);
+        return BaseResult.success(resVo);
+    }
+
+}
+
+
+
+

+ 111 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/forkliftapp/LoadingRecordsController.java

@@ -0,0 +1,111 @@
+package com.sckw.system.controller.forkliftapp;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.sckw.core.utils.BeanUtils;
+import com.sckw.core.web.response.BaseResult;
+import com.sckw.system.model.LoadingRecords;
+import com.sckw.system.model.vo.res.LoadingRecordsResVo;
+import com.sckw.system.repository.LoadingRecordsService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 装载作业记录Controller
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+@RestController
+@RequestMapping("/loadingRecords")
+@Tag(name = "装载记录相关接口")
+public class LoadingRecordsController {
+
+    @Autowired
+    private LoadingRecordsService loadingRecordsService;
+
+    /**
+     * 获取订单列表(装载记录列表)
+     * @param pageNum 页码
+     * @param pageSize 每页数量
+     * @param operatorId 操作员ID(可选)
+     * @param workDate 作业日期(可选)
+     * @return BaseResult
+     */
+    @GetMapping("/orders")
+    @Operation(summary = "获取订单列表", description = "分页获取装载记录列表")
+    public BaseResult<IPage<LoadingRecordsResVo>> getOrders(
+            @RequestParam(defaultValue = "1") Integer pageNum,
+            @RequestParam(defaultValue = "10") Integer pageSize,
+            @RequestParam(required = false) Long operatorId,
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date workDate) {
+        
+        Page<LoadingRecords> page = new Page<>(pageNum, pageSize);
+        LambdaQueryWrapper<LoadingRecords> wrapper = new LambdaQueryWrapper<>();
+        
+        if (operatorId != null) {
+            wrapper.eq(LoadingRecords::getOperatorId, operatorId);
+        }
+        if (workDate != null) {
+            wrapper.eq(LoadingRecords::getWorkDate, workDate);
+        }
+        
+        wrapper.orderByDesc(LoadingRecords::getCreatedAt);
+        
+        IPage<LoadingRecords> result = loadingRecordsService.page(page, wrapper);
+        
+        // 转换为ResVo
+        Page<LoadingRecordsResVo> resVoPage = new Page<>(result.getCurrent(), result.getSize(), result.getTotal());
+        List<LoadingRecordsResVo> resVoList = result.getRecords().stream()
+                .map(record -> {
+                    LoadingRecordsResVo resVo = new LoadingRecordsResVo();
+                    BeanUtils.copyProperties(record, resVo);
+                    return resVo;
+                })
+                .collect(Collectors.toList());
+        resVoPage.setRecords(resVoList);
+        
+        return BaseResult.success(resVoPage);
+    }
+
+    /**
+     * 创建装载记录
+     * @param loadingRecord 装载记录
+     * @return BaseResult
+     */
+    @PostMapping("/create")
+    @Operation(summary = "创建装载记录", description = "创建新的装载作业记录")
+    public BaseResult<String> createLoadingRecord(@RequestBody LoadingRecords loadingRecord) {
+        boolean success = loadingRecordsService.save(loadingRecord);
+        if (success) {
+            return BaseResult.success("创建成功");
+        }
+        return BaseResult.failed("创建失败");
+    }
+
+    /**
+     * 根据ID获取装载记录详情
+     * @param id 记录ID
+     * @return BaseResult
+     */
+    @GetMapping("/detail")
+    @Operation(summary = "获取装载记录详情", description = "根据ID获取装载记录详细信息")
+    public BaseResult<LoadingRecordsResVo> getLoadingRecordDetail(@RequestParam Long id) {
+        LoadingRecords record = loadingRecordsService.getById(id);
+        if (record == null) {
+            return BaseResult.success(null);
+        }
+        LoadingRecordsResVo resVo = new LoadingRecordsResVo();
+        BeanUtils.copyProperties(record, resVo);
+        return BaseResult.success(resVo);
+    }
+
+}
+

+ 65 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/forkliftapp/MaterialsController.java

@@ -0,0 +1,65 @@
+package com.sckw.system.controller.forkliftapp;
+
+import com.sckw.core.utils.BeanUtils;
+import com.sckw.core.web.response.BaseResult;
+import com.sckw.system.model.Materials;
+import com.sckw.system.model.vo.res.MaterialsResVo;
+import com.sckw.system.repository.MaterialsService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 物料信息Controller
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+@RestController
+@RequestMapping("/materials")
+@Tag(name = "物料信息相关接口")
+public class MaterialsController {
+
+    @Autowired
+    private MaterialsService materialsService;
+
+    /**
+     * 获取物料列表
+     * @return BaseResult
+     */
+    @GetMapping("/list")
+    @Operation(summary = "获取物料列表", description = "获取所有物料信息列表")
+    public BaseResult<List<MaterialsResVo>> getMaterialsList() {
+        List<Materials> list = materialsService.list();
+        List<MaterialsResVo> resVoList = list.stream()
+                .map(material -> {
+                    MaterialsResVo resVo = new MaterialsResVo();
+                    BeanUtils.copyProperties(material, resVo);
+                    return resVo;
+                })
+                .collect(Collectors.toList());
+        return BaseResult.success(resVoList);
+    }
+
+    /**
+     * 根据ID获取物料信息
+     * @param id 物料ID
+     * @return BaseResult
+     */
+    @GetMapping("/detail")
+    @Operation(summary = "获取物料详情", description = "根据ID获取物料详细信息")
+    public BaseResult<MaterialsResVo> getMaterialDetail(@RequestParam Long id) {
+        Materials material = materialsService.getById(id);
+        if (material == null) {
+            return BaseResult.success(null);
+        }
+        MaterialsResVo resVo = new MaterialsResVo();
+        BeanUtils.copyProperties(material, resVo);
+        return BaseResult.success(resVo);
+    }
+
+}
+

+ 60 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/forkliftapp/SysArticlesController.java

@@ -0,0 +1,60 @@
+package com.sckw.system.controller.forkliftapp;
+
+import com.sckw.core.web.response.BaseResult;
+import com.sckw.system.model.vo.res.SysArticlesResVo;
+import com.sckw.system.service.KwsSysArticlesService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 系统内容配置Controller
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+@RestController
+@RequestMapping("/sysArticles")
+@Tag(name = "系统文章相关接口")
+@RequiredArgsConstructor
+public class SysArticlesController {
+
+
+    private final KwsSysArticlesService kwsSysArticlesService;
+
+    /**
+     * 获取隐私协议
+     * @return BaseResult
+     */
+    @GetMapping("/privacyPolicy")
+    @Operation(summary = "获取隐私协议", description = "获取隐私协议内容")
+    public BaseResult<SysArticlesResVo> getPrivacyPolicy() {
+
+        return BaseResult.success(kwsSysArticlesService.getPrivacyPolicy("PRIVACY_POLICY"));
+    }
+
+    /**
+     * 获取服务协议
+     * @return BaseResult
+     */
+    @GetMapping("/serviceAgreement")
+    @Operation(summary = "获取服务协议", description = "获取服务协议内容")
+    public BaseResult<SysArticlesResVo> getServiceAgreement() {
+
+        return BaseResult.success(kwsSysArticlesService.getServiceAgreement("REG_AGREEMENT"));
+    }
+
+    /**
+     * 根据文章标识获取文章
+     * @param articleKey 文章标识
+     * @return BaseResult
+     */
+    @GetMapping("/getByKey")
+    @Operation(summary = "根据标识获取文章", description = "根据文章标识获取文章内容")
+    public BaseResult<SysArticlesResVo> getByArticleKey(@RequestParam String articleKey) {
+
+        return BaseResult.success(kwsSysArticlesService.getByArticleKey(articleKey));
+    }
+
+}
+

+ 104 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/forkliftapp/UsersController.java

@@ -0,0 +1,104 @@
+package com.sckw.system.controller.forkliftapp;
+
+import com.sckw.core.web.response.BaseResult;
+import com.sckw.system.model.vo.req.LoginReqVo;
+import com.sckw.system.model.vo.req.LogoutReqVo;
+import com.sckw.system.model.vo.req.SwitchAccountReqVo;
+import com.sckw.system.model.vo.req.UpdatePasswordReqVo;
+import com.sckw.system.model.vo.req.UserInfoReqVo;
+import com.sckw.system.model.vo.res.LoginResVo;
+import com.sckw.system.model.vo.res.SwitchAccountResVo;
+import com.sckw.system.model.vo.res.UserInfoResVo;
+import com.sckw.system.service.KwsUserService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 用户基础信息Controller
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+@RestController
+@RequestMapping("/users")
+@Tag(name = "用户信息相关接口")
+@RequiredArgsConstructor
+public class UsersController {
+
+    private final KwsUserService kwsUserService;
+
+    /**
+     * 用户登录
+     * @param reqVo 登录请求对象
+     * @param request HTTP请求
+     * @return BaseResult<LoginResVo>
+     */
+//    @PostMapping("/login")
+//    @Operation(summary = "用户登录", description = "用户登录,返回用户信息和角色")
+//    public BaseResult<LoginResVo> login(@RequestBody @Valid LoginReqVo reqVo, HttpServletRequest request) {
+//        return BaseResult.success(kwsUserService.login(
+//                reqVo.getAccount(),
+//                reqVo.getPassword(),
+//                reqVo.getSystemType(),
+//                request));
+//    }
+
+    /**
+     * 根据用户ID查询用户信息
+     * @param reqVo 用户信息查询请求对象
+     * @return BaseResult<UserInfoResVo>
+     */
+//    @PostMapping("/getUserInfo")
+//    @Operation(summary = "查询用户信息", description = "根据用户ID查询头像、姓名、电话、角色、账号状态")
+//    public BaseResult<UserInfoResVo> getUserInfo(@RequestBody UserInfoReqVo reqVo) {
+//        return BaseResult.success(kwsUserService.getUserInfoById(reqVo.getUserId()));
+//    }
+
+    /**
+     * 修改密码
+     * @param reqVo 修改密码请求对象
+     * @return BaseResult<String>
+     */
+    @PostMapping("/updatePassword")
+    @Operation(summary = "修改密码", description = "用户修改密码")
+    public BaseResult<String> updatePassword(@RequestBody @Valid UpdatePasswordReqVo reqVo, HttpServletRequest request) {
+        int systemType = request.getIntHeader("System-Type");
+        String clientType = request.getHeader("Client-Type");
+        kwsUserService.updatePasswordForApp(reqVo.getUserId(),
+                reqVo.getOldPassword(), reqVo.getNewPassword(),
+                clientType, systemType, reqVo.getDeviceId());
+        return BaseResult.success("密码修改成功");
+    }
+
+    /**
+     * 切换账号
+     * @param reqVo 切换账号请求对象
+     * @return BaseResult<SwitchAccountResVo>
+     */
+//    @PostMapping("/switchAccount")
+//    @Operation(summary = "切换账号", description = "切换到其他用户账号")
+//    public BaseResult<SwitchAccountResVo> switchAccount(@RequestBody @Valid SwitchAccountReqVo reqVo, HttpServletRequest request) {
+//        return BaseResult.success(kwsUserService.switchAccount(
+//                reqVo.getAccount(),
+//                reqVo.getPassword(),
+//                reqVo.getSystemType(),
+//                request));
+//    }
+
+    /**
+     * 退出登录
+     * @param reqVo 退出登录请求对象
+     * @return BaseResult<String>
+     */
+//    @PostMapping("/logout")
+//    @Operation(summary = "退出登录", description = "用户退出登录,清除登录状态和Token")
+//    public BaseResult<String> logout(@RequestBody @Valid LogoutReqVo reqVo) {
+//        kwsUserService.logout(reqVo.getUserId());
+//        return BaseResult.success("退出登录成功");
+//    }
+
+}
+

+ 21 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/dao/AppVersionsDao.java

@@ -0,0 +1,21 @@
+package com.sckw.system.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.sckw.system.model.AppVersions;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 系统版本管理表
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+@Mapper
+public interface AppVersionsDao extends BaseMapper<AppVersions> {
+
+}
+
+
+
+
+
+

+ 21 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/dao/FeedbacksDao.java

@@ -0,0 +1,21 @@
+package com.sckw.system.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.sckw.system.model.Feedbacks;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 意见反馈表
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+@Mapper
+public interface FeedbacksDao extends BaseMapper<Feedbacks> {
+
+}
+
+
+
+
+
+

+ 21 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/dao/LoadingRecordsDao.java

@@ -0,0 +1,21 @@
+package com.sckw.system.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.sckw.system.model.LoadingRecords;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 装载作业记录表
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+@Mapper
+public interface LoadingRecordsDao extends BaseMapper<LoadingRecords> {
+
+}
+
+
+
+
+
+

+ 21 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/dao/LoginLogsDao.java

@@ -0,0 +1,21 @@
+package com.sckw.system.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.sckw.system.model.LoginLogs;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 登录日志表
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+@Mapper
+public interface LoginLogsDao extends BaseMapper<LoginLogs> {
+
+}
+
+
+
+
+
+

+ 21 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/dao/MaterialsDao.java

@@ -0,0 +1,21 @@
+package com.sckw.system.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.sckw.system.model.Materials;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 物料信息字典表
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+@Mapper
+public interface MaterialsDao extends BaseMapper<Materials> {
+
+}
+
+
+
+
+
+

+ 21 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/dao/SysArticlesDao.java

@@ -0,0 +1,21 @@
+package com.sckw.system.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.sckw.system.model.SysArticles;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 系统内容配置表
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+@Mapper
+public interface SysArticlesDao extends BaseMapper<SysArticles> {
+
+}
+
+
+
+
+
+

+ 76 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/AppVersions.java

@@ -0,0 +1,76 @@
+package com.sckw.system.model;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 系统版本管理表
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+@EqualsAndHashCode(callSuper = false)
+@Data
+@Accessors(chain = true)
+@TableName("app_versions")
+@Builder
+public class AppVersions implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 雪花ID
+     */
+    @TableId(type = IdType.ASSIGN_ID)
+    private Long id;
+
+    /**
+     * 版本号
+     */
+    @TableField("version_code")
+    private String versionCode;
+
+    /**
+     * 平台类型: 1-Android, 2-iOS
+     */
+    @TableField("platform")
+    private Integer platform;
+
+    /**
+     * 更新日志
+     */
+    @TableField("update_log")
+    private String updateLog;
+
+    /**
+     * 安装包下载地址
+     */
+    @TableField("download_url")
+    private String downloadUrl;
+
+    /**
+     * 是否强制更新: 0-否, 1-是
+     */
+    @TableField("is_force_update")
+    private Integer isForceUpdate;
+
+    /**
+     * 创建时间
+     */
+    @TableField("created_at")
+    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdAt;
+
+}
+

+ 74 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/Feedbacks.java

@@ -0,0 +1,74 @@
+package com.sckw.system.model;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 意见反馈表
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+@EqualsAndHashCode(callSuper = false)
+@Data
+@Accessors(chain = true)
+@TableName("feedbacks")
+public class Feedbacks implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 雪花ID
+     */
+    @TableId(type = IdType.ASSIGN_ID)
+    private Long id;
+
+    /**
+     * 反馈人ID
+     */
+    @TableField("user_id")
+    private Long userId;
+
+    /**
+     * 反馈正文
+     */
+    @TableField("content")
+    private String content;
+
+    /**
+     * 图片附件地址(JSON格式)
+     */
+    @TableField("images")
+    private String images;
+
+    /**
+     * 联系方式
+     */
+    @TableField("contact_info")
+    private String contactInfo;
+
+    /**
+     * 是否已处理: 0-未处理, 1-已处理
+     */
+    @TableField("is_processed")
+    private Integer isProcessed;
+
+    /**
+     * 创建时间
+     */
+    @TableField("created_at")
+    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdAt;
+
+}
+

+ 88 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/LoadingRecords.java

@@ -0,0 +1,88 @@
+package com.sckw.system.model;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 装载作业记录表
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+@EqualsAndHashCode(callSuper = false)
+@Data
+@Accessors(chain = true)
+@TableName("loading_records")
+public class LoadingRecords implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 雪花ID
+     */
+    @TableId(type = IdType.ASSIGN_ID)
+    private Long id;
+
+    /**
+     * 操作员ID
+     */
+    @TableField("operator_id")
+    private Long operatorId;
+
+    /**
+     * 车牌号
+     */
+    @TableField("license_plate")
+    private String licensePlate;
+
+    /**
+     * 客户名称
+     */
+    @TableField("customer_name")
+    private String customerName;
+
+    /**
+     * 装载类型: 1-装载, 2-补货
+     */
+    @TableField("load_type")
+    private Integer loadType;
+
+    /**
+     * 物料ID
+     */
+    @TableField("material_id")
+    private Long materialId;
+
+    /**
+     * 装载数量/重量
+     */
+    @TableField("quantity")
+    private BigDecimal quantity;
+
+    /**
+     * 作业日期
+     */
+    @TableField("work_date")
+    @JsonFormat(pattern="yyyy-MM-dd", timezone = "GMT+8")
+    private Date workDate;
+
+    /**
+     * 记录创建时间
+     */
+    @TableField("created_at")
+    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdAt;
+
+}
+

+ 68 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/LoginLogs.java

@@ -0,0 +1,68 @@
+package com.sckw.system.model;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 登录日志表
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+@EqualsAndHashCode(callSuper = false)
+@Data
+@Accessors(chain = true)
+@TableName("login_logs")
+public class LoginLogs implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 雪花ID
+     */
+    @TableId(type = IdType.ASSIGN_ID)
+    private Long id;
+
+    /**
+     * 关联用户ID
+     */
+    @TableField("user_id")
+    private Long userId;
+
+    /**
+     * 设备型号/标识
+     */
+    @TableField("device_info")
+    private String deviceInfo;
+
+    /**
+     * 登录IP
+     */
+    @TableField("login_ip")
+    private String loginIp;
+
+    /**
+     * 登录时间
+     */
+    @TableField("login_time")
+    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date loginTime;
+
+    /**
+     * 登录类型: 1-首次登录, 2-切换账号登录
+     */
+    @TableField("login_type")
+    private Integer loginType;
+
+}
+

+ 53 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/Materials.java

@@ -0,0 +1,53 @@
+package com.sckw.system.model;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 物料信息字典表
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+@EqualsAndHashCode(callSuper = false)
+@Data
+@Accessors(chain = true)
+@TableName("materials")
+public class Materials implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 雪花ID
+     */
+    @TableId(type = IdType.ASSIGN_ID)
+    private Long id;
+
+    /**
+     * 物料名称
+     */
+    @TableField("material_name")
+    private String materialName;
+
+    /**
+     * 物料分类
+     */
+    @TableField("category")
+    private String category;
+
+    /**
+     * 计量单位
+     */
+    @TableField("unit")
+    private String unit;
+
+}
+

+ 68 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/SysArticles.java

@@ -0,0 +1,68 @@
+package com.sckw.system.model;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 系统内容配置表
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+@EqualsAndHashCode(callSuper = false)
+@Data
+@Accessors(chain = true)
+@TableName("sys_articles")
+public class SysArticles implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 雪花ID
+     */
+    @TableId(type = IdType.ASSIGN_ID)
+    private Long id;
+
+    /**
+     * 条款唯一标识 (如: REG_AGREEMENT, PRIVACY_POLICY)
+     */
+    @TableField("article_key")
+    private String articleKey;
+
+    /**
+     * 条款标题
+     */
+    @TableField("title")
+    private String title;
+
+    /**
+     * 条款详细内容 (富文本/HTML)
+     */
+    @TableField("content")
+    private String content;
+
+    /**
+     * 版本号
+     */
+    @TableField("version")
+    private String version;
+
+    /**
+     * 更新时间
+     */
+    @TableField("updated_at")
+    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedAt;
+
+}
+

+ 45 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/FeedbackListReqVo.java

@@ -0,0 +1,45 @@
+package com.sckw.system.model.vo.req;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 反馈列表查询请求对象
+ * @author chenxiaofei
+ * @date 2026-01-12
+ */
+@Data
+@Schema(description = "反馈列表查询请求对象")
+public class FeedbackListReqVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 用户ID
+     */
+    @NotNull(message = "用户ID不能为空")
+    @Schema(description = "用户ID", required = true, example = "1234567890")
+    private Long userId;
+
+    /**
+     * 当前页码
+     */
+    @Schema(description = "当前页码", example = "1")
+    private Integer pageNum = 1;
+
+    /**
+     * 每页数量
+     */
+    @Schema(description = "每页数量", example = "10")
+    private Integer pageSize = 10;
+
+}
+
+
+
+

+ 46 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/LoginReqVo.java

@@ -0,0 +1,46 @@
+package com.sckw.system.model.vo.req;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 登录请求对象
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+@Data
+@Schema(description = "登录请求对象")
+public class LoginReqVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 账号(手机号)
+     */
+    @NotBlank(message = "账号不能为空")
+    @Schema(description = "账号(手机号)", required = true, example = "13800138000")
+    private String account;
+
+    /**
+     * 密码
+     */
+    @NotBlank(message = "密码不能为空")
+    @Schema(description = "密码", required = true, example = "123456")
+    private String password;
+
+    /**
+     * 系统类型: 1-运营端, 2-企业端
+     */
+    @NotNull(message = "系统类型不能为空")
+    @Schema(description = "系统类型: 1-运营端, 2-企业端", required = true, example = "2")
+    private Integer systemType;
+
+}
+
+

+ 34 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/LogoutReqVo.java

@@ -0,0 +1,34 @@
+package com.sckw.system.model.vo.req;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 退出登录请求对象
+ * @author chenxiaofei
+ * @date 2026-01-08
+ */
+@Data
+@Schema(description = "退出登录请求对象")
+public class LogoutReqVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 用户ID
+     */
+    @NotNull(message = "用户ID不能为空")
+    @Schema(description = "用户ID", required = true, example = "1234567890")
+    private Long userId;
+
+}
+
+
+
+
+

+ 54 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/SubmitFeedbackReqVo.java

@@ -0,0 +1,54 @@
+package com.sckw.system.model.vo.req;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 提交意见反馈请求对象
+ * @author chenxiaofei
+ * @date 2026-01-12
+ */
+@Data
+@Schema(description = "提交意见反馈请求对象")
+public class SubmitFeedbackReqVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 用户ID
+     */
+    @NotNull(message = "用户ID不能为空")
+    @Schema(description = "用户ID", required = true, example = "1234567890")
+    private Long userId;
+
+    /**
+     * 反馈内容
+     */
+    @NotBlank(message = "反馈内容不能为空")
+    @Schema(description = "反馈内容", required = true, example = "系统操作不流畅,建议优化...")
+    private String content;
+
+    /**
+     * 图片附件地址列表
+     */
+    @Schema(description = "图片附件地址列表", example = "[\"https://example.com/image1.jpg\", \"https://example.com/image2.jpg\"]")
+    private List<String> images;
+
+    /**
+     * 联系方式
+     */
+    @Schema(description = "联系方式", example = "13800138000")
+    private String contactInfo;
+
+}
+
+
+
+

+ 49 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/SwitchAccountReqVo.java

@@ -0,0 +1,49 @@
+package com.sckw.system.model.vo.req;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 切换账号请求对象
+ * @author chenxiaofei
+ * @date 2026-01-08
+ */
+@Data
+@Schema(description = "切换账号请求对象")
+public class SwitchAccountReqVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 账号(手机号)
+     */
+    @NotBlank(message = "账号不能为空")
+    @Schema(description = "账号(手机号)", required = true, example = "13800138000")
+    private String account;
+
+    /**
+     * 密码
+     */
+    @NotBlank(message = "密码不能为空")
+    @Schema(description = "密码", required = true, example = "123456")
+    private String password;
+
+    /**
+     * 系统类型: 1-运营端, 2-企业端
+     */
+    @NotNull(message = "系统类型不能为空")
+    @Schema(description = "系统类型: 1-运营端, 2-企业端", required = true, example = "2")
+    private Integer systemType;
+
+}
+
+
+
+
+

+ 18 - 14
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/UpdatePasswordReqVo.java

@@ -1,5 +1,6 @@
 package com.sckw.system.model.vo.req;
 package com.sckw.system.model.vo.req;
 
 
+import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.constraints.NotBlank;
 import jakarta.validation.constraints.NotBlank;
 import jakarta.validation.constraints.NotNull;
 import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 import lombok.Data;
@@ -8,38 +9,41 @@ import java.io.Serial;
 import java.io.Serializable;
 import java.io.Serializable;
 
 
 /**
 /**
- * @desc: 修改密码入参
- * @author: czh
- * @date: 2023/6/21
+ * 修改密码请求对象
+ * @author chenxiaofei
+ * @date 2026-01-08
  */
  */
 @Data
 @Data
+@Schema(description = "修改密码请求对象")
 public class UpdatePasswordReqVo implements Serializable {
 public class UpdatePasswordReqVo implements Serializable {
 
 
     @Serial
     @Serial
-    private static final long serialVersionUID = 4911945623876338313L;
+    private static final long serialVersionUID = 1L;
 
 
     /**
     /**
-     * 账号
+     * 用户ID
      */
      */
-    @NotBlank(message = "账号不能为空")
-    private String account;
+    @NotNull(message = "用户ID不能为空")
+    @Schema(description = "用户ID", required = true, example = "1234567890")
+    private Long userId;
 
 
     /**
     /**
      * 旧密码
      * 旧密码
      */
      */
-    @NotBlank(message = "密码不能为空")
-    private String password;
+    @NotBlank(message = "旧密码不能为空")
+    @Schema(description = "旧密码", required = true, example = "Old123456")
+    private String oldPassword;
 
 
     /**
     /**
      * 新密码
      * 新密码
      */
      */
     @NotBlank(message = "新密码不能为空")
     @NotBlank(message = "新密码不能为空")
+    @Schema(description = "新密码", required = true, example = "New123456")
     private String newPassword;
     private String newPassword;
-
     /**
     /**
-     * 系统类型(1运营端、2企业开户)
+     * 设备id
      */
      */
-    @NotNull(message = "系统类型不能为空")
-    private int systemType;
-
+    @NotBlank(message = "设备id不能为空")
+    @Schema(description = "设备id", required = true, example = "1234567890")
+    private String deviceId;
 }
 }

+ 32 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/UserInfoReqVo.java

@@ -0,0 +1,32 @@
+package com.sckw.system.model.vo.req;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 用户信息查询请求对象
+ * @author chenxiaofei
+ * @date 2026-01-08
+ */
+@Data
+@Schema(description = "用户信息查询请求对象")
+public class UserInfoReqVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 用户ID
+     */
+    @Schema(description = "用户ID", required = true, example = "1234567890")
+    private Long userId;
+
+}
+
+
+
+
+

+ 95 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/UsersReq.java

@@ -0,0 +1,95 @@
+package com.sckw.system.model.vo.req;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 用户基础信息表
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+@EqualsAndHashCode(callSuper = false)
+@Data
+@Accessors(chain = true)
+@TableName("users")
+@Schema(description = "用户基础信息")
+public class UsersReq implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 雪花ID
+     */
+    @Schema(description = "雪花ID")
+    private Long id;
+
+    /**
+     * 登录账号
+     */
+    @Schema(description = "登录账号")
+    private String username;
+
+    /**
+     * 加密存储的密码
+     */
+    @Schema(description = "加密存储的密码")
+    private String passwordHash;
+
+    /**
+     * 用户姓名
+     */
+    @Schema(description = "用户姓名")
+    private String fullName;
+
+    /**
+     * 头像URL地址
+     */
+    @Schema(description = "头像URL地址")
+    private String avatarUrl;
+
+    /**
+     * 联系电话
+     */
+    @Schema(description = "联系电话")
+    private String phone;
+
+    /**
+     * 角色: 1-铲车司机(监管员), 2-门卫
+     */
+    @Schema(description = "角色: 1-铲车司机(监管员), 2-门卫")
+    private Integer roleType;
+
+    /**
+     * 状态: 0-禁用, 1-启用
+     */
+    @Schema(description = "状态: 0-禁用, 1-启用")
+    private Integer status;
+
+    /**
+     * 注册时间
+     */
+    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @Schema(description = "注册时间")
+    private Date createdAt;
+
+    /**
+     * 更新时间
+     */
+    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @Schema(description = "更新时间")
+    private Date updatedAt;
+
+}
+

+ 81 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/AppVersionsResVo.java

@@ -0,0 +1,81 @@
+package com.sckw.system.model.vo.res;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.sckw.core.utils.LongToStringUtils;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 系统版本管理返回对象
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Schema(description = "系统版本管理返回对象")
+public class AppVersionsResVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 雪花ID
+     */
+    @JsonSerialize(using = LongToStringUtils.class)
+    @Schema(description = "雪花ID")
+    private Long id;
+
+    /**
+     * 版本号
+     */
+    @Schema(description = "版本号")
+    private String versionCode;
+
+    /**
+     * 平台类型: 1-Android, 2-iOS
+     */
+    @Schema(description = "平台类型: 1-Android, 2-iOS")
+    private Integer platform;
+
+    /**
+     * 更新日志
+     */
+    @Schema(description = "更新日志")
+    private String updateLog;
+
+    /**
+     * 安装包下载地址
+     */
+    @Schema(description = "安装包下载地址")
+    private String downloadUrl;
+
+    /**
+     * 是否强制更新: 0-否, 1-是
+     */
+    @Schema(description = "是否强制更新: 0-否, 1-是")
+    private Integer isForceUpdate;
+
+    /**
+     * 创建时间
+     */
+    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @Schema(description = "创建时间")
+    private Date createdAt;
+
+}
+
+
+
+
+
+

+ 87 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/FeedbackResVo.java

@@ -0,0 +1,87 @@
+package com.sckw.system.model.vo.res;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.sckw.core.utils.LongToStringUtils;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 意见反馈响应对象
+ * @author chenxiaofei
+ * @date 2026-01-12
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Schema(description = "意见反馈响应对象")
+public class FeedbackResVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 反馈ID
+     */
+    @JsonSerialize(using = LongToStringUtils.class)
+    @Schema(description = "反馈ID", example = "1234567890")
+    private Long id;
+
+    /**
+     * 用户ID
+     */
+    @JsonSerialize(using = LongToStringUtils.class)
+    @Schema(description = "用户ID", example = "1234567890")
+    private Long userId;
+
+    /**
+     * 反馈内容
+     */
+    @Schema(description = "反馈内容", example = "系统操作不流畅,建议优化...")
+    private String content;
+
+    /**
+     * 图片附件地址列表
+     */
+    @Schema(description = "图片附件地址列表")
+    private List<String> images;
+
+    /**
+     * 联系方式
+     */
+    @Schema(description = "联系方式", example = "13800138000")
+    private String contactInfo;
+
+    /**
+     * 是否已处理: 0-未处理, 1-已处理
+     */
+    @Schema(description = "是否已处理: 0-未处理, 1-已处理", example = "0")
+    private Integer isProcessed;
+
+    /**
+     * 处理状态描述
+     */
+    @Schema(description = "处理状态描述", example = "未处理")
+    private String processedDesc;
+
+    /**
+     * 创建时间
+     */
+    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @Schema(description = "创建时间", example = "2026-01-12 10:30:00")
+    private Date createdAt;
+
+}
+
+
+
+

+ 93 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/LoadingRecordsResVo.java

@@ -0,0 +1,93 @@
+package com.sckw.system.model.vo.res;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.sckw.core.utils.LongToStringUtils;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 装载作业记录返回对象
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Schema(description = "装载作业记录返回对象")
+public class LoadingRecordsResVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 雪花ID
+     */
+    @JsonSerialize(using = LongToStringUtils.class)
+    @Schema(description = "雪花ID")
+    private Long id;
+
+    /**
+     * 操作员ID
+     */
+    @JsonSerialize(using = LongToStringUtils.class)
+    @Schema(description = "操作员ID")
+    private Long operatorId;
+
+    /**
+     * 车牌号
+     */
+    @Schema(description = "车牌号")
+    private String licensePlate;
+
+    /**
+     * 客户名称
+     */
+    @Schema(description = "客户名称")
+    private String customerName;
+
+    /**
+     * 装载类型: 1-装载, 2-补货
+     */
+    @Schema(description = "装载类型: 1-装载, 2-补货")
+    private Integer loadType;
+
+    /**
+     * 物料ID
+     */
+    @JsonSerialize(using = LongToStringUtils.class)
+    @Schema(description = "物料ID")
+    private Long materialId;
+
+    /**
+     * 装载数量/重量
+     */
+    @Schema(description = "装载数量/重量")
+    private BigDecimal quantity;
+
+    /**
+     * 作业日期
+     */
+    @JsonFormat(pattern="yyyy-MM-dd", timezone = "GMT+8")
+    @Schema(description = "作业日期")
+    private Date workDate;
+
+    /**
+     * 记录创建时间
+     */
+    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @Schema(description = "记录创建时间")
+    private Date createdAt;
+
+}
+
+

+ 96 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/LoginResVo.java

@@ -0,0 +1,96 @@
+package com.sckw.system.model.vo.res;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 登录响应对象
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Schema(description = "登录响应对象")
+public class LoginResVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 用户ID
+     */
+    @Schema(description = "用户ID", example = "1234567890")
+    private Long userId;
+
+    /**
+     * 用户姓名
+     */
+    @Schema(description = "用户姓名", example = "张三")
+    private String name;
+
+    /**
+     * 账号
+     */
+    @Schema(description = "账号", example = "13800138000")
+    private String account;
+
+    /**
+     * 手机号
+     */
+    @Schema(description = "手机号", example = "13800138000")
+    private String phone;
+
+    /**
+     * 头像
+     */
+    @Schema(description = "头像URL", example = "https://example.com/avatar.jpg")
+    private String photo;
+
+    /**
+     * 角色ID
+     */
+    @Schema(description = "角色ID", example = "1")
+    private Long roleId;
+    /**
+     * 角色名称
+     */
+    @Schema(description = "角色名称", example = "管理员")
+    private String roleName;
+
+    /**
+     * 系统类型
+     */
+    @Schema(description = "系统类型: 1-运营端, 2-企业端", example = "2")
+    private Integer systemType;
+    /**
+     * 登录token
+     */
+    @Schema(description = "登录token")
+    private String token;
+    /**
+     * 企业id
+     */
+    @Schema(description = "企业id", example = "1")
+    private Long entId;
+    /**
+     * 企业名称
+     */
+    @Schema(description = "企业名称", example = "企业A")
+    private String enterpriseName;
+    /**
+     * 菜单列表
+     */
+    @Schema(description = "菜单列表")
+    List<KwsMenuResVo> menuList;
+}
+
+

+ 55 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/MaterialsResVo.java

@@ -0,0 +1,55 @@
+package com.sckw.system.model.vo.res;
+
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.sckw.core.utils.LongToStringUtils;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 物料信息返回对象
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Schema(description = "物料信息返回对象")
+public class MaterialsResVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 雪花ID
+     */
+    @Schema(description = "雪花ID")
+    private Long id;
+
+    /**
+     * 物料名称
+     */
+    @Schema(description = "物料名称")
+    private String materialName;
+
+    /**
+     * 物料分类
+     */
+    @Schema(description = "物料分类")
+    private String category;
+
+    /**
+     * 计量单位
+     */
+    @Schema(description = "计量单位")
+    private String unit;
+
+}
+
+

+ 99 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/SwitchAccountResVo.java

@@ -0,0 +1,99 @@
+package com.sckw.system.model.vo.res;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 切换账号响应对象
+ * @author chenxiaofei
+ * @date 2026-01-08
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Schema(description = "切换账号响应对象")
+public class SwitchAccountResVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 用户ID
+     */
+    @Schema(description = "用户ID", example = "1234567890")
+    private Long userId;
+
+    /**
+     * 用户姓名
+     */
+    @Schema(description = "用户姓名", example = "张三")
+    private String name;
+
+    /**
+     * 账号
+     */
+    @Schema(description = "账号", example = "13800138000")
+    private String account;
+
+    /**
+     * 手机号
+     */
+    @Schema(description = "手机号", example = "13800138000")
+    private String phone;
+
+    /**
+     * 头像
+     */
+    @Schema(description = "头像URL", example = "https://example.com/avatar.jpg")
+    private String photo;
+
+    /**
+     * 角色ID
+     */
+    @Schema(description = "角色ID", example = "1")
+    private Long roleId;
+
+    /**
+     * 角色名称
+     */
+    @Schema(description = "角色名称", example = "管理员")
+    private String roleName;
+
+    /**
+     * 系统类型
+     */
+    @Schema(description = "系统类型: 1-运营端, 2-企业端", example = "2")
+    private Integer systemType;
+    /**
+     * 登录Token
+     */
+    private String token;
+    /**
+     * 企业id
+     */
+    @Schema(description = "企业id", example = "1")
+    private Long entId;
+    /**
+     * 企业名称
+     */
+    @Schema(description = "企业名称", example = "企业A")
+    private String enterpriseName;
+    /**
+     * 菜单列表
+     */
+    @Schema(description = "菜单列表")
+    List<KwsMenuResVo> menuList;
+}
+
+
+
+
+

+ 70 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/SysArticlesResVo.java

@@ -0,0 +1,70 @@
+package com.sckw.system.model.vo.res;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.sckw.core.utils.LongToStringUtils;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 系统内容配置返回对象
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Schema(description = "系统内容配置返回对象")
+public class SysArticlesResVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 雪花ID
+     */
+    @Schema(description = "雪花ID", example = "1234567890123456789")
+    private Long id;
+
+    /**
+     * 条款唯一标识 (如: REG_AGREEMENT, PRIVACY_POLICY)
+     */
+    @Schema(description = "条款唯一标识,例如: REG_AGREEMENT、PRIVACY_POLICY", example = "REG_AGREEMENT")
+    private String articleKey;
+
+    /**
+     * 条款标题
+     */
+    @Schema(description = "条款标题", example = "用户注册协议")
+    private String title;
+
+    /**
+     * 条款详细内容 (富文本/HTML)
+     */
+    @Schema(description = "条款详细内容,富文本或HTML格式")
+    private String content;
+
+    /**
+     * 版本号
+     */
+    @Schema(description = "版本号", example = "v1.0")
+    private String version;
+
+    /**
+     * 更新时间
+     */
+    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @Schema(description = "更新时间,格式:yyyy-MM-dd HH:mm:ss", example = "2026-01-07 12:34:56")
+    private Date updatedAt;
+
+}
+

+ 75 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/UserInfoResVo.java

@@ -0,0 +1,75 @@
+package com.sckw.system.model.vo.res;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 用户信息响应对象
+ * @author chenxiaofei
+ * @date 2026-01-08
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Schema(description = "用户信息响应对象")
+public class UserInfoResVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 用户ID
+     */
+    @Schema(description = "用户ID", example = "1234567890")
+    private Long userId;
+
+    /**
+     * 头像URL
+     */
+    @Schema(description = "头像URL", example = "https://example.com/avatar.jpg")
+    private String photo;
+
+    /**
+     * 用户姓名
+     */
+    @Schema(description = "用户姓名", example = "张三")
+    private String name;
+
+    /**
+     * 手机号
+     */
+    @Schema(description = "手机号", example = "13800138000")
+    private String phone;
+
+    /**
+     * 角色名称
+     */
+    @Schema(description = "角色名称", example = "管理员")
+    private String roleName;
+
+    /**
+     * 账号状态: 0-正常, 1-冻结
+     */
+    @Schema(description = "账号状态: 0-正常, 1-冻结", example = "0")
+    private Integer status;
+
+    /**
+     * 账号状态描述
+     */
+    @Schema(description = "账号状态描述", example = "正常")
+    private String statusDesc;
+
+}
+
+
+
+
+

+ 21 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/repository/AppVersionsService.java

@@ -0,0 +1,21 @@
+package com.sckw.system.repository;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.sckw.system.model.AppVersions;
+
+/**
+ * 系统版本管理表Service接口
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+public interface AppVersionsService extends IService<AppVersions> {
+
+    /**
+     * 获取最新版本
+     * @param platform 平台类型: 1-Android, 2-iOS
+     * @return AppVersions
+     */
+    AppVersions getLatestVersion(Integer platform);
+
+}
+

+ 14 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/repository/FeedbacksService.java

@@ -0,0 +1,14 @@
+package com.sckw.system.repository;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.sckw.system.model.Feedbacks;
+
+/**
+ * 意见反馈表Service接口
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+public interface FeedbacksService extends IService<Feedbacks> {
+
+}
+

+ 14 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/repository/LoadingRecordsService.java

@@ -0,0 +1,14 @@
+package com.sckw.system.repository;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.sckw.system.model.LoadingRecords;
+
+/**
+ * 装载作业记录表Service接口
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+public interface LoadingRecordsService extends IService<LoadingRecords> {
+
+}
+

+ 14 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/repository/LoginLogsService.java

@@ -0,0 +1,14 @@
+package com.sckw.system.repository;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.sckw.system.model.LoginLogs;
+
+/**
+ * 登录日志表Service接口
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+public interface LoginLogsService extends IService<LoginLogs> {
+
+}
+

+ 14 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/repository/MaterialsService.java

@@ -0,0 +1,14 @@
+package com.sckw.system.repository;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.sckw.system.model.Materials;
+
+/**
+ * 物料信息字典表Service接口
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+public interface MaterialsService extends IService<Materials> {
+
+}
+

+ 21 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/repository/SysArticlesService.java

@@ -0,0 +1,21 @@
+package com.sckw.system.repository;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.sckw.system.model.SysArticles;
+
+/**
+ * 系统内容配置表Service接口
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+public interface SysArticlesService extends IService<SysArticles> {
+
+    /**
+     * 根据文章标识获取文章
+     * @param articleKey 文章标识
+     * @return SysArticles
+     */
+    SysArticles getByArticleKey(String articleKey);
+
+}
+

+ 27 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/repository/impl/AppVersionsServiceImpl.java

@@ -0,0 +1,27 @@
+package com.sckw.system.repository.impl;
+
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.sckw.system.dao.AppVersionsDao;
+import com.sckw.system.model.AppVersions;
+import com.sckw.system.repository.AppVersionsService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 系统版本管理表Service实现类
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+@Service
+public class AppVersionsServiceImpl extends ServiceImpl<AppVersionsDao, AppVersions> implements AppVersionsService {
+
+    @Override
+    public AppVersions getLatestVersion(Integer platform) {
+        return this.getOne(Wrappers.<AppVersions>lambdaQuery()
+                .eq(AppVersions::getPlatform, platform)
+                .orderByDesc(AppVersions::getCreatedAt)
+                .last("limit 1"));
+    }
+
+}
+

+ 18 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/repository/impl/FeedbacksServiceImpl.java

@@ -0,0 +1,18 @@
+package com.sckw.system.repository.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.sckw.system.dao.FeedbacksDao;
+import com.sckw.system.model.Feedbacks;
+import com.sckw.system.repository.FeedbacksService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 意见反馈表Service实现类
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+@Service
+public class FeedbacksServiceImpl extends ServiceImpl<FeedbacksDao, Feedbacks> implements FeedbacksService {
+
+}
+

+ 18 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/repository/impl/LoadingRecordsServiceImpl.java

@@ -0,0 +1,18 @@
+package com.sckw.system.repository.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.sckw.system.dao.LoadingRecordsDao;
+import com.sckw.system.model.LoadingRecords;
+import com.sckw.system.repository.LoadingRecordsService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 装载作业记录表Service实现类
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+@Service
+public class LoadingRecordsServiceImpl extends ServiceImpl<LoadingRecordsDao, LoadingRecords> implements LoadingRecordsService {
+
+}
+

+ 18 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/repository/impl/LoginLogsServiceImpl.java

@@ -0,0 +1,18 @@
+package com.sckw.system.repository.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.sckw.system.dao.LoginLogsDao;
+import com.sckw.system.model.LoginLogs;
+import com.sckw.system.repository.LoginLogsService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 登录日志表Service实现类
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+@Service
+public class LoginLogsServiceImpl extends ServiceImpl<LoginLogsDao, LoginLogs> implements LoginLogsService {
+
+}
+

+ 18 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/repository/impl/MaterialsServiceImpl.java

@@ -0,0 +1,18 @@
+package com.sckw.system.repository.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.sckw.system.dao.MaterialsDao;
+import com.sckw.system.model.Materials;
+import com.sckw.system.repository.MaterialsService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 物料信息字典表Service实现类
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+@Service
+public class MaterialsServiceImpl extends ServiceImpl<MaterialsDao, Materials> implements MaterialsService {
+
+}
+

+ 25 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/repository/impl/SysArticlesServiceImpl.java

@@ -0,0 +1,25 @@
+package com.sckw.system.repository.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.sckw.system.dao.SysArticlesDao;
+import com.sckw.system.model.SysArticles;
+import com.sckw.system.repository.SysArticlesService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 系统内容配置表Service实现类
+ * @author chenxiaofei
+ * @date 2026-01-07
+ */
+@Service
+public class SysArticlesServiceImpl extends ServiceImpl<SysArticlesDao, SysArticles> implements SysArticlesService {
+
+    @Override
+    public SysArticles getByArticleKey(String articleKey) {
+        return this.getOne(new LambdaQueryWrapper<SysArticles>()
+                .eq(SysArticles::getArticleKey, articleKey));
+    }
+
+}
+

+ 41 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/service/KwsAppVersionsService.java

@@ -0,0 +1,41 @@
+package com.sckw.system.service;
+
+import com.sckw.core.utils.BeanUtils;
+import com.sckw.core.web.response.BaseResult;
+import com.sckw.system.model.AppVersions;
+import com.sckw.system.model.vo.res.AppVersionsResVo;
+import com.sckw.system.repository.AppVersionsService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author :chenXiaoFei
+ * @version :1.0
+ * @description : 版本管理相关接口
+ * @create :2026-01-07 15:50:00
+ */
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class KwsAppVersionsService {
+
+    private final AppVersionsService appVersionsService;
+
+    public AppVersionsResVo getLatestVersion(Integer platform) {
+        log.info("查询最新版本信息,平台类型: {}", platform);
+        AppVersions latestVersion = appVersionsService.getLatestVersion(platform);
+        if (latestVersion == null) {
+            return new AppVersionsResVo();
+        }
+        
+        return AppVersionsResVo.builder()
+                .id(latestVersion.getId())
+                .versionCode(latestVersion.getVersionCode())
+                .platform(latestVersion.getPlatform())
+                .downloadUrl(latestVersion.getDownloadUrl())
+                .isForceUpdate(latestVersion.getIsForceUpdate())
+                .build();
+    }
+}

+ 155 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/service/KwsFeedbackService.java

@@ -0,0 +1,155 @@
+package com.sckw.system.service;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson2.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.sckw.core.exception.SystemException;
+import com.sckw.core.web.constant.HttpStatus;
+import com.sckw.system.model.Feedbacks;
+import com.sckw.system.model.vo.req.FeedbackListReqVo;
+import com.sckw.system.model.vo.req.SubmitFeedbackReqVo;
+import com.sckw.system.model.vo.res.FeedbackResVo;
+import com.sckw.system.repository.FeedbacksService;
+import com.sckw.system.repository.KwsUserRepository;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * 意见反馈业务Service实现类
+ * @author chenxiaofei
+ * @date 2026-01-12
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class KwsFeedbackService {
+
+    private final FeedbacksService feedbacksService;
+    private final KwsUserRepository kwsUserRepository;
+
+    @Transactional(rollbackFor = Exception.class)
+    public FeedbackResVo submitFeedback(SubmitFeedbackReqVo reqVo) {
+        log.info("提交意见反馈,用户ID: {}", reqVo.getUserId());
+
+        // 1. 校验用户是否存在
+        if (Objects.isNull(kwsUserRepository.getById(reqVo.getUserId()))) {
+            throw new SystemException(HttpStatus.QUERY_FAIL_CODE, "用户不存在");
+        }
+
+        // 2. 构建反馈实体
+        Feedbacks feedback = new Feedbacks();
+        feedback.setUserId(reqVo.getUserId());
+        feedback.setContent(reqVo.getContent());
+        
+        // 3. 处理图片列表,转换为JSON字符串
+        if (CollUtil.isNotEmpty(reqVo.getImages())) {
+            feedback.setImages(JSON.toJSONString(reqVo.getImages()));
+        } else {
+            feedback.setImages("[]");
+        }
+        
+        feedback.setContactInfo(StrUtil.isNotBlank(reqVo.getContactInfo()) ? reqVo.getContactInfo() : "");
+        // 默认未处理
+        feedback.setIsProcessed(0);
+        feedback.setCreatedAt(new Date());
+
+        // 4. 保存到数据库
+        if (!feedbacksService.save(feedback)) {
+            throw new SystemException(HttpStatus.CRUD_FAIL_CODE, "提交反馈失败");
+        }
+
+        log.info("意见反馈提交成功,反馈ID: {}", feedback.getId());
+
+        // 5. 返回响应对象
+        return convertToResVo(feedback);
+    }
+
+
+    public Page<FeedbackResVo> getFeedbackList(FeedbackListReqVo reqVo) {
+        log.info("查询用户反馈列表,用户ID: {}, 页码: {}, 每页数量: {}", 
+                reqVo.getUserId(), reqVo.getPageNum(), reqVo.getPageSize());
+
+        // 1. 校验用户是否存在
+        if (Objects.isNull(kwsUserRepository.getById(reqVo.getUserId()))) {
+            throw new SystemException(HttpStatus.QUERY_FAIL_CODE, "用户不存在");
+        }
+
+        // 2. 构建分页查询条件
+        Page<Feedbacks> page = new Page<>(reqVo.getPageNum(), reqVo.getPageSize());
+        LambdaQueryWrapper<Feedbacks> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(Feedbacks::getUserId, reqVo.getUserId())
+                    .orderByDesc(Feedbacks::getCreatedAt);
+
+        // 3. 执行查询
+        Page<Feedbacks> feedbackPage = feedbacksService.page(page, queryWrapper);
+
+        // 4. 转换为响应对象
+        Page<FeedbackResVo> resPage = new Page<>(feedbackPage.getCurrent(), feedbackPage.getSize(), feedbackPage.getTotal());
+        List<FeedbackResVo> resVoList = feedbackPage.getRecords().stream()
+                .map(this::convertToResVo)
+                .collect(Collectors.toList());
+        resPage.setRecords(resVoList);
+
+        log.info("查询反馈列表成功,共 {} 条记录", resPage.getTotal());
+        return resPage;
+    }
+
+
+    public FeedbackResVo getFeedbackDetail(Long feedbackId) {
+        log.info("查询反馈详情,反馈ID: {}", feedbackId);
+
+        // 1. 根据ID查询反馈
+        Feedbacks feedback = feedbacksService.getById(feedbackId);
+        if (Objects.isNull(feedback)) {
+            throw new SystemException(HttpStatus.QUERY_FAIL_CODE, "反馈记录不存在");
+        }
+
+        // 2. 转换为响应对象
+        return convertToResVo(feedback);
+    }
+
+    /**
+     * 将实体对象转换为响应对象
+     * @param feedback 反馈实体
+     * @return FeedbackResVo
+     */
+    private FeedbackResVo convertToResVo(Feedbacks feedback) {
+        // 解析图片JSON字符串为列表
+        List<String> imageList = new ArrayList<>();
+        if (StrUtil.isNotBlank(feedback.getImages())) {
+            try {
+                imageList = JSON.parseArray(feedback.getImages(), String.class);
+            } catch (Exception e) {
+                log.warn("解析图片JSON失败,反馈ID: {}, images: {}", feedback.getId(), feedback.getImages(), e);
+                imageList = new ArrayList<>();
+            }
+        }
+
+        // 处理状态描述
+        String processedDesc = feedback.getIsProcessed() == 1 ? "已处理" : "未处理";
+
+        return FeedbackResVo.builder()
+                .id(feedback.getId())
+                .userId(feedback.getUserId())
+                .content(feedback.getContent())
+                .images(imageList)
+                .contactInfo(feedback.getContactInfo())
+                .isProcessed(feedback.getIsProcessed())
+                .processedDesc(processedDesc)
+                .createdAt(feedback.getCreatedAt())
+                .build();
+    }
+
+}
+
+

+ 69 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/service/KwsSysArticlesService.java

@@ -0,0 +1,69 @@
+package com.sckw.system.service;
+
+import com.sckw.core.utils.BeanUtils;
+import com.sckw.core.web.response.BaseResult;
+import com.sckw.system.model.SysArticles;
+import com.sckw.system.model.vo.res.SysArticlesResVo;
+import com.sckw.system.repository.SysArticlesService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author :chenXiaoFei
+ * @version :1.0
+ * @description : 系统文件相关接口
+ * @create :2026-01-07 16:41:00
+ */
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class KwsSysArticlesService {
+    private final SysArticlesService sysArticlesService;
+    public SysArticlesResVo getPrivacyPolicy(String privacyPolicy) {
+        SysArticles article = sysArticlesService.getByArticleKey(privacyPolicy);
+        if (article == null) {
+            log.error("隐私协议内容不存在");
+            return new SysArticlesResVo();
+        }
+        return SysArticlesResVo.builder()
+                .id(article.getId())
+                .articleKey(article.getArticleKey())
+                .title(article.getTitle())
+                .content(article.getContent())
+                .version(article.getVersion())
+                .build();
+    }
+
+    public SysArticlesResVo getServiceAgreement(String regAgreement) {
+        SysArticles article = sysArticlesService.getByArticleKey(regAgreement);
+        if (article == null) {
+            log.error("服务协议内容不存在");
+            return new SysArticlesResVo();
+        }
+       return SysArticlesResVo.builder()
+                .id(article.getId())
+                .articleKey(article.getArticleKey())
+                .title(article.getTitle())
+                .content(article.getContent())
+                .version(article.getVersion())
+                .build();
+    }
+
+    public SysArticlesResVo getByArticleKey(String articleKey) {
+        SysArticles article = sysArticlesService.getByArticleKey(articleKey);
+
+        if (article == null) {
+            log.error("文章内容不存在");
+            return new SysArticlesResVo();
+        }
+
+        return SysArticlesResVo.builder()
+                .id(article.getId())
+                .articleKey(article.getArticleKey())
+                .title(article.getTitle())
+                .content(article.getContent())
+                .version(article.getVersion())
+                .build();
+    }
+}

+ 479 - 3
sckw-modules/sckw-system/src/main/java/com/sckw/system/service/KwsUserService.java

@@ -1,6 +1,8 @@
 package com.sckw.system.service;
 package com.sckw.system.service;
 
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.http.useragent.UserAgent;
+import cn.hutool.http.useragent.UserAgentUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSON;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.sckw.core.common.enums.enums.DictEnum;
 import com.sckw.core.common.enums.enums.DictEnum;
@@ -12,33 +14,39 @@ import com.sckw.core.model.enums.ClientTypeEnum;
 import com.sckw.core.model.enums.SystemTypeEnum;
 import com.sckw.core.model.enums.SystemTypeEnum;
 import com.sckw.core.utils.*;
 import com.sckw.core.utils.*;
 import com.sckw.core.web.constant.HttpStatus;
 import com.sckw.core.web.constant.HttpStatus;
+import com.sckw.core.web.constant.RequestConstant;
 import com.sckw.core.web.context.LoginUserHolder;
 import com.sckw.core.web.context.LoginUserHolder;
+import com.sckw.core.web.model.LoginUserInfo;
+import com.sckw.core.web.request.RequestUtil;
 import com.sckw.excel.utils.ExcelUtil;
 import com.sckw.excel.utils.ExcelUtil;
 import com.sckw.redis.constant.RedisConstant;
 import com.sckw.redis.constant.RedisConstant;
 import com.sckw.redis.utils.RedissonUtils;
 import com.sckw.redis.utils.RedissonUtils;
 import com.sckw.system.api.model.dto.req.RegisterReqDto;
 import com.sckw.system.api.model.dto.req.RegisterReqDto;
 import com.sckw.system.api.model.dto.req.UpdatePasswordReqDto;
 import com.sckw.system.api.model.dto.req.UpdatePasswordReqDto;
 import com.sckw.system.api.model.dto.res.AreaTreeFrontResDto;
 import com.sckw.system.api.model.dto.res.AreaTreeFrontResDto;
+import com.sckw.system.api.model.dto.res.EntCacheResDto;
 import com.sckw.system.api.model.dto.res.KwsUserResDto;
 import com.sckw.system.api.model.dto.res.KwsUserResDto;
 import com.sckw.system.api.model.dto.res.RegisterResDto;
 import com.sckw.system.api.model.dto.res.RegisterResDto;
 import com.sckw.system.api.model.dto.res.SysDictResDto;
 import com.sckw.system.api.model.dto.res.SysDictResDto;
 import com.sckw.system.dao.*;
 import com.sckw.system.dao.*;
 import com.sckw.system.dubbo.RemoteSystemServiceImpl;
 import com.sckw.system.dubbo.RemoteSystemServiceImpl;
 import com.sckw.system.model.*;
 import com.sckw.system.model.*;
+import com.sckw.system.model.LoginLogs;
 import com.sckw.system.api.model.dto.res.RoleInfoDto;
 import com.sckw.system.api.model.dto.res.RoleInfoDto;
 import com.sckw.system.model.report.KwsUserExcel;
 import com.sckw.system.model.report.KwsUserExcel;
 import com.sckw.system.model.vo.req.*;
 import com.sckw.system.model.vo.req.*;
-import com.sckw.system.model.vo.res.KwsUserResVo;
-import com.sckw.system.model.vo.res.KwsUserSystemTypeVo;
-import com.sckw.system.model.vo.res.SalesResp;
+import com.sckw.system.model.vo.res.*;
 import com.sckw.system.repository.KwsRoleRepository;
 import com.sckw.system.repository.KwsRoleRepository;
 import com.sckw.system.repository.KwsUserRepository;
 import com.sckw.system.repository.KwsUserRepository;
 import com.sckw.system.repository.KwsUserRoleRepository;
 import com.sckw.system.repository.KwsUserRoleRepository;
+import com.sckw.system.repository.LoginLogsService;
 import com.sckw.transport.api.dubbo.TransportRemoteStatisticsService;
 import com.sckw.transport.api.dubbo.TransportRemoteStatisticsService;
+import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.servlet.http.HttpServletResponse;
 import lombok.RequiredArgsConstructor;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.dubbo.config.annotation.DubboReference;
 import org.apache.dubbo.config.annotation.DubboReference;
+import org.jetbrains.annotations.NotNull;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.annotation.Transactional;
@@ -96,6 +104,8 @@ public class KwsUserService {
 
 
     private final KwsUserRepository kwsUserRepository;
     private final KwsUserRepository kwsUserRepository;
 
 
+    private final LoginLogsService loginLogsService;
+
     @DubboReference(version = "1.0.0", group = "design", check = false)
     @DubboReference(version = "1.0.0", group = "design", check = false)
     private TransportRemoteStatisticsService transportStatisticsService;
     private TransportRemoteStatisticsService transportStatisticsService;
 
 
@@ -816,4 +826,470 @@ public class KwsUserService {
         return kwsUserResVo;
         return kwsUserResVo;
 
 
     }
     }
+
+    /**
+     * 根据用户ID查询用户信息(头像、姓名、电话、角色、账号状态)
+     *
+     * @param userId 用户ID
+     * @return UserInfoResVo
+     */
+    public UserInfoResVo getUserInfoById(Long userId) {
+        log.info("查询用户信息,用户ID: {}", userId);
+        
+        // 1. 查询用户基本信息
+        KwsUser kwsUser = kwsUserRepository.getById(userId);
+        if (Objects.isNull(kwsUser) || kwsUser.getDelFlag() != Global.NO) {
+            throw new SystemException(HttpStatus.QUERY_FAIL_CODE, "用户不存在或已删除");
+        }
+
+        // 2. 查询用户角色信息
+        List<RoleInfoDto> roleInfoList = kwsUserRoleDao.queryRoleList(userId);
+        String roleName = "";
+        if (CollUtil.isNotEmpty(roleInfoList)) {
+            // 获取第一个角色名称,如有多个角色可用逗号分隔
+            roleName = roleInfoList.stream()
+                    .map(RoleInfoDto::getRoleName)
+                    .collect(Collectors.joining(","));
+        }
+
+        // 3. 组装返回结果
+        String statusDesc = kwsUser.getStatus() != null && kwsUser.getStatus() == Global.YES ? "锁定" : "正常";
+        
+        return UserInfoResVo.builder()
+                .userId(kwsUser.getId())
+                .photo(kwsUser.getPhoto())
+                .name(kwsUser.getName())
+                .phone(kwsUser.getPhone())
+                .roleName(roleName)
+                .status(kwsUser.getStatus())
+                .statusDesc(statusDesc)
+                .build();
+    }
+
+    /**
+     * 修改密码(叉车APP)
+     *
+     * @param userId 用户ID
+     * @param oldPassword 旧密码
+     * @param newPassword 新密码
+     */
+    public void updatePasswordForApp(Long userId, String oldPassword, String newPassword,
+                                     String clientType, Integer systemType, String deviceId) {
+        log.info("修改密码,用户ID: {}", userId);
+        
+        // 1. 查询用户信息
+        KwsUser kwsUser = checkUserBase(userId);
+
+        // 2. 校验旧密码
+        String account = kwsUser.getAccount();
+        String currentPwd = kwsUser.getPassword();
+        String salt = kwsUser.getSalt();
+        oldPassword =  PasswordUtils.md5(oldPassword);
+        checkPassword(account, oldPassword, currentPwd, salt);
+
+        // 3. 校验新密码是否与旧密码相同
+        if (PasswordUtils.validatePassword(kwsUser.getAccount() + newPassword, currentPwd, kwsUser.getSalt())) {
+            throw new SystemException(HttpStatus.CODE_10301, "新密码不能与旧密码相同");
+        }
+
+        // 4. 修改密码
+        updatePwd(newPassword, kwsUser);
+        // 5. 清除当前登录用户的token缓存
+        clearCurrentUserToken(deviceId,kwsUser);
+        log.info("密码修改成功,用户ID: {}", userId);
+    }
+    private void clearCurrentUserToken(String deviceId, KwsUser kwsUser) {
+        try {
+            if (kwsUser.getId()!= null) {
+                for (ClientTypeEnum clientTypeEnum : ClientTypeEnum.values()) {
+                    // 清除当前用户的refreshToken
+                    String refreshTokenKey = Global.getFullRefreshTokenKey(clientTypeEnum.getValue(), kwsUser.getId(), deviceId);
+                    RedissonUtils.delete(refreshTokenKey);
+                }
+
+                for (SystemTypeEnum systemTypeEnum : SystemTypeEnum.values()) {
+                    // 清除当前用户的登录信息缓存
+                    String loginKey = Global.getFullUserLoginKey(systemTypeEnum.getCode(), kwsUser.getId());
+                    RedissonUtils.delete(loginKey);
+                }
+                log.info("已清除当前用户token缓存,userId:{}", kwsUser.getId());
+            }
+        } catch (Exception e) {
+            log.warn("清除当前用户token缓存失败", e);
+        }
+    }
+    /**
+     * 用户登录(叉车APP)
+     *
+     * @param account 账号
+     * @param password 密码
+     * @param systemType 系统类型
+     * @param request HTTP请求(用于记录登录日志)
+     * @return LoginResVo
+     */
+    public LoginResVo login(String account, String password, Integer systemType, HttpServletRequest request) {
+        log.info("用户登录,账号: {}, 系统类型: {}", account, systemType);
+
+        // 1. 查询用户信息
+        KwsUser kwsUser = getKwsUser(account, systemType);
+        password= PasswordUtils.md5(password);
+        // 3. 校验密码
+        checkPassword(account, password, kwsUser.getPassword(), kwsUser.getSalt());
+
+        // 4. 查询用户角色
+        List<RoleInfoDto> roleInfoList = kwsUserRoleDao.queryRoleList(kwsUser.getId());
+        if (CollUtil.isEmpty(roleInfoList)){
+            throw new SystemException(HttpStatus.CODE_60603, "该用户没有分配角色");
+        }
+        List<String> roleNames = Arrays.asList("铲车司机", "门卫");
+        roleInfoList = roleInfoList.stream().filter(x -> roleNames.contains(x.getRoleName()))
+                .collect(Collectors.toList());
+        if (CollUtil.isEmpty(roleInfoList)) {
+            throw new SystemException(HttpStatus.CODE_60603, "你没有登录该系统权限");
+        }
+
+        String roleName = roleInfoList.stream()
+                .map(RoleInfoDto::getRoleName)
+                .collect(Collectors.joining(","));
+        Long roleId = roleInfoList.get(0).getRoleId();
+
+        // 5. 获取客户端类型
+        String clientType = request != null ? request.getHeader(RequestConstant.CLIENT_TYPE) : null;
+        if (StringUtils.isBlank(clientType)) {
+            clientType = ClientTypeEnum.app.getValue(); // 默认使用app
+        }
+
+        // 6. 生成token并缓存
+
+        String token = generateToken(kwsUser.getId(), account, clientType, systemType);
+        log.info("生成token成功,用户ID: {}, token: {}", kwsUser.getId(), token);
+        // 7. 缓存用户登录信息
+        saveUserToCache(kwsUser, systemType, clientType, roleId);
+
+        // 8. 缓存企业信息
+        KwsEnterprise kwsEnterprise = null;
+        if ( kwsUser.getEntId() != null) {
+            kwsEnterprise = saveEntToCache(kwsUser.getEntId());
+        }
+
+        // 9. 缓存菜单权限
+        List<KwsMenuResVo> kwsMenuResVos  = saveMenusToCache(kwsUser.getId(), systemType, roleId);
+
+        // 10. 组装返回结果
+        LoginResVo result = LoginResVo.builder()
+                .userId(kwsUser.getId())
+                .name(kwsUser.getName())
+                .account(kwsUser.getAccount())
+                .phone(kwsUser.getPhone())
+                .photo(kwsUser.getPhoto())
+                .roleId(roleId)
+                .roleName(roleName)
+                .token(token)
+                .entId(kwsUser.getEntId())
+                .enterpriseName(Optional.ofNullable(kwsEnterprise).map(KwsEnterprise::getFirmName).orElse(""))
+                .menuList(kwsMenuResVos)
+                .systemType(kwsUser.getSystemType())
+                .build();
+
+        // 11. 记录登录日志(首次登录)
+        if (request != null) {
+            saveLoginLogs(kwsUser.getId(), request, 1);
+        }
+
+        log.info("登录成功,用户ID: {}", kwsUser.getId());
+        return result;
+    }
+
+    /**
+     * 切换账号(叉车APP)
+     *
+     * @param account 账号
+     * @param password 密码
+     * @param systemType 系统类型
+     * @param request HTTP请求(可选,用于记录登录日志)
+     * @return SwitchAccountResVo
+     */
+    public SwitchAccountResVo switchAccount(String account, String password, Integer systemType, HttpServletRequest request) {
+        log.info("切换账号,账号: {}, 系统类型: {}", account, systemType);
+        
+        // 1. 查询用户信息
+        KwsUser kwsUser = getKwsUser(account, systemType);
+
+        // 3. 校验密码
+        password = PasswordUtils.md5(password);
+        checkPassword(account, password, kwsUser.getPassword(), kwsUser.getSalt());
+
+        // 4. 查询用户角色
+        List<RoleInfoDto> roleInfoList = kwsUserRoleDao.queryRoleList(kwsUser.getId());
+        if (CollUtil.isEmpty(roleInfoList)){
+            throw new SystemException(HttpStatus.CODE_60603, "该用户没有分配角色");
+        }
+        List<String> roleNames = Arrays.asList("铲车司机", "门卫");
+        roleInfoList = roleInfoList.stream().filter(x -> roleNames.contains(x.getRoleName()))
+                .collect(Collectors.toList());
+        if (CollUtil.isEmpty(roleInfoList)) {
+            throw new SystemException(HttpStatus.CODE_60603, "你没有登录该系统权限");
+        }
+
+        String roleName = roleInfoList.stream()
+                .map(RoleInfoDto::getRoleName)
+                .collect(Collectors.joining(","));
+        Long roleId = roleInfoList.get(0).getRoleId();
+
+
+        // 5. 获取客户端类型
+        String clientType = request != null ? request.getHeader(RequestConstant.CLIENT_TYPE) : null;
+        if (StringUtils.isBlank(clientType)) {
+            // 默认使用app
+            clientType = ClientTypeEnum.app.getValue();
+        }
+
+        // 6. 生成token并缓存
+        String token = generateToken(kwsUser.getId(), account, clientType, systemType);
+        log.info("生成token成功,用户ID: {}, token: {}", kwsUser.getId(), token);
+
+        // 7. 缓存用户登录信息
+        saveUserToCache(kwsUser, systemType, clientType, roleId);
+
+        // 8. 缓存企业信息
+        KwsEnterprise kwsEnterprise = null;
+        if ( kwsUser.getEntId() != null) {
+            kwsEnterprise = saveEntToCache(kwsUser.getEntId());
+        }
+
+        // 9. 缓存菜单权限
+        List<KwsMenuResVo> kwsMenuResVos = saveMenusToCache(kwsUser.getId(), systemType, roleId);
+
+        // 10. 组装返回结果
+        SwitchAccountResVo result = SwitchAccountResVo.builder()
+                .userId(kwsUser.getId())
+                .name(kwsUser.getName())
+                .account(kwsUser.getAccount())
+                .phone(kwsUser.getPhone())
+                .photo(kwsUser.getPhoto())
+                .roleId(roleId)
+                .roleName(roleName)
+                .token(token)
+                .entId(kwsUser.getEntId())
+                .enterpriseName(Optional.ofNullable(kwsEnterprise).map(KwsEnterprise::getFirmName).orElse(""))
+                .menuList(kwsMenuResVos)
+                .systemType(kwsUser.getSystemType())
+                .build();
+
+        // 11. 记录切换账号登录日志
+        if (request != null) {
+            saveLoginLogs(kwsUser.getId(), request, 2);
+        }
+
+        log.info("切换账号成功,用户ID: {}", kwsUser.getId());
+        return result;
+    }
+
+    @NotNull
+    private KwsUser getKwsUser(String account, Integer systemType) {
+        KwsUser kwsUser = getUserByAccount(account, systemType);
+        if (Objects.isNull(kwsUser)) {
+            throw new SystemException(HttpStatus.QUERY_FAIL_CODE, "账号不存在");
+        }
+
+        // 2. 检查用户状态
+        if (kwsUser.getStatus() != null && kwsUser.getStatus() == Global.YES) {
+            throw new SystemException(HttpStatus.CODE_10301, "账号已被锁定");
+        }
+        return kwsUser;
+    }
+
+    /**
+     * 退出登录(叉车APP)
+     *
+     * @param userId 用户ID
+     */
+    public void logout(Long userId) {
+        log.info("退出登录,用户ID: {}", userId);
+        
+        // 1. 校验用户是否存在
+        KwsUser kwsUser = kwsUserRepository.getById(userId);
+        if (Objects.isNull(kwsUser)) {
+            throw new SystemException(HttpStatus.QUERY_FAIL_CODE, "用户不存在");
+        }
+
+        // 2. 清除所有客户端类型的 Token
+        for (ClientTypeEnum value : ClientTypeEnum.values()) {
+            String fullUserTokenKey = Global.getFullUserTokenKey(value.getValue(), userId);
+            RedissonUtils.delete(fullUserTokenKey);
+        }
+
+        // 3. 清除用户登录信息
+        for (SystemTypeEnum systemTypeEnum : SystemTypeEnum.values()) {
+            String fullUserLoginKey = Global.getFullUserLoginKey(systemTypeEnum.getCode(), userId);
+            RedissonUtils.remove(fullUserLoginKey);
+        }
+
+        //4.清除企业缓存
+        String key = Global.getFullUserEntKey(kwsUser.getEntId());
+        RedissonUtils.remove(key);
+        //5.清除菜单缓存
+        String menuKey = Global.REDIS_SYS_MENU_PREFIX + userId;
+        RedissonUtils.remove(menuKey);
+
+        log.info("退出登录成功,用户ID: {}", userId);
+    }
+
+    /**
+     * 生成token并缓存
+     *
+     * @param userId     用户ID
+     * @param account    账号
+     * @param clientType 客户端类型
+     * @param systemType 系统类型
+     */
+    private String generateToken(Long userId, String account, String clientType, Integer systemType) {
+        Map<String, Object> info = new HashMap<>(Global.NUMERICAL_SIXTEEN);
+        info.put("userId", userId);
+        info.put("account", account);
+        info.put("clientType", clientType);
+        info.put("systemType", systemType);
+        String key = Global.getFullUserTokenKey(clientType, userId);
+        String token = EncryUtil.encryV1(Global.PRI_KEY, JSON.toJSONString(info));
+        int expireTime = ClientTypeEnum.expireTime(clientType);
+        RedissonUtils.putString(key, token, expireTime);
+        return token;
+    }
+
+    /**
+     * 缓存用户登录信息
+     * @param kwsUser 用户信息
+     * @param systemType 系统类型
+     * @param clientType 客户端类型
+     * @param roleId 角色ID
+     */
+    private void saveUserToCache(KwsUser kwsUser, Integer systemType, String clientType, Long roleId) {
+        try {
+            LoginUserInfo loginUserInfo = new LoginUserInfo();
+            loginUserInfo.setId(kwsUser.getId());
+            loginUserInfo.setSystemType(systemType);
+            loginUserInfo.setClientType(clientType);
+            loginUserInfo.setAccount(kwsUser.getAccount());
+            loginUserInfo.setUserName(kwsUser.getName());
+            loginUserInfo.setPhone(kwsUser.getPhone());
+            loginUserInfo.setStatus(kwsUser.getStatus());
+            loginUserInfo.setIsMain(kwsUser.getIsMain() != null ? kwsUser.getIsMain() : Global.NO);
+            loginUserInfo.setEntId(kwsUser.getEntId());
+            loginUserInfo.setUseRoleId(roleId);
+            loginUserInfo.setUseEntId(kwsUser.getEntId());
+
+            // 查询企业信息
+            if (kwsUser.getEntId() != null) {
+                EntCacheResDto enterprise = remoteSystemService.queryEntDetails(kwsUser.getEntId());
+                if (enterprise != null) {
+                    loginUserInfo.setEntName(enterprise.getFirmName());
+                }
+            }
+
+            // 查询数据权限(暂时不实现,需要时可以通过RemoteUserService调用)
+            // 如果需要数据权限,可以通过Dubbo调用RemoteUserService.queryAuthUserList
+
+            String key = Global.getFullUserLoginKey(systemType, kwsUser.getId());
+            RedissonUtils.putString(key, JSON.toJSONString(loginUserInfo), Global.APP_TOKEN_EXPIRE);
+        } catch (Exception e) {
+            log.error("缓存用户登录信息失败,用户ID: {}", kwsUser.getId(), e);
+        }
+    }
+
+    /**
+     * 缓存企业信息
+     * @param entId 企业ID
+     */
+    private KwsEnterprise saveEntToCache(Long entId) {
+        try {
+            //企业信息
+            KwsEnterprise enterprise = kwsEnterpriseDao.selectByKey(entId);
+            if (enterprise != null) {
+                String key = Global.getFullUserEntKey(entId);
+                RedissonUtils.putString(key, JSON.toJSONString(enterprise), Global.APP_TOKEN_EXPIRE);
+            }
+            return enterprise;
+        } catch (Exception e) {
+            log.error("缓存企业信息失败,企业ID: {}", entId, e);
+        }
+      return null;
+    }
+
+    /**
+     * 缓存菜单权限
+     * @param userId 用户ID
+     * @param systemType 系统类型
+     * @param roleId 角色ID
+     */
+    private List<KwsMenuResVo> saveMenusToCache(Long userId, Integer systemType, Long roleId) {
+        if (Objects.isNull(roleId)){
+            return List.of();
+        }
+        try {
+            // 使用本地服务查询菜单
+            com.sckw.system.model.pojo.FindMenuTreePojo findMenuTreePojo = new com.sckw.system.model.pojo.FindMenuTreePojo();
+            findMenuTreePojo.setRoleIds(Collections.singletonList(roleId));
+            
+            List<KwsMenuResVo> kwsMenuResVos = kwsMenuService.findList(findMenuTreePojo);
+            if (CollectionUtils.isEmpty(kwsMenuResVos)) {
+                RedissonUtils.delete(Global.REDIS_SYS_MENU_PREFIX + systemType + Global.COLON + userId);
+                log.warn("未查询到角色{}的菜单权限", roleId);
+                return List.of();
+            }
+
+            List<String> menus = new ArrayList<>();
+            for (KwsMenuResVo menuResVo : kwsMenuResVos) {
+                String links = menuResVo.getLinks();
+                if (StringUtils.isNotBlank(links)) {
+                    menus.addAll(Arrays.asList(links.split(",")));
+                }
+            }
+            RedissonUtils.putSet(Global.REDIS_SYS_MENU_PREFIX + userId, menus);
+            return kwsMenuResVos;
+        } catch (Exception e) {
+            log.error("缓存菜单权限失败,用户ID: {}, 角色ID: {}", userId, roleId, e);
+        }
+        return List.of();
+    }
+
+    /**
+     * 保存登录日志
+     * @param userId 用户ID
+     * @param request HTTP请求
+     * @param loginType 登录类型: 1-首次登录, 2-切换账号登录
+     */
+    public void saveLoginLogs(Long userId, HttpServletRequest request, Integer loginType) {
+        try {
+            // 获取登录IP
+            String loginIp = RequestUtil.getClientIp(request);
+            
+            // 获取设备信息
+            String userAgentStr = request.getHeader("User-Agent");
+            String deviceInfo = "";
+            if (userAgentStr != null && !userAgentStr.isEmpty()) {
+                try {
+                    UserAgent userAgent = UserAgentUtil.parse(userAgentStr);
+                    String browser = userAgent.getBrowser().getName();
+                    String browserVersion = userAgent.getVersion();
+                    String os = userAgent.getOs().getName();
+                    String device = userAgent.getPlatform().getName();
+                    deviceInfo = String.format("%s %s / %s / %s", browser, browserVersion, os, device);
+                } catch (Exception e) {
+                    deviceInfo = userAgentStr.length() > 100 ? userAgentStr.substring(0, 100) : userAgentStr;
+                }
+            }
+
+            LoginLogs loginLogs = new LoginLogs();
+            loginLogs.setId(new IdWorker(1).nextId());
+            loginLogs.setUserId(userId);
+            loginLogs.setLoginIp(loginIp != null ? loginIp : "");
+            loginLogs.setDeviceInfo(deviceInfo);
+            loginLogs.setLoginType(loginType);
+            loginLogs.setLoginTime(new Date());
+            loginLogsService.save(loginLogs);
+        } catch (Exception e) {
+            // 记录日志失败不影响主流程
+            log.error("保存登录日志失败,用户ID: {}", userId, e);
+        }
+    }
 }
 }

+ 69 - 0
sql/2026/01/2026_01_07_chenxiaofei_create.sql

@@ -0,0 +1,69 @@
+
+-- 2. 登录日志表
+CREATE TABLE `login_logs` (
+                              `id` BIGINT UNSIGNED PRIMARY KEY COMMENT '雪花ID',
+                              `user_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '关联用户ID',
+                              `device_info` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '设备型号/标识',
+                              `login_ip` VARCHAR(45) NOT NULL DEFAULT '' COMMENT '登录IP',
+                              `login_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+                              `login_type` TINYINT NOT NULL DEFAULT 1 COMMENT '1-首次登录, 2-切换账号登录',
+                               INDEX `idx_user_id` (`user_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='登录日志表';
+
+-- 3. 物料信息字典表
+CREATE TABLE `materials` (
+                             `id` BIGINT UNSIGNED PRIMARY KEY COMMENT '雪花ID',
+                             `material_name` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '物料名称',
+                             `category` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '物料分类',
+                             `unit` VARCHAR(20) NOT NULL DEFAULT '吨' COMMENT '计量单位',
+                             UNIQUE KEY `uk_material_name` (`material_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='物料信息表';
+
+-- 4. 装载作业记录表 (核心业务表)
+CREATE TABLE `loading_records` (
+                                   `id` BIGINT UNSIGNED PRIMARY KEY COMMENT '雪花ID',
+                                   `operator_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '操作员ID',
+                                   `license_plate` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '车牌号',
+                                   `customer_name` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '客户名称',
+                                   `load_type` TINYINT NOT NULL DEFAULT 1 COMMENT '装载类型: 1-装载, 2-补货',
+                                   `material_id` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '物料ID',
+                                   `quantity` DECIMAL(12, 2) NOT NULL DEFAULT 0.00 COMMENT '装载数量/重量',
+                                   `work_date` DATE NOT NULL DEFAULT '1000-01-01' COMMENT '作业日期',
+                                   `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
+                                   INDEX `idx_query_composite` (`work_date`, `load_type`, `license_plate`),
+                                   INDEX `idx_operator` (`operator_id`),
+                                   INDEX `idx_customer` (`customer_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='铲车装载记录表';
+
+-- 5. 意见反馈表
+CREATE TABLE `feedbacks` (
+                             `id` BIGINT UNSIGNED PRIMARY KEY COMMENT '雪花ID',
+                             `user_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '反馈人ID',
+                             `content` TEXT NOT NULL COMMENT '反馈正文',
+                             `images` JSON NOT NULL COMMENT '图片附件地址',
+                             `contact_info` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '联系方式',
+                             `is_processed` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否已处理',
+                             `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户反馈表';
+
+-- 6. 系统版本管理表
+CREATE TABLE `app_versions` (
+                                `id` BIGINT UNSIGNED PRIMARY KEY COMMENT '雪花ID',
+                                `version_code` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '版本号',
+                                `platform` TINYINT NOT NULL DEFAULT 1 COMMENT '平台类型(1-Android,2-iOS)',
+                                `update_log` TEXT NOT NULL COMMENT '更新日志',
+                                `download_url` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '安装包下载地址',
+                                `is_force_update` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否强制更新',
+                                `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='版本控制表';
+
+-- 系统内容配置表
+CREATE TABLE `sys_articles` (
+                                `id` BIGINT UNSIGNED PRIMARY KEY COMMENT '雪花ID',
+                                `article_key` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '条款唯一标识 (如: REG_AGREEMENT, PRIVACY_POLICY)',
+                                `title` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '条款标题',
+                                `content` LONGTEXT NOT NULL COMMENT '条款详细内容 (富文本/HTML)',
+                                `version` VARCHAR(20) NOT NULL DEFAULT '1.0.0' COMMENT '版本号',
+                                `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+                                UNIQUE KEY `uk_key` (`article_key`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='法律条款配置表';