LIJIAN 1 mesiac pred
commit
5189d1eed7
100 zmenil súbory, kde vykonal 5685 pridanie a 0 odobranie
  1. 178 0
      ema-admin/pom.xml
  2. 45 0
      ema-admin/src/main/java/com/ema/admin/AdminAppStarter.java
  3. 169 0
      ema-admin/src/main/java/com/ema/admin/aspect/ControllerLogAspect.java
  4. 75 0
      ema-admin/src/main/java/com/ema/admin/codegen/MybatisFlexCodegen.java
  5. 72 0
      ema-admin/src/main/java/com/ema/admin/config/AsyncExecutorConfig.java
  6. 52 0
      ema-admin/src/main/java/com/ema/admin/config/MyBatisFlexConfig.java
  7. 28 0
      ema-admin/src/main/java/com/ema/admin/config/SchedulingConfig.java
  8. 17 0
      ema-admin/src/main/java/com/ema/admin/config/SystemProperties.java
  9. 22 0
      ema-admin/src/main/java/com/ema/admin/config/WebMvcConfig.java
  10. 147 0
      ema-admin/src/main/java/com/ema/admin/handler/GlobalExceptionHandler.java
  11. 99 0
      ema-admin/src/main/java/com/ema/admin/handler/GlobalResponseAdvice.java
  12. 92 0
      ema-admin/src/main/java/com/ema/admin/handler/MyBatisFlexInsertListener.java
  13. 30 0
      ema-admin/src/main/java/com/ema/admin/handler/MyBatisFlexUpdateListener.java
  14. 37 0
      ema-admin/src/main/java/com/ema/admin/handler/OperLoggerHandler.java
  15. 41 0
      ema-admin/src/main/java/com/ema/admin/handler/SaTokenStpInterface.java
  16. 38 0
      ema-admin/src/main/java/com/ema/admin/handler/SystemEventListener.java
  17. 73 0
      ema-admin/src/main/java/com/ema/admin/handler/WebMvcHandler.java
  18. 116 0
      ema-admin/src/main/java/com/ema/admin/modules/system/controller/SysAuthController.java
  19. 122 0
      ema-admin/src/main/java/com/ema/admin/modules/system/controller/SysDepartmentController.java
  20. 140 0
      ema-admin/src/main/java/com/ema/admin/modules/system/controller/SysDictDataController.java
  21. 100 0
      ema-admin/src/main/java/com/ema/admin/modules/system/controller/SysDictTypeController.java
  22. 104 0
      ema-admin/src/main/java/com/ema/admin/modules/system/controller/SysFileController.java
  23. 117 0
      ema-admin/src/main/java/com/ema/admin/modules/system/controller/SysMenuController.java
  24. 50 0
      ema-admin/src/main/java/com/ema/admin/modules/system/controller/SysOperLogController.java
  25. 119 0
      ema-admin/src/main/java/com/ema/admin/modules/system/controller/SysRoleController.java
  26. 143 0
      ema-admin/src/main/java/com/ema/admin/modules/system/controller/SysTimersJobController.java
  27. 126 0
      ema-admin/src/main/java/com/ema/admin/modules/system/controller/SysUserController.java
  28. 16 0
      ema-admin/src/main/java/com/ema/admin/modules/system/converter/SysDepartmentConverter.java
  29. 14 0
      ema-admin/src/main/java/com/ema/admin/modules/system/converter/SysFileConverter.java
  30. 17 0
      ema-admin/src/main/java/com/ema/admin/modules/system/converter/SysMenuConverter.java
  31. 11 0
      ema-admin/src/main/java/com/ema/admin/modules/system/converter/SysOperLogConverter.java
  32. 20 0
      ema-admin/src/main/java/com/ema/admin/modules/system/converter/SysRoleConverter.java
  33. 29 0
      ema-admin/src/main/java/com/ema/admin/modules/system/converter/SysUserConverter.java
  34. 61 0
      ema-admin/src/main/java/com/ema/admin/modules/system/entity/SysDepartment.java
  35. 41 0
      ema-admin/src/main/java/com/ema/admin/modules/system/entity/SysDepartmentUser.java
  36. 73 0
      ema-admin/src/main/java/com/ema/admin/modules/system/entity/SysDictData.java
  37. 61 0
      ema-admin/src/main/java/com/ema/admin/modules/system/entity/SysDictType.java
  38. 91 0
      ema-admin/src/main/java/com/ema/admin/modules/system/entity/SysFile.java
  39. 109 0
      ema-admin/src/main/java/com/ema/admin/modules/system/entity/SysMenu.java
  40. 129 0
      ema-admin/src/main/java/com/ema/admin/modules/system/entity/SysOperLog.java
  41. 44 0
      ema-admin/src/main/java/com/ema/admin/modules/system/entity/SysOperLogQueryVo.java
  42. 61 0
      ema-admin/src/main/java/com/ema/admin/modules/system/entity/SysRole.java
  43. 44 0
      ema-admin/src/main/java/com/ema/admin/modules/system/entity/SysRoleMenu.java
  44. 90 0
      ema-admin/src/main/java/com/ema/admin/modules/system/entity/SysTimersJob.java
  45. 85 0
      ema-admin/src/main/java/com/ema/admin/modules/system/entity/SysUser.java
  46. 44 0
      ema-admin/src/main/java/com/ema/admin/modules/system/entity/SysUserRole.java
  47. 29 0
      ema-admin/src/main/java/com/ema/admin/modules/system/enums/SysOperLogEnum.java
  48. 39 0
      ema-admin/src/main/java/com/ema/admin/modules/system/enums/SysRoleEnum.java
  49. 30 0
      ema-admin/src/main/java/com/ema/admin/modules/system/enums/SysTimerJobEnum.java
  50. 54 0
      ema-admin/src/main/java/com/ema/admin/modules/system/enums/SysUserEnum.java
  51. 14 0
      ema-admin/src/main/java/com/ema/admin/modules/system/mapper/SysDepartmentMapper.java
  52. 14 0
      ema-admin/src/main/java/com/ema/admin/modules/system/mapper/SysDepartmentUserMapper.java
  53. 14 0
      ema-admin/src/main/java/com/ema/admin/modules/system/mapper/SysDictDataMapper.java
  54. 14 0
      ema-admin/src/main/java/com/ema/admin/modules/system/mapper/SysDictTypeMapper.java
  55. 14 0
      ema-admin/src/main/java/com/ema/admin/modules/system/mapper/SysFileMapper.java
  56. 14 0
      ema-admin/src/main/java/com/ema/admin/modules/system/mapper/SysMenuMapper.java
  57. 14 0
      ema-admin/src/main/java/com/ema/admin/modules/system/mapper/SysOperLogMapper.java
  58. 14 0
      ema-admin/src/main/java/com/ema/admin/modules/system/mapper/SysRoleMapper.java
  59. 14 0
      ema-admin/src/main/java/com/ema/admin/modules/system/mapper/SysRoleMenuMapper.java
  60. 14 0
      ema-admin/src/main/java/com/ema/admin/modules/system/mapper/SysTimersJobMapper.java
  61. 14 0
      ema-admin/src/main/java/com/ema/admin/modules/system/mapper/SysUserMapper.java
  62. 14 0
      ema-admin/src/main/java/com/ema/admin/modules/system/mapper/SysUserRoleMapper.java
  63. 57 0
      ema-admin/src/main/java/com/ema/admin/modules/system/service/SysAuthService.java
  64. 43 0
      ema-admin/src/main/java/com/ema/admin/modules/system/service/SysDepartmentService.java
  65. 14 0
      ema-admin/src/main/java/com/ema/admin/modules/system/service/SysDepartmentUserService.java
  66. 31 0
      ema-admin/src/main/java/com/ema/admin/modules/system/service/SysDictDataService.java
  67. 23 0
      ema-admin/src/main/java/com/ema/admin/modules/system/service/SysDictTypeService.java
  68. 26 0
      ema-admin/src/main/java/com/ema/admin/modules/system/service/SysEmailService.java
  69. 17 0
      ema-admin/src/main/java/com/ema/admin/modules/system/service/SysFileService.java
  70. 56 0
      ema-admin/src/main/java/com/ema/admin/modules/system/service/SysMenuService.java
  71. 24 0
      ema-admin/src/main/java/com/ema/admin/modules/system/service/SysOperLogService.java
  72. 14 0
      ema-admin/src/main/java/com/ema/admin/modules/system/service/SysRoleMenuService.java
  73. 40 0
      ema-admin/src/main/java/com/ema/admin/modules/system/service/SysRoleService.java
  74. 36 0
      ema-admin/src/main/java/com/ema/admin/modules/system/service/SysTimersJobService.java
  75. 14 0
      ema-admin/src/main/java/com/ema/admin/modules/system/service/SysUserRoleService.java
  76. 56 0
      ema-admin/src/main/java/com/ema/admin/modules/system/service/SysUserService.java
  77. 143 0
      ema-admin/src/main/java/com/ema/admin/modules/system/service/impl/SysAuthServiceImpl.java
  78. 86 0
      ema-admin/src/main/java/com/ema/admin/modules/system/service/impl/SysDepartmentServiceImpl.java
  79. 19 0
      ema-admin/src/main/java/com/ema/admin/modules/system/service/impl/SysDepartmentUserServiceImpl.java
  80. 39 0
      ema-admin/src/main/java/com/ema/admin/modules/system/service/impl/SysDictDataServiceImpl.java
  81. 30 0
      ema-admin/src/main/java/com/ema/admin/modules/system/service/impl/SysDictTypeServiceImpl.java
  82. 78 0
      ema-admin/src/main/java/com/ema/admin/modules/system/service/impl/SysEmailServiceImpl.java
  83. 87 0
      ema-admin/src/main/java/com/ema/admin/modules/system/service/impl/SysFileRecorderServiceImpl.java
  84. 22 0
      ema-admin/src/main/java/com/ema/admin/modules/system/service/impl/SysFileServiceImpl.java
  85. 93 0
      ema-admin/src/main/java/com/ema/admin/modules/system/service/impl/SysMenuServiceImpl.java
  86. 35 0
      ema-admin/src/main/java/com/ema/admin/modules/system/service/impl/SysOperLogServiceImpl.java
  87. 18 0
      ema-admin/src/main/java/com/ema/admin/modules/system/service/impl/SysRoleMenuServiceImpl.java
  88. 79 0
      ema-admin/src/main/java/com/ema/admin/modules/system/service/impl/SysRoleServiceImpl.java
  89. 73 0
      ema-admin/src/main/java/com/ema/admin/modules/system/service/impl/SysTimersJobServiceImpl.java
  90. 18 0
      ema-admin/src/main/java/com/ema/admin/modules/system/service/impl/SysUserRoleServiceImpl.java
  91. 96 0
      ema-admin/src/main/java/com/ema/admin/modules/system/service/impl/SysUserServiceImpl.java
  92. 44 0
      ema-admin/src/main/java/com/ema/admin/modules/system/vo/RoleMenuAssignVO.java
  93. 28 0
      ema-admin/src/main/java/com/ema/admin/modules/system/vo/SendEmailCodeVo.java
  94. 57 0
      ema-admin/src/main/java/com/ema/admin/modules/system/vo/SysDepartmentQueryVo.java
  95. 84 0
      ema-admin/src/main/java/com/ema/admin/modules/system/vo/SysDepartmentVo.java
  96. 63 0
      ema-admin/src/main/java/com/ema/admin/modules/system/vo/SysDictDataQueryVo.java
  97. 51 0
      ema-admin/src/main/java/com/ema/admin/modules/system/vo/SysDictTypeQueryVo.java
  98. 69 0
      ema-admin/src/main/java/com/ema/admin/modules/system/vo/SysMenuQueryVo.java
  99. 144 0
      ema-admin/src/main/java/com/ema/admin/modules/system/vo/SysMenuVo.java
  100. 66 0
      ema-admin/src/main/java/com/ema/admin/modules/system/vo/SysRoleInfoVo.java

+ 178 - 0
ema-admin/pom.xml

@@ -0,0 +1,178 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.ema</groupId>
+        <artifactId>ema_project</artifactId>
+        <version>1.0.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>ema-admin</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <!-- 相关项目 -->
+        <dependency>
+            <groupId>com.ema</groupId>
+            <artifactId>ema-common</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
+        </dependency>
+
+        <!-- 相关项目 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+            <exclusions>
+                <exclusion>
+                    <artifactId>spring-boot-starter-tomcat</artifactId>
+                    <groupId>org.springframework.boot</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-undertow</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-logging</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency><!-- freemarker模版 -->
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-freemarker</artifactId>
+        </dependency>
+        <!-- websocket支持 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
+
+
+
+        <!-- Sa-Token 权限认证,在线文档:https://sa-token.cc -->
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-spring-boot-starter</artifactId>
+        </dependency>
+        <!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-redis-jackson</artifactId>
+        </dependency>
+
+        <dependency><!-- spring-boot缓存依赖 -->
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-cache</artifactId>
+        </dependency>
+
+        <!-- 数据库相关 -->
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.zaxxer</groupId>
+            <artifactId>HikariCP</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.mybatis-flex</groupId>
+            <artifactId>mybatis-flex-spring-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.mybatis-flex</groupId>
+            <artifactId>mybatis-flex-processor</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.mybatis-flex</groupId>
+            <artifactId>mybatis-flex-codegen</artifactId>
+        </dependency>
+        <!-- 数据库相关 -->
+
+        <!-- swagger接口文档 -->
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.dromara.x-file-storage</groupId>
+            <artifactId>x-file-storage-spring</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun.oss</groupId>
+            <artifactId>aliyun-sdk-oss</artifactId>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <!--<finalName>${project.artifactId}</finalName>-->
+        <resources>
+            <resource>
+                <directory>src/main/resources/</directory>
+                <filtering>true</filtering>
+            </resource>
+        </resources>
+
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-resources-plugin</artifactId>
+                <version>3.3.0</version>
+                <configuration>
+                    <encoding>UTF-8</encoding>
+                    <nonFilteredFileExtensions>
+                        <!--过滤掉不需要编码的文件:过滤后缀为 .doc、.xlsx。。。 的所有文件,不对其进行统一编码-->
+                        <nonFilteredFileExtension>eot</nonFilteredFileExtension>
+                        <nonFilteredFileExtension>ttf</nonFilteredFileExtension>
+                        <nonFilteredFileExtension>woff</nonFilteredFileExtension>
+                        <nonFilteredFileExtension>woff2</nonFilteredFileExtension>
+                        <nonFilteredFileExtension>xdb</nonFilteredFileExtension>
+                        <nonFilteredFileExtension>txt</nonFilteredFileExtension>
+                    </nonFilteredFileExtensions>
+                </configuration>
+            </plugin>
+            <!-- maven install 打包可直接 java -jar 运行插件 -->
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <mainClass>com.ema.admin.AdminAppStarter</mainClass>
+                    <includeSystemScope>true</includeSystemScope>
+                </configuration>
+            </plugin>
+
+        </plugins>
+
+    </build>
+
+</project>

+ 45 - 0
ema-admin/src/main/java/com/ema/admin/AdminAppStarter.java

@@ -0,0 +1,45 @@
+package com.ema.admin;
+
+import com.burukeyou.retry.spring.EnableFastRetry;
+import com.burukeyou.uniapi.annotation.UniAPIScan;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.x.file.storage.spring.EnableFileStorage;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.core.env.Environment;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+import java.net.InetAddress;
+
+
+@Slf4j
+@SpringBootApplication
+@EnableScheduling
+@EnableTransactionManagement // 开启事务支持
+@EnableAspectJAutoProxy // 开启AOP代理
+@EnableFastRetry // 开启重试机制
+@EnableCaching  // 开启缓存支持
+@EnableFileStorage // 开启文件存储
+@ComponentScan(basePackages = {"com.ema"})
+@UniAPIScan("com.ema.admin.remote")
+public class AdminAppStarter {
+
+    @SneakyThrows
+    public static void main(String[] args) {
+        ConfigurableApplicationContext context = SpringApplication.run(AdminAppStarter.class, args);
+        Environment env = context.getEnvironment();
+        StringBuffer sb = new StringBuffer().append("\n");
+        sb.append("##################	Started AdminAppStarter ##################").append("\n");
+        sb.append("Application Name: ").append(env.getProperty("spring.application.name")).append("\n");
+        sb.append("Application Local: ").append("http://").append(InetAddress.getLocalHost().getHostAddress()).append(":").append(env.getProperty("server.port")).append("\n");
+        sb.append("Application Swagger: ").append("http://").append(InetAddress.getLocalHost().getHostAddress()).append(":").append(env.getProperty("server.port")).append("/doc.html").append("\n");
+        sb.append("##################	       Started  SUCCESS       ##################").append("\n");
+        log.info(sb.toString());
+    }
+}

+ 169 - 0
ema-admin/src/main/java/com/ema/admin/aspect/ControllerLogAspect.java

@@ -0,0 +1,169 @@
+package com.ema.admin.aspect;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Controller 日志切面
+ * 自动拦截所有 Controller 方法并打印 uri + 出入参数
+ *
+ * @author 升道
+ */
+@Slf4j
+@Aspect
+@Component
+public class ControllerLogAspect {
+
+    private final ObjectMapper objectMapper = new ObjectMapper();
+
+    /**
+     * 切点:拦截所有 Controller 的public方法
+     * 包含 modules 下的所有 controller 和 websocket.controller
+     */
+    @Pointcut("execution(public * com.ema.admin.modules..controller..*.*(..)) || " +
+              "execution(public * com.ema.admin.websocket.controller..*.*(..))")
+    public void controllerPointcut() {
+    }
+
+    /**
+     * 环绕通知:记录 Controller 方法执行日志
+     * 打印 uri + 入参 + 出参 + 执行时间
+     *
+     * @param joinPoint 连接点
+     * @return 方法执行结果
+     * @throws Throwable 方法执行异常
+     */
+    @Around("controllerPointcut()")
+    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
+        long startTime = System.currentTimeMillis();
+
+        // 获取请求信息
+        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+        HttpServletRequest request = attributes != null ? attributes.getRequest() : null;
+
+        // 获取方法信息
+        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+        String className = signature.getDeclaringType().getSimpleName();
+        String methodName = signature.getName();
+        String uri = request != null ? request.getRequestURI() : className + "." + methodName;
+        String httpMethod = request != null ? request.getMethod() : "";
+
+        // 获取入参
+        Object[] args = joinPoint.getArgs();
+        String argsJson = getArgsJson(args);
+
+        // 执行方法
+        Object result = null;
+        Throwable throwable = null;
+        try {
+            result = joinPoint.proceed();
+            return result;
+        } catch (Throwable e) {
+            throwable = e;
+            throw e;
+        } finally {
+            long costTime = System.currentTimeMillis() - startTime;
+
+            // 获取出参
+            String resultJson = throwable != null ? "Exception: " + throwable.getClass().getSimpleName()
+                    : getResultJson(result);
+
+            // 打印日志
+            log.info("【Controller日志】{} {} | {}#{} | 入参: {} | 出参: {} | 耗时: {}ms",
+                    httpMethod, uri, className, methodName, argsJson, resultJson, costTime);
+        }
+    }
+
+    /**
+     * 获取入参 JSON 字符串
+     * 过滤掉不可序列化的参数(如 HttpServletRequest, HttpServletResponse 等)
+     *
+     * @param args 参数数组
+     * @return JSON 字符串
+     */
+    private String getArgsJson(Object[] args) {
+        if (args == null || args.length == 0) {
+            return "[]";
+        }
+
+        StringBuilder sb = new StringBuilder("[");
+        boolean first = true;
+
+        for (Object arg : args) {
+            if (arg == null) {
+                continue;
+            }
+            // 跳过不可序列化的参数
+            if (isExcludeType(arg)) {
+                continue;
+            }
+
+            if (!first) {
+                sb.append(", ");
+            }
+            first = false;
+
+            try {
+                sb.append(objectMapper.writeValueAsString(arg));
+            } catch (JsonProcessingException e) {
+                sb.append(arg.toString());
+            }
+        }
+
+        sb.append("]");
+        return sb.length() == 2 ? "[]" : sb.toString();
+    }
+
+    /**
+     * 获取出参 JSON 字符串
+     *
+     * @param result 返回值
+     * @return JSON 字符串
+     */
+    private String getResultJson(Object result) {
+        if (result == null) {
+            return "null";
+        }
+
+        // 截断过长的返回内容
+        try {
+            String json = objectMapper.writeValueAsString(result);
+            if (json.length() > 500) {
+                return json.substring(0, 500) + "...(truncated)";
+            }
+            return json;
+        } catch (JsonProcessingException e) {
+            return result.toString();
+        }
+    }
+
+    /**
+     * 判断是否为需要排除的参数类型
+     *
+     * @param obj 参数对象
+     * @return true-需要排除
+     */
+    private boolean isExcludeType(Object obj) {
+        String className = obj.getClass().getName();
+        // 排除 Servlet 相关对象
+        return className.contains("HttpServletRequest")
+                || className.contains("HttpServletResponse")
+                || className.contains("HttpSession")
+                || className.contains("MultipartFile")
+                || className.contains("BindingResult")
+                || className.contains("Model")
+                || className.contains("RedirectAttributes")
+                || className.contains("SessionStatus");
+    }
+}

+ 75 - 0
ema-admin/src/main/java/com/ema/admin/codegen/MybatisFlexCodegen.java

@@ -0,0 +1,75 @@
+package com.ema.admin.codegen;
+
+import cn.hutool.core.util.ArrayUtil;
+import com.mybatisflex.annotation.KeyType;
+import com.mybatisflex.codegen.Generator;
+import com.mybatisflex.codegen.config.ColumnConfig;
+import com.mybatisflex.codegen.config.GlobalConfig;
+import com.mybatisflex.core.keygen.KeyGenerators;
+import com.ema.common.domain.base.BaseEntity;
+import com.zaxxer.hikari.HikariDataSource;
+
+/**
+ * MybatisFlex代码生成器
+ */
+public class MybatisFlexCodegen {
+
+        public static void main(String[] args) {
+            //配置数据源
+            HikariDataSource dataSource = new HikariDataSource();
+            dataSource.setJdbcUrl("jdbc:mysql://172.16.0.212:3306/ema_project?useUnicode=true&characterEncoding=UTF-8&&serverTimezone=GMT%2B8&allowMultiQueries=true&nullCatalogMeansCurrent=true");
+            dataSource.setUsername("root");
+            dataSource.setPassword("Xcsz@2024");
+            String module = "system";
+            String[] tables = {"sys_department","sys_department_user"};
+
+            //创建全局配置
+            GlobalConfig globalConfig = createGlobalConfigUseStyle(module, tables);
+            //通过 datasource 和 globalConfig 创建代码生成器
+            Generator generator = new Generator(dataSource, globalConfig);
+            //生成代码
+            generator.generate();
+        }
+
+        /**
+         * 创建全局配置
+         * @param module 模块名称
+         * @param tables 表名称 null 表示生成所有表
+         * @return
+         */
+        public static GlobalConfig createGlobalConfigUseStyle(String module, String[] tables) {
+            //创建配置内容
+            GlobalConfig globalConfig = new GlobalConfig();
+
+            globalConfig.setBasePackage("com.ema.admin.modules."+module);
+            if(ArrayUtil.isNotEmpty(tables)){
+                //设置表前缀和只生成哪些表
+                //globalConfig.setTablePrefix("g_");
+                globalConfig.setGenerateTable(tables);
+            }
+
+            //设置生成 entity 并启用 Lombok 和 Swagger
+            globalConfig.setEntitySuperClass(BaseEntity.class);
+            globalConfig.setEntityGenerateEnable(true);
+            globalConfig.setEntityWithLombok(true);
+            globalConfig.setEntityWithSwagger(true);
+
+            //设置生成 mapper
+            globalConfig.setMapperGenerateEnable(true);
+            globalConfig.setServiceGenerateEnable(true);
+            globalConfig.setServiceImplGenerateEnable(true);
+            globalConfig.setMapperXmlGenerateEnable(true);
+            globalConfig.setControllerGenerateEnable(true);
+            //globalConfig.setTableDefGenerateEnable(true);
+
+            //可以单独配置某个列
+            ColumnConfig columnConfig = new ColumnConfig();
+            columnConfig.setColumnName("id");
+            columnConfig.setKeyType(KeyType.Generator);
+            columnConfig.setKeyValue(KeyGenerators.snowFlakeId);
+            globalConfig.setColumnConfig(columnConfig);
+
+            return globalConfig;
+        }
+
+    }

+ 72 - 0
ema-admin/src/main/java/com/ema/admin/config/AsyncExecutorConfig.java

@@ -0,0 +1,72 @@
+package com.ema.admin.config;
+
+import com.ema.common.consts.CommonConst;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * 异步线程配置类
+ * 参考文档:https://www.xncoding.com/2017/07/20/spring/sb-async.html
+ *
+ * @author LJ
+ */
+@Slf4j
+@Configuration
+@EnableAsync
+public class AsyncExecutorConfig {
+
+    @Bean(name = CommonConst.ASYNC_POOL)
+    public ThreadPoolTaskExecutor asyncExecutor() {
+        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+        // 线程池维护线程的最少数量
+        executor.setCorePoolSize(8);
+        // 线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程
+        executor.setMaxPoolSize(32);
+        // 缓存队列
+        executor.setQueueCapacity(64);
+        // 允许的空闲时间,当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
+        executor.setKeepAliveSeconds(60);
+
+        // 线程池对拒绝任务(无线程可用)的处理策略,目前只支持AbortPolicy、CallerRunsPolicy;默认为后者
+        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+        // 表明等待所有线程执行完,默认为false
+        executor.setWaitForTasksToCompleteOnShutdown(true);
+        // 等待时长
+        executor.setAwaitTerminationSeconds(600);
+        // 线程名称前缀
+        executor.setThreadNamePrefix(CommonConst.ASYNC_POOL + "-");
+        executor.initialize(); // 如果不初始化,导致找到不到执行器
+        return executor;
+    }
+
+    /**
+     * 回调处理专用线程池
+     * 针对高并发场景(1000+)优化配置
+     */
+    @Bean(name = "callbackExecutor")
+    public ThreadPoolTaskExecutor callbackExecutor() {
+        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+        // 核心线程数,满足高并发处理
+        executor.setCorePoolSize(20);
+        // 最大线程数
+        executor.setMaxPoolSize(100);
+        // 队列容量,积压更多任务
+        executor.setQueueCapacity(1000);
+        // 空闲线程存活时间
+        executor.setKeepAliveSeconds(60);
+
+        // 拒绝策略:由调用线程执行
+        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+        executor.setWaitForTasksToCompleteOnShutdown(true);
+        executor.setAwaitTerminationSeconds(600);
+        executor.setThreadNamePrefix("callback-");
+        executor.initialize();
+        return executor;
+    }
+
+}

+ 52 - 0
ema-admin/src/main/java/com/ema/admin/config/MyBatisFlexConfig.java

@@ -0,0 +1,52 @@
+package com.ema.admin.config;
+
+import com.mybatisflex.core.FlexGlobalConfig;
+import com.mybatisflex.core.audit.AuditManager;
+import com.mybatisflex.core.dialect.DbType;
+import com.mybatisflex.core.mybatis.FlexConfiguration;
+import com.mybatisflex.spring.boot.ConfigurationCustomizer;
+import com.ema.admin.handler.MyBatisFlexInsertListener;
+import com.ema.admin.handler.MyBatisFlexUpdateListener;
+import com.ema.common.domain.base.BaseEntity;
+import lombok.extern.slf4j.Slf4j;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * MyBatisFlex配置
+ */
+@Slf4j
+@Configuration
+@MapperScan(basePackages = {"com.ema.admin.modules.**.mapper"})
+public class MyBatisFlexConfig implements ConfigurationCustomizer {
+
+    public MyBatisFlexConfig() {
+//        配置mybatis-flex全局配置
+        FlexGlobalConfig globalConfig = FlexGlobalConfig.getDefaultConfig();
+        globalConfig.setPrintBanner(true);//是否打印banner
+        globalConfig.setDbType(DbType.MYSQL);//数据库方言
+
+//        更新和插入时启用mybatis-flex监听器,设置BaseEntity类
+        globalConfig.registerInsertListener(new MyBatisFlexInsertListener(), BaseEntity.class);
+        globalConfig.registerUpdateListener(new MyBatisFlexUpdateListener(), BaseEntity.class);
+
+//        逻辑删除的默认值配置,0未删除,1已删除
+        globalConfig.setNormalValueOfLogicDelete("0");
+        globalConfig.setDeletedValueOfLogicDelete("1");
+
+//        注册自定义主键
+//        KeyGeneratorFactory.register("uuid", new UUIDKeyGenerator());
+
+        //        开启审计功能
+        AuditManager.setAuditEnable(false);
+        //设置 SQL 审计收集器
+        AuditManager.setMessageCollector(auditMessage ->
+                log.info("\n执行SQL耗时: {} ms  \n执行SQL: {}", auditMessage.getElapsedTime(), auditMessage.getFullSql())
+        );
+    }
+
+    @Override
+    public void customize(FlexConfiguration flexConfiguration) {
+//        flexConfiguration.setCacheEnabled(false);//开启或关闭mybatis二级缓存
+    }
+}

+ 28 - 0
ema-admin/src/main/java/com/ema/admin/config/SchedulingConfig.java

@@ -0,0 +1,28 @@
+package com.ema.admin.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler;
+
+import java.util.concurrent.Executors;
+
+/**
+ * Scheduled定时任务配置
+ */
+@Configuration
+@EnableScheduling // 开启定时任务
+public class SchedulingConfig {
+
+    /**
+     * 创建一个固定大小的线程池:
+     * 使得@Scheduled的任务可以通过线程池并行执行。
+     * 注意:线程池的大小需要根据实际情况调整,不要设置过大,否则可能会导致系统资源不足。
+     * @return
+     */
+    @Bean
+    public ConcurrentTaskScheduler taskScheduler() {
+        return new ConcurrentTaskScheduler(Executors.newScheduledThreadPool(2));
+    }
+
+}

+ 17 - 0
ema-admin/src/main/java/com/ema/admin/config/SystemProperties.java

@@ -0,0 +1,17 @@
+package com.ema.admin.config;
+
+import lombok.Data;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+/**
+ * 系统属性配置类
+ */
+@Data
+@Component
+public class SystemProperties {
+
+    @Value("${spring.profiles.active:}")
+    private String active;
+
+}

+ 22 - 0
ema-admin/src/main/java/com/ema/admin/config/WebMvcConfig.java

@@ -0,0 +1,22 @@
+package com.ema.admin.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * Web MVC 配置
+ */
+@Configuration
+public class WebMvcConfig implements WebMvcConfigurer {
+
+    @Override
+    public void addCorsMappings(CorsRegistry registry) {
+        registry.addMapping("/**")
+                .allowedOriginPatterns("*")
+                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
+                .allowedHeaders("*")
+                .allowCredentials(true)
+                .maxAge(3600);
+    }
+}

+ 147 - 0
ema-admin/src/main/java/com/ema/admin/handler/GlobalExceptionHandler.java

@@ -0,0 +1,147 @@
+package com.ema.admin.handler;
+
+
+import cn.dev33.satoken.exception.*;
+import com.baomidou.lock.exception.LockFailureException;
+import com.ema.common.domain.vo.ResultVo;
+import com.ema.common.exceptions.MyException;
+import com.ema.common.exceptions.ResultEnum;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.BindException;
+import org.springframework.validation.FieldError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.util.NestedServletException;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.ConstraintViolationException;
+import java.util.stream.Collectors;
+
+/**
+ * 异常处理
+ */
+@Slf4j
+@ControllerAdvice
+public class GlobalExceptionHandler {
+    /**
+     * 处理业务异常
+     *
+     * @param e
+     * @return
+     */
+    @ExceptionHandler(value = MyException.class)
+    @ResponseBody
+    public ResultVo<String> handleBaseException(MyException e) {
+        log.error(">>>>>>   业务异常 : {}", e.getMessage());
+        return ResultVo.failure(e.getCode(), e.getMessage());
+    }
+
+    /**
+     * 处理系统异常
+     *
+     * @param e
+     * @return
+     */
+    @ExceptionHandler(value = Exception.class)
+    @ResponseBody
+    public ResultVo<String> handleException(Exception e) {
+        log.error(">>>>>>   系统异常!", e);
+        return ResultVo.failure(ResultEnum.SYSTEM_ERROR);
+    }
+
+    /**
+     * 处理方法参数无效异常
+     *
+     * @param e
+     * @return
+     */
+    @ExceptionHandler(value = MethodArgumentNotValidException.class)
+    @ResponseBody
+    public ResultVo<String> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
+        return ResultVo.failure(ResultEnum.PARAMETER_ERROR, e.getBindingResult().getFieldErrors().stream().map(FieldError::getDefaultMessage).collect(Collectors.joining(";")));
+    }
+
+    /**
+     * 处理验证约束异常
+     *
+     * @param e
+     * @return
+     */
+    @ExceptionHandler(value = ConstraintViolationException.class)
+    @ResponseBody
+    public ResultVo<String> handleConstraintViolationException(ConstraintViolationException e) {
+        return ResultVo.failure(ResultEnum.PARAMETER_ERROR, e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(";")));
+    }
+
+    /**
+     * 处理数据绑定异常
+     *
+     * @param e
+     * @return
+     */
+    @ExceptionHandler(value = BindException.class)
+    @ResponseBody
+    public ResultVo<String> handleBindException(BindException e) {
+        return ResultVo.failure(ResultEnum.PARAMETER_ERROR, e.getBindingResult().getFieldErrors().stream().map(FieldError::getDefaultMessage).collect(Collectors.joining(";")));
+    }
+
+    // 拦截:未登录异常
+    @ExceptionHandler(NotLoginException.class)
+    @ResponseBody
+    @SneakyThrows
+    public ResultVo<String> handlerException(NotLoginException e) {
+        return ResultVo.failure(ResultEnum.NOT_LOGIN);
+    }
+
+    // 拦截:缺少权限异常
+    @ExceptionHandler(NotPermissionException.class)
+    @ResponseBody
+    public ResultVo<String> handlerException(NotPermissionException e) {
+        return ResultVo.failure(ResultEnum.NOT_POWER);
+    }
+
+    // 拦截:缺少角色异常
+    @ExceptionHandler(NotRoleException.class)
+    @ResponseBody
+    public ResultVo<String> handlerException(NotRoleException e) {
+        return ResultVo.failure(ResultEnum.NOT_POWER);
+    }
+
+    // 拦截:二级认证校验失败异常
+    @ExceptionHandler(NotSafeException.class)
+    @ResponseBody
+    public ResultVo<String> handlerException(NotSafeException e) {
+        return ResultVo.failure(ResultEnum.NOT_POWER,"二次认证校验失败!");
+    }
+
+    // 拦截:服务封禁异常
+    @ExceptionHandler(DisableServiceException.class)
+    @ResponseBody
+    public ResultVo<String> handlerException(DisableServiceException e) {
+        return ResultVo.failure(ResultEnum.NOT_POWER,"服务封禁异常 !");
+    }
+
+    // 拦截:Http Basic 校验失败异常
+    @ExceptionHandler(LockFailureException.class)
+    @ResponseBody
+    public ResultVo<String> handlerException(LockFailureException e) {
+        return ResultVo.failure(ResultEnum.FAILURE,"获取分布式锁失败 !");
+    }
+
+    @ExceptionHandler(SaTokenContextException.class)
+    @ResponseBody
+    public ResultVo<String> handlerException(SaTokenContextException e) {
+        return ResultVo.failure(ResultEnum.NOT_LOGIN);
+    }
+
+    @ExceptionHandler(NestedServletException.class)
+    @ResponseBody
+    public ResultVo<String> handlerException(NestedServletException e) {
+        return ResultVo.failure(ResultEnum.FAILURE,"访问接口地址错误 !");
+    }
+
+
+}

+ 99 - 0
ema-admin/src/main/java/com/ema/admin/handler/GlobalResponseAdvice.java

@@ -0,0 +1,99 @@
+package com.ema.admin.handler;
+
+import com.ema.common.domain.vo.ResultVo;
+import org.jetbrains.annotations.Nullable;
+import org.springframework.core.MethodParameter;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.server.ServerHttpRequest;
+import org.springframework.http.server.ServerHttpResponse;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 全局响应拦截器:自动将Controller返回值封装为ResultVo格式
+ * 无需在Controller方法上写ResultVo<User>,直接返回User即可
+ */
+
+@ControllerAdvice // 作用于所有Controller
+public class GlobalResponseAdvice implements ResponseBodyAdvice<Object> {
+
+    /**
+     * 需要排除拦截的URL路径列表
+     * 可根据实际需求添加或修改
+     */
+    private static final List<String> EXCLUDE_URLS = Arrays.asList(
+            "/swagger-resources",
+            "/v2/api-docs",
+            "/v3/api-docs"
+    );
+
+    /**
+     * 判断是否需要拦截:除了ResultVo、String类型,其他所有返回值都需要拦截封装
+     * 同时排除指定URL路径的请求
+     *
+     * @param returnType    方法返回值类型
+     * @param converterType 消息转换器类型
+     * @return true=拦截,false=不拦截
+     */
+    @Override
+    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
+        // 排除ResultVo类型,避免重复封装
+        Class<?> clazz = returnType.getParameterType();
+        if (ResultVo.class.isAssignableFrom(clazz)) {
+            return false;
+        }
+        // 排除String类型,避免ClassCastException
+        if (String.class.isAssignableFrom(clazz)) {
+            return false;
+        }
+        // 通过URL路径排除特定请求
+        return !shouldExcludeByPath();
+    }
+
+    /**
+     * 判断当前请求是否需要排除
+     * 通过检查URL路径是否在排除列表中
+     *
+     * @return true=需要排除,false=不需要排除
+     */
+    private boolean shouldExcludeByPath() {
+        try {
+            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+            if (attributes == null) {
+                return false;
+            }
+            javax.servlet.http.HttpServletRequest request = attributes.getRequest();
+            String uri = request.getRequestURI();
+            // 检查URL是否在排除列表中(支持路径前缀匹配)
+            return EXCLUDE_URLS.stream().anyMatch(uri::startsWith);
+        } catch (Exception e) {
+            // 如果获取请求信息失败,默认不排除
+            return false;
+        }
+    }
+
+    /**
+     * 拦截后的处理:将返回值封装为ResultVo.success(data)
+     * @param body 原始返回值(如User、List、String等)
+     * @param returnType 方法返回值类型
+     * @param selectedContentType 响应媒体类型
+     * @param selectedConverterType 消息转换器类型
+     * @param request 请求对象
+     * @param response 响应对象
+     * @return 封装后的ResultVo对象
+     *
+     */
+    @Nullable
+    @Override
+    public Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
+        // 自动封装为成功响应,body为业务数据
+        return ResultVo.success(body);
+    }
+
+}

+ 92 - 0
ema-admin/src/main/java/com/ema/admin/handler/MyBatisFlexInsertListener.java

@@ -0,0 +1,92 @@
+package com.ema.admin.handler;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.StrUtil;
+import com.mybatisflex.annotation.InsertListener;
+import com.ema.common.domain.base.BaseEntity;
+import com.ema.common.domain.enums.BooleanEnum;
+
+import java.lang.reflect.Field;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * MyBatisFlex插入监听器
+ */
+public class MyBatisFlexInsertListener implements InsertListener {
+
+    @Override
+    public void onInsert(Object entity) {
+        if (entity == null) {
+            return;
+        }
+
+        // 1. 处理BaseEntity公共字段
+        if (entity instanceof BaseEntity) {
+            BaseEntity baseEntity = (BaseEntity) entity;
+            baseEntity.setDelFlag(BooleanEnum.FALSE.getValue());
+            baseEntity.setCreateTime(DateUtil.date());
+            baseEntity.setUpdateTime(DateUtil.date());
+            try {
+                baseEntity.setCreateBy(StrUtil.isNotBlank(baseEntity.getCreateBy()) ? baseEntity.getCreateBy()
+                        : (StpUtil.isLogin() ? StpUtil.getLoginIdAsString() : "system"));
+                baseEntity.setUpdateBy(StrUtil.isNotBlank(baseEntity.getUpdateBy()) ? baseEntity.getUpdateBy()
+                        : (StpUtil.isLogin() ? StpUtil.getLoginIdAsString() : "system"));
+            } catch (Exception e) {
+                // 异步时,无法获取用户登录信息,设置为async
+                baseEntity.setUpdateBy("async");
+                baseEntity.setCreateBy("async");
+            }
+        }
+
+        // 2. 处理所有null字段,字符串给'',数值给0
+        setDefaultValues(entity);
+    }
+
+    /**
+     * 为实体类的null字段设置默认值
+     * - String类型:设置为 ""
+     * - 数值类型(Integer, Long, Double, Float, BigDecimal):设置为 0
+     * - Date类型:设置为当前时间
+     * - Boolean/boolean、集合类型:不处理(保持null)
+     */
+    private void setDefaultValues(Object entity) {
+        Class<?> clazz = entity.getClass();
+        Field[] fields = clazz.getDeclaredFields();
+        Date now = DateUtil.date();
+
+        for (Field field : fields) {
+            field.setAccessible(true);
+            try {
+                Object value = field.get(entity);
+                if (value == null) {
+                    Class<?> fieldType = field.getType();
+
+                    // String类型设置为空字符串
+                    if (fieldType == String.class) {
+                        field.set(entity, "");
+                    }
+                    // 数值类型设置为0
+                    else if (fieldType == Integer.class || fieldType == int.class) {
+                        field.set(entity, 0);
+                    } else if (fieldType == Long.class || fieldType == long.class) {
+                        field.set(entity, 0L);
+                    } else if (fieldType == Double.class || fieldType == double.class) {
+                        field.set(entity, 0.0);
+                    } else if (fieldType == Float.class || fieldType == float.class) {
+                        field.set(entity, 0.0f);
+                    } else if (fieldType == BigDecimal.class) {
+                        field.set(entity, BigDecimal.ZERO);
+                    }
+                    // Date类型设置为当前时间
+                    else if (fieldType == Date.class) {
+                        field.set(entity, now);
+                    }
+                }
+            } catch (IllegalAccessException e) {
+                // 忽略访问异常
+            }
+        }
+    }
+}

+ 30 - 0
ema-admin/src/main/java/com/ema/admin/handler/MyBatisFlexUpdateListener.java

@@ -0,0 +1,30 @@
+package com.ema.admin.handler;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.StrUtil;
+import com.mybatisflex.annotation.UpdateListener;
+import com.ema.common.domain.base.BaseEntity;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * MyBatisFlex更新监听器
+ */
+@Slf4j
+public class MyBatisFlexUpdateListener implements UpdateListener {
+
+    @Override
+    public void onUpdate(Object entity) {
+        if (entity instanceof BaseEntity) {
+            BaseEntity baseEntity = (BaseEntity) entity;
+            baseEntity.setUpdateTime(DateUtil.date());
+            try {
+                baseEntity.setUpdateBy(StrUtil.isNotBlank(baseEntity.getUpdateBy()) ? baseEntity.getUpdateBy()
+                        : (StpUtil.isLogin() ? StpUtil.getLoginIdAsString() : "system"));
+            } catch (Exception e) {
+                // 异步时,无法获取用户登录信息,设置为async
+                baseEntity.setUpdateBy("async");
+            }
+        }
+    }
+}

+ 37 - 0
ema-admin/src/main/java/com/ema/admin/handler/OperLoggerHandler.java

@@ -0,0 +1,37 @@
+package com.ema.admin.handler;
+
+import cn.dev33.satoken.stp.StpUtil;
+import com.ema.admin.modules.system.converter.SysOperLogConverter;
+import com.ema.admin.modules.system.service.SysOperLogService;
+import com.ema.common.aspect.logger.LoggerHandler;
+import com.ema.common.aspect.logger.LoggerVo;
+import com.ema.common.consts.CommonConst;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+/**
+ * 操作日志处理器
+ */
+@Slf4j
+@Component
+public class OperLoggerHandler implements LoggerHandler {
+
+
+    @Resource(name = CommonConst.ASYNC_POOL)
+    private ThreadPoolTaskExecutor threadPoolExecutor;
+
+    @Resource
+    private SysOperLogService sysOperLogService;
+    @Resource
+    private SysOperLogConverter sysOperLogConverter;
+
+    @Override
+    public void handler(LoggerVo loggerVo) {
+        String userId = StpUtil.isLogin() ? StpUtil.getLoginIdAsString() : null;
+        threadPoolExecutor.execute(() -> {
+            sysOperLogService.save(sysOperLogConverter.toSysOperLog(userId, loggerVo));
+        });
+    }
+}

+ 41 - 0
ema-admin/src/main/java/com/ema/admin/handler/SaTokenStpInterface.java

@@ -0,0 +1,41 @@
+package com.ema.admin.handler;
+
+import cn.dev33.satoken.stp.StpInterface;
+import com.ema.admin.modules.system.service.SysAuthService;
+import com.ema.admin.modules.system.vo.SysUserVo;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 自定义权限加载接口实现类,
+ * 可根据业务需要在实现方法里实现自己的权限加载逻辑
+ */
+@Component
+public class SaTokenStpInterface implements StpInterface {
+
+    @Resource
+    private SysAuthService sysAuthService;
+
+    /**
+     * 返回一个账号所拥有的权限码集合
+     */
+    @Override
+    public List<String> getPermissionList(Object loginId, String loginType) {
+        SysUserVo currentUser = sysAuthService.getCurrentUser();
+        return Objects.isNull(currentUser) ? null : currentUser.getPermsKeys();
+    }
+
+    /**
+     * 返回一个账号所拥有的角色标识集合 (权限与角色可分开校验)
+     */
+    @Override
+    public List<String> getRoleList(Object loginId, String loginType) {
+        SysUserVo currentUser = sysAuthService.getCurrentUser();
+        return Objects.isNull(currentUser) ? null : currentUser.getRoleKeys();
+    }
+
+}
+

+ 38 - 0
ema-admin/src/main/java/com/ema/admin/handler/SystemEventListener.java

@@ -0,0 +1,38 @@
+package com.ema.admin.handler;
+
+import com.ema.common.consts.CommonConst;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.context.event.ApplicationStartedEvent;
+import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * 系统启动监听器
+ */
+@Slf4j
+@Component
+public class SystemEventListener {
+
+    @Resource(name = CommonConst.ASYNC_POOL)
+    private ThreadPoolTaskExecutor threadPoolExecutor;
+
+    /**
+     * 监听项目启动后,初始化所有各任务
+     * @param event
+     */
+    @EventListener(ApplicationStartedEvent.class)
+    public void runListener(ApplicationStartedEvent event) {
+
+        //初始化所有定时任务
+        threadPoolExecutor.execute(() -> {
+            //sysTimersJobService.initAllTimerJob();
+        });
+
+    }
+
+
+
+}

+ 73 - 0
ema-admin/src/main/java/com/ema/admin/handler/WebMvcHandler.java

@@ -0,0 +1,73 @@
+package com.ema.admin.handler;
+
+import cn.dev33.satoken.interceptor.SaInterceptor;
+import cn.dev33.satoken.router.SaRouter;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.collection.CollUtil;
+import org.dromara.x.file.storage.spring.SpringFileStorageProperties;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * WebMvc 是配置静态资源访问、拦截器、权限验证处理器
+ */
+@Configuration
+public class WebMvcHandler implements WebMvcConfigurer {
+
+    @Resource
+    private SpringFileStorageProperties springFileStorageProperties;
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        // 注册 Sa-Token 拦截器,定义详细认证规则 https://sa-token.cc/doc.html
+        registry.addInterceptor(new SaInterceptor(handler -> {
+            // 指定一条 match 规则
+            SaRouter.match("/**")    // 拦截的 path 列表,可以写多个
+                    .notMatch("*.html", "*.css", "*.js", "/swagger**/**", "/webjars/**", "/v3/**", "/doc.html", "/csrf", "/error")
+                    .notMatch("/queue.html", "/queue/**") // 排除队列监控页面
+                    .notMatch("/static/**") // 排除掉静态资源
+                    .notMatch("/login", "/logout", "/register", "/sendEmailCode", "/imgCode/**") // 排除登录、注册、发送邮件验证码接口
+                    .notMatch("/ws/**") // 排除 WebSocket 接口
+                    .notMatch( "/marketing/callback/**") // 排除极光回调接口
+                    .notMatch("/api/websocket/**") // 排除 WebSocket API 文档接口
+                    .notMatch("/v2/api-docs", "/swagger-resources/**", "/swagger-ui.html") // 排除 Knife4j 接口
+                    .check(r -> StpUtil.isLogin()); // 要执行的校验动作,可以写完整的 lambda 表达式
+        })).addPathPatterns("/**");
+    }
+
+
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry) {
+        // 配置文件存储本地上传资源访问
+        configureFileStorageResourceHandlers(registry);
+
+        // 配置其他静态资源访问
+        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
+        registry.addResourceHandler("/queue.html", "/redismq/**").addResourceLocations("classpath:/redismq/");
+    }
+
+    /**
+     * 配置文件存储资源处理器
+     */
+    private void configureFileStorageResourceHandlers(ResourceHandlerRegistry registry) {
+        List<? extends SpringFileStorageProperties.SpringLocalPlusConfig> localPlusList =
+                springFileStorageProperties.getLocalPlus();
+        if (CollUtil.isEmpty(localPlusList)) {
+            return;
+        }
+        // 只遍历一次启用存储的配置
+        for (SpringFileStorageProperties.SpringLocalPlusConfig config : localPlusList) {
+            if (config.getEnableStorage()) {
+                registry.addResourceHandler(config.getPathPatterns())
+                        .addResourceLocations("file:/");
+            }
+        }
+    }
+}
+
+

+ 116 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/controller/SysAuthController.java

@@ -0,0 +1,116 @@
+package com.ema.admin.modules.system.controller;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.util.StrUtil;
+import com.ema.admin.modules.system.entity.SysMenu;
+import com.ema.admin.modules.system.entity.SysRole;
+import com.ema.admin.modules.system.service.SysAuthService;
+import com.ema.admin.modules.system.service.SysEmailService;
+import com.ema.admin.modules.system.vo.*;
+import com.ema.common.aspect.logger.LogTypeEnum;
+import com.ema.common.aspect.logger.Logger;
+import com.ema.common.exceptions.MyAssert;
+import com.ema.common.tools.redis.RedisService;
+import com.ema.common.utils.CodeUtil;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+import java.util.List;
+
+@Api(tags = "用户登录注册授权管理")
+@Slf4j
+@Validated
+@RestController
+public class SysAuthController {
+
+    @Resource
+    private SysAuthService sysAuthService;
+    @Resource
+    private RedisService redisService;
+    @Resource
+    private SysEmailService sysEmailService;
+
+    @ApiOperation(value = "通过账号密码登录")
+    @PostMapping(value = "/login")
+    @Logger(type = LogTypeEnum.LOGIN)
+    public SysUserVo login(@RequestBody @Valid SysUserPwdVo pwdVo) {
+        String imgCode = redisService.getStr("IMG_CODE", pwdVo.getAccount());
+        MyAssert.isTrue(StrUtil.equalsIgnoreCase(imgCode, pwdVo.getCode()), "验证码错误");
+        return sysAuthService.login(pwdVo);
+    }
+
+    @ApiOperation(value = "获取图片验证码(返回图片流)")
+    @ApiImplicitParam(name = "account", value = "账号", required = true, dataType = "string")
+    @GetMapping(value = "/imgCode/{account}")
+    public void getImgCode(@PathVariable String account, HttpServletResponse response) {
+        String code = CodeUtil.outputImg(100, 40, response);
+        redisService.set("IMG_CODE", account, code, 300L);
+    }
+
+    @ApiOperation(value = "发送邮箱验证码")
+    @PostMapping(value = "/sendEmailCode")
+    public void sendEmailCode(@RequestBody @Valid SendEmailCodeVo vo) {
+        sysEmailService.sendCode("EMAIL_TEMPLATES", vo.getCode(), vo.getEmail());
+    }
+
+    @ApiOperation(value = "退出登录")
+    @PostMapping(value = "/logout")
+    public void logout() {
+        if (StpUtil.isLogin()) {
+            StpUtil.logout();
+        }
+    }
+
+    @ApiOperation(value = "获取当前登录用户数量")
+    @GetMapping(value = "/auth/loginUserNum")
+    public Integer loginUserNum() {
+        // 获取所有登录的用户ids
+        List<String> logIds = StpUtil.searchSessionId("", 0, -1, false);
+        return logIds.size();
+    }
+
+    @ApiOperation(value = "获取当前登录用户信息")
+    @GetMapping(value = "/auth/currentUser")
+    public SysUserVo getCurrentUser() {
+        return sysAuthService.getCurrentUser();
+    }
+
+    @ApiOperation(value = "获取当前登录用户菜单列表")
+    @GetMapping(value = "/auth/menus")
+    public List<SysMenu> getCurrentUserMenus() {
+        return sysAuthService.getCurrentUserMenus();
+    }
+
+    @ApiOperation(value = "获取当前登录用户菜单树")
+    @GetMapping(value = "/auth/menuTree")
+    public List<SysMenuVo> getCurrentUserMenuTree() {
+        return sysAuthService.getCurrentUserMenuTree();
+    }
+
+    @ApiOperation(value = "获取当前登录用户角色列表")
+    @GetMapping(value = "/auth/roles")
+    public List<SysRole> getCurrentUseRoles() {
+        return sysAuthService.getCurrentUseRoles();
+    }
+
+
+    @ApiOperation(value = "修改密码")
+    @PostMapping(value = "/auth/updatePwd")
+    public void updatePwd(@RequestBody @Valid SysUserPwdUpdateVo updatePwdVo) {
+        sysAuthService.updatePwd(updatePwdVo);
+    }
+
+    @ApiOperation(value = "忘记密码")
+    @PostMapping(value = "/auth/forgotPassword")
+    public void forgotPassword(@RequestBody @Valid SysUserForgotPwdVo forgotPwdVo) {
+        sysAuthService.forgotPassword(forgotPwdVo);
+    }
+
+}

+ 122 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/controller/SysDepartmentController.java

@@ -0,0 +1,122 @@
+package com.ema.admin.modules.system.controller;
+
+import com.mybatisflex.core.paginate.Page;
+import com.ema.admin.modules.system.entity.SysDepartment;
+import com.ema.admin.modules.system.entity.SysUser;
+import com.ema.admin.modules.system.service.SysDepartmentService;
+import com.ema.admin.modules.system.vo.SysDepartmentQueryVo;
+import com.ema.admin.modules.system.vo.SysDepartmentVo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ *  部门信息控制层。
+ *
+ * @author LIJIAN
+ * @since 2026-05-12
+ */
+@RestController
+@Api(tags = "部门信息接口")
+@RequestMapping("/sysDepartment")
+public class SysDepartmentController {
+
+    @Autowired
+    private SysDepartmentService sysDepartmentService;
+
+    /**
+     * 添加。
+     *
+     * @param sysDepartment 
+     * @return {@code true} 添加成功,{@code false} 添加失败
+     */
+    @PostMapping("save")
+    @ApiOperation("保存")
+    public boolean save(@RequestBody @ApiParam("部门信息") SysDepartment sysDepartment) {
+        return sysDepartmentService.save(sysDepartment);
+    }
+
+    /**
+     * 根据主键删除。
+     *
+     * @param id 主键
+     * @return {@code true} 删除成功,{@code false} 删除失败
+     */
+    @DeleteMapping("remove/{id}")
+    @ApiOperation("根据主键")
+    public boolean remove(@PathVariable @ApiParam("主键") String id) {
+        return sysDepartmentService.removeById(id);
+    }
+
+    /**
+     * 根据主键更新。
+     *
+     * @param sysDepartment 
+     * @return {@code true} 更新成功,{@code false} 更新失败
+     */
+    @PutMapping("update")
+    @ApiOperation("根据主键更新")
+    public boolean update(@RequestBody @ApiParam("主键") SysDepartment sysDepartment) {
+        return sysDepartmentService.updateById(sysDepartment);
+    }
+
+    /**
+     * 查询所有。
+     *
+     * @return 所有数据
+     */
+    @GetMapping("list")
+    @ApiOperation("查询所有")
+    public List<SysDepartment> list() {
+        return sysDepartmentService.list();
+    }
+
+    /**
+     * 根据主键获取详细信息。
+     *
+     * @param id 主键
+     * @return 详情
+     */
+    @GetMapping("getInfo/{id}")
+    @ApiOperation("根据主键获取")
+    public SysDepartment getInfo(@PathVariable @ApiParam("主键") String id) {
+        return sysDepartmentService.getById(id);
+    }
+
+    /**
+     * 分页查询(带条件)。
+     *
+     * @param queryVo 查询条件
+     * @return 分页对象
+     */
+    @PostMapping("page")
+    @ApiOperation("分页查询(带条件)")
+    public Page<SysDepartment> page(@RequestBody @ApiParam("查询条件") SysDepartmentQueryVo queryVo) {
+        return sysDepartmentService.page(queryVo);
+    }
+
+    /**
+     * 查询部门树。
+     *
+     * @return 部门树
+     */
+    @GetMapping("tree")
+    @ApiOperation("部门树")
+    public List<SysDepartmentVo> departmentTree() {
+        List<SysDepartmentVo> departmentTree = sysDepartmentService.tree();
+        return departmentTree;
+    }
+
+    //根据部门查询用户列表
+    @GetMapping("userList/{departmentId}")
+    @ApiOperation("根据部门查询用户列表")
+    public List<SysUser> userList(@PathVariable @ApiParam("部门ID") String departmentId) {
+        List<SysUser> userList = sysDepartmentService.userList(departmentId);
+        return userList;
+    }
+
+}

+ 140 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/controller/SysDictDataController.java

@@ -0,0 +1,140 @@
+package com.ema.admin.modules.system.controller;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSON;
+import cn.hutool.json.JSONUtil;
+import com.mybatisflex.core.paginate.Page;
+import com.ema.admin.modules.system.entity.SysDictData;
+import com.ema.admin.modules.system.service.SysDictDataService;
+import com.ema.admin.modules.system.vo.SysDictDataQueryVo;
+import com.ema.common.exceptions.MyAssert;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 系统字典值表 控制层。
+ *
+ * @author lijian
+ * @since 2025-07-31
+ */
+@RestController
+@Api(tags = "系统字典值表接口")
+@RequestMapping("/sysDictData")
+public class SysDictDataController {
+
+    @Autowired
+    private SysDictDataService sysDictDataService;
+
+    /**
+     * 添加系统字典值表。
+     *
+     * @param sysDictData 系统字典值表
+     * @return {@code true} 添加成功,{@code false} 添加失败
+     */
+    @PostMapping("save")
+    @ApiOperation("保存系统字典值表")
+    public boolean save(@RequestBody @ApiParam("系统字典值表") SysDictData sysDictData) {
+        SysDictData data = sysDictDataService.getByCode(sysDictData.getTypeCode(), sysDictData.getCode());
+        MyAssert.isNull(data, "字典编码已存在");
+        return sysDictDataService.save(sysDictData);
+    }
+
+    /**
+     * 根据主键删除系统字典值表。
+     *
+     * @param id 主键
+     * @return {@code true} 删除成功,{@code false} 删除失败
+     */
+    @DeleteMapping("remove/{id}")
+    @ApiOperation("根据主键系统字典值表")
+    public boolean remove(@PathVariable @ApiParam("系统字典值表主键") Long id) {
+        return sysDictDataService.removeById(id);
+    }
+
+    /**
+     * 根据主键更新系统字典值表。
+     *
+     * @param sysDictData 系统字典值表
+     * @return {@code true} 更新成功,{@code false} 更新失败
+     */
+    @PutMapping("update")
+    @ApiOperation("根据主键更新系统字典值表")
+    public boolean update(@RequestBody @ApiParam("系统字典值表主键") SysDictData sysDictData) {
+        SysDictData data = sysDictDataService.getByCode(sysDictData.getTypeCode(), sysDictData.getCode());
+        MyAssert.isTrue(data == null || data.getId().equals(sysDictData.getId()), "字典编码已存在");
+        return sysDictDataService.updateById(sysDictData);
+    }
+
+    /**
+     * 查询所有系统字典值表。
+     *
+     * @return 所有数据
+     */
+    @GetMapping("list")
+    @ApiOperation("查询所有系统字典值表")
+    public List<SysDictData> list() {
+        return sysDictDataService.list();
+    }
+
+    /**
+     * 根据系统字典值表主键获取详细信息。
+     *
+     * @param id 系统字典值表主键
+     * @return 系统字典值表详情
+     */
+    @GetMapping("getInfo/{id}")
+    @ApiOperation("根据主键获取系统字典值表")
+    public SysDictData getInfo(@PathVariable @ApiParam("系统字典值表主键") Long id) {
+        return sysDictDataService.getById(id);
+    }
+
+    /**
+     * 分页查询系统字典值表(带条件)。
+     *
+     * @param queryVo 查询条件
+     * @return 分页对象
+     */
+    @PostMapping("page")
+    @ApiOperation("分页查询系统字典值表(带条件)")
+    public Page<SysDictData> page(@RequestBody @ApiParam("查询条件") SysDictDataQueryVo queryVo) {
+        return sysDictDataService.page(queryVo);
+    }
+
+    /**
+     * 获取字典数据信息(字典类型+字典编码)
+     *
+     * @param typeCode 字典类型
+     * @param dictCode 字典编码
+     * @return 字典数据信息
+     */
+    @GetMapping("getData/{typeCode}/{dictCode}")
+    @ApiOperation("获取字典数据信息(字典类型+字典编码)")
+    public JSON getDictData(@PathVariable @ApiParam("字典类型") String typeCode, @PathVariable @ApiParam("字典编码") String dictCode) {
+        SysDictData dictData = sysDictDataService.getByCode(typeCode, dictCode);
+        MyAssert.notNull(dictData, "字典数据不存在");
+        String dictValue = dictData.getValue();
+        if(StrUtil.startWith(dictValue, "[") && StrUtil.endWith(dictValue, "]")){
+            return JSONUtil.parseArray(dictValue);
+        }else if(StrUtil.startWith(dictValue, "{") && StrUtil.endWith(dictValue, "}")){
+            return JSONUtil.parseObj(dictValue);
+        }
+        return JSONUtil.createObj().set("value", dictValue);
+    }
+
+    /**
+     * 获取字典数据列表(字典类型)
+     *
+     * @param typeCode 字典类型
+     * @return 字典数据列表
+     */
+    @GetMapping("getList/{typeCode}")
+    @ApiOperation("获取字典数据列表(字典类型)")
+    public List<SysDictData> getDictDataList(@PathVariable @ApiParam("字典类型") String typeCode) {
+        return sysDictDataService.queryChain().eq(SysDictData::getTypeCode, typeCode).list();
+    }
+}

+ 100 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/controller/SysDictTypeController.java

@@ -0,0 +1,100 @@
+package com.ema.admin.modules.system.controller;
+
+import com.mybatisflex.core.paginate.Page;
+import com.ema.admin.modules.system.entity.SysDictType;
+import com.ema.admin.modules.system.service.SysDictTypeService;
+import com.ema.admin.modules.system.vo.SysDictTypeQueryVo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 系统字典类型表 控制层。
+ *
+ * @author lijian
+ * @since 2025-07-31
+ */
+@RestController
+@Api(tags = "系统字典类型表接口")
+@RequestMapping("/sysDictType")
+public class SysDictTypeController {
+
+    @Autowired
+    private SysDictTypeService sysDictTypeService;
+
+    /**
+     * 添加系统字典类型表。
+     *
+     * @param sysDictType 系统字典类型表
+     * @return {@code true} 添加成功,{@code false} 添加失败
+     */
+    @PostMapping("save")
+    @ApiOperation("保存系统字典类型表")
+    public boolean save(@RequestBody @ApiParam("系统字典类型表") SysDictType sysDictType) {
+        return sysDictTypeService.save(sysDictType);
+    }
+
+    /**
+     * 根据主键删除系统字典类型表。
+     *
+     * @param id 主键
+     * @return {@code true} 删除成功,{@code false} 删除失败
+     */
+    @DeleteMapping("remove/{id}")
+    @ApiOperation("根据主键系统字典类型表")
+    public boolean remove(@PathVariable @ApiParam("系统字典类型表主键") Long id) {
+        return sysDictTypeService.removeById(id);
+    }
+
+    /**
+     * 根据主键更新系统字典类型表。
+     *
+     * @param sysDictType 系统字典类型表
+     * @return {@code true} 更新成功,{@code false} 更新失败
+     */
+    @PutMapping("update")
+    @ApiOperation("根据主键更新系统字典类型表")
+    public boolean update(@RequestBody @ApiParam("系统字典类型表主键") SysDictType sysDictType) {
+        return sysDictTypeService.updateById(sysDictType);
+    }
+
+    /**
+     * 查询所有系统字典类型表。
+     *
+     * @return 所有数据
+     */
+    @GetMapping("list")
+    @ApiOperation("查询所有系统字典类型表")
+    public List<SysDictType> list() {
+        return sysDictTypeService.list();
+    }
+
+    /**
+     * 根据系统字典类型表主键获取详细信息。
+     *
+     * @param id 系统字典类型表主键
+     * @return 系统字典类型表详情
+     */
+    @GetMapping("getInfo/{id}")
+    @ApiOperation("根据主键获取系统字典类型表")
+    public SysDictType getInfo(@PathVariable @ApiParam("系统字典类型表主键") Long id) {
+        return sysDictTypeService.getById(id);
+    }
+
+    /**
+     * 分页查询系统字典类型表(带条件)。
+     *
+     * @param queryVo 查询条件
+     * @return 分页对象
+     */
+    @PostMapping("page")
+    @ApiOperation("分页查询系统字典类型表(带条件)")
+    public Page<SysDictType> page(@RequestBody @ApiParam("查询条件") SysDictTypeQueryVo queryVo) {
+        return sysDictTypeService.page(queryVo);
+    }
+
+}

+ 104 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/controller/SysFileController.java

@@ -0,0 +1,104 @@
+package com.ema.admin.modules.system.controller;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.io.FileUtil;
+import com.ema.admin.modules.system.converter.SysFileConverter;
+import com.ema.admin.modules.system.entity.SysFile;
+import com.ema.admin.modules.system.service.SysAuthService;
+import com.ema.admin.modules.system.service.SysFileService;
+import com.ema.common.exceptions.MyAssert;
+import com.ema.common.utils.CodeUtil;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiOperation;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.x.file.storage.core.FileInfo;
+import org.dromara.x.file.storage.core.FileStorageService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 文件上传表 前端控制器
+ */
+@Api(tags = "系统文件管理")
+@Slf4j
+@Validated
+@RestController
+public class SysFileController {
+
+    @Resource
+    private FileStorageService fileStorageService;
+    @Resource
+    private SysFileService sysFileService;
+    @Resource
+    private SysAuthService sysAuthService;
+    @Resource
+    private SysFileConverter sysFileConverter;
+
+    @SneakyThrows
+    @ApiOperation(value = "文件上传")
+    @PostMapping("/upload/file")
+    public SysFile upload(@NotNull MultipartFile file, Integer fileType) {
+        return uploadFile(file);
+    }
+
+    @SneakyThrows
+    @ApiOperation(value = "批量文件上传")
+    @PostMapping("/upload/batch")
+    public List<SysFile> batchUpload(@NotNull MultipartFile[] files) {
+        List<SysFile> sysFiles = CollUtil.newArrayList();
+        for (MultipartFile file : files) {
+            sysFiles.add(uploadFile(file));
+        }
+        return sysFiles;
+    }
+
+    /**
+     * 文件上传到服务器
+     *
+     * @param file     文件
+     * @return 上传后的文件信息
+     */
+    private SysFile uploadFile(MultipartFile file) {
+        FileInfo fileInfo = fileStorageService.of(file)
+                .setPath(DateUtil.format(new Date(), "yyyyMMdd") + "/") //保存到相对路径下,为了方便管理,不需要可以不写
+                .setSaveFilename(CodeUtil.getUUID() + "." + FileUtil.getSuffix(file.getOriginalFilename())) //设置保存的文件名,不需要可以不写,会随机生成
+                .upload();  //将文件上传到对应地方
+        return sysFileConverter.toSysFile(fileInfo);
+    }
+
+    @SneakyThrows
+    @ApiOperation(value = "文件下载")
+    @ApiImplicitParam(name = "url", value = "文件url", paramType = "query", required = true, dataTypeClass = String.class)
+    @GetMapping("/download")
+    public void download(@NotBlank String url, HttpServletResponse response) {
+        // 获取文件信息
+        FileInfo fileInfo = fileStorageService.getFileInfoByUrl(url);
+        MyAssert.notNull(fileInfo, "文件不存在");
+        fileStorageService.download(fileInfo).outputStream(response.getOutputStream());
+    }
+
+    /**
+     * 文件删除
+     */
+    @ApiOperation(value = "文件删除")
+    @ApiImplicitParam(name = "url", value = "文件url", paramType = "query", required = true, dataTypeClass = String.class)
+    @GetMapping("/delete")
+    public void delete(@NotBlank String url) {
+        fileStorageService.delete(url);
+    }
+
+
+}

+ 117 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/controller/SysMenuController.java

@@ -0,0 +1,117 @@
+package com.ema.admin.modules.system.controller;
+
+import com.mybatisflex.core.paginate.Page;
+import com.ema.admin.modules.system.entity.SysMenu;
+import com.ema.admin.modules.system.service.SysMenuService;
+import com.ema.admin.modules.system.vo.SysMenuQueryVo;
+import com.ema.admin.modules.system.vo.SysMenuVo;
+import com.ema.common.domain.vo.ResultVo;
+import io.swagger.annotations.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 菜单权限表 控制层。
+ *
+ * @author LIJIAN
+ * @since 2026-03-23
+ */
+@RestController
+@Api(tags = "菜单权限表接口")
+@RequestMapping("/sysMenu")
+public class SysMenuController {
+
+    @Autowired
+    private SysMenuService sysMenuService;
+
+    /**
+     * 添加菜单权限表。
+     *
+     * @param sysMenu 菜单权限表
+     * @return {@code true} 添加成功,{@code false} 添加失败
+     */
+    @PostMapping("save")
+    @ApiOperation("保存菜单权限表")
+    public boolean save(@RequestBody @ApiParam("菜单权限表") SysMenu sysMenu) {
+        return sysMenuService.save(sysMenu);
+    }
+
+    /**
+     * 根据主键删除菜单权限表。
+     *
+     * @param id 主键
+     * @return {@code true} 删除成功,{@code false} 删除失败
+     */
+    @DeleteMapping("remove/{id}")
+    @ApiOperation("根据主键菜单权限表")
+    public boolean remove(@PathVariable @ApiParam("菜单权限表主键") String id) {
+        return sysMenuService.removeById(id);
+    }
+
+    /**
+     * 根据主键更新菜单权限表。
+     *
+     * @param sysMenu 菜单权限表
+     * @return {@code true} 更新成功,{@code false} 更新失败
+     */
+    @PutMapping("update")
+    @ApiOperation("根据主键更新菜单权限表")
+    public boolean update(@RequestBody @ApiParam("菜单权限表主键") SysMenu sysMenu) {
+        return sysMenuService.updateById(sysMenu);
+    }
+
+    /**
+     * 查询所有菜单权限表。
+     *
+     * @return 所有数据
+     */
+    @GetMapping("list")
+    @ApiOperation("查询所有菜单权限表")
+    public List<SysMenu> list() {
+        return sysMenuService.list();
+    }
+
+    /**
+     * 根据菜单权限表主键获取详细信息。
+     *
+     * @param id 菜单权限表主键
+     * @return 菜单权限表详情
+     */
+    @GetMapping("getInfo/{id}")
+    @ApiOperation("根据主键获取菜单权限表")
+    public SysMenu getInfo(@PathVariable @ApiParam("菜单权限表主键") String id) {
+        return sysMenuService.getById(id);
+    }
+
+    /**
+     * 分页查询菜单权限表(带条件)。
+     *
+     * @param queryVo 查询条件
+     * @return 分页对象
+     */
+    @PostMapping("page")
+    @ApiOperation("分页查询菜单权限表(带条件)")
+    public Page<SysMenu> page(@RequestBody @ApiParam("查询条件") SysMenuQueryVo queryVo) {
+        return sysMenuService.page(queryVo);
+    }
+
+    /**
+     * 查询菜单树。
+     * @param status 状态
+     * @param roleId 角色id
+     * @return 菜单树
+     */
+    @ApiOperation(value = "查询菜单树")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "status", value = "菜单状态 (0停用 1正常)", paramType = "query",  dataTypeClass = String.class),
+            @ApiImplicitParam(name = "roleId", value = "角色id", paramType = "query",  dataTypeClass = String.class)
+    })
+    @GetMapping("tree")
+    public ResultVo<List<SysMenuVo>> getTree(String status, String roleId) {
+        List<SysMenuVo> menuTree = sysMenuService.getTree(status, roleId);
+        return ResultVo.success(menuTree);
+    }
+
+}

+ 50 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/controller/SysOperLogController.java

@@ -0,0 +1,50 @@
+package com.ema.admin.modules.system.controller;
+
+import com.mybatisflex.core.paginate.Page;
+import com.ema.admin.modules.system.entity.SysOperLog;
+import com.ema.admin.modules.system.entity.SysOperLogQueryVo;
+import com.ema.admin.modules.system.service.SysOperLogService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 操作日志记录 控制层。
+ *
+ * @author LIJIAN
+ * @since 2026-03-25
+ */
+@RestController
+@Api(tags = "操作日志记录接口")
+@RequestMapping("/sysOperLog")
+public class SysOperLogController {
+
+    @Autowired
+    private SysOperLogService sysOperLogService;
+
+    /**
+     * 根据操作日志记录主键获取详细信息。
+     *
+     * @param id 操作日志记录主键
+     * @return 操作日志记录详情
+     */
+    @GetMapping("getInfo/{id}")
+    @ApiOperation("根据主键获取操作日志记录")
+    public SysOperLog getInfo(@PathVariable @ApiParam("操作日志记录主键") String id) {
+        return sysOperLogService.getById(id);
+    }
+
+    /**
+     * 分页查询操作日志记录(带查询条件)。
+     *
+     * @param queryVo 查询条件
+     * @return 分页对象
+     */
+    @PostMapping("page")
+    @ApiOperation("分页查询操作日志记录(带查询条件)")
+    public Page<SysOperLog> page(@RequestBody @ApiParam("查询条件") SysOperLogQueryVo queryVo) {
+        return sysOperLogService.page(queryVo);
+    }
+}

+ 119 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/controller/SysRoleController.java

@@ -0,0 +1,119 @@
+package com.ema.admin.modules.system.controller;
+
+import cn.hutool.core.collection.CollUtil;
+import com.mybatisflex.core.paginate.Page;
+import com.ema.admin.modules.system.converter.SysRoleConverter;
+import com.ema.admin.modules.system.entity.SysRole;
+import com.ema.admin.modules.system.service.SysMenuService;
+import com.ema.admin.modules.system.service.SysRoleService;
+import com.ema.admin.modules.system.vo.SysMenuVo;
+import com.ema.admin.modules.system.vo.SysRoleInfoVo;
+import com.ema.admin.modules.system.vo.SysRoleQueryVo;
+import com.ema.admin.modules.system.vo.SysRoleSaveVo;
+import com.ema.common.domain.enums.BooleanEnum;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 系统角色信息表 控制层。
+ *
+ * @author LIJIAN
+ * @since 2026-03-23
+ */
+@RestController
+@Api(tags = "系统角色信息表接口")
+@RequestMapping("/sysRole")
+public class SysRoleController {
+
+    @Autowired
+    private SysRoleService sysRoleService;
+    @Autowired
+    private SysMenuService sysMenuService;
+    @Autowired
+    private SysRoleConverter sysRoleConverter;
+
+    /**
+     * 添加系统角色信息表。
+     *
+     * @param sysRole 系统角色信息表
+     * @return {@code true} 添加成功,{@code false} 添加失败
+     */
+    @PostMapping("save")
+    @ApiOperation("保存系统角色信息表")
+    public boolean save(@RequestBody @ApiParam("系统角色信息表") SysRoleSaveVo saveVo) {
+        SysRole sysRole = sysRoleConverter.toSysRole(saveVo);
+        boolean save = sysRoleService.save(sysRole);
+        if(save && CollUtil.isNotEmpty(saveVo.getMenuIds())){
+            sysRoleService.assignMenusToRole(sysRole.getId(),saveVo.getMenuIds());
+        }
+        return save;
+    }
+
+    /**
+     * 根据主键删除系统角色信息表。
+     *
+     * @param id 主键
+     * @return {@code true} 删除成功,{@code false} 删除失败
+     */
+    @DeleteMapping("remove/{id}")
+    @ApiOperation("根据主键系统角色信息表")
+    public boolean remove(@PathVariable @ApiParam("系统角色信息表主键") String id) {
+        return sysRoleService.removeById(id);
+    }
+
+    /**
+     * 根据主键更新系统角色信息表。
+     *
+     * @param sysRole 系统角色信息表
+     * @return {@code true} 更新成功,{@code false} 更新失败
+     */
+    @PutMapping("update")
+    @ApiOperation("根据主键更新系统角色信息表")
+    public boolean update(@RequestBody @ApiParam("系统角色信息表主键") SysRoleSaveVo sysRole) {
+        sysRoleService.assignMenusToRole(sysRole.getId(),sysRole.getMenuIds());
+        return sysRoleService.updateById(sysRoleConverter.toSysRole(sysRole));
+    }
+
+    /**
+     * 查询所有系统角色信息表。
+     *
+     * @return 所有数据
+     */
+    @GetMapping("list")
+    @ApiOperation("查询所有系统角色信息表")
+    public List<SysRole> list() {
+        return sysRoleService.list();
+    }
+
+    /**
+     * 根据系统角色信息表主键获取详细信息。
+     *
+     * @param id 系统角色信息表主键
+     * @return 系统角色信息表详情
+     */
+    @GetMapping("getInfo/{id}")
+    @ApiOperation("根据主键获取系统角色信息表")
+    public SysRoleInfoVo getInfo(@PathVariable @ApiParam("系统角色信息表主键") String id) {
+        SysRole sysRole = sysRoleService.getById(id);
+        List<SysMenuVo> menuTree = sysMenuService.getTree(BooleanEnum.TRUE.getValue(), id);
+        return sysRoleConverter.toSysRoleInfoVo(sysRole, menuTree);
+    }
+
+    /**
+     * 分页查询系统角色信息表(带条件)。
+     *
+     * @param queryVo 查询条件
+     * @return 分页对象
+     */
+    @PostMapping("page")
+    @ApiOperation("分页查询系统角色信息表(带条件)")
+    public Page<SysRole> page(@RequestBody @ApiParam("查询条件") SysRoleQueryVo queryVo) {
+        return sysRoleService.page(queryVo);
+    }
+
+}

+ 143 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/controller/SysTimersJobController.java

@@ -0,0 +1,143 @@
+package com.ema.admin.modules.system.controller;
+
+import com.mybatisflex.core.paginate.Page;
+import com.ema.admin.modules.system.entity.SysTimersJob;
+import com.ema.admin.modules.system.enums.SysTimerJobEnum;
+import com.ema.admin.modules.system.service.SysTimersJobService;
+import com.ema.admin.modules.system.vo.SysTimersJobQueryVo;
+import com.ema.common.tools.timer.TimerExeService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * 定时任务 控制层。
+ *
+ * @author LIJIAN
+ * @since 2026-03-24
+ */
+@RestController
+@Api(tags = "定时任务接口")
+@RequestMapping("/sysTimersJob")
+public class SysTimersJobController {
+
+    @Resource
+    private SysTimersJobService sysTimersJobService;
+    @Resource
+    private TimerExeService timerExeService;
+
+    /**
+     * 添加定时任务。
+     *
+     * @param sysTimersJob 定时任务
+     * @return {@code true} 添加成功,{@code false} 添加失败
+     */
+    @PostMapping("save")
+    @ApiOperation("保存定时任务")
+    public boolean save(@RequestBody @ApiParam("定时任务") SysTimersJob sysTimersJob) {
+        boolean save = sysTimersJobService.save(sysTimersJob);
+        if (save && SysTimerJobEnum.Status.RUNNING.getCode().equals(sysTimersJob.getJobStatus())) {
+            return timerExeService.startTimer(sysTimersJob.getId(), sysTimersJob.getCron(), sysTimersJob.getActionClass());
+        }
+        return save;
+    }
+
+    /**
+     * 根据主键删除定时任务。
+     *
+     * @param id 主键
+     * @return {@code true} 删除成功,{@code false} 删除失败
+     */
+    @DeleteMapping("remove/{id}")
+    @ApiOperation("根据主键定时任务")
+    public boolean remove(@PathVariable @ApiParam("定时任务主键") String id) {
+        boolean remove = sysTimersJobService.removeById(id);
+        if (remove) {
+            return timerExeService.stopTimer(id);
+        }
+        return false;
+    }
+
+    /**
+     * 根据主键更新定时任务。
+     *
+     * @param sysTimersJob 定时任务
+     * @return {@code true} 更新成功,{@code false} 更新失败
+     */
+    @PutMapping("update")
+    @ApiOperation("根据主键更新定时任务")
+    public boolean update(@RequestBody @ApiParam("定时任务主键") SysTimersJob sysTimersJob) {
+        boolean update = sysTimersJobService.updateById(sysTimersJob);
+        if (update && SysTimerJobEnum.Status.STOP.getCode().equals(sysTimersJob.getJobStatus())) {
+            return timerExeService.stopTimer(sysTimersJob.getId());
+        } else if (update && SysTimerJobEnum.Status.RUNNING.getCode().equals(sysTimersJob.getJobStatus())) {
+            return timerExeService.startTimer(sysTimersJob.getId(), sysTimersJob.getCron(), sysTimersJob.getActionClass());
+        }
+        return update;
+    }
+
+    /**
+     * 查询所有定时任务。
+     *
+     * @return 所有数据
+     */
+    @GetMapping("list")
+    @ApiOperation("查询所有定时任务")
+    public List<SysTimersJob> list() {
+        return sysTimersJobService.list();
+    }
+
+    /**
+     * 根据定时任务主键获取详细信息。
+     *
+     * @param id 定时任务主键
+     * @return 定时任务详情
+     */
+    @GetMapping("getInfo/{id}")
+    @ApiOperation("根据主键获取定时任务")
+    public SysTimersJob getInfo(@PathVariable @ApiParam("定时任务主键") String id) {
+        return sysTimersJobService.getById(id);
+    }
+
+    /**
+     * 分页查询定时任务(带条件)。
+     *
+     * @param queryVo 查询条件
+     * @return 分页对象
+     */
+    @PostMapping("page")
+    @ApiOperation("分页查询定时任务(带条件)")
+    public Page<SysTimersJob> page(@RequestBody @ApiParam("查询条件") SysTimersJobQueryVo queryVo) {
+        return sysTimersJobService.page(queryVo);
+    }
+
+
+    @PostMapping("stop/{id}")
+    @ApiOperation("暂停定时任务")
+    public boolean stop(@PathVariable @ApiParam("定时任务主键") String id) {
+        SysTimersJob sysTimersJob = sysTimersJobService.getById(id);
+        sysTimersJob.setJobStatus(SysTimerJobEnum.Status.STOP.getCode());
+        boolean update = sysTimersJobService.updateById(sysTimersJob);
+        if (update) {
+            return timerExeService.stopTimer(id);
+        }
+        return false;
+    }
+
+    @PostMapping("start/{id}")
+    @ApiOperation("启动定时任务")
+    public boolean start(@PathVariable @ApiParam("定时任务主键") String id) {
+        SysTimersJob sysTimersJob = sysTimersJobService.getById(id);
+        sysTimersJob.setJobStatus(SysTimerJobEnum.Status.RUNNING.getCode());
+        boolean update = sysTimersJobService.updateById(sysTimersJob);
+        if (update) {
+            return timerExeService.startTimer(sysTimersJob.getId(), sysTimersJob.getCron(), sysTimersJob.getActionClass());
+        }
+        return false;
+    }
+
+}

+ 126 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/controller/SysUserController.java

@@ -0,0 +1,126 @@
+package com.ema.admin.modules.system.controller;
+
+import cn.dev33.satoken.secure.SaSecureUtil;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import com.mybatisflex.core.paginate.Page;
+import com.ema.admin.modules.system.converter.SysUserConverter;
+import com.ema.admin.modules.system.entity.SysRole;
+import com.ema.admin.modules.system.entity.SysUser;
+import com.ema.admin.modules.system.service.SysAuthService;
+import com.ema.admin.modules.system.service.SysRoleService;
+import com.ema.admin.modules.system.service.SysUserService;
+import com.ema.admin.modules.system.vo.SysUserInfoVo;
+import com.ema.admin.modules.system.vo.SysUserQueryVo;
+import com.ema.admin.modules.system.vo.SysUserSaveVo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 系统用户信息表 控制层。
+ *
+ * @author LIJIAN
+ * @since 2026-03-23
+ */
+@RestController
+@Api(tags = "系统用户信息表接口")
+@RequestMapping("/sysUser")
+public class SysUserController {
+
+    @Autowired
+    private SysUserService sysUserService;
+    @Autowired
+    private SysUserConverter sysUserConverter;
+    @Autowired
+    private SysAuthService sysAuthService;
+    @Autowired
+    private SysRoleService sysRoleService;
+
+    /**
+     * 添加系统用户信息表。
+     *
+     * @param sysUser 系统用户信息表
+     * @return {@code true} 添加成功,{@code false} 添加失败
+     */
+    @PostMapping("save")
+    @ApiOperation("保存系统用户信息表")
+    public boolean save(@RequestBody @ApiParam("系统用户信息表") SysUserSaveVo sysUser) {
+        sysUser.setPassword(SaSecureUtil.md5(sysUser.getPassword()));
+        boolean save = sysUserService.save(sysUserConverter.toSysUser(sysUser));
+        if(CollUtil.isNotEmpty(sysUser.getRoleIds())){
+            sysUserService.assignRolesToUser(sysUser.getId(),sysUser.getRoleIds());
+        }
+        return save;
+    }
+
+    /**
+     * 根据主键删除系统用户信息表。
+     *
+     * @param id 主键
+     * @return {@code true} 删除成功,{@code false} 删除失败
+     */
+    @DeleteMapping("remove/{id}")
+    @ApiOperation("根据主键系统用户信息表")
+    public boolean remove(@PathVariable @ApiParam("系统用户信息表主键") String id) {
+        return sysUserService.removeById(id);
+    }
+
+    /**
+     * 根据主键更新系统用户信息表。
+     *
+     * @param sysUser 系统用户信息表
+     * @return {@code true} 更新成功,{@code false} 更新失败
+     */
+    @PutMapping("update")
+    @ApiOperation("根据主键更新系统用户信息表")
+    public boolean update(@RequestBody @ApiParam("系统用户信息表主键") SysUserSaveVo sysUser) {
+        if(StrUtil.isNotBlank(sysUser.getPassword())){
+            sysUser.setPassword(SaSecureUtil.md5(sysUser.getPassword()));
+        }
+        sysUserService.assignRolesToUser(sysUser.getId(),sysUser.getRoleIds());
+        return sysUserService.updateById(sysUserConverter.toSysUser(sysUser));
+    }
+
+    /**
+     * 查询所有系统用户信息表。
+     *
+     * @return 所有数据
+     */
+    @GetMapping("list")
+    @ApiOperation("查询所有系统用户信息表")
+    public List<SysUser> list() {
+        return sysUserService.list();
+    }
+
+    /**
+     * 根据系统用户信息表主键获取详细信息。
+     *
+     * @param id 系统用户信息表主键
+     * @return 系统用户信息表详情
+     */
+    @GetMapping("getInfo/{id}")
+    @ApiOperation("根据主键获取系统用户信息表")
+    public SysUserInfoVo getInfo(@PathVariable @ApiParam("系统用户信息表主键") String id) {
+        SysUser sysUser = sysUserService.getById(id);
+        List<SysRole> roleList = sysRoleService.getRoleByUserId(id);
+        return sysUserConverter.toSysUserInfoVo(sysUser,roleList);
+    }
+
+    /**
+     * 分页查询系统用户信息表(带条件)。
+     *
+     * @param queryVo 查询条件
+     * @return 分页对象
+     */
+    @PostMapping("page")
+    @ApiOperation("分页查询系统用户信息表(带条件)")
+    public Page<SysUserInfoVo> page(@RequestBody @ApiParam("查询条件") SysUserQueryVo queryVo) {
+        return sysUserService.page(queryVo);
+    }
+
+}

+ 16 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/converter/SysDepartmentConverter.java

@@ -0,0 +1,16 @@
+package com.ema.admin.modules.system.converter;
+
+import com.ema.admin.modules.system.entity.SysDepartment;
+import com.ema.admin.modules.system.vo.SysDepartmentVo;
+import com.ema.common.mapstruct.MyMapStructMapper;
+import org.mapstruct.Mapper;
+
+import java.util.List;
+
+
+@Mapper(componentModel = "spring", uses = MyMapStructMapper.class)
+public interface SysDepartmentConverter {
+
+    List<SysDepartmentVo> toSysDepartmentVo(List<SysDepartment> list);
+
+}

+ 14 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/converter/SysFileConverter.java

@@ -0,0 +1,14 @@
+package com.ema.admin.modules.system.converter;
+
+import com.ema.admin.modules.system.entity.SysFile;
+import com.ema.common.mapstruct.MyMapStructMapper;
+import org.dromara.x.file.storage.core.FileInfo;
+import org.mapstruct.Mapper;
+
+@Mapper(componentModel = "spring", uses = MyMapStructMapper.class)
+public interface SysFileConverter {
+
+    SysFile toSysFile(FileInfo fileInfo);
+
+    FileInfo toFileInfo(SysFile sysFile);
+}

+ 17 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/converter/SysMenuConverter.java

@@ -0,0 +1,17 @@
+package com.ema.admin.modules.system.converter;
+
+
+import com.ema.admin.modules.system.entity.SysMenu;
+import com.ema.admin.modules.system.vo.SysMenuVo;
+import com.ema.common.mapstruct.MyMapStructMapper;
+import org.mapstruct.Mapper;
+
+import java.util.List;
+
+
+@Mapper(componentModel = "spring", uses = MyMapStructMapper.class)
+public interface SysMenuConverter {
+
+    List<SysMenuVo> toSysMenuVo(List<SysMenu> menuList);
+
+}

+ 11 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/converter/SysOperLogConverter.java

@@ -0,0 +1,11 @@
+package com.ema.admin.modules.system.converter;
+
+import com.ema.admin.modules.system.entity.SysOperLog;
+import com.ema.common.aspect.logger.LoggerVo;
+import com.ema.common.mapstruct.MyMapStructMapper;
+import org.mapstruct.Mapper;
+
+@Mapper(componentModel = "spring", uses = MyMapStructMapper.class)
+public interface SysOperLogConverter {
+    SysOperLog toSysOperLog(String userId, LoggerVo loggerVo);
+}

+ 20 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/converter/SysRoleConverter.java

@@ -0,0 +1,20 @@
+package com.ema.admin.modules.system.converter;
+
+
+import com.ema.admin.modules.system.entity.SysRole;
+import com.ema.admin.modules.system.vo.SysMenuVo;
+import com.ema.admin.modules.system.vo.SysRoleInfoVo;
+import com.ema.admin.modules.system.vo.SysRoleSaveVo;
+import com.ema.common.mapstruct.MyMapStructMapper;
+import org.mapstruct.Mapper;
+
+import java.util.List;
+
+
+@Mapper(componentModel = "spring", uses = MyMapStructMapper.class)
+public interface SysRoleConverter {
+
+    SysRole toSysRole(SysRoleSaveVo sysRole);
+
+    SysRoleInfoVo toSysRoleInfoVo(SysRole sysRole, List<SysMenuVo> menuTree);
+}

+ 29 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/converter/SysUserConverter.java

@@ -0,0 +1,29 @@
+package com.ema.admin.modules.system.converter;
+
+
+import com.ema.admin.modules.system.entity.SysRole;
+import com.ema.admin.modules.system.entity.SysUser;
+import com.ema.admin.modules.system.vo.SysUserInfoVo;
+import com.ema.admin.modules.system.vo.SysUserSaveVo;
+import com.ema.admin.modules.system.vo.SysUserVo;
+import com.ema.common.mapstruct.MyMapStructMapper;
+import org.mapstruct.Mapper;
+
+import java.util.List;
+
+
+@Mapper(componentModel = "spring", uses = MyMapStructMapper.class)
+public interface SysUserConverter {
+
+    SysUserVo toSysUserVo(SysUser sysUser);
+
+    SysUser toSysUser(SysUserSaveVo sysUser);
+
+    SysUserInfoVo toSysUserInfoVo(SysUser sysUser);
+
+    default SysUserInfoVo toSysUserInfoVo(SysUser sysUser, List<SysRole> roleList) {
+        SysUserInfoVo sysUserInfoVo = toSysUserInfoVo(sysUser);
+        sysUserInfoVo.setRoleList(roleList);
+        return sysUserInfoVo;
+    }
+}

+ 61 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/entity/SysDepartment.java

@@ -0,0 +1,61 @@
+package com.ema.admin.modules.system.entity;
+
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.KeyType;
+import com.mybatisflex.annotation.Table;
+import com.ema.common.domain.base.BaseEntity;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.*;
+
+import java.io.Serializable;
+
+/**
+ *  部门信息实体类。
+ *
+ * @author LIJIAN
+ * @since 2026-05-12
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@ApiModel("部门信息")
+@Table("sys_department")
+public class SysDepartment extends BaseEntity implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 部门id
+     */
+    @Id(keyType = KeyType.Generator, value = "snowFlakeId")
+    @ApiModelProperty("部门id")
+    private String id;
+
+    /**
+     * 部门编码
+     */
+    @ApiModelProperty("部门编码")
+    private String deptCode;
+
+    /**
+     * 部门名称
+     */
+    @ApiModelProperty("部门名称")
+    private String deptName;
+
+    /**
+     * 上级部门ID
+     */
+    @ApiModelProperty("上级部门ID")
+    private String parentId;
+
+    /**
+     * 显示顺序
+     */
+    @ApiModelProperty("显示顺序")
+    private Integer sort;
+
+}

+ 41 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/entity/SysDepartmentUser.java

@@ -0,0 +1,41 @@
+package com.ema.admin.modules.system.entity;
+
+import com.mybatisflex.annotation.Table;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ *   部门用户关联实体类。
+ *
+ * @author LIJIAN
+ * @since 2026-05-12
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@ApiModel("部门用户关联")
+@Table("sys_department_user")
+public class SysDepartmentUser implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 部门id
+     */
+    @ApiModelProperty("部门id")
+    private String deptId;
+
+    /**
+     * 用户ID
+     */
+    @ApiModelProperty("用户ID")
+    private String userId;
+
+}

+ 73 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/entity/SysDictData.java

@@ -0,0 +1,73 @@
+package com.ema.admin.modules.system.entity;
+
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.KeyType;
+import com.mybatisflex.annotation.Table;
+import com.ema.common.domain.base.BaseEntity;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.*;
+
+import java.io.Serializable;
+
+/**
+ * 系统字典值表 实体类。
+ *
+ * @author lijian
+ * @since 2025-07-31
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@ApiModel("系统字典值表")
+@Table("sys_dict_data")
+public class SysDictData extends BaseEntity implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    @Id(keyType = KeyType.Generator, value = "snowFlakeId")
+    @ApiModelProperty("主键")
+    private String id;
+
+    /**
+     * 字典类型
+     */
+    @ApiModelProperty("字典类型")
+    private String typeCode;
+
+    /**
+     * 值
+     */
+    @ApiModelProperty("值")
+    private String value;
+
+    /**
+     * 编码
+     */
+    @ApiModelProperty("编码")
+    private String code;
+
+    /**
+     * 排序
+     */
+    @ApiModelProperty("排序")
+    private Integer sort;
+
+    /**
+     * 备注
+     */
+    @ApiModelProperty("备注")
+    private String remark;
+
+    /**
+     * 状态(字典 0停用 1正常)
+     */
+    @ApiModelProperty("状态(字典 0停用 1正常)")
+    private Integer status;
+
+}

+ 61 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/entity/SysDictType.java

@@ -0,0 +1,61 @@
+package com.ema.admin.modules.system.entity;
+
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.KeyType;
+import com.mybatisflex.annotation.Table;
+import com.ema.common.domain.base.BaseEntity;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.*;
+
+import java.io.Serializable;
+
+/**
+ * 系统字典类型表 实体类。
+ *
+ * @author lijian
+ * @since 2025-07-31
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@ApiModel("系统字典类型表")
+@Table("sys_dict_type")
+public class SysDictType extends BaseEntity implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    @Id(keyType = KeyType.Generator, value = "snowFlakeId")
+    @ApiModelProperty("主键")
+    private String id;
+
+    /**
+     * 名称
+     */
+    @ApiModelProperty("名称")
+    private String name;
+
+    /**
+     * 编码
+     */
+    @ApiModelProperty("编码")
+    private String code;
+
+    /**
+     * 排序
+     */
+    @ApiModelProperty("排序")
+    private Integer sort;
+
+    /**
+     * 备注
+     */
+    @ApiModelProperty("备注")
+    private String remark;
+
+}

+ 91 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/entity/SysFile.java

@@ -0,0 +1,91 @@
+package com.ema.admin.modules.system.entity;
+
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.KeyType;
+import com.mybatisflex.annotation.Table;
+import com.ema.common.domain.base.BaseEntity;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.*;
+
+import java.io.Serializable;
+
+/**
+ * 文件记录表 实体类。
+ *
+ * @author LIJIAN
+ * @since 2026-04-16
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@ApiModel("文件记录表")
+@Table("sys_file")
+public class SysFile extends BaseEntity implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 文件id
+     */
+    @Id(keyType = KeyType.Generator, value = "snowFlakeId")
+    @ApiModelProperty("文件id")
+    private String id;
+
+    /**
+     * 文件访问地址
+     */
+    @ApiModelProperty("文件访问地址")
+    private String url;
+
+    /**
+     * 文件大小,单位字节
+     */
+    @ApiModelProperty("文件大小,单位字节")
+    private Long size;
+
+    /**
+     * 文件名称
+     */
+    @ApiModelProperty("文件名称")
+    private String filename;
+
+    /**
+     * 原始文件名
+     */
+    @ApiModelProperty("原始文件名")
+    private String originalFilename;
+
+    /**
+     * 基础存储路径
+     */
+    @ApiModelProperty("基础存储路径")
+    private String basePath;
+
+    /**
+     * 存储路径
+     */
+    @ApiModelProperty("存储路径")
+    private String path;
+
+    /**
+     * 文件扩展名
+     */
+    @ApiModelProperty("文件扩展名")
+    private String ext;
+
+    /**
+     * MIME类型
+     */
+    @ApiModelProperty("MIME类型")
+    private String contentType;
+
+    /**
+     * 存储平台
+     */
+    @ApiModelProperty("存储平台")
+    private String platform;
+
+}

+ 109 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/entity/SysMenu.java

@@ -0,0 +1,109 @@
+package com.ema.admin.modules.system.entity;
+
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.KeyType;
+import com.mybatisflex.annotation.Table;
+import com.ema.common.domain.base.BaseEntity;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.*;
+
+import java.io.Serializable;
+
+/**
+ * 菜单权限表 实体类。
+ *
+ * @author LIJIAN
+ * @since 2026-03-23
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@ApiModel("菜单权限表")
+@Table("sys_menu")
+public class SysMenu extends BaseEntity implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 菜单ID
+     */
+    @Id(keyType = KeyType.Generator, value = "snowFlakeId")
+    @ApiModelProperty("菜单ID")
+    private String id;
+
+    /**
+     * 菜单名称
+     */
+    @ApiModelProperty("菜单名称")
+    private String menuName;
+
+    /**
+     * 父菜单ID (0为顶级菜单)
+     */
+    @ApiModelProperty("父菜单ID (0为顶级菜单)")
+    private String parentId;
+
+    /**
+     * 显示顺序
+     */
+    @ApiModelProperty("显示顺序")
+    private Integer sort;
+
+    /**
+     * 前端路由地址
+     */
+    @ApiModelProperty("前端路由地址")
+    private String path;
+
+    /**
+     * 前端组件路径
+     */
+    @ApiModelProperty("前端组件路径")
+    private String component;
+
+    /**
+     * 权限标识 (例如: system:user:list)
+     */
+    @ApiModelProperty("权限标识 (例如: system:user:list)")
+    private String perms;
+
+    /**
+     * 菜单图标
+     */
+    @ApiModelProperty("菜单图标")
+    private String icon;
+
+    /**
+     * 是否为外链 (0否 1是)
+     */
+    @ApiModelProperty("是否为外链 (0否 1是)")
+    private String isFrame;
+
+    /**
+     * 菜单类型 (M目录 C菜单 F按钮)
+     */
+    @ApiModelProperty("菜单类型 (M目录 C菜单 F按钮)")
+    private String menuType;
+
+    /**
+     * 菜单状态 (0隐藏,1显示 )
+     */
+    @ApiModelProperty("菜单状态 (0隐藏,1显示 )")
+    private String visible;
+
+    /**
+     * 菜单状态 ((0停用 1正常)
+     */
+    @ApiModelProperty("菜单状态 (0停用 1正常)")
+    private String status;
+
+    /**
+     * 备注
+     */
+    @ApiModelProperty("备注")
+    private String remark;
+
+}

+ 129 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/entity/SysOperLog.java

@@ -0,0 +1,129 @@
+package com.ema.admin.modules.system.entity;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.KeyType;
+import com.mybatisflex.annotation.Table;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 操作日志记录 实体类。
+ *
+ * @author LIJIAN
+ * @since 2026-03-25
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@ApiModel("操作日志记录")
+@Table("sys_oper_log")
+public class SysOperLog implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 日志主键
+     */
+    @Id(keyType = KeyType.Generator, value = "snowFlakeId")
+    @ApiModelProperty("日志主键")
+    private String id;
+
+    /**
+     * 日志类型
+     */
+    @ApiModelProperty("日志类型")
+    private String type;
+
+    /**
+     * 模块名称
+     */
+    @ApiModelProperty(value = "模块名称")
+    private String module;
+
+    /**
+     * 日志标题
+     */
+    @ApiModelProperty("日志标题")
+    private String title;
+
+    /**
+     * 请求方法
+     */
+    @ApiModelProperty("请求方法")
+    private String method;
+
+    /**
+     * 请求url
+     */
+    @ApiModelProperty("请求url")
+    private String url;
+
+    /**
+     * 请求参数
+     */
+    @ApiModelProperty("请求参数")
+    private String params;
+
+    /**
+     * 执行状态:0-异常;1-成功
+     */
+    @ApiModelProperty("执行状态:0-异常;1-成功")
+    private String status;
+
+    /**
+     * 请求耗时:毫秒
+     */
+    @ApiModelProperty("请求耗时:毫秒")
+    private Long duration;
+
+    /**
+     * 错误消息
+     */
+    @ApiModelProperty("错误消息")
+    private String errorMsg;
+
+    /**
+     * 用户ID
+     */
+    @ApiModelProperty("用户ID")
+    private String userId;
+
+    /**
+     * 请求IP
+     */
+    @ApiModelProperty("请求IP")
+    private String ip;
+
+    /**
+     * 请求地区
+     */
+    @ApiModelProperty("请求地区")
+    private String region;
+
+    /**
+     * 浏览器
+     */
+    @ApiModelProperty("浏览器")
+    private String browser;
+
+    /**
+     * 操作系统
+     */
+    @ApiModelProperty("操作系统")
+    private String os;
+
+    @ApiModelProperty(value = "操作时间")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", locale = "zh", timezone = "GMT+8")
+    private Date operTime;
+}

+ 44 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/entity/SysOperLogQueryVo.java

@@ -0,0 +1,44 @@
+package com.ema.admin.modules.system.entity;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 操作日志查询条件VO
+ *
+ * @author LIJIAN
+ * @since 2026-03-25
+ */
+@Data
+@ApiModel("操作日志查询参数")
+public class SysOperLogQueryVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty("日志类型")
+    private String type;
+
+    @ApiModelProperty(value = "模块名称")
+    private String module;
+
+    @ApiModelProperty("日志标题(模糊查询)")
+    private String title;
+
+    @ApiModelProperty("请求URL(模糊查询)")
+    private String url;
+
+    @ApiModelProperty("执行状态:0-异常;1-成功")
+    private String status;
+
+    @ApiModelProperty("用户ID")
+    private String userId;
+
+    @ApiModelProperty("页码")
+    private Integer pageNumber = 1;
+
+    @ApiModelProperty("每页条数")
+    private Integer pageSize = 10;
+}

+ 61 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/entity/SysRole.java

@@ -0,0 +1,61 @@
+package com.ema.admin.modules.system.entity;
+
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.KeyType;
+import com.mybatisflex.annotation.Table;
+import com.ema.common.domain.base.BaseEntity;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.*;
+
+import java.io.Serializable;
+
+/**
+ * 系统角色信息表 实体类。
+ *
+ * @author LIJIAN
+ * @since 2026-03-23
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@ApiModel("系统角色信息表")
+@Table("sys_role")
+public class SysRole extends BaseEntity implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 角色ID
+     */
+    @Id(keyType = KeyType.Generator, value = "snowFlakeId")
+    @ApiModelProperty("角色ID")
+    private String id;
+
+    /**
+     * 角色名称
+     */
+    @ApiModelProperty("角色名称")
+    private String roleName;
+
+    /**
+     * 角色权限Key (例如: admin, common)
+     */
+    @ApiModelProperty("角色权限Key (例如: admin, common)")
+    private String roleKey;
+
+    /**
+     * 角色状态 (0停用 1正常)
+     */
+    @ApiModelProperty("角色状态 (0停用 1正常)")
+    private String status;
+
+    /**
+     * 备注
+     */
+    @ApiModelProperty("备注")
+    private String remark;
+
+}

+ 44 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/entity/SysRoleMenu.java

@@ -0,0 +1,44 @@
+package com.ema.admin.modules.system.entity;
+
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.Table;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 系统角色和菜单关联表 实体类。
+ *
+ * @author LIJIAN
+ * @since 2026-03-23
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@ApiModel("系统角色和菜单关联表")
+@Table("sys_role_menu")
+public class SysRoleMenu implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 角色ID
+     */
+    @Id
+    @ApiModelProperty("角色ID")
+    private String roleId;
+
+    /**
+     * 菜单ID
+     */
+    @Id
+    @ApiModelProperty("菜单ID")
+    private String menuId;
+
+}

+ 90 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/entity/SysTimersJob.java

@@ -0,0 +1,90 @@
+package com.ema.admin.modules.system.entity;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.KeyType;
+import com.mybatisflex.annotation.Table;
+import com.ema.common.domain.base.BaseEntity;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.*;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 定时任务 实体类。
+ *
+ * @author LIJIAN
+ * @since 2026-03-24
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@ApiModel("定时任务")
+@Table("sys_timers_job")
+public class SysTimersJob extends BaseEntity implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 定时器id
+     */
+    @Id(keyType = KeyType.Generator, value = "snowFlakeId")
+    @ApiModelProperty("定时器id")
+    private String id;
+
+    /**
+     * 任务名称
+     */
+    @ApiModelProperty("任务名称")
+    private String timerName;
+
+    /**
+     * 执行任务的class的类名(实现了TimerTaskRunner接口的类的全称)
+     */
+    @ApiModelProperty("执行任务的class的类名(实现了TimerTaskRunner接口的类的全称)")
+    private String actionClass;
+
+    /**
+     * 定时任务表达式
+     */
+    @ApiModelProperty("定时任务表达式")
+    private String cron;
+
+    /**
+     * 状态(1运行  2停止 3异常)
+     */
+    @ApiModelProperty("状态(1运行  2停止 3异常)")
+    private String jobStatus;
+
+    /**
+     * 备注信息
+     */
+    @ApiModelProperty("备注信息")
+    private String remark;
+
+    /**
+     * 上次执行时间
+     */
+    @ApiModelProperty("上次执行时间")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", locale = "zh", timezone = "GMT+8")
+    private Date lastTime;
+
+    /**
+     * 上次执行状态(0失败 1成功)
+     */
+    @ApiModelProperty("上次执行状态(0失败 1成功)")
+    private String lastStatus;
+
+    /**
+     * 上次执行信息
+     */
+    @ApiModelProperty("上次执行信息")
+    private String lastMsg;
+
+}

+ 85 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/entity/SysUser.java

@@ -0,0 +1,85 @@
+package com.ema.admin.modules.system.entity;
+
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.KeyType;
+import com.mybatisflex.annotation.Table;
+import com.ema.common.domain.base.BaseEntity;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.*;
+
+import java.io.Serializable;
+
+/**
+ * 系统用户信息表 实体类。
+ *
+ * @author LIJIAN
+ * @since 2026-03-23
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@ApiModel("系统用户信息表")
+@Table("sys_user")
+public class SysUser extends BaseEntity implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 用户ID
+     */
+    @Id(keyType = KeyType.Generator, value = "snowFlakeId")
+    @ApiModelProperty("用户ID")
+    private String id;
+
+    /**
+     * 用户账号
+     */
+    @ApiModelProperty("用户账号")
+    private String account;
+
+    /**
+     * 密码
+     */
+    @ApiModelProperty(value = "密码",hidden = true)
+    private String password;
+
+    /**
+     * 姓名
+     */
+    @ApiModelProperty("姓名")
+    private String name;
+
+    /**
+     * 头像地址
+     */
+    @ApiModelProperty("头像地址")
+    private String avatar;
+
+    /**
+     * 用户邮箱
+     */
+    @ApiModelProperty("用户邮箱")
+    private String email;
+
+    /**
+     * 手机号码
+     */
+    @ApiModelProperty("手机号码")
+    private String phone;
+
+    /**
+     * 帐号状态(0停用 1正常)
+     */
+    @ApiModelProperty("帐号状态(0停用 1正常)")
+    private String status;
+
+    /**
+     * 备注
+     */
+    @ApiModelProperty("备注")
+    private String remark;
+
+}

+ 44 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/entity/SysUserRole.java

@@ -0,0 +1,44 @@
+package com.ema.admin.modules.system.entity;
+
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.Table;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 系统用户和角色关联表 实体类。
+ *
+ * @author LIJIAN
+ * @since 2026-03-23
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@ApiModel("系统用户和角色关联表")
+@Table("sys_user_role")
+public class SysUserRole implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 用户ID
+     */
+    @Id
+    @ApiModelProperty("用户ID")
+    private String userId;
+
+    /**
+     * 角色ID
+     */
+    @Id
+    @ApiModelProperty("角色ID")
+    private String roleId;
+
+}

+ 29 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/enums/SysOperLogEnum.java

@@ -0,0 +1,29 @@
+package com.ema.admin.modules.system.enums;
+
+import lombok.Data;
+
+@Data
+public class SysOperLogEnum {
+    /**
+     * 日志类型枚举
+     */
+    public enum Type {
+
+        LOGIN("1","登录日志"),
+        OPER("1","操作日志"),
+        ;
+        private String value;
+        private String describe;
+
+        Type(String describe, String value) {
+            this.describe = describe;
+            this.value = value;
+        }
+        public String getValue() {
+            return value;
+        }
+        public String getDescribe() {
+            return describe;
+        }
+    }
+}

+ 39 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/enums/SysRoleEnum.java

@@ -0,0 +1,39 @@
+package com.ema.admin.modules.system.enums;
+
+import lombok.Data;
+
+@Data
+public class SysRoleEnum {
+
+
+    /**
+     * 角色标识枚举
+     */
+    public enum RoleKey {
+
+        ADMIN("1", "surper-admin", "系统管理员")
+        ;
+
+        private String roleId;
+        private String value;
+        private String describe;
+
+        RoleKey(String roleId, String value, String describe) {
+            this.roleId = roleId;
+            this.describe = describe;
+            this.value = value;
+        }
+
+        public String getRoleId() {
+            return roleId;
+        }
+        public String getValue() {
+            return value;
+        }
+
+        public String getDescribe() {
+            return describe;
+        }
+    }
+
+}

+ 30 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/enums/SysTimerJobEnum.java

@@ -0,0 +1,30 @@
+package com.ema.admin.modules.system.enums;
+
+import lombok.Data;
+
+@Data
+public class SysTimerJobEnum {
+
+    public enum Status {
+
+        /**
+         * 启动状态
+         */
+        RUNNING("1"),
+
+        /**
+         * 停止状态
+         */
+        STOP("2");
+
+        private final String code;
+
+        Status(String code) {
+            this.code = code;
+        }
+
+        public String getCode() {
+            return code;
+        }
+    }
+}

+ 54 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/enums/SysUserEnum.java

@@ -0,0 +1,54 @@
+package com.ema.admin.modules.system.enums;
+
+import lombok.Data;
+
+@Data
+public class SysUserEnum {
+
+    /**
+     * 帐号状态(0停用 1正常)
+     */
+    public enum Status {
+        DISABLE("0", "停用"),
+        ENABLE("1", "正常")
+        ;
+        private String value;
+        private String describe;
+
+        Status(String value, String describe) {
+            this.describe = describe;
+            this.value = value;
+        }
+        public String getValue() {
+            return value;
+        }
+        public String getDescribe() {
+            return describe;
+        }
+    }
+
+    /**
+     * 账户类型(1SAAS用户 2系统用户)
+     */
+    public enum Type {
+
+        SYSTEM("0", "系统用户"),
+        SAAS("1", "SAAS用户"),
+        CHANNEL("2", "渠道用户")
+        ;
+        private String value;
+        private String describe;
+
+        Type(String value,String describe) {
+            this.describe = describe;
+            this.value = value;
+        }
+        public String getValue() {
+            return value;
+        }
+        public String getDescribe() {
+            return describe;
+        }
+    }
+
+}

+ 14 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/mapper/SysDepartmentMapper.java

@@ -0,0 +1,14 @@
+package com.ema.admin.modules.system.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.ema.admin.modules.system.entity.SysDepartment;
+
+/**
+ *  部门信息表 映射层。
+ *
+ * @author LIJIAN
+ * @since 2026-05-12
+ */
+public interface SysDepartmentMapper extends BaseMapper<SysDepartment> {
+
+}

+ 14 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/mapper/SysDepartmentUserMapper.java

@@ -0,0 +1,14 @@
+package com.ema.admin.modules.system.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.ema.admin.modules.system.entity.SysDepartmentUser;
+
+/**
+ *  部门用户信息表 映射层。
+ *
+ * @author LIJIAN
+ * @since 2026-05-12
+ */
+public interface SysDepartmentUserMapper extends BaseMapper<SysDepartmentUser> {
+
+}

+ 14 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/mapper/SysDictDataMapper.java

@@ -0,0 +1,14 @@
+package com.ema.admin.modules.system.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.ema.admin.modules.system.entity.SysDictData;
+
+/**
+ * 系统字典值表 映射层。
+ *
+ * @author lijian
+ * @since 2025-07-31
+ */
+public interface SysDictDataMapper extends BaseMapper<SysDictData> {
+
+}

+ 14 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/mapper/SysDictTypeMapper.java

@@ -0,0 +1,14 @@
+package com.ema.admin.modules.system.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.ema.admin.modules.system.entity.SysDictType;
+
+/**
+ * 系统字典类型表 映射层。
+ *
+ * @author lijian
+ * @since 2025-07-31
+ */
+public interface SysDictTypeMapper extends BaseMapper<SysDictType> {
+
+}

+ 14 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/mapper/SysFileMapper.java

@@ -0,0 +1,14 @@
+package com.ema.admin.modules.system.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.ema.admin.modules.system.entity.SysFile;
+
+/**
+ * 系统文件信息表 映射层。
+ *
+ * @author lijian
+ * @since 2026-03-23
+ */
+public interface SysFileMapper extends BaseMapper<SysFile> {
+
+}

+ 14 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/mapper/SysMenuMapper.java

@@ -0,0 +1,14 @@
+package com.ema.admin.modules.system.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.ema.admin.modules.system.entity.SysMenu;
+
+/**
+ * 菜单权限表 映射层。
+ *
+ * @author LIJIAN
+ * @since 2026-03-23
+ */
+public interface SysMenuMapper extends BaseMapper<SysMenu> {
+
+}

+ 14 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/mapper/SysOperLogMapper.java

@@ -0,0 +1,14 @@
+package com.ema.admin.modules.system.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.ema.admin.modules.system.entity.SysOperLog;
+
+/**
+ * 操作日志记录 映射层。
+ *
+ * @author LIJIAN
+ * @since 2026-03-25
+ */
+public interface SysOperLogMapper extends BaseMapper<SysOperLog> {
+
+}

+ 14 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/mapper/SysRoleMapper.java

@@ -0,0 +1,14 @@
+package com.ema.admin.modules.system.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.ema.admin.modules.system.entity.SysRole;
+
+/**
+ * 系统角色信息表 映射层。
+ *
+ * @author LIJIAN
+ * @since 2026-03-23
+ */
+public interface SysRoleMapper extends BaseMapper<SysRole> {
+
+}

+ 14 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/mapper/SysRoleMenuMapper.java

@@ -0,0 +1,14 @@
+package com.ema.admin.modules.system.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.ema.admin.modules.system.entity.SysRoleMenu;
+
+/**
+ * 系统角色和菜单关联表 映射层。
+ *
+ * @author LIJIAN
+ * @since 2026-03-23
+ */
+public interface SysRoleMenuMapper extends BaseMapper<SysRoleMenu> {
+
+}

+ 14 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/mapper/SysTimersJobMapper.java

@@ -0,0 +1,14 @@
+package com.ema.admin.modules.system.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.ema.admin.modules.system.entity.SysTimersJob;
+
+/**
+ * 定时任务 映射层。
+ *
+ * @author LIJIAN
+ * @since 2026-03-24
+ */
+public interface SysTimersJobMapper extends BaseMapper<SysTimersJob> {
+
+}

+ 14 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/mapper/SysUserMapper.java

@@ -0,0 +1,14 @@
+package com.ema.admin.modules.system.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.ema.admin.modules.system.entity.SysUser;
+
+/**
+ * 系统用户信息表 映射层。
+ *
+ * @author LIJIAN
+ * @since 2026-03-23
+ */
+public interface SysUserMapper extends BaseMapper<SysUser> {
+
+}

+ 14 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/mapper/SysUserRoleMapper.java

@@ -0,0 +1,14 @@
+package com.ema.admin.modules.system.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.ema.admin.modules.system.entity.SysUserRole;
+
+/**
+ * 系统用户和角色关联表 映射层。
+ *
+ * @author LIJIAN
+ * @since 2026-03-23
+ */
+public interface SysUserRoleMapper extends BaseMapper<SysUserRole> {
+
+}

+ 57 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/service/SysAuthService.java

@@ -0,0 +1,57 @@
+package com.ema.admin.modules.system.service;
+
+import com.ema.admin.modules.system.entity.SysMenu;
+import com.ema.admin.modules.system.entity.SysRole;
+import com.ema.admin.modules.system.vo.*;
+
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+ * 系统用户授权服务
+ */
+public interface SysAuthService {
+    /**
+     * 登录
+     * @param pwdVo
+     * @return
+     */
+    SysUserVo login(SysUserPwdVo pwdVo);
+
+    /**
+     * 忘记密码
+     * @param forgotPwdVo
+     */
+    void forgotPassword(SysUserForgotPwdVo forgotPwdVo);
+
+    /**
+     * 获取当前登录用户信息
+     * @return
+     */
+    SysUserVo getCurrentUser();
+
+    /**
+     * 获取菜单树
+     *
+     * @return
+     */
+    List<SysMenuVo> getCurrentUserMenuTree();
+
+    /**
+     * 获取菜单列表
+     * @return
+     */
+    List<SysMenu> getCurrentUserMenus();
+
+    /**
+     * 获取当前用户角色列表
+     * @return
+     */
+    List<SysRole> getCurrentUseRoles();
+
+    /**
+     * 修改密码
+     * @param updatePwdVo
+     */
+    void updatePwd(@Valid SysUserPwdUpdateVo updatePwdVo);
+}

+ 43 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/service/SysDepartmentService.java

@@ -0,0 +1,43 @@
+package com.ema.admin.modules.system.service;
+
+import com.mybatisflex.core.paginate.Page;
+import com.mybatisflex.core.service.IService;
+import com.ema.admin.modules.system.entity.SysDepartment;
+import com.ema.admin.modules.system.entity.SysUser;
+import com.ema.admin.modules.system.vo.SysDepartmentQueryVo;
+import com.ema.admin.modules.system.vo.SysDepartmentVo;
+
+import java.util.List;
+
+/**
+ *  部门信息表 服务层。
+ *
+ * @author LIJIAN
+ * @since 2026-05-12
+ */
+public interface SysDepartmentService extends IService<SysDepartment> {
+
+    /**
+     * 分页查询(带条件)。
+     *
+     * @param queryVo 查询条件
+     * @return 分页对象
+     */
+    Page<SysDepartment> page(SysDepartmentQueryVo queryVo);
+
+    /**
+     * 获取部门树形结构。
+     *
+     * @return 部门树
+     */
+    List<SysDepartmentVo> tree();
+
+    /**
+     * 根据部门查询用户列表。
+     *
+     * @param departmentId 部门ID
+     * @return 用户列表
+     */
+    List<SysUser> userList(String departmentId);
+
+}

+ 14 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/service/SysDepartmentUserService.java

@@ -0,0 +1,14 @@
+package com.ema.admin.modules.system.service;
+
+import com.mybatisflex.core.service.IService;
+import com.ema.admin.modules.system.entity.SysDepartmentUser;
+
+/**
+ * 部门用户关联 服务层。
+ *
+ * @author LIJIAN
+ * @since 2026-05-12
+ */
+public interface SysDepartmentUserService extends IService<SysDepartmentUser> {
+
+}

+ 31 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/service/SysDictDataService.java

@@ -0,0 +1,31 @@
+package com.ema.admin.modules.system.service;
+
+import com.mybatisflex.core.paginate.Page;
+import com.mybatisflex.core.service.IService;
+import com.ema.admin.modules.system.entity.SysDictData;
+import com.ema.admin.modules.system.vo.SysDictDataQueryVo;
+
+/**
+ * 系统字典值表 服务层。
+ *
+ * @author lijian
+ * @since 2025-07-31
+ */
+public interface SysDictDataService extends IService<SysDictData> {
+
+    /**
+     * 分页查询系统字典值表。
+     *
+     * @param queryVo 查询参数
+     * @return 分页对象
+     */
+    Page<SysDictData> page(SysDictDataQueryVo queryVo);
+
+    /**
+     * 根据字典类型和字典值获取字典数据
+     * @param dictTypeCode 字典类型
+     * @param dictCode 字典值
+     * @return 字典数据
+     */
+    SysDictData getByCode(String dictTypeCode, String dictCode);
+}

+ 23 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/service/SysDictTypeService.java

@@ -0,0 +1,23 @@
+package com.ema.admin.modules.system.service;
+
+import com.mybatisflex.core.paginate.Page;
+import com.mybatisflex.core.service.IService;
+import com.ema.admin.modules.system.entity.SysDictType;
+import com.ema.admin.modules.system.vo.SysDictTypeQueryVo;
+
+/**
+ * 系统字典类型表 服务层。
+ *
+ * @author lijian
+ * @since 2025-07-31
+ */
+public interface SysDictTypeService extends IService<SysDictType> {
+
+    /**
+     * 分页查询系统字典类型表。
+     *
+     * @param queryVo 查询参数
+     * @return 分页对象
+     */
+    Page<SysDictType> page(SysDictTypeQueryVo queryVo);
+}

+ 26 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/service/SysEmailService.java

@@ -0,0 +1,26 @@
+package com.ema.admin.modules.system.service;
+
+/**
+ * 邮件服务接口
+ */
+public interface SysEmailService {
+
+    /**
+     * 发送邮箱验证码
+     *
+     * @param dictTypeCode 字典类型编码
+     * @param dictCode 字典编码
+     * @param email 邮箱地址
+     * @return 验证码
+     */
+    void sendCode(String dictTypeCode, String dictCode, String email);
+
+    /**
+     * 验证邮箱验证码
+     *
+     * @param email 邮箱地址
+     * @param code  验证码
+     * @return true=验证通过,false=验证失败
+     */
+    void verifyCode(String email, String code);
+}

+ 17 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/service/SysFileService.java

@@ -0,0 +1,17 @@
+package com.ema.admin.modules.system.service;
+
+import com.mybatisflex.core.service.IService;
+import com.ema.admin.modules.system.entity.SysFile;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * 系统文件信息表 服务层。
+ *
+ * @author lijian
+ * @since 2026-03-23
+ */
+public interface SysFileService extends IService<SysFile> {
+
+    SysFile getByUrl(@NotBlank String url);
+}

+ 56 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/service/SysMenuService.java

@@ -0,0 +1,56 @@
+package com.ema.admin.modules.system.service;
+
+import com.mybatisflex.core.paginate.Page;
+import com.mybatisflex.core.service.IService;
+import com.ema.admin.modules.system.entity.SysMenu;
+import com.ema.admin.modules.system.entity.SysRole;
+import com.ema.admin.modules.system.vo.SysMenuQueryVo;
+import com.ema.admin.modules.system.vo.SysMenuVo;
+
+import java.util.List;
+
+/**
+ * 菜单权限表 服务层。
+ *
+ * @author LIJIAN
+ * @since 2026-03-23
+ */
+public interface SysMenuService extends IService<SysMenu> {
+
+    /**
+     * 根据角色获取菜单
+     * @param sysRoles
+     * @return
+     */
+    List<SysMenu> getMenuByRoles(List<SysRole> sysRoles);
+
+    /**
+     * 根据角色id获取菜单
+     * @param roleIds
+     * @return
+     */
+    List<SysMenu> getMenuByRoleIds(List<String> roleIds);
+
+    /**
+     * 分页查询菜单权限表。
+     *
+     * @param queryVo 查询参数
+     * @return 分页对象
+     */
+    Page<SysMenu> page(SysMenuQueryVo queryVo);
+
+    /**
+     * 获取菜单树形结构
+     * @param status
+     * @param roleId
+     * @return
+     */
+    List<SysMenuVo> getTree(String status, String roleId);
+
+    /**
+     * 获取菜单列表
+     * @param status
+     * @return
+     */
+    List<SysMenu> getList(String status);
+}

+ 24 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/service/SysOperLogService.java

@@ -0,0 +1,24 @@
+package com.ema.admin.modules.system.service;
+
+import com.mybatisflex.core.paginate.Page;
+import com.mybatisflex.core.service.IService;
+import com.ema.admin.modules.system.entity.SysOperLog;
+import com.ema.admin.modules.system.entity.SysOperLogQueryVo;
+
+/**
+ * 操作日志服务接口
+ *
+ * @author LIJIAN
+ * @since 2026-03-25
+ */
+public interface SysOperLogService extends IService<SysOperLog> {
+
+    /**
+     * 分页查询操作日志(带查询条件)
+     *
+     * @param queryVo 查询参数
+     * @return 分页结果
+     */
+    Page<SysOperLog> page(SysOperLogQueryVo queryVo);
+
+}

+ 14 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/service/SysRoleMenuService.java

@@ -0,0 +1,14 @@
+package com.ema.admin.modules.system.service;
+
+import com.mybatisflex.core.service.IService;
+import com.ema.admin.modules.system.entity.SysRoleMenu;
+
+/**
+ * 系统角色和菜单关联表 服务层。
+ *
+ * @author LIJIAN
+ * @since 2026-03-23
+ */
+public interface SysRoleMenuService extends IService<SysRoleMenu> {
+
+}

+ 40 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/service/SysRoleService.java

@@ -0,0 +1,40 @@
+package com.ema.admin.modules.system.service;
+
+import com.mybatisflex.core.paginate.Page;
+import com.mybatisflex.core.service.IService;
+import com.ema.admin.modules.system.entity.SysRole;
+import com.ema.admin.modules.system.vo.SysRoleQueryVo;
+
+import java.util.List;
+
+/**
+ * 系统角色信息表 服务层。
+ *
+ * @author LIJIAN
+ * @since 2026-03-23
+ */
+public interface SysRoleService extends IService<SysRole> {
+
+    /**
+     * 根据用户id获取角色信息
+     * @param userId 用户id
+     * @return 角色信息
+     */
+    List<SysRole> getRoleByUserId(String userId);
+
+    /**
+     * 分页查询系统角色信息表。
+     *
+     * @param queryVo 查询参数
+     * @return 分页对象
+     */
+    Page<SysRole> page(SysRoleQueryVo queryVo);
+
+
+    /**
+     * 给角色分配菜单权限
+     * @param roleId 角色id
+     * @param menuIds 菜单id列表
+     */
+    void assignMenusToRole(String roleId, List<String> menuIds);
+}

+ 36 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/service/SysTimersJobService.java

@@ -0,0 +1,36 @@
+package com.ema.admin.modules.system.service;
+
+import com.mybatisflex.core.paginate.Page;
+import com.mybatisflex.core.service.IService;
+import com.ema.admin.modules.system.entity.SysTimersJob;
+import com.ema.admin.modules.system.vo.SysTimersJobQueryVo;
+
+/**
+ * 定时任务 服务层。
+ *
+ * @author LIJIAN
+ * @since 2026-03-24
+ */
+public interface SysTimersJobService extends IService<SysTimersJob> {
+
+    /**
+     * 分页查询定时任务。
+     *
+     * @param queryVo 查询参数
+     * @return 分页对象
+     */
+    Page<SysTimersJob> page(SysTimersJobQueryVo queryVo);
+
+    /**
+     * 初始化启动全部定时任务
+     */
+    void initAllTimerJob();
+
+    /**
+     * 更新任务执行结果
+     * @param taskId 任务id
+     * @param isSuccess 是否成功
+     * @param msg 消息
+     */
+    void updateActionResult(String taskId, boolean isSuccess, String msg);
+}

+ 14 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/service/SysUserRoleService.java

@@ -0,0 +1,14 @@
+package com.ema.admin.modules.system.service;
+
+import com.mybatisflex.core.service.IService;
+import com.ema.admin.modules.system.entity.SysUserRole;
+
+/**
+ * 系统用户和角色关联表 服务层。
+ *
+ * @author LIJIAN
+ * @since 2026-03-23
+ */
+public interface SysUserRoleService extends IService<SysUserRole> {
+
+}

+ 56 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/service/SysUserService.java

@@ -0,0 +1,56 @@
+package com.ema.admin.modules.system.service;
+
+import com.mybatisflex.core.paginate.Page;
+import com.mybatisflex.core.service.IService;
+import com.ema.admin.modules.system.entity.SysUser;
+import com.ema.admin.modules.system.vo.SysUserInfoVo;
+import com.ema.admin.modules.system.vo.SysUserQueryVo;
+
+import javax.validation.constraints.NotBlank;
+import java.util.List;
+
+/**
+ * 系统用户信息表 服务层。
+ *
+ * @author LIJIAN
+ * @since 2026-03-23
+ */
+public interface SysUserService extends IService<SysUser> {
+
+    /**
+     * 根据账号获取用户信息
+     * @param account 账号
+     * @return 用户信息
+     */
+    SysUser getByAccount(@NotBlank(message = "账号不能为空") String account);
+
+    /**
+     * 保存用户角色关联
+     * @param userId 用户ID
+     * @param roleId 角色ID
+     */
+    void saveUserRole(String userId, String roleId);
+
+    /**
+     * 分页查询系统用户信息表。
+     *
+     * @param queryVo 查询参数
+     * @return 分页对象
+     */
+    Page<SysUserInfoVo> page(SysUserQueryVo queryVo);
+
+    /**
+     * 给用户分配角色
+     * @param userId 用户ID
+     * @param roleIds 角色ID列表
+     */
+    void assignRolesToUser(String userId, List<String> roleIds);
+
+    /**
+     * 分页查询系统用户信息表。
+     *
+     * @param queryVo 查询参数
+     * @return 分页对象
+     */
+    Page<SysUser> pageSysUser(SysUserQueryVo queryVo);
+}

+ 143 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/service/impl/SysAuthServiceImpl.java

@@ -0,0 +1,143 @@
+package com.ema.admin.modules.system.service.impl;
+
+import cn.dev33.satoken.secure.SaSecureUtil;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import com.ema.admin.modules.system.converter.SysMenuConverter;
+import com.ema.admin.modules.system.converter.SysUserConverter;
+import com.ema.admin.modules.system.entity.SysMenu;
+import com.ema.admin.modules.system.entity.SysRole;
+import com.ema.admin.modules.system.entity.SysUser;
+import com.ema.admin.modules.system.enums.SysUserEnum;
+import com.ema.admin.modules.system.service.*;
+import com.ema.admin.modules.system.vo.*;
+import com.ema.common.exceptions.MyAssert;
+import com.ema.common.utils.TreeUtil;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * 用户登录注册授权管理 服务层实现。
+ */
+@Service
+public class SysAuthServiceImpl implements SysAuthService {
+
+    @Resource
+    private SysUserService sysUserService;
+    @Resource
+    private SysRoleService sysRoleService;
+    @Resource
+    private SysMenuService sysMenuService;
+    @Resource
+    private SysUserRoleService sysUserRoleService;
+    @Resource
+    private SysEmailService sysEmailService;
+    @Resource
+    private SysUserConverter sysUserConverter;
+    @Resource
+    private SysMenuConverter sysMenuConverter;
+
+
+    public static final String MENU = "menu";
+    public static final String ROLE = "role";
+
+    @Override
+    public SysUserVo login(SysUserPwdVo pwdVo) {
+        SysUser sysUser = sysUserService.getByAccount(pwdVo.getAccount());
+        MyAssert.isTrue(Objects.nonNull(sysUser)
+                        && SaSecureUtil.md5(pwdVo.getPwd()).equals(sysUser.getPassword()),
+                "账号或密码错误!");
+        MyAssert.isTrue(sysUser.getStatus().equals(SysUserEnum.Status.ENABLE.getValue()),
+                "账号已被禁用!");
+        SysUserVo userVo = this.setLoginSession(sysUser);
+        return userVo;
+    }
+
+    @Override
+    public void forgotPassword(SysUserForgotPwdVo forgotPwdVo) {
+
+        // 根据邮箱查询用户
+        SysUser sysUser = sysUserService.queryChain()
+                .eq(SysUser::getEmail, forgotPwdVo.getEmail()).one();
+        MyAssert.notNull(sysUser, "该邮箱未注册,请先注册!");
+
+        // 验证邮箱验证码
+        sysEmailService.verifyCode(forgotPwdVo.getEmail(), forgotPwdVo.getCode());
+
+        // 更新密码
+        sysUser.setPassword(SaSecureUtil.md5(forgotPwdVo.getNewPwd()));
+        boolean updateResult = sysUserService.updateById(sysUser);
+        MyAssert.isTrue(updateResult, "密码重置失败,请稍后重试!");
+    }
+
+    /**
+     * 获取用户登录信息
+     *
+     * @param sysUser
+     * @return
+     */
+    private SysUserVo setLoginSession(SysUser sysUser) {
+        SysUserVo userVo = sysUserConverter.toSysUserVo(sysUser);
+
+        // 会话登录:参数填写要登录的账号id
+        StpUtil.login(userVo.getId());
+        //获取登录后的token
+        userVo.setToken(StpUtil.getTokenValue());
+        StpUtil.getSession().set(userVo.getToken(), userVo); // 缓存 userVo 对象
+
+        //查询用户角色
+        List<SysRole> sysRoles = sysRoleService.getRoleByUserId(userVo.getId());
+        MyAssert.isTrue(CollUtil.isNotEmpty(sysRoles), "用户角色不存在,请联系管理员!");
+        userVo.setRoleKeys(sysRoles.stream().map(SysRole::getRoleKey).distinct().collect(Collectors.toList()));
+
+        //查询用户权限
+        List<SysMenu> sysMenus = sysMenuService.getMenuByRoles(sysRoles);
+        userVo.setPermsKeys(sysMenus.stream().map(SysMenu::getPerms).filter(StrUtil::isNotBlank).distinct().collect(Collectors.toList()));
+        StpUtil.getSession().set(ROLE + userVo.getToken(), sysRoles);//缓存角色信息
+        StpUtil.getSession().set(MENU + userVo.getToken(), sysMenus);
+        return userVo;
+    }
+
+    @Override
+    public SysUserVo getCurrentUser() {
+        return StpUtil.getSession().getModel(StpUtil.getTokenValue(), SysUserVo.class);
+    }
+
+    @Override
+    public List<SysMenuVo> getCurrentUserMenuTree() {
+        List<SysMenu> menuList = this.getCurrentUserMenus();
+        List<SysMenuVo> menuVoList = sysMenuConverter.toSysMenuVo(menuList);
+        // 构建树形菜单
+        return TreeUtil.buildTree(menuVoList, null);
+    }
+
+    @Override
+    public List<SysMenu> getCurrentUserMenus() {
+        SysUserVo userVo = this.getCurrentUser();
+        return (List<SysMenu>) StpUtil.getSession().get(MENU + userVo.getToken());
+    }
+
+    @Override
+    public List<SysRole> getCurrentUseRoles() {
+        SysUserVo userVo = this.getCurrentUser();
+        return (List<SysRole>) StpUtil.getSession().get(ROLE + userVo.getToken());
+    }
+
+    @Override
+    public void updatePwd(SysUserPwdUpdateVo updatePwdVo) {
+        SysUserVo userVo = this.getCurrentUser();
+        SysUser sysUser = sysUserService.getById(userVo.getId());
+        MyAssert.isTrue(sysUser != null
+                        && SaSecureUtil.md5(updatePwdVo.getOldPwd()).equals(sysUser.getPassword()),
+                "原密码错误!");
+        sysUser.setPassword(SaSecureUtil.md5(updatePwdVo.getNewPwd()));
+        sysUserService.updateById(sysUser);
+    }
+
+
+}

+ 86 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/service/impl/SysDepartmentServiceImpl.java

@@ -0,0 +1,86 @@
+package com.ema.admin.modules.system.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import com.mybatisflex.core.paginate.Page;
+import com.mybatisflex.core.query.QueryWrapper;
+import com.mybatisflex.spring.service.impl.ServiceImpl;
+import com.ema.admin.modules.system.converter.SysDepartmentConverter;
+import com.ema.admin.modules.system.converter.SysUserConverter;
+import com.ema.admin.modules.system.entity.SysDepartment;
+import com.ema.admin.modules.system.entity.SysDepartmentUser;
+import com.ema.admin.modules.system.entity.SysUser;
+import com.ema.admin.modules.system.mapper.SysDepartmentMapper;
+import com.ema.admin.modules.system.service.SysDepartmentService;
+import com.ema.admin.modules.system.service.SysDepartmentUserService;
+import com.ema.admin.modules.system.service.SysUserService;
+import com.ema.admin.modules.system.vo.SysDepartmentQueryVo;
+import com.ema.admin.modules.system.vo.SysDepartmentVo;
+import com.ema.common.utils.TreeUtil;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ *  部门信息表 服务层实现。
+ *
+ * @author LIJIAN
+ * @since 2026-05-12
+ */
+@Service
+public class SysDepartmentServiceImpl extends ServiceImpl<SysDepartmentMapper, SysDepartment>  implements SysDepartmentService{
+
+    @Resource
+    private SysDepartmentConverter sysDepartmentConverter;
+    @Resource
+    private SysDepartmentUserService sysDepartmentUserService;
+    @Resource
+    private SysUserService sysUserService;
+    @Resource
+    private SysUserConverter sysUserConverter;
+
+    @Override
+    public Page<SysDepartment> page(SysDepartmentQueryVo queryVo) {
+        QueryWrapper queryWrapper = QueryWrapper.create();
+        queryWrapper.like(SysDepartment::getDeptCode, queryVo.getDeptCode(), StrUtil.isNotBlank(queryVo.getDeptCode()));
+        queryWrapper.like(SysDepartment::getDeptName, queryVo.getDeptName(), StrUtil.isNotBlank(queryVo.getDeptName()));
+        queryWrapper.eq(SysDepartment::getParentId, queryVo.getParentId(), queryVo.getParentId() != null);
+        queryWrapper.orderBy(SysDepartment::getSort);
+        return this.page(new Page<>(queryVo.getPageNumber(), queryVo.getPageSize()), queryWrapper);
+    }
+
+    @Override
+    public List<SysDepartmentVo> tree() {
+        List<SysDepartment> departmentList = this.queryChain()
+                .orderBy(SysDepartment::getSort, true).list();
+        if (CollUtil.isEmpty(departmentList)) {
+            return CollUtil.newArrayList();
+        }
+        return TreeUtil.buildTree(sysDepartmentConverter.toSysDepartmentVo(departmentList), null);
+    }
+
+    @Override
+    public List<SysUser> userList(String departmentId) {
+        if (StrUtil.isBlank(departmentId)) {
+            return Collections.emptyList();
+        }
+        // 查询部门关联的用户ID列表
+        List<SysDepartmentUser> deptUsers = sysDepartmentUserService.queryChain()
+                .eq(SysDepartmentUser::getDeptId, departmentId)
+                .list();
+        if (CollUtil.isEmpty(deptUsers)) {
+            return Collections.emptyList();
+        }
+        List<String> userIds = deptUsers.stream()
+                .map(SysDepartmentUser::getUserId)
+                .collect(Collectors.toList());
+        // 根据用户ID列表查询用户信息
+        return sysUserService.queryChain()
+                .in(SysUser::getId, userIds)
+                .list();
+    }
+
+}

+ 19 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/service/impl/SysDepartmentUserServiceImpl.java

@@ -0,0 +1,19 @@
+package com.ema.admin.modules.system.service.impl;
+
+import com.mybatisflex.spring.service.impl.ServiceImpl;
+import com.ema.admin.modules.system.entity.SysDepartmentUser;
+import com.ema.admin.modules.system.mapper.SysDepartmentUserMapper;
+import com.ema.admin.modules.system.service.SysDepartmentUserService;
+import org.springframework.stereotype.Service;
+
+
+/**
+ *  部门用户关联表 服务层实现。
+ *
+ * @author LIJIAN
+ * @since 2026-05-12
+ */
+@Service
+public class SysDepartmentUserServiceImpl extends ServiceImpl<SysDepartmentUserMapper, SysDepartmentUser>  implements SysDepartmentUserService {
+
+}

+ 39 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/service/impl/SysDictDataServiceImpl.java

@@ -0,0 +1,39 @@
+package com.ema.admin.modules.system.service.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.mybatisflex.core.paginate.Page;
+import com.mybatisflex.core.query.QueryWrapper;
+import com.mybatisflex.spring.service.impl.ServiceImpl;
+import com.ema.admin.modules.system.entity.SysDictData;
+import com.ema.admin.modules.system.mapper.SysDictDataMapper;
+import com.ema.admin.modules.system.service.SysDictDataService;
+import com.ema.admin.modules.system.vo.SysDictDataQueryVo;
+import org.springframework.stereotype.Service;
+
+/**
+ * 系统字典值表 服务层实现。
+ *
+ * @author lijian
+ * @since 2025-07-31
+ */
+@Service
+public class SysDictDataServiceImpl extends ServiceImpl<SysDictDataMapper, SysDictData>  implements SysDictDataService {
+
+    @Override
+    public Page<SysDictData> page(SysDictDataQueryVo queryVo) {
+        QueryWrapper queryWrapper = QueryWrapper.create();
+        queryWrapper.eq(SysDictData::getTypeCode, queryVo.getTypeCode(), StrUtil.isNotBlank(queryVo.getTypeCode()));
+        queryWrapper.like(SysDictData::getValue, queryVo.getValue(), StrUtil.isNotBlank(queryVo.getValue()));
+        queryWrapper.like(SysDictData::getCode, queryVo.getCode(), StrUtil.isNotBlank(queryVo.getCode()));
+        queryWrapper.eq(SysDictData::getStatus, queryVo.getStatus(), queryVo.getStatus() != null);
+        queryWrapper.orderBy(SysDictData::getSort);
+        return this.page( new Page<>(queryVo.getPageNumber(), queryVo.getPageSize()),queryWrapper);
+    }
+
+    @Override
+    public SysDictData getByCode(String dictTypeCode, String dictCode) {
+        return this.queryChain()
+                .eq(SysDictData::getTypeCode, dictTypeCode)
+                .eq(SysDictData::getCode, dictCode).one();
+    }
+}

+ 30 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/service/impl/SysDictTypeServiceImpl.java

@@ -0,0 +1,30 @@
+package com.ema.admin.modules.system.service.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.mybatisflex.core.paginate.Page;
+import com.mybatisflex.core.query.QueryWrapper;
+import com.mybatisflex.spring.service.impl.ServiceImpl;
+import com.ema.admin.modules.system.entity.SysDictType;
+import com.ema.admin.modules.system.mapper.SysDictTypeMapper;
+import com.ema.admin.modules.system.service.SysDictTypeService;
+import com.ema.admin.modules.system.vo.SysDictTypeQueryVo;
+import org.springframework.stereotype.Service;
+
+/**
+ * 系统字典类型表 服务层实现。
+ *
+ * @author lijian
+ * @since 2025-07-31
+ */
+@Service
+public class SysDictTypeServiceImpl extends ServiceImpl<SysDictTypeMapper, SysDictType>  implements SysDictTypeService {
+
+    @Override
+    public Page<SysDictType> page(SysDictTypeQueryVo queryVo) {
+        QueryWrapper queryWrapper = QueryWrapper.create();
+        queryWrapper.like(SysDictType::getName, queryVo.getName(), StrUtil.isNotBlank(queryVo.getName()));
+        queryWrapper.like(SysDictType::getCode, queryVo.getCode(), StrUtil.isNotBlank(queryVo.getCode()));
+        queryWrapper.orderBy(SysDictType::getSort);
+        return this.page(new Page<>(queryVo.getPageNumber(), queryVo.getPageSize()), queryWrapper);
+    }
+}

+ 78 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/service/impl/SysEmailServiceImpl.java

@@ -0,0 +1,78 @@
+package com.ema.admin.modules.system.service.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.ema.admin.modules.system.entity.SysDictData;
+import com.ema.admin.modules.system.service.SysDictDataService;
+import com.ema.admin.modules.system.service.SysEmailService;
+import com.ema.common.consts.CommonConst;
+import com.ema.common.exceptions.MyAssert;
+import com.ema.common.tools.email.EmailService;
+import com.ema.common.tools.redis.RedisService;
+import com.ema.common.utils.CodeUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 邮件服务实现
+ */
+@Slf4j
+@Service
+public class SysEmailServiceImpl implements SysEmailService {
+
+    @Resource
+    private RedisService redisService;
+    @Resource
+    private EmailService emailService;
+    @Resource
+    private SysDictDataService sysDictDataService;
+    @Resource(name = CommonConst.ASYNC_POOL)
+    private ThreadPoolTaskExecutor threadPoolExecutor;
+
+    /**
+     * 验证码缓存key前缀
+     */
+    private static final String EMAIL_CODE_PREFIX = "email:code";
+
+    /**
+     * 验证码有效期(分钟)
+     */
+    private static final long CODE_EXPIRE_MINUTES = 15;
+
+    /**
+     * 验证码长度
+     */
+    private static final int CODE_LENGTH = 6;
+
+    @Override
+    public void sendCode(String dictTypeCode, String dictCode, String email) {
+        // 生成6位随机验证码
+        String code = CodeUtil.getCode(CODE_LENGTH);
+        // 缓存验证码到Redis,有效期15分钟
+        redisService.set(EMAIL_CODE_PREFIX, email, code, CODE_EXPIRE_MINUTES * 60);
+
+        SysDictData dictData = sysDictDataService.getByCode(dictTypeCode, dictCode);
+        MyAssert.notNull(dictData, "数据模板不存在");
+
+        threadPoolExecutor.execute(() -> {
+            Map<String, Object> map = new HashMap<>();
+            map.put("code", code);
+            map.put("expireTime", CODE_EXPIRE_MINUTES + "分钟");
+            String content = StrUtil.format(dictData.getValue(), map);
+            emailService.sendText(email, dictData.getRemark(), content);
+        });
+    }
+
+    @Override
+    public void verifyCode(String email, String code) {
+        String cachedCode = redisService.getStr(EMAIL_CODE_PREFIX, email);
+        MyAssert.isTrue(StrUtil.isNotBlank(cachedCode), "验证码已过期或不存在,请重新获取!");
+        MyAssert.isTrue(StrUtil.equalsIgnoreCase(cachedCode, code), "验证码不正确,请重新输入!");
+        redisService.delete(EMAIL_CODE_PREFIX, email);
+    }
+
+}

+ 87 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/service/impl/SysFileRecorderServiceImpl.java

@@ -0,0 +1,87 @@
+package com.ema.admin.modules.system.service.impl;
+
+import com.ema.admin.modules.system.converter.SysFileConverter;
+import com.ema.admin.modules.system.entity.SysFile;
+import com.ema.admin.modules.system.service.SysFileService;
+import com.ema.common.exceptions.MyAssert;
+import lombok.SneakyThrows;
+import org.dromara.x.file.storage.core.FileInfo;
+import org.dromara.x.file.storage.core.recorder.FileRecorder;
+import org.dromara.x.file.storage.core.upload.FilePartInfo;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+
+/**
+ * 文件信息记录器
+ * @author lijian
+ */
+@Service
+public class SysFileRecorderServiceImpl implements FileRecorder {
+
+    @Resource
+    private SysFileService sysFileService;
+    @Resource
+    private SysFileConverter sysFileConverter;
+    
+    /**
+     * 保存文件信息到数据库
+     */
+    @SneakyThrows
+    @Override
+    public boolean save(FileInfo info) {
+        SysFile sysFile = sysFileConverter.toSysFile(info);
+        boolean save = sysFileService.save(sysFile);
+        MyAssert.isTrue(save, "文件信息保存失败");
+        info.setId(sysFile.getId());
+        return save;
+    }
+
+    /**
+     * 更新文件记录,可以根据文件 ID 或 URL 来更新文件记录,
+     * 主要用在手动分片上传文件-完成上传,作用是更新文件信息
+     */
+    @SneakyThrows
+    @Override
+    public void update(FileInfo info) {
+        SysFile sysFile = sysFileConverter.toSysFile(info);
+        sysFileService.updateById(sysFile);
+    }
+
+    /**
+     * 根据 url 查询文件信息
+     */
+    @SneakyThrows
+    @Override
+    public FileInfo getByUrl(String url) {
+        SysFile sysFile = sysFileService.queryChain().eq(SysFile::getUrl, url).one();
+        return sysFileConverter.toFileInfo(sysFile);
+    }
+
+    /**
+     * 根据 url 删除文件信息
+     */
+    @Override
+    public boolean delete(String url) {
+        sysFileService.remove(sysFileService.queryChain().eq(SysFile::getUrl, url));
+        return true;
+    }
+
+    /**
+     * 保存文件分片信息
+     * @param filePartInfo 文件分片信息
+     */
+    @Override
+    public void saveFilePart(FilePartInfo filePartInfo) {
+
+    }
+
+    /**
+     * 删除文件分片信息
+     */
+    @Override
+    public void deleteFilePartByUploadId(String uploadId) {
+
+    }
+
+}

+ 22 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/service/impl/SysFileServiceImpl.java

@@ -0,0 +1,22 @@
+package com.ema.admin.modules.system.service.impl;
+
+import com.mybatisflex.spring.service.impl.ServiceImpl;
+import com.ema.admin.modules.system.entity.SysFile;
+import com.ema.admin.modules.system.mapper.SysFileMapper;
+import com.ema.admin.modules.system.service.SysFileService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 系统文件信息表 服务层实现。
+ *
+ * @author lijian
+ * @since 2026-03-23
+ */
+@Service
+public class SysFileServiceImpl extends ServiceImpl<SysFileMapper, SysFile>  implements SysFileService{
+
+    @Override
+    public SysFile getByUrl(String url) {
+        return this.queryChain().eq(SysFile::getUrl, url).one();
+    }
+}

+ 93 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/service/impl/SysMenuServiceImpl.java

@@ -0,0 +1,93 @@
+package com.ema.admin.modules.system.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import com.mybatisflex.core.paginate.Page;
+import com.mybatisflex.core.query.QueryWrapper;
+import com.mybatisflex.spring.service.impl.ServiceImpl;
+import com.ema.admin.modules.system.converter.SysMenuConverter;
+import com.ema.admin.modules.system.entity.SysMenu;
+import com.ema.admin.modules.system.entity.SysRole;
+import com.ema.admin.modules.system.entity.SysRoleMenu;
+import com.ema.admin.modules.system.mapper.SysMenuMapper;
+import com.ema.admin.modules.system.service.SysMenuService;
+import com.ema.admin.modules.system.service.SysRoleMenuService;
+import com.ema.admin.modules.system.vo.SysMenuQueryVo;
+import com.ema.admin.modules.system.vo.SysMenuVo;
+import com.ema.common.domain.enums.BooleanEnum;
+import com.ema.common.utils.TreeUtil;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * 菜单权限表 服务层实现。
+ *
+ * @author LIJIAN
+ * @since 2026-03-23
+ */
+@Service
+public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu>  implements SysMenuService{
+
+    @Resource
+    private SysRoleMenuService sysRoleMenuService;
+    @Resource
+    private SysMenuConverter sysMenuConverter;
+
+    @Override
+    public List<SysMenu> getMenuByRoles(List<SysRole> sysRoles) {
+        List<String> roleIds = sysRoles.stream().map(SysRole::getId).distinct().collect(Collectors.toList());
+        return this.getMenuByRoleIds(roleIds);
+    }
+
+    @Override
+    public List<SysMenu> getMenuByRoleIds(List<String> roleIds) {
+        List<SysRoleMenu> roleMenus = sysRoleMenuService.queryChain()
+                .in(SysRoleMenu::getRoleId, roleIds).list();
+        if (CollUtil.isEmpty(roleMenus)) {
+            return CollUtil.newArrayList();
+        }
+        List<String> menuIds = roleMenus.stream()
+                .map(SysRoleMenu::getMenuId)
+                .distinct().collect(Collectors.toList());
+        return this.queryChain().in(SysMenu::getId, menuIds)
+                .eq(SysMenu::getStatus, BooleanEnum.TRUE.getValue())
+                .orderBy(SysMenu::getSort, true).list();
+    }
+
+    @Override
+    public Page<SysMenu> page(SysMenuQueryVo queryVo) {
+        QueryWrapper queryWrapper = QueryWrapper.create();
+        queryWrapper.like(SysMenu::getMenuName, queryVo.getMenuName(), StrUtil.isNotBlank(queryVo.getMenuName()));
+        queryWrapper.eq(SysMenu::getParentId, queryVo.getParentId(), StrUtil.isNotBlank(queryVo.getParentId()));
+        queryWrapper.eq(SysMenu::getMenuType, queryVo.getMenuType(), StrUtil.isNotBlank(queryVo.getMenuType()));
+        queryWrapper.eq(SysMenu::getVisible, queryVo.getVisible(), StrUtil.isNotBlank(queryVo.getVisible()));
+        queryWrapper.eq(SysMenu::getStatus, queryVo.getStatus(), StrUtil.isNotBlank(queryVo.getStatus()));
+        queryWrapper.orderBy(SysMenu::getSort);
+        return this.page(new Page<>(queryVo.getPageNumber(), queryVo.getPageSize()), queryWrapper);
+    }
+
+    @Override
+    public List<SysMenuVo> getTree(String status, String roleId) {
+        List<SysMenu> menuList = this.getList(status);
+        if (CollUtil.isEmpty(menuList)) return CollUtil.newArrayList();
+
+        List<String> childIds = null;
+        if (StrUtil.isNotBlank(roleId)) {
+            childIds = sysRoleMenuService.queryChain()
+                    .eq(SysRoleMenu::getRoleId, roleId).list()
+                    .stream().map(SysRoleMenu::getMenuId).collect(Collectors.toList());
+        }
+        return TreeUtil.buildTree(sysMenuConverter.toSysMenuVo(menuList), childIds);
+    }
+
+    @Override
+    public List<SysMenu> getList(String status) {
+        return this.queryChain()
+                .eq(SysMenu::getStatus, status, Objects.nonNull(status))
+                .orderBy(SysMenu::getSort).asc().list();
+    }
+}

+ 35 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/service/impl/SysOperLogServiceImpl.java

@@ -0,0 +1,35 @@
+package com.ema.admin.modules.system.service.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.mybatisflex.core.paginate.Page;
+import com.mybatisflex.core.query.QueryWrapper;
+import com.mybatisflex.spring.service.impl.ServiceImpl;
+import com.ema.admin.modules.system.entity.SysOperLog;
+import com.ema.admin.modules.system.entity.SysOperLogQueryVo;
+import com.ema.admin.modules.system.mapper.SysOperLogMapper;
+import com.ema.admin.modules.system.service.SysOperLogService;
+import org.springframework.stereotype.Service;
+
+
+/**
+ * 操作日志记录 服务层实现。
+ *
+ * @author LIJIAN
+ * @since 2026-03-25
+ */
+@Service
+public class SysOperLogServiceImpl extends ServiceImpl<SysOperLogMapper, SysOperLog> implements SysOperLogService {
+
+    @Override
+    public Page<SysOperLog> page(SysOperLogQueryVo queryVo) {
+        QueryWrapper queryWrapper = QueryWrapper.create();
+        queryWrapper.eq(SysOperLog::getType, queryVo.getType(), StrUtil.isNotBlank(queryVo.getType()));
+        queryWrapper.eq(SysOperLog::getModule, queryVo.getModule(), StrUtil.isNotBlank(queryVo.getModule()));
+        queryWrapper.like(SysOperLog::getTitle, queryVo.getTitle(), StrUtil.isNotBlank(queryVo.getTitle()));
+        queryWrapper.like(SysOperLog::getUrl, queryVo.getUrl(), StrUtil.isNotBlank(queryVo.getUrl()));
+        queryWrapper.eq(SysOperLog::getStatus, queryVo.getStatus(), StrUtil.isNotBlank(queryVo.getStatus()));
+        queryWrapper.eq(SysOperLog::getUserId, queryVo.getUserId(), StrUtil.isNotBlank(queryVo.getUserId()));
+        queryWrapper.orderBy(SysOperLog::getOperTime, false);
+        return this.page(new Page<>(queryVo.getPageNumber(), queryVo.getPageSize()), queryWrapper);
+    }
+}

+ 18 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/service/impl/SysRoleMenuServiceImpl.java

@@ -0,0 +1,18 @@
+package com.ema.admin.modules.system.service.impl;
+
+import com.mybatisflex.spring.service.impl.ServiceImpl;
+import com.ema.admin.modules.system.entity.SysRoleMenu;
+import com.ema.admin.modules.system.mapper.SysRoleMenuMapper;
+import com.ema.admin.modules.system.service.SysRoleMenuService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 系统角色和菜单关联表 服务层实现。
+ *
+ * @author LIJIAN
+ * @since 2026-03-23
+ */
+@Service
+public class SysRoleMenuServiceImpl extends ServiceImpl<SysRoleMenuMapper, SysRoleMenu>  implements SysRoleMenuService{
+
+}

+ 79 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/service/impl/SysRoleServiceImpl.java

@@ -0,0 +1,79 @@
+package com.ema.admin.modules.system.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import com.mybatisflex.core.paginate.Page;
+import com.mybatisflex.core.query.QueryWrapper;
+import com.mybatisflex.spring.service.impl.ServiceImpl;
+import com.ema.admin.modules.system.entity.SysRole;
+import com.ema.admin.modules.system.entity.SysRoleMenu;
+import com.ema.admin.modules.system.entity.SysUserRole;
+import com.ema.admin.modules.system.mapper.SysRoleMapper;
+import com.ema.admin.modules.system.service.*;
+import com.ema.admin.modules.system.vo.SysRoleQueryVo;
+import com.ema.common.exceptions.MyAssert;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 系统角色信息表 服务层实现。
+ *
+ * @author LIJIAN
+ * @since 2026-03-23
+ */
+@Service
+public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole>  implements SysRoleService{
+
+    @Resource
+    private SysUserService sysUserService;
+    @Resource
+    private SysUserRoleService sysUserRoleService;
+    @Resource
+    private SysMenuService sysMenuService;
+    @Resource
+    private SysRoleMenuService sysRoleMenuService;
+
+    @Override
+    public List<SysRole> getRoleByUserId(String userId) {
+        MyAssert.notNull(userId, "用户id不能为空");
+        //获取用户角色
+        List<SysUserRole> userRoles =  sysUserRoleService.queryChain().eq(SysUserRole::getUserId, userId).list();
+        if (CollUtil.isNotEmpty(userRoles)) {
+            List<String> roleIds = userRoles.stream().map(SysUserRole::getRoleId).collect(Collectors.toList());
+            return this.listByIds(CollUtil.distinct(roleIds));
+        }
+        return CollUtil.newArrayList();
+    }
+
+    @Override
+    public Page<SysRole> page(SysRoleQueryVo queryVo) {
+        QueryWrapper queryWrapper = QueryWrapper.create();
+        queryWrapper.like(SysRole::getRoleName, queryVo.getRoleName(), StrUtil.isNotBlank(queryVo.getRoleName()));
+        queryWrapper.like(SysRole::getRoleKey, queryVo.getRoleKey(), StrUtil.isNotBlank(queryVo.getRoleKey()));
+        queryWrapper.eq(SysRole::getStatus, queryVo.getStatus(), StrUtil.isNotBlank(queryVo.getStatus()));
+        return this.page(new Page<>(queryVo.getPageNumber(), queryVo.getPageSize()), queryWrapper);
+    }
+
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void assignMenusToRole(String roleId, List<String> menuIds) {
+        // 先删除角色菜单关联
+        sysRoleMenuService.remove(sysRoleMenuService.queryChain()
+                .eq(SysRoleMenu::getRoleId, roleId));
+
+        // 批量创建角色菜单关联
+        List<SysRoleMenu> roleMenus = menuIds.stream()
+                .map(menuId -> SysRoleMenu.builder()
+                        .roleId(roleId)
+                        .menuId(menuId)
+                        .build())
+                .collect(Collectors.toList());
+        sysRoleMenuService.saveBatch(roleMenus);
+    }
+
+}

+ 73 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/service/impl/SysTimersJobServiceImpl.java

@@ -0,0 +1,73 @@
+package com.ema.admin.modules.system.service.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.mybatisflex.core.paginate.Page;
+import com.mybatisflex.core.query.QueryWrapper;
+import com.mybatisflex.spring.service.impl.ServiceImpl;
+import com.ema.admin.modules.system.entity.SysTimersJob;
+import com.ema.admin.modules.system.enums.SysTimerJobEnum;
+import com.ema.admin.modules.system.mapper.SysTimersJobMapper;
+import com.ema.admin.modules.system.service.SysTimersJobService;
+import com.ema.admin.modules.system.vo.SysTimersJobQueryVo;
+import com.ema.common.domain.enums.BooleanEnum;
+import com.ema.common.tools.timer.TimerExeService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 定时任务 服务层实现。
+ *
+ * @author LIJIAN
+ * @since 2026-03-24
+ */
+@Slf4j
+@Service
+public class SysTimersJobServiceImpl extends ServiceImpl<SysTimersJobMapper, SysTimersJob>  implements SysTimersJobService{
+
+    @Resource
+    private TimerExeService timerExeService;
+
+    @Override
+    public Page<SysTimersJob> page(SysTimersJobQueryVo queryVo) {
+        QueryWrapper queryWrapper = QueryWrapper.create();
+        queryWrapper.like(SysTimersJob::getTimerName, queryVo.getTimerName(), StrUtil.isNotBlank(queryVo.getTimerName()));
+        queryWrapper.like(SysTimersJob::getActionClass, queryVo.getActionClass(), StrUtil.isNotBlank(queryVo.getActionClass()));
+        queryWrapper.eq(SysTimersJob::getJobStatus, queryVo.getJobStatus(), StrUtil.isNotBlank(queryVo.getJobStatus()));
+        queryWrapper.eq(SysTimersJob::getLastStatus, queryVo.getLastStatus(), StrUtil.isNotBlank(queryVo.getLastStatus()));
+        return this.page(new Page<>(queryVo.getPageNumber(), queryVo.getPageSize()), queryWrapper);
+    }
+
+
+    @Override
+    public void initAllTimerJob() {
+        // 获取所有开启状态的任务
+        List<SysTimersJob> list = this.queryChain()
+                .eq(SysTimersJob::getJobStatus, SysTimerJobEnum.Status.RUNNING.getCode())
+                .list();
+        // 添加定时任务到调度器(使用顺序流,避免并行导致调度器启动冲突)
+        list.forEach(sysTimersJob -> {
+            try {
+                timerExeService.startTimer(sysTimersJob.getId(), sysTimersJob.getCron(), sysTimersJob.getActionClass());
+                log.info(">>>>>> ✓ 定时任务恢复成功:[{}] {}", sysTimersJob.getId(), sysTimersJob.getTimerName());
+            } catch (Exception e) {
+                log.error(">>>>>> ✗ 定时任务启动失败:[{}] {} - {}", sysTimersJob.getId(), sysTimersJob.getTimerName(), e.getMessage());
+                this.updateActionResult(sysTimersJob.getId(), false, "定时任务启动失败: "+e.getMessage());
+            }
+        });
+    }
+
+    @Override
+    public void updateActionResult(String taskId, boolean isSuccess, String msg) {
+        this.updateChain().set(SysTimersJob::getLastTime, new Date())
+                .set(SysTimersJob::getLastStatus, isSuccess ? BooleanEnum.TRUE.getValue() : BooleanEnum.FALSE.getValue())
+                .set(SysTimersJob::getLastMsg, msg)
+                .eq(SysTimersJob::getId, taskId)
+                .eq(SysTimersJob::getUpdateBy,"system")
+                .update();
+    }
+
+}

+ 18 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/service/impl/SysUserRoleServiceImpl.java

@@ -0,0 +1,18 @@
+package com.ema.admin.modules.system.service.impl;
+
+import com.mybatisflex.spring.service.impl.ServiceImpl;
+import com.ema.admin.modules.system.entity.SysUserRole;
+import com.ema.admin.modules.system.mapper.SysUserRoleMapper;
+import com.ema.admin.modules.system.service.SysUserRoleService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 系统用户和角色关联表 服务层实现。
+ *
+ * @author LIJIAN
+ * @since 2026-03-23
+ */
+@Service
+public class SysUserRoleServiceImpl extends ServiceImpl<SysUserRoleMapper, SysUserRole>  implements SysUserRoleService{
+
+}

+ 96 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/service/impl/SysUserServiceImpl.java

@@ -0,0 +1,96 @@
+package com.ema.admin.modules.system.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import com.mybatisflex.core.paginate.Page;
+import com.mybatisflex.core.query.QueryWrapper;
+import com.mybatisflex.spring.service.impl.ServiceImpl;
+import com.ema.admin.modules.system.converter.SysUserConverter;
+import com.ema.admin.modules.system.entity.SysRole;
+import com.ema.admin.modules.system.entity.SysUser;
+import com.ema.admin.modules.system.entity.SysUserRole;
+import com.ema.admin.modules.system.mapper.SysUserMapper;
+import com.ema.admin.modules.system.service.SysRoleService;
+import com.ema.admin.modules.system.service.SysUserRoleService;
+import com.ema.admin.modules.system.service.SysUserService;
+import com.ema.admin.modules.system.vo.SysUserInfoVo;
+import com.ema.admin.modules.system.vo.SysUserQueryVo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 系统用户信息表 服务层实现。
+ *
+ * @author LIJIAN
+ * @since 2026-03-23
+ */
+@Service
+public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser>  implements SysUserService{
+
+    @Resource
+    private SysUserRoleService sysUserRoleService;
+    @Autowired
+    private SysUserConverter sysUserConverter;
+    @Autowired
+    private SysRoleService sysRoleService;
+
+    @Override
+    public SysUser getByAccount(String account) {
+        return this.queryChain().eq(SysUser::getAccount, account).one();
+    }
+
+    @Override
+    public void saveUserRole(String userId, String roleId) {
+        SysUserRole sysUserRole = SysUserRole.builder()
+                .userId(userId)
+                .roleId(roleId)
+                .build();
+        sysUserRoleService.save(sysUserRole);
+    }
+
+    @Override
+    public Page<SysUserInfoVo> page(SysUserQueryVo queryVo) {
+        Page<SysUser> userPage = pageSysUser(queryVo);
+        List<SysUserInfoVo> voList = userPage.getRecords().stream().map(sysUser -> {
+            List<SysRole> roleList = sysRoleService.getRoleByUserId(sysUser.getId());
+            return sysUserConverter.toSysUserInfoVo(sysUser,roleList);
+        }).collect(Collectors.toList());
+        return new Page(voList,  userPage.getPageNumber(), userPage.getPageSize(), userPage.getTotalRow());
+    }
+
+    @Override
+    public Page<SysUser> pageSysUser(SysUserQueryVo queryVo) {
+        QueryWrapper queryWrapper = QueryWrapper.create();
+        queryWrapper.like(SysUser::getAccount, queryVo.getAccount(), StrUtil.isNotBlank(queryVo.getAccount()));
+        queryWrapper.like(SysUser::getName, queryVo.getName(), StrUtil.isNotBlank(queryVo.getName()));
+        queryWrapper.like(SysUser::getPhone, queryVo.getPhone(), StrUtil.isNotBlank(queryVo.getPhone()));
+        queryWrapper.like(SysUser::getEmail, queryVo.getEmail(), StrUtil.isNotBlank(queryVo.getEmail()));
+        queryWrapper.eq(SysUser::getStatus, queryVo.getStatus(), StrUtil.isNotBlank(queryVo.getStatus()));
+        return this.page(new Page<>(queryVo.getPageNumber(), queryVo.getPageSize()), queryWrapper);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void assignRolesToUser(String userId, List<String> roleIds) {
+
+        // 先删除用户角色关联
+        sysUserRoleService.remove(sysUserRoleService.queryChain()
+                .eq(SysUserRole::getUserId, userId));
+
+        if(CollUtil.isNotEmpty(roleIds)){
+            // 批量创建用户角色关联
+            List<SysUserRole> userRoles = roleIds.stream()
+                    .map(roleId -> SysUserRole.builder()
+                            .userId(userId)
+                            .roleId(roleId)
+                            .build())
+                    .collect(Collectors.toList());
+            sysUserRoleService.saveBatch(userRoles);
+        }
+    }
+}

+ 44 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/vo/RoleMenuAssignVO.java

@@ -0,0 +1,44 @@
+package com.ema.admin.modules.system.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotEmpty;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 角色菜单权限分配 VO。
+ *
+ * @author LIJIAN
+ * @since 2026-03-24
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@ApiModel("角色菜单权限分配")
+public class RoleMenuAssignVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 角色ID
+     */
+    @ApiModelProperty(value = "角色ID", required = true)
+    @NotBlank(message = "角色ID不能为空")
+    private String roleId;
+
+    /**
+     * 菜单ID列表
+     */
+    @ApiModelProperty(value = "菜单ID列表", required = true)
+    @NotEmpty(message = "菜单ID列表不能为空")
+    private List<String> menuIds;
+
+}

+ 28 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/vo/SendEmailCodeVo.java

@@ -0,0 +1,28 @@
+package com.ema.admin.modules.system.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.Email;
+import javax.validation.constraints.NotBlank;
+import java.io.Serializable;
+
+@ApiModel(value = "SendEmailCodeVo", description = "发送邮箱验证码请求参数")
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class SendEmailCodeVo implements Serializable {
+
+    @ApiModelProperty(value = "邮箱地址", required = true)
+    @NotBlank(message = "邮箱不能为空")
+    @Email(message = "邮箱格式不正确")
+    private String email;
+
+    @ApiModelProperty(value = "模板Code:注册 EMAIL_REGISTER_CODE、重置密码 EMAIL_RESET_PWD_CODE", required = true)
+    @NotBlank(message = "模板Code不能为空")
+    private String code;
+
+}

+ 57 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/vo/SysDepartmentQueryVo.java

@@ -0,0 +1,57 @@
+package com.ema.admin.modules.system.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 部门信息查询条件 VO。
+ *
+ * @author LIJIAN
+ * @since 2026-05-12
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@ApiModel("部门信息查询条件")
+public class SysDepartmentQueryVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 当前页。
+     */
+    @ApiModelProperty("当前页")
+    private long pageNumber = 1;
+
+    /**
+     * 每页数据数量。
+     */
+    @ApiModelProperty("每页数据数量")
+    private long pageSize = 10;
+
+    /**
+     * 部门编码(支持模糊查询)
+     */
+    @ApiModelProperty("部门编码")
+    private String deptCode;
+
+    /**
+     * 部门名称(支持模糊查询)
+     */
+    @ApiModelProperty("部门名称")
+    private String deptName;
+
+    /**
+     * 上级部门ID
+     */
+    @ApiModelProperty("上级部门ID")
+    private Integer parentId;
+
+}

+ 84 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/vo/SysDepartmentVo.java

@@ -0,0 +1,84 @@
+package com.ema.admin.modules.system.vo;
+
+import com.ema.common.utils.TreeNode;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 部门信息 VO。
+ *
+ * @author LIJIAN
+ * @since 2026-05-12
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@ApiModel("部门信息")
+public class SysDepartmentVo implements TreeNode<SysDepartmentVo, String>, Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 部门id
+     */
+    @ApiModelProperty("部门id")
+    private String id;
+
+    /**
+     * 部门编码
+     */
+    @ApiModelProperty("部门编码")
+    private String deptCode;
+
+    /**
+     * 部门名称
+     */
+    @ApiModelProperty("部门名称")
+    private String deptName;
+
+    /**
+     * 上级部门ID (0为顶级部门)
+     */
+    @ApiModelProperty("上级部门ID")
+    private String parentId;
+
+    /**
+     * 显示顺序
+     */
+    @ApiModelProperty("显示顺序")
+    private Integer sort;
+
+    /**
+     * 下级部门
+     */
+    @ApiModelProperty("下级部门")
+    private List<SysDepartmentVo> children;
+
+    @Override
+    public void addChild(SysDepartmentVo child) {
+        if (children == null) {
+            children = new ArrayList<>();
+        }
+        children.add(child);
+    }
+
+    @Override
+    public void setChecked(boolean checked) {
+        // 部门树暂不支持选中状态
+    }
+
+    @Override
+    public int compareTo(SysDepartmentVo other) {
+        return this.sort == null || other.sort == null ? 0 : this.sort - other.sort;
+    }
+
+}

+ 63 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/vo/SysDictDataQueryVo.java

@@ -0,0 +1,63 @@
+package com.ema.admin.modules.system.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 系统字典值表查询条件 VO。
+ *
+ * @author lijian
+ * @since 2025-07-31
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@ApiModel("系统字典值表查询条件")
+public class SysDictDataQueryVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 当前页。
+     */
+    @ApiModelProperty("当前页")
+    private long pageNumber = 1;
+
+    /**
+     * 每页数据数量。
+     */
+    @ApiModelProperty("每页数据数量")
+    private long pageSize = 10;
+
+    /**
+     * 字典类型
+     */
+    @ApiModelProperty("字典类型")
+    private String typeCode;
+
+    /**
+     * 值(支持模糊查询)
+     */
+    @ApiModelProperty("值")
+    private String value;
+
+    /**
+     * 编码(支持模糊查询)
+     */
+    @ApiModelProperty("编码")
+    private String code;
+
+    /**
+     * 状态(0 停用 1 正常)
+     */
+    @ApiModelProperty("状态(0 停用 1 正常)")
+    private Integer status;
+
+}

+ 51 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/vo/SysDictTypeQueryVo.java

@@ -0,0 +1,51 @@
+package com.ema.admin.modules.system.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 系统字典类型表查询条件 VO。
+ *
+ * @author lijian
+ * @since 2026-03-24
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@ApiModel("系统字典类型表查询条件")
+public class SysDictTypeQueryVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 当前页。
+     */
+    @ApiModelProperty("当前页")
+    private long pageNumber = 1;
+
+    /**
+     * 每页数据数量。
+     */
+    @ApiModelProperty("每页数据数量")
+    private long pageSize = 10;
+
+    /**
+     * 名称(支持模糊查询)
+     */
+    @ApiModelProperty("名称")
+    private String name;
+
+    /**
+     * 编码(支持模糊查询)
+     */
+    @ApiModelProperty("编码")
+    private String code;
+
+}

+ 69 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/vo/SysMenuQueryVo.java

@@ -0,0 +1,69 @@
+package com.ema.admin.modules.system.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 菜单权限表查询条件 VO。
+ *
+ * @author LIJIAN
+ * @since 2026-03-24
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@ApiModel("菜单权限表查询条件")
+public class SysMenuQueryVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 当前页。
+     */
+    @ApiModelProperty("当前页")
+    private long pageNumber = 1;
+
+    /**
+     * 每页数据数量。
+     */
+    @ApiModelProperty("每页数据数量")
+    private long pageSize = 10;
+
+    /**
+     * 菜单名称(支持模糊查询)
+     */
+    @ApiModelProperty("菜单名称")
+    private String menuName;
+
+    /**
+     * 父菜单ID
+     */
+    @ApiModelProperty("父菜单ID")
+    private String parentId;
+
+    /**
+     * 菜单类型 (M目录 C菜单 F按钮)
+     */
+    @ApiModelProperty("菜单类型 (M目录 C菜单 F按钮)")
+    private String menuType;
+
+    /**
+     * 显示状态 (0隐藏,1显示)
+     */
+    @ApiModelProperty("显示状态 (0隐藏,1显示)")
+    private String visible;
+
+    /**
+     * 菜单状态 (0停用 1正常)
+     */
+    @ApiModelProperty("菜单状态 (0停用 1正常)")
+    private String status;
+
+}

+ 144 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/vo/SysMenuVo.java

@@ -0,0 +1,144 @@
+package com.ema.admin.modules.system.vo;
+
+import com.ema.common.utils.TreeNode;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 菜单权限表 实体类。
+ *
+ * @author LIJIAN
+ * @since 2026-03-23
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@ApiModel("菜单权限表")
+public class SysMenuVo implements TreeNode<SysMenuVo, String>, Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 菜单ID
+     */
+    @ApiModelProperty("菜单ID")
+    private String id;
+
+    /**
+     * 菜单名称
+     */
+    @ApiModelProperty("菜单名称")
+    private String menuName;
+
+    /**
+     * 父菜单ID (0为顶级菜单)
+     */
+    @ApiModelProperty("父菜单ID (0为顶级菜单)")
+    private String parentId;
+
+    /**
+     * 显示顺序
+     */
+    @ApiModelProperty("显示顺序")
+    private Integer sort;
+
+    /**
+     * 前端路由地址
+     */
+    @ApiModelProperty("前端路由地址")
+    private String path;
+
+    /**
+     * 前端组件路径
+     */
+    @ApiModelProperty("前端组件路径")
+    private String component;
+
+    /**
+     * 权限标识 (例如: system:user:list)
+     */
+    @ApiModelProperty("权限标识 (例如: system:user:list)")
+    private String perms;
+
+    /**
+     * 菜单图标
+     */
+    @ApiModelProperty("菜单图标")
+    private String icon;
+
+    /**
+     * 是否为外链 (0否 1是)
+     */
+    @ApiModelProperty("是否为外链 (0否 1是)")
+    private String isFrame;
+
+    /**
+     * 菜单类型 (M目录 C菜单 F按钮)
+     */
+    @ApiModelProperty("菜单类型 (M目录 C菜单 F按钮)")
+    private String menuType;
+
+    /**
+     * 菜单状态 (0隐藏,1显示 )
+     */
+    @ApiModelProperty("菜单状态 (0隐藏,1显示 )")
+    private String visible;
+
+    /**
+     * 菜单状态 ((0停用 1正常)
+     */
+    @ApiModelProperty("菜单状态 (0停用 1正常)")
+    private String status;
+
+    /**
+     * 备注
+     */
+    @ApiModelProperty("备注")
+    private String remark;
+
+    @ApiModelProperty(value = "是否选中:0=否,1=是")
+    private Integer isCheck;
+
+    @ApiModelProperty(value = "下级权限")
+    private List<SysMenuVo> children;
+
+
+    /**
+     * 添加子菜单
+     *
+     * @param child 要添加的子菜单VO对象
+     */
+    @Override
+    public void addChild(SysMenuVo child) {
+        if (children == null) {
+            children = new ArrayList<>();
+        }
+        children.add(child);
+    }
+
+    @Override
+    public void setChecked(boolean checked) {
+        isCheck = checked ? 1 : 0;
+    }
+
+    /**
+     * 实现Tree接口的compareTo方法,用于菜单的排序
+     *
+     * @param other 另一个菜单VO对象
+     * @return 返回基于sort属性的比较结果
+     */
+    @Override
+    public int compareTo(SysMenuVo other) {
+        return this.sort == null || other.sort == null ? 0 : this.sort - other.sort;
+    }
+
+}

+ 66 - 0
ema-admin/src/main/java/com/ema/admin/modules/system/vo/SysRoleInfoVo.java

@@ -0,0 +1,66 @@
+package com.ema.admin.modules.system.vo;
+
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.KeyType;
+import com.mybatisflex.annotation.Table;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 系统角色信息表 实体类。
+ *
+ * @author LIJIAN
+ * @since 2026-03-23
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@ApiModel("系统角色信息InfoVo")
+@Table("sys_role")
+public class SysRoleInfoVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 角色ID
+     */
+    @Id(keyType = KeyType.Generator, value = "snowFlakeId")
+    @ApiModelProperty("角色ID")
+    private String id;
+
+    /**
+     * 角色名称
+     */
+    @ApiModelProperty("角色名称")
+    private String roleName;
+
+    /**
+     * 角色权限Key (例如: admin, common)
+     */
+    @ApiModelProperty("角色权限Key (例如: admin, common)")
+    private String roleKey;
+
+    /**
+     * 角色状态 (0停用 1正常)
+     */
+    @ApiModelProperty("角色状态 (0停用 1正常)")
+    private String status;
+
+    /**
+     * 备注
+     */
+    @ApiModelProperty("备注")
+    private String remark;
+
+    @ApiModelProperty(value = "菜单树")
+    private List<SysMenuVo> menuTree;
+
+}

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov