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/ruoyi-admin/src/test/java/com/ruoyi/test/PasswordTest.java b/ruoyi-admin/src/test/java/com/ruoyi/test/PasswordTest.java
new file mode 100644
index 0000000..def913f
--- /dev/null
+++ b/ruoyi-admin/src/test/java/com/ruoyi/test/PasswordTest.java
@@ -0,0 +1,19 @@
+package com.ruoyi.test;
+
+import cn.dev33.satoken.secure.BCrypt;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+
+public class PasswordTest {
+
+ @Test
+ public void pwd() {
+ System.out.println(BCrypt.hashpw("123456"));
+ }
+
+ @Test
+ public void abc() {
+ new File("E:\\upload\\2023\\7\\12\\75s0.jpg").delete();
+ }
+}
diff --git a/ruoyi-admin/src/test/java/com/ruoyi/test/TagUnitTest.java b/ruoyi-admin/src/test/java/com/ruoyi/test/TagUnitTest.java
new file mode 100644
index 0000000..04240a0
--- /dev/null
+++ b/ruoyi-admin/src/test/java/com/ruoyi/test/TagUnitTest.java
@@ -0,0 +1,54 @@
+package com.ruoyi.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/ruoyi-common/pom.xml b/ruoyi-common/pom.xml
new file mode 100644
index 0000000..0dad879
--- /dev/null
+++ b/ruoyi-common/pom.xml
@@ -0,0 +1,176 @@
+
+
+
+ ruoyi-vue-plus
+ com.ruoyi
+ 4.6.0
+
+ 4.0.0
+
+ ruoyi-common
+
+
+ common通用工具
+
+
+
+
+
+
+ org.springframework
+ spring-context-support
+
+
+
+
+ org.springframework
+ spring-web
+
+
+
+
+ cn.dev33
+ sa-token-spring-boot-starter
+
+
+
+ cn.dev33
+ sa-token-jwt
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+
+
+ org.apache.commons
+ commons-lang3
+
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+
+ com.alibaba
+ easyexcel
+
+
+
+
+ org.yaml
+ snakeyaml
+
+
+
+
+ javax.servlet
+ javax.servlet-api
+
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+
+
+
+
+ com.baomidou
+ dynamic-datasource-spring-boot-starter
+
+
+
+ cn.hutool
+ hutool-core
+
+
+
+ cn.hutool
+ hutool-http
+
+
+
+ cn.hutool
+ hutool-captcha
+
+
+
+ cn.hutool
+ hutool-jwt
+
+
+
+ cn.hutool
+ hutool-extra
+
+
+
+ com.sun.mail
+ jakarta.mail
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+ org.springdoc
+ springdoc-openapi-webmvc-core
+
+
+
+ org.springdoc
+ springdoc-openapi-javadoc
+
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+
+
+
+
+ org.redisson
+ redisson-spring-boot-starter
+
+
+
+ org.redisson
+ redisson-spring-data-27
+
+
+
+ com.baomidou
+ lock4j-redisson-spring-boot-starter
+
+
+
+
+ org.bouncycastle
+ bcprov-jdk15to18
+
+
+
+ com.github.binarywang
+ weixin-java-miniapp
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-mongodb
+
+
+
+
+
+
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/CellMerge.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/CellMerge.java
new file mode 100644
index 0000000..4af822e
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/CellMerge.java
@@ -0,0 +1,24 @@
+package com.ruoyi.common.annotation;
+
+import com.ruoyi.common.excel.CellMergeStrategy;
+
+import java.lang.annotation.*;
+
+/**
+ * excel 列单元格合并(合并列相同项)
+ *
+ * 需搭配 {@link CellMergeStrategy} 策略使用
+ *
+ * @author Lion Li
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface CellMerge {
+
+ /**
+ * col index
+ */
+ int index() default -1;
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataColumn.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataColumn.java
new file mode 100644
index 0000000..df416ed
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataColumn.java
@@ -0,0 +1,28 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 数据权限
+ *
+ * 一个注解只能对应一个模板
+ *
+ * @author Lion Li
+ * @version 3.5.0
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface DataColumn {
+
+ /**
+ * 占位符关键字
+ */
+ String[] key() default "deptName";
+
+ /**
+ * 占位符替换值
+ */
+ String[] value() default "dept_id";
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataPermission.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataPermission.java
new file mode 100644
index 0000000..73d9c03
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataPermission.java
@@ -0,0 +1,18 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 数据权限组
+ *
+ * @author Lion Li
+ * @version 3.5.0
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface DataPermission {
+
+ DataColumn[] value();
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Dev.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Dev.java
new file mode 100644
index 0000000..efaeebd
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Dev.java
@@ -0,0 +1,9 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.*;
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface Dev {
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DictDataMapper.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DictDataMapper.java
new file mode 100644
index 0000000..564b2a4
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DictDataMapper.java
@@ -0,0 +1,29 @@
+package com.ruoyi.common.annotation;
+
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.ruoyi.common.jackson.DictDataJsonSerializer;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 字典数据映射注解
+ *
+ * @author itino
+ * @deprecated 建议使用通用翻译注解
+ */
+@Deprecated
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD})
+@JacksonAnnotationsInside
+@JsonSerialize(using = DictDataJsonSerializer.class)
+public @interface DictDataMapper {
+
+ /**
+ * 设置字典的type值 (如: sys_user_sex)
+ */
+ String dictType() default "";
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/EncryptField.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/EncryptField.java
new file mode 100644
index 0000000..2d6a321
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/EncryptField.java
@@ -0,0 +1,44 @@
+package com.ruoyi.common.annotation;
+
+import com.ruoyi.common.enums.AlgorithmType;
+import com.ruoyi.common.enums.EncodeType;
+
+import java.lang.annotation.*;
+
+/**
+ * 字段加密注解
+ *
+ * @author 老马
+ */
+@Documented
+@Inherited
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EncryptField {
+
+ /**
+ * 加密算法
+ */
+ AlgorithmType algorithm() default AlgorithmType.DEFAULT;
+
+ /**
+ * 秘钥。AES、SM4需要
+ */
+ String password() default "";
+
+ /**
+ * 公钥。RSA、SM2需要
+ */
+ String publicKey() default "";
+
+ /**
+ * 公钥。RSA、SM2需要
+ */
+ String privateKey() default "";
+
+ /**
+ * 编码方式。对加密算法为BASE64的不起作用
+ */
+ EncodeType encode() default EncodeType.DEFAULT;
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/ExcelDictFormat.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/ExcelDictFormat.java
new file mode 100644
index 0000000..4bb0cc8
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/ExcelDictFormat.java
@@ -0,0 +1,32 @@
+package com.ruoyi.common.annotation;
+
+import com.ruoyi.common.utils.StringUtils;
+
+import java.lang.annotation.*;
+
+/**
+ * 字典格式化
+ *
+ * @author Lion Li
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface ExcelDictFormat {
+
+ /**
+ * 如果是字典类型,请设置字典的type值 (如: sys_user_sex)
+ */
+ String dictType() default "";
+
+ /**
+ * 读取内容转表达式 (如: 0=男,1=女,2=未知)
+ */
+ String readConverterExp() default "";
+
+ /**
+ * 分隔符,读取字符串组内容
+ */
+ String separator() default StringUtils.SEPARATOR;
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/ExcelEnumFormat.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/ExcelEnumFormat.java
new file mode 100644
index 0000000..a8f550a
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/ExcelEnumFormat.java
@@ -0,0 +1,30 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 枚举格式化
+ *
+ * @author Liang
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface ExcelEnumFormat {
+
+ /**
+ * 字典枚举类型
+ */
+ Class extends Enum>> enumClass();
+
+ /**
+ * 字典枚举类中对应的code属性名称,默认为code
+ */
+ String codeField() default "code";
+
+ /**
+ * 字典枚举类中对应的text属性名称,默认为text
+ */
+ String textField() default "text";
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/IgnoreResponse.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/IgnoreResponse.java
new file mode 100644
index 0000000..3213b69
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/IgnoreResponse.java
@@ -0,0 +1,24 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ *
+ * - 返回结果不需要保证成统一格式结果
+ * Author : J.L.Zhou
+ * E-Mail : 2233875735@qq.com
+ * Tel : 151 1104 7708
+ * Date : 2021-07-29 19:01
+ * Version : 1.0
+ * Copyright 2021 jlzhou.top Inc. All rights reserved.
+ * Warning: this content is only for internal circulation of the company.
+ * It is forbidden to divulge it or use it for other commercial purposes.
+ *
+ **/
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface IgnoreResponse {
+}
\ No newline at end of file
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Log.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Log.java
new file mode 100644
index 0000000..f9ba001
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Log.java
@@ -0,0 +1,47 @@
+package com.ruoyi.common.annotation;
+
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.enums.OperatorType;
+
+import java.lang.annotation.*;
+
+/**
+ * 自定义操作日志记录注解
+ *
+ * @author ruoyi
+ */
+@Target({ElementType.PARAMETER, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Log {
+ /**
+ * 模块
+ */
+ String title() default "";
+
+ /**
+ * 功能
+ */
+ BusinessType businessType() default BusinessType.OTHER;
+
+ /**
+ * 操作人类别
+ */
+ OperatorType operatorType() default OperatorType.MANAGE;
+
+ /**
+ * 是否保存请求的参数
+ */
+ boolean isSaveRequestData() default true;
+
+ /**
+ * 是否保存响应的参数
+ */
+ boolean isSaveResponseData() default true;
+
+ /**
+ * 排除指定的请求参数
+ */
+ String[] excludeParamNames() default {};
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RateLimiter.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RateLimiter.java
new file mode 100644
index 0000000..6eeb2f2
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RateLimiter.java
@@ -0,0 +1,41 @@
+package com.ruoyi.common.annotation;
+
+import com.ruoyi.common.enums.LimitType;
+
+import java.lang.annotation.*;
+
+/**
+ * 限流注解
+ *
+ * @author Lion Li
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RateLimiter {
+ /**
+ * 限流key,支持使用Spring el表达式来动态获取方法上的参数值
+ * 格式类似于 #code.id #{#code}
+ */
+ String key() default "";
+
+ /**
+ * 限流时间,单位秒
+ */
+ int time() default 60;
+
+ /**
+ * 限流次数
+ */
+ int count() default 100;
+
+ /**
+ * 限流类型
+ */
+ LimitType limitType() default LimitType.DEFAULT;
+
+ /**
+ * 提示消息 支持国际化 格式为 {code}
+ */
+ String message() default "{rate.limiter.message}";
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java
new file mode 100644
index 0000000..d30962d
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java
@@ -0,0 +1,29 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 自定义注解防止表单重复提交
+ *
+ * @author Lion Li
+ */
+@Inherited
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RepeatSubmit {
+
+ /**
+ * 间隔时间(ms),小于此时间视为重复提交
+ */
+ int interval() default 5000;
+
+ TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
+
+ /**
+ * 提示消息 支持国际化 格式为 {code}
+ */
+ String message() default "{repeat.submit.message}";
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Sensitive.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Sensitive.java
new file mode 100644
index 0000000..2ad9777
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Sensitive.java
@@ -0,0 +1,24 @@
+package com.ruoyi.common.annotation;
+
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.ruoyi.common.enums.SensitiveStrategy;
+import com.ruoyi.common.jackson.SensitiveJsonSerializer;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 数据脱敏注解
+ *
+ * @author zhujie
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+@JacksonAnnotationsInside
+@JsonSerialize(using = SensitiveJsonSerializer.class)
+public @interface Sensitive {
+ SensitiveStrategy strategy();
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Translation.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Translation.java
new file mode 100644
index 0000000..ba8cd22
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Translation.java
@@ -0,0 +1,39 @@
+package com.ruoyi.common.annotation;
+
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.ruoyi.common.translation.handler.TranslationHandler;
+
+import java.lang.annotation.*;
+
+/**
+ * 通用翻译注解
+ *
+ * @author Lion Li
+ */
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD})
+@Documented
+@JacksonAnnotationsInside
+@JsonSerialize(using = TranslationHandler.class)
+public @interface Translation {
+
+ /**
+ * 类型 (需与实现类上的 {@link com.ruoyi.common.annotation.TranslationType} 注解type对应)
+ *
+ * 默认取当前字段的值 如果设置了 @{@link Translation#mapper()} 则取映射字段的值
+ */
+ String type();
+
+ /**
+ * 映射字段 (如果不为空则取此字段的值)
+ */
+ String mapper() default "";
+
+ /**
+ * 其他条件 例如: 字典type(sys_user_sex)
+ */
+ String other() default "";
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/TranslationType.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/TranslationType.java
new file mode 100644
index 0000000..f592f6d
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/TranslationType.java
@@ -0,0 +1,21 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 翻译类型注解 (标注到{@link com.ruoyi.common.translation.TranslationInterface} 的实现类)
+ *
+ * @author Lion Li
+ */
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+@Documented
+public @interface TranslationType {
+
+ /**
+ * 类型
+ */
+ String type();
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/captcha/UnsignedMathGenerator.java b/ruoyi-common/src/main/java/com/ruoyi/common/captcha/UnsignedMathGenerator.java
new file mode 100644
index 0000000..5dd00ec
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/captcha/UnsignedMathGenerator.java
@@ -0,0 +1,85 @@
+package com.ruoyi.common.captcha;
+
+import cn.hutool.captcha.generator.CodeGenerator;
+import cn.hutool.core.math.Calculator;
+import cn.hutool.core.util.CharUtil;
+import cn.hutool.core.util.RandomUtil;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * 无符号计算生成器
+ *
+ * @author Lion Li
+ */
+public class UnsignedMathGenerator implements CodeGenerator {
+
+ private static final long serialVersionUID = -5514819971774091076L;
+
+ private static final String OPERATORS = "+-*";
+
+ /**
+ * 参与计算数字最大长度
+ */
+ private final int numberLength;
+
+ /**
+ * 构造
+ */
+ public UnsignedMathGenerator() {
+ this(2);
+ }
+
+ /**
+ * 构造
+ *
+ * @param numberLength 参与计算最大数字位数
+ */
+ public UnsignedMathGenerator(int numberLength) {
+ this.numberLength = numberLength;
+ }
+
+ @Override
+ public String generate() {
+ final int limit = getLimit();
+ int a = RandomUtil.randomInt(limit);
+ int b = RandomUtil.randomInt(limit);
+ String max = Integer.toString(Math.max(a,b));
+ String min = Integer.toString(Math.min(a,b));
+ max = StringUtils.rightPad(max, this.numberLength, CharUtil.SPACE);
+ min = StringUtils.rightPad(min, this.numberLength, CharUtil.SPACE);
+
+ return max + RandomUtil.randomChar(OPERATORS) + min + '=';
+ }
+
+ @Override
+ public boolean verify(String code, String userInputCode) {
+ int result;
+ try {
+ result = Integer.parseInt(userInputCode);
+ } catch (NumberFormatException e) {
+ // 用户输入非数字
+ return false;
+ }
+
+ final int calculateResult = (int) Calculator.conversion(code);
+ return result == calculateResult;
+ }
+
+ /**
+ * 获取验证码长度
+ *
+ * @return 验证码长度
+ */
+ public int getLength() {
+ return this.numberLength * 2 + 2;
+ }
+
+ /**
+ * 根据长度获取参与计算数字最大值
+ *
+ * @return 最大值
+ */
+ private int getLimit() {
+ return Integer.parseInt("1" + StringUtils.repeat('0', this.numberLength));
+ }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/config/RSAProperties.java b/ruoyi-common/src/main/java/com/ruoyi/common/config/RSAProperties.java
new file mode 100644
index 0000000..4e38d78
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/config/RSAProperties.java
@@ -0,0 +1,47 @@
+package com.ruoyi.common.config;
+
+
+import cn.hutool.core.codec.Base64;
+import cn.hutool.crypto.SecureUtil;
+import cn.hutool.crypto.asymmetric.RSA;
+import lombok.AccessLevel;
+import lombok.Data;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+import javax.annotation.PostConstruct;
+import java.security.KeyPair;
+
+@Configuration
+@ConfigurationProperties(prefix = "ruoyi.rsa")
+@Data
+public class RSAProperties {
+
+ /**
+ * RSA算法公钥
+ */
+ private String publicKey="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgUXdJsNiXcP7PYSQYsqqAXOHW7H3nya+9XbxDnKS3VP6erO9B4nxebmb761W+NaLImhpooCk6uwDinPVggkqKkpAXxbtepU2INW+0bqqIMHAd1hnsqlx8kRQCfCeP+ZHXas1bN0uvue5uPdHZGbaRPnWpgJsHSko5VTEoAwjITwIDAQAB";
+
+ /**
+ * RSA算法秘钥
+ */
+ private String privateKey="MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAOBRd0mw2Jdw/s9hJBiyqoBc4dbsfefJr71dvEOcpLdU/p6s70HifF5uZvvrVb41osiaGmigKTq7AOKc9WCCSoqSkBfFu16lTYg1b7RuqogwcB3WGeyqXHyRFAJ8J4/5kddqzVs3S6+57m490dkZtpE+damAmwdKSjlVMSgDCMhPAgMBAAECgYAH+an0WHw2cGVMKatFX4YjkIZF5/izjCWokEymoLzUBycTAe7k2H8UCYZ8lzRufKWVeUBAHXkw5hVGzqOu8zhDukJB7nZwD+bOhrUo1T01kYbh/kNiM97tpqEH8dEdIvxQyA6vznkqsoV8pvjCHrYDuc7YZBF1jSKudRVvfmcTTQJBAP1cjcBD1MheKfbDofscI0AVFLzd7G7hyiQkPIaS704YDC+sCnvL7HCb74b8uVDmpQ7i0PMlHD31xE8l+rbfKYsCQQDip3wMyZ6wRpAcg1x9VHQhN0PWfAJdea/9axSchpA9WZRBdQm6PQ9X+b6pism3ys1mPmb4Wtc5i7kZzq+NZgzNAkEArSms2FOEAs4Y8EcVVoMMtKez3MHw7nfzqG/7zh5u4HkfGxYCtEnT98McGq5wGhMJjPsIxKYAf3iSC2ZxzLsJsQJBAN18w8mPQUaLPgrPB5Pl8rH+2oj/mCQWZTek63hmOw/ouOsaXw4i2xqikIIHgUXcpcmzU1aBFu9CLfkdNApeXkUCQQDFBad1+kTHX5X9ZGSakSsUOcDM1Nqruoj6P8IxUswXc20C6h9nm1kiydzUVK+jR3PbDCM2JdYpz35NJSCixr1t";
+
+
+ @Setter(AccessLevel.NONE)//不要setter方法
+ private RSA rsa;
+
+ @PostConstruct
+ public void init() {
+ rsa = new RSA(privateKey, publicKey);
+ }
+
+ public static void main(String[] args) {
+ KeyPair pair = SecureUtil.generateKeyPair("RSA");
+ System.out.println(Base64.encode(pair.getPublic().getEncoded()));
+ System.out.println();
+ System.out.println(Base64.encode(pair.getPrivate().getEncoded()));
+
+ }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java b/ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java
new file mode 100644
index 0000000..bbd984c
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java
@@ -0,0 +1,112 @@
+package com.ruoyi.common.config;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.ruoyi.common.enums.UserStatus;
+import com.ruoyi.common.enums.UserType;
+import lombok.Data;
+import lombok.Getter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 读取项目相关配置
+ *
+ * @author Lion Li
+ */
+
+@Data
+@Component
+@ConfigurationProperties(prefix = "ruoyi")
+public class RuoYiConfig {
+ /**
+ * 开发模式
+ */
+ private Boolean dev=false;
+ /**
+ * 项目名称
+ */
+ private String name;
+
+ /**
+ * 版本
+ */
+ private String version;
+
+ /**
+ * 版权年份
+ */
+ private String copyrightYear;
+
+ /**
+ * 实例演示开关
+ */
+ private boolean demoEnabled;
+
+ /**
+ * 缓存懒加载
+ */
+ private boolean cacheLazy;
+
+ /**
+ * 获取地址开关
+ */
+ @Getter
+ private static boolean addressEnabled;
+
+ public void setAddressEnabled(boolean addressEnabled) {
+ RuoYiConfig.addressEnabled = addressEnabled;
+ }
+
+ private DefaultUser defaultUser = new DefaultUser();
+
+ private Tencentcloud tencentcloud = new Tencentcloud();
+
+ @Data
+ public static class DefaultUser {
+ private Long deptId = 100L;
+
+ private String userType = UserType.APP_USER.getUserType();
+ /**
+ * 帐号状态(0正常 1停用)
+ */
+ private String status = UserStatus.OK.getCode();
+ /**
+ * 角色组
+ */
+ private Long[] roleIds = {2L};
+
+ /**
+ * 岗位组
+ */
+ private Long[] postIds = {4L};
+
+ }
+
+
+ public Upload upload = new Upload();
+
+ /**
+ * 本地文件存储配置
+ */
+ @Data
+ public static class Upload {
+
+ /**
+ * 资源地址前缀
+ */
+ public String pre = "/upload";
+
+ /**
+ * 保存位置
+ */
+ public String savePath="/upload";
+ }
+
+ @Data
+ public static class Tencentcloud {
+ private String secretId = "AKIDoeWFoKdhaLuFLD1sX2LRItFMI2f7NRRh";
+ private String secretKey = "RkspdHuOflngNgnhXRL4Zpq096pLhrmQ";
+ private String ocrEndpoint = "ocr.ap-guangzhou.tencentcloudapi.com";
+ private String ocrRegion = "ap-guangzhou";
+ }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/config/WxMaConfiguration.java b/ruoyi-common/src/main/java/com/ruoyi/common/config/WxMaConfiguration.java
new file mode 100644
index 0000000..aee0e69
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/config/WxMaConfiguration.java
@@ -0,0 +1,129 @@
+package com.ruoyi.common.config;
+
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
+import cn.binarywang.wx.miniapp.bean.WxMaKefuMessage;
+import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage;
+import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
+import cn.binarywang.wx.miniapp.message.WxMaMessageHandler;
+import cn.binarywang.wx.miniapp.message.WxMaMessageRouter;
+import com.google.common.collect.Lists;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.error.WxRuntimeException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.io.File;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author Binary Wang
+ */
+@Slf4j
+@Configuration
+@EnableConfigurationProperties(WxMaProperties.class)
+public class WxMaConfiguration {
+ private final WxMaProperties properties;
+
+ @Autowired
+ public WxMaConfiguration(WxMaProperties properties) {
+ this.properties = properties;
+ }
+
+ @Bean
+ public WxMaService wxMaService() {
+ List configs = this.properties.getConfigs();
+ if (configs == null) {
+ throw new WxRuntimeException("大哥,拜托先看下项目首页的说明(readme文件),添加下相关配置,注意别配错了!");
+ }
+ WxMaService maService = new WxMaServiceImpl();
+ maService.setMultiConfigs(
+ configs.stream()
+ .map(a -> {
+ WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl();
+ config.setAppid(a.getAppid());
+ config.setSecret(a.getSecret());
+ config.setToken(a.getToken());
+ config.setAesKey(a.getAesKey());
+ config.setMsgDataFormat(a.getMsgDataFormat());
+ return config;
+ }).collect(Collectors.toMap(WxMaDefaultConfigImpl::getAppid, a -> a, (o, n) -> o)));
+ return maService;
+ }
+
+ @Bean
+ public WxMaMessageRouter wxMaMessageRouter(WxMaService wxMaService) {
+ final WxMaMessageRouter router = new WxMaMessageRouter(wxMaService);
+ router
+ .rule().handler(logHandler).next()
+ .rule().async(false).content("订阅消息").handler(subscribeMsgHandler).end()
+ .rule().async(false).content("文本").handler(textHandler).end()
+ .rule().async(false).content("图片").handler(picHandler).end()
+ .rule().async(false).content("二维码").handler(qrcodeHandler).end();
+ return router;
+ }
+
+ private final WxMaMessageHandler subscribeMsgHandler = (wxMessage, context, service, sessionManager) -> {
+ service.getMsgService().sendSubscribeMsg(WxMaSubscribeMessage.builder()
+ .templateId("此处更换为自己的模板id")
+ .data(Lists.newArrayList(
+ new WxMaSubscribeMessage.MsgData("keyword1", "339208499")))
+ .toUser(wxMessage.getFromUser())
+ .build());
+ return null;
+ };
+
+ private final WxMaMessageHandler logHandler = (wxMessage, context, service, sessionManager) -> {
+ log.info("收到消息:" + wxMessage.toString());
+ service.getMsgService().sendKefuMsg(WxMaKefuMessage.newTextBuilder().content("收到信息为:" + wxMessage.toJson())
+ .toUser(wxMessage.getFromUser()).build());
+ return null;
+ };
+
+ private final WxMaMessageHandler textHandler = (wxMessage, context, service, sessionManager) -> {
+ service.getMsgService().sendKefuMsg(WxMaKefuMessage.newTextBuilder().content("回复文本消息")
+ .toUser(wxMessage.getFromUser()).build());
+ return null;
+ };
+
+ private final WxMaMessageHandler picHandler = (wxMessage, context, service, sessionManager) -> {
+ try {
+ WxMediaUploadResult uploadResult = service.getMediaService()
+ .uploadMedia("image", "png",
+ ClassLoader.getSystemResourceAsStream("tmp.png"));
+ service.getMsgService().sendKefuMsg(
+ WxMaKefuMessage
+ .newImageBuilder()
+ .mediaId(uploadResult.getMediaId())
+ .toUser(wxMessage.getFromUser())
+ .build());
+ } catch (WxErrorException e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ };
+
+ private final WxMaMessageHandler qrcodeHandler = (wxMessage, context, service, sessionManager) -> {
+ try {
+ final File file = service.getQrcodeService().createQrcode("123", 430);
+ WxMediaUploadResult uploadResult = service.getMediaService().uploadMedia("image", file);
+ service.getMsgService().sendKefuMsg(
+ WxMaKefuMessage
+ .newImageBuilder()
+ .mediaId(uploadResult.getMediaId())
+ .toUser(wxMessage.getFromUser())
+ .build());
+ } catch (WxErrorException e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ };
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/config/WxMaProperties.java b/ruoyi-common/src/main/java/com/ruoyi/common/config/WxMaProperties.java
new file mode 100644
index 0000000..a8390c5
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/config/WxMaProperties.java
@@ -0,0 +1,45 @@
+package com.ruoyi.common.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.List;
+
+/**
+ * @author Binary Wang
+ */
+@Data
+@ConfigurationProperties(prefix = "wx.miniapp")
+public class WxMaProperties {
+
+ private List configs;
+
+ @Data
+ public static class Config {
+ /**
+ * 设置微信小程序的appid
+ */
+ private String appid;
+
+ /**
+ * 设置微信小程序的Secret
+ */
+ private String secret;
+
+ /**
+ * 设置微信小程序消息服务器配置的token
+ */
+ private String token;
+
+ /**
+ * 设置微信小程序消息服务器配置的EncodingAESKey
+ */
+ private String aesKey;
+
+ /**
+ * 消息格式,XML或者JSON
+ */
+ private String msgDataFormat;
+ }
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java
new file mode 100644
index 0000000..1cdf07e
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java
@@ -0,0 +1,49 @@
+package com.ruoyi.common.constant;
+
+/**
+ * 缓存的key 常量
+ *
+ * @author ruoyi
+ */
+public interface CacheConstants {
+
+ /**
+ * 登录用户 redis key
+ */
+ String LOGIN_TOKEN_KEY = "Authorization:login:token:";
+
+ /**
+ * 在线用户 redis key
+ */
+ String ONLINE_TOKEN_KEY = "online_tokens:";
+
+ /**
+ * 验证码 redis key
+ */
+ String CAPTCHA_CODE_KEY = "captcha_codes:";
+
+ /**
+ * 参数管理 cache key
+ */
+ String SYS_CONFIG_KEY = "sys_config:";
+
+ /**
+ * 字典管理 cache key
+ */
+ String SYS_DICT_KEY = "sys_dict:";
+
+ /**
+ * 防重提交 redis key
+ */
+ String REPEAT_SUBMIT_KEY = "repeat_submit:";
+
+ /**
+ * 限流 redis key
+ */
+ String RATE_LIMIT_KEY = "rate_limit:";
+
+ /**
+ * 登录账户密码错误次数 redis key
+ */
+ String PWD_ERR_CNT_KEY = "pwd_err_cnt:";
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheNames.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheNames.java
new file mode 100644
index 0000000..687dfd2
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheNames.java
@@ -0,0 +1,66 @@
+package com.ruoyi.common.constant;
+
+/**
+ * 缓存组名称常量
+ *
+ * key 格式为 cacheNames#ttl#maxIdleTime#maxSize
+ *
+ * ttl 过期时间 如果设置为0则不过期 默认为0
+ * maxIdleTime 最大空闲时间 根据LRU算法清理空闲数据 如果设置为0则不检测 默认为0
+ * maxSize 组最大长度 根据LRU算法清理溢出数据 如果设置为0则无限长 默认为0
+ *
+ * 例子: test#60s、test#0#60s、test#0#1m#1000、test#1h#0#500
+ *
+ * @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_USER_NAME = "sys_user_name#1d";
+
+ String SYS_USER_NICKNAME = "sys_user_nickname#10s";
+
+ String SYS_USER_TEL = "sys_user_tel#10s";
+ String SYS_USER_EMAIL = "sys_user_email#10s";
+
+
+ String SYS_USER_OPENID = "sys_user_openid#1d";
+
+ /**
+ * 部门
+ */
+ String SYS_DEPT = "sys_dept#30d";
+
+ /**
+ * OSS内容
+ */
+ String SYS_OSS = "sys_oss#30d";
+
+ /**
+ * OSS配置
+ */
+ String SYS_OSS_CONFIG = "sys_oss_config";
+
+ /**
+ * 在线用户
+ */
+ String ONLINE_TOKEN = "online_tokens";
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java
new file mode 100644
index 0000000..e634ed2
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java
@@ -0,0 +1,76 @@
+package com.ruoyi.common.constant;
+
+/**
+ * 通用常量信息
+ *
+ * @author ruoyi
+ */
+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;
+
+ /**
+ * 令牌
+ */
+ String TOKEN = "token";
+
+}
+
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java
new file mode 100644
index 0000000..bb08d50
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java
@@ -0,0 +1,193 @@
+package com.ruoyi.common.constant;
+
+/**
+ * 代码生成通用常量
+ *
+ * @author ruoyi
+ */
+public interface GenConstants {
+ /**
+ * 单表(增删改查)
+ */
+ String TPL_CRUD = "crud";
+
+ /**
+ * 树表(增删改查)
+ */
+ String TPL_TREE = "tree";
+
+ /**
+ * 主子表(增删改查)
+ */
+ String TPL_SUB = "sub";
+
+ /**
+ * 树编码字段
+ */
+ String TREE_CODE = "treeCode";
+
+ /**
+ * 树父编码字段
+ */
+ String TREE_PARENT_CODE = "treeParentCode";
+
+ /**
+ * 树名称字段
+ */
+ String TREE_NAME = "treeName";
+
+ /**
+ * 上级菜单ID字段
+ */
+ String PARENT_MENU_ID = "parentMenuId";
+
+ /**
+ * 上级菜单名称字段
+ */
+ String PARENT_MENU_NAME = "parentMenuName";
+
+ /**
+ * 数据库字符串类型
+ */
+ String[] COLUMNTYPE_STR = {"char", "varchar", "nvarchar", "varchar2"};
+
+ /**
+ * 数据库文本类型
+ */
+ String[] COLUMNTYPE_TEXT = {"tinytext", "text", "mediumtext", "longtext"};
+
+ /**
+ * 数据库时间类型
+ */
+ String[] COLUMNTYPE_TIME = {"datetime", "time", "date", "timestamp"};
+
+ /**
+ * 数据库数字类型
+ */
+ String[] COLUMNTYPE_NUMBER = {"tinyint", "smallint", "mediumint", "int", "number", "integer",
+ "bit", "bigint", "float", "double", "decimal"};
+
+ /**
+ * BO对象 不需要添加字段
+ */
+ String[] COLUMNNAME_NOT_ADD = {"create_by", "create_time", "del_flag", "update_by",
+ "update_time", "version"};
+
+ /**
+ * BO对象 不需要编辑字段
+ */
+ String[] COLUMNNAME_NOT_EDIT = {"create_by", "create_time", "del_flag", "update_by",
+ "update_time", "version"};
+
+ /**
+ * VO对象 不需要返回字段
+ */
+ String[] COLUMNNAME_NOT_LIST = {"create_by", "create_time", "del_flag", "update_by",
+ "update_time", "version"};
+
+ /**
+ * BO对象 不需要查询字段
+ */
+ String[] COLUMNNAME_NOT_QUERY = {"id", "create_by", "create_time", "del_flag", "update_by",
+ "update_time", "remark", "version"};
+
+ /**
+ * Entity基类字段
+ */
+ String[] BASE_ENTITY = {"createBy", "createTime", "updateBy", "updateTime"};
+
+ /**
+ * Tree基类字段
+ */
+ String[] TREE_ENTITY = {"parentName", "parentId", "children"};
+
+ /**
+ * 文本框
+ */
+ String HTML_INPUT = "input";
+
+ /**
+ * 文本域
+ */
+ String HTML_TEXTAREA = "textarea";
+
+ /**
+ * 下拉框
+ */
+ String HTML_SELECT = "select";
+
+ /**
+ * 单选框
+ */
+ String HTML_RADIO = "radio";
+
+ /**
+ * 复选框
+ */
+ String HTML_CHECKBOX = "checkbox";
+
+ /**
+ * 日期控件
+ */
+ String HTML_DATETIME = "datetime";
+
+ /**
+ * 图片上传控件
+ */
+ String HTML_IMAGE_UPLOAD = "imageUpload";
+
+ /**
+ * 文件上传控件
+ */
+ String HTML_FILE_UPLOAD = "fileUpload";
+
+ /**
+ * 富文本控件
+ */
+ String HTML_EDITOR = "editor";
+
+ /**
+ * 字符串类型
+ */
+ String TYPE_STRING = "String";
+
+ /**
+ * 整型
+ */
+ String TYPE_INTEGER = "Integer";
+
+ /**
+ * 长整型
+ */
+ String TYPE_LONG = "Long";
+
+ /**
+ * 浮点型
+ */
+ String TYPE_DOUBLE = "Double";
+
+ /**
+ * 高精度计算类型
+ */
+ String TYPE_BIGDECIMAL = "BigDecimal";
+
+ /**
+ * 时间类型
+ */
+ String TYPE_DATE = "Date";
+
+ /**
+ * 模糊查询
+ */
+ String QUERY_LIKE = "LIKE";
+
+ /**
+ * 相等查询
+ */
+ String QUERY_EQ = "EQ";
+
+ /**
+ * 需要
+ */
+ String REQUIRE = "1";
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/HttpStatus.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/HttpStatus.java
new file mode 100644
index 0000000..f007b8c
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/HttpStatus.java
@@ -0,0 +1,93 @@
+package com.ruoyi.common.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/ruoyi-common/src/main/java/com/ruoyi/common/constant/TransConstant.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/TransConstant.java
new file mode 100644
index 0000000..5c5e5f8
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/TransConstant.java
@@ -0,0 +1,30 @@
+package com.ruoyi.common.constant;
+
+/**
+ * 翻译常量
+ *
+ * @author Lion Li
+ */
+public interface TransConstant {
+
+ /**
+ * 用户id转账号
+ */
+ String USER_ID_TO_NAME = "user_id_to_name";
+
+ /**
+ * 部门id转名称
+ */
+ String DEPT_ID_TO_NAME = "dept_id_to_name";
+
+ /**
+ * 字典type转label
+ */
+ String DICT_TYPE_TO_LABEL = "dict_type_to_label";
+
+ /**
+ * ossId转url
+ */
+ String OSS_ID_TO_URL = "oss_id_to_url";
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java
new file mode 100644
index 0000000..4a095fa
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java
@@ -0,0 +1,132 @@
+package com.ruoyi.common.constant;
+
+/**
+ * 用户常量信息
+ *
+ * @author ruoyi
+ */
+public interface UserConstants {
+
+ /**
+ * 平台内系统用户的唯一标志
+ */
+ String SYS_USER = "SYS_USER";
+
+ /**
+ * 正常状态
+ */
+ String NORMAL = "0";
+
+ /**
+ * 异常状态
+ */
+ String EXCEPTION = "1";
+
+ /**
+ * 用户正常状态
+ */
+ String USER_NORMAL = "0";
+
+ /**
+ * 用户封禁状态
+ */
+ String USER_DISABLE = "1";
+
+ /**
+ * 角色正常状态
+ */
+ String ROLE_NORMAL = "0";
+
+ /**
+ * 角色封禁状态
+ */
+ String ROLE_DISABLE = "1";
+
+ /**
+ * 部门正常状态
+ */
+ String DEPT_NORMAL = "0";
+
+ /**
+ * 部门停用状态
+ */
+ String DEPT_DISABLE = "1";
+
+ /**
+ * 字典正常状态
+ */
+ String DICT_NORMAL = "0";
+
+ /**
+ * 是否为系统默认(是)
+ */
+ String YES = "Y";
+
+ /**
+ * 是否菜单外链(是)
+ */
+ String YES_FRAME = "0";
+
+ /**
+ * 是否菜单外链(否)
+ */
+ String NO_FRAME = "1";
+
+ /**
+ * 菜单正常状态
+ */
+ String MENU_NORMAL = "0";
+
+ /**
+ * 菜单停用状态
+ */
+ String MENU_DISABLE = "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";
+
+ /**
+ * 用户名长度限制
+ */
+ int USERNAME_MIN_LENGTH = 2;
+ int USERNAME_MAX_LENGTH = 20;
+
+ /**
+ * 密码长度限制
+ */
+ int PASSWORD_MIN_LENGTH = 5;
+ int PASSWORD_MAX_LENGTH = 20;
+
+ /**
+ * 管理员ID
+ */
+ Long ADMIN_ID = 1L;
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/convert/ExcelBigNumberConvert.java b/ruoyi-common/src/main/java/com/ruoyi/common/convert/ExcelBigNumberConvert.java
new file mode 100644
index 0000000..432ab74
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/convert/ExcelBigNumberConvert.java
@@ -0,0 +1,52 @@
+package com.ruoyi.common.convert;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.excel.converters.Converter;
+import com.alibaba.excel.enums.CellDataTypeEnum;
+import com.alibaba.excel.metadata.GlobalConfiguration;
+import com.alibaba.excel.metadata.data.ReadCellData;
+import com.alibaba.excel.metadata.data.WriteCellData;
+import com.alibaba.excel.metadata.property.ExcelContentProperty;
+import lombok.extern.slf4j.Slf4j;
+
+import java.math.BigDecimal;
+
+/**
+ * 大数值转换
+ * Excel 数值长度位15位 大于15位的数值转换位字符串
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class ExcelBigNumberConvert implements Converter {
+
+ @Override
+ public Class supportJavaTypeKey() {
+ return Long.class;
+ }
+
+ @Override
+ public CellDataTypeEnum supportExcelTypeKey() {
+ return CellDataTypeEnum.STRING;
+ }
+
+ @Override
+ public Long convertToJavaData(ReadCellData> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+ return Convert.toLong(cellData.getData());
+ }
+
+ @Override
+ public WriteCellData convertToExcelData(Long object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+ if (ObjectUtil.isNotNull(object)) {
+ String str = Convert.toStr(object);
+ if (str.length() > 15) {
+ return new WriteCellData<>(str);
+ }
+ }
+ WriteCellData cellData = new WriteCellData<>(new BigDecimal(object));
+ cellData.setType(CellDataTypeEnum.NUMBER);
+ return cellData;
+ }
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/convert/ExcelDictConvert.java b/ruoyi-common/src/main/java/com/ruoyi/common/convert/ExcelDictConvert.java
new file mode 100644
index 0000000..03dad1f
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/convert/ExcelDictConvert.java
@@ -0,0 +1,73 @@
+package com.ruoyi.common.convert;
+
+import cn.hutool.core.annotation.AnnotationUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.excel.converters.Converter;
+import com.alibaba.excel.enums.CellDataTypeEnum;
+import com.alibaba.excel.metadata.GlobalConfiguration;
+import com.alibaba.excel.metadata.data.ReadCellData;
+import com.alibaba.excel.metadata.data.WriteCellData;
+import com.alibaba.excel.metadata.property.ExcelContentProperty;
+import com.ruoyi.common.annotation.ExcelDictFormat;
+import com.ruoyi.common.core.service.DictService;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.utils.spring.SpringUtils;
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.Field;
+
+/**
+ * 字典格式化转换处理
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class ExcelDictConvert implements Converter {
+
+ @Override
+ public Class supportJavaTypeKey() {
+ return Object.class;
+ }
+
+ @Override
+ public CellDataTypeEnum supportExcelTypeKey() {
+ return null;
+ }
+
+ @Override
+ public Object convertToJavaData(ReadCellData> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+ ExcelDictFormat anno = getAnnotation(contentProperty.getField());
+ String type = anno.dictType();
+ String label = cellData.getStringValue();
+ String value;
+ if (StringUtils.isBlank(type)) {
+ value = ExcelUtil.reverseByExp(label, anno.readConverterExp(), anno.separator());
+ } else {
+ value = SpringUtils.getBean(DictService.class).getDictValue(type, label, anno.separator());
+ }
+ return Convert.convert(contentProperty.getField().getType(), value);
+ }
+
+ @Override
+ public WriteCellData convertToExcelData(Object object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+ if (ObjectUtil.isNull(object)) {
+ return new WriteCellData<>("");
+ }
+ ExcelDictFormat anno = getAnnotation(contentProperty.getField());
+ String type = anno.dictType();
+ String value = Convert.toStr(object);
+ String label;
+ if (StringUtils.isBlank(type)) {
+ label = ExcelUtil.convertByExp(value, anno.readConverterExp(), anno.separator());
+ } else {
+ label = SpringUtils.getBean(DictService.class).getDictLabel(type, value, anno.separator());
+ }
+ return new WriteCellData<>(label);
+ }
+
+ private ExcelDictFormat getAnnotation(Field field) {
+ return AnnotationUtil.getAnnotation(field, ExcelDictFormat.class);
+ }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/convert/ExcelEnumConvert.java b/ruoyi-common/src/main/java/com/ruoyi/common/convert/ExcelEnumConvert.java
new file mode 100644
index 0000000..c69d90c
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/convert/ExcelEnumConvert.java
@@ -0,0 +1,75 @@
+package com.ruoyi.common.convert;
+
+import cn.hutool.core.annotation.AnnotationUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.excel.converters.Converter;
+import com.alibaba.excel.enums.CellDataTypeEnum;
+import com.alibaba.excel.metadata.GlobalConfiguration;
+import com.alibaba.excel.metadata.data.ReadCellData;
+import com.alibaba.excel.metadata.data.WriteCellData;
+import com.alibaba.excel.metadata.property.ExcelContentProperty;
+import com.ruoyi.common.annotation.ExcelEnumFormat;
+import com.ruoyi.common.utils.reflect.ReflectUtils;
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 枚举格式化转换处理
+ *
+ * @author Liang
+ */
+@Slf4j
+public class ExcelEnumConvert implements Converter {
+
+ @Override
+ public Class supportJavaTypeKey() {
+ return Object.class;
+ }
+
+ @Override
+ public CellDataTypeEnum supportExcelTypeKey() {
+ return null;
+ }
+
+ @Override
+ public Object convertToJavaData(ReadCellData> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+ Object codeValue = cellData.getData();
+ // 如果是空值
+ if (ObjectUtil.isNull(codeValue)) {
+ return null;
+ }
+ Map enumValueMap = beforeConvert(contentProperty);
+ String textValue = enumValueMap.get(codeValue);
+ return Convert.convert(contentProperty.getField().getType(), textValue);
+ }
+
+ @Override
+ public WriteCellData convertToExcelData(Object object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+ if (ObjectUtil.isNull(object)) {
+ return new WriteCellData<>("");
+ }
+ Map enumValueMap = beforeConvert(contentProperty);
+ String value = Convert.toStr(enumValueMap.get(object), "");
+ return new WriteCellData<>(value);
+ }
+
+ private Map beforeConvert(ExcelContentProperty contentProperty) {
+ ExcelEnumFormat anno = getAnnotation(contentProperty.getField());
+ Map enumValueMap = new HashMap<>();
+ Enum>[] enumConstants = anno.enumClass().getEnumConstants();
+ for (Enum> enumConstant : enumConstants) {
+ Object codeValue = ReflectUtils.invokeGetter(enumConstant, anno.codeField());
+ String textValue = ReflectUtils.invokeGetter(enumConstant, anno.textField());
+ enumValueMap.put(codeValue, textValue);
+ }
+ return enumValueMap;
+ }
+
+ private ExcelEnumFormat getAnnotation(Field field) {
+ return AnnotationUtil.getAnnotation(field, ExcelEnumFormat.class);
+ }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java
new file mode 100644
index 0000000..1e0a5d5
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java
@@ -0,0 +1,69 @@
+package com.ruoyi.common.core.controller;
+
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.helper.LoginHelper;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * web层通用数据处理
+ *
+ * @author Lion Li
+ */
+public class BaseController {
+
+ /**
+ * 响应返回结果
+ *
+ * @param rows 影响行数
+ * @return 操作结果
+ */
+ protected R toAjax(int rows) {
+ return rows > 0 ? R.ok() : R.fail();
+ }
+
+ /**
+ * 响应返回结果
+ *
+ * @param result 结果
+ * @return 操作结果
+ */
+ protected R toAjax(boolean result) {
+ return result ? R.ok() : R.fail();
+ }
+
+ /**
+ * 页面跳转
+ */
+ public String redirect(String url) {
+ return StringUtils.format("redirect:{}", url);
+ }
+
+ /**
+ * 获取用户缓存信息
+ */
+ public LoginUser getLoginUser() {
+ return LoginHelper.getLoginUser();
+ }
+
+ /**
+ * 获取登录用户id
+ */
+ public Long getUserId() {
+ return LoginHelper.getUserId();
+ }
+
+ /**
+ * 获取登录部门id
+ */
+ public Long getDeptId() {
+ return LoginHelper.getDeptId();
+ }
+
+ /**
+ * 获取登录用户名
+ */
+ public String getUsername() {
+ return LoginHelper.getUsername();
+ }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BaseEntity.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BaseEntity.java
new file mode 100644
index 0000000..bd31607
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BaseEntity.java
@@ -0,0 +1,63 @@
+package com.ruoyi.common.core.domain;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Entity基类
+ *
+ * @author Lion Li
+ */
+
+@Data
+public class BaseEntity implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 搜索值
+ */
+ @JsonIgnore
+ @TableField(exist = false)
+ private String searchValue;
+
+ /**
+ * 创建者
+ */
+ @TableField(fill = FieldFill.INSERT)
+ private String createBy;
+
+ /**
+ * 创建时间
+ */
+ @TableField(fill = FieldFill.INSERT)
+ private Date createTime;
+
+ /**
+ * 更新者
+ */
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ private String updateBy;
+
+ /**
+ * 更新时间
+ */
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ private Date updateTime;
+
+ /**
+ * 请求参数
+ */
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ @TableField(exist = false)
+ private Map params = new HashMap<>();
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/Page.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/Page.java
new file mode 100644
index 0000000..e40e6f1
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/Page.java
@@ -0,0 +1,105 @@
+package com.ruoyi.common.core.domain;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.ListUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.metadata.OrderItem;
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 扩展分页条件orderBy
+ * @param
+ */
+public class Page extends com.baomidou.mybatisplus.extension.plugins.pagination.Page {
+
+ private final static String REGEX = "^\\w+( (asc|desc|ASC|DESC)?)?(,\\w+( (asc|desc|ASC|DESC)?)?)*$";
+
+ /**
+ * 分词后的关键字
+ */
+ @Getter
+ @Setter
+ private String keys;
+
+ public Page() {
+ }
+
+ public Page(long current, long size) {
+ super(current, size);
+ }
+
+ public Page(long current, long size, long total) {
+ super(current, size, total);
+ }
+
+ public Page(long current, long size, boolean searchCount) {
+ super(current, size, searchCount);
+ }
+
+ public Page(long current, long size, long total, boolean searchCount) {
+ super(current, size, total, searchCount);
+ }
+
+ public void setOrderBy(String orderBy) {
+ if (StrUtil.isBlank(orderBy)) {
+ return;
+ }
+ if (!orderBy.matches(REGEX)) {
+ throw new RuntimeException("排序格式不正确");
+ }
+ String[] os = orderBy.split(",");
+ super.setOrders(new ArrayList<>());
+ for (String o : os) {
+ int index = o.indexOf(" ");
+ if (index == -1) {
+ super.addOrder(OrderItem.asc(o));
+ } else if(o.endsWith(" desc") || o.endsWith(" DESC")) {
+ super.addOrder(OrderItem.desc(o.substring(0,index)));
+ }else{
+ super.addOrder(OrderItem.asc(o.substring(0,index)));
+ }
+ }
+ }
+
+ /**
+ * 转化为Spring-data模块的排序
+ * @return
+ */
+ public Sort sort(){
+ if(CollUtil.isNotEmpty(orders())){
+ List orders = ListUtil.list(true);
+ orders().forEach(o->{
+ orders.add(o.isAsc()?Sort.Order.asc(o.getColumn()):Sort.Order.desc(o.getColumn()));
+ });
+ return Sort.by(orders);
+ }
+ return Sort.unsorted();
+ }
+
+ /**
+ * 转换为Spring-data模块的分页,含排序
+ * @return
+ */
+ public Pageable pageable(){
+ return PageRequest.of((int)getCurrent()-1,(int)getSize(),sort());
+ }
+
+ private static void main(String[] args) {
+ System.out.println("asdf".matches(REGEX));
+ System.out.println("asdf'".matches(REGEX));
+ System.out.println("asdf asc".matches(REGEX));
+ System.out.println("asdf desc".matches(REGEX));
+ System.out.println("asdf abc".matches(REGEX));
+ System.out.println("asdf,abc".matches(REGEX));
+ System.out.println("asdf desc,abc".matches(REGEX));
+ System.out.println("asdf desc,abc asc".matches(REGEX));
+ System.out.println("asdf desc,abc asc".matches(REGEX));
+ }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/PageQuery.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/PageQuery.java
new file mode 100644
index 0000000..43f7671
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/PageQuery.java
@@ -0,0 +1,117 @@
+package com.ruoyi.common.core.domain;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.metadata.OrderItem;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.sql.SqlUtil;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 分页查询实体类
+ *
+ * @author Lion Li
+ */
+
+@Data
+public class PageQuery implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 分页大小
+ */
+ private Integer pageSize;
+
+ /**
+ * 当前页数
+ */
+ private Integer pageNum;
+
+ /**
+ * 排序列
+ */
+ private String orderByColumn;
+
+ /**
+ * 排序的方向desc或者asc
+ */
+ private String isAsc;
+
+ private Boolean searchCount=true;
+
+ /**
+ * 当前记录起始索引 默认值
+ */
+ public static final int DEFAULT_PAGE_NUM = 1;
+
+ /**
+ * 每页显示记录数 默认值 默认查全部
+ */
+ public static final int DEFAULT_PAGE_SIZE = Integer.MAX_VALUE;
+
+
+
+ public Page build() {
+ Integer pageNum = ObjectUtil.defaultIfNull(getPageNum(), DEFAULT_PAGE_NUM);
+ Integer pageSize = ObjectUtil.defaultIfNull(getPageSize(), DEFAULT_PAGE_SIZE);
+ if (pageNum <= 0) {
+ pageNum = DEFAULT_PAGE_NUM;
+ }
+ Page page = new Page<>(pageNum, pageSize);
+ List orderItems = buildOrderItem();
+ if (CollUtil.isNotEmpty(orderItems)) {
+ page.addOrder(orderItems);
+ }
+ page.setSearchCount(searchCount);
+ return page;
+ }
+
+ /**
+ * 构建排序
+ *
+ * 支持的用法如下:
+ * {isAsc:"asc",orderByColumn:"id"} order by id asc
+ * {isAsc:"asc",orderByColumn:"id,createTime"} order by id asc,create_time asc
+ * {isAsc:"desc",orderByColumn:"id,createTime"} order by id desc,create_time desc
+ * {isAsc:"asc,desc",orderByColumn:"id,createTime"} order by id asc,create_time desc
+ */
+ private List buildOrderItem() {
+ if (StringUtils.isBlank(orderByColumn) || StringUtils.isBlank(isAsc)) {
+ return null;
+ }
+ String orderBy = SqlUtil.escapeOrderBySql(orderByColumn);
+ orderBy = StringUtils.toUnderScoreCase(orderBy);
+
+ // 兼容前端排序类型
+ isAsc = StringUtils.replaceEach(isAsc, new String[]{"ascending", "descending"}, new String[]{"asc", "desc"});
+
+ String[] orderByArr = orderBy.split(StringUtils.SEPARATOR);
+ String[] isAscArr = isAsc.split(StringUtils.SEPARATOR);
+ if (isAscArr.length != 1 && isAscArr.length != orderByArr.length) {
+ throw new ServiceException("排序参数有误");
+ }
+
+ List list = new ArrayList<>();
+ // 每个字段各自排序
+ for (int i = 0; i < orderByArr.length; i++) {
+ String orderByStr = orderByArr[i];
+ String isAscStr = isAscArr.length == 1 ? isAscArr[0] : isAscArr[i];
+ if ("asc".equals(isAscStr)) {
+ list.add(OrderItem.asc(orderByStr));
+ } else if ("desc".equals(isAscStr)) {
+ list.add(OrderItem.desc(orderByStr));
+ } else {
+ throw new ServiceException("排序参数有误");
+ }
+ }
+ return list;
+ }
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/R.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/R.java
new file mode 100644
index 0000000..85042a0
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/R.java
@@ -0,0 +1,163 @@
+package com.ruoyi.common.core.domain;
+
+import com.ruoyi.common.constant.HttpStatus;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 响应信息主体
+ *
+ * @author Lion Li
+ */
+@Data
+@NoArgsConstructor
+public class R implements Serializable {
+ 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 R setCode(int code){
+ this.code = code;
+ return this;
+ }
+
+ public R setMsg(String msg){
+ this.msg = msg;
+ return this;
+ }
+
+ public R setData(T data) {
+ this.data = data;
+ return this;
+ }
+
+ 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();
+ }
+
+ public static R> map(){
+ R> r = new R<>();
+ r.setCode(SUCCESS);
+ r.setMsg("操作成功");
+ r.setData(new HashMap<>(10));
+ return r;
+ }
+
+ @SuppressWarnings("unchecked")
+ public R> put(String key,Object value){
+ if(this.data instanceof Map) {
+ ((Map)this.data).put(key, value);
+ return (R>) this;
+ }else {
+ throw new RuntimeException("data does not belong to Map");
+ }
+ }
+
+ public static R> list(){
+ R> r = new R<>();
+ r.setData(new ArrayList<>(10));
+ r.setCode(SUCCESS);
+ r.setMsg("操作成功");
+ return r;
+ }
+
+ @SuppressWarnings("unchecked")
+ public R> add(Object value){
+ if(this.data instanceof List) {
+ ((List)this.data).add(value);
+ return (R>) this;
+ }else {
+ throw new RuntimeException("data does not belong to List");
+ }
+ }
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeEntity.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeEntity.java
new file mode 100644
index 0000000..c7bb9b4
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeEntity.java
@@ -0,0 +1,39 @@
+package com.ruoyi.common.core.domain;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tree基类
+ *
+ * @author Lion Li
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class TreeEntity extends BaseEntity {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 父菜单名称
+ */
+ @TableField(exist = false)
+ private String parentName;
+
+ /**
+ * 父菜单ID
+ */
+ private Long parentId;
+
+ /**
+ * 子部门
+ */
+ @TableField(exist = false)
+ private List children = new ArrayList<>();
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/dto/RoleDTO.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/dto/RoleDTO.java
new file mode 100644
index 0000000..e25243f
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/dto/RoleDTO.java
@@ -0,0 +1,38 @@
+package com.ruoyi.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 角色
+ *
+ * @author Lion Li
+ */
+
+@Data
+@NoArgsConstructor
+public class RoleDTO implements Serializable {
+
+ /**
+ * 角色ID
+ */
+ private Long roleId;
+
+ /**
+ * 角色名称
+ */
+ private String roleName;
+
+ /**
+ * 角色权限
+ */
+ private String roleKey;
+
+ /**
+ * 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限)
+ */
+ private String dataScope;
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/dto/UserOnlineDTO.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/dto/UserOnlineDTO.java
new file mode 100644
index 0000000..cdd3905
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/dto/UserOnlineDTO.java
@@ -0,0 +1,60 @@
+package com.ruoyi.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 当前在线会话
+ *
+ * @author ruoyi
+ */
+
+@Data
+@NoArgsConstructor
+public class UserOnlineDTO implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 会话编号
+ */
+ private String tokenId;
+
+ /**
+ * 部门名称
+ */
+ private String deptName;
+
+ /**
+ * 用户名称
+ */
+ private String userName;
+
+ /**
+ * 登录IP地址
+ */
+ private String ipaddr;
+
+ /**
+ * 登录地址
+ */
+ private String loginLocation;
+
+ /**
+ * 浏览器类型
+ */
+ private String browser;
+
+ /**
+ * 操作系统
+ */
+ private String os;
+
+ /**
+ * 登录时间
+ */
+ private Long loginTime;
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java
new file mode 100644
index 0000000..ba4a382
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java
@@ -0,0 +1,80 @@
+package com.ruoyi.common.core.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.ruoyi.common.core.domain.TreeEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import javax.validation.constraints.Email;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+
+/**
+ * 部门表 sys_dept
+ *
+ * @author Lion Li
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("sys_dept")
+public class SysDept extends TreeEntity {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 部门ID
+ */
+ @TableId(value = "dept_id")
+ private Long deptId;
+
+ /**
+ * 部门名称
+ */
+ @NotBlank(message = "部门名称不能为空")
+ @Size(min = 0, max = 30, message = "部门名称长度不能超过{max}个字符")
+ private String deptName;
+
+ /**
+ * 显示顺序
+ */
+ @NotNull(message = "显示顺序不能为空")
+ private Integer orderNum;
+
+ /**
+ * 负责人
+ */
+ private String leader;
+
+ /**
+ * 联系电话
+ */
+ @Size(min = 0, max = 11, message = "联系电话长度不能超过{max}个字符")
+ private String phone;
+
+ /**
+ * 邮箱
+ */
+ @Email(message = "邮箱格式不正确")
+ @Size(min = 0, max = 50, message = "邮箱长度不能超过{max}个字符")
+ private String email;
+
+ /**
+ * 部门状态:0正常,1停用
+ */
+ private String status;
+
+ /**
+ * 删除标志(0代表存在 2代表删除)
+ */
+ @TableLogic
+ private String delFlag;
+
+ /**
+ * 祖级列表
+ */
+ private String ancestors;
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictData.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictData.java
new file mode 100644
index 0000000..4ed2c54
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictData.java
@@ -0,0 +1,100 @@
+package com.ruoyi.common.core.domain.entity;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.ruoyi.common.annotation.ExcelDictFormat;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.convert.ExcelDictConvert;
+import com.ruoyi.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+
+/**
+ * 字典数据表 sys_dict_data
+ *
+ * @author Lion Li
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("sys_dict_data")
+@ExcelIgnoreUnannotated
+public class SysDictData extends BaseEntity {
+
+ /**
+ * 字典编码
+ */
+ @ExcelProperty(value = "字典编码")
+ @TableId(value = "dict_code")
+ private Long dictCode;
+
+ /**
+ * 字典排序
+ */
+ @ExcelProperty(value = "字典排序")
+ private Integer dictSort;
+
+ /**
+ * 字典标签
+ */
+ @ExcelProperty(value = "字典标签")
+ @NotBlank(message = "字典标签不能为空")
+ @Size(min = 0, max = 100, message = "字典标签长度不能超过{max}个字符")
+ private String dictLabel;
+
+ /**
+ * 字典键值
+ */
+ @ExcelProperty(value = "字典键值")
+ @NotBlank(message = "字典键值不能为空")
+ @Size(min = 0, max = 100, message = "字典键值长度不能超过{max}个字符")
+ private String dictValue;
+
+ /**
+ * 字典类型
+ */
+ @ExcelProperty(value = "字典类型")
+ @NotBlank(message = "字典类型不能为空")
+ @Size(min = 0, max = 100, message = "字典类型长度不能超过{max}个字符")
+ private String dictType;
+
+ /**
+ * 样式属性(其他样式扩展)
+ */
+ @Size(min = 0, max = 100, message = "样式属性长度不能超过{max}个字符")
+ private String cssClass;
+
+ /**
+ * 表格字典样式
+ */
+ private String listClass;
+
+ /**
+ * 是否默认(Y是 N否)
+ */
+ @ExcelProperty(value = "是否默认", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(dictType = "sys_yes_no")
+ private String isDefault;
+
+ /**
+ * 状态(0正常 1停用)
+ */
+ @ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(dictType = "sys_normal_disable")
+ private String status;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+ public boolean getDefault() {
+ return UserConstants.YES.equals(this.isDefault);
+ }
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictType.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictType.java
new file mode 100644
index 0000000..76c20e1
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictType.java
@@ -0,0 +1,65 @@
+package com.ruoyi.common.core.domain.entity;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.ruoyi.common.annotation.ExcelDictFormat;
+import com.ruoyi.common.convert.ExcelDictConvert;
+import com.ruoyi.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+
+/**
+ * 字典类型表 sys_dict_type
+ *
+ * @author Lion Li
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("sys_dict_type")
+@ExcelIgnoreUnannotated
+public class SysDictType extends BaseEntity {
+
+ /**
+ * 字典主键
+ */
+ @ExcelProperty(value = "字典主键")
+ @TableId(value = "dict_id")
+ private Long dictId;
+
+ /**
+ * 字典名称
+ */
+ @ExcelProperty(value = "字典名称")
+ @NotBlank(message = "字典名称不能为空")
+ @Size(min = 0, max = 100, message = "字典类型名称长度不能超过{max}个字符")
+ private String dictName;
+
+ /**
+ * 字典类型
+ */
+ @ExcelProperty(value = "字典类型")
+ @NotBlank(message = "字典类型不能为空")
+ @Size(min = 0, max = 100, message = "字典类型类型长度不能超过{max}个字符")
+ @Pattern(regexp = "^[a-z][a-z0-9_]*$", message = "字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)")
+ private String dictType;
+
+ /**
+ * 状态(0正常 1停用)
+ */
+ @ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(dictType = "sys_normal_disable")
+ private String status;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java
new file mode 100644
index 0000000..38bd936
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java
@@ -0,0 +1,104 @@
+package com.ruoyi.common.core.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.ruoyi.common.core.domain.TreeEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+
+/**
+ * 菜单权限表 sys_menu
+ *
+ * @author Lion Li
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("sys_menu")
+public class SysMenu extends TreeEntity {
+
+ /**
+ * 菜单ID
+ */
+ @TableId(value = "menu_id")
+ private Long menuId;
+
+ /**
+ * 菜单名称
+ */
+ @NotBlank(message = "菜单名称不能为空")
+ @Size(min = 0, max = 50, message = "菜单名称长度不能超过{max}个字符")
+ private String menuName;
+
+ /**
+ * 显示顺序
+ */
+ @NotNull(message = "显示顺序不能为空")
+ private Integer orderNum;
+
+ /**
+ * 路由地址
+ */
+ @Size(min = 0, max = 200, message = "路由地址不能超过{max}个字符")
+ private String path;
+
+ /**
+ * 组件路径
+ */
+ @Size(min = 0, max = 200, message = "组件路径不能超过{max}个字符")
+ private String component;
+
+ /**
+ * 路由参数
+ */
+ private String queryParam;
+
+ /**
+ * 是否为外链(0是 1否)
+ */
+ private String isFrame;
+
+ /**
+ * 是否缓存(0缓存 1不缓存)
+ */
+ private String isCache;
+
+ /**
+ * 类型(M目录 C菜单 F按钮)
+ */
+ @NotBlank(message = "菜单类型不能为空")
+ private String menuType;
+
+ /**
+ * 显示状态(0显示 1隐藏)
+ */
+ private String visible;
+
+ /**
+ * 菜单状态(0正常 1停用)
+ */
+ private String status;
+
+ /**
+ * 权限字符串
+ */
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ @Size(min = 0, max = 100, message = "权限标识长度不能超过{max}个字符")
+ private String perms;
+
+ /**
+ * 菜单图标
+ */
+ private String icon;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysRole.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysRole.java
new file mode 100644
index 0000000..5949569
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysRole.java
@@ -0,0 +1,124 @@
+package com.ruoyi.common.core.domain.entity;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.ruoyi.common.annotation.ExcelDictFormat;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.convert.ExcelDictConvert;
+import com.ruoyi.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+
+/**
+ * 角色表 sys_role
+ *
+ * @author Lion Li
+ */
+
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@TableName("sys_role")
+@ExcelIgnoreUnannotated
+public class SysRole extends BaseEntity {
+
+ /**
+ * 角色ID
+ */
+ @ExcelProperty(value = "角色序号")
+ @TableId(value = "role_id")
+ private Long roleId;
+
+ /**
+ * 角色名称
+ */
+ @ExcelProperty(value = "角色名称")
+ @NotBlank(message = "角色名称不能为空")
+ @Size(min = 0, max = 30, message = "角色名称长度不能超过{max}个字符")
+ private String roleName;
+
+ /**
+ * 角色权限
+ */
+ @ExcelProperty(value = "角色权限")
+ @NotBlank(message = "权限字符不能为空")
+ @Size(min = 0, max = 100, message = "权限字符长度不能超过{max}个字符")
+ private String roleKey;
+
+ /**
+ * 角色排序
+ */
+ @ExcelProperty(value = "角色排序")
+ @NotNull(message = "显示顺序不能为空")
+ private Integer roleSort;
+
+ /**
+ * 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限)
+ */
+ @ExcelProperty(value = "数据范围", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(readConverterExp = "1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限,5=仅本人数据权限")
+ private String dataScope;
+
+ /**
+ * 菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示)
+ */
+ private Boolean menuCheckStrictly;
+
+ /**
+ * 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示 )
+ */
+ private Boolean deptCheckStrictly;
+
+ /**
+ * 角色状态(0正常 1停用)
+ */
+ @ExcelProperty(value = "角色状态", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(dictType = "sys_normal_disable")
+ private String status;
+
+ /**
+ * 删除标志(0代表存在 2代表删除)
+ */
+ @TableLogic
+ private String delFlag;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+ /**
+ * 用户是否存在此角色标识 默认不存在
+ */
+ @TableField(exist = false)
+ private boolean flag = false;
+
+ /**
+ * 菜单组
+ */
+ @TableField(exist = false)
+ private Long[] menuIds;
+
+ /**
+ * 部门组(数据权限)
+ */
+ @TableField(exist = false)
+ private Long[] deptIds;
+
+ public SysRole(Long roleId) {
+ this.roleId = roleId;
+ }
+
+ public boolean isAdmin() {
+ return UserConstants.ADMIN_ID.equals(this.roleId);
+ }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java
new file mode 100644
index 0000000..c2dac74
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java
@@ -0,0 +1,170 @@
+package com.ruoyi.common.core.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.ruoyi.common.annotation.Sensitive;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.domain.BaseEntity;
+import com.ruoyi.common.enums.SensitiveStrategy;
+import com.ruoyi.common.xss.Xss;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.Email;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 用户对象 sys_user
+ *
+ * @author Lion Li
+ */
+
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@TableName("sys_user")
+public class SysUser extends BaseEntity {
+
+ /**
+ * 用户ID
+ */
+ @TableId(value = "user_id")
+ private Long userId;
+
+ /**
+ * 部门ID
+ */
+ private Long deptId;
+
+
+ private String openid;
+ /**
+ * 用户账号
+ */
+ @Xss(message = "用户账号不能包含脚本字符")
+ @NotBlank(message = "用户账号不能为空")
+ @Size(min = 0, max = 20, message = "用户账号长度不能超过{max}个字符")
+ private String userName;
+
+ /**
+ * 用户昵称
+ */
+ @Xss(message = "用户昵称不能包含脚本字符")
+ @Size(min = 0, max = 20, message = "用户昵称长度不能超过{max}个字符")
+ private String nickName;
+
+ /**
+ * 用户类型(sys_user系统用户)
+ */
+ private String userType;
+
+ /**
+ * 用户邮箱
+ */
+// @Sensitive(strategy = SensitiveStrategy.EMAIL)
+ @Email(message = "邮箱格式不正确")
+ @Size(min = 0, max = 50, message = "邮箱长度不能超过{max}个字符")
+ private String email;
+
+ /**
+ * 手机号码
+ */
+// @Sensitive(strategy = SensitiveStrategy.PHONE)
+ private String phonenumber;
+
+ /**
+ * 用户性别
+ */
+ private String sex;
+
+ /**
+ * 用户头像
+ */
+ private String avatar;
+
+ /**
+ * 密码
+ */
+ @TableField(
+ insertStrategy = FieldStrategy.NOT_EMPTY,
+ updateStrategy = FieldStrategy.NOT_EMPTY,
+ whereStrategy = FieldStrategy.NOT_EMPTY
+ )
+ private String password;
+
+ @JsonIgnore
+ @JsonProperty
+ public String getPassword() {
+ return password;
+ }
+
+ /**
+ * 帐号状态(0正常 1停用)
+ */
+ private String status;
+
+ /**
+ * 删除标志(0代表存在 2代表删除)
+ */
+ @TableLogic
+ private String delFlag;
+
+ /**
+ * 最后登录IP
+ */
+ private String loginIp;
+
+ /**
+ * 最后登录时间
+ */
+ private Date loginDate;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+ /**
+ * 部门对象
+ */
+ @TableField(exist = false)
+ private SysDept dept;
+
+ /**
+ * 角色对象
+ */
+ @TableField(exist = false)
+ private List roles;
+
+ /**
+ * 角色组
+ */
+ @TableField(exist = false)
+ private Long[] roleIds;
+
+ /**
+ * 岗位组
+ */
+ @TableField(exist = false)
+ private Long[] postIds;
+
+ /**
+ * 数据权限 当前角色ID
+ */
+ @TableField(exist = false)
+ private Long roleId;
+
+ public SysUser(Long userId) {
+ this.userId = userId;
+ }
+
+ public boolean isAdmin() {
+ return UserConstants.ADMIN_ID.equals(this.userId);
+ }
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/event/LogininforEvent.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/event/LogininforEvent.java
new file mode 100644
index 0000000..733f97f
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/event/LogininforEvent.java
@@ -0,0 +1,44 @@
+package com.ruoyi.common.core.domain.event;
+
+import lombok.Data;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.Serializable;
+
+/**
+ * 登录事件
+ *
+ * @author Lion Li
+ */
+
+@Data
+public class LogininforEvent implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 用户账号
+ */
+ private String username;
+
+ /**
+ * 登录状态 0成功 1失败
+ */
+ private String status;
+
+ /**
+ * 提示消息
+ */
+ private String message;
+
+ /**
+ * 请求体
+ */
+ private HttpServletRequest request;
+
+ /**
+ * 其他参数
+ */
+ private Object[] args;
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/event/OperLogEvent.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/event/OperLogEvent.java
new file mode 100644
index 0000000..3a29978
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/event/OperLogEvent.java
@@ -0,0 +1,104 @@
+package com.ruoyi.common.core.domain.event;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 操作日志事件
+ *
+ * @author Lion Li
+ */
+
+@Data
+public class OperLogEvent implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 日志主键
+ */
+ private Long operId;
+
+ /**
+ * 操作模块
+ */
+ private String title;
+
+ /**
+ * 业务类型(0其它 1新增 2修改 3删除)
+ */
+ private Integer businessType;
+
+ /**
+ * 业务类型数组
+ */
+ private Integer[] businessTypes;
+
+ /**
+ * 请求方法
+ */
+ private String method;
+
+ /**
+ * 请求方式
+ */
+ private String requestMethod;
+
+ /**
+ * 操作类别(0其它 1后台用户 2手机端用户)
+ */
+ private Integer operatorType;
+
+ /**
+ * 操作人员
+ */
+ private String operName;
+
+ /**
+ * 部门名称
+ */
+ private String deptName;
+
+ /**
+ * 请求url
+ */
+ private String operUrl;
+
+ /**
+ * 操作地址
+ */
+ private String operIp;
+
+ /**
+ * 操作地点
+ */
+ private String operLocation;
+
+ /**
+ * 请求参数
+ */
+ private String operParam;
+
+ /**
+ * 返回参数
+ */
+ private String jsonResult;
+
+ /**
+ * 操作状态(0正常 1异常)
+ */
+ private Integer status;
+
+ /**
+ * 错误消息
+ */
+ private String errorMsg;
+
+ /**
+ * 操作时间
+ */
+ private Date operTime;
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java
new file mode 100644
index 0000000..4a4cfb5
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java
@@ -0,0 +1,42 @@
+package com.ruoyi.common.core.domain.model;
+
+import com.ruoyi.common.constant.UserConstants;
+import lombok.Data;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * 用户登录对象
+ *
+ * @author Lion Li
+ */
+
+@Data
+public class LoginBody {
+
+ /**
+ * 用户名
+ */
+ @NotBlank(message = "{user.username.not.blank}")
+ @Length(min = UserConstants.USERNAME_MIN_LENGTH, max = UserConstants.USERNAME_MAX_LENGTH, message = "{user.username.length.valid}")
+ private String username;
+
+ /**
+ * 用户密码
+ */
+ @NotBlank(message = "{user.password.not.blank}")
+ @Length(min = UserConstants.PASSWORD_MIN_LENGTH, max = UserConstants.PASSWORD_MAX_LENGTH, message = "{user.password.length.valid}")
+ private String password;
+
+ /**
+ * 验证码
+ */
+ private String code;
+
+ /**
+ * 唯一标识
+ */
+ private String uuid;
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java
new file mode 100644
index 0000000..83fcf9f
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java
@@ -0,0 +1,116 @@
+package com.ruoyi.common.core.domain.model;
+
+import com.ruoyi.common.core.domain.dto.RoleDTO;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 登录用户身份权限
+ *
+ * @author Lion Li
+ */
+
+@Data
+@NoArgsConstructor
+public class LoginUser implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 用户ID
+ */
+ private Long userId;
+
+ /**
+ * 部门ID
+ */
+ private Long deptId;
+
+ /**
+ * 部门名
+ */
+ 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 List roles;
+
+ /**
+ * 数据权限 当前角色ID
+ */
+ private Long roleId;
+
+ /**
+ * 获取登录id
+ */
+ public String getLoginId() {
+ if (userType == null) {
+ throw new IllegalArgumentException("用户类型不能为空");
+ }
+ if (userId == null) {
+ throw new IllegalArgumentException("用户ID不能为空");
+ }
+ return userType + ":" + userId;
+ }
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/RegisterBody.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/RegisterBody.java
new file mode 100644
index 0000000..88367e7
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/RegisterBody.java
@@ -0,0 +1,17 @@
+package com.ruoyi.common.core.domain.model;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 用户注册对象
+ *
+ * @author Lion Li
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class RegisterBody extends LoginBody {
+
+ private String userType;
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/SmsLoginBody.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/SmsLoginBody.java
new file mode 100644
index 0000000..ce774ac
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/SmsLoginBody.java
@@ -0,0 +1,28 @@
+package com.ruoyi.common.core.domain.model;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * 短信登录对象
+ *
+ * @author Lion Li
+ */
+
+@Data
+public class SmsLoginBody {
+
+ /**
+ * 用户名
+ */
+ @NotBlank(message = "{user.phonenumber.not.blank}")
+ private String phonenumber;
+
+ /**
+ * 用户密码
+ */
+ @NotBlank(message = "{sms.code.not.blank}")
+ private String smsCode;
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/XcxLoginUser.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/XcxLoginUser.java
new file mode 100644
index 0000000..83313a0
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/XcxLoginUser.java
@@ -0,0 +1,24 @@
+package com.ruoyi.common.core.domain.model;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * 小程序登录用户身份权限
+ *
+ * @author Lion Li
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+public class XcxLoginUser extends LoginUser {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * openid
+ */
+ private String openid;
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/query/CountQuery.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/query/CountQuery.java
new file mode 100644
index 0000000..b2b19c4
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/query/CountQuery.java
@@ -0,0 +1,33 @@
+package com.ruoyi.common.core.domain.query;
+
+import cn.hutool.core.date.DateUtil;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+/**
+ * @author jlzhou
+ */
+@Data
+public class CountQuery {
+
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm")
+ private Date endTime = new Date();
+
+ private Integer num = 5;
+
+ private Integer per;
+
+ private Long userId;
+
+ public String getTimeKey() {
+ return userId+":"+num+":"+per+":"+ DateUtil.format(endTime,"yyMMddHHmm");
+ }
+
+ public String getDateKey() {
+ return userId+":"+num+":"+per+":"+ DateUtil.format(endTime,"yyMMdd");
+ }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/vo/CountVO.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/vo/CountVO.java
new file mode 100644
index 0000000..5c7e249
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/vo/CountVO.java
@@ -0,0 +1,18 @@
+package com.ruoyi.common.core.domain.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class CountVO {
+
+ private Integer i;
+
+ private String item1;
+ private String item2;
+ private String item3;
+ private String item4;
+ private String item5;
+ private BigDecimal value;
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/mapper/BaseMapperPlus.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/mapper/BaseMapperPlus.java
new file mode 100644
index 0000000..0a8539a
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/mapper/BaseMapperPlus.java
@@ -0,0 +1,192 @@
+package com.ruoyi.common.core.mapper;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.*;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.toolkit.Db;
+import com.ruoyi.common.utils.BeanCopyUtils;
+import org.apache.ibatis.logging.Log;
+import org.apache.ibatis.logging.LogFactory;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 自定义 Mapper 接口, 实现 自定义扩展
+ *
+ * @param mapper 泛型
+ * @param table 泛型
+ * @param vo 泛型
+ * @author Lion Li
+ * @since 2021-05-13
+ */
+@SuppressWarnings("unchecked")
+public interface BaseMapperPlus extends BaseMapper {
+
+ Log log = LogFactory.getLog(BaseMapperPlus.class);
+
+ default Class currentVoClass() {
+ return (Class) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 2);
+ }
+
+ default Class currentModelClass() {
+ return (Class) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 1);
+ }
+
+ default Class currentMapperClass() {
+ return (Class) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 0);
+ }
+
+ default List selectList() {
+ return this.selectList(new QueryWrapper<>());
+ }
+
+ /**
+ * 批量插入
+ */
+ default boolean insertBatch(Collection entityList) {
+ return Db.saveBatch(entityList);
+ }
+
+ /**
+ * 批量更新
+ */
+ default boolean updateBatchById(Collection entityList) {
+ return Db.updateBatchById(entityList);
+ }
+
+ /**
+ * 批量插入或更新
+ */
+ default boolean insertOrUpdateBatch(Collection