list = new ArrayList<>();
+ list.add("t1");
+ list.add("t2");
+ list.add("t3");
+ return list.stream();
+ }
+
+ @BeforeEach
+ public void testBeforeEach() {
+ System.out.println("@BeforeEach ==================");
+ }
+
+ @AfterEach
+ public void testAfterEach() {
+ System.out.println("@AfterEach ==================");
+ }
+
+
+}
diff --git a/fys-admin/src/test/java/com/fuyuanshen/test/TagUnitTest.java b/fys-admin/src/test/java/com/fuyuanshen/test/TagUnitTest.java
new file mode 100644
index 0000000..b4b0e50
--- /dev/null
+++ b/fys-admin/src/test/java/com/fuyuanshen/test/TagUnitTest.java
@@ -0,0 +1,54 @@
+package com.fuyuanshen.test;
+
+import org.junit.jupiter.api.*;
+import org.springframework.boot.test.context.SpringBootTest;
+
+/**
+ * 标签单元测试案例
+ *
+ * @author Lion Li
+ */
+@SpringBootTest
+@DisplayName("标签单元测试案例")
+public class TagUnitTest {
+
+ @Tag("dev")
+ @DisplayName("测试 @Tag dev")
+ @Test
+ public void testTagDev() {
+ System.out.println("dev");
+ }
+
+ @Tag("prod")
+ @DisplayName("测试 @Tag prod")
+ @Test
+ public void testTagProd() {
+ System.out.println("prod");
+ }
+
+ @Tag("local")
+ @DisplayName("测试 @Tag local")
+ @Test
+ public void testTagLocal() {
+ System.out.println("local");
+ }
+
+ @Tag("exclude")
+ @DisplayName("测试 @Tag exclude")
+ @Test
+ public void testTagExclude() {
+ System.out.println("exclude");
+ }
+
+ @BeforeEach
+ public void testBeforeEach() {
+ System.out.println("@BeforeEach ==================");
+ }
+
+ @AfterEach
+ public void testAfterEach() {
+ System.out.println("@AfterEach ==================");
+ }
+
+
+}
diff --git a/fys-common/fys-common-bom/pom.xml b/fys-common/fys-common-bom/pom.xml
new file mode 100644
index 0000000..d9eea50
--- /dev/null
+++ b/fys-common/fys-common-bom/pom.xml
@@ -0,0 +1,185 @@
+
+
+ 4.0.0
+
+ com.fuyuanshen
+ fys-common-bom
+ ${revision}
+ pom
+
+
+ fys-common-bom common依赖项
+
+
+
+ 5.4.0
+
+
+
+
+
+
+ com.fuyuanshen
+ fys-common-core
+ ${revision}
+
+
+
+
+ com.fuyuanshen
+ fys-common-doc
+ ${revision}
+
+
+
+
+ com.fuyuanshen
+ fys-common-excel
+ ${revision}
+
+
+
+
+ com.fuyuanshen
+ fys-common-idempotent
+ ${revision}
+
+
+
+
+ com.fuyuanshen
+ fys-common-job
+ ${revision}
+
+
+
+
+ com.fuyuanshen
+ fys-common-log
+ ${revision}
+
+
+
+
+ com.fuyuanshen
+ fys-common-mail
+ ${revision}
+
+
+
+
+ com.fuyuanshen
+ fys-common-mybatis
+ ${revision}
+
+
+
+
+ com.fuyuanshen
+ fys-common-oss
+ ${revision}
+
+
+
+
+ com.fuyuanshen
+ fys-common-ratelimiter
+ ${revision}
+
+
+
+
+ com.fuyuanshen
+ fys-common-redis
+ ${revision}
+
+
+
+
+ com.fuyuanshen
+ fys-common-satoken
+ ${revision}
+
+
+
+
+ com.fuyuanshen
+ fys-common-security
+ ${revision}
+
+
+
+
+ com.fuyuanshen
+ fys-common-sms
+ ${revision}
+
+
+
+ com.fuyuanshen
+ fys-common-social
+ ${revision}
+
+
+
+
+ com.fuyuanshen
+ fys-common-web
+ ${revision}
+
+
+
+
+ com.fuyuanshen
+ fys-common-translation
+ ${revision}
+
+
+
+
+ com.fuyuanshen
+ fys-common-sensitive
+ ${revision}
+
+
+
+
+ com.fuyuanshen
+ fys-common-json
+ ${revision}
+
+
+
+
+ com.fuyuanshen
+ fys-common-encrypt
+ ${revision}
+
+
+
+
+ com.fuyuanshen
+ fys-common-tenant
+ ${revision}
+
+
+
+
+ com.fuyuanshen
+ fys-common-websocket
+ ${revision}
+
+
+
+
+ com.fuyuanshen
+ fys-common-sse
+ ${revision}
+
+
+
+
+
+
diff --git a/fys-common/fys-common-core/pom.xml b/fys-common/fys-common-core/pom.xml
new file mode 100644
index 0000000..1ba5b46
--- /dev/null
+++ b/fys-common/fys-common-core/pom.xml
@@ -0,0 +1,143 @@
+
+
+
+ com.fuyuanshen
+ fys-common
+ ${revision}
+
+ 4.0.0
+
+ fys-common-core
+
+
+ fys-common-core 核心模块
+
+
+
+ 5.8.35
+
+
+
+
+
+ org.springframework
+ spring-context-support
+
+
+
+
+ org.springframework
+ spring-web
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+
+ org.springframework.boot
+ spring-boot-starter-aop
+
+
+
+
+ org.apache.commons
+ commons-lang3
+
+
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+
+
+
+ cn.hutool
+ hutool-core
+
+
+
+
+ cn.hutool
+ hutool-all
+ ${hutool.version}
+
+
+
+ cn.hutool
+ hutool-http
+
+
+
+ cn.hutool
+ hutool-extra
+
+
+
+
+ org.apache.poi
+ poi
+ 5.4.0
+
+
+ org.apache.poi
+ poi-ooxml
+ 5.4.0
+
+
+ xerces
+ xercesImpl
+ 2.12.2
+
+
+ org.apache.poi
+ poi-scratchpad
+ 5.4.0
+
+
+ org.apache.poi
+ poi-ooxml-full
+ 5.4.0
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+
+
+
+ org.springframework.boot
+ spring-boot-properties-migrator
+ runtime
+
+
+
+ io.github.linpeilie
+ mapstruct-plus-spring-boot-starter
+
+
+
+
+ org.lionsoul
+ ip2region
+
+
+ com.baomidou
+ mybatis-plus-core
+ 3.5.12
+ compile
+
+
+
+
+
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/config/ApplicationConfig.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/config/ApplicationConfig.java
new file mode 100644
index 0000000..caea7ec
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/config/ApplicationConfig.java
@@ -0,0 +1,17 @@
+package com.fuyuanshen.common.core.config;
+
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.scheduling.annotation.EnableAsync;
+
+/**
+ * 程序注解配置
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration
+@EnableAspectJAutoProxy
+@EnableAsync(proxyTargetClass = true)
+public class ApplicationConfig {
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/config/AsyncConfig.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/config/AsyncConfig.java
new file mode 100644
index 0000000..bda1b42
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/config/AsyncConfig.java
@@ -0,0 +1,52 @@
+package com.fuyuanshen.common.core.config;
+
+import cn.hutool.core.util.ArrayUtil;
+import com.fuyuanshen.common.core.exception.ServiceException;
+import com.fuyuanshen.common.core.utils.SpringUtils;
+import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.core.task.VirtualThreadTaskExecutor;
+import org.springframework.scheduling.annotation.AsyncConfigurer;
+
+import java.util.Arrays;
+import java.util.concurrent.Executor;
+
+/**
+ * 异步配置
+ *
+ * 如果未使用虚拟线程则生效
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration
+public class AsyncConfig implements AsyncConfigurer {
+
+ /**
+ * 自定义 @Async 注解使用系统线程池
+ */
+ @Override
+ public Executor getAsyncExecutor() {
+ if(SpringUtils.isVirtual()) {
+ return new VirtualThreadTaskExecutor("async-");
+ }
+ return SpringUtils.getBean("scheduledExecutorService");
+ }
+
+ /**
+ * 异步执行异常处理
+ */
+ @Override
+ public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
+ return (throwable, method, objects) -> {
+ throwable.printStackTrace();
+ StringBuilder sb = new StringBuilder();
+ sb.append("Exception message - ").append(throwable.getMessage())
+ .append(", Method name - ").append(method.getName());
+ if (ArrayUtil.isNotEmpty(objects)) {
+ sb.append(", Parameter value - ").append(Arrays.toString(objects));
+ }
+ throw new ServiceException(sb.toString());
+ };
+ }
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/config/ThreadPoolConfig.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/config/ThreadPoolConfig.java
new file mode 100644
index 0000000..f80c1eb
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/config/ThreadPoolConfig.java
@@ -0,0 +1,87 @@
+package com.fuyuanshen.common.core.config;
+
+import jakarta.annotation.PreDestroy;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.concurrent.BasicThreadFactory;
+import com.fuyuanshen.common.core.config.properties.ThreadPoolProperties;
+import com.fuyuanshen.common.core.utils.SpringUtils;
+import com.fuyuanshen.common.core.utils.Threads;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.task.VirtualThreadTaskExecutor;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * 线程池配置
+ *
+ * @author Lion Li
+ **/
+@Slf4j
+@AutoConfiguration
+@EnableConfigurationProperties(ThreadPoolProperties.class)
+public class ThreadPoolConfig {
+
+ /**
+ * 核心线程数 = cpu 核心数 + 1
+ */
+ private final int core = Runtime.getRuntime().availableProcessors() + 1;
+
+ private ScheduledExecutorService scheduledExecutorService;
+
+ @Bean(name = "threadPoolTaskExecutor")
+ @ConditionalOnProperty(prefix = "thread-pool", name = "enabled", havingValue = "true")
+ public ThreadPoolTaskExecutor threadPoolTaskExecutor(ThreadPoolProperties threadPoolProperties) {
+ ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+ executor.setCorePoolSize(core);
+ executor.setMaxPoolSize(core * 2);
+ executor.setQueueCapacity(threadPoolProperties.getQueueCapacity());
+ executor.setKeepAliveSeconds(threadPoolProperties.getKeepAliveSeconds());
+ executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+ return executor;
+ }
+
+ /**
+ * 执行周期性或定时任务
+ */
+ @Bean(name = "scheduledExecutorService")
+ protected ScheduledExecutorService scheduledExecutorService() {
+ // daemon 必须为 true
+ BasicThreadFactory.Builder builder = new BasicThreadFactory.Builder().daemon(true);
+ if (SpringUtils.isVirtual()) {
+ builder.namingPattern("virtual-schedule-pool-%d").wrappedFactory(new VirtualThreadTaskExecutor().getVirtualThreadFactory());
+ } else {
+ builder.namingPattern("schedule-pool-%d");
+ }
+ ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(core,
+ builder.build(),
+ new ThreadPoolExecutor.CallerRunsPolicy()) {
+ @Override
+ protected void afterExecute(Runnable r, Throwable t) {
+ super.afterExecute(r, t);
+ Threads.printException(r, t);
+ }
+ };
+ this.scheduledExecutorService = scheduledThreadPoolExecutor;
+ return scheduledThreadPoolExecutor;
+ }
+
+ /**
+ * 销毁事件
+ */
+ @PreDestroy
+ public void destroy() {
+ try {
+ log.info("====关闭后台任务任务线程池====");
+ Threads.shutdownAndAwaitTermination(scheduledExecutorService);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ }
+ }
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/config/ValidatorConfig.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/config/ValidatorConfig.java
new file mode 100644
index 0000000..7369a77
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/config/ValidatorConfig.java
@@ -0,0 +1,41 @@
+package com.fuyuanshen.common.core.config;
+
+import jakarta.validation.Validator;
+import org.hibernate.validator.HibernateValidator;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
+import org.springframework.context.MessageSource;
+import org.springframework.context.annotation.Bean;
+import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
+
+import java.util.Properties;
+
+/**
+ * 校验框架配置类
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration(before = ValidationAutoConfiguration.class)
+public class ValidatorConfig {
+
+ /**
+ * 配置校验框架 快速失败模式
+ */
+ @Bean
+ public Validator validator(MessageSource messageSource) {
+ try (LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean()) {
+ // 国际化
+ factoryBean.setValidationMessageSource(messageSource);
+ // 设置使用 HibernateValidator 校验器
+ factoryBean.setProviderClass(HibernateValidator.class);
+ Properties properties = new Properties();
+ // 设置快速失败模式(fail-fast),即校验过程中一旦遇到失败,立即停止并返回错误
+ properties.setProperty("hibernate.validator.fail_fast", "true");
+ factoryBean.setValidationProperties(properties);
+ // 加载配置
+ factoryBean.afterPropertiesSet();
+ return factoryBean.getValidator();
+ }
+ }
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/config/properties/ThreadPoolProperties.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/config/properties/ThreadPoolProperties.java
new file mode 100644
index 0000000..c2532e6
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/config/properties/ThreadPoolProperties.java
@@ -0,0 +1,30 @@
+package com.fuyuanshen.common.core.config.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * 线程池 配置属性
+ *
+ * @author Lion Li
+ */
+@Data
+@ConfigurationProperties(prefix = "thread-pool")
+public class ThreadPoolProperties {
+
+ /**
+ * 是否开启线程池
+ */
+ private boolean enabled;
+
+ /**
+ * 队列最大长度
+ */
+ private int queueCapacity;
+
+ /**
+ * 线程池维护线程所允许的空闲时间
+ */
+ private int keepAliveSeconds;
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/CacheConstants.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/CacheConstants.java
new file mode 100644
index 0000000..9d73f2a
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/CacheConstants.java
@@ -0,0 +1,30 @@
+package com.fuyuanshen.common.core.constant;
+
+/**
+ * 缓存的key 常量
+ *
+ * @author Lion Li
+ */
+public interface CacheConstants {
+
+ /**
+ * 在线用户 redis key
+ */
+ String ONLINE_TOKEN_KEY = "online_tokens:";
+
+ /**
+ * 参数管理 cache key
+ */
+ String SYS_CONFIG_KEY = "sys_config:";
+
+ /**
+ * 字典管理 cache key
+ */
+ String SYS_DICT_KEY = "sys_dict:";
+
+ /**
+ * 登录账户密码错误次数 redis key
+ */
+ String PWD_ERR_CNT_KEY = "pwd_err_cnt:";
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/CacheNames.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/CacheNames.java
new file mode 100644
index 0000000..2519212
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/CacheNames.java
@@ -0,0 +1,89 @@
+package com.fuyuanshen.common.core.constant;
+
+/**
+ * 缓存组名称常量
+ *
+ * key 格式为 cacheNames#ttl#maxIdleTime#maxSize#local
+ *
+ * ttl 过期时间 如果设置为0则不过期 默认为0
+ * maxIdleTime 最大空闲时间 根据LRU算法清理空闲数据 如果设置为0则不检测 默认为0
+ * maxSize 组最大长度 根据LRU算法清理溢出数据 如果设置为0则无限长 默认为0
+ * local 默认开启本地缓存为1 关闭本地缓存为0
+ *
+ * 例子: test#60s、test#0#60s、test#0#1m#1000、test#1h#0#500、test#1h#0#500#0
+ *
+ * @author Lion Li
+ */
+public interface CacheNames {
+
+ /**
+ * 演示案例
+ */
+ String DEMO_CACHE = "demo:cache#60s#10m#20";
+
+ /**
+ * 系统配置
+ */
+ String SYS_CONFIG = "sys_config";
+
+ /**
+ * 数据字典
+ */
+ String SYS_DICT = "sys_dict";
+
+ /**
+ * 数据字典类型
+ */
+ String SYS_DICT_TYPE = "sys_dict_type";
+
+ /**
+ * 租户
+ */
+ String SYS_TENANT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_tenant#30d";
+
+ /**
+ * 客户端
+ */
+ String SYS_CLIENT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_client#30d";
+
+ /**
+ * 用户账户
+ */
+ String SYS_USER_NAME = "sys_user_name#30d";
+
+ /**
+ * 用户名称
+ */
+ String SYS_NICKNAME = "sys_nickname#30d";
+
+ /**
+ * 部门
+ */
+ String SYS_DEPT = "sys_dept#30d";
+
+ /**
+ * OSS内容
+ */
+ String SYS_OSS = "sys_oss#30d";
+
+ /**
+ * 角色自定义权限
+ */
+ String SYS_ROLE_CUSTOM = "sys_role_custom#30d";
+
+ /**
+ * 部门及以下权限
+ */
+ String SYS_DEPT_AND_CHILD = "sys_dept_and_child#30d";
+
+ /**
+ * OSS配置
+ */
+ String SYS_OSS_CONFIG = GlobalConstants.GLOBAL_REDIS_KEY + "sys_oss_config";
+
+ /**
+ * 在线用户
+ */
+ String ONLINE_TOKEN = "online_tokens";
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/Constants.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/Constants.java
new file mode 100644
index 0000000..fe50dc5
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/Constants.java
@@ -0,0 +1,76 @@
+package com.fuyuanshen.common.core.constant;
+
+/**
+ * 通用常量信息
+ *
+ * @author fys
+ */
+public interface Constants {
+
+ /**
+ * UTF-8 字符集
+ */
+ String UTF8 = "UTF-8";
+
+ /**
+ * GBK 字符集
+ */
+ String GBK = "GBK";
+
+ /**
+ * www主域
+ */
+ String WWW = "www.";
+
+ /**
+ * http请求
+ */
+ String HTTP = "http://";
+
+ /**
+ * https请求
+ */
+ String HTTPS = "https://";
+
+ /**
+ * 通用成功标识
+ */
+ String SUCCESS = "0";
+
+ /**
+ * 通用失败标识
+ */
+ String FAIL = "1";
+
+ /**
+ * 登录成功
+ */
+ String LOGIN_SUCCESS = "Success";
+
+ /**
+ * 注销
+ */
+ String LOGOUT = "Logout";
+
+ /**
+ * 注册
+ */
+ String REGISTER = "Register";
+
+ /**
+ * 登录失败
+ */
+ String LOGIN_FAIL = "Error";
+
+ /**
+ * 验证码有效期(分钟)
+ */
+ Integer CAPTCHA_EXPIRATION = 2;
+
+ /**
+ * 顶级父级id
+ */
+ Long TOP_PARENT_ID = 0L;
+
+}
+
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/GlobalConstants.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/GlobalConstants.java
new file mode 100644
index 0000000..18257d9
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/GlobalConstants.java
@@ -0,0 +1,34 @@
+package com.fuyuanshen.common.core.constant;
+
+/**
+ * 全局的key常量 (业务无关的key)
+ *
+ * @author Lion Li
+ */
+public interface GlobalConstants {
+
+ /**
+ * 全局 redis key (业务无关的key)
+ */
+ String GLOBAL_REDIS_KEY = "global:";
+
+ /**
+ * 验证码 redis key
+ */
+ String CAPTCHA_CODE_KEY = GLOBAL_REDIS_KEY + "captcha_codes:";
+
+ /**
+ * 防重提交 redis key
+ */
+ String REPEAT_SUBMIT_KEY = GLOBAL_REDIS_KEY + "repeat_submit:";
+
+ /**
+ * 限流 redis key
+ */
+ String RATE_LIMIT_KEY = GLOBAL_REDIS_KEY + "rate_limit:";
+
+ /**
+ * 三方认证 redis key
+ */
+ String SOCIAL_AUTH_CODE_KEY = GLOBAL_REDIS_KEY + "social_auth_codes:";
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/HttpStatus.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/HttpStatus.java
new file mode 100644
index 0000000..f7ba342
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/HttpStatus.java
@@ -0,0 +1,93 @@
+package com.fuyuanshen.common.core.constant;
+
+/**
+ * 返回状态码
+ *
+ * @author Lion Li
+ */
+public interface HttpStatus {
+ /**
+ * 操作成功
+ */
+ int SUCCESS = 200;
+
+ /**
+ * 对象创建成功
+ */
+ int CREATED = 201;
+
+ /**
+ * 请求已经被接受
+ */
+ int ACCEPTED = 202;
+
+ /**
+ * 操作已经执行成功,但是没有返回数据
+ */
+ int NO_CONTENT = 204;
+
+ /**
+ * 资源已被移除
+ */
+ int MOVED_PERM = 301;
+
+ /**
+ * 重定向
+ */
+ int SEE_OTHER = 303;
+
+ /**
+ * 资源没有被修改
+ */
+ int NOT_MODIFIED = 304;
+
+ /**
+ * 参数列表错误(缺少,格式不匹配)
+ */
+ int BAD_REQUEST = 400;
+
+ /**
+ * 未授权
+ */
+ int UNAUTHORIZED = 401;
+
+ /**
+ * 访问受限,授权过期
+ */
+ int FORBIDDEN = 403;
+
+ /**
+ * 资源,服务未找到
+ */
+ int NOT_FOUND = 404;
+
+ /**
+ * 不允许的http方法
+ */
+ int BAD_METHOD = 405;
+
+ /**
+ * 资源冲突,或者资源被锁
+ */
+ int CONFLICT = 409;
+
+ /**
+ * 不支持的数据,媒体类型
+ */
+ int UNSUPPORTED_TYPE = 415;
+
+ /**
+ * 系统内部错误
+ */
+ int ERROR = 500;
+
+ /**
+ * 接口未实现
+ */
+ int NOT_IMPLEMENTED = 501;
+
+ /**
+ * 系统警告消息
+ */
+ int WARN = 601;
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/RegexConstants.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/RegexConstants.java
new file mode 100644
index 0000000..b4716e0
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/RegexConstants.java
@@ -0,0 +1,59 @@
+package com.fuyuanshen.common.core.constant;
+
+import cn.hutool.core.lang.RegexPool;
+
+/**
+ * 常用正则表达式字符串
+ *
+ * 常用正则表达式集合,更多正则见: https://any86.github.io/any-rule/
+ *
+ * @author Feng
+ */
+public interface RegexConstants extends RegexPool {
+
+ /**
+ * 字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)
+ */
+ String DICTIONARY_TYPE = "^[a-z][a-z0-9_]*$";
+
+ /**
+ * 权限标识必须符合以下格式:
+ * 1. 标准格式:xxx:yyy:zzz
+ * - 第一部分(xxx):只能包含字母、数字和下划线(_),不能使用 `*`
+ * - 第二部分(yyy):可以包含字母、数字、下划线(_)和 `*`
+ * - 第三部分(zzz):可以包含字母、数字、下划线(_)和 `*`
+ * 2. 允许空字符串(""),表示没有权限标识
+ */
+ String PERMISSION_STRING = "^$|^[a-zA-Z0-9_]+:[a-zA-Z0-9_*]+:[a-zA-Z0-9_*]+$";
+
+ /**
+ * 身份证号码(后6位)
+ */
+ String ID_CARD_LAST_6 = "^(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$";
+
+ /**
+ * QQ号码
+ */
+ String QQ_NUMBER = "^[1-9][0-9]\\d{4,9}$";
+
+ /**
+ * 邮政编码
+ */
+ String POSTAL_CODE = "^[1-9]\\d{5}$";
+
+ /**
+ * 注册账号
+ */
+ String ACCOUNT = "^[a-zA-Z][a-zA-Z0-9_]{4,15}$";
+
+ /**
+ * 密码:包含至少8个字符,包括大写字母、小写字母、数字和特殊字符
+ */
+ String PASSWORD = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$";
+
+ /**
+ * 通用状态(0表示正常,1表示停用)
+ */
+ String STATUS = "^[01]$";
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/ResponseMessageConstants.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/ResponseMessageConstants.java
new file mode 100644
index 0000000..a571cb0
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/ResponseMessageConstants.java
@@ -0,0 +1,28 @@
+package com.fuyuanshen.common.core.constant;
+
+/**
+ * 响应消息常量类
+ *
+ * @author: 默苍璃
+ * @date: 2025-06-2117:21
+ */
+public class ResponseMessageConstants {
+
+ /**
+ * 删除操作成功提示
+ */
+ public static final String DELETE_SUCCESS = "删除成功!";
+
+ /**
+ * 新增操作成功提示
+ */
+ public static final String SAVE_SUCCESS = "新增成功!";
+
+ /**
+ * 更新操作成功提示
+ */
+ public static final String UPDATE_SUCCESS = "更新成功!";
+
+ // 可根据业务需求继续扩展其他常用提示信息
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/SystemConstants.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/SystemConstants.java
new file mode 100644
index 0000000..51b8df7
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/SystemConstants.java
@@ -0,0 +1,80 @@
+package com.fuyuanshen.common.core.constant;
+
+/**
+ * 系统常量信息
+ *
+ * @author Lion Li
+ */
+public interface SystemConstants {
+
+ /**
+ * 正常状态
+ */
+ String NORMAL = "0";
+
+ /**
+ * 异常状态
+ */
+ String DISABLE = "1";
+
+ /**
+ * 是否为系统默认(是)
+ */
+ String YES = "Y";
+
+ /**
+ * 是否为系统默认(否)
+ */
+ String NO = "N";
+
+ /**
+ * 是否菜单外链(是)
+ */
+ String YES_FRAME = "0";
+
+ /**
+ * 是否菜单外链(否)
+ */
+ String NO_FRAME = "1";
+
+ /**
+ * 菜单类型(目录)
+ */
+ String TYPE_DIR = "M";
+
+ /**
+ * 菜单类型(菜单)
+ */
+ String TYPE_MENU = "C";
+
+ /**
+ * 菜单类型(按钮)
+ */
+ String TYPE_BUTTON = "F";
+
+ /**
+ * Layout组件标识
+ */
+ String LAYOUT = "Layout";
+
+ /**
+ * ParentView组件标识
+ */
+ String PARENT_VIEW = "ParentView";
+
+ /**
+ * InnerLink组件标识
+ */
+ String INNER_LINK = "InnerLink";
+
+ /**
+ * 超级管理员ID
+ */
+ Long SUPER_ADMIN_ID = 1L;
+
+ /**
+ * 根部门祖级列表
+ */
+ String ROOT_DEPT_ANCESTORS = "0";
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/TenantConstants.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/TenantConstants.java
new file mode 100644
index 0000000..43839b2
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/TenantConstants.java
@@ -0,0 +1,40 @@
+package com.fuyuanshen.common.core.constant;
+
+/**
+ * 租户常量信息
+ *
+ * @author Lion Li
+ */
+public interface TenantConstants {
+
+ /**
+ * 超级管理员ID
+ */
+ Long SUPER_ADMIN_ID = 1L;
+
+ /**
+ * 超级管理员角色 roleKey
+ */
+ String SUPER_ADMIN_ROLE_KEY = "superadmin";
+
+ /**
+ * 租户管理员角色 roleKey
+ */
+ String TENANT_ADMIN_ROLE_KEY = "admin";
+
+ /**
+ * 富源晟内部员工 roleKey
+ */
+ String FYS_ROLE_KEY = "fel";
+
+ /**
+ * 租户管理员角色名称
+ */
+ String TENANT_ADMIN_ROLE_NAME = "管理员";
+
+ /**
+ * 默认租户ID
+ */
+ String DEFAULT_TENANT_ID = "000000";
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/PageResult.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/PageResult.java
new file mode 100644
index 0000000..2d46205
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/PageResult.java
@@ -0,0 +1,24 @@
+package com.fuyuanshen.common.core.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 分页结果封装类
+ * @author Zheng Jie
+ * @date 2018-11-23
+ * @param
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class PageResult implements Serializable {
+
+ private List rows;
+
+ private long total;
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/R.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/R.java
new file mode 100644
index 0000000..9eb8074
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/R.java
@@ -0,0 +1,110 @@
+package com.fuyuanshen.common.core.domain;
+
+import com.fuyuanshen.common.core.constant.HttpStatus;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 响应信息主体
+ *
+ * @author Lion Li
+ */
+@Data
+@NoArgsConstructor
+public class R implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 成功
+ */
+ public static final int SUCCESS = 200;
+
+ /**
+ * 失败
+ */
+ public static final int FAIL = 500;
+
+ private int code;
+
+ private String msg;
+
+ private T data;
+
+ public static R ok() {
+ return restResult(null, SUCCESS, "操作成功");
+ }
+
+ public static R ok(T data) {
+ return restResult(data, SUCCESS, "操作成功");
+ }
+
+ public static R ok(String msg) {
+ return restResult(null, SUCCESS, msg);
+ }
+
+ public static R ok(String msg, T data) {
+ return restResult(data, SUCCESS, msg);
+ }
+
+ public static R fail() {
+ return restResult(null, FAIL, "操作失败");
+ }
+
+ public static R fail(String msg) {
+ return restResult(null, FAIL, msg);
+ }
+
+ public static R fail(T data) {
+ return restResult(data, FAIL, "操作失败");
+ }
+
+ public static R fail(String msg, T data) {
+ return restResult(data, FAIL, msg);
+ }
+
+ public static R fail(int code, String msg) {
+ return restResult(null, code, msg);
+ }
+
+ /**
+ * 返回警告消息
+ *
+ * @param msg 返回内容
+ * @return 警告消息
+ */
+ public static R warn(String msg) {
+ return restResult(null, HttpStatus.WARN, msg);
+ }
+
+ /**
+ * 返回警告消息
+ *
+ * @param msg 返回内容
+ * @param data 数据对象
+ * @return 警告消息
+ */
+ public static R warn(String msg, T data) {
+ return restResult(data, HttpStatus.WARN, msg);
+ }
+
+ private static R restResult(T data, int code, String msg) {
+ R r = new R<>();
+ r.setCode(code);
+ r.setData(data);
+ r.setMsg(msg);
+ return r;
+ }
+
+ public static Boolean isError(R ret) {
+ return !isSuccess(ret);
+ }
+
+ public static Boolean isSuccess(R ret) {
+ return R.SUCCESS == ret.getCode();
+ }
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/ResponseVO.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/ResponseVO.java
new file mode 100644
index 0000000..ed130c1
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/ResponseVO.java
@@ -0,0 +1,64 @@
+package com.fuyuanshen.common.core.domain;
+
+import lombok.Data;
+
+/**
+ * @Description: 返回体
+ * @Author: WY
+ * @Date: 2025/6/2
+ **/
+@Data
+public class ResponseVO {
+
+ private Integer code; // 成功:0 失败:-1
+ private String msg;
+ private T data;
+
+ public ResponseVO(Integer code, String msg, T data) {
+ this.code = code;
+ this.msg = msg;
+ this.data = data;
+ }
+
+ public ResponseVO(Integer code, String msg) {
+ this.code = code;
+ this.msg = msg;
+ }
+
+ // 静态方法,用于创建成功的响应
+ public static ResponseVO success() {
+ return new ResponseVO<>(0, "操作成功", null);
+ }
+
+ public static ResponseVO success(T data) {
+ return new ResponseVO<>(0, "操作成功", data);
+ }
+
+ public static ResponseVO success(String msg, T data) {
+ return new ResponseVO<>(0, msg, data);
+ }
+
+ // 静态方法,用于创建失败的响应
+ public static ResponseVO fail(String msg) {
+ return new ResponseVO<>(-1, msg, null);
+ }
+
+ public static ResponseVO fail(String msg, T data) {
+ return new ResponseVO<>(-1, msg, data);
+ }
+
+ // 链式方法 - 添加泛型支持
+ public ResponseVO withData(R data) {
+ return new ResponseVO<>(this.code, this.msg, data);
+ }
+
+ public ResponseVO withMsg(String msg) {
+ this.msg = msg;
+ return this;
+ }
+
+ public ResponseVO withCode(Integer code) {
+ this.code = code;
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/CompleteTaskDTO.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/CompleteTaskDTO.java
new file mode 100644
index 0000000..42e2444
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/CompleteTaskDTO.java
@@ -0,0 +1,71 @@
+package com.fuyuanshen.common.core.domain.dto;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * 办理任务请求对象
+ *
+ * @author may
+ */
+@Data
+public class CompleteTaskDTO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 任务id
+ */
+ private Long taskId;
+
+ /**
+ * 附件id
+ */
+ private String fileId;
+
+ /**
+ * 抄送人员
+ */
+ private List flowCopyList;
+
+ /**
+ * 消息类型
+ */
+ private List messageType;
+
+ /**
+ * 办理意见
+ */
+ private String message;
+
+ /**
+ * 消息通知
+ */
+ private String notice;
+
+ /**
+ * 流程变量
+ */
+ private Map variables;
+
+ /**
+ * 扩展变量(此处为逗号分隔的ossId)
+ */
+ private String ext;
+
+ public Map getVariables() {
+ if (variables == null) {
+ return new HashMap<>(16);
+ }
+ variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
+ return variables;
+ }
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/DeptDTO.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/DeptDTO.java
new file mode 100644
index 0000000..e6c8d4b
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/DeptDTO.java
@@ -0,0 +1,36 @@
+package com.fuyuanshen.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 部门
+ *
+ * @author AprilWind
+ */
+@Data
+@NoArgsConstructor
+public class DeptDTO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 部门ID
+ */
+ private Long deptId;
+
+ /**
+ * 父部门ID
+ */
+ private Long parentId;
+
+ /**
+ * 部门名称
+ */
+ private String deptName;
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/DictDataDTO.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/DictDataDTO.java
new file mode 100644
index 0000000..d9d7a2b
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/DictDataDTO.java
@@ -0,0 +1,41 @@
+package com.fuyuanshen.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 字典数据DTO
+ *
+ * @author AprilWind
+ */
+@Data
+@NoArgsConstructor
+public class DictDataDTO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 字典标签
+ */
+ private String dictLabel;
+
+ /**
+ * 字典键值
+ */
+ private String dictValue;
+
+ /**
+ * 是否默认(Y是 N否)
+ */
+ private String isDefault;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/DictTypeDTO.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/DictTypeDTO.java
new file mode 100644
index 0000000..ccbc20d
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/DictTypeDTO.java
@@ -0,0 +1,41 @@
+package com.fuyuanshen.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 字典类型DTO
+ *
+ * @author AprilWind
+ */
+@Data
+@NoArgsConstructor
+public class DictTypeDTO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 字典主键
+ */
+ private Long dictId;
+
+ /**
+ * 字典名称
+ */
+ private String dictName;
+
+ /**
+ * 字典类型
+ */
+ private String dictType;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/FlowCopyDTO.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/FlowCopyDTO.java
new file mode 100644
index 0000000..caef884
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/FlowCopyDTO.java
@@ -0,0 +1,30 @@
+package com.fuyuanshen.common.core.domain.dto;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+
+/**
+ * 抄送
+ *
+ * @author may
+ */
+@Data
+public class FlowCopyDTO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 用户id
+ */
+ private Long userId;
+
+ /**
+ * 用户名称
+ */
+ private String userName;
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/OssDTO.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/OssDTO.java
new file mode 100644
index 0000000..616d4bb
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/OssDTO.java
@@ -0,0 +1,46 @@
+package com.fuyuanshen.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * OSS对象
+ *
+ * @author Lion Li
+ */
+@Data
+@NoArgsConstructor
+public class OssDTO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 对象存储主键
+ */
+ private Long ossId;
+
+ /**
+ * 文件名
+ */
+ private String fileName;
+
+ /**
+ * 原名
+ */
+ private String originalName;
+
+ /**
+ * 文件后缀名
+ */
+ private String fileSuffix;
+
+ /**
+ * URL地址
+ */
+ private String url;
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/PostDTO.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/PostDTO.java
new file mode 100644
index 0000000..51946ad
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/PostDTO.java
@@ -0,0 +1,46 @@
+package com.fuyuanshen.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 岗位
+ *
+ * @author AprilWind
+ */
+@Data
+@NoArgsConstructor
+public class PostDTO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 岗位ID
+ */
+ private Long postId;
+
+ /**
+ * 部门id
+ */
+ private Long deptId;
+
+ /**
+ * 岗位编码
+ */
+ private String postCode;
+
+ /**
+ * 岗位名称
+ */
+ private String postName;
+
+ /**
+ * 岗位类别编码
+ */
+ private String postCategory;
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/RoleDTO.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/RoleDTO.java
new file mode 100644
index 0000000..8a82964
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/RoleDTO.java
@@ -0,0 +1,42 @@
+package com.fuyuanshen.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 角色
+ *
+ * @author Lion Li
+ */
+
+@Data
+@NoArgsConstructor
+public class RoleDTO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 角色ID
+ */
+ private Long roleId;
+
+ /**
+ * 角色名称
+ */
+ private String roleName;
+
+ /**
+ * 角色权限
+ */
+ private String roleKey;
+
+ /**
+ * 数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限 5:仅本人数据权限 6:部门及以下或本人数据权限)
+ */
+ private String dataScope;
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/StartProcessDTO.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/StartProcessDTO.java
new file mode 100644
index 0000000..6d872f6
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/StartProcessDTO.java
@@ -0,0 +1,45 @@
+package com.fuyuanshen.common.core.domain.dto;
+
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * 启动流程对象
+ *
+ * @author may
+ */
+@Data
+public class StartProcessDTO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 业务唯一值id
+ */
+ private String businessId;
+
+ /**
+ * 流程定义编码
+ */
+ private String flowCode;
+
+ /**
+ * 流程变量,前端会提交一个元素{'entity': {业务详情数据对象}}
+ */
+ private Map variables;
+
+ public Map getVariables() {
+ if (variables == null) {
+ return new HashMap<>(16);
+ }
+ variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
+ return variables;
+ }
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/StartProcessReturnDTO.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/StartProcessReturnDTO.java
new file mode 100644
index 0000000..ae87462
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/StartProcessReturnDTO.java
@@ -0,0 +1,30 @@
+package com.fuyuanshen.common.core.domain.dto;
+
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 启动流程返回对象
+ *
+ * @author Lion Li
+ */
+@Data
+public class StartProcessReturnDTO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 流程实例id
+ */
+ private Long processInstanceId;
+
+ /**
+ * 任务id
+ */
+ private Long taskId;
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/TaskAssigneeDTO.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/TaskAssigneeDTO.java
new file mode 100644
index 0000000..85de311
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/TaskAssigneeDTO.java
@@ -0,0 +1,101 @@
+package com.fuyuanshen.common.core.domain.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * 任务受让人
+ *
+ * @author AprilWind
+ */
+@Data
+@NoArgsConstructor
+public class TaskAssigneeDTO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 总大小
+ */
+ private Long total = 0L;
+
+ /**
+ *
+ */
+ private List list;
+
+ public TaskAssigneeDTO(Long total, List list) {
+ this.total = total;
+ this.list = list;
+ }
+
+ /**
+ * 将源列表转换为 TaskHandler 列表
+ *
+ * @param 通用类型
+ * @param sourceList 待转换的源列表
+ * @param storageId 提取 storageId 的函数
+ * @param handlerCode 提取 handlerCode 的函数
+ * @param handlerName 提取 handlerName 的函数
+ * @param groupName 提取 groupName 的函数
+ * @param createTimeMapper 提取 createTime 的函数
+ * @return 转换后的 TaskHandler 列表
+ */
+ public static List convertToHandlerList(
+ List sourceList,
+ Function storageId,
+ Function handlerCode,
+ Function handlerName,
+ Function groupName,
+ Function createTimeMapper) {
+ return sourceList.stream()
+ .map(item -> new TaskHandler(
+ String.valueOf(storageId.apply(item)),
+ handlerCode.apply(item),
+ handlerName.apply(item),
+ groupName != null ? String.valueOf(groupName.apply(item)) : null,
+ createTimeMapper.apply(item)
+ )).collect(Collectors.toList());
+ }
+
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class TaskHandler {
+
+ /**
+ * 主键
+ */
+ private String storageId;
+
+ /**
+ * 权限编码
+ */
+ private String handlerCode;
+
+ /**
+ * 权限名称
+ */
+ private String handlerName;
+
+ /**
+ * 权限分组
+ */
+ private String groupName;
+
+ /**
+ * 创建时间
+ */
+ private Date createTime;
+ }
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/UserDTO.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/UserDTO.java
new file mode 100644
index 0000000..a5a30d1
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/UserDTO.java
@@ -0,0 +1,73 @@
+package com.fuyuanshen.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+/**
+ * 用户
+ *
+ * @author Michelle.Chung
+ */
+@Data
+@NoArgsConstructor
+public class UserDTO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 用户ID
+ */
+ private Long userId;
+
+ /**
+ * 部门ID
+ */
+ private Long deptId;
+
+ /**
+ * 用户账号
+ */
+ private String userName;
+
+ /**
+ * 用户昵称
+ */
+ private String nickName;
+
+ /**
+ * 用户类型(sys_user系统用户)
+ */
+ private String userType;
+
+ /**
+ * 用户邮箱
+ */
+ private String email;
+
+ /**
+ * 手机号码
+ */
+ private String phonenumber;
+
+ /**
+ * 用户性别(0男 1女 2未知)
+ */
+ private String sex;
+
+ /**
+ * 帐号状态(0正常 1停用)
+ */
+ private String status;
+
+ /**
+ * 创建时间
+ */
+ private Date createTime;
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/UserOnlineDTO.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/UserOnlineDTO.java
new file mode 100644
index 0000000..08500f1
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/dto/UserOnlineDTO.java
@@ -0,0 +1,72 @@
+package com.fuyuanshen.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 当前在线会话
+ *
+ * @author fys
+ */
+
+@Data
+@NoArgsConstructor
+public class UserOnlineDTO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 会话编号
+ */
+ private String tokenId;
+
+ /**
+ * 部门名称
+ */
+ private String deptName;
+
+ /**
+ * 用户名称
+ */
+ private String userName;
+
+ /**
+ * 客户端
+ */
+ private String clientKey;
+
+ /**
+ * 设备类型
+ */
+ private String deviceType;
+
+ /**
+ * 登录IP地址
+ */
+ private String ipaddr;
+
+ /**
+ * 登录地址
+ */
+ private String loginLocation;
+
+ /**
+ * 浏览器类型
+ */
+ private String browser;
+
+ /**
+ * 操作系统
+ */
+ private String os;
+
+ /**
+ * 登录时间
+ */
+ private Long loginTime;
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/event/ProcessDeleteEvent.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/event/ProcessDeleteEvent.java
new file mode 100644
index 0000000..4f746ca
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/event/ProcessDeleteEvent.java
@@ -0,0 +1,34 @@
+package com.fuyuanshen.common.core.domain.event;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 删除流程监听
+ *
+ * @author AprilWind
+ */
+@Data
+public class ProcessDeleteEvent implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 租户ID
+ */
+ private String tenantId;
+
+ /**
+ * 流程定义编码
+ */
+ private String flowCode;
+
+ /**
+ * 业务id
+ */
+ private String businessId;
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/event/ProcessEvent.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/event/ProcessEvent.java
new file mode 100644
index 0000000..94f197f
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/event/ProcessEvent.java
@@ -0,0 +1,65 @@
+package com.fuyuanshen.common.core.domain.event;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * 总体流程监听
+ *
+ * @author may
+ */
+@Data
+public class ProcessEvent implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 租户ID
+ */
+ private String tenantId;
+
+ /**
+ * 流程定义编码
+ */
+ private String flowCode;
+
+ /**
+ * 业务id
+ */
+ private String businessId;
+
+ /**
+ * 节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)
+ */
+ private Integer nodeType;
+
+ /**
+ * 流程节点编码
+ */
+ private String nodeCode;
+
+ /**
+ * 流程节点名称
+ */
+ private String nodeName;
+
+ /**
+ * 流程状态
+ */
+ private String status;
+
+ /**
+ * 办理参数
+ */
+ private Map params;
+
+ /**
+ * 当为true时为申请人节点办理
+ */
+ private Boolean submit;
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/event/ProcessTaskEvent.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/event/ProcessTaskEvent.java
new file mode 100644
index 0000000..76b0bb5
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/event/ProcessTaskEvent.java
@@ -0,0 +1,59 @@
+package com.fuyuanshen.common.core.domain.event;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 流程任务监听
+ *
+ * @author may
+ */
+@Data
+public class ProcessTaskEvent implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 租户ID
+ */
+ private String tenantId;
+
+ /**
+ * 流程定义编码
+ */
+ private String flowCode;
+
+ /**
+ * 节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)
+ */
+ private Integer nodeType;
+
+ /**
+ * 流程节点编码
+ */
+ private String nodeCode;
+
+ /**
+ * 流程节点名称
+ */
+ private String nodeName;
+
+ /**
+ * 任务id
+ */
+ private Long taskId;
+
+ /**
+ * 业务id
+ */
+ private String businessId;
+
+ /**
+ * 流程状态
+ */
+ private String status;
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/AppLoginUser.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/AppLoginUser.java
new file mode 100644
index 0000000..78c0e8a
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/AppLoginUser.java
@@ -0,0 +1,148 @@
+package com.fuyuanshen.common.core.domain.model;
+
+import com.fuyuanshen.common.core.domain.dto.PostDTO;
+import com.fuyuanshen.common.core.domain.dto.RoleDTO;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 登录用户身份权限
+ *
+ * @author Lion Li
+ */
+@Data
+@NoArgsConstructor
+public class AppLoginUser implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 租户ID
+ */
+ private String tenantId;
+
+ /**
+ * 用户ID
+ */
+ private Long userId;
+
+ /**
+ * 部门ID
+ */
+ private Long deptId;
+
+ /**
+ * 部门类别编码
+ */
+ private String deptCategory;
+
+ /**
+ * 部门名
+ */
+ private String deptName;
+
+ /**
+ * 用户唯一标识
+ */
+ private String token;
+
+ /**
+ * 用户类型
+ */
+ private String userType;
+
+ /**
+ * 登录时间
+ */
+ private Long loginTime;
+
+ /**
+ * 过期时间
+ */
+ private Long expireTime;
+
+ /**
+ * 登录IP地址
+ */
+ private String ipaddr;
+
+ /**
+ * 登录地点
+ */
+ private String loginLocation;
+
+ /**
+ * 浏览器类型
+ */
+ private String browser;
+
+ /**
+ * 操作系统
+ */
+ private String os;
+
+ /**
+ * 菜单权限
+ */
+ private Set menuPermission;
+
+ /**
+ * 角色权限
+ */
+ private Set rolePermission;
+
+ /**
+ * 用户名
+ */
+ private String username;
+
+ /**
+ * 用户昵称
+ */
+ private String nickname;
+
+ /**
+ * 角色对象
+ */
+ private List roles;
+
+ /**
+ * 岗位对象
+ */
+ private List posts;
+
+ /**
+ * 数据权限 当前角色ID
+ */
+ private Long roleId;
+
+ /**
+ * 客户端
+ */
+ private String clientKey;
+
+ /**
+ * 设备类型
+ */
+ private String deviceType;
+
+ /**
+ * 获取登录id
+ */
+ public String getLoginId() {
+ if (userType == null) {
+ throw new IllegalArgumentException("用户类型不能为空");
+ }
+ if (userId == null) {
+ throw new IllegalArgumentException("用户ID不能为空");
+ }
+ return userType + ":" + userId;
+ }
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/EmailLoginBody.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/EmailLoginBody.java
new file mode 100644
index 0000000..f9bf216
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/EmailLoginBody.java
@@ -0,0 +1,31 @@
+package com.fuyuanshen.common.core.domain.model;
+
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 邮件登录对象
+ *
+ * @author Lion Li
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class EmailLoginBody extends LoginBody {
+
+ /**
+ * 邮箱
+ */
+ @NotBlank(message = "{user.email.not.blank}")
+ @Email(message = "{user.email.not.valid}")
+ private String email;
+
+ /**
+ * 邮箱code
+ */
+ @NotBlank(message = "{email.code.not.blank}")
+ private String emailCode;
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/LoginBody.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/LoginBody.java
new file mode 100644
index 0000000..bc567cc
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/LoginBody.java
@@ -0,0 +1,48 @@
+package com.fuyuanshen.common.core.domain.model;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 用户登录对象
+ *
+ * @author Lion Li
+ */
+
+@Data
+public class LoginBody implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 客户端id
+ */
+ @NotBlank(message = "{auth.clientid.not.blank}")
+ private String clientId;
+
+ /**
+ * 授权类型
+ */
+ @NotBlank(message = "{auth.grant.type.not.blank}")
+ private String grantType;
+
+ /**
+ * 租户ID
+ */
+ private String tenantId;
+
+ /**
+ * 验证码
+ */
+ private String code;
+
+ /**
+ * 唯一标识
+ */
+ private String uuid;
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/LoginUser.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/LoginUser.java
new file mode 100644
index 0000000..2f6260b
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/LoginUser.java
@@ -0,0 +1,155 @@
+package com.fuyuanshen.common.core.domain.model;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import com.fuyuanshen.common.core.domain.dto.PostDTO;
+import com.fuyuanshen.common.core.domain.dto.RoleDTO;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 登录用户身份权限
+ *
+ * @author Lion Li
+ */
+@Data
+@NoArgsConstructor
+public class LoginUser implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 租户ID
+ */
+ private String tenantId;
+
+ /**
+ * 用户ID
+ */
+ private Long userId;
+ private Long pid;
+
+ /**
+ * 部门ID
+ */
+ private Long deptId;
+
+ /**
+ * 部门类别编码
+ */
+ private String deptCategory;
+
+ /**
+ * 部门名
+ */
+ private String deptName;
+
+ /**
+ * 用户唯一标识
+ */
+ private String token;
+
+ /**
+ * 用户类型
+ */
+ private String userType;
+
+ /**
+ * 登录时间
+ */
+ private Long loginTime;
+
+ /**
+ * 过期时间
+ */
+ private Long expireTime;
+
+ /**
+ * 登录IP地址
+ */
+ private String ipaddr;
+
+ /**
+ * 登录地点
+ */
+ private String loginLocation;
+
+ /**
+ * 浏览器类型
+ */
+ private String browser;
+
+ /**
+ * 操作系统
+ */
+ private String os;
+
+ /**
+ * 菜单权限
+ */
+ private Set menuPermission;
+
+ /**
+ * 角色权限
+ */
+ private Set rolePermission;
+
+ /**
+ * 用户名
+ */
+ private String username;
+
+ /**
+ * 用户昵称
+ */
+ private String nickname;
+
+ /**
+ * 角色对象
+ */
+ private List roles;
+
+ /**
+ * 岗位对象
+ */
+ private List posts;
+
+ /**
+ * 数据权限 当前角色ID
+ */
+ private Long roleId;
+
+ /**
+ * 客户端
+ */
+ private String clientKey;
+
+ /**
+ * 设备类型
+ */
+ private String deviceType;
+
+ /**
+ * 用户等级
+ */
+ private Byte userLevel;
+
+ /**
+ * 获取登录id
+ */
+ public String getLoginId() {
+ if (userType == null) {
+ throw new IllegalArgumentException("用户类型不能为空");
+ }
+ if (userId == null) {
+ throw new IllegalArgumentException("用户ID不能为空");
+ }
+ return userType + ":" + userId;
+ }
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/PasswordLoginBody.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/PasswordLoginBody.java
new file mode 100644
index 0000000..1ba0e32
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/PasswordLoginBody.java
@@ -0,0 +1,31 @@
+package com.fuyuanshen.common.core.domain.model;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.hibernate.validator.constraints.Length;
+
+/**
+ * 密码登录对象
+ *
+ * @author Lion Li
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class PasswordLoginBody extends LoginBody {
+
+ /**
+ * 用户名
+ */
+ @NotBlank(message = "{user.username.not.blank}")
+ @Length(min = 2, max = 30, message = "{user.username.length.valid}")
+ private String username;
+
+ /**
+ * 用户密码
+ */
+ @NotBlank(message = "{user.password.not.blank}")
+ @Length(min = 5, max = 30, message = "{user.password.length.valid}")
+ private String password;
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/RegisterBody.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/RegisterBody.java
new file mode 100644
index 0000000..f111926
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/RegisterBody.java
@@ -0,0 +1,33 @@
+package com.fuyuanshen.common.core.domain.model;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.hibernate.validator.constraints.Length;
+
+/**
+ * 用户注册对象
+ *
+ * @author Lion Li
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class RegisterBody extends LoginBody {
+
+ /**
+ * 用户名
+ */
+ @NotBlank(message = "{user.username.not.blank}")
+ @Length(min = 2, max = 30, message = "{user.username.length.valid}")
+ private String username;
+
+ /**
+ * 用户密码
+ */
+ @NotBlank(message = "{user.password.not.blank}")
+ @Length(min = 5, max = 30, message = "{user.password.length.valid}")
+ private String password;
+
+ private String userType;
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/SmsLoginBody.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/SmsLoginBody.java
new file mode 100644
index 0000000..82a3312
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/SmsLoginBody.java
@@ -0,0 +1,29 @@
+package com.fuyuanshen.common.core.domain.model;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 短信登录对象
+ *
+ * @author Lion Li
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class SmsLoginBody extends LoginBody {
+
+ /**
+ * 手机号
+ */
+ @NotBlank(message = "{user.phonenumber.not.blank}")
+ private String phonenumber;
+
+ /**
+ * 短信code
+ */
+ @NotBlank(message = "{sms.code.not.blank}")
+ private String smsCode;
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/SocialLoginBody.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/SocialLoginBody.java
new file mode 100644
index 0000000..e165eb5
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/SocialLoginBody.java
@@ -0,0 +1,35 @@
+package com.fuyuanshen.common.core.domain.model;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 三方登录对象
+ *
+ * @author Lion Li
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class SocialLoginBody extends LoginBody {
+
+ /**
+ * 第三方登录平台
+ */
+ @NotBlank(message = "{social.source.not.blank}")
+ private String source;
+
+ /**
+ * 第三方登录code
+ */
+ @NotBlank(message = "{social.code.not.blank}")
+ private String socialCode;
+
+ /**
+ * 第三方登录socialState
+ */
+ @NotBlank(message = "{social.state.not.blank}")
+ private String socialState;
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/TaskAssigneeBody.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/TaskAssigneeBody.java
new file mode 100644
index 0000000..8c3ab81
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/TaskAssigneeBody.java
@@ -0,0 +1,56 @@
+package com.fuyuanshen.common.core.domain.model;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 任务受让人
+ *
+ * @author AprilWind
+ */
+@Data
+@NoArgsConstructor
+public class TaskAssigneeBody implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 权限编码
+ */
+ private String handlerCode;
+
+ /**
+ * 权限名称
+ */
+ private String handlerName;
+
+ /**
+ * 权限分组
+ */
+ private String groupId;
+
+ /**
+ * 开始时间
+ */
+ private String beginTime;
+
+ /**
+ * 结束时间
+ */
+ private String endTime;
+
+ /**
+ * 当前页
+ */
+ private Integer pageNum = 1;
+
+ /**
+ * 每页显示条数
+ */
+ private Integer pageSize = 10;
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/XcxLoginBody.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/XcxLoginBody.java
new file mode 100644
index 0000000..06a1245
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/XcxLoginBody.java
@@ -0,0 +1,28 @@
+package com.fuyuanshen.common.core.domain.model;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 三方登录对象
+ *
+ * @author Lion Li
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class XcxLoginBody extends LoginBody {
+
+ /**
+ * 小程序id(多个小程序时使用)
+ */
+ private String appid;
+
+ /**
+ * 小程序code
+ */
+ @NotBlank(message = "{xcx.code.not.blank}")
+ private String xcxCode;
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/XcxLoginUser.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/XcxLoginUser.java
new file mode 100644
index 0000000..96eed6f
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/XcxLoginUser.java
@@ -0,0 +1,27 @@
+package com.fuyuanshen.common.core.domain.model;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+
+/**
+ * 小程序登录用户身份权限
+ *
+ * @author Lion Li
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+public class XcxLoginUser extends LoginUser {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * openid
+ */
+ private String openid;
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/enums/BusinessStatusEnum.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/enums/BusinessStatusEnum.java
new file mode 100644
index 0000000..f92197d
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/enums/BusinessStatusEnum.java
@@ -0,0 +1,215 @@
+package com.fuyuanshen.common.core.enums;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import com.fuyuanshen.common.core.exception.ServiceException;
+import com.fuyuanshen.common.core.utils.StringUtils;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * 业务状态枚举
+ *
+ * @author may
+ */
+@Getter
+@AllArgsConstructor
+public enum BusinessStatusEnum {
+
+ /**
+ * 已撤销
+ */
+ CANCEL("cancel", "已撤销"),
+
+ /**
+ * 草稿
+ */
+ DRAFT("draft", "草稿"),
+
+ /**
+ * 待审核
+ */
+ WAITING("waiting", "待审核"),
+
+ /**
+ * 已完成
+ */
+ FINISH("finish", "已完成"),
+
+ /**
+ * 已作废
+ */
+ INVALID("invalid", "已作废"),
+
+ /**
+ * 已退回
+ */
+ BACK("back", "已退回"),
+
+ /**
+ * 已终止
+ */
+ TERMINATION("termination", "已终止");
+
+ /**
+ * 状态
+ */
+ private final String status;
+
+ /**
+ * 描述
+ */
+ private final String desc;
+
+ private static final Map STATUS_MAP = Arrays.stream(BusinessStatusEnum.values())
+ .collect(Collectors.toConcurrentMap(BusinessStatusEnum::getStatus, Function.identity()));
+
+ /**
+ * 根据状态获取对应的 BusinessStatusEnum 枚举
+ *
+ * @param status 业务状态码
+ * @return 对应的 BusinessStatusEnum 枚举,如果找不到则返回 null
+ */
+ public static BusinessStatusEnum getByStatus(String status) {
+ // 使用 STATUS_MAP 获取对应的枚举,若找不到则返回 null
+ return STATUS_MAP.get(status);
+ }
+
+ /**
+ * 根据状态获取对应的业务状态描述信息
+ *
+ * @param status 业务状态码
+ * @return 返回业务状态描述,若状态码为空或未找到对应的枚举,返回空字符串
+ */
+ public static String findByStatus(String status) {
+ if (StringUtils.isBlank(status)) {
+ return StrUtil.EMPTY;
+ }
+ BusinessStatusEnum statusEnum = STATUS_MAP.get(status);
+ return (statusEnum != null) ? statusEnum.getDesc() : StrUtil.EMPTY;
+ }
+
+ /**
+ * 判断是否为指定的状态之一:草稿、已撤销或已退回
+ *
+ * @param status 要检查的状态
+ * @return 如果状态为草稿、已撤销或已退回之一,则返回 true;否则返回 false
+ */
+ public static boolean isDraftOrCancelOrBack(String status) {
+ return DRAFT.status.equals(status) || CANCEL.status.equals(status) || BACK.status.equals(status);
+ }
+
+ /**
+ * 判断是否为撤销,退回,作废,终止
+ *
+ * @param status status
+ * @return 结果
+ */
+ public static boolean initialState(String status) {
+ return CANCEL.status.equals(status) || BACK.status.equals(status) || INVALID.status.equals(status) || TERMINATION.status.equals(status);
+ }
+
+ /**
+ * 获取运行中的实例状态列表
+ *
+ * @return 包含运行中实例状态的不可变列表
+ * (包含 DRAFT、WAITING、BACK 和 CANCEL 状态)
+ */
+ public static List runningStatus() {
+ return Arrays.asList(DRAFT.status, WAITING.status, BACK.status, CANCEL.status);
+ }
+
+ /**
+ * 获取结束实例的状态列表
+ *
+ * @return 包含结束实例状态的不可变列表
+ * (包含 FINISH、INVALID 和 TERMINATION 状态)
+ */
+ public static List finishStatus() {
+ return Arrays.asList(FINISH.status, INVALID.status, TERMINATION.status);
+ }
+
+ /**
+ * 启动流程校验
+ *
+ * @param status 状态
+ */
+ public static void checkStartStatus(String status) {
+ if (WAITING.getStatus().equals(status)) {
+ throw new ServiceException("该单据已提交过申请,正在审批中!");
+ } else if (FINISH.getStatus().equals(status)) {
+ throw new ServiceException("该单据已完成申请!");
+ } else if (INVALID.getStatus().equals(status)) {
+ throw new ServiceException("该单据已作废!");
+ } else if (TERMINATION.getStatus().equals(status)) {
+ throw new ServiceException("该单据已终止!");
+ } else if (StringUtils.isBlank(status)) {
+ throw new ServiceException("流程状态为空!");
+ }
+ }
+
+ /**
+ * 撤销流程校验
+ *
+ * @param status 状态
+ */
+ public static void checkCancelStatus(String status) {
+ if (CANCEL.getStatus().equals(status)) {
+ throw new ServiceException("该单据已撤销!");
+ } else if (FINISH.getStatus().equals(status)) {
+ throw new ServiceException("该单据已完成申请!");
+ } else if (INVALID.getStatus().equals(status)) {
+ throw new ServiceException("该单据已作废!");
+ } else if (TERMINATION.getStatus().equals(status)) {
+ throw new ServiceException("该单据已终止!");
+ } else if (BACK.getStatus().equals(status)) {
+ throw new ServiceException("该单据已退回!");
+ } else if (StringUtils.isBlank(status)) {
+ throw new ServiceException("流程状态为空!");
+ }
+ }
+
+ /**
+ * 驳回流程校验
+ *
+ * @param status 状态
+ */
+ public static void checkBackStatus(String status) {
+ if (BACK.getStatus().equals(status)) {
+ throw new ServiceException("该单据已退回!");
+ } else if (FINISH.getStatus().equals(status)) {
+ throw new ServiceException("该单据已完成申请!");
+ } else if (INVALID.getStatus().equals(status)) {
+ throw new ServiceException("该单据已作废!");
+ } else if (TERMINATION.getStatus().equals(status)) {
+ throw new ServiceException("该单据已终止!");
+ } else if (CANCEL.getStatus().equals(status)) {
+ throw new ServiceException("该单据已撤销!");
+ } else if (StringUtils.isBlank(status)) {
+ throw new ServiceException("流程状态为空!");
+ }
+ }
+
+ /**
+ * 作废,终止流程校验
+ *
+ * @param status 状态
+ */
+ public static void checkInvalidStatus(String status) {
+ if (FINISH.getStatus().equals(status)) {
+ throw new ServiceException("该单据已完成申请!");
+ } else if (INVALID.getStatus().equals(status)) {
+ throw new ServiceException("该单据已作废!");
+ } else if (TERMINATION.getStatus().equals(status)) {
+ throw new ServiceException("该单据已终止!");
+ } else if (StringUtils.isBlank(status)) {
+ throw new ServiceException("流程状态为空!");
+ }
+ }
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/enums/DeviceType.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/enums/DeviceType.java
new file mode 100644
index 0000000..4bf21c5
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/enums/DeviceType.java
@@ -0,0 +1,39 @@
+package com.fuyuanshen.common.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 设备类型
+ *
+ * @author Lion Li
+ */
+@Getter
+@AllArgsConstructor
+public enum DeviceType {
+
+ /**
+ * pc端
+ */
+ PC("pc"),
+
+ /**
+ * app端
+ */
+ APP("app"),
+
+ /**
+ * 小程序端
+ */
+ XCX("xcx"),
+
+ /**
+ * 第三方社交登录平台
+ */
+ SOCIAL("social");
+
+ /**
+ * 设备标识
+ */
+ private final String device;
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/enums/FormatsType.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/enums/FormatsType.java
new file mode 100644
index 0000000..f880140
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/enums/FormatsType.java
@@ -0,0 +1,146 @@
+package com.fuyuanshen.common.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import com.fuyuanshen.common.core.utils.StringUtils;
+
+/*
+ * 日期格式
+ * "yyyy":4位数的年份,例如:2023年表示为"2023"。
+ * "yy":2位数的年份,例如:2023年表示为"23"。
+ * "MM":2位数的月份,取值范围为01到12,例如:7月表示为"07"。
+ * "M":不带前导零的月份,取值范围为1到12,例如:7月表示为"7"。
+ * "dd":2位数的日期,取值范围为01到31,例如:22日表示为"22"。
+ * "d":不带前导零的日期,取值范围为1到31,例如:22日表示为"22"。
+ * "EEEE":星期的全名,例如:星期三表示为"Wednesday"。
+ * "E":星期的缩写,例如:星期三表示为"Wed"。
+ * "DDD" 或 "D":一年中的第几天,取值范围为001到366,例如:第200天表示为"200"。
+ * 时间格式
+ * "HH":24小时制的小时数,取值范围为00到23,例如:下午5点表示为"17"。
+ * "hh":12小时制的小时数,取值范围为01到12,例如:下午5点表示为"05"。
+ * "mm":分钟数,取值范围为00到59,例如:30分钟表示为"30"。
+ * "ss":秒数,取值范围为00到59,例如:45秒表示为"45"。
+ * "SSS":毫秒数,取值范围为000到999,例如:123毫秒表示为"123"。
+ */
+
+/**
+ * 日期格式与时间格式枚举
+ */
+@Getter
+@AllArgsConstructor
+public enum FormatsType {
+
+ /**
+ * 例如:2023年表示为"23"
+ */
+ YY("yy"),
+
+ /**
+ * 例如:2023年表示为"2023"
+ */
+ YYYY("yyyy"),
+
+ /**
+ * 例例如,2023年7月可以表示为 "2023-07"
+ */
+ YYYY_MM("yyyy-MM"),
+
+ /**
+ * 例如,日期 "2023年7月22日" 可以表示为 "2023-07-22"
+ */
+ YYYY_MM_DD("yyyy-MM-dd"),
+
+ /**
+ * 例如,当前时间如果是 "2023年7月22日下午3点30分",则可以表示为 "2023-07-22 15:30"
+ */
+ YYYY_MM_DD_HH_MM("yyyy-MM-dd HH:mm"),
+
+ /**
+ * 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023-07-22 15:30:45"
+ */
+ YYYY_MM_DD_HH_MM_SS("yyyy-MM-dd HH:mm:ss"),
+
+ /**
+ * 例如:下午3点30分45秒,表示为 "15:30:45"
+ */
+ HH_MM_SS("HH:mm:ss"),
+
+ /**
+ * 例例如,2023年7月可以表示为 "2023/07"
+ */
+ YYYY_MM_SLASH("yyyy/MM"),
+
+ /**
+ * 例如,日期 "2023年7月22日" 可以表示为 "2023/07/22"
+ */
+ YYYY_MM_DD_SLASH("yyyy/MM/dd"),
+
+ /**
+ * 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023/07/22 15:30:45"
+ */
+ YYYY_MM_DD_HH_MM_SLASH("yyyy/MM/dd HH:mm"),
+
+ /**
+ * 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023/07/22 15:30:45"
+ */
+ YYYY_MM_DD_HH_MM_SS_SLASH("yyyy/MM/dd HH:mm:ss"),
+
+ /**
+ * 例例如,2023年7月可以表示为 "2023.07"
+ */
+ YYYY_MM_DOT("yyyy.MM"),
+
+ /**
+ * 例如,日期 "2023年7月22日" 可以表示为 "2023.07.22"
+ */
+ YYYY_MM_DD_DOT("yyyy.MM.dd"),
+
+ /**
+ * 例如,当前时间如果是 "2023年7月22日下午3点30分",则可以表示为 "2023.07.22 15:30"
+ */
+ YYYY_MM_DD_HH_MM_DOT("yyyy.MM.dd HH:mm"),
+
+ /**
+ * 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023.07.22 15:30:45"
+ */
+ YYYY_MM_DD_HH_MM_SS_DOT("yyyy.MM.dd HH:mm:ss"),
+
+ /**
+ * 例如,2023年7月可以表示为 "202307"
+ */
+ YYYYMM("yyyyMM"),
+
+ /**
+ * 例如,2023年7月22日可以表示为 "20230722"
+ */
+ YYYYMMDD("yyyyMMdd"),
+
+ /**
+ * 例如,2023年7月22日下午3点可以表示为 "2023072215"
+ */
+ YYYYMMDDHH("yyyyMMddHH"),
+
+ /**
+ * 例如,2023年7月22日下午3点30分可以表示为 "202307221530"
+ */
+ YYYYMMDDHHMM("yyyyMMddHHmm"),
+
+ /**
+ * 例如,2023年7月22日下午3点30分45秒可以表示为 "20230722153045"
+ */
+ YYYYMMDDHHMMSS("yyyyMMddHHmmss");
+
+ /**
+ * 时间格式
+ */
+ private final String timeFormat;
+
+ public static FormatsType getFormatsType(String str) {
+ for (FormatsType value : values()) {
+ if (StringUtils.contains(str, value.getTimeFormat())) {
+ return value;
+ }
+ }
+ throw new RuntimeException("'FormatsType' not found By " + str);
+ }
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/enums/LoginType.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/enums/LoginType.java
new file mode 100644
index 0000000..84f99b1
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/enums/LoginType.java
@@ -0,0 +1,44 @@
+package com.fuyuanshen.common.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 登录类型
+ *
+ * @author Lion Li
+ */
+@Getter
+@AllArgsConstructor
+public enum LoginType {
+
+ /**
+ * 密码登录
+ */
+ PASSWORD("user.password.retry.limit.exceed", "user.password.retry.limit.count"),
+
+ /**
+ * 短信登录
+ */
+ SMS("sms.code.retry.limit.exceed", "sms.code.retry.limit.count"),
+
+ /**
+ * 邮箱登录
+ */
+ EMAIL("email.code.retry.limit.exceed", "email.code.retry.limit.count"),
+
+ /**
+ * 小程序登录
+ */
+ XCX("", "");
+
+ /**
+ * 登录重试超出限制提示
+ */
+ final String retryLimitExceed;
+
+ /**
+ * 登录重试限制计数提示
+ */
+ final String retryLimitCount;
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/enums/UserStatus.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/enums/UserStatus.java
new file mode 100644
index 0000000..6ddd8d7
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/enums/UserStatus.java
@@ -0,0 +1,30 @@
+package com.fuyuanshen.common.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 用户状态
+ *
+ * @author fys
+ */
+@Getter
+@AllArgsConstructor
+public enum UserStatus {
+ /**
+ * 正常
+ */
+ OK("0", "正常"),
+ /**
+ * 停用
+ */
+ DISABLE("1", "停用"),
+ /**
+ * 删除
+ */
+ DELETED("2", "删除");
+
+ private final String code;
+ private final String info;
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/enums/UserType.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/enums/UserType.java
new file mode 100644
index 0000000..bb93e36
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/enums/UserType.java
@@ -0,0 +1,39 @@
+package com.fuyuanshen.common.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import com.fuyuanshen.common.core.utils.StringUtils;
+
+/**
+ * 用户类型
+ *
+ * @author Lion Li
+ */
+@Getter
+@AllArgsConstructor
+public enum UserType {
+
+ /**
+ * 后台系统用户
+ */
+ SYS_USER("sys_user"),
+
+ /**
+ * 移动客户端用户
+ */
+ APP_USER("app_user");
+
+ /**
+ * 用户类型标识(用于 token、权限识别等)
+ */
+ private final String userType;
+
+ public static UserType getUserType(String str) {
+ for (UserType value : values()) {
+ if (StringUtils.contains(str, value.getUserType())) {
+ return value;
+ }
+ }
+ throw new RuntimeException("'UserType' not found By " + str);
+ }
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/BadRequestException.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/BadRequestException.java
new file mode 100644
index 0000000..19266a7
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/BadRequestException.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2019-2025 Zheng Jie
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.fuyuanshen.common.core.exception;
+
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+import static org.springframework.http.HttpStatus.BAD_REQUEST;
+
+/**
+ * @author Zheng Jie
+ * @date 2018-11-23
+ * 统一异常处理
+ */
+@Getter
+public class BadRequestException extends RuntimeException{
+
+ private Integer status = BAD_REQUEST.value();
+
+ public BadRequestException(String msg){
+ super(msg);
+ }
+
+ public BadRequestException(HttpStatus status,String msg){
+ super(msg);
+ this.status = status.value();
+ }
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/EntityExistException.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/EntityExistException.java
new file mode 100644
index 0000000..648c078
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/EntityExistException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2019-2025 Zheng Jie
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.fuyuanshen.common.core.exception;
+
+import org.springframework.util.StringUtils;
+
+/**
+ * @author Zheng Jie
+ * @date 2018-11-23
+ */
+public class EntityExistException extends RuntimeException {
+
+ public EntityExistException(Class clazz, String field, String val) {
+ super(EntityExistException.generateMessage(clazz.getSimpleName(), field, val));
+ }
+
+ private static String generateMessage1(String entity, String field, String val) {
+ return StringUtils.capitalize(entity) + " with " + field + " " + val + " existed";
+ }
+
+ private static String generateMessage(String entity, String field, String val) {
+ return field + " :" + val + " 已存在";
+ }
+
+}
\ No newline at end of file
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/EntityNotFoundException.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/EntityNotFoundException.java
new file mode 100644
index 0000000..841dcb8
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/EntityNotFoundException.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2019-2025 Zheng Jie
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.fuyuanshen.common.core.exception;
+
+import org.springframework.util.StringUtils;
+
+/**
+ * @author Zheng Jie
+ * @date 2018-11-23
+ */
+public class EntityNotFoundException extends RuntimeException {
+
+ public EntityNotFoundException(Class clazz, String field, String val) {
+ super(EntityNotFoundException.generateMessage(clazz.getSimpleName(), field, val));
+ }
+
+ private static String generateMessage(String entity, String field, String val) {
+ return StringUtils.capitalize(entity)
+ + " with " + field + " "+ val + " does not exist";
+ }
+}
\ No newline at end of file
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/ServiceException.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/ServiceException.java
new file mode 100644
index 0000000..2219a93
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/ServiceException.java
@@ -0,0 +1,59 @@
+package com.fuyuanshen.common.core.exception;
+
+import lombok.*;
+
+import java.io.Serial;
+
+/**
+ * 业务异常
+ *
+ * @author fys
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+@AllArgsConstructor
+public final class ServiceException extends RuntimeException {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 错误码
+ */
+ private Integer code;
+
+ /**
+ * 错误提示
+ */
+ private String message;
+
+ /**
+ * 错误明细,内部调试错误
+ */
+ private String detailMessage;
+
+ public ServiceException(String message) {
+ this.message = message;
+ }
+
+ public ServiceException(String message, Integer code) {
+ this.message = message;
+ this.code = code;
+ }
+
+ @Override
+ public String getMessage() {
+ return message;
+ }
+
+ public ServiceException setMessage(String message) {
+ this.message = message;
+ return this;
+ }
+
+ public ServiceException setDetailMessage(String detailMessage) {
+ this.detailMessage = detailMessage;
+ return this;
+ }
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/SseException.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/SseException.java
new file mode 100644
index 0000000..0e42bab
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/SseException.java
@@ -0,0 +1,62 @@
+package com.fuyuanshen.common.core.exception;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+
+/**
+ * sse 特制异常
+ *
+ * @author LionLi
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+@AllArgsConstructor
+public final class SseException extends RuntimeException {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 错误码
+ */
+ private Integer code;
+
+ /**
+ * 错误提示
+ */
+ private String message;
+
+ /**
+ * 错误明细,内部调试错误
+ */
+ private String detailMessage;
+
+ public SseException(String message) {
+ this.message = message;
+ }
+
+ public SseException(String message, Integer code) {
+ this.message = message;
+ this.code = code;
+ }
+
+ @Override
+ public String getMessage() {
+ return message;
+ }
+
+ public SseException setMessage(String message) {
+ this.message = message;
+ return this;
+ }
+
+ public SseException setDetailMessage(String detailMessage) {
+ this.detailMessage = detailMessage;
+ return this;
+ }
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/base/BaseException.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/base/BaseException.java
new file mode 100644
index 0000000..a5116b0
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/base/BaseException.java
@@ -0,0 +1,74 @@
+package com.fuyuanshen.common.core.exception.base;
+
+import lombok.AllArgsConstructor;
+import com.fuyuanshen.common.core.utils.MessageUtils;
+import com.fuyuanshen.common.core.utils.StringUtils;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+
+/**
+ * 基础异常
+ *
+ * @author fys
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+@AllArgsConstructor
+public class BaseException extends RuntimeException {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 所属模块
+ */
+ private String module;
+
+ /**
+ * 错误码
+ */
+ private String code;
+
+ /**
+ * 错误码对应的参数
+ */
+ private Object[] args;
+
+ /**
+ * 错误消息
+ */
+ private String defaultMessage;
+
+ public BaseException(String module, String code, Object[] args) {
+ this(module, code, args, null);
+ }
+
+ public BaseException(String module, String defaultMessage) {
+ this(module, null, null, defaultMessage);
+ }
+
+ public BaseException(String code, Object[] args) {
+ this(null, code, args, null);
+ }
+
+ public BaseException(String defaultMessage) {
+ this(null, null, null, defaultMessage);
+ }
+
+ @Override
+ public String getMessage() {
+ String message = null;
+ if (!StringUtils.isEmpty(code)) {
+ message = MessageUtils.message(code, args);
+ }
+ if (message == null) {
+ message = defaultMessage;
+ }
+ return message;
+ }
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/file/FileException.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/file/FileException.java
new file mode 100644
index 0000000..ff1afc3
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/file/FileException.java
@@ -0,0 +1,21 @@
+package com.fuyuanshen.common.core.exception.file;
+
+import com.fuyuanshen.common.core.exception.base.BaseException;
+
+import java.io.Serial;
+
+/**
+ * 文件信息异常类
+ *
+ * @author fys
+ */
+public class FileException extends BaseException {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ public FileException(String code, Object[] args) {
+ super("file", code, args, null);
+ }
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/file/FileNameLengthLimitExceededException.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/file/FileNameLengthLimitExceededException.java
new file mode 100644
index 0000000..d851eda
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/file/FileNameLengthLimitExceededException.java
@@ -0,0 +1,18 @@
+package com.fuyuanshen.common.core.exception.file;
+
+import java.io.Serial;
+
+/**
+ * 文件名称超长限制异常类
+ *
+ * @author fys
+ */
+public class FileNameLengthLimitExceededException extends FileException {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ public FileNameLengthLimitExceededException(int defaultFileNameLength) {
+ super("upload.filename.exceed.length", new Object[]{defaultFileNameLength});
+ }
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/file/FileSizeLimitExceededException.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/file/FileSizeLimitExceededException.java
new file mode 100644
index 0000000..360b642
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/file/FileSizeLimitExceededException.java
@@ -0,0 +1,18 @@
+package com.fuyuanshen.common.core.exception.file;
+
+import java.io.Serial;
+
+/**
+ * 文件名大小限制异常类
+ *
+ * @author fys
+ */
+public class FileSizeLimitExceededException extends FileException {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ public FileSizeLimitExceededException(long defaultMaxSize) {
+ super("upload.exceed.maxSize", new Object[]{defaultMaxSize});
+ }
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/handler/ApiError.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/handler/ApiError.java
new file mode 100644
index 0000000..9243976
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/handler/ApiError.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2019-2025 Zheng Jie
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.fuyuanshen.common.core.exception.handler;
+
+import lombok.Data;
+
+/**
+ * @author Zheng Jie
+ * @date 2018-11-23
+ */
+@Data
+public class ApiError {
+
+ private Integer status = 400;
+ private Long timestamp;
+ private String message;
+
+ private ApiError() {
+ timestamp = System.currentTimeMillis();
+ }
+
+ public static ApiError error(String message){
+ ApiError apiError = new ApiError();
+ apiError.setMessage(message);
+ return apiError;
+ }
+
+ public static ApiError error(Integer status, String message){
+ ApiError apiError = new ApiError();
+ apiError.setStatus(status);
+ apiError.setMessage(message);
+ return apiError;
+ }
+}
+
+
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/user/CaptchaException.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/user/CaptchaException.java
new file mode 100644
index 0000000..653dc48
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/user/CaptchaException.java
@@ -0,0 +1,18 @@
+package com.fuyuanshen.common.core.exception.user;
+
+import java.io.Serial;
+
+/**
+ * 验证码错误异常类
+ *
+ * @author fys
+ */
+public class CaptchaException extends UserException {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ public CaptchaException() {
+ super("user.jcaptcha.error");
+ }
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/user/CaptchaExpireException.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/user/CaptchaExpireException.java
new file mode 100644
index 0000000..8bc0fda
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/user/CaptchaExpireException.java
@@ -0,0 +1,18 @@
+package com.fuyuanshen.common.core.exception.user;
+
+import java.io.Serial;
+
+/**
+ * 验证码失效异常类
+ *
+ * @author fys
+ */
+public class CaptchaExpireException extends UserException {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ public CaptchaExpireException() {
+ super("user.jcaptcha.expire");
+ }
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/user/UserException.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/user/UserException.java
new file mode 100644
index 0000000..b94a076
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/exception/user/UserException.java
@@ -0,0 +1,20 @@
+package com.fuyuanshen.common.core.exception.user;
+
+import com.fuyuanshen.common.core.exception.base.BaseException;
+
+import java.io.Serial;
+
+/**
+ * 用户信息异常类
+ *
+ * @author fys
+ */
+public class UserException extends BaseException {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ public UserException(String code, Object... args) {
+ super("user", code, args, null);
+ }
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/factory/RegexPatternPoolFactory.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/factory/RegexPatternPoolFactory.java
new file mode 100644
index 0000000..98efcea
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/factory/RegexPatternPoolFactory.java
@@ -0,0 +1,52 @@
+package com.fuyuanshen.common.core.factory;
+
+import cn.hutool.core.lang.PatternPool;
+import com.fuyuanshen.common.core.constant.RegexConstants;
+
+import java.util.regex.Pattern;
+
+/**
+ * 正则表达式模式池工厂
+ * 初始化的时候将正则表达式加入缓存池当中
+ * 提高正则表达式的性能,避免重复编译相同的正则表达式
+ *
+ * @author 21001
+ */
+public class RegexPatternPoolFactory extends PatternPool {
+
+ /**
+ * 字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)
+ */
+ public static final Pattern DICTIONARY_TYPE = get(RegexConstants.DICTIONARY_TYPE);
+
+ /**
+ * 身份证号码(后6位)
+ */
+ public static final Pattern ID_CARD_LAST_6 = get(RegexConstants.ID_CARD_LAST_6);
+
+ /**
+ * QQ号码
+ */
+ public static final Pattern QQ_NUMBER = get(RegexConstants.QQ_NUMBER);
+
+ /**
+ * 邮政编码
+ */
+ public static final Pattern POSTAL_CODE = get(RegexConstants.POSTAL_CODE);
+
+ /**
+ * 注册账号
+ */
+ public static final Pattern ACCOUNT = get(RegexConstants.ACCOUNT);
+
+ /**
+ * 密码:包含至少8个字符,包括大写字母、小写字母、数字和特殊字符
+ */
+ public static final Pattern PASSWORD = get(RegexConstants.PASSWORD);
+
+ /**
+ * 通用状态(0表示正常,1表示停用)
+ */
+ public static final Pattern STATUS = get(RegexConstants.STATUS);
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/factory/YmlPropertySourceFactory.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/factory/YmlPropertySourceFactory.java
new file mode 100644
index 0000000..83c1f10
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/factory/YmlPropertySourceFactory.java
@@ -0,0 +1,31 @@
+package com.fuyuanshen.common.core.factory;
+
+import com.fuyuanshen.common.core.utils.StringUtils;
+import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
+import org.springframework.core.env.PropertiesPropertySource;
+import org.springframework.core.env.PropertySource;
+import org.springframework.core.io.support.DefaultPropertySourceFactory;
+import org.springframework.core.io.support.EncodedResource;
+
+import java.io.IOException;
+
+/**
+ * yml 配置源工厂
+ *
+ * @author Lion Li
+ */
+public class YmlPropertySourceFactory extends DefaultPropertySourceFactory {
+
+ @Override
+ public PropertySource> createPropertySource(String name, EncodedResource resource) throws IOException {
+ String sourceName = resource.getResource().getFilename();
+ if (StringUtils.isNotBlank(sourceName) && StringUtils.endsWithAny(sourceName, ".yml", ".yaml")) {
+ YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
+ factory.setResources(resource.getResource());
+ factory.afterPropertiesSet();
+ return new PropertiesPropertySource(sourceName, factory.getObject());
+ }
+ return super.createPropertySource(name, resource);
+ }
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/AppPermissionService.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/AppPermissionService.java
new file mode 100644
index 0000000..ef4b6e9
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/AppPermissionService.java
@@ -0,0 +1,28 @@
+package com.fuyuanshen.common.core.service;
+
+import java.util.Set;
+
+/**
+ * 用户权限处理
+ *
+ * @author Lion Li
+ */
+public interface AppPermissionService {
+
+ /**
+ * 获取角色数据权限
+ *
+ * @param userId 用户id
+ * @return 角色权限信息
+ */
+ Set getRolePermission(Long userId);
+
+ /**
+ * 获取菜单数据权限
+ *
+ * @param userId 用户id
+ * @return 菜单权限信息
+ */
+ Set getMenuPermission(Long userId);
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/ConfigService.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/ConfigService.java
new file mode 100644
index 0000000..8aaf7f5
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/ConfigService.java
@@ -0,0 +1,18 @@
+package com.fuyuanshen.common.core.service;
+
+/**
+ * 通用 参数配置服务
+ *
+ * @author Lion Li
+ */
+public interface ConfigService {
+
+ /**
+ * 根据参数 key 获取参数值
+ *
+ * @param configKey 参数 key
+ * @return 参数值
+ */
+ String getConfigValue(String configKey);
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/DeptService.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/DeptService.java
new file mode 100644
index 0000000..b1b6b41
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/DeptService.java
@@ -0,0 +1,37 @@
+package com.fuyuanshen.common.core.service;
+
+import com.fuyuanshen.common.core.domain.dto.DeptDTO;
+
+import java.util.List;
+
+/**
+ * 通用 部门服务
+ *
+ * @author Lion Li
+ */
+public interface DeptService {
+
+ /**
+ * 通过部门ID查询部门名称
+ *
+ * @param deptIds 部门ID串逗号分隔
+ * @return 部门名称串逗号分隔
+ */
+ String selectDeptNameByIds(String deptIds);
+
+ /**
+ * 根据部门ID查询部门负责人
+ *
+ * @param deptId 部门ID,用于指定需要查询的部门
+ * @return 返回该部门的负责人ID
+ */
+ Long selectDeptLeaderById(Long deptId);
+
+ /**
+ * 查询部门
+ *
+ * @return 部门列表
+ */
+ List selectDeptsByList();
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/DictService.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/DictService.java
new file mode 100644
index 0000000..cfc2f53
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/DictService.java
@@ -0,0 +1,87 @@
+package com.fuyuanshen.common.core.service;
+
+import com.fuyuanshen.common.core.domain.dto.DictDataDTO;
+import com.fuyuanshen.common.core.domain.dto.DictTypeDTO;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 通用 字典服务
+ *
+ * @author Lion Li
+ */
+public interface DictService {
+
+ /**
+ * 分隔符
+ */
+ String SEPARATOR = ",";
+
+ /**
+ * 根据字典类型和字典值获取字典标签
+ *
+ * @param dictType 字典类型
+ * @param dictValue 字典值
+ * @return 字典标签
+ */
+ default String getDictLabel(String dictType, String dictValue) {
+ return getDictLabel(dictType, dictValue, SEPARATOR);
+ }
+
+ /**
+ * 根据字典类型和字典标签获取字典值
+ *
+ * @param dictType 字典类型
+ * @param dictLabel 字典标签
+ * @return 字典值
+ */
+ default String getDictValue(String dictType, String dictLabel) {
+ return getDictValue(dictType, dictLabel, SEPARATOR);
+ }
+
+ /**
+ * 根据字典类型和字典值获取字典标签
+ *
+ * @param dictType 字典类型
+ * @param dictValue 字典值
+ * @param separator 分隔符
+ * @return 字典标签
+ */
+ String getDictLabel(String dictType, String dictValue, String separator);
+
+ /**
+ * 根据字典类型和字典标签获取字典值
+ *
+ * @param dictType 字典类型
+ * @param dictLabel 字典标签
+ * @param separator 分隔符
+ * @return 字典值
+ */
+ String getDictValue(String dictType, String dictLabel, String separator);
+
+ /**
+ * 获取字典下所有的字典值与标签
+ *
+ * @param dictType 字典类型
+ * @return dictValue为key,dictLabel为值组成的Map
+ */
+ Map getAllDictByDictType(String dictType);
+
+ /**
+ * 根据字典类型查询详细信息
+ *
+ * @param dictType 字典类型
+ * @return 字典类型详细信息
+ */
+ DictTypeDTO getDictType(String dictType);
+
+ /**
+ * 根据字典类型查询字典数据列表
+ *
+ * @param dictType 字典类型
+ * @return 字典数据列表
+ */
+ List getDictData(String dictType);
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/OssService.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/OssService.java
new file mode 100644
index 0000000..6170629
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/OssService.java
@@ -0,0 +1,29 @@
+package com.fuyuanshen.common.core.service;
+
+import com.fuyuanshen.common.core.domain.dto.OssDTO;
+
+import java.util.List;
+
+/**
+ * 通用 OSS服务
+ *
+ * @author Lion Li
+ */
+public interface OssService {
+
+ /**
+ * 通过ossId查询对应的url
+ *
+ * @param ossIds ossId串逗号分隔
+ * @return url串逗号分隔
+ */
+ String selectUrlByIds(String ossIds);
+
+ /**
+ * 通过ossId查询列表
+ *
+ * @param ossIds ossId串逗号分隔
+ * @return 列表
+ */
+ List selectByIds(String ossIds);
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/PermissionService.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/PermissionService.java
new file mode 100644
index 0000000..85fe060
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/PermissionService.java
@@ -0,0 +1,28 @@
+package com.fuyuanshen.common.core.service;
+
+import java.util.Set;
+
+/**
+ * 用户权限处理
+ *
+ * @author Lion Li
+ */
+public interface PermissionService {
+
+ /**
+ * 获取角色数据权限
+ *
+ * @param userId 用户id
+ * @return 角色权限信息
+ */
+ Set getRolePermission(Long userId);
+
+ /**
+ * 获取菜单数据权限
+ *
+ * @param userId 用户id
+ * @return 菜单权限信息
+ */
+ Set getMenuPermission(Long userId);
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/PostService.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/PostService.java
new file mode 100644
index 0000000..df7a947
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/PostService.java
@@ -0,0 +1,10 @@
+package com.fuyuanshen.common.core.service;
+
+/**
+ * 通用 岗位服务
+ *
+ * @author AprilWind
+ */
+public interface PostService {
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/RoleService.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/RoleService.java
new file mode 100644
index 0000000..2142179
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/RoleService.java
@@ -0,0 +1,10 @@
+package com.fuyuanshen.common.core.service;
+
+/**
+ * 通用 角色服务
+ *
+ * @author AprilWind
+ */
+public interface RoleService {
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/TaskAssigneeService.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/TaskAssigneeService.java
new file mode 100644
index 0000000..55ddb02
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/TaskAssigneeService.java
@@ -0,0 +1,45 @@
+package com.fuyuanshen.common.core.service;
+
+import com.fuyuanshen.common.core.domain.dto.TaskAssigneeDTO;
+import com.fuyuanshen.common.core.domain.model.TaskAssigneeBody;
+
+/**
+ * 工作流设计器获取任务执行人
+ *
+ * @author Lion Li
+ */
+public interface TaskAssigneeService {
+
+ /**
+ * 查询角色并返回任务指派的列表,支持分页
+ *
+ * @param taskQuery 查询条件
+ * @return 办理人
+ */
+ TaskAssigneeDTO selectRolesByTaskAssigneeList(TaskAssigneeBody taskQuery);
+
+ /**
+ * 查询岗位并返回任务指派的列表,支持分页
+ *
+ * @param taskQuery 查询条件
+ * @return 办理人
+ */
+ TaskAssigneeDTO selectPostsByTaskAssigneeList(TaskAssigneeBody taskQuery);
+
+ /**
+ * 查询部门并返回任务指派的列表,支持分页
+ *
+ * @param taskQuery 查询条件
+ * @return 办理人
+ */
+ TaskAssigneeDTO selectDeptsByTaskAssigneeList(TaskAssigneeBody taskQuery);
+
+ /**
+ * 查询用户并返回任务指派的列表,支持分页
+ *
+ * @param taskQuery 查询条件
+ * @return 办理人
+ */
+ TaskAssigneeDTO selectUsersByTaskAssigneeList(TaskAssigneeBody taskQuery);
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/UserService.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/UserService.java
new file mode 100644
index 0000000..7f1bf48
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/UserService.java
@@ -0,0 +1,127 @@
+package com.fuyuanshen.common.core.service;
+
+import com.fuyuanshen.common.core.domain.dto.UserDTO;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 通用 用户服务
+ *
+ * @author Lion Li
+ */
+public interface UserService {
+
+ /**
+ * 通过用户ID查询用户账户
+ *
+ * @param userId 用户ID
+ * @return 用户账户
+ */
+ String selectUserNameById(Long userId);
+
+ /**
+ * 通过用户ID查询用户账户
+ *
+ * @param userId 用户ID
+ * @return 用户名称
+ */
+ String selectNicknameById(Long userId);
+
+ /**
+ * 通过用户ID查询用户账户
+ *
+ * @param userIds 用户ID 多个用逗号隔开
+ * @return 用户名称
+ */
+ String selectNicknameByIds(String userIds);
+
+ /**
+ * 通过用户ID查询用户手机号
+ *
+ * @param userId 用户id
+ * @return 用户手机号
+ */
+ String selectPhonenumberById(Long userId);
+
+ /**
+ * 通过用户ID查询用户邮箱
+ *
+ * @param userId 用户id
+ * @return 用户邮箱
+ */
+ String selectEmailById(Long userId);
+
+ /**
+ * 通过用户ID查询用户列表
+ *
+ * @param userIds 用户ids
+ * @return 用户列表
+ */
+ List selectListByIds(List userIds);
+
+ /**
+ * 通过角色ID查询用户ID
+ *
+ * @param roleIds 角色ids
+ * @return 用户ids
+ */
+ List selectUserIdsByRoleIds(List roleIds);
+
+ /**
+ * 通过角色ID查询用户
+ *
+ * @param roleIds 角色ids
+ * @return 用户
+ */
+ List selectUsersByRoleIds(List roleIds);
+
+ /**
+ * 通过部门ID查询用户
+ *
+ * @param deptIds 部门ids
+ * @return 用户
+ */
+ List selectUsersByDeptIds(List deptIds);
+
+ /**
+ * 通过岗位ID查询用户
+ *
+ * @param postIds 岗位ids
+ * @return 用户
+ */
+ List selectUsersByPostIds(List postIds);
+
+ /**
+ * 根据用户 ID 列表查询用户名称映射关系
+ *
+ * @param userIds 用户 ID 列表
+ * @return Map,其中 key 为用户 ID,value 为对应的用户名称
+ */
+ Map selectUserNamesByIds(List userIds);
+
+ /**
+ * 根据角色 ID 列表查询角色名称映射关系
+ *
+ * @param roleIds 角色 ID 列表
+ * @return Map,其中 key 为角色 ID,value 为对应的角色名称
+ */
+ Map selectRoleNamesByIds(List roleIds);
+
+ /**
+ * 根据部门 ID 列表查询部门名称映射关系
+ *
+ * @param deptIds 部门 ID 列表
+ * @return Map,其中 key 为部门 ID,value 为对应的部门名称
+ */
+ Map selectDeptNamesByIds(List deptIds);
+
+ /**
+ * 根据岗位 ID 列表查询岗位名称映射关系
+ *
+ * @param postIds 岗位 ID 列表
+ * @return Map,其中 key 为岗位 ID,value 为对应的岗位名称
+ */
+ Map selectPostNamesByIds(List postIds);
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/WorkflowService.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/WorkflowService.java
new file mode 100644
index 0000000..37705ff
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/service/WorkflowService.java
@@ -0,0 +1,95 @@
+package com.fuyuanshen.common.core.service;
+
+import com.fuyuanshen.common.core.domain.dto.CompleteTaskDTO;
+import com.fuyuanshen.common.core.domain.dto.StartProcessDTO;
+import com.fuyuanshen.common.core.domain.dto.StartProcessReturnDTO;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 通用 工作流服务
+ *
+ * @author may
+ */
+public interface WorkflowService {
+
+ /**
+ * 运行中的实例 删除程实例,删除历史记录,删除业务与流程关联信息
+ *
+ * @param businessIds 业务id
+ * @return 结果
+ */
+ boolean deleteInstance(List businessIds);
+
+ /**
+ * 获取当前流程状态
+ *
+ * @param taskId 任务id
+ * @return 状态
+ */
+ String getBusinessStatusByTaskId(Long taskId);
+
+ /**
+ * 获取当前流程状态
+ *
+ * @param businessId 业务id
+ * @return 状态
+ */
+ String getBusinessStatus(String businessId);
+
+ /**
+ * 设置流程变量
+ *
+ * @param instanceId 流程实例id
+ * @param variable 流程变量
+ */
+ void setVariable(Long instanceId, Map variable);
+
+ /**
+ * 获取流程变量
+ *
+ * @param instanceId 流程实例id
+ */
+ Map instanceVariable(Long instanceId);
+
+ /**
+ * 按照业务id查询流程实例id
+ *
+ * @param businessId 业务id
+ * @return 结果
+ */
+ Long getInstanceIdByBusinessId(String businessId);
+
+ /**
+ * 新增租户流程定义
+ *
+ * @param tenantId 租户id
+ */
+ void syncDef(String tenantId);
+
+ /**
+ * 启动流程
+ *
+ * @param startProcess 参数
+ * @return 结果
+ */
+ StartProcessReturnDTO startWorkFlow(StartProcessDTO startProcess);
+
+ /**
+ * 办理任务
+ * 系统后台发起审批 无用户信息 需要忽略权限
+ * completeTask.getVariables().put("ignore", true);
+ *
+ * @param completeTask 参数
+ */
+ boolean completeTask(CompleteTaskDTO completeTask);
+
+ /**
+ * 办理任务
+ *
+ * @param taskId 任务ID
+ * @param message 办理意见
+ */
+ boolean completeTask(Long taskId, String message);
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/CloseUtil.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/CloseUtil.java
new file mode 100644
index 0000000..809d77c
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/CloseUtil.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2019-2025 Zheng Jie
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.fuyuanshen.common.core.utils;
+
+import java.io.Closeable;
+
+/**
+ * @author Zheng Jie
+ * @description 用于关闭各种连接,缺啥补啥
+ * @date 2021-03-05
+ **/
+public class CloseUtil {
+
+ public static void close(Closeable closeable) {
+ if (null != closeable) {
+ try {
+ closeable.close();
+ } catch (Exception e) {
+ // 静默关闭
+ }
+ }
+ }
+
+ public static void close(AutoCloseable closeable) {
+ if (null != closeable) {
+ try {
+ closeable.close();
+ } catch (Exception e) {
+ // 静默关闭
+ }
+ }
+ }
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/DateUtils.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/DateUtils.java
new file mode 100644
index 0000000..91aacb6
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/DateUtils.java
@@ -0,0 +1,300 @@
+package com.fuyuanshen.common.core.utils;
+
+import org.apache.commons.lang3.time.DateFormatUtils;
+import com.fuyuanshen.common.core.enums.FormatsType;
+import com.fuyuanshen.common.core.exception.ServiceException;
+
+import java.lang.management.ManagementFactory;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.*;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 时间工具类
+ *
+ * @author fys
+ */
+public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
+ private static final String[] PARSE_PATTERNS = {
+ "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
+ "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
+ "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
+
+ @Deprecated
+ private DateUtils() {
+ }
+
+ /**
+ * 获取当前日期和时间
+ *
+ * @return 当前日期和时间的 Date 对象表示
+ */
+ public static Date getNowDate() {
+ return new Date();
+ }
+
+ /**
+ * 获取当前日期的字符串表示,格式为YYYY-MM-DD
+ *
+ * @return 当前日期的字符串表示
+ */
+ public static String getDate() {
+ return dateTimeNow(FormatsType.YYYY_MM_DD);
+ }
+
+ /**
+ * 获取当前日期的字符串表示,格式为yyyyMMdd
+ *
+ * @return 当前日期的字符串表示
+ */
+ public static String getCurrentDate() {
+ return DateFormatUtils.format(new Date(), FormatsType.YYYYMMDD.getTimeFormat());
+ }
+
+ /**
+ * 获取当前日期的路径格式字符串,格式为"yyyy/MM/dd"
+ *
+ * @return 当前日期的路径格式字符串
+ */
+ public static String datePath() {
+ Date now = new Date();
+ return DateFormatUtils.format(now, FormatsType.YYYY_MM_DD_SLASH.getTimeFormat());
+ }
+
+ /**
+ * 获取当前时间的字符串表示,格式为YYYY-MM-DD HH:MM:SS
+ *
+ * @return 当前时间的字符串表示
+ */
+ public static String getTime() {
+ return dateTimeNow(FormatsType.YYYY_MM_DD_HH_MM_SS);
+ }
+
+ /**
+ * 获取当前时间的字符串表示,格式为 "HH:MM:SS"
+ *
+ * @return 当前时间的字符串表示,格式为 "HH:MM:SS"
+ */
+ public static String getTimeWithHourMinuteSecond() {
+ return dateTimeNow(FormatsType.HH_MM_SS);
+ }
+
+ /**
+ * 获取当前日期和时间的字符串表示,格式为YYYYMMDDHHMMSS
+ *
+ * @return 当前日期和时间的字符串表示
+ */
+ public static String dateTimeNow() {
+ return dateTimeNow(FormatsType.YYYYMMDDHHMMSS);
+ }
+
+ /**
+ * 获取当前日期和时间的指定格式的字符串表示
+ *
+ * @param format 日期时间格式,例如"YYYY-MM-DD HH:MM:SS"
+ * @return 当前日期和时间的字符串表示
+ */
+ public static String dateTimeNow(final FormatsType format) {
+ return parseDateToStr(format, new Date());
+ }
+
+ /**
+ * 将指定日期格式化为 YYYY-MM-DD 格式的字符串
+ *
+ * @param date 要格式化的日期对象
+ * @return 格式化后的日期字符串
+ */
+ public static String formatDate(final Date date) {
+ return parseDateToStr(FormatsType.YYYY_MM_DD, date);
+ }
+
+ /**
+ * 将指定日期格式化为 YYYY-MM-DD HH:MM:SS 格式的字符串
+ *
+ * @param date 要格式化的日期对象
+ * @return 格式化后的日期时间字符串
+ */
+ public static String formatDateTime(final Date date) {
+ return parseDateToStr(FormatsType.YYYY_MM_DD_HH_MM_SS, date);
+ }
+
+ /**
+ * 将指定日期按照指定格式进行格式化
+ *
+ * @param format 要使用的日期时间格式,例如"YYYY-MM-DD HH:MM:SS"
+ * @param date 要格式化的日期对象
+ * @return 格式化后的日期时间字符串
+ */
+ public static String parseDateToStr(final FormatsType format, final Date date) {
+ return new SimpleDateFormat(format.getTimeFormat()).format(date);
+ }
+
+ /**
+ * 将指定格式的日期时间字符串转换为 Date 对象
+ *
+ * @param format 要解析的日期时间格式,例如"YYYY-MM-DD HH:MM:SS"
+ * @param ts 要解析的日期时间字符串
+ * @return 解析后的 Date 对象
+ * @throws RuntimeException 如果解析过程中发生异常
+ */
+ public static Date parseDateTime(final FormatsType format, final String ts) {
+ try {
+ return new SimpleDateFormat(format.getTimeFormat()).parse(ts);
+ } catch (ParseException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 将对象转换为日期对象
+ *
+ * @param str 要转换的对象,通常是字符串
+ * @return 转换后的日期对象,如果转换失败或输入为null,则返回null
+ */
+ public static Date parseDate(Object str) {
+ if (str == null) {
+ return null;
+ }
+ try {
+ return parseDate(str.toString(), PARSE_PATTERNS);
+ } catch (ParseException e) {
+ return null;
+ }
+ }
+
+ /**
+ * 获取服务器启动时间
+ *
+ * @return 服务器启动时间的 Date 对象表示
+ */
+ public static Date getServerStartDate() {
+ long time = ManagementFactory.getRuntimeMXBean().getStartTime();
+ return new Date(time);
+ }
+
+ /**
+ * 计算两个时间之间的时间差,并以指定单位返回(绝对值)
+ *
+ * @param start 起始时间
+ * @param end 结束时间
+ * @param unit 所需返回的时间单位(DAYS、HOURS、MINUTES、SECONDS、MILLISECONDS、MICROSECONDS、NANOSECONDS)
+ * @return 时间差的绝对值,以指定单位表示
+ */
+ public static long difference(Date start, Date end, TimeUnit unit) {
+ // 计算时间差,单位为毫秒,取绝对值避免负数
+ long diffInMillis = Math.abs(end.getTime() - start.getTime());
+
+ // 根据目标单位转换时间差
+ return switch (unit) {
+ case DAYS -> diffInMillis / TimeUnit.DAYS.toMillis(1);
+ case HOURS -> diffInMillis / TimeUnit.HOURS.toMillis(1);
+ case MINUTES -> diffInMillis / TimeUnit.MINUTES.toMillis(1);
+ case SECONDS -> diffInMillis / TimeUnit.SECONDS.toMillis(1);
+ case MILLISECONDS -> diffInMillis;
+ case MICROSECONDS -> TimeUnit.MILLISECONDS.toMicros(diffInMillis);
+ case NANOSECONDS -> TimeUnit.MILLISECONDS.toNanos(diffInMillis);
+ };
+ }
+
+ /**
+ * 计算两个日期之间的时间差,并以天、小时和分钟的格式返回
+ *
+ * @param endDate 结束日期
+ * @param nowDate 当前日期
+ * @return 表示时间差的字符串,格式为"天 小时 分钟"
+ */
+ public static String getDatePoor(Date endDate, Date nowDate) {
+ long diffInMillis = endDate.getTime() - nowDate.getTime();
+ long day = TimeUnit.MILLISECONDS.toDays(diffInMillis);
+ long hour = TimeUnit.MILLISECONDS.toHours(diffInMillis) % 24;
+ long min = TimeUnit.MILLISECONDS.toMinutes(diffInMillis) % 60;
+ return String.format("%d天 %d小时 %d分钟", day, hour, min);
+ }
+
+ /**
+ * 计算两个时间点的差值(天、小时、分钟、秒),当值为0时不显示该单位
+ *
+ * @param endDate 结束时间
+ * @param nowDate 当前时间
+ * @return 时间差字符串,格式为 "x天 x小时 x分钟 x秒",若为 0 则不显示
+ */
+ public static String getTimeDifference(Date endDate, Date nowDate) {
+ long diffInMillis = endDate.getTime() - nowDate.getTime();
+ long day = TimeUnit.MILLISECONDS.toDays(diffInMillis);
+ long hour = TimeUnit.MILLISECONDS.toHours(diffInMillis) % 24;
+ long min = TimeUnit.MILLISECONDS.toMinutes(diffInMillis) % 60;
+ long sec = TimeUnit.MILLISECONDS.toSeconds(diffInMillis) % 60;
+ // 构建时间差字符串,条件是值不为0才显示
+ StringBuilder result = new StringBuilder();
+ if (day > 0) {
+ result.append(String.format("%d天 ", day));
+ }
+ if (hour > 0) {
+ result.append(String.format("%d小时 ", hour));
+ }
+ if (min > 0) {
+ result.append(String.format("%d分钟 ", min));
+ }
+ if (sec > 0) {
+ result.append(String.format("%d秒", sec));
+ }
+ return result.length() > 0 ? result.toString().trim() : "0秒";
+ }
+
+ /**
+ * 将 LocalDateTime 对象转换为 Date 对象
+ *
+ * @param temporalAccessor 要转换的 LocalDateTime 对象
+ * @return 转换后的 Date 对象
+ */
+ public static Date toDate(LocalDateTime temporalAccessor) {
+ ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault());
+ return Date.from(zdt.toInstant());
+ }
+
+ /**
+ * 将 LocalDate 对象转换为 Date 对象
+ *
+ * @param temporalAccessor 要转换的 LocalDate 对象
+ * @return 转换后的 Date 对象
+ */
+ public static Date toDate(LocalDate temporalAccessor) {
+ LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0));
+ ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
+ return Date.from(zdt.toInstant());
+ }
+
+ /**
+ * 校验日期范围
+ *
+ * @param startDate 开始日期
+ * @param endDate 结束日期
+ * @param maxValue 最大时间跨度的限制值
+ * @param unit 时间跨度的单位,可选择 "DAYS"、"HOURS" 或 "MINUTES"
+ */
+ public static void validateDateRange(Date startDate, Date endDate, int maxValue, TimeUnit unit) {
+ // 校验结束日期不能早于开始日期
+ if (endDate.before(startDate)) {
+ throw new ServiceException("结束日期不能早于开始日期");
+ }
+
+ // 计算时间跨度
+ long diffInMillis = endDate.getTime() - startDate.getTime();
+
+ // 根据单位转换时间跨度
+ long diff = switch (unit) {
+ case DAYS -> TimeUnit.MILLISECONDS.toDays(diffInMillis);
+ case HOURS -> TimeUnit.MILLISECONDS.toHours(diffInMillis);
+ case MINUTES -> TimeUnit.MILLISECONDS.toMinutes(diffInMillis);
+ default -> throw new IllegalArgumentException("不支持的时间单位");
+ };
+
+ // 校验时间跨度不超过最大限制
+ if (diff > maxValue) {
+ throw new ServiceException("最大时间跨度为 " + maxValue + " " + unit.toString().toLowerCase());
+ }
+ }
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/ImageToCArrayConverterUtils.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/ImageToCArrayConverterUtils.java
new file mode 100644
index 0000000..598db1e
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/ImageToCArrayConverterUtils.java
@@ -0,0 +1,90 @@
+package com.fuyuanshen.common.core.utils;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+public class ImageToCArrayConverterUtils {
+
+ public static void main(String[] args) {
+ try {
+ convertImageToCArray("E:\\workspace\\6170_强光_160_80_2.jpg", "E:\\workspace\\output.c", 160, 80);
+ System.out.println("转换成功!");
+ } catch (IOException e) {
+ System.err.println("转换失败: " + e.getMessage());
+ }
+ }
+
+ public static void convertImageToCArray(String inputPath, String outputPath,
+ int width, int height) throws IOException {
+ // 读取原始图片
+ BufferedImage originalImage = ImageIO.read(new File(inputPath));
+
+ // 调整图片尺寸
+ BufferedImage resizedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+ resizedImage.getGraphics().drawImage(
+ originalImage, 0, 0, width, height, null);
+
+ // 转换像素数据为RGB565格式(高位在前)
+ ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ int rgb = resizedImage.getRGB(x, y);
+
+ // 提取RGB分量
+ int r = (rgb >> 16) & 0xFF;
+ int g = (rgb >> 8) & 0xFF;
+ int b = rgb & 0xFF;
+
+ // 转换为RGB565(5位红,6位绿,5位蓝)
+ int r5 = (r >> 3) & 0x1F;
+ int g6 = (g >> 2) & 0x3F;
+ int b5 = (b >> 3) & 0x1F;
+
+ // 组合为16位值
+ int rgb565 = (r5 << 11) | (g6 << 5) | b5;
+
+ // 高位在前(大端序)写入字节
+ byteStream.write((rgb565 >> 8) & 0xFF); // 高字节
+ byteStream.write(rgb565 & 0xFF); // 低字节
+ }
+ }
+
+ byte[] imageData = byteStream.toByteArray();
+
+ // 生成C语言数组文件
+ try (FileOutputStream fos = new FileOutputStream(outputPath)) {
+ // 写入注释行(包含尺寸信息)
+ String header = String.format("/* 0X10,0X10,0X00,0X%02X,0X00,0X%02X,0X01,0X1B, */\n",
+ width, height);
+ fos.write(header.getBytes());
+
+ // 写入数组声明
+ fos.write("const unsigned char gImage_data[] = {\n".getBytes());
+
+ // 写入数据(每行16个字节)
+ for (int i = 0; i < imageData.length; i++) {
+ // 写入0X前缀
+ fos.write(("0X" + String.format("%02X", imageData[i] & 0xFF)).getBytes());
+
+ // 添加逗号(最后一个除外)
+ if (i < imageData.length - 1) {
+ fos.write(',');
+ }
+
+ // 换行和缩进
+ if ((i + 1) % 16 == 0) {
+ fos.write('\n');
+ } else {
+ fos.write(' ');
+ }
+ }
+
+ // 写入数组结尾
+ fos.write("\n};\n".getBytes());
+ }
+ }
+}
\ No newline at end of file
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/MapstructUtils.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/MapstructUtils.java
new file mode 100644
index 0000000..2adbe0f
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/MapstructUtils.java
@@ -0,0 +1,93 @@
+package com.fuyuanshen.common.core.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ObjectUtil;
+import io.github.linpeilie.Converter;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Mapstruct 工具类
+ * 参考文档:mapstruct-plus
+ *
+ *
+ * @author Michelle.Chung
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class MapstructUtils {
+
+ private final static Converter CONVERTER = SpringUtils.getBean(Converter.class);
+
+ /**
+ * 将 T 类型对象,转换为 desc 类型的对象并返回
+ *
+ * @param source 数据来源实体
+ * @param desc 描述对象 转换后的对象
+ * @return desc
+ */
+ public static V convert(T source, Class desc) {
+ if (ObjectUtil.isNull(source)) {
+ return null;
+ }
+ if (ObjectUtil.isNull(desc)) {
+ return null;
+ }
+ return CONVERTER.convert(source, desc);
+ }
+
+ /**
+ * 将 T 类型对象,按照配置的映射字段规则,给 desc 类型的对象赋值并返回 desc 对象
+ *
+ * @param source 数据来源实体
+ * @param desc 转换后的对象
+ * @return desc
+ */
+ public static V convert(T source, V desc) {
+ if (ObjectUtil.isNull(source)) {
+ return null;
+ }
+ if (ObjectUtil.isNull(desc)) {
+ return null;
+ }
+ return CONVERTER.convert(source, desc);
+ }
+
+ /**
+ * 将 T 类型的集合,转换为 desc 类型的集合并返回
+ *
+ * @param sourceList 数据来源实体列表
+ * @param desc 描述对象 转换后的对象
+ * @return desc
+ */
+ public static List convert(List sourceList, Class desc) {
+ if (ObjectUtil.isNull(sourceList)) {
+ return null;
+ }
+ if (CollUtil.isEmpty(sourceList)) {
+ return CollUtil.newArrayList();
+ }
+ return CONVERTER.convert(sourceList, desc);
+ }
+
+ /**
+ * 将 Map 转换为 beanClass 类型的集合并返回
+ *
+ * @param map 数据来源
+ * @param beanClass bean类
+ * @return bean对象
+ */
+ public static T convert(Map map, Class beanClass) {
+ if (MapUtil.isEmpty(map)) {
+ return null;
+ }
+ if (ObjectUtil.isNull(beanClass)) {
+ return null;
+ }
+ return CONVERTER.convert(map, beanClass);
+ }
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/MessageUtils.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/MessageUtils.java
new file mode 100644
index 0000000..0b3b98c
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/MessageUtils.java
@@ -0,0 +1,33 @@
+package com.fuyuanshen.common.core.utils;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.springframework.context.MessageSource;
+import org.springframework.context.NoSuchMessageException;
+import org.springframework.context.i18n.LocaleContextHolder;
+
+/**
+ * 获取i18n资源文件
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class MessageUtils {
+
+ private static final MessageSource MESSAGE_SOURCE = SpringUtils.getBean(MessageSource.class);
+
+ /**
+ * 根据消息键和参数 获取消息 委托给spring messageSource
+ *
+ * @param code 消息键
+ * @param args 参数
+ * @return 获取国际化翻译值
+ */
+ public static String message(String code, Object... args) {
+ try {
+ return MESSAGE_SOURCE.getMessage(code, args, LocaleContextHolder.getLocale());
+ } catch (NoSuchMessageException e) {
+ return code;
+ }
+ }
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/NetUtils.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/NetUtils.java
new file mode 100644
index 0000000..a6fd5e1
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/NetUtils.java
@@ -0,0 +1,84 @@
+package com.fuyuanshen.common.core.utils;
+
+import cn.hutool.core.lang.PatternPool;
+import cn.hutool.core.net.NetUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import com.fuyuanshen.common.core.utils.regex.RegexUtils;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * 增强网络相关工具类
+ *
+ * @author 秋辞未寒
+ */
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class NetUtils extends NetUtil {
+
+ /**
+ * 判断是否为IPv6地址
+ *
+ * @param ip IP地址
+ * @return 是否为IPv6地址
+ */
+ public static boolean isIPv6(String ip) {
+ try {
+ // 判断是否为IPv6地址
+ return InetAddress.getByName(ip) instanceof Inet6Address;
+ } catch (UnknownHostException e) {
+ return false;
+ }
+ }
+
+ /**
+ * 判断IPv6地址是否为内网地址
+ *
+ * 以下地址将归类为本地地址,如有业务场景有需要,请根据需求自行处理:
+ *
+ * 通配符地址 0:0:0:0:0:0:0:0
+ * 链路本地地址 fe80::/10
+ * 唯一本地地址 fec0::/10
+ * 环回地址 ::1
+ *
+ *
+ * @param ip IP地址
+ * @return 是否为内网地址
+ */
+ public static boolean isInnerIPv6(String ip) {
+ try {
+ // 判断是否为IPv6地址
+ if (InetAddress.getByName(ip) instanceof Inet6Address inet6Address) {
+ // isAnyLocalAddress 判断是否为通配符地址,通常不会将其视为内网地址,根据业务场景自行处理判断
+ // isLinkLocalAddress 判断是否为链路本地地址,通常不算内网地址,是否划分归属于内网需要根据业务场景自行处理判断
+ // isLoopbackAddress 判断是否为环回地址,与IPv4的 127.0.0.1 同理,用于表示本机
+ // isSiteLocalAddress 判断是否为本地站点地址,IPv6唯一本地地址(Unique Local Addresses,简称ULA)
+ if (inet6Address.isAnyLocalAddress()
+ || inet6Address.isLinkLocalAddress()
+ || inet6Address.isLoopbackAddress()
+ || inet6Address.isSiteLocalAddress()) {
+ return true;
+ }
+ }
+ } catch (UnknownHostException e) {
+ // 注意,isInnerIPv6方法和isIPv6方法的适用范围不同,所以此处不能忽略其异常信息。
+ throw new IllegalArgumentException("Invalid IPv6 address!", e);
+ }
+ return false;
+ }
+
+ /**
+ * 判断是否为IPv4地址
+ *
+ * @param ip IP地址
+ * @return 是否为IPv4地址
+ */
+ public static boolean isIPv4(String ip) {
+ return RegexUtils.isMatch(PatternPool.IPV4, ip);
+ }
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/ObjectUtils.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/ObjectUtils.java
new file mode 100644
index 0000000..36c4e4f
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/ObjectUtils.java
@@ -0,0 +1,60 @@
+package com.fuyuanshen.common.core.utils;
+
+import cn.hutool.core.util.ObjectUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.util.function.Function;
+
+/**
+ * 对象工具类
+ *
+ * @author 秋辞未寒
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ObjectUtils extends ObjectUtil {
+
+ /**
+ * 如果对象不为空,则获取对象中的某个字段 ObjectUtils.notNullGetter(user, User::getName);
+ *
+ * @param obj 对象
+ * @param func 获取方法
+ * @return 对象字段
+ */
+ public static E notNullGetter(T obj, Function func) {
+ if (isNotNull(obj) && isNotNull(func)) {
+ return func.apply(obj);
+ }
+ return null;
+ }
+
+ /**
+ * 如果对象不为空,则获取对象中的某个字段,否则返回默认值
+ *
+ * @param obj 对象
+ * @param func 获取方法
+ * @param defaultValue 默认值
+ * @return 对象字段
+ */
+ public static E notNullGetter(T obj, Function func, E defaultValue) {
+ if (isNotNull(obj) && isNotNull(func)) {
+ return func.apply(obj);
+ }
+ return defaultValue;
+ }
+
+ /**
+ * 如果值不为空,则返回值,否则返回默认值
+ *
+ * @param obj 对象
+ * @param defaultValue 默认值
+ * @return 对象字段
+ */
+ public static T notNull(T obj, T defaultValue) {
+ if (isNotNull(obj)) {
+ return obj;
+ }
+ return defaultValue;
+ }
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/PageUtil.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/PageUtil.java
new file mode 100644
index 0000000..b10bb35
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/PageUtil.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2019-2025 Zheng Jie
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.fuyuanshen.common.core.utils;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.fuyuanshen.common.core.domain.PageResult;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 分页工具
+ * @author Zheng Jie
+ * @date 2018-12-10
+ */
+public class PageUtil extends cn.hutool.core.util.PageUtil {
+
+ /**
+ * List 分页
+ */
+ public static List paging(int page, int size , List list) {
+ int fromIndex = page * size;
+ int toIndex = page * size + size;
+ if(fromIndex > list.size()){
+ return Collections.emptyList();
+ } else if(toIndex >= list.size()) {
+ return list.subList(fromIndex,list.size());
+ } else {
+ return list.subList(fromIndex,toIndex);
+ }
+ }
+
+ /**
+ * Page 数据处理
+ */
+ public static PageResult toPage(IPage page) {
+ return new PageResult<>(page.getRecords(), page.getTotal());
+ }
+
+ /**
+ * 自定义分页
+ */
+ public static PageResult toPage(List list) {
+ return new PageResult<>(list, list.size());
+ }
+
+ /**
+ * 返回空数据
+ */
+ public static PageResult noData () {
+ return new PageResult<>(null, 0);
+ }
+
+ /**
+ * 自定义分页
+ */
+ public static PageResult toPage(List list, long totalElements) {
+ return new PageResult<>(list, totalElements);
+ }
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/ServletUtils.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/ServletUtils.java
new file mode 100644
index 0000000..d6d890d
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/ServletUtils.java
@@ -0,0 +1,289 @@
+package com.fuyuanshen.common.core.utils;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.extra.servlet.JakartaServletUtil;
+import cn.hutool.http.HttpStatus;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpSession;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.springframework.http.MediaType;
+import org.springframework.util.LinkedCaseInsensitiveMap;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import java.io.IOException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 客户端工具类,提供获取请求参数、响应处理、头部信息等常用操作
+ *
+ * @author fys
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ServletUtils extends JakartaServletUtil {
+
+ /**
+ * 获取指定名称的 String 类型的请求参数
+ *
+ * @param name 参数名
+ * @return 参数值
+ */
+ public static String getParameter(String name) {
+ return getRequest().getParameter(name);
+ }
+
+ /**
+ * 获取指定名称的 String 类型的请求参数,若参数不存在,则返回默认值
+ *
+ * @param name 参数名
+ * @param defaultValue 默认值
+ * @return 参数值或默认值
+ */
+ public static String getParameter(String name, String defaultValue) {
+ return Convert.toStr(getRequest().getParameter(name), defaultValue);
+ }
+
+ /**
+ * 获取指定名称的 Integer 类型的请求参数
+ *
+ * @param name 参数名
+ * @return 参数值
+ */
+ public static Integer getParameterToInt(String name) {
+ return Convert.toInt(getRequest().getParameter(name));
+ }
+
+ /**
+ * 获取指定名称的 Integer 类型的请求参数,若参数不存在,则返回默认值
+ *
+ * @param name 参数名
+ * @param defaultValue 默认值
+ * @return 参数值或默认值
+ */
+ public static Integer getParameterToInt(String name, Integer defaultValue) {
+ return Convert.toInt(getRequest().getParameter(name), defaultValue);
+ }
+
+ /**
+ * 获取指定名称的 Boolean 类型的请求参数
+ *
+ * @param name 参数名
+ * @return 参数值
+ */
+ public static Boolean getParameterToBool(String name) {
+ return Convert.toBool(getRequest().getParameter(name));
+ }
+
+ /**
+ * 获取指定名称的 Boolean 类型的请求参数,若参数不存在,则返回默认值
+ *
+ * @param name 参数名
+ * @param defaultValue 默认值
+ * @return 参数值或默认值
+ */
+ public static Boolean getParameterToBool(String name, Boolean defaultValue) {
+ return Convert.toBool(getRequest().getParameter(name), defaultValue);
+ }
+
+ /**
+ * 获取所有请求参数(以 Map 的形式返回)
+ *
+ * @param request 请求对象{@link ServletRequest}
+ * @return 请求参数的 Map,键为参数名,值为参数值数组
+ */
+ public static Map getParams(ServletRequest request) {
+ final Map map = request.getParameterMap();
+ return Collections.unmodifiableMap(map);
+ }
+
+ /**
+ * 获取所有请求参数(以 Map 的形式返回,值为字符串形式的拼接)
+ *
+ * @param request 请求对象{@link ServletRequest}
+ * @return 请求参数的 Map,键为参数名,值为拼接后的字符串
+ */
+ public static Map getParamMap(ServletRequest request) {
+ Map params = new HashMap<>();
+ for (Map.Entry entry : getParams(request).entrySet()) {
+ params.put(entry.getKey(), StringUtils.join(entry.getValue(), StringUtils.SEPARATOR));
+ }
+ return params;
+ }
+
+ /**
+ * 获取当前 HTTP 请求对象
+ *
+ * @return 当前 HTTP 请求对象
+ */
+ public static HttpServletRequest getRequest() {
+ try {
+ return getRequestAttributes().getRequest();
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * 获取当前 HTTP 响应对象
+ *
+ * @return 当前 HTTP 响应对象
+ */
+ public static HttpServletResponse getResponse() {
+ try {
+ return getRequestAttributes().getResponse();
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * 获取当前请求的 HttpSession 对象
+ *
+ * 如果当前请求已经关联了一个会话(即已经存在有效的 session ID),
+ * 则返回该会话对象;如果没有关联会话,则会创建一个新的会话对象并返回。
+ *
+ * HttpSession 用于存储会话级别的数据,如用户登录信息、购物车内容等,
+ * 可以在多个请求之间共享会话数据
+ *
+ * @return 当前请求的 HttpSession 对象
+ */
+ public static HttpSession getSession() {
+ return getRequest().getSession();
+ }
+
+ /**
+ * 获取当前请求的请求属性
+ *
+ * @return {@link ServletRequestAttributes} 请求属性对象
+ */
+ public static ServletRequestAttributes getRequestAttributes() {
+ try {
+ RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
+ return (ServletRequestAttributes) attributes;
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * 获取指定请求头的值,如果头部为空则返回空字符串
+ *
+ * @param request 请求对象
+ * @param name 头部名称
+ * @return 头部值
+ */
+ public static String getHeader(HttpServletRequest request, String name) {
+ String value = request.getHeader(name);
+ if (StringUtils.isEmpty(value)) {
+ return StringUtils.EMPTY;
+ }
+ return urlDecode(value);
+ }
+
+ /**
+ * 获取所有请求头的 Map,键为头部名称,值为头部值
+ *
+ * @param request 请求对象
+ * @return 请求头的 Map
+ */
+ public static Map getHeaders(HttpServletRequest request) {
+ Map map = new LinkedCaseInsensitiveMap<>();
+ Enumeration enumeration = request.getHeaderNames();
+ if (enumeration != null) {
+ while (enumeration.hasMoreElements()) {
+ String key = enumeration.nextElement();
+ String value = request.getHeader(key);
+ map.put(key, value);
+ }
+ }
+ return map;
+ }
+
+ /**
+ * 将字符串渲染到客户端(以 JSON 格式返回)
+ *
+ * @param response 渲染对象
+ * @param string 待渲染的字符串
+ */
+ public static void renderString(HttpServletResponse response, String string) {
+ try {
+ response.setStatus(HttpStatus.HTTP_OK);
+ response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+ response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
+ response.getWriter().print(string);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 判断当前请求是否为 Ajax 异步请求
+ *
+ * @param request 请求对象
+ * @return 是否为 Ajax 请求
+ */
+ public static boolean isAjaxRequest(HttpServletRequest request) {
+
+ // 判断 Accept 头部是否包含 application/json
+ String accept = request.getHeader("accept");
+ if (accept != null && accept.contains(MediaType.APPLICATION_JSON_VALUE)) {
+ return true;
+ }
+
+ // 判断 X-Requested-With 头部是否包含 XMLHttpRequest
+ String xRequestedWith = request.getHeader("X-Requested-With");
+ if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")) {
+ return true;
+ }
+
+ // 判断 URI 后缀是否为 .json 或 .xml
+ String uri = request.getRequestURI();
+ if (StringUtils.equalsAnyIgnoreCase(uri, ".json", ".xml")) {
+ return true;
+ }
+
+ // 判断请求参数 __ajax 是否为 json 或 xml
+ String ajax = request.getParameter("__ajax");
+ return StringUtils.equalsAnyIgnoreCase(ajax, "json", "xml");
+ }
+
+ /**
+ * 获取客户端 IP 地址
+ *
+ * @return 客户端 IP 地址
+ */
+ public static String getClientIP() {
+ return getClientIP(getRequest());
+ }
+
+ /**
+ * 对内容进行 URL 编码
+ *
+ * @param str 内容
+ * @return 编码后的内容
+ */
+ public static String urlEncode(String str) {
+ return URLEncoder.encode(str, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * 对内容进行 URL 解码
+ *
+ * @param str 内容
+ * @return 解码后的内容
+ */
+ public static String urlDecode(String str) {
+ return URLDecoder.decode(str, StandardCharsets.UTF_8);
+ }
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/SpringUtils.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/SpringUtils.java
new file mode 100644
index 0000000..ef1f73a
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/SpringUtils.java
@@ -0,0 +1,67 @@
+package com.fuyuanshen.common.core.utils;
+
+import cn.hutool.extra.spring.SpringUtil;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.boot.autoconfigure.thread.Threading;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.env.Environment;
+import org.springframework.stereotype.Component;
+
+/**
+ * spring工具类
+ *
+ * @author Lion Li
+ */
+@Component
+public final class SpringUtils extends SpringUtil {
+
+ /**
+ * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
+ */
+ public static boolean containsBean(String name) {
+ return getBeanFactory().containsBean(name);
+ }
+
+ /**
+ * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。
+ * 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
+ */
+ public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
+ return getBeanFactory().isSingleton(name);
+ }
+
+ /**
+ * @return Class 注册对象的类型
+ */
+ public static Class> getType(String name) throws NoSuchBeanDefinitionException {
+ return getBeanFactory().getType(name);
+ }
+
+ /**
+ * 如果给定的bean名字在bean定义中有别名,则返回这些别名
+ */
+ public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
+ return getBeanFactory().getAliases(name);
+ }
+
+ /**
+ * 获取aop代理对象
+ */
+ @SuppressWarnings("unchecked")
+ public static T getAopProxy(T invoker) {
+ return (T) getBean(invoker.getClass());
+ }
+
+
+ /**
+ * 获取spring上下文
+ */
+ public static ApplicationContext context() {
+ return getApplicationContext();
+ }
+
+ public static boolean isVirtual() {
+ return Threading.VIRTUAL.isActive(getBean(Environment.class));
+ }
+
+}
diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/StreamUtils.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/StreamUtils.java
new file mode 100644
index 0000000..94f5eda
--- /dev/null
+++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/StreamUtils.java
@@ -0,0 +1,282 @@
+package com.fuyuanshen.common.core.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.util.*;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+/**
+ * stream 流工具类
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class StreamUtils {
+
+ /**
+ * 将collection过滤
+ *
+ * @param collection 需要转化的集合
+ * @param function 过滤方法
+ * @return 过滤后的list
+ */
+ public static List filter(Collection collection, Predicate function) {
+ if (CollUtil.isEmpty(collection)) {
+ return CollUtil.newArrayList();
+ }
+ // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题
+ return collection.stream().filter(function).collect(Collectors.toList());
+ }
+
+ /**
+ * 找到流中满足条件的第一个元素
+ *
+ * @param collection 需要查询的集合
+ * @param function 过滤方法
+ * @return 找到符合条件的第一个元素,没有则返回null
+ */
+ public static E findFirst(Collection collection, Predicate function) {
+ if (CollUtil.isEmpty(collection)) {
+ return null;
+ }
+ return collection.stream().filter(function).findFirst().orElse(null);
+ }
+
+ /**
+ * 找到流中任意一个满足条件的元素
+ *
+ * @param collection 需要查询的集合
+ * @param function 过滤方法
+ * @return 找到符合条件的任意一个元素,没有则返回null
+ */
+ public static Optional findAny(Collection collection, Predicate function) {
+ if (CollUtil.isEmpty(collection)) {
+ return Optional.empty();
+ }
+ return collection.stream().filter(function).findAny();
+ }
+
+ /**
+ * 将collection拼接
+ *
+ * @param collection 需要转化的集合
+ * @param function 拼接方法
+ * @return 拼接后的list
+ */
+ public static String join(Collection collection, Function function) {
+ return join(collection, function, StringUtils.SEPARATOR);
+ }
+
+ /**
+ * 将collection拼接
+ *
+ * @param collection 需要转化的集合
+ * @param function 拼接方法
+ * @param delimiter 拼接符
+ * @return 拼接后的list
+ */
+ public static String join(Collection collection, Function function, CharSequence delimiter) {
+ if (CollUtil.isEmpty(collection)) {
+ return StringUtils.EMPTY;
+ }
+ return collection.stream().map(function).filter(Objects::nonNull).collect(Collectors.joining(delimiter));
+ }
+
+ /**
+ * 将collection排序
+ *
+ * @param collection 需要转化的集合
+ * @param comparing 排序方法
+ * @return 排序后的list
+ */
+ public static List sorted(Collection collection, Comparator comparing) {
+ if (CollUtil.isEmpty(collection)) {
+ return CollUtil.newArrayList();
+ }
+ // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题
+ return collection.stream().filter(Objects::nonNull).sorted(comparing).collect(Collectors.toList());
+ }
+
+ /**
+ * 将collection转化为类型不变的map
+ * {@code Collection ----> Map}
+ *
+ * @param collection 需要转化的集合
+ * @param key V类型转化为K类型的lambda方法
+ * @param collection中的泛型
+ * @param map中的key类型
+ * @return 转化后的map
+ */
+ public static Map toIdentityMap(Collection collection, Function key) {
+ if (CollUtil.isEmpty(collection)) {
+ return MapUtil.newHashMap();
+ }
+ return collection.stream().filter(Objects::nonNull).collect(Collectors.toMap(key, Function.identity(), (l, r) -> l));
+ }
+
+ /**
+ * 将Collection转化为map(value类型与collection的泛型不同)
+ * {@code Collection -----> Map }
+ *
+ * @param collection 需要转化的集合
+ * @param key E类型转化为K类型的lambda方法
+ * @param value E类型转化为V类型的lambda方法
+ * @param collection中的泛型
+ * @param map中的key类型
+ * @param map中的value类型
+ * @return 转化后的map
+ */
+ public static Map toMap(Collection collection, Function key, Function value) {
+ if (CollUtil.isEmpty(collection)) {
+ return MapUtil.newHashMap();
+ }
+ return collection.stream().filter(Objects::nonNull).collect(Collectors.toMap(key, value, (l, r) -> l));
+ }
+
+ /**
+ * 将collection按照规则(比如有相同的班级id)分类成map
+ * {@code Collection -------> Map> }
+ *
+ * @param collection 需要分类的集合
+ * @param key 分类的规则
+ * @param collection中的泛型
+ * @param map中的key类型
+ * @return 分类后的map
+ */
+ public static Map> groupByKey(Collection collection, Function key) {
+ if (CollUtil.isEmpty(collection)) {
+ return MapUtil.newHashMap();
+ }
+ return collection
+ .stream().filter(Objects::nonNull)
+ .collect(Collectors.groupingBy(key, LinkedHashMap::new, Collectors.toList()));
+ }
+
+ /**
+ * 将collection按照两个规则(比如有相同的年级id,班级id)分类成双层map
+ * {@code Collection ---> Map>> }
+ *
+ * @param collection 需要分类的集合
+ * @param key1 第一个分类的规则
+ * @param key2 第二个分类的规则
+ * @param 集合元素类型
+ * @param 第一个map中的key类型
+ * @param 第二个map中的key类型
+ * @return 分类后的map
+ */
+ public static Map>> groupBy2Key(Collection collection, Function