diff --git a/.gitignore b/.gitignore
index 9845fd9ce..9acb04aef 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,6 @@
.idea/*
*.iml
*/target/*
-*/*.iml
\ No newline at end of file
+*/*.iml
+/.gradle/
+/application.pid
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index 91e3c252c..9f53f735f 100644
--- a/LICENSE
+++ b/LICENSE
@@ -176,7 +176,7 @@ recommend that a file or class name and description of purpose be included on
the same "printed page" as the copyright notice for easier identification within
third-party archives.
- Copyright 2018 Elune
+ Copyright 2019-2023 Zheng Jie
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/README.md b/README.md
index bcb758ea1..4b8224787 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-
[](https://github.com/elunez/eladmin/blob/master/LICENSE)
@@ -9,75 +9,119 @@
#### 项目简介
-eladmin基于 Spring Boot 2.1.0 、 Jpa、 Spring Security、redis、Vue的前后端分离的后台管理系统, 权限控制的方式为RBAC,项目支持数据字典与数据权限管理,支持一键生成前后端代码,支持前端菜单动态路由
+一个基于 Spring Boot 2.7.18 、 Spring Boot Jpa、 JWT、Spring Security、Redis、Vue的前后端分离的后台管理系统
-**开发文档** [https://docs.auauz.net/](https://docs.auauz.net)
+现已发布基于 mybatis-plus 版本,项目地址:[https://github.com/elunez/eladmin-mp](https://github.com/elunez/eladmin-mp)、[https://gitee.com/elunez/eladmin-mp](https://gitee.com/elunez/eladmin-mp)。
-**体验地址** [https://auauz.net/](https://auauz.net/)
+**开发文档:** [https://eladmin.vip](https://eladmin.vip)
-**账号密码** ```admin/123456```(默认密码都是123456)
+**体验地址:** [https://eladmin.vip/demo](https://eladmin.vip/demo)
+
+**账号密码:** `admin / 123456`
#### 项目源码
| | 后端源码 | 前端源码 |
|--- |--- | --- |
-| github | https://github.com/elunez/eladmin | https://github.com/elunez/eladmin-qd |
-| 码云 | https://gitee.com/elunez/eladmin | https://gitee.com/elunez/eladmin-qt |
+| github | https://github.com/elunez/eladmin | https://github.com/elunez/eladmin-web |
+| 码云 | https://gitee.com/elunez/eladmin | https://gitee.com/elunez/eladmin-web |
+
+#### VPS推荐
+
+
eladmin
me.zhengjie
- 2.1
+ 2.7
4.0.0
+
+ 5.8.35
+
eladmin-common
+ 公共模块
+
+
+
+ cn.hutool
+ hutool-all
+ ${hutool.version}
+
+
\ No newline at end of file
diff --git a/eladmin-common/src/main/java/me/zhengjie/annotation/DataPermission.java b/eladmin-common/src/main/java/me/zhengjie/annotation/DataPermission.java
new file mode 100644
index 000000000..7f9576048
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/annotation/DataPermission.java
@@ -0,0 +1,47 @@
+/*
+ * 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 me.zhengjie.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ *
+ * 用于判断是否过滤数据权限
+ * 1、如果没有用到 @OneToOne 这种关联关系,只需要填写 fieldName [参考:DeptQueryCriteria.class]
+ * 2、如果用到了 @OneToOne ,fieldName 和 joinName 都需要填写,拿UserQueryCriteria.class举例:
+ * 应该是 @DataPermission(joinName = "dept", fieldName = "id")
+ *
+ * @author Zheng Jie
+ * @website ...
+ * @date 2020-05-07
+ **/
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DataPermission {
+
+ /**
+ * Entity 中的字段名称
+ */
+ String fieldName() default "";
+
+ /**
+ * Entity 中与部门关联的字段名称
+ */
+ String joinName() default "";
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/annotation/Limit.java b/eladmin-common/src/main/java/me/zhengjie/annotation/Limit.java
index a00008fca..d73878a10 100644
--- a/eladmin-common/src/main/java/me/zhengjie/annotation/Limit.java
+++ b/eladmin-common/src/main/java/me/zhengjie/annotation/Limit.java
@@ -1,7 +1,21 @@
+/*
+ * 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 me.zhengjie.annotation;
import me.zhengjie.aspect.LimitType;
-
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
diff --git a/eladmin-common/src/main/java/me/zhengjie/annotation/Query.java b/eladmin-common/src/main/java/me/zhengjie/annotation/Query.java
index dc394b82f..18bc4c4a4 100644
--- a/eladmin-common/src/main/java/me/zhengjie/annotation/Query.java
+++ b/eladmin-common/src/main/java/me/zhengjie/annotation/Query.java
@@ -1,3 +1,18 @@
+/*
+ * 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 me.zhengjie.annotation;
import java.lang.annotation.ElementType;
@@ -13,46 +28,55 @@
@Retention(RetentionPolicy.RUNTIME)
public @interface Query {
- /** Dong ZhaoYang 2017/8/7 基本对象的属性名 */
+ // Dong ZhaoYang 2017/8/7 基本对象的属性名
String propName() default "";
- /** Dong ZhaoYang 2017/8/7 查询方式 */
+ // Dong ZhaoYang 2017/8/7 查询方式
Type type() default Type.EQUAL;
/**
* 连接查询的属性名,如User类中的dept
- * @return
*/
String joinName() default "";
/**
* 默认左连接
- * @return
*/
Join join() default Join.LEFT;
/**
* 多字段模糊搜索,仅支持String类型字段,多个用逗号隔开, 如@Query(blurry = "email,username")
- * @return
*/
String blurry() default "";
enum Type {
- /** jie 2019/6/4 相等 */
+ // jie 2019/6/4 相等
EQUAL
- /** Dong ZhaoYang 2017/8/7 大于等于 */
+ // Dong ZhaoYang 2017/8/7 大于等于
, GREATER_THAN
- /** Dong ZhaoYang 2017/8/7 小于等于 */
+ // Dong ZhaoYang 2017/8/7 小于等于
, LESS_THAN
- /** Dong ZhaoYang 2017/8/7 中模糊查询 */
+ // Dong ZhaoYang 2017/8/7 中模糊查询
, INNER_LIKE
- /** Dong ZhaoYang 2017/8/7 左模糊查询 */
+ // Dong ZhaoYang 2017/8/7 左模糊查询
, LEFT_LIKE
- /** Dong ZhaoYang 2017/8/7 右模糊查询 */
+ // Dong ZhaoYang 2017/8/7 右模糊查询
, RIGHT_LIKE
- /** Dong ZhaoYang 2017/8/7 小于 */
+ // Dong ZhaoYang 2017/8/7 小于
, LESS_THAN_NQ
- //** jie 2019/6/4 包含 */
+ // jie 2019/6/4 包含
, IN
+ // 不包含
+ , NOT_IN
+ // 不等于
+ ,NOT_EQUAL
+ // between
+ ,BETWEEN
+ // 不为空
+ ,NOT_NULL
+ // 为空
+ ,IS_NULL,
+ // Aborn Jiang 2022/06/01, 对应SQL: SELECT * FROM table WHERE FIND_IN_SET('querytag', table.tags);
+ FIND_IN_SET
}
/**
@@ -60,10 +84,8 @@ enum Type {
* 适用于简单连接查询,复杂的请自定义该注解,或者使用sql查询
*/
enum Join {
- /** jie 2019-6-4 13:18:30 左连接 */
- LEFT
- /** jie 2019-6-4 13:18:30 右连接 */
- , RIGHT
+ /** jie 2019-6-4 13:18:30 */
+ LEFT, RIGHT, INNER
}
}
diff --git a/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousAccess.java b/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousAccess.java
new file mode 100644
index 000000000..c66fcb7c1
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousAccess.java
@@ -0,0 +1,30 @@
+/*
+ * 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 me.zhengjie.annotation.rest;
+
+import java.lang.annotation.*;
+
+/**
+ * @author jacky
+ * 用于标记匿名访问方法
+ */
+@Inherited
+@Documented
+@Target({ElementType.METHOD,ElementType.ANNOTATION_TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface AnonymousAccess {
+
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousDeleteMapping.java b/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousDeleteMapping.java
new file mode 100644
index 000000000..0617e3837
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousDeleteMapping.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * 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 me.zhengjie.annotation.rest;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+/**
+ * Annotation for mapping HTTP {@code DELETE} requests onto specific handler
+ * methods.
+ * 支持匿名访问 DeleteMapping
+ *
+ * @author liaojinlong
+ * @see AnonymousGetMapping
+ * @see AnonymousPostMapping
+ * @see AnonymousPutMapping
+ * @see AnonymousPatchMapping
+ * @see RequestMapping
+ */
+@AnonymousAccess
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@RequestMapping(method = RequestMethod.DELETE)
+public @interface AnonymousDeleteMapping {
+
+ /**
+ * Alias for {@link RequestMapping#name}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String name() default "";
+
+ /**
+ * Alias for {@link RequestMapping#value}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] value() default {};
+
+ /**
+ * Alias for {@link RequestMapping#path}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] path() default {};
+
+ /**
+ * Alias for {@link RequestMapping#params}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] params() default {};
+
+ /**
+ * Alias for {@link RequestMapping#headers}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] headers() default {};
+
+ /**
+ * Alias for {@link RequestMapping#consumes}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] consumes() default {};
+
+ /**
+ * Alias for {@link RequestMapping#produces}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] produces() default {};
+
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousGetMapping.java b/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousGetMapping.java
new file mode 100644
index 000000000..55cbf63b6
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousGetMapping.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * 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 me.zhengjie.annotation.rest;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+/**
+ * Annotation for mapping HTTP {@code GET} requests onto specific handler
+ * methods.
+ *
+ * 支持匿名访问 GetMapping
+ *
+ * @author liaojinlong
+ * @see RequestMapping
+ */
+@AnonymousAccess
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@RequestMapping(method = RequestMethod.GET)
+public @interface AnonymousGetMapping {
+
+ /**
+ * Alias for {@link RequestMapping#name}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String name() default "";
+
+ /**
+ * Alias for {@link RequestMapping#value}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] value() default {};
+
+ /**
+ * Alias for {@link RequestMapping#path}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] path() default {};
+
+ /**
+ * Alias for {@link RequestMapping#params}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] params() default {};
+
+ /**
+ * Alias for {@link RequestMapping#headers}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] headers() default {};
+
+ /**
+ * Alias for {@link RequestMapping#consumes}.
+ *
+ * @since 4.3.5
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] consumes() default {};
+
+ /**
+ * Alias for {@link RequestMapping#produces}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] produces() default {};
+
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousPatchMapping.java b/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousPatchMapping.java
new file mode 100644
index 000000000..67d941c0e
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousPatchMapping.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * 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 me.zhengjie.annotation.rest;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+/**
+ * Annotation for mapping HTTP {@code PATCH} requests onto specific handler
+ * methods.
+ * * 支持匿名访问 PatchMapping
+ *
+ * @author liaojinlong
+ * @see AnonymousGetMapping
+ * @see AnonymousPostMapping
+ * @see AnonymousPutMapping
+ * @see AnonymousDeleteMapping
+ * @see RequestMapping
+ */
+@AnonymousAccess
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@RequestMapping(method = RequestMethod.PATCH)
+public @interface AnonymousPatchMapping {
+
+ /**
+ * Alias for {@link RequestMapping#name}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String name() default "";
+
+ /**
+ * Alias for {@link RequestMapping#value}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] value() default {};
+
+ /**
+ * Alias for {@link RequestMapping#path}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] path() default {};
+
+ /**
+ * Alias for {@link RequestMapping#params}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] params() default {};
+
+ /**
+ * Alias for {@link RequestMapping#headers}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] headers() default {};
+
+ /**
+ * Alias for {@link RequestMapping#consumes}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] consumes() default {};
+
+ /**
+ * Alias for {@link RequestMapping#produces}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] produces() default {};
+
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousPostMapping.java b/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousPostMapping.java
new file mode 100644
index 000000000..a6fe644db
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousPostMapping.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * 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 me.zhengjie.annotation.rest;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+/**
+ * Annotation for mapping HTTP {@code POST} requests onto specific handler
+ * methods.
+ * 支持匿名访问 PostMapping
+ *
+ * @author liaojinlong
+ * @see AnonymousGetMapping
+ * @see AnonymousPostMapping
+ * @see AnonymousPutMapping
+ * @see AnonymousDeleteMapping
+ * @see RequestMapping
+ */
+@AnonymousAccess
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@RequestMapping(method = RequestMethod.POST)
+public @interface AnonymousPostMapping {
+
+ /**
+ * Alias for {@link RequestMapping#name}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String name() default "";
+
+ /**
+ * Alias for {@link RequestMapping#value}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] value() default {};
+
+ /**
+ * Alias for {@link RequestMapping#path}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] path() default {};
+
+ /**
+ * Alias for {@link RequestMapping#params}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] params() default {};
+
+ /**
+ * Alias for {@link RequestMapping#headers}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] headers() default {};
+
+ /**
+ * Alias for {@link RequestMapping#consumes}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] consumes() default {};
+
+ /**
+ * Alias for {@link RequestMapping#produces}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] produces() default {};
+
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousPutMapping.java b/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousPutMapping.java
new file mode 100644
index 000000000..bb2d890eb
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousPutMapping.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * 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 me.zhengjie.annotation.rest;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+/**
+ * Annotation for mapping HTTP {@code PUT} requests onto specific handler
+ * methods.
+ * * 支持匿名访问 PutMapping
+ *
+ * @author liaojinlong
+ * @see AnonymousGetMapping
+ * @see AnonymousPostMapping
+ * @see AnonymousPutMapping
+ * @see AnonymousDeleteMapping
+ * @see RequestMapping
+ */
+@AnonymousAccess
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@RequestMapping(method = RequestMethod.PUT)
+public @interface AnonymousPutMapping {
+
+ /**
+ * Alias for {@link RequestMapping#name}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String name() default "";
+
+ /**
+ * Alias for {@link RequestMapping#value}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] value() default {};
+
+ /**
+ * Alias for {@link RequestMapping#path}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] path() default {};
+
+ /**
+ * Alias for {@link RequestMapping#params}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] params() default {};
+
+ /**
+ * Alias for {@link RequestMapping#headers}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] headers() default {};
+
+ /**
+ * Alias for {@link RequestMapping#consumes}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] consumes() default {};
+
+ /**
+ * Alias for {@link RequestMapping#produces}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] produces() default {};
+
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/aspect/LimitAspect.java b/eladmin-common/src/main/java/me/zhengjie/aspect/LimitAspect.java
index 555774c7d..5cc3c6dfa 100644
--- a/eladmin-common/src/main/java/me/zhengjie/aspect/LimitAspect.java
+++ b/eladmin-common/src/main/java/me/zhengjie/aspect/LimitAspect.java
@@ -1,5 +1,21 @@
+/*
+ * 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 me.zhengjie.aspect;
+import cn.hutool.core.util.ObjUtil;
import com.google.common.collect.ImmutableList;
import me.zhengjie.annotation.Limit;
import me.zhengjie.exception.BadRequestException;
@@ -12,7 +28,6 @@
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
@@ -20,13 +35,19 @@
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
+/**
+ * @author /
+ */
@Aspect
@Component
public class LimitAspect {
- @Autowired
- private RedisTemplate redisTemplate;
+
+ private final RedisTemplate redisTemplate;
private static final Logger logger = LoggerFactory.getLogger(LimitAspect.class);
+ public LimitAspect(RedisTemplate redisTemplate) {
+ this.redisTemplate = redisTemplate;
+ }
@Pointcut("@annotation(me.zhengjie.annotation.Limit)")
public void pointcut() {
@@ -41,21 +62,19 @@ public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
LimitType limitType = limit.limitType();
String key = limit.key();
if (StringUtils.isEmpty(key)) {
- switch (limitType) {
- case IP:
- key = StringUtils.getIP(request);
- break;
- default:
- key = signatureMethod.getName();
+ if (limitType == LimitType.IP) {
+ key = StringUtils.getIp(request);
+ } else {
+ key = signatureMethod.getName();
}
}
- ImmutableList keys = ImmutableList.of(StringUtils.join(limit.prefix(), "_", key, "_", request.getRequestURI().replaceAll("/","_")));
+ ImmutableList keys = ImmutableList.of(StringUtils.join(limit.prefix(), "_", key, "_", request.getRequestURI().replace("/","_")));
String luaScript = buildLuaScript();
- RedisScript redisScript = new DefaultRedisScript<>(luaScript, Number.class);
- Number count = (Number) redisTemplate.execute(redisScript, keys, limit.count(), limit.period());
- if (null != count && count.intValue() <= limit.count()) {
+ RedisScript redisScript = new DefaultRedisScript<>(luaScript, Long.class);
+ Long count = redisTemplate.execute(redisScript, keys, limit.count(), limit.period());
+ if (ObjUtil.isNotNull(count) && count.intValue() <= limit.count()) {
logger.info("第{}次访问key为 {},描述为 [{}] 的接口", count, keys, limit.name());
return joinPoint.proceed();
} else {
diff --git a/eladmin-common/src/main/java/me/zhengjie/aspect/LimitType.java b/eladmin-common/src/main/java/me/zhengjie/aspect/LimitType.java
index 6f3830473..42d041510 100644
--- a/eladmin-common/src/main/java/me/zhengjie/aspect/LimitType.java
+++ b/eladmin-common/src/main/java/me/zhengjie/aspect/LimitType.java
@@ -1,7 +1,27 @@
+/*
+ * 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 me.zhengjie.aspect;
+/**
+ * 限流枚举
+ * @author /
+ */
public enum LimitType {
+ // 默认
CUSTOMER,
-// by ip addr
- IP;
+ // by ip addr
+ IP
}
diff --git a/eladmin-common/src/main/java/me/zhengjie/base/BaseDTO.java b/eladmin-common/src/main/java/me/zhengjie/base/BaseDTO.java
new file mode 100644
index 000000000..84dad94d1
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/base/BaseDTO.java
@@ -0,0 +1,49 @@
+package me.zhengjie.base;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.sql.Timestamp;
+
+/**
+ * @author Zheng Jie
+ * @date 2019年10月24日20:48:53
+ */
+@Getter
+@Setter
+public class BaseDTO implements Serializable {
+
+ @ApiModelProperty(value = "创建人")
+ private String createBy;
+
+ @ApiModelProperty(value = "修改人")
+ private String updateBy;
+
+ @ApiModelProperty(value = "创建时间: yyyy-MM-dd HH:mm:ss", hidden = true)
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
+ private Timestamp createTime;
+
+ @ApiModelProperty(value = "更新时间: yyyy-MM-dd HH:mm:ss", hidden = true)
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
+ private Timestamp updateTime;
+
+
+ @Override
+ public String toString() {
+ ToStringBuilder builder = new ToStringBuilder(this);
+ Field[] fields = this.getClass().getDeclaredFields();
+ try {
+ for (Field f : fields) {
+ f.setAccessible(true);
+ builder.append(f.getName(), f.get(this)).append("\n");
+ }
+ } catch (Exception e) {
+ builder.append("toString builder encounter an error");
+ }
+ return builder.toString();
+ }
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/base/BaseEntity.java b/eladmin-common/src/main/java/me/zhengjie/base/BaseEntity.java
new file mode 100644
index 000000000..7f5a0d9f5
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/base/BaseEntity.java
@@ -0,0 +1,88 @@
+/*
+ * 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 me.zhengjie.base;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.hibernate.annotations.CreationTimestamp;
+import org.hibernate.annotations.UpdateTimestamp;
+import org.springframework.data.annotation.CreatedBy;
+import org.springframework.data.annotation.LastModifiedBy;
+import org.springframework.data.jpa.domain.support.AuditingEntityListener;
+import javax.persistence.Column;
+import javax.persistence.EntityListeners;
+import javax.persistence.MappedSuperclass;
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.sql.Timestamp;
+
+/**
+ * 通用字段, is_del 根据需求自行添加
+ * @author Zheng Jie
+ * @date 2019年10月24日20:46:32
+ */
+@Getter
+@Setter
+@MappedSuperclass
+@EntityListeners(AuditingEntityListener.class)
+public class BaseEntity implements Serializable {
+
+ @CreatedBy
+ @Column(name = "create_by", updatable = false)
+ @ApiModelProperty(value = "创建人", hidden = true)
+ private String createBy;
+
+ @LastModifiedBy
+ @Column(name = "update_by")
+ @ApiModelProperty(value = "更新人", hidden = true)
+ private String updateBy;
+
+ @CreationTimestamp
+ @Column(name = "create_time", updatable = false)
+ @ApiModelProperty(value = "创建时间: yyyy-MM-dd HH:mm:ss", hidden = true)
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
+ private Timestamp createTime;
+
+ @UpdateTimestamp
+ @Column(name = "update_time")
+ @ApiModelProperty(value = "更新时间: yyyy-MM-dd HH:mm:ss", hidden = true)
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
+ private Timestamp updateTime;
+
+ /* 分组校验 */
+ public @interface Create {}
+
+ /* 分组校验 */
+ public @interface Update {}
+
+ @Override
+ public String toString() {
+ ToStringBuilder builder = new ToStringBuilder(this);
+ Field[] fields = this.getClass().getDeclaredFields();
+ try {
+ for (Field f : fields) {
+ f.setAccessible(true);
+ builder.append(f.getName(), f.get(this)).append("\n");
+ }
+ } catch (Exception e) {
+ builder.append("toString builder encounter an error");
+ }
+ return builder.toString();
+ }
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/base/BaseMapper.java b/eladmin-common/src/main/java/me/zhengjie/base/BaseMapper.java
new file mode 100644
index 000000000..6f69b1382
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/base/BaseMapper.java
@@ -0,0 +1,53 @@
+/*
+ * 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 me.zhengjie.base;
+
+import java.util.List;
+
+/**
+ * @author Zheng Jie
+ * @date 2018-11-23
+ */
+public interface BaseMapper {
+
+ /**
+ * DTO转Entity
+ * @param dto /
+ * @return /
+ */
+ E toEntity(D dto);
+
+ /**
+ * Entity转DTO
+ * @param entity /
+ * @return /
+ */
+ D toDto(E entity);
+
+ /**
+ * DTO集合转Entity集合
+ * @param dtoList /
+ * @return /
+ */
+ List toEntity(List dtoList);
+
+ /**
+ * Entity集合转DTO集合
+ * @param entityList /
+ * @return /
+ */
+ List toDto(List entityList);
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/config/AsyncExecutor.java b/eladmin-common/src/main/java/me/zhengjie/config/AsyncExecutor.java
new file mode 100644
index 000000000..ee0764062
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/config/AsyncExecutor.java
@@ -0,0 +1,96 @@
+/*
+ * 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 me.zhengjie.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.AsyncConfigurer;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * 创建自定义的线程池
+ * @author Zheng Jie
+ * @description
+ * @date 2023-06-08
+ **/
+@EnableAsync
+@Configuration
+public class AsyncExecutor implements AsyncConfigurer {
+
+ public static int corePoolSize;
+
+ public static int maxPoolSize;
+
+ public static int keepAliveSeconds;
+
+ public static int queueCapacity;
+
+ @Value("${task.pool.core-pool-size}")
+ public void setCorePoolSize(int corePoolSize) {
+ AsyncExecutor.corePoolSize = corePoolSize;
+ }
+
+ @Value("${task.pool.max-pool-size}")
+ public void setMaxPoolSize(int maxPoolSize) {
+ AsyncExecutor.maxPoolSize = maxPoolSize;
+ }
+
+ @Value("${task.pool.keep-alive-seconds}")
+ public void setKeepAliveSeconds(int keepAliveSeconds) {
+ AsyncExecutor.keepAliveSeconds = keepAliveSeconds;
+ }
+
+ @Value("${task.pool.queue-capacity}")
+ public void setQueueCapacity(int queueCapacity) {
+ AsyncExecutor.queueCapacity = queueCapacity;
+ }
+
+ /**
+ * 自定义线程池,用法 @Async
+ * @return Executor
+ */
+ @Override
+ public Executor getAsyncExecutor() {
+ // 自定义工厂
+ ThreadFactory factory = r -> new Thread(r, "el-async-" + new AtomicInteger(1).getAndIncrement());
+ // 自定义线程池
+ return new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveSeconds,
+ TimeUnit.SECONDS, new ArrayBlockingQueue<>(queueCapacity), factory,
+ new ThreadPoolExecutor.CallerRunsPolicy());
+ }
+
+ /**
+ * 自定义线程池,用法,注入到类中使用
+ * private ThreadPoolTaskExecutor taskExecutor;
+ * @return ThreadPoolTaskExecutor
+ */
+ @Bean("taskAsync")
+ public ThreadPoolTaskExecutor taskAsync() {
+ ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+ executor.setCorePoolSize(2);
+ executor.setMaxPoolSize(4);
+ executor.setQueueCapacity(20);
+ executor.setKeepAliveSeconds(60);
+ executor.setThreadNamePrefix("el-task-");
+ executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+ return executor;
+ }
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/config/AuditorConfig.java b/eladmin-common/src/main/java/me/zhengjie/config/AuditorConfig.java
new file mode 100644
index 000000000..d693c58a7
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/config/AuditorConfig.java
@@ -0,0 +1,45 @@
+/*
+ * 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 me.zhengjie.config;
+
+import me.zhengjie.utils.SecurityUtils;
+import org.springframework.data.domain.AuditorAware;
+import org.springframework.stereotype.Component;
+import java.util.Optional;
+
+/**
+ * @description : 设置审计
+ * @author : Dong ZhaoYang
+ * @date : 2019/10/28
+ */
+@Component("auditorAware")
+public class AuditorConfig implements AuditorAware {
+
+ /**
+ * 返回操作员标志信息
+ *
+ * @return /
+ */
+ @Override
+ public Optional getCurrentAuditor() {
+ try {
+ // 这里应根据实际业务情况获取具体信息
+ return Optional.of(SecurityUtils.getCurrentUsername());
+ }catch (Exception ignored){}
+ // 用户定时任务,或者无Token调用的情况
+ return Optional.of("System");
+ }
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/config/AuthorityConfig.java b/eladmin-common/src/main/java/me/zhengjie/config/AuthorityConfig.java
new file mode 100644
index 000000000..b65385913
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/config/AuthorityConfig.java
@@ -0,0 +1,42 @@
+/*
+ * 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 me.zhengjie.config;
+
+import me.zhengjie.utils.SecurityUtils;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.stereotype.Service;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author Zheng Jie
+ */
+@Service(value = "el")
+public class AuthorityConfig {
+
+ /**
+ * 判断接口是否有权限
+ * @param permissions 权限
+ * @return /
+ */
+ public Boolean check(String ...permissions){
+ // 获取当前用户的所有权限
+ List elPermissions = SecurityUtils.getCurrentUser().getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
+ // 判断当前用户的所有权限是否包含接口上定义的权限
+ return elPermissions.contains("admin") || Arrays.stream(permissions).anyMatch(elPermissions::contains);
+ }
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/config/CustomP6SpyLogger.java b/eladmin-common/src/main/java/me/zhengjie/config/CustomP6SpyLogger.java
new file mode 100644
index 000000000..08ab5cca3
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/config/CustomP6SpyLogger.java
@@ -0,0 +1,62 @@
+/*
+ * 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 me.zhengjie.config;
+
+import cn.hutool.core.util.StrUtil;
+import com.p6spy.engine.spy.appender.MessageFormattingStrategy;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author Zheng Jie
+ * @description 自定义 p6spy sql输出格式
+ * @date 2024-12-26
+ **/
+@Slf4j
+public class CustomP6SpyLogger implements MessageFormattingStrategy {
+
+ // 重置颜色
+ private static final String RESET = "\u001B[0m";
+ // 红色
+ private static final String RED = "\u001B[31m";
+ // 绿色
+ private static final String GREEN = "\u001B[32m";
+
+ /**
+ * 格式化 sql
+ * @param connectionId 连接id
+ * @param now 当前时间
+ * @param elapsed 执行时长
+ * @param category sql分类
+ * @param prepared 预编译sql
+ * @param sql 执行sql
+ * @param url 数据库连接url
+ * @return 格式化后的sql
+ */
+ @Override
+ public String formatMessage(int connectionId, String now, long elapsed, String category, String prepared, String sql, String url) {
+ // 去掉换行和多余空格
+ if(StrUtil.isNotBlank(sql)){
+ sql = sql.replaceAll("\\s+", " ").trim();
+ }
+
+ // 格式化并加上颜色
+ return String.format(
+ "%s[Time: %dms]%s - %s%s%s;",
+ GREEN, elapsed, RESET, RED, sql, RESET
+ );
+ }
+}
+
diff --git a/eladmin-common/src/main/java/me/zhengjie/config/RedisConfiguration.java b/eladmin-common/src/main/java/me/zhengjie/config/RedisConfiguration.java
new file mode 100644
index 000000000..399e51bdc
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/config/RedisConfiguration.java
@@ -0,0 +1,191 @@
+/*
+ * 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 me.zhengjie.config;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONFactory;
+import com.alibaba.fastjson2.JSONWriter;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.digest.MurmurHash3;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
+import org.springframework.cache.Cache;
+import org.springframework.cache.annotation.CachingConfigurerSupport;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.cache.interceptor.CacheErrorHandler;
+import org.springframework.cache.interceptor.KeyGenerator;
+import org.springframework.cache.interceptor.SimpleCacheErrorHandler;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.RedisSerializationContext;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.data.redis.serializer.SerializationException;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Zheng Jie
+ * @date 2025-01-13
+ */
+@Slf4j
+@Configuration
+@EnableCaching
+@AutoConfigureBefore(RedisAutoConfiguration.class)
+public class RedisConfiguration extends CachingConfigurerSupport {
+
+ // 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全)
+ private static final String[] WHITELIST_STR = {"me.zhengjie" };
+
+ /**
+ * 设置 redis 数据默认过期时间,默认2小时
+ * 设置@cacheable 序列化方式
+ */
+ @Bean
+ public RedisCacheConfiguration redisCacheConfiguration(){
+ FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
+ RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
+ configuration = configuration.serializeValuesWith(RedisSerializationContext.
+ SerializationPair.fromSerializer(fastJsonRedisSerializer)).entryTtl(Duration.ofHours(2));
+ return configuration;
+ }
+
+ @Bean(name = "redisTemplate")
+ public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
+ RedisTemplate template = new RedisTemplate<>();
+ // 指定 key 和 value 的序列化方案
+ FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
+ // value值的序列化采用fastJsonRedisSerializer
+ template.setValueSerializer(fastJsonRedisSerializer);
+ template.setHashValueSerializer(fastJsonRedisSerializer);
+ // 设置fastJson的序列化白名单
+ for (String pack : WHITELIST_STR) {
+ JSONFactory.getDefaultObjectReaderProvider().addAutoTypeAccept(pack);
+ }
+ // key的序列化采用StringRedisSerializer
+ template.setKeySerializer(new StringRedisSerializer());
+ template.setHashKeySerializer(new StringRedisSerializer());
+ template.setConnectionFactory(redisConnectionFactory);
+ return template;
+ }
+
+ /**
+ * 缓存管理器
+ * @param redisConnectionFactory /
+ * @return 缓存管理器
+ */
+ @Bean
+ public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
+ RedisCacheConfiguration config = redisCacheConfiguration();
+ return RedisCacheManager.builder(redisConnectionFactory)
+ .cacheDefaults(config)
+ .build();
+ }
+
+ /**
+ * 自定义缓存key生成策略
+ */
+ @Bean
+ public KeyGenerator keyGenerator() {
+ return (target, method, params) -> {
+ Map container = new HashMap<>(8);
+ Class> targetClassClass = target.getClass();
+ // 类地址
+ container.put("class",targetClassClass.toGenericString());
+ // 方法名称
+ container.put("methodName",method.getName());
+ // 包名称
+ container.put("package",targetClassClass.getPackage());
+ // 参数列表
+ for (int i = 0; i < params.length; i++) {
+ container.put(String.valueOf(i),params[i]);
+ }
+ // 转为JSON字符串
+ String jsonString = JSON.toJSONString(container);
+ // 使用 MurmurHash 生成 hash
+ return Integer.toHexString(MurmurHash3.hash32x86(jsonString.getBytes()));
+ };
+ }
+
+ @Bean
+ @SuppressWarnings({"unchecked","all"})
+ public CacheErrorHandler errorHandler() {
+ return new SimpleCacheErrorHandler() {
+ @Override
+ public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
+ // 处理缓存读取错误
+ log.error("Cache Get Error: {}",exception.getMessage());
+ }
+ @Override
+ public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
+ // 处理缓存写入错误
+ log.error("Cache Put Error: {}",exception.getMessage());
+ }
+ @Override
+ public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
+ // 处理缓存删除错误
+ log.error("Cache Evict Error: {}",exception.getMessage());
+ }
+ @Override
+ public void handleCacheClearError(RuntimeException exception, Cache cache) {
+ // 处理缓存清除错误
+ log.error("Cache Clear Error: {}",exception.getMessage());
+ }
+ };
+ }
+
+ /**
+ * Value 序列化
+ *
+ * @param
+ * @author /
+ */
+ static class FastJsonRedisSerializer implements RedisSerializer {
+
+ private final Class clazz;
+
+ FastJsonRedisSerializer(Class clazz) {
+ super();
+ this.clazz = clazz;
+ }
+
+ @Override
+ public byte[] serialize(T t) throws SerializationException
+ {
+ if (t == null) {
+ return new byte[0];
+ }
+ return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(StandardCharsets.UTF_8);
+ }
+
+ @Override
+ public T deserialize(byte[] bytes) throws SerializationException
+ {
+ if (bytes == null || bytes.length == 0) {
+ return null;
+ }
+ String str = new String(bytes, StandardCharsets.UTF_8);
+ return JSON.parseObject(str, clazz);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/eladmin-common/src/main/java/me/zhengjie/config/RedissonConfiguration.java b/eladmin-common/src/main/java/me/zhengjie/config/RedissonConfiguration.java
new file mode 100644
index 000000000..d7e0c70e0
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/config/RedissonConfiguration.java
@@ -0,0 +1,71 @@
+/*
+ * 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 me.zhengjie.config;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.Data;
+import org.redisson.Redisson;
+import org.redisson.api.RedissonClient;
+import org.redisson.config.Config;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Data
+@Configuration
+@AutoConfigureBefore(RedisAutoConfiguration.class)
+public class RedissonConfiguration {
+
+ @Value("${spring.redis.host}")
+ private String redisHost;
+
+ @Value("${spring.redis.port}")
+ private int redisPort;
+
+ @Value("${spring.redis.database}")
+ private int redisDatabase;
+
+ @Value("${spring.redis.password:}")
+ private String redisPassword;
+
+ @Value("${spring.redis.timeout:5000}")
+ private int timeout;
+
+ @Value("${spring.redis.lettuce.pool.max-active:64}")
+ private int connectionPoolSize;
+
+ @Value("${spring.redis.lettuce.pool.min-idle:16}")
+ private int connectionMinimumIdleSize;
+
+ @Bean
+ public RedissonClient redissonClient() {
+ Config config = new Config();
+ config.useSingleServer()
+ .setAddress("redis://" + redisHost + ":" + redisPort)
+ .setDatabase(redisDatabase)
+ .setTimeout(timeout)
+ .setConnectionPoolSize(connectionPoolSize)
+ .setConnectionMinimumIdleSize(connectionMinimumIdleSize);
+ if(StrUtil.isNotBlank(redisPassword)){
+ config.useSingleServer().setPassword(redisPassword);
+ }
+ return Redisson.create(config);
+ }
+}
+
+
diff --git a/eladmin-common/src/main/java/me/zhengjie/config/RemoveDruidAdConfig.java b/eladmin-common/src/main/java/me/zhengjie/config/RemoveDruidAdConfig.java
new file mode 100644
index 000000000..8757ead1a
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/config/RemoveDruidAdConfig.java
@@ -0,0 +1,78 @@
+package me.zhengjie.config;
+
+import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
+import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
+import com.alibaba.druid.util.Utils;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @author Zheng Jie
+ * @description
+ * @date 2025-01-11
+ **/
+@Configuration
+@SuppressWarnings({"unchecked","all"})
+@ConditionalOnWebApplication
+@AutoConfigureAfter(DruidDataSourceAutoConfigure.class)
+@ConditionalOnProperty(name = "spring.datasource.druid.stat-view-servlet.enabled",
+ havingValue = "true", matchIfMissing = true)
+public class RemoveDruidAdConfig {
+
+ /**
+ * 方法名: removeDruidAdFilterRegistrationBean
+ * 方法描述 除去页面底部的广告
+ * @param properties com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties
+ * @return org.springframework.boot.web.servlet.FilterRegistrationBean
+ */
+ @Bean
+ public FilterRegistrationBean removeDruidAdFilterRegistrationBean(DruidStatProperties properties) {
+
+ // 获取web监控页面的参数
+ DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
+ // 提取common.js的配置路径
+ String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
+ String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
+
+ final String filePath = "support/http/resources/js/common.js";
+
+ //创建filter进行过滤
+ Filter filter = new Filter() {
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {}
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+ HttpServletRequest httpRequest = (HttpServletRequest) request;
+ HttpServletResponse httpResponse = (HttpServletResponse) response;
+ if (httpRequest.getRequestURI().endsWith("js/common.js")) {
+ // 获取common.js
+ String text = Utils.readFromResource(filePath);
+ // 正则替换banner, 除去底部的广告信息
+ text = text.replaceAll(" ", "");
+ text = text.replaceAll("powered by.*?shrek.wang", "");
+ httpResponse.setContentType("application/javascript");
+ httpResponse.setCharacterEncoding("UTF-8");
+ httpResponse.getWriter().write(text);
+ } else {
+ chain.doFilter(request, response);
+ }
+ }
+ @Override
+ public void destroy() {}
+ };
+ FilterRegistrationBean registrationBean = new FilterRegistrationBean();
+ registrationBean.setFilter(filter);
+ registrationBean.addUrlPatterns(commonJsPattern);
+ return registrationBean;
+ }
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/config/properties/FileProperties.java b/eladmin-common/src/main/java/me/zhengjie/config/properties/FileProperties.java
new file mode 100644
index 000000000..6b7d2b6f9
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/config/properties/FileProperties.java
@@ -0,0 +1,60 @@
+/*
+ * 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 me.zhengjie.config.properties;
+
+import lombok.Data;
+import me.zhengjie.utils.ElConstant;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Zheng Jie
+ */
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "file")
+public class FileProperties {
+
+ /** 文件大小限制 */
+ private Long maxSize;
+
+ /** 头像大小限制 */
+ private Long avatarMaxSize;
+
+ private ElPath mac;
+
+ private ElPath linux;
+
+ private ElPath windows;
+
+ public ElPath getPath(){
+ String os = System.getProperty("os.name");
+ if(os.toLowerCase().startsWith(ElConstant.WIN)) {
+ return windows;
+ } else if(os.toLowerCase().startsWith(ElConstant.MAC)){
+ return mac;
+ }
+ return linux;
+ }
+
+ @Data
+ public static class ElPath{
+
+ private String path;
+
+ private String avatar;
+ }
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/config/properties/RsaProperties.java b/eladmin-common/src/main/java/me/zhengjie/config/properties/RsaProperties.java
new file mode 100644
index 000000000..9eb327c51
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/config/properties/RsaProperties.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 me.zhengjie.config.properties;
+
+import lombok.Data;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Zheng Jie
+ * @website https://eladmin.vip
+ * @description
+ * @date 2020-05-18
+ **/
+@Data
+@Component
+public class RsaProperties {
+
+ public static String privateKey;
+
+ @Value("${rsa.private_key}")
+ public void setPrivateKey(String privateKey) {
+ RsaProperties.privateKey = privateKey;
+ }
+}
\ No newline at end of file
diff --git a/eladmin-common/src/main/java/me/zhengjie/config/webConfig/ConfigurerAdapter.java b/eladmin-common/src/main/java/me/zhengjie/config/webConfig/ConfigurerAdapter.java
new file mode 100644
index 000000000..72c54bbe4
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/config/webConfig/ConfigurerAdapter.java
@@ -0,0 +1,90 @@
+/*
+ * 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 me.zhengjie.config.webConfig;
+
+import com.alibaba.fastjson2.JSONWriter;
+import com.alibaba.fastjson2.support.config.FastJsonConfig;
+import com.alibaba.fastjson2.support.spring.http.converter.FastJsonHttpMessageConverter;
+import me.zhengjie.config.properties.FileProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.filter.CorsFilter;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * WebMvcConfigurer
+ *
+ * @author Zheng Jie
+ * @date 2018-11-30
+ */
+@Configuration
+@EnableWebMvc
+public class ConfigurerAdapter implements WebMvcConfigurer {
+
+ /** 文件配置 */
+ private final FileProperties properties;
+
+ public ConfigurerAdapter(FileProperties properties) {
+ this.properties = properties;
+ }
+
+ @Bean
+ public CorsFilter corsFilter() {
+ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+ CorsConfiguration config = new CorsConfiguration();
+ config.setAllowCredentials(true);
+ config.addAllowedOriginPattern("*");
+ config.addAllowedHeader("*");
+ config.addAllowedMethod("*");
+ source.registerCorsConfiguration("/**", config);
+ return new CorsFilter(source);
+ }
+
+ @Override
+ public void addResourceHandlers(ResourceHandlerRegistry registry) {
+ FileProperties.ElPath path = properties.getPath();
+ String avatarUtl = "file:" + path.getAvatar().replace("\\","/");
+ String pathUtl = "file:" + path.getPath().replace("\\","/");
+ registry.addResourceHandler("/avatar/**").addResourceLocations(avatarUtl).setCachePeriod(0);
+ registry.addResourceHandler("/file/**").addResourceLocations(pathUtl).setCachePeriod(0);
+ registry.addResourceHandler("/**").addResourceLocations("classpath:/META-INF/resources/").setCachePeriod(0);
+ }
+
+ @Override
+ public void configureMessageConverters(List> converters) {
+ // 配置 FastJsonHttpMessageConverter
+ FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
+ List supportMediaTypeList = new ArrayList<>();
+ supportMediaTypeList.add(MediaType.APPLICATION_JSON);
+ FastJsonConfig config = new FastJsonConfig();
+ config.setDateFormat("yyyy-MM-dd HH:mm:ss");
+ // 开启引用检测
+ config.setWriterFeatures(JSONWriter.Feature.ReferenceDetection);
+ converter.setFastJsonConfig(config);
+ converter.setSupportedMediaTypes(supportMediaTypeList);
+ converter.setDefaultCharset(StandardCharsets.UTF_8);
+ converters.add(converter);
+ }
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/config/webConfig/MultipartConfig.java b/eladmin-common/src/main/java/me/zhengjie/config/webConfig/MultipartConfig.java
new file mode 100644
index 000000000..acd59601c
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/config/webConfig/MultipartConfig.java
@@ -0,0 +1,47 @@
+/*
+ * 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 me.zhengjie.config.webConfig;
+
+import org.springframework.boot.web.servlet.MultipartConfigFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import javax.servlet.MultipartConfigElement;
+import java.io.File;
+
+/**
+ * @date 2018-12-28
+ * @author ...
+ */
+@Configuration
+public class MultipartConfig {
+
+ /**
+ * 文件上传临时路径
+ */
+ @Bean
+ MultipartConfigElement multipartConfigElement() {
+ MultipartConfigFactory factory = new MultipartConfigFactory();
+ String location = System.getProperty("user.home") + "/.eladmin/file/tmp";
+ File tmpFile = new File(location);
+ if (!tmpFile.exists()) {
+ if (!tmpFile.mkdirs()) {
+ System.out.println("create was not successful.");
+ }
+ }
+ factory.setLocation(location);
+ return factory.createMultipartConfig();
+ }
+}
\ No newline at end of file
diff --git a/eladmin-common/src/main/java/me/zhengjie/config/webConfig/QueryCustomizer.java b/eladmin-common/src/main/java/me/zhengjie/config/webConfig/QueryCustomizer.java
new file mode 100644
index 000000000..075439ff2
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/config/webConfig/QueryCustomizer.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2019-2023 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 me.zhengjie.config.webConfig;
+
+import org.apache.catalina.connector.Connector;
+import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author bearBoy80
+ */
+@Configuration(proxyBeanMethods = false)
+public class QueryCustomizer implements TomcatConnectorCustomizer {
+ @Override
+ public void customize(Connector connector) {
+ connector.setProperty("relaxedQueryChars", "[]{}");
+ }
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/config/webConfig/SwaggerConfig.java b/eladmin-common/src/main/java/me/zhengjie/config/webConfig/SwaggerConfig.java
new file mode 100644
index 000000000..2b73c897d
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/config/webConfig/SwaggerConfig.java
@@ -0,0 +1,169 @@
+/*
+ * 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 me.zhengjie.config.webConfig;
+
+import lombok.RequiredArgsConstructor;
+import me.zhengjie.utils.AnonTagUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.service.ApiKey;
+import springfox.documentation.service.AuthorizationScope;
+import springfox.documentation.service.SecurityReference;
+import springfox.documentation.service.SecurityScheme;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spi.service.contexts.SecurityContext;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider;
+import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * api页面 /doc.html
+ * @author Zheng Jie
+ * @date 2018-11-23
+ */
+@Configuration
+@EnableSwagger2
+@RequiredArgsConstructor
+public class SwaggerConfig {
+
+ @Value("${jwt.header}")
+ private String tokenHeader;
+
+ @Value("${swagger.enabled}")
+ private Boolean enabled;
+
+ @Value("${server.servlet.context-path:}")
+ private String apiPath;
+
+ private final ApplicationContext applicationContext;
+
+ @Bean
+ @SuppressWarnings({"unchecked","all"})
+ public Docket createRestApi() {
+ return new Docket(DocumentationType.SWAGGER_2)
+ .enable(enabled)
+ .pathMapping("/")
+ .apiInfo(apiInfo())
+ .select()
+ .paths(PathSelectors.regex("^(?!/error).*"))
+ .paths(PathSelectors.any())
+ .build()
+ //添加登陆认证
+ .securitySchemes(securitySchemes())
+ .securityContexts(securityContexts());
+ }
+
+ private ApiInfo apiInfo() {
+ return new ApiInfoBuilder()
+ .description("一个简单且易上手的 Spring boot 后台管理框架")
+ .title("ELADMIN 接口文档")
+ .version("2.7")
+ .build();
+ }
+
+ private List securitySchemes() {
+ //设置请求头信息
+ List securitySchemes = new ArrayList<>();
+ ApiKey apiKey = new ApiKey(tokenHeader, tokenHeader, "header");
+ securitySchemes.add(apiKey);
+ return securitySchemes;
+ }
+
+ private List securityContexts() {
+ //设置需要登录认证的路径
+ List securityContexts = new ArrayList<>();
+ securityContexts.add(getContextByPath());
+ return securityContexts;
+ }
+
+ private SecurityContext getContextByPath() {
+ Set urls = AnonTagUtils.getAllAnonymousUrl(applicationContext);
+ urls = urls.stream().filter(url -> !url.equals("/")).collect(Collectors.toSet());
+ String regExp = "^(?!" + apiPath + String.join("|" + apiPath, urls) + ").*$";
+ return SecurityContext.builder()
+ .securityReferences(defaultAuth())
+ .operationSelector(o->o.requestMappingPattern()
+ // 排除不需要认证的接口
+ .matches(regExp))
+ .build();
+ }
+
+ private List defaultAuth() {
+ List securityReferences = new ArrayList<>();
+ AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
+ AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
+ authorizationScopes[0] = authorizationScope;
+ securityReferences.add(new SecurityReference(tokenHeader, authorizationScopes));
+ return securityReferences;
+ }
+
+ /**
+ * 解决Springfox与SpringBoot集成后,WebMvcRequestHandlerProvider和WebFluxRequestHandlerProvider冲突问题
+ * @return /
+ */
+ @Bean
+ @SuppressWarnings({"all"})
+ public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
+ return new BeanPostProcessor() {
+
+ @Override
+ public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+ if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
+ customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
+ }
+ return bean;
+ }
+
+ private void customizeSpringfoxHandlerMappings(List mappings) {
+ List filteredMappings = mappings.stream()
+ .filter(mapping -> mapping.getPatternParser() == null)
+ .collect(Collectors.toList());
+ mappings.clear();
+ mappings.addAll(filteredMappings);
+ }
+
+ private List getHandlerMappings(Object bean) {
+ Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
+ if (field != null) {
+ field.setAccessible(true);
+ try {
+ return (List) field.get(bean);
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException("Failed to access handlerMappings field", e);
+ }
+ }
+ return Collections.emptyList();
+ }
+ };
+ }
+}
+
diff --git a/eladmin-common/src/main/java/me/zhengjie/config/webConfig/SwaggerDataConfig.java b/eladmin-common/src/main/java/me/zhengjie/config/webConfig/SwaggerDataConfig.java
new file mode 100644
index 000000000..2fba84077
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/config/webConfig/SwaggerDataConfig.java
@@ -0,0 +1,52 @@
+package me.zhengjie.config.webConfig;
+
+import cn.hutool.core.collection.CollUtil;
+import com.fasterxml.classmate.TypeResolver;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.Ordered;
+import org.springframework.data.domain.Pageable;
+import springfox.documentation.schema.AlternateTypeRule;
+import springfox.documentation.schema.AlternateTypeRuleConvention;
+
+import java.util.List;
+
+import static springfox.documentation.schema.AlternateTypeRules.newRule;
+
+/**
+ * 将Pageable转换展示在swagger中
+ */
+@Configuration
+public class SwaggerDataConfig {
+
+ @Bean
+ public AlternateTypeRuleConvention pageableConvention(final TypeResolver resolver) {
+ return new AlternateTypeRuleConvention() {
+ @Override
+ public int getOrder() {
+ return Ordered.HIGHEST_PRECEDENCE;
+ }
+
+ @Override
+ public List rules() {
+ return CollUtil.newArrayList(newRule(resolver.resolve(Pageable.class), resolver.resolve(Page.class)));
+ }
+ };
+ }
+
+ @ApiModel
+ @Data
+ private static class Page {
+ @ApiModelProperty("页码 (0..N)")
+ private Integer page;
+
+ @ApiModelProperty("每页显示的数目")
+ private Integer size;
+
+ @ApiModelProperty("以下列格式排序标准:property[,asc | desc]。 默认排序顺序为升序。 支持多种排序条件:如:id,asc")
+ private List sort;
+ }
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/config/webConfig/WebSocketConfig.java b/eladmin-common/src/main/java/me/zhengjie/config/webConfig/WebSocketConfig.java
new file mode 100644
index 000000000..4751bb1cd
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/config/webConfig/WebSocketConfig.java
@@ -0,0 +1,33 @@
+/*
+ * 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 me.zhengjie.config.webConfig;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.server.standard.ServerEndpointExporter;
+
+/**
+ * @author ZhangHouYing
+ * @date 2019-08-24 15:44
+ */
+@Configuration
+public class WebSocketConfig {
+
+ @Bean
+ public ServerEndpointExporter serverEndpointExporter() {
+ return new ServerEndpointExporter();
+ }
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/exception/BadRequestException.java b/eladmin-common/src/main/java/me/zhengjie/exception/BadRequestException.java
index adef657a5..1d6297b91 100644
--- a/eladmin-common/src/main/java/me/zhengjie/exception/BadRequestException.java
+++ b/eladmin-common/src/main/java/me/zhengjie/exception/BadRequestException.java
@@ -1,8 +1,22 @@
+/*
+ * 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 me.zhengjie.exception;
import lombok.Getter;
import org.springframework.http.HttpStatus;
-
import static org.springframework.http.HttpStatus.BAD_REQUEST;
/**
diff --git a/eladmin-common/src/main/java/me/zhengjie/exception/EntityExistException.java b/eladmin-common/src/main/java/me/zhengjie/exception/EntityExistException.java
index 23417a726..32d0e38f7 100644
--- a/eladmin-common/src/main/java/me/zhengjie/exception/EntityExistException.java
+++ b/eladmin-common/src/main/java/me/zhengjie/exception/EntityExistException.java
@@ -1,34 +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 me.zhengjie.exception;
import org.springframework.util.StringUtils;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.stream.IntStream;
-
/**
* @author Zheng Jie
* @date 2018-11-23
*/
public class EntityExistException extends RuntimeException {
- public EntityExistException(Class clazz, Object... saveBodyParamsMap) {
- super(EntityExistException.generateMessage(clazz.getSimpleName(), toMap(String.class, String.class, saveBodyParamsMap)));
- }
-
- private static String generateMessage(String entity, Map saveBodyParams) {
- return StringUtils.capitalize(entity) +
- " 已存在 " +
- saveBodyParams;
+ public EntityExistException(Class clazz, String field, String val) {
+ super(EntityExistException.generateMessage(clazz.getSimpleName(), field, val));
}
- private static Map toMap(
- Class keyType, Class valueType, Object... entries) {
- if (entries.length % 2 == 1)
- throw new IllegalArgumentException("Invalid entries");
- return IntStream.range(0, entries.length / 2).map(i -> i * 2)
- .collect(HashMap::new,
- (m, i) -> m.put(keyType.cast(entries[i]), valueType.cast(entries[i + 1])),
- Map::putAll);
+ private static String generateMessage(String entity, String field, String val) {
+ return StringUtils.capitalize(entity)
+ + " with " + field + " "+ val + " existed";
}
}
\ No newline at end of file
diff --git a/eladmin-common/src/main/java/me/zhengjie/exception/EntityNotFoundException.java b/eladmin-common/src/main/java/me/zhengjie/exception/EntityNotFoundException.java
index 31e1cbdfb..4d9f4d964 100644
--- a/eladmin-common/src/main/java/me/zhengjie/exception/EntityNotFoundException.java
+++ b/eladmin-common/src/main/java/me/zhengjie/exception/EntityNotFoundException.java
@@ -1,35 +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 me.zhengjie.exception;
import org.springframework.util.StringUtils;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.stream.IntStream;
-
/**
* @author Zheng Jie
* @date 2018-11-23
*/
public class EntityNotFoundException extends RuntimeException {
- public EntityNotFoundException(Class clazz, Object... searchParamsMap) {
- super(EntityNotFoundException.generateMessage(clazz.getSimpleName(), toMap(String.class, String.class, searchParamsMap)));
+ public EntityNotFoundException(Class clazz, String field, String val) {
+ super(EntityNotFoundException.generateMessage(clazz.getSimpleName(), field, val));
}
- private static String generateMessage(String entity, Map searchParams) {
- return StringUtils.capitalize(entity) +
- " 不存在 " +
- searchParams;
+ private static String generateMessage(String entity, String field, String val) {
+ return StringUtils.capitalize(entity)
+ + " with " + field + " "+ val + " does not exist";
}
-
- private static Map toMap(
- Class keyType, Class valueType, Object... entries) {
- if (entries.length % 2 == 1)
- throw new IllegalArgumentException("Invalid entries");
- return IntStream.range(0, entries.length / 2).map(i -> i * 2)
- .collect(HashMap::new,
- (m, i) -> m.put(keyType.cast(entries[i]), valueType.cast(entries[i + 1])),
- Map::putAll);
- }
-
}
\ No newline at end of file
diff --git a/eladmin-common/src/main/java/me/zhengjie/exception/handler/ApiError.java b/eladmin-common/src/main/java/me/zhengjie/exception/handler/ApiError.java
index db43891e7..aa377c4f3 100644
--- a/eladmin-common/src/main/java/me/zhengjie/exception/handler/ApiError.java
+++ b/eladmin-common/src/main/java/me/zhengjie/exception/handler/ApiError.java
@@ -1,30 +1,48 @@
+/*
+ * 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 me.zhengjie.exception.handler;
-import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
-import java.time.LocalDateTime;
-
/**
* @author Zheng Jie
* @date 2018-11-23
*/
@Data
-class ApiError {
+public class ApiError {
- private Integer status;
- @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
- private LocalDateTime timestamp;
+ private Integer status = 400;
+ private Long timestamp;
private String message;
private ApiError() {
- timestamp = LocalDateTime.now();
+ timestamp = System.currentTimeMillis();
+ }
+
+ public static ApiError error(String message){
+ ApiError apiError = new ApiError();
+ apiError.setMessage(message);
+ return apiError;
}
- public ApiError(Integer status,String message) {
- this();
- this.status = status;
- this.message = message;
+ public static ApiError error(Integer status, String message){
+ ApiError apiError = new ApiError();
+ apiError.setStatus(status);
+ apiError.setMessage(message);
+ return apiError;
}
}
diff --git a/eladmin-common/src/main/java/me/zhengjie/exception/handler/GlobalExceptionHandler.java b/eladmin-common/src/main/java/me/zhengjie/exception/handler/GlobalExceptionHandler.java
index 5e58c7844..4e4e7165a 100644
--- a/eladmin-common/src/main/java/me/zhengjie/exception/handler/GlobalExceptionHandler.java
+++ b/eladmin-common/src/main/java/me/zhengjie/exception/handler/GlobalExceptionHandler.java
@@ -1,3 +1,18 @@
+/*
+ * 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 me.zhengjie.exception.handler;
import lombok.extern.slf4j.Slf4j;
@@ -7,7 +22,9 @@
import me.zhengjie.utils.ThrowableUtil;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
-import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.validation.FieldError;
+import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@@ -23,91 +40,74 @@ public class GlobalExceptionHandler {
/**
* 处理所有不可知的异常
- * @param e
- * @return
*/
@ExceptionHandler(Throwable.class)
- public ResponseEntity handleException(Throwable e){
+ public ResponseEntity handleException(Throwable e){
// 打印堆栈信息
log.error(ThrowableUtil.getStackTrace(e));
- ApiError apiError = new ApiError(BAD_REQUEST.value(),e.getMessage());
- return buildResponseEntity(apiError);
+ return buildResponseEntity(ApiError.error(e.getMessage()));
}
/**
- * 处理 接口无权访问异常AccessDeniedException
- * @param e
- * @return
+ * BadCredentialsException
*/
- @ExceptionHandler(AccessDeniedException.class)
- public ResponseEntity handleAccessDeniedException(AccessDeniedException e){
+ @ExceptionHandler(BadCredentialsException.class)
+ public ResponseEntity badCredentialsException(BadCredentialsException e){
// 打印堆栈信息
- log.error(ThrowableUtil.getStackTrace(e));
- ApiError apiError = new ApiError(FORBIDDEN.value(),e.getMessage());
- return buildResponseEntity(apiError);
+ String message = "坏的凭证".equals(e.getMessage()) ? "用户名或密码不正确" : e.getMessage();
+ log.error(message);
+ return buildResponseEntity(ApiError.error(message));
}
/**
* 处理自定义异常
- * @param e
- * @return
*/
@ExceptionHandler(value = BadRequestException.class)
public ResponseEntity badRequestException(BadRequestException e) {
// 打印堆栈信息
log.error(ThrowableUtil.getStackTrace(e));
- ApiError apiError = new ApiError(e.getStatus(),e.getMessage());
- return buildResponseEntity(apiError);
+ return buildResponseEntity(ApiError.error(e.getStatus(),e.getMessage()));
}
/**
* 处理 EntityExist
- * @param e
- * @return
*/
@ExceptionHandler(value = EntityExistException.class)
public ResponseEntity entityExistException(EntityExistException e) {
// 打印堆栈信息
log.error(ThrowableUtil.getStackTrace(e));
- ApiError apiError = new ApiError(BAD_REQUEST.value(),e.getMessage());
- return buildResponseEntity(apiError);
+ return buildResponseEntity(ApiError.error(e.getMessage()));
}
/**
* 处理 EntityNotFound
- * @param e
- * @return
*/
@ExceptionHandler(value = EntityNotFoundException.class)
public ResponseEntity entityNotFoundException(EntityNotFoundException e) {
// 打印堆栈信息
log.error(ThrowableUtil.getStackTrace(e));
- ApiError apiError = new ApiError(NOT_FOUND.value(),e.getMessage());
- return buildResponseEntity(apiError);
+ return buildResponseEntity(ApiError.error(NOT_FOUND.value(),e.getMessage()));
}
/**
* 处理所有接口数据验证异常
- * @param e
- * @returns
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
// 打印堆栈信息
log.error(ThrowableUtil.getStackTrace(e));
- String[] str = e.getBindingResult().getAllErrors().get(0).getCodes()[1].split("\\.");
- StringBuffer msg = new StringBuffer(str[1]+":");
- msg.append(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
- ApiError apiError = new ApiError(BAD_REQUEST.value(),msg.toString());
- return buildResponseEntity(apiError);
+ ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
+ String message = objectError.getDefaultMessage();
+ if (objectError instanceof FieldError) {
+ message = ((FieldError) objectError).getField() + ": " + message;
+ }
+ return buildResponseEntity(ApiError.error(message));
}
/**
* 统一返回
- * @param apiError
- * @return
*/
private ResponseEntity buildResponseEntity(ApiError apiError) {
- return new ResponseEntity(apiError, HttpStatus.valueOf(apiError.getStatus()));
+ return new ResponseEntity<>(apiError, HttpStatus.valueOf(apiError.getStatus()));
}
}
diff --git a/eladmin-common/src/main/java/me/zhengjie/mapper/EntityMapper.java b/eladmin-common/src/main/java/me/zhengjie/mapper/EntityMapper.java
deleted file mode 100644
index 98bc55db2..000000000
--- a/eladmin-common/src/main/java/me/zhengjie/mapper/EntityMapper.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package me.zhengjie.mapper;
-
-import java.util.List;
-
-/**
- * @author Zheng Jie
- * @date 2018-11-23
- */
-public interface EntityMapper {
-
- /**
- * DTO转Entity
- * @param dto
- * @return
- */
- E toEntity(D dto);
-
- /**
- * Entity转DTO
- * @param entity
- * @return
- */
- D toDto(E entity);
-
- /**
- * DTO集合转Entity集合
- * @param dtoList
- * @return
- */
- List toEntity(List dtoList);
-
- /**
- * Entity集合转DTO集合
- * @param entityList
- * @return
- */
- List toDto(List entityList);
-}
diff --git a/eladmin-common/src/main/java/me/zhengjie/redis/FastJsonRedisSerializer.java b/eladmin-common/src/main/java/me/zhengjie/redis/FastJsonRedisSerializer.java
deleted file mode 100644
index d2e224c87..000000000
--- a/eladmin-common/src/main/java/me/zhengjie/redis/FastJsonRedisSerializer.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package me.zhengjie.redis;
-
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.serializer.SerializerFeature;
-import org.springframework.data.redis.serializer.RedisSerializer;
-import org.springframework.data.redis.serializer.SerializationException;
-
-import java.nio.charset.Charset;
-
-/**
- * Value 序列化
- *
- * @author /
- * @param
- */
-public class FastJsonRedisSerializer implements RedisSerializer {
-
- public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
-
- private Class clazz;
-
- public FastJsonRedisSerializer(Class clazz) {
- super();
- this.clazz = clazz;
- }
-
- @Override
- public byte[] serialize(T t) throws SerializationException {
- if (t == null) {
- return new byte[0];
- }
- return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
- }
-
- @Override
- public T deserialize(byte[] bytes) throws SerializationException {
- if (bytes == null || bytes.length <= 0) {
- return null;
- }
- String str = new String(bytes, DEFAULT_CHARSET);
- return (T) JSON.parseObject(str, clazz);
- }
-
-}
diff --git a/eladmin-common/src/main/java/me/zhengjie/redis/RedisConfig.java b/eladmin-common/src/main/java/me/zhengjie/redis/RedisConfig.java
deleted file mode 100644
index a94861650..000000000
--- a/eladmin-common/src/main/java/me/zhengjie/redis/RedisConfig.java
+++ /dev/null
@@ -1,93 +0,0 @@
-package me.zhengjie.redis;
-
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.parser.ParserConfig;
-import lombok.extern.slf4j.Slf4j;
-import me.zhengjie.utils.StringUtils;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.cache.annotation.CachingConfigurerSupport;
-import org.springframework.cache.annotation.EnableCaching;
-import org.springframework.cache.interceptor.KeyGenerator;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.data.redis.cache.RedisCacheConfiguration;
-import org.springframework.data.redis.connection.RedisConnectionFactory;
-import org.springframework.data.redis.core.RedisOperations;
-import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.data.redis.serializer.RedisSerializationContext;
-import java.time.Duration;
-
-/**
- * @author Zheng Jie
- * @date 2018-11-24
- */
-@Slf4j
-@Configuration
-@EnableCaching
-// 自动配置
-@ConditionalOnClass(RedisOperations.class)
-@EnableConfigurationProperties(RedisProperties.class)
-public class RedisConfig extends CachingConfigurerSupport {
-
- /**
- * 设置 redis 数据默认过期时间,默认1天
- * 设置@cacheable 序列化方式
- * @return
- */
- @Bean
- public RedisCacheConfiguration redisCacheConfiguration(){
- FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
- RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
- configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer)).entryTtl(Duration.ofDays(1));
- return configuration;
- }
-
- @Bean(name = "redisTemplate")
- @ConditionalOnMissingBean(name = "redisTemplate")
- public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
- RedisTemplate template = new RedisTemplate<>();
- //序列化
- FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class);
- // value值的序列化采用fastJsonRedisSerializer
- template.setValueSerializer(fastJsonRedisSerializer);
- template.setHashValueSerializer(fastJsonRedisSerializer);
-
- // 全局开启AutoType,不建议使用
- // ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
- // 建议使用这种方式,小范围指定白名单
- ParserConfig.getGlobalInstance().addAccept("me.zhengjie.domain");
- ParserConfig.getGlobalInstance().addAccept("me.zhengjie.modules.system.service.dto");
- ParserConfig.getGlobalInstance().addAccept("me.zhengjie.modules.system.domain");
- ParserConfig.getGlobalInstance().addAccept("me.zhengjie.modules.quartz.domain");
- ParserConfig.getGlobalInstance().addAccept("me.zhengjie.modules.monitor.domain");
- ParserConfig.getGlobalInstance().addAccept("me.zhengjie.modules.security.security");
- // key的序列化采用StringRedisSerializer
- template.setKeySerializer(new StringRedisSerializer());
- template.setHashKeySerializer(new StringRedisSerializer());
- template.setConnectionFactory(redisConnectionFactory);
- return template;
- }
-
- /**
- * 自定义缓存key生成策略
- * 使用方法 @Cacheable(keyGenerator="keyGenerator")
- * @return
- */
- @Bean
- @Override
- public KeyGenerator keyGenerator() {
- return (target, method, params) -> {
- StringBuilder sb = new StringBuilder();
- sb.append(target.getClass().getName());
- sb.append(method.getName());
- for (Object obj : params) {
- sb.append(JSON.toJSONString(obj).hashCode());
- }
- return sb.toString();
- };
- }
-}
diff --git a/eladmin-common/src/main/java/me/zhengjie/redis/StringRedisSerializer.java b/eladmin-common/src/main/java/me/zhengjie/redis/StringRedisSerializer.java
deleted file mode 100644
index e0f54f10d..000000000
--- a/eladmin-common/src/main/java/me/zhengjie/redis/StringRedisSerializer.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package me.zhengjie.redis;
-
-import cn.hutool.core.lang.Assert;
-import com.alibaba.fastjson.JSON;
-import org.springframework.data.redis.serializer.RedisSerializer;
-
-import java.nio.charset.Charset;
-
-/**
- * 重写序列化器
- *
- * @author /
- */
-public class StringRedisSerializer implements RedisSerializer {
-
- private final Charset charset;
-
- private final String target = "\"";
-
- private final String replacement = "";
-
- public StringRedisSerializer() {
- this(Charset.forName("UTF8"));
- }
-
- public StringRedisSerializer(Charset charset) {
- Assert.notNull(charset, "Charset must not be null!");
- this.charset = charset;
- }
-
- @Override
- public String deserialize(byte[] bytes) {
- return (bytes == null ? null : new String(bytes, charset));
- }
-
- @Override
- public byte[] serialize(Object object) {
- String string = JSON.toJSONString(object);
- if (string == null) {
- return null;
- }
- string = string.replace(target, replacement);
- return string.getBytes(charset);
- }
-}
\ No newline at end of file
diff --git a/eladmin-common/src/main/java/me/zhengjie/swagger2/SwaggerConfig.java b/eladmin-common/src/main/java/me/zhengjie/swagger2/SwaggerConfig.java
deleted file mode 100644
index 207873cd6..000000000
--- a/eladmin-common/src/main/java/me/zhengjie/swagger2/SwaggerConfig.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package me.zhengjie.swagger2;
-
-import com.google.common.base.Predicates;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import springfox.documentation.builders.ApiInfoBuilder;
-import springfox.documentation.builders.ParameterBuilder;
-import springfox.documentation.builders.PathSelectors;
-import springfox.documentation.schema.ModelRef;
-import springfox.documentation.service.ApiInfo;
-import springfox.documentation.service.Parameter;
-import springfox.documentation.spi.DocumentationType;
-import springfox.documentation.spring.web.plugins.Docket;
-import springfox.documentation.swagger2.annotations.EnableSwagger2;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * api页面 /swagger-ui.html
- * @author Zheng Jie
- * @date 2018-11-23
- */
-
-@Configuration
-@EnableSwagger2
-public class SwaggerConfig {
-
- @Value("${jwt.header}")
- private String tokenHeader;
-
- @Value("${swagger.enabled}")
- private Boolean enabled;
-
- @Bean
- public Docket createRestApi() {
- ParameterBuilder ticketPar = new ParameterBuilder();
- List pars = new ArrayList();
- ticketPar.name(tokenHeader).description("token")
- .modelRef(new ModelRef("string"))
- .parameterType("header")
- .defaultValue("Bearer ")
- .required(true)
- .build();
- pars.add(ticketPar.build());
- return new Docket(DocumentationType.SWAGGER_2)
- .enable(enabled)
- .apiInfo(apiInfo())
- .select()
- .paths(Predicates.not(PathSelectors.regex("/error.*")))
- .build()
- .globalOperationParameters(pars);
- }
-
- private ApiInfo apiInfo() {
- return new ApiInfoBuilder()
- .title("eladmin 接口文档")
- .version("2.1")
- .build();
- }
-
-}
diff --git a/eladmin-common/src/main/java/me/zhengjie/swagger2/SwaggerDataConfig.java b/eladmin-common/src/main/java/me/zhengjie/swagger2/SwaggerDataConfig.java
deleted file mode 100644
index 35f0edf1a..000000000
--- a/eladmin-common/src/main/java/me/zhengjie/swagger2/SwaggerDataConfig.java
+++ /dev/null
@@ -1 +0,0 @@
-package me.zhengjie.swagger2;
import com.fasterxml.classmate.TypeResolver;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.data.domain.Pageable;
import springfox.documentation.schema.AlternateTypeRule;
import springfox.documentation.schema.AlternateTypeRuleConvention;
import java.util.List;
import static com.google.common.collect.Lists.newArrayList;
import static springfox.documentation.schema.AlternateTypeRules.newRule;
/**
* 将Pageable转换展示在swagger中
*/
@Configuration
public class SwaggerDataConfig {
@Bean
public AlternateTypeRuleConvention pageableConvention(final TypeResolver resolver) {
return new AlternateTypeRuleConvention() {
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
@Override
public List rules() {
return newArrayList(newRule(resolver.resolve(Pageable.class), resolver.resolve(Page.class)));
}
};
}
@ApiModel
static class Page {
@ApiModelProperty("页码 (0..N)")
private Integer page;
@ApiModelProperty("每页显示的数目")
private Integer size;
@ApiModelProperty("以下列格式排序标准:property[,asc | desc]。 默认排序顺序为升序。 支持多种排序条件:如:id,asc")
private List sort;
public Integer getPage() {
return page;
}
public void setPage(Integer page) {
this.page = page;
}
public Integer getSize() {
return size;
}
public void setSize(Integer size) {
this.size = size;
}
public List getSort() {
return sort;
}
public void setSort(List sort) {
this.sort = sort;
}
}
}
\ No newline at end of file
diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/AnonTagUtils.java b/eladmin-common/src/main/java/me/zhengjie/utils/AnonTagUtils.java
new file mode 100644
index 000000000..9a2832ded
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/utils/AnonTagUtils.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2019-2020 the original author or authors.
+ *
+ * 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 me.zhengjie.utils;
+
+import me.zhengjie.annotation.rest.AnonymousAccess;
+import me.zhengjie.utils.enums.RequestMethodEnum;
+import org.springframework.context.ApplicationContext;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+import java.util.*;
+
+/**
+ * @author Zheng Jie
+ * @description 匿名标记工具
+ * @date 2025-01-13
+ **/
+public class AnonTagUtils {
+
+ /**
+ * 获取匿名标记的URL
+ * @param applicationContext /
+ * @return /
+ */
+ public static Map> getAnonymousUrl(ApplicationContext applicationContext){
+ RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) applicationContext.getBean("requestMappingHandlerMapping");
+ Map handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
+ Map> anonymousUrls = new HashMap<>(8);
+ // 获取匿名标记
+ Set get = new HashSet<>();
+ Set post = new HashSet<>();
+ Set put = new HashSet<>();
+ Set patch = new HashSet<>();
+ Set delete = new HashSet<>();
+ Set all = new HashSet<>();
+ for (Map.Entry infoEntry : handlerMethodMap.entrySet()) {
+ HandlerMethod handlerMethod = infoEntry.getValue();
+ AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);
+ if (null != anonymousAccess) {
+ List requestMethods = new ArrayList<>(infoEntry.getKey().getMethodsCondition().getMethods());
+ RequestMethodEnum request = RequestMethodEnum.find(requestMethods.isEmpty() ? RequestMethodEnum.ALL.getType() : requestMethods.get(0).name());
+ if (infoEntry.getKey().getPatternsCondition()!=null) {
+ switch (Objects.requireNonNull(request)) {
+ case GET:
+ get.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
+ break;
+ case POST:
+ post.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
+ break;
+ case PUT:
+ put.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
+ break;
+ case PATCH:
+ patch.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
+ break;
+ case DELETE:
+ delete.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
+ break;
+ default:
+ all.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
+ break;
+ }
+ }
+ }
+ }
+ anonymousUrls.put(RequestMethodEnum.GET.getType(), get);
+ anonymousUrls.put(RequestMethodEnum.POST.getType(), post);
+ anonymousUrls.put(RequestMethodEnum.PUT.getType(), put);
+ anonymousUrls.put(RequestMethodEnum.PATCH.getType(), patch);
+ anonymousUrls.put(RequestMethodEnum.DELETE.getType(), delete);
+ anonymousUrls.put(RequestMethodEnum.ALL.getType(), all);
+ return anonymousUrls;
+ }
+
+ /**
+ * 获取所有匿名标记的URL
+ * @param applicationContext /
+ * @return /
+ */
+ public static Set getAllAnonymousUrl(ApplicationContext applicationContext){
+ Set allUrl = new HashSet<>();
+ Map> anonymousUrls = getAnonymousUrl(applicationContext);
+ for (String key : anonymousUrls.keySet()) {
+ allUrl.addAll(anonymousUrls.get(key));
+ }
+ return allUrl;
+ }
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/BigDecimalUtils.java b/eladmin-common/src/main/java/me/zhengjie/utils/BigDecimalUtils.java
new file mode 100644
index 000000000..ff3d81fa2
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/utils/BigDecimalUtils.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2019-2020 the original author or authors.
+ *
+ * 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 me.zhengjie.utils;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
+/**
+ * @author Zheng Jie
+ * @description 计算类
+ * @date 2024-12-27
+ **/
+public class BigDecimalUtils {
+
+ /**
+ * 将对象转换为 BigDecimal
+ * @param obj 输入对象
+ * @return 转换后的 BigDecimal
+ */
+ private static BigDecimal toBigDecimal(Object obj) {
+ if (obj instanceof BigDecimal) {
+ return (BigDecimal) obj;
+ } else if (obj instanceof Long) {
+ return BigDecimal.valueOf((Long) obj);
+ } else if (obj instanceof Integer) {
+ return BigDecimal.valueOf((Integer) obj);
+ } else if (obj instanceof Double) {
+ return new BigDecimal(String.valueOf(obj));
+ } else {
+ throw new IllegalArgumentException("Unsupported type");
+ }
+ }
+
+ /**
+ * 加法
+ * @param a 加数
+ * @param b 加数
+ * @return 两个加数的和,保留两位小数
+ */
+ public static BigDecimal add(Object a, Object b) {
+ BigDecimal bdA = toBigDecimal(a);
+ BigDecimal bdB = toBigDecimal(b);
+ return bdA.add(bdB).setScale(2, RoundingMode.HALF_UP);
+ }
+
+ /**
+ * 减法
+ * @param a 被减数
+ * @param b 减数
+ * @return 两数的差,保留两位小数
+ */
+ public static BigDecimal subtract(Object a, Object b) {
+ BigDecimal bdA = toBigDecimal(a);
+ BigDecimal bdB = toBigDecimal(b);
+ return bdA.subtract(bdB).setScale(2, RoundingMode.HALF_UP);
+ }
+
+ /**
+ * 乘法
+ * @param a 乘数
+ * @param b 乘数
+ * @return 两个乘数的积,保留两位小数
+ */
+ public static BigDecimal multiply(Object a, Object b) {
+ BigDecimal bdA = toBigDecimal(a);
+ BigDecimal bdB = toBigDecimal(b);
+ return bdA.multiply(bdB).setScale(2, RoundingMode.HALF_UP);
+ }
+
+ /**
+ * 除法
+ * @param a 被除数
+ * @param b 除数
+ * @return 两数的商,保留两位小数
+ */
+ public static BigDecimal divide(Object a, Object b) {
+ BigDecimal bdA = toBigDecimal(a);
+ BigDecimal bdB = toBigDecimal(b);
+ return bdA.divide(bdB, 2, RoundingMode.HALF_UP);
+ }
+
+ /**
+ * 除法
+ * @param a 被除数
+ * @param b 除数
+ * @param scale 保留小数位数
+ * @return 两数的商,保留两位小数
+ */
+ public static BigDecimal divide(Object a, Object b, int scale) {
+ BigDecimal bdA = toBigDecimal(a);
+ BigDecimal bdB = toBigDecimal(b);
+ return bdA.divide(bdB, scale, RoundingMode.HALF_UP);
+ }
+
+ /**
+ * 分转元
+ * @param obj 分的金额
+ * @return 转换后的元,保留两位小数
+ */
+ public static BigDecimal centsToYuan(Object obj) {
+ BigDecimal cents = toBigDecimal(obj);
+ return cents.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
+ }
+
+ /**
+ * 元转分
+ * @param obj 元的金额
+ * @return 转换后的分
+ */
+ public static Long yuanToCents(Object obj) {
+ BigDecimal yuan = toBigDecimal(obj);
+ return yuan.multiply(BigDecimal.valueOf(100)).setScale(0, RoundingMode.HALF_UP).longValue();
+ }
+
+ public static void main(String[] args) {
+ BigDecimal num1 = new BigDecimal("10.123");
+ BigDecimal num2 = new BigDecimal("2.456");
+
+ System.out.println("加法结果: " + add(num1, num2));
+ System.out.println("减法结果: " + subtract(num1, num2));
+ System.out.println("乘法结果: " + multiply(num1, num2));
+ System.out.println("除法结果: " + divide(num1, num2));
+
+ Long cents = 12345L;
+ System.out.println("分转元结果: " + centsToYuan(cents));
+
+ BigDecimal yuan = new BigDecimal("123.45");
+ System.out.println("元转分结果: " + yuanToCents(yuan));
+ }
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/CacheKey.java b/eladmin-common/src/main/java/me/zhengjie/utils/CacheKey.java
new file mode 100644
index 000000000..e8fb08609
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/utils/CacheKey.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2019-2020 the original author or authors.
+ *
+ * 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 me.zhengjie.utils;
+
+/**
+ * @author liaojinlong
+ * @date 2020/6/11 15:49
+ * @description 关于缓存的Key集合
+ */
+public interface CacheKey {
+
+ /**
+ * 用户
+ */
+ String USER_ID = "user::id:";
+
+ /**
+ * 数据
+ */
+ String DATA_USER = "data::user:";
+
+ /**
+ * 菜单
+ */
+ String MENU_ID = "menu::id:";
+ String MENU_USER = "menu::user:";
+
+ /**
+ * 角色授权
+ */
+ String ROLE_AUTH = "role::auth:";
+ String ROLE_USER = "role::user:";
+
+ /**
+ * 角色信息
+ */
+ String ROLE_ID = "role::id:";
+
+ /**
+ * 部门
+ */
+ String DEPT_ID = "dept::id:";
+
+ /**
+ * 岗位
+ */
+ String JOB_ID = "job::id:";
+
+ /**
+ * 数据字典
+ */
+ String DICT_NAME = "dict::name:";
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/CloseUtil.java b/eladmin-common/src/main/java/me/zhengjie/utils/CloseUtil.java
new file mode 100644
index 000000000..b095ec63b
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/utils/CloseUtil.java
@@ -0,0 +1,47 @@
+/*
+ * 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 me.zhengjie.utils;
+
+import java.io.Closeable;
+
+/**
+ * @author Zheng Jie
+ * @website https://eladmin.vip
+ * @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/eladmin-common/src/main/java/me/zhengjie/utils/DateUtil.java b/eladmin-common/src/main/java/me/zhengjie/utils/DateUtil.java
new file mode 100644
index 000000000..0b0bf6370
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/utils/DateUtil.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2019-2020 the original author or authors.
+ *
+ * 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 me.zhengjie.utils;
+
+import java.time.*;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
+
+/**
+ * @author: liaojinlong
+ * @date: 2020/6/11 16:28
+ * @apiNote: JDK 8 新日期类 格式化与字符串转换 工具类
+ */
+public class DateUtil {
+
+ public static final DateTimeFormatter DFY_MD_HMS = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+ public static final DateTimeFormatter DFY_MD = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+
+ /**
+ * LocalDateTime 转时间戳
+ *
+ * @param localDateTime /
+ * @return /
+ */
+ public static Long getTimeStamp(LocalDateTime localDateTime) {
+ return localDateTime.atZone(ZoneId.systemDefault()).toEpochSecond();
+ }
+
+ /**
+ * 时间戳转LocalDateTime
+ *
+ * @param timeStamp /
+ * @return /
+ */
+ public static LocalDateTime fromTimeStamp(Long timeStamp) {
+ return LocalDateTime.ofEpochSecond(timeStamp, 0, OffsetDateTime.now().getOffset());
+ }
+
+ /**
+ * LocalDateTime 转 Date
+ * Jdk8 后 不推荐使用 {@link Date} Date
+ *
+ * @param localDateTime /
+ * @return /
+ */
+ public static Date toDate(LocalDateTime localDateTime) {
+ return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
+ }
+
+ /**
+ * LocalDate 转 Date
+ * Jdk8 后 不推荐使用 {@link Date} Date
+ *
+ * @param localDate /
+ * @return /
+ */
+ public static Date toDate(LocalDate localDate) {
+ return toDate(localDate.atTime(LocalTime.now(ZoneId.systemDefault())));
+ }
+
+
+ /**
+ * Date转 LocalDateTime
+ * Jdk8 后 不推荐使用 {@link Date} Date
+ *
+ * @param date /
+ * @return /
+ */
+ public static LocalDateTime toLocalDateTime(Date date) {
+ return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
+ }
+
+ /**
+ * 日期 格式化
+ *
+ * @param localDateTime /
+ * @param patten /
+ * @return /
+ */
+ public static String localDateTimeFormat(LocalDateTime localDateTime, String patten) {
+ DateTimeFormatter df = DateTimeFormatter.ofPattern(patten);
+ return df.format(localDateTime);
+ }
+
+ /**
+ * 日期 格式化
+ *
+ * @param localDateTime /
+ * @param df /
+ * @return /
+ */
+ public static String localDateTimeFormat(LocalDateTime localDateTime, DateTimeFormatter df) {
+ return df.format(localDateTime);
+ }
+
+ /**
+ * 日期格式化 yyyy-MM-dd HH:mm:ss
+ *
+ * @param localDateTime /
+ * @return /
+ */
+ public static String localDateTimeFormatyMdHms(LocalDateTime localDateTime) {
+ return DFY_MD_HMS.format(localDateTime);
+ }
+
+ /**
+ * 日期格式化 yyyy-MM-dd
+ *
+ * @param localDateTime /
+ * @return /
+ */
+ public String localDateTimeFormatyMd(LocalDateTime localDateTime) {
+ return DFY_MD.format(localDateTime);
+ }
+
+ /**
+ * 字符串转 LocalDateTime ,字符串格式 yyyy-MM-dd
+ *
+ * @param localDateTime /
+ * @return /
+ */
+ public static LocalDateTime parseLocalDateTimeFormat(String localDateTime, String pattern) {
+ DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern);
+ return LocalDateTime.from(dateTimeFormatter.parse(localDateTime));
+ }
+
+ /**
+ * 字符串转 LocalDateTime ,字符串格式 yyyy-MM-dd
+ *
+ * @param localDateTime /
+ * @return /
+ */
+ public static LocalDateTime parseLocalDateTimeFormat(String localDateTime, DateTimeFormatter dateTimeFormatter) {
+ return LocalDateTime.from(dateTimeFormatter.parse(localDateTime));
+ }
+
+ /**
+ * 字符串转 LocalDateTime ,字符串格式 yyyy-MM-dd HH:mm:ss
+ *
+ * @param localDateTime /
+ * @return /
+ */
+ public static LocalDateTime parseLocalDateTimeFormatyMdHms(String localDateTime) {
+ return LocalDateTime.from(DFY_MD_HMS.parse(localDateTime));
+ }
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/ElAdminConstant.java b/eladmin-common/src/main/java/me/zhengjie/utils/ElAdminConstant.java
deleted file mode 100644
index 89bfeaae9..000000000
--- a/eladmin-common/src/main/java/me/zhengjie/utils/ElAdminConstant.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package me.zhengjie.utils;
-
-/**
- * 常用静态常量
- * @author Zheng Jie
- * @date 2018-12-26
- */
-public class ElAdminConstant {
-
- public static final String RESET_PASS = "重置密码";
-
- public static final String RESET_MAIL = "重置邮箱";
-
- /**
- * 用于IP定位转换
- */
- public static final String REGION = "内网IP|内网IP";
-
- /**
- * 常用接口
- */
- public static class Url{
- public static final String SM_MS_URL = "https://sm.ms/api/upload";
- }
-}
diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/ElConstant.java b/eladmin-common/src/main/java/me/zhengjie/utils/ElConstant.java
new file mode 100644
index 000000000..fa58845e0
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/utils/ElConstant.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 me.zhengjie.utils;
+
+/**
+ * 常用静态常量
+ *
+ * @author Zheng Jie
+ * @date 2018-12-26
+ */
+public class ElConstant {
+ /**
+ * win 系统
+ */
+ public static final String WIN = "win";
+
+ /**
+ * mac 系统
+ */
+ public static final String MAC = "mac";
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/EncryptUtils.java b/eladmin-common/src/main/java/me/zhengjie/utils/EncryptUtils.java
index 5bddf3a51..83bf918f3 100644
--- a/eladmin-common/src/main/java/me/zhengjie/utils/EncryptUtils.java
+++ b/eladmin-common/src/main/java/me/zhengjie/utils/EncryptUtils.java
@@ -1,11 +1,26 @@
+/*
+ * 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 me.zhengjie.utils;
-import org.springframework.util.DigestUtils;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
+import java.nio.charset.StandardCharsets;
/**
* 加密
@@ -14,36 +29,50 @@
*/
public class EncryptUtils {
- private static String strKey = "Passw0rd", strParam = "Passw0rd";
+ private static final String STR_PARAM = "Passw0rd";
+ private static final IvParameterSpec IV = new IvParameterSpec(STR_PARAM.getBytes(StandardCharsets.UTF_8));
+
+ private static DESKeySpec getDesKeySpec(String source) throws Exception {
+ if (source == null || source.isEmpty()) {
+ return null;
+ }
+ String strKey = "Passw0rd";
+ return new DESKeySpec(strKey.getBytes(StandardCharsets.UTF_8));
+ }
/**
* 对称加密
- * @param source
- * @return
- * @throws Exception
*/
public static String desEncrypt(String source) throws Exception {
- if (source == null || source.length() == 0){
- return null;
- }
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
- DESKeySpec desKeySpec = new DESKeySpec(strKey.getBytes("UTF-8"));
+ DESKeySpec desKeySpec = getDesKeySpec(source);
+ SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
+ SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
+ cipher.init(Cipher.ENCRYPT_MODE, secretKey, IV);
+ return byte2hex(cipher.doFinal(source.getBytes(StandardCharsets.UTF_8))).toUpperCase();
+ }
+
+ /**
+ * 对称解密
+ */
+ public static String desDecrypt(String source) throws Exception {
+ Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
+ byte[] src = hex2byte(source.getBytes(StandardCharsets.UTF_8));
+ DESKeySpec desKeySpec = getDesKeySpec(source);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
- IvParameterSpec iv = new IvParameterSpec(strParam.getBytes("UTF-8"));
- cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
- return byte2hex(
- cipher.doFinal(source.getBytes("UTF-8"))).toUpperCase();
+ cipher.init(Cipher.DECRYPT_MODE, secretKey, IV);
+ byte[] retByte = cipher.doFinal(src);
+ return new String(retByte);
}
- public static String byte2hex(byte[] inStr) {
+ private static String byte2hex(byte[] inStr) {
String stmp;
- StringBuffer out = new StringBuffer(inStr.length * 2);
- for (int n = 0; n < inStr.length; n++) {
- stmp = Integer.toHexString(inStr[n] & 0xFF);
+ StringBuilder out = new StringBuilder(inStr.length * 2);
+ for (byte b : inStr) {
+ stmp = Integer.toHexString(b & 0xFF);
if (stmp.length() == 1) {
- // 如果是0至F的单位字符串,则添加0
- out.append("0" + stmp);
+ out.append("0").append(stmp);
} else {
out.append(stmp);
}
@@ -51,46 +80,16 @@ public static String byte2hex(byte[] inStr) {
return out.toString();
}
-
- public static byte[] hex2byte(byte[] b) {
- if ((b.length % 2) != 0){
+ private static byte[] hex2byte(byte[] b) {
+ int size = 2;
+ if ((b.length % size) != 0) {
throw new IllegalArgumentException("长度不是偶数");
}
byte[] b2 = new byte[b.length / 2];
- for (int n = 0; n < b.length; n += 2) {
+ for (int n = 0; n < b.length; n += size) {
String item = new String(b, n, 2);
b2[n / 2] = (byte) Integer.parseInt(item, 16);
}
return b2;
}
-
- /**
- * 对称解密
- * @param source
- * @return
- * @throws Exception
- */
- public static String desDecrypt(String source) throws Exception {
- if (source == null || source.length() == 0){
- return null;
- }
- byte[] src = hex2byte(source.getBytes());
- Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
- DESKeySpec desKeySpec = new DESKeySpec(strKey.getBytes("UTF-8"));
- SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
- SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
- IvParameterSpec iv = new IvParameterSpec(strParam.getBytes("UTF-8"));
- cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
- byte[] retByte = cipher.doFinal(src);
- return new String(retByte);
- }
-
- /**
- * 密码加密
- * @param password
- * @return
- */
- public static String encryptPassword(String password){
- return DigestUtils.md5DigestAsHex(password.getBytes());
- }
}
diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/FileUtil.java b/eladmin-common/src/main/java/me/zhengjie/utils/FileUtil.java
index 5549b8149..590f76b68 100644
--- a/eladmin-common/src/main/java/me/zhengjie/utils/FileUtil.java
+++ b/eladmin-common/src/main/java/me/zhengjie/utils/FileUtil.java
@@ -1,18 +1,62 @@
+/*
+ * 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 me.zhengjie.utils;
+import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
+import cn.hutool.poi.excel.BigExcelWriter;
+import cn.hutool.poi.excel.ExcelUtil;
+import lombok.extern.slf4j.Slf4j;
+import me.zhengjie.exception.BadRequestException;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.springframework.web.multipart.MultipartFile;
-
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
import java.io.*;
+import java.nio.file.Files;
+import java.security.MessageDigest;
import java.text.DecimalFormat;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.stream.Collectors;
/**
* File工具类,扩展 hutool 工具包
+ *
* @author Zheng Jie
* @date 2018-12-27
*/
+@Slf4j
public class FileUtil extends cn.hutool.core.io.FileUtil {
+ /**
+ * 系统临时目录
+ *
+ * windows 包含路径分割符,但Linux 不包含,
+ * 在windows \\==\ 前提下,
+ * 为安全起见 同意拼装 路径分割符,
+ *
+ * java.io.tmpdir
+ * windows : C:\Users/xxx\AppData\Local\Temp\
+ * linux: /temp
+ *
+ */
+ public static final String SYS_TEM_DIR = System.getProperty("java.io.tmpdir") + File.separator;
/**
* 定义GB的计算常量
*/
@@ -31,49 +75,40 @@ public class FileUtil extends cn.hutool.core.io.FileUtil {
*/
private static final DecimalFormat DF = new DecimalFormat("0.00");
+ public static final String IMAGE = "图片";
+ public static final String TXT = "文档";
+ public static final String MUSIC = "音乐";
+ public static final String VIDEO = "视频";
+ public static final String OTHER = "其他";
+
+
/**
* MultipartFile转File
- * @param multipartFile
- * @return
*/
- public static File toFile(MultipartFile multipartFile){
+ public static File toFile(MultipartFile multipartFile) {
// 获取文件名
String fileName = multipartFile.getOriginalFilename();
// 获取文件后缀
- String prefix="."+getExtensionName(fileName);
+ String prefix = "." + getExtensionName(fileName);
File file = null;
try {
// 用uuid作为文件名,防止生成的临时文件重复
- file = File.createTempFile(IdUtil.simpleUUID(), prefix);
+ file = new File(SYS_TEM_DIR + IdUtil.simpleUUID() + prefix);
// MultipartFile to File
multipartFile.transferTo(file);
} catch (IOException e) {
- e.printStackTrace();
+ log.error(e.getMessage(), e);
}
return file;
}
/**
- * 删除
- * @param files
- */
- public static void deleteFile(File... files) {
- for (File file : files) {
- if (file.exists()) {
- file.delete();
- }
- }
- }
-
- /**
- * 获取文件扩展名
- * @param filename
- * @return
+ * 获取文件扩展名,不带 .
*/
public static String getExtensionName(String filename) {
- if ((filename != null) && (filename.length() > 0)) {
+ if ((filename != null) && (!filename.isEmpty())) {
int dot = filename.lastIndexOf('.');
- if ((dot >-1) && (dot < (filename.length() - 1))) {
+ if ((dot > -1) && (dot < (filename.length() - 1))) {
return filename.substring(dot + 1);
}
}
@@ -82,13 +117,11 @@ public static String getExtensionName(String filename) {
/**
* Java文件操作 获取不带扩展名的文件名
- * @param filename
- * @return
*/
public static String getFileNameNoEx(String filename) {
- if ((filename != null) && (filename.length() > 0)) {
+ if ((filename != null) && (!filename.isEmpty())) {
int dot = filename.lastIndexOf('.');
- if ((dot >-1) && (dot < (filename.length()))) {
+ if (dot > -1) {
return filename.substring(0, dot);
}
}
@@ -97,46 +130,280 @@ public static String getFileNameNoEx(String filename) {
/**
* 文件大小转换
- * @param size
- * @return
*/
- public static String getSize(int size){
- String resultSize = "";
+ public static String getSize(long size) {
+ String resultSize;
if (size / GB >= 1) {
//如果当前Byte的值大于等于1GB
- resultSize = DF.format(size / (float) GB) + "GB ";
+ resultSize = DF.format(size / (float) GB) + "GB";
} else if (size / MB >= 1) {
//如果当前Byte的值大于等于1MB
- resultSize = DF.format(size / (float) MB) + "MB ";
+ resultSize = DF.format(size / (float) MB) + "MB";
} else if (size / KB >= 1) {
//如果当前Byte的值大于等于1KB
- resultSize = DF.format(size / (float) KB) + "KB ";
+ resultSize = DF.format(size / (float) KB) + "KB";
} else {
- resultSize = size + "B ";
+ resultSize = size + "B";
}
return resultSize;
}
/**
* inputStream 转 File
- * @param ins
- * @param name
- * @return
- * @throws Exception
*/
- public static File inputStreamToFile(InputStream ins, String name) throws Exception{
- File file = new File(System.getProperty("java.io.tmpdir") + name);
+ static File inputStreamToFile(InputStream ins, String name){
+ File file = new File(SYS_TEM_DIR + name);
if (file.exists()) {
return file;
}
- OutputStream os = new FileOutputStream(file);
- int bytesRead = 0;
- byte[] buffer = new byte[8192];
- while ((bytesRead = ins.read(buffer, 0, 8192)) != -1) {
- os.write(buffer, 0, bytesRead);
+ OutputStream os = null;
+ try {
+ os = Files.newOutputStream(file.toPath());
+ int bytesRead;
+ int len = 8192;
+ byte[] buffer = new byte[len];
+ while ((bytesRead = ins.read(buffer, 0, len)) != -1) {
+ os.write(buffer, 0, bytesRead);
+ }
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ } finally {
+ CloseUtil.close(os);
+ CloseUtil.close(ins);
}
- os.close();
- ins.close();
return file;
}
+
+ /**
+ * 将文件名解析成文件的上传路径
+ */
+ public static File upload(MultipartFile file, String filePath) {
+ Date date = new Date();
+ SimpleDateFormat format = new SimpleDateFormat("yyyyMMddhhmmssS");
+ // 过滤非法文件名
+ String name = getFileNameNoEx(verifyFilename(file.getOriginalFilename()));
+ String suffix = getExtensionName(file.getOriginalFilename());
+ String nowStr = "-" + format.format(date);
+ try {
+ String fileName = name + nowStr + "." + suffix;
+ String path = filePath + fileName;
+ // getCanonicalFile 可解析正确各种路径
+ File dest = new File(path).getCanonicalFile();
+ // 检测是否存在目录
+ if (!dest.getParentFile().exists()) {
+ if (!dest.getParentFile().mkdirs()) {
+ System.out.println("was not successful.");
+ }
+ }
+ // 文件写入
+ file.transferTo(dest);
+ return dest;
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ }
+ return null;
+ }
+
+ /**
+ * 导出excel
+ */
+ public static void downloadExcel(List> list, HttpServletResponse response) throws IOException {
+ String tempPath = SYS_TEM_DIR + IdUtil.fastSimpleUUID() + ".xlsx";
+ File file = new File(tempPath);
+ BigExcelWriter writer = ExcelUtil.getBigWriter(file);
+ // 处理数据以防止CSV注入
+ List> sanitizedList = list.parallelStream().map(map -> {
+ Map sanitizedMap = new LinkedHashMap<>();
+ map.forEach((key, value) -> {
+ if (value instanceof String) {
+ String strValue = (String) value;
+ // 检查并处理以特殊字符开头的值
+ if (strValue.startsWith("=") || strValue.startsWith("+") || strValue.startsWith("-") || strValue.startsWith("@")) {
+ strValue = "'" + strValue; // 添加单引号前缀
+ }
+ sanitizedMap.put(key, strValue);
+ } else {
+ sanitizedMap.put(key, value);
+ }
+ });
+ return sanitizedMap;
+ }).collect(Collectors.toList());
+ // 一次性写出内容,使用默认样式,强制输出标题
+ writer.write(sanitizedList, true);
+ SXSSFSheet sheet = (SXSSFSheet)writer.getSheet();
+ //上面需要强转SXSSFSheet 不然没有trackAllColumnsForAutoSizing方法
+ sheet.trackAllColumnsForAutoSizing();
+ //列宽自适应
+ writer.autoSizeColumnAll();
+ //response为HttpServletResponse对象
+ response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
+ //test.xls是弹出下载对话框的文件名,不能为中文,中文请自行编码
+ response.setHeader("Content-Disposition", "attachment;filename=file.xlsx");
+ ServletOutputStream out = response.getOutputStream();
+ // 终止后删除临时文件
+ file.deleteOnExit();
+ writer.flush(out, true);
+ //此处记得关闭输出Servlet流
+ IoUtil.close(out);
+ }
+
+ public static String getFileType(String type) {
+ String documents = "txt doc pdf ppt pps xlsx xls docx";
+ String music = "mp3 wav wma mpa ram ra aac aif m4a";
+ String video = "avi mpg mpe mpeg asf wmv mov qt rm mp4 flv m4v webm ogv ogg";
+ String image = "bmp dib pcp dif wmf gif jpg tif eps psd cdr iff tga pcd mpt png jpeg";
+ if (image.contains(type)) {
+ return IMAGE;
+ } else if (documents.contains(type)) {
+ return TXT;
+ } else if (music.contains(type)) {
+ return MUSIC;
+ } else if (video.contains(type)) {
+ return VIDEO;
+ } else {
+ return OTHER;
+ }
+ }
+
+ public static void checkSize(long maxSize, long size) {
+ // 1M
+ int len = 1024 * 1024;
+ if (size > (maxSize * len)) {
+ throw new BadRequestException("文件超出规定大小:" + maxSize + "MB");
+ }
+ }
+
+ /**
+ * 判断两个文件是否相同
+ */
+ public static boolean check(File file1, File file2) {
+ String img1Md5 = getMd5(file1);
+ String img2Md5 = getMd5(file2);
+ if(img1Md5 != null){
+ return img1Md5.equals(img2Md5);
+ }
+ return false;
+ }
+
+ /**
+ * 判断两个文件是否相同
+ */
+ public static boolean check(String file1Md5, String file2Md5) {
+ return file1Md5.equals(file2Md5);
+ }
+
+ private static byte[] getByte(File file) {
+ // 得到文件长度
+ byte[] b = new byte[(int) file.length()];
+ InputStream in = null;
+ try {
+ in = Files.newInputStream(file.toPath());
+ try {
+ System.out.println(in.read(b));
+ } catch (IOException e) {
+ log.error(e.getMessage(), e);
+ }
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return null;
+ } finally {
+ CloseUtil.close(in);
+ }
+ return b;
+ }
+
+ private static String getMd5(byte[] bytes) {
+ // 16进制字符
+ char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+ try {
+ MessageDigest mdTemp = MessageDigest.getInstance("MD5");
+ mdTemp.update(bytes);
+ byte[] md = mdTemp.digest();
+ int j = md.length;
+ char[] str = new char[j * 2];
+ int k = 0;
+ // 移位 输出字符串
+ for (byte byte0 : md) {
+ str[k++] = hexDigits[byte0 >>> 4 & 0xf];
+ str[k++] = hexDigits[byte0 & 0xf];
+ }
+ return new String(str);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ }
+ return null;
+ }
+
+ /**
+ * 下载文件
+ *
+ * @param request /
+ * @param response /
+ * @param file /
+ */
+ public static void downloadFile(HttpServletRequest request, HttpServletResponse response, File file, boolean deleteOnExit) {
+ response.setCharacterEncoding(request.getCharacterEncoding());
+ response.setContentType("application/octet-stream");
+ FileInputStream fis = null;
+ try {
+ fis = new FileInputStream(file);
+ response.setHeader("Content-Disposition", "attachment; filename=" + file.getName());
+ IOUtils.copy(fis, response.getOutputStream());
+ response.flushBuffer();
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ if (deleteOnExit) {
+ file.deleteOnExit();
+ }
+ } catch (IOException e) {
+ log.error(e.getMessage(), e);
+ }
+ }
+ }
+ }
+
+ /**
+ * 验证并过滤非法的文件名
+ * @param fileName 文件名
+ * @return 文件名
+ */
+ public static String verifyFilename(String fileName) {
+ // 过滤掉特殊字符
+ fileName = fileName.replaceAll("[\\\\/:*?\"<>|~\\s]", "");
+
+ // 去掉文件名开头和结尾的空格和点
+ fileName = fileName.trim().replaceAll("^[. ]+|[. ]+$", "");
+
+ // 不允许文件名超过255(在Mac和Linux中)或260(在Windows中)个字符
+ int maxFileNameLength = 255;
+ if (System.getProperty("os.name").startsWith("Windows")) {
+ maxFileNameLength = 260;
+ }
+ if (fileName.length() > maxFileNameLength) {
+ fileName = fileName.substring(0, maxFileNameLength);
+ }
+
+ // 过滤掉控制字符
+ fileName = fileName.replaceAll("[\\p{Cntrl}]", "");
+
+ // 过滤掉 ".." 路径
+ fileName = fileName.replaceAll("\\.{2,}", "");
+
+ // 去掉文件名开头的 ".."
+ fileName = fileName.replaceAll("^\\.+/", "");
+
+ // 保留文件名中最后一个 "." 字符,过滤掉其他 "."
+ fileName = fileName.replaceAll("^(.*)(\\.[^.]*)$", "$1").replaceAll("\\.", "") +
+ fileName.replaceAll("^(.*)(\\.[^.]*)$", "$2");
+
+ return fileName;
+ }
+
+ public static String getMd5(File file) {
+ return getMd5(getByte(file));
+ }
}
diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/PageResult.java b/eladmin-common/src/main/java/me/zhengjie/utils/PageResult.java
new file mode 100644
index 000000000..f6019cd8e
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/utils/PageResult.java
@@ -0,0 +1,24 @@
+package me.zhengjie.utils;
+
+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 content;
+
+ private long totalElements;
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/PageUtil.java b/eladmin-common/src/main/java/me/zhengjie/utils/PageUtil.java
index 7c4c7e703..cbb99e544 100644
--- a/eladmin-common/src/main/java/me/zhengjie/utils/PageUtil.java
+++ b/eladmin-common/src/main/java/me/zhengjie/utils/PageUtil.java
@@ -1,10 +1,22 @@
+/*
+ * 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 me.zhengjie.utils;
import org.springframework.data.domain.Page;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
/**
* 分页工具
@@ -15,17 +27,12 @@ public class PageUtil extends cn.hutool.core.util.PageUtil {
/**
* List 分页
- * @param page
- * @param size
- * @param list
- * @return
*/
- public static List toPage(int page, int size , List 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 new ArrayList();
+ return Collections.emptyList();
} else if(toIndex >= list.size()) {
return list.subList(fromIndex,list.size());
} else {
@@ -35,30 +42,22 @@ public static List toPage(int page, int size , List list) {
/**
* Page 数据处理,预防redis反序列化报错
- * @param page
- * @return
*/
- public static Map toPage(Page page) {
- Map map = new HashMap();
-
- map.put("content",page.getContent());
- map.put("totalElements",page.getTotalElements());
-
- return map;
+ public static PageResult toPage(Page page) {
+ return new PageResult<>(page.getContent(), page.getTotalElements());
}
/**
- * @param object
- * @param totalElements
- * @return
+ * 自定义分页
*/
- public static Map toPage(Object object, Object totalElements) {
- Map map = new HashMap();
-
- map.put("content",object);
- map.put("totalElements",totalElements);
-
- return map;
+ public static PageResult toPage(List list, long totalElements) {
+ return new PageResult<>(list, totalElements);
}
+ /**
+ * 返回空数据
+ */
+ public static PageResult noData () {
+ return new PageResult<>(null, 0);
+ }
}
diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/QueryHelp.java b/eladmin-common/src/main/java/me/zhengjie/utils/QueryHelp.java
index c1ad8ab8d..cbb8b65f8 100644
--- a/eladmin-common/src/main/java/me/zhengjie/utils/QueryHelp.java
+++ b/eladmin-common/src/main/java/me/zhengjie/utils/QueryHelp.java
@@ -1,8 +1,25 @@
+/*
+ * 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 me.zhengjie.utils;
import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
+import me.zhengjie.annotation.DataPermission;
import me.zhengjie.annotation.Query;
import javax.persistence.criteria.*;
import java.lang.reflect.Field;
@@ -13,25 +30,34 @@
* @date 2019-6-4 14:59:48
*/
@Slf4j
+@SuppressWarnings({"unchecked","all"})
public class QueryHelp {
- /**
- * @描述 : 转换为Predicate
- * @作者 : Dong ZhaoYang
- * @日期 : 2017/8/7
- * @时间 : 17:25
- */
- @SuppressWarnings("unchecked")
public static Predicate getPredicate(Root root, Q query, CriteriaBuilder cb) {
List list = new ArrayList<>();
-
if(query == null){
- return cb.and(list.toArray(new Predicate[list.size()]));
+ return cb.and(list.toArray(new Predicate[0]));
+ }
+ // 数据权限验证
+ DataPermission permission = query.getClass().getAnnotation(DataPermission.class);
+ if(permission != null){
+ // 获取数据权限
+ List dataScopes = SecurityUtils.getCurrentUserDataScope();
+ if(CollectionUtil.isNotEmpty(dataScopes)){
+ if(StringUtils.isNotBlank(permission.joinName()) && StringUtils.isNotBlank(permission.fieldName())) {
+ Join join = root.join(permission.joinName(), JoinType.LEFT);
+ list.add(getExpression(permission.fieldName(),join, root).in(dataScopes));
+ } else if (StringUtils.isBlank(permission.joinName()) && StringUtils.isNotBlank(permission.fieldName())) {
+ list.add(getExpression(permission.fieldName(),null, root).in(dataScopes));
+ }
+ }
}
try {
+ Map joinKey = new HashMap<>();
List fields = getAllFields(query.getClass(), new ArrayList<>());
for (Field field : fields) {
boolean accessible = field.isAccessible();
+ // 设置对象的访问权限,保证对private的属性的访
field.setAccessible(true);
Query q = field.getAnnotation(Query.class);
if (q != null) {
@@ -41,7 +67,7 @@ public static Predicate getPredicate(Root root, Q query, CriteriaBuild
String attributeName = isBlank(propName) ? field.getName() : propName;
Class> fieldType = field.getType();
Object val = field.get(query);
- if (ObjectUtil.isNull(val)) {
+ if (ObjectUtil.isNull(val) || "".equals(val)) {
continue;
}
Join join = null;
@@ -50,21 +76,43 @@ public static Predicate getPredicate(Root root, Q query, CriteriaBuild
String[] blurrys = blurry.split(",");
List orPredicate = new ArrayList<>();
for (String s : blurrys) {
- orPredicate.add(cb.like(root.get(s)
- .as(String.class), "%" + val.toString() + "%"));
+ orPredicate.add(cb.like(root.get(s).as(String.class), "%" + val.toString() + "%"));
}
Predicate[] p = new Predicate[orPredicate.size()];
list.add(cb.or(orPredicate.toArray(p)));
continue;
}
if (ObjectUtil.isNotEmpty(joinName)) {
- switch (q.join()) {
- case LEFT:
- join = root.join(joinName, JoinType.LEFT);
- break;
- case RIGHT:
- join = root.join(joinName, JoinType.RIGHT);
- break;
+ join = joinKey.get(joinName);
+ if(join == null){
+ String[] joinNames = joinName.split(">");
+ for (String name : joinNames) {
+ switch (q.join()) {
+ case LEFT:
+ if(ObjectUtil.isNotNull(join) && ObjectUtil.isNotNull(val)){
+ join = join.join(name, JoinType.LEFT);
+ } else {
+ join = root.join(name, JoinType.LEFT);
+ }
+ break;
+ case RIGHT:
+ if(ObjectUtil.isNotNull(join) && ObjectUtil.isNotNull(val)){
+ join = join.join(name, JoinType.RIGHT);
+ } else {
+ join = root.join(name, JoinType.RIGHT);
+ }
+ break;
+ case INNER:
+ if(ObjectUtil.isNotNull(join) && ObjectUtil.isNotNull(val)){
+ join = join.join(name, JoinType.INNER);
+ } else {
+ join = root.join(name, JoinType.INNER);
+ }
+ break;
+ default: break;
+ }
+ }
+ joinKey.put(joinName, join);
}
}
switch (q.type()) {
@@ -95,11 +143,38 @@ public static Predicate getPredicate(Root root, Q query, CriteriaBuild
case RIGHT_LIKE:
list.add(cb.like(getExpression(attributeName,join,root)
.as(String.class), val.toString() + "%"));
+ break;
case IN:
- if (CollUtil.isNotEmpty((Collection)val)) {
- list.add(getExpression(attributeName,join,root).in((Collection) val));
+ if (CollUtil.isNotEmpty((Collection)val)) {
+ list.add(getExpression(attributeName,join,root).in((Collection) val));
}
break;
+ case NOT_IN:
+ if (CollUtil.isNotEmpty((Collection)val)) {
+ list.add(getExpression(attributeName,join,root).in((Collection) val).not());
+ }
+ break;
+ case NOT_EQUAL:
+ list.add(cb.notEqual(getExpression(attributeName,join,root), val));
+ break;
+ case NOT_NULL:
+ list.add(cb.isNotNull(getExpression(attributeName,join,root)));
+ break;
+ case IS_NULL:
+ list.add(cb.isNull(getExpression(attributeName,join,root)));
+ break;
+ case BETWEEN:
+ List between = new ArrayList<>((List)val);
+ if(between.size() == 2){
+ list.add(cb.between(getExpression(attributeName, join, root).as((Class extends Comparable>) between.get(0).getClass()),
+ (Comparable) between.get(0), (Comparable) between.get(1)));
+ }
+ break;
+ case FIND_IN_SET:
+ list.add(cb.greaterThan(cb.function("FIND_IN_SET", Integer.class,
+ cb.literal(val.toString()), root.get(attributeName)), 0));
+ break;
+ default: break;
}
}
field.setAccessible(accessible);
@@ -107,7 +182,8 @@ public static Predicate getPredicate(Root root, Q query, CriteriaBuild
} catch (Exception e) {
log.error(e.getMessage(), e);
}
- return cb.and(list.toArray(new Predicate[list.size()]));
+ int size = list.size();
+ return cb.and(list.toArray(new Predicate[size]));
}
@SuppressWarnings("unchecked")
@@ -119,22 +195,20 @@ private static Expression getExpression(String attributeName, Join joi
}
}
- @SuppressWarnings("unchecked")
- public static boolean isBlank(final CharSequence cs) {
+ private static boolean isBlank(final CharSequence cs) {
int strLen;
if (cs == null || (strLen = cs.length()) == 0) {
return true;
}
for (int i = 0; i < strLen; i++) {
- if (Character.isWhitespace(cs.charAt(i)) == false) {
+ if (!Character.isWhitespace(cs.charAt(i))) {
return false;
}
}
return true;
}
- @SuppressWarnings("unchecked")
- private static List getAllFields(Class clazz, List fields) {
+ public static List getAllFields(Class clazz, List fields) {
if (clazz != null) {
fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
getAllFields(clazz.getSuperclass(), fields);
diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/RedisUtils.java b/eladmin-common/src/main/java/me/zhengjie/utils/RedisUtils.java
new file mode 100644
index 000000000..806431ccc
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/utils/RedisUtils.java
@@ -0,0 +1,809 @@
+/*
+ * 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 me.zhengjie.utils;
+
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson2.JSON;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.redis.connection.RedisConnection;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.*;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+ * @author /
+ */
+@Component
+@SuppressWarnings({"all"})
+public class RedisUtils {
+ private static final Logger log = LoggerFactory.getLogger(RedisUtils.class);
+
+ private RedisTemplate redisTemplate;
+
+ public RedisUtils(RedisTemplate redisTemplate) {
+ this.redisTemplate = redisTemplate;
+ this.redisTemplate.setKeySerializer(new StringRedisSerializer());
+ this.redisTemplate.setHashKeySerializer(new StringRedisSerializer());
+ }
+
+ /**
+ * 指定缓存失效时间
+ *
+ * @param key 键
+ * @param time 时间(秒) 注意:这里将会替换原有的时间
+ */
+ public boolean expire(String key, long time) {
+ try {
+ if (time > 0) {
+ redisTemplate.expire(key, time, TimeUnit.SECONDS);
+ }
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * 指定缓存失效时间
+ *
+ * @param key 键
+ * @param time 时间(秒) 注意:这里将会替换原有的时间
+ * @param timeUnit 单位
+ */
+ public boolean expire(String key, long time, TimeUnit timeUnit) {
+ try {
+ if (time > 0) {
+ redisTemplate.expire(key, time, timeUnit);
+ }
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * 根据 key 获取过期时间
+ *
+ * @param key 键 不能为null
+ * @return 时间(秒) 返回0代表为永久有效
+ */
+ public long getExpire(Object key) {
+ return redisTemplate.getExpire(key, TimeUnit.SECONDS);
+ }
+
+ /**
+ * 查找匹配key
+ *
+ * @param pattern key
+ * @return /
+ */
+ public List scan(String pattern) {
+ ScanOptions options = ScanOptions.scanOptions().match(pattern).build();
+ RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
+ RedisConnection rc = Objects.requireNonNull(factory).getConnection();
+ Cursor cursor = rc.scan(options);
+ List result = new ArrayList<>();
+ while (cursor.hasNext()) {
+ result.add(new String(cursor.next()));
+ }
+ try {
+ RedisConnectionUtils.releaseConnection(rc, factory);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ }
+ return result;
+ }
+
+ /**
+ * 分页查询 key
+ *
+ * @param patternKey key
+ * @param page 页码
+ * @param size 每页数目
+ * @return /
+ */
+ public List findKeysForPage(String patternKey, int page, int size) {
+ ScanOptions options = ScanOptions.scanOptions().match(patternKey).build();
+ RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
+ RedisConnection rc = Objects.requireNonNull(factory).getConnection();
+ Cursor cursor = rc.scan(options);
+ List result = new ArrayList<>(size);
+ int tmpIndex = 0;
+ int fromIndex = page * size;
+ int toIndex = page * size + size;
+ while (cursor.hasNext()) {
+ if (tmpIndex >= fromIndex && tmpIndex < toIndex) {
+ result.add(new String(cursor.next()));
+ tmpIndex++;
+ continue;
+ }
+ // 获取到满足条件的数据后,就可以退出了
+ if (tmpIndex >= toIndex) {
+ break;
+ }
+ tmpIndex++;
+ cursor.next();
+ }
+ try {
+ RedisConnectionUtils.releaseConnection(rc, factory);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ }
+ return result;
+ }
+
+ /**
+ * 判断key是否存在
+ *
+ * @param key 键
+ * @return true 存在 false不存在
+ */
+ public boolean hasKey(String key) {
+ try {
+ return redisTemplate.hasKey(key);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return false;
+ }
+ }
+
+ /**
+ * 删除缓存
+ *
+ * @param key 可以传一个值 或多个
+ */
+ public void del(String... keys) {
+ if (keys != null && keys.length > 0) {
+ if (keys.length == 1) {
+ boolean result = redisTemplate.delete(keys[0]);
+ log.debug("--------------------------------------------");
+ log.debug(new StringBuilder("删除缓存:").append(keys[0]).append(",结果:").append(result).toString());
+ log.debug("--------------------------------------------");
+ } else {
+ Set keySet = new HashSet<>();
+ for (String key : keys) {
+ if (redisTemplate.hasKey(key))
+ keySet.add(key);
+ }
+ long count = redisTemplate.delete(keySet);
+ log.debug("--------------------------------------------");
+ log.debug("成功删除缓存:" + keySet.toString());
+ log.debug("缓存删除数量:" + count + "个");
+ log.debug("--------------------------------------------");
+ }
+ }
+ }
+
+ /**
+ * 批量模糊删除key
+ * @param pattern
+ */
+ public void scanDel(String pattern){
+ ScanOptions options = ScanOptions.scanOptions().match(pattern).build();
+ try (Cursor cursor = redisTemplate.executeWithStickyConnection(
+ (RedisCallback>) connection -> (Cursor) new ConvertingCursor<>(
+ connection.scan(options), redisTemplate.getKeySerializer()::deserialize))) {
+ while (cursor.hasNext()) {
+ redisTemplate.delete(cursor.next());
+ }
+ }
+ }
+
+ // ============================String=============================
+
+ /**
+ * 普通缓存获取
+ *
+ * @param key 键
+ * @return 值
+ */
+ public Object get(String key) {
+ return key == null ? null : redisTemplate.opsForValue().get(key);
+ }
+
+ /**
+ * 普通缓存获取
+ *
+ * @param key 键
+ * @return 值
+ */
+ public T get(String key, Class clazz) {
+ Object value = key == null ? null : redisTemplate.opsForValue().get(key);
+ if (value == null) {
+ return null;
+ }
+ // 如果 value 不是目标类型,则尝试将其反序列化为 clazz 类型
+ if (!clazz.isInstance(value)) {
+ return JSON.parseObject(value.toString(), clazz);
+ } else if (clazz.isInstance(value)) {
+ return clazz.cast(value);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * 普通缓存获取
+ *
+ * @param key 键
+ * @param clazz 列表中元素的类型
+ * @return 值
+ */
+ public List getList(String key, Class clazz) {
+ Object value = key == null ? null : redisTemplate.opsForValue().get(key);
+ if (value == null) {
+ return null;
+ }
+ if (value instanceof List>) {
+ List> list = (List>) value;
+ // 检查每个元素是否为指定类型
+ if (list.stream().allMatch(clazz::isInstance)) {
+ return list.stream().map(clazz::cast).collect(Collectors.toList());
+ }
+ }
+ return null;
+ }
+
+
+ /**
+ * 普通缓存获取
+ *
+ * @param key 键
+ * @return 值
+ */
+ public String getStr(String key) {
+ if(StrUtil.isBlank(key)){
+ return null;
+ }
+ Object value = redisTemplate.opsForValue().get(key);
+ if (value == null) {
+ return null;
+ } else {
+ return String.valueOf(value);
+ }
+ }
+
+ /**
+ * 批量获取
+ *
+ * @param keys
+ * @return
+ */
+ public List multiGet(List keys) {
+ List list = redisTemplate.opsForValue().multiGet(Sets.newHashSet(keys));
+ List resultList = Lists.newArrayList();
+ Optional.ofNullable(list).ifPresent(e-> list.forEach(ele-> Optional.ofNullable(ele).ifPresent(resultList::add)));
+ return resultList;
+ }
+
+ /**
+ * 普通缓存放入
+ *
+ * @param key 键
+ * @param value 值
+ * @return true成功 false失败
+ */
+ public boolean set(String key, Object value) {
+ int attempt = 0;
+ while (attempt < 3) {
+ try {
+ redisTemplate.opsForValue().set(key, value);
+ return true;
+ } catch (Exception e) {
+ attempt++;
+ log.error("Attempt {} failed: {}", attempt, e.getMessage(), e);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 普通缓存放入并设置时间
+ *
+ * @param key 键
+ * @param value 值
+ * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期,注意:这里将会替换原有的时间
+ * @return true成功 false 失败
+ */
+ public boolean set(String key, Object value, long time) {
+ try {
+ if (time > 0) {
+ redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
+ } else {
+ set(key, value);
+ }
+ return true;
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return false;
+ }
+ }
+
+ /**
+ * 普通缓存放入并设置时间
+ *
+ * @param key 键
+ * @param value 值
+ * @param time 时间,注意:这里将会替换原有的时间
+ * @param timeUnit 类型
+ * @return true成功 false 失败
+ */
+ public boolean set(String key, Object value, long time, TimeUnit timeUnit) {
+ try {
+ if (time > 0) {
+ redisTemplate.opsForValue().set(key, value, time, timeUnit);
+ } else {
+ set(key, value);
+ }
+ return true;
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return false;
+ }
+ }
+
+ // ================================Map=================================
+
+ /**
+ * HashGet
+ *
+ * @param key 键 不能为null
+ * @param item 项 不能为null
+ * @return 值
+ */
+ public Object hget(String key, String item) {
+ return redisTemplate.opsForHash().get(key, item);
+ }
+
+ /**
+ * 获取hashKey对应的所有键值
+ *
+ * @param key 键
+ * @return 对应的多个键值
+ */
+ public Map hmget(String key) {
+ return redisTemplate.opsForHash().entries(key);
+
+ }
+
+ /**
+ * HashSet
+ *
+ * @param key 键
+ * @param map 对应多个键值
+ * @return true 成功 false 失败
+ */
+ public boolean hmset(String key, Map map) {
+ try {
+ redisTemplate.opsForHash().putAll(key, map);
+ return true;
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return false;
+ }
+ }
+
+ /**
+ * HashSet
+ *
+ * @param key 键
+ * @param map 对应多个键值
+ * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
+ * @return true成功 false失败
+ */
+ public boolean hmset(String key, Map map, long time) {
+ try {
+ redisTemplate.opsForHash().putAll(key, map);
+ if (time > 0) {
+ expire(key, time);
+ }
+ return true;
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return false;
+ }
+ }
+
+ /**
+ * 向一张hash表中放入数据,如果不存在将创建
+ *
+ * @param key 键
+ * @param item 项
+ * @param value 值
+ * @return true 成功 false失败
+ */
+ public boolean hset(String key, String item, Object value) {
+ try {
+ redisTemplate.opsForHash().put(key, item, value);
+ return true;
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return false;
+ }
+ }
+
+ /**
+ * 向一张hash表中放入数据,如果不存在将创建
+ *
+ * @param key 键
+ * @param item 项
+ * @param value 值
+ * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
+ * @return true 成功 false失败
+ */
+ public boolean hset(String key, String item, Object value, long time) {
+ try {
+ redisTemplate.opsForHash().put(key, item, value);
+ if (time > 0) {
+ expire(key, time);
+ }
+ return true;
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return false;
+ }
+ }
+
+ /**
+ * 删除hash表中的值
+ *
+ * @param key 键 不能为null
+ * @param item 项 可以使多个 不能为null
+ */
+ public void hdel(String key, Object... item) {
+ redisTemplate.opsForHash().delete(key, item);
+ }
+
+ /**
+ * 判断hash表中是否有该项的值
+ *
+ * @param key 键 不能为null
+ * @param item 项 不能为null
+ * @return true 存在 false不存在
+ */
+ public boolean hHasKey(String key, String item) {
+ return redisTemplate.opsForHash().hasKey(key, item);
+ }
+
+ /**
+ * hash递增 如果不存在,就会创建一个 并把新增后的值返回
+ *
+ * @param key 键
+ * @param item 项
+ * @param by 要增加几(大于0)
+ * @return
+ */
+ public double hincr(String key, String item, double by) {
+ return redisTemplate.opsForHash().increment(key, item, by);
+ }
+
+ /**
+ * hash递减
+ *
+ * @param key 键
+ * @param item 项
+ * @param by 要减少记(小于0)
+ * @return
+ */
+ public double hdecr(String key, String item, double by) {
+ return redisTemplate.opsForHash().increment(key, item, -by);
+ }
+
+ // ============================set=============================
+
+ /**
+ * 根据key获取Set中的所有值
+ *
+ * @param key 键
+ * @return
+ */
+ public Set sGet(String key) {
+ try {
+ return redisTemplate.opsForSet().members(key);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return null;
+ }
+ }
+
+ /**
+ * 根据value从一个set中查询,是否存在
+ *
+ * @param key 键
+ * @param value 值
+ * @return true 存在 false不存在
+ */
+ public boolean sHasKey(String key, Object value) {
+ try {
+ return redisTemplate.opsForSet().isMember(key, value);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return false;
+ }
+ }
+
+ /**
+ * 将数据放入set缓存
+ *
+ * @param key 键
+ * @param values 值 可以是多个
+ * @return 成功个数
+ */
+ public long sSet(String key, Object... values) {
+ try {
+ return redisTemplate.opsForSet().add(key, values);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return 0;
+ }
+ }
+
+ /**
+ * 将set数据放入缓存
+ *
+ * @param key 键
+ * @param time 时间(秒) 注意:这里将会替换原有的时间
+ * @param values 值 可以是多个
+ * @return 成功个数
+ */
+ public long sSetAndTime(String key, long time, Object... values) {
+ try {
+ Long count = redisTemplate.opsForSet().add(key, values);
+ if (time > 0) {
+ expire(key, time);
+ }
+ return count;
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return 0;
+ }
+ }
+
+ /**
+ * 获取set缓存的长度
+ *
+ * @param key 键
+ * @return
+ */
+ public long sGetSetSize(String key) {
+ try {
+ return redisTemplate.opsForSet().size(key);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return 0;
+ }
+ }
+
+ /**
+ * 移除值为value的
+ *
+ * @param key 键
+ * @param values 值 可以是多个
+ * @return 移除的个数
+ */
+ public long setRemove(String key, Object... values) {
+ try {
+ Long count = redisTemplate.opsForSet().remove(key, values);
+ return count;
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return 0;
+ }
+ }
+
+ // ===============================list=================================
+
+ /**
+ * 获取list缓存的内容
+ *
+ * @param key 键
+ * @param start 开始
+ * @param end 结束 0 到 -1代表所有值
+ * @return
+ */
+ public List lGet(String key, long start, long end) {
+ try {
+ return redisTemplate.opsForList().range(key, start, end);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return null;
+ }
+ }
+
+ /**
+ * 获取list缓存的长度
+ *
+ * @param key 键
+ * @return
+ */
+ public long lGetListSize(String key) {
+ try {
+ return redisTemplate.opsForList().size(key);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return 0;
+ }
+ }
+
+ /**
+ * 通过索引 获取list中的值
+ *
+ * @param key 键
+ * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
+ * @return
+ */
+ public Object lGetIndex(String key, long index) {
+ try {
+ return redisTemplate.opsForList().index(key, index);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return null;
+ }
+ }
+
+ /**
+ * 将list放入缓存
+ *
+ * @param key 键
+ * @param value 值
+ * @return
+ */
+ public boolean lSet(String key, Object value) {
+ try {
+ redisTemplate.opsForList().rightPush(key, value);
+ return true;
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return false;
+ }
+ }
+
+ /**
+ * 将list放入缓存
+ *
+ * @param key 键
+ * @param value 值
+ * @param time 时间(秒) 注意:这里将会替换原有的时间
+ * @return
+ */
+ public boolean lSet(String key, Object value, long time) {
+ try {
+ redisTemplate.opsForList().rightPush(key, value);
+ if (time > 0) {
+ expire(key, time);
+ }
+ return true;
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return false;
+ }
+ }
+
+ /**
+ * 将list放入缓存
+ *
+ * @param key 键
+ * @param value 值
+ * @return
+ */
+ public boolean lSet(String key, List value) {
+ try {
+ redisTemplate.opsForList().rightPushAll(key, value);
+ return true;
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return false;
+ }
+ }
+
+ /**
+ * 将list放入缓存
+ *
+ * @param key 键
+ * @param value 值
+ * @param time 时间(秒) 注意:这里将会替换原有的时间
+ * @return
+ */
+ public boolean lSet(String key, List value, long time) {
+ try {
+ redisTemplate.opsForList().rightPushAll(key, value);
+ if (time > 0) {
+ expire(key, time);
+ }
+ return true;
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return false;
+ }
+ }
+
+ /**
+ * 根据索引修改list中的某条数据
+ *
+ * @param key 键
+ * @param index 索引
+ * @param value 值
+ * @return /
+ */
+ public boolean lUpdateIndex(String key, long index, Object value) {
+ try {
+ redisTemplate.opsForList().set(key, index, value);
+ return true;
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return false;
+ }
+ }
+
+ /**
+ * 移除N个值为value
+ *
+ * @param key 键
+ * @param count 移除多少个
+ * @param value 值
+ * @return 移除的个数
+ */
+ public long lRemove(String key, long count, Object value) {
+ try {
+ return redisTemplate.opsForList().remove(key, count, value);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return 0;
+ }
+ }
+
+ /**
+ * @param prefix 前缀
+ * @param ids id
+ */
+ public void delByKeys(String prefix, Set ids) {
+ Set keys = new HashSet<>();
+ for (Long id : ids) {
+ keys.addAll(redisTemplate.keys(new StringBuffer(prefix).append(id).toString()));
+ }
+ long count = redisTemplate.delete(keys);
+ }
+
+ // ============================incr=============================
+
+ /**
+ * 递增
+ * @param key
+ * @return
+ */
+ public Long increment(String key) {
+ return redisTemplate.opsForValue().increment(key);
+ }
+
+ /**
+ * 递减
+ * @param key
+ * @return
+ */
+ public Long decrement(String key) {
+ return redisTemplate.opsForValue().decrement(key);
+ }
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/RequestHolder.java b/eladmin-common/src/main/java/me/zhengjie/utils/RequestHolder.java
index 343b451ba..8d90ac48e 100644
--- a/eladmin-common/src/main/java/me/zhengjie/utils/RequestHolder.java
+++ b/eladmin-common/src/main/java/me/zhengjie/utils/RequestHolder.java
@@ -1,8 +1,24 @@
+/*
+ * 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 me.zhengjie.utils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
+import java.util.Objects;
/**
* 获取 HttpServletRequest
@@ -12,6 +28,6 @@
public class RequestHolder {
public static HttpServletRequest getHttpServletRequest() {
- return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
+ return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
}
}
diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/RsaUtils.java b/eladmin-common/src/main/java/me/zhengjie/utils/RsaUtils.java
new file mode 100644
index 000000000..8123ff388
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/utils/RsaUtils.java
@@ -0,0 +1,198 @@
+package me.zhengjie.utils;
+
+import org.apache.commons.codec.binary.Base64;
+import javax.crypto.Cipher;
+import java.io.ByteArrayOutputStream;
+import java.security.*;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+/**
+ * @author https://www.cnblogs.com/nihaorz/p/10690643.html
+ * @description Rsa 工具类,公钥私钥生成,加解密
+ * @date 2020-05-18
+ **/
+public class RsaUtils {
+
+ private static final String SRC = "123456";
+
+ public static void main(String[] args) throws Exception {
+ System.out.println("\n");
+ RsaKeyPair keyPair = generateKeyPair();
+ System.out.println("公钥:" + keyPair.getPublicKey());
+ System.out.println("私钥:" + keyPair.getPrivateKey());
+ System.out.println("\n");
+ test1(keyPair);
+ System.out.println("\n");
+ test2(keyPair);
+ System.out.println("\n");
+ }
+
+ /**
+ * 公钥加密私钥解密
+ */
+ private static void test1(RsaKeyPair keyPair) throws Exception {
+ System.out.println("***************** 公钥加密私钥解密开始 *****************");
+ String text1 = encryptByPublicKey(keyPair.getPublicKey(), RsaUtils.SRC);
+ String text2 = decryptByPrivateKey(keyPair.getPrivateKey(), text1);
+ System.out.println("加密前:" + RsaUtils.SRC);
+ System.out.println("加密后:" + text1);
+ System.out.println("解密后:" + text2);
+ if (RsaUtils.SRC.equals(text2)) {
+ System.out.println("解密字符串和原始字符串一致,解密成功");
+ } else {
+ System.out.println("解密字符串和原始字符串不一致,解密失败");
+ }
+ System.out.println("***************** 公钥加密私钥解密结束 *****************");
+ }
+
+ /**
+ * 私钥加密公钥解密
+ * @throws Exception /
+ */
+ private static void test2(RsaKeyPair keyPair) throws Exception {
+ System.out.println("***************** 私钥加密公钥解密开始 *****************");
+ String text1 = encryptByPrivateKey(keyPair.getPrivateKey(), RsaUtils.SRC);
+ String text2 = decryptByPublicKey(keyPair.getPublicKey(), text1);
+ System.out.println("加密前:" + RsaUtils.SRC);
+ System.out.println("加密后:" + text1);
+ System.out.println("解密后:" + text2);
+ if (RsaUtils.SRC.equals(text2)) {
+ System.out.println("解密字符串和原始字符串一致,解密成功");
+ } else {
+ System.out.println("解密字符串和原始字符串不一致,解密失败");
+ }
+ System.out.println("***************** 私钥加密公钥解密结束 *****************");
+ }
+
+ /**
+ * 公钥解密
+ *
+ * @param publicKeyText 公钥
+ * @param text 待解密的信息
+ * @return /
+ * @throws Exception /
+ */
+ public static String decryptByPublicKey(String publicKeyText, String text) throws Exception {
+ X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText));
+ KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+ PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
+ Cipher cipher = Cipher.getInstance("RSA");
+ cipher.init(Cipher.DECRYPT_MODE, publicKey);
+ byte[] result = doLongerCipherFinal(Cipher.DECRYPT_MODE, cipher, Base64.decodeBase64(text));
+ return new String(result);
+ }
+
+ /**
+ * 私钥加密
+ *
+ * @param privateKeyText 私钥
+ * @param text 待加密的信息
+ * @return /
+ * @throws Exception /
+ */
+ public static String encryptByPrivateKey(String privateKeyText, String text) throws Exception {
+ PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText));
+ KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+ PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
+ Cipher cipher = Cipher.getInstance("RSA");
+ cipher.init(Cipher.ENCRYPT_MODE, privateKey);
+ byte[] result = doLongerCipherFinal(Cipher.ENCRYPT_MODE, cipher, text.getBytes());
+ return Base64.encodeBase64String(result);
+ }
+
+ /**
+ * 私钥解密
+ *
+ * @param privateKeyText 私钥
+ * @param text 待解密的文本
+ * @return /
+ * @throws Exception /
+ */
+ public static String decryptByPrivateKey(String privateKeyText, String text) throws Exception {
+ PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText));
+ KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+ PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5);
+ Cipher cipher = Cipher.getInstance("RSA");
+ cipher.init(Cipher.DECRYPT_MODE, privateKey);
+ byte[] result = doLongerCipherFinal(Cipher.DECRYPT_MODE, cipher, Base64.decodeBase64(text));
+ return new String(result);
+ }
+
+ /**
+ * 公钥加密
+ *
+ * @param publicKeyText 公钥
+ * @param text 待加密的文本
+ * @return /
+ */
+ public static String encryptByPublicKey(String publicKeyText, String text) throws Exception {
+ X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText));
+ KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+ PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec2);
+ Cipher cipher = Cipher.getInstance("RSA");
+ cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+ byte[] result = doLongerCipherFinal(Cipher.ENCRYPT_MODE, cipher, text.getBytes());
+ return Base64.encodeBase64String(result);
+ }
+
+ private static byte[] doLongerCipherFinal(int opMode,Cipher cipher, byte[] source) throws Exception {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ if (opMode == Cipher.DECRYPT_MODE) {
+ out.write(cipher.doFinal(source));
+ } else {
+ int offset = 0;
+ int totalSize = source.length;
+ while (totalSize - offset > 0) {
+ int size = Math.min(cipher.getOutputSize(0) - 11, totalSize - offset);
+ out.write(cipher.doFinal(source, offset, size));
+ offset += size;
+ }
+ }
+ out.close();
+ return out.toByteArray();
+ }
+
+ /**
+ * 构建RSA密钥对
+ *
+ * @return /
+ * @throws NoSuchAlgorithmException /
+ */
+ public static RsaKeyPair generateKeyPair() throws NoSuchAlgorithmException {
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+ keyPairGenerator.initialize(1024);
+ KeyPair keyPair = keyPairGenerator.generateKeyPair();
+ RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
+ RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
+ String publicKeyString = Base64.encodeBase64String(rsaPublicKey.getEncoded());
+ String privateKeyString = Base64.encodeBase64String(rsaPrivateKey.getEncoded());
+ return new RsaKeyPair(publicKeyString, privateKeyString);
+ }
+
+
+ /**
+ * RSA密钥对对象
+ */
+ public static class RsaKeyPair {
+
+ private final String publicKey;
+ private final String privateKey;
+
+ public RsaKeyPair(String publicKey, String privateKey) {
+ this.publicKey = publicKey;
+ this.privateKey = privateKey;
+ }
+
+ public String getPublicKey() {
+ return publicKey;
+ }
+
+ public String getPrivateKey() {
+ return privateKey;
+ }
+
+ }
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/SecurityUtils.java b/eladmin-common/src/main/java/me/zhengjie/utils/SecurityUtils.java
index e040c111b..1c1f81341 100644
--- a/eladmin-common/src/main/java/me/zhengjie/utils/SecurityUtils.java
+++ b/eladmin-common/src/main/java/me/zhengjie/utils/SecurityUtils.java
@@ -1,44 +1,145 @@
+/*
+ * 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 me.zhengjie.utils;
-import cn.hutool.json.JSONObject;
-import me.zhengjie.exception.BadRequestException;
-import org.springframework.http.HttpStatus;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.jwt.JWT;
+import cn.hutool.jwt.JWTUtil;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONArray;
+import com.alibaba.fastjson2.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import me.zhengjie.utils.enums.DataScopeEnum;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.List;
+import java.util.Objects;
/**
* 获取当前登录的用户
* @author Zheng Jie
* @date 2019-01-17
*/
+@Slf4j
+@Component
public class SecurityUtils {
- public static UserDetails getUserDetails() {
- UserDetails userDetails = null;
- try {
- userDetails = (UserDetails) org.springframework.security.core.context.SecurityContextHolder.getContext().getAuthentication().getPrincipal();
- } catch (Exception e) {
- throw new BadRequestException(HttpStatus.UNAUTHORIZED, "登录状态过期");
+ public static String header;
+
+ public static String tokenStartWith;
+
+ @Value("${jwt.header}")
+ public void setHeader(String header) {
+ SecurityUtils.header = header;
+ }
+
+ @Value("${jwt.token-start-with}")
+ public void setTokenStartWith(String tokenStartWith) {
+ SecurityUtils.tokenStartWith = tokenStartWith;
+ }
+
+ /**
+ * 获取当前登录的用户
+ * @return UserDetails
+ */
+ public static UserDetails getCurrentUser() {
+ UserDetailsService userDetailsService = SpringBeanHolder.getBean(UserDetailsService.class);
+ return userDetailsService.loadUserByUsername(getCurrentUsername());
+ }
+
+ /**
+ * 获取当前用户的数据权限
+ * @return /
+ */
+ public static List getCurrentUserDataScope(){
+ UserDetails userDetails = getCurrentUser();
+ // 将 Java 对象转换为 JSONObject 对象
+ JSONObject jsonObject = (JSONObject) JSON.toJSON(userDetails);
+ JSONArray jsonArray = jsonObject.getJSONArray("dataScopes");
+ return JSON.parseArray(jsonArray.toJSONString(), Long.class);
+ }
+
+ /**
+ * 获取数据权限级别
+ * @return 级别
+ */
+ public static String getDataScopeType() {
+ List dataScopes = getCurrentUserDataScope();
+ if(CollUtil.isEmpty(dataScopes)){
+ return "";
}
- return userDetails;
+ return DataScopeEnum.ALL.getValue();
+ }
+
+ /**
+ * 获取用户ID
+ * @return 系统用户ID
+ */
+ public static Long getCurrentUserId() {
+ return getCurrentUserId(getToken());
+ }
+
+ /**
+ * 获取用户ID
+ * @return 系统用户ID
+ */
+ public static Long getCurrentUserId(String token) {
+ JWT jwt = JWTUtil.parseToken(token);
+ return Long.valueOf(jwt.getPayload("userId").toString());
+ }
+
+ /**
+ * 获取系统用户名称
+ *
+ * @return 系统用户名称
+ */
+ public static String getCurrentUsername() {
+ return getCurrentUsername(getToken());
}
/**
* 获取系统用户名称
+ *
* @return 系统用户名称
*/
- public static String getUsername(){
- Object obj = getUserDetails();
- JSONObject json = new JSONObject(obj);
- return json.get("username", String.class);
+ public static String getCurrentUsername(String token) {
+ JWT jwt = JWTUtil.parseToken(token);
+ return jwt.getPayload("sub").toString();
}
/**
- * 获取系统用户id
- * @return 系统用户id
+ * 获取Token
+ * @return /
*/
- public static Long getUserId(){
- Object obj = getUserDetails();
- JSONObject json = new JSONObject(obj);
- return json.get("id", Long.class);
+ public static String getToken() {
+ HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder
+ .getRequestAttributes())).getRequest();
+ String bearerToken = request.getHeader(header);
+ if (bearerToken != null && bearerToken.startsWith(tokenStartWith)) {
+ // 去掉令牌前缀
+ return bearerToken.replace(tokenStartWith, "");
+ } else {
+ log.debug("非法Token:{}", bearerToken);
+ }
+ return null;
}
}
diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/SpringBeanHolder.java b/eladmin-common/src/main/java/me/zhengjie/utils/SpringBeanHolder.java
new file mode 100644
index 000000000..ef7ac7b35
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/utils/SpringBeanHolder.java
@@ -0,0 +1,172 @@
+/*
+ * 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 me.zhengjie.utils;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.core.env.Environment;
+import org.springframework.stereotype.Service;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author Jie
+ * @date 2019-01-07
+ */
+@Slf4j
+@SuppressWarnings({"unchecked","all"})
+public class SpringBeanHolder implements ApplicationContextAware, DisposableBean {
+
+ private static ApplicationContext applicationContext = null;
+ private static final List CALL_BACKS = new ArrayList<>();
+ private static boolean addCallback = true;
+
+ /**
+ * 针对 某些初始化方法,在SpringContextHolder 未初始化时 提交回调方法。
+ * 在SpringContextHolder 初始化后,进行回调使用
+ *
+ * @param callBack 回调函数
+ */
+ public synchronized static void addCallBacks(SpringBeanHolder.CallBack callBack) {
+ if (addCallback) {
+ SpringBeanHolder.CALL_BACKS.add(callBack);
+ } else {
+ log.warn("CallBack:{} 已无法添加!立即执行", callBack.getCallBackName());
+ callBack.executor();
+ }
+ }
+
+ /**
+ * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
+ */
+ public static T getBean(String name) {
+ assertContextInjected();
+ return (T) applicationContext.getBean(name);
+ }
+
+ /**
+ * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
+ */
+ public static T getBean(Class requiredType) {
+ assertContextInjected();
+ return applicationContext.getBean(requiredType);
+ }
+
+ /**
+ * 获取SpringBoot 配置信息
+ *
+ * @param property 属性key
+ * @param defaultValue 默认值
+ * @param requiredType 返回类型
+ * @return /
+ */
+ public static T getProperties(String property, T defaultValue, Class requiredType) {
+ T result = defaultValue;
+ try {
+ result = getBean(Environment.class).getProperty(property, requiredType);
+ } catch (Exception ignored) {}
+ return result;
+ }
+
+ /**
+ * 获取SpringBoot 配置信息
+ *
+ * @param property 属性key
+ * @return /
+ */
+ public static String getProperties(String property) {
+ return getProperties(property, null, String.class);
+ }
+
+ /**
+ * 获取SpringBoot 配置信息
+ *
+ * @param property 属性key
+ * @param requiredType 返回类型
+ * @return /
+ */
+ public static T getProperties(String property, Class requiredType) {
+ return getProperties(property, null, requiredType);
+ }
+
+ /**
+ * 检查ApplicationContext不为空.
+ */
+ private static void assertContextInjected() {
+ if (applicationContext == null) {
+ throw new IllegalStateException("applicaitonContext属性未注入, 请在applicationContext" +
+ ".xml中定义SpringContextHolder或在SpringBoot启动类中注册SpringContextHolder.");
+ }
+ }
+
+ /**
+ * 清除SpringContextHolder中的ApplicationContext为Null.
+ */
+ private static void clearHolder() {
+ log.debug("清除SpringContextHolder中的ApplicationContext:"
+ + applicationContext);
+ applicationContext = null;
+ }
+
+ @Override
+ public void destroy() {
+ SpringBeanHolder.clearHolder();
+ }
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ if (SpringBeanHolder.applicationContext != null) {
+ log.warn("SpringContextHolder中的ApplicationContext被覆盖, 原有ApplicationContext为:" + SpringBeanHolder.applicationContext);
+ }
+ SpringBeanHolder.applicationContext = applicationContext;
+ if (addCallback) {
+ for (SpringBeanHolder.CallBack callBack : SpringBeanHolder.CALL_BACKS) {
+ callBack.executor();
+ }
+ CALL_BACKS.clear();
+ }
+ SpringBeanHolder.addCallback = false;
+ }
+
+ /**
+ * 获取 @Service 的所有 bean 名称
+ * @return /
+ */
+ public static List getAllServiceBeanName() {
+ return new ArrayList<>(Arrays.asList(applicationContext
+ .getBeanNamesForAnnotation(Service.class)));
+ }
+
+ interface CallBack {
+
+ /**
+ * 回调执行方法
+ */
+ void executor();
+
+ /**
+ * 本回调任务名称
+ * @return /
+ */
+ default String getCallBackName() {
+ return Thread.currentThread().getId() + ":" + this.getClass().getName();
+ }
+ }
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/SpringContextHolder.java b/eladmin-common/src/main/java/me/zhengjie/utils/SpringContextHolder.java
deleted file mode 100644
index e4bfcbbc4..000000000
--- a/eladmin-common/src/main/java/me/zhengjie/utils/SpringContextHolder.java
+++ /dev/null
@@ -1,73 +0,0 @@
-package me.zhengjie.utils;
-
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.BeansException;
-import org.springframework.beans.factory.DisposableBean;
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.ApplicationContextAware;
-
-/**
- * @author
- * @date 2019-01-07
- */
-@Slf4j
-public class SpringContextHolder implements ApplicationContextAware, DisposableBean {
-
- private static ApplicationContext applicationContext = null;
-
- /**
- * 取得存储在静态变量中的ApplicationContext.
- */
- public static ApplicationContext getApplicationContext() {
- assertContextInjected();
- return applicationContext;
- }
-
- /**
- * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
- */
- public static T getBean(String name) {
- assertContextInjected();
- return (T) applicationContext.getBean(name);
- }
-
- /**
- * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
- */
- public static T getBean(Class requiredType) {
- assertContextInjected();
- return applicationContext.getBean(requiredType);
- }
-
- /**
- * 检查ApplicationContext不为空.
- */
- private static void assertContextInjected() {
- if (applicationContext == null) {
- throw new IllegalStateException("applicaitonContext属性未注入, 请在applicationContext" +
- ".xml中定义SpringContextHolder或在SpringBoot启动类中注册SpringContextHolder.");
- }
- }
-
- /**
- * 清除SpringContextHolder中的ApplicationContext为Null.
- */
- public static void clearHolder() {
- log.debug("清除SpringContextHolder中的ApplicationContext:"
- + applicationContext);
- applicationContext = null;
- }
-
- @Override
- public void destroy() throws Exception {
- SpringContextHolder.clearHolder();
- }
-
- @Override
- public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
- if (SpringContextHolder.applicationContext != null) {
- log.warn("SpringContextHolder中的ApplicationContext被覆盖, 原有ApplicationContext为:" + SpringContextHolder.applicationContext);
- }
- SpringContextHolder.applicationContext = applicationContext;
- }
-}
diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/StringUtils.java b/eladmin-common/src/main/java/me/zhengjie/utils/StringUtils.java
index fd2647d04..c6f1d9585 100644
--- a/eladmin-common/src/main/java/me/zhengjie/utils/StringUtils.java
+++ b/eladmin-common/src/main/java/me/zhengjie/utils/StringUtils.java
@@ -1,42 +1,47 @@
+/*
+ * 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 me.zhengjie.utils;
-import cn.hutool.core.io.resource.ClassPathResource;
-import org.lionsoul.ip2region.DataBlock;
-import org.lionsoul.ip2region.DbConfig;
-import org.lionsoul.ip2region.DbSearcher;
-
+import cn.hutool.http.useragent.UserAgent;
+import cn.hutool.http.useragent.UserAgentUtil;
+import lombok.extern.slf4j.Slf4j;
+import net.dreamlu.mica.ip2region.core.Ip2regionSearcher;
+import net.dreamlu.mica.ip2region.core.IpInfo;
import javax.servlet.http.HttpServletRequest;
-import java.io.File;
-import java.io.UnsupportedEncodingException;
-import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.UnknownHostException;
import java.util.Calendar;
import java.util.Date;
+import java.util.Enumeration;
/**
+ * @author Zheng Jie
* 字符串工具类, 继承org.apache.commons.lang3.StringUtils类
*/
+@Slf4j
public class StringUtils extends org.apache.commons.lang3.StringUtils {
private static final char SEPARATOR = '_';
- private static final String CHARSET_NAME = "UTF-8";
+ private static final String UNKNOWN = "unknown";
/**
- * 是否包含字符串
- *
- * @param str 验证字符串
- * @param strs 字符串组
- * @return 包含返回true
+ * 注入bean
*/
- public static boolean inString(String str, String... strs) {
- if (str != null) {
- for (String s : strs) {
- if (str.equals(trim(s))) {
- return true;
- }
- }
- }
- return false;
- }
+ private final static Ip2regionSearcher IP_SEARCHER = SpringBeanHolder.getBean(Ip2regionSearcher.class);
/**
* 驼峰命名法工具
@@ -92,7 +97,7 @@ public static String toCapitalizeCamelCase(String s) {
* toCapitalizeCamelCase("hello_world") == "HelloWorld"
* toUnderScoreCase("helloWorld") = "hello_world"
*/
- public static String toUnderScoreCase(String s) {
+ static String toUnderScoreCase(String s) {
if (s == null) {
return null;
}
@@ -125,77 +130,106 @@ public static String toUnderScoreCase(String s) {
/**
* 获取ip地址
- * @param request
- * @return
*/
- public static String getIP(HttpServletRequest request) {
+ public static String getIp(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
- if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+ if (ip == null || ip.isEmpty() || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
- if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+ if (ip == null || ip.isEmpty() || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
- if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+ if (ip == null || ip.isEmpty() || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
- String[] ips = ip.split(",");
- return "0:0:0:0:0:0:0:1".equals(ips[0])?"127.0.0.1":ips[0];
+ String comma = ",";
+ String localhost = "127.0.0.1";
+ if (ip.contains(comma)) {
+ ip = ip.split(",")[0];
+ }
+ if (localhost.equals(ip)) {
+ // 获取本机真正的ip地址
+ try {
+ ip = InetAddress.getLocalHost().getHostAddress();
+ } catch (UnknownHostException e) {
+ log.error(e.getMessage(), e);
+ }
+ }
+ return ip;
}
/**
* 根据ip获取详细地址
- * @param ip
- * @return
*/
public static String getCityInfo(String ip) {
- try {
- String path = "ip2region/ip2region.db";
- String name = "ip2region.db";
- int algorithm = DbSearcher.BTREE_ALGORITHM;
- DbConfig config = new DbConfig();
- File file = FileUtil.inputStreamToFile(new ClassPathResource(path).getStream(), name);
- DbSearcher searcher = new DbSearcher(config, file.getPath());
- Method method = null;
- switch (algorithm) {
- case DbSearcher.BTREE_ALGORITHM:
- method = searcher.getClass().getMethod("btreeSearch", String.class);
- break;
- case DbSearcher.BINARY_ALGORITHM:
- method = searcher.getClass().getMethod("binarySearch", String.class);
- break;
- case DbSearcher.MEMORY_ALGORITYM:
- method = searcher.getClass().getMethod("memorySearch", String.class);
- break;
- default:
- method = searcher.getClass().getMethod("memorySearch", String.class);
- break;
- }
- DataBlock dataBlock = null;
- dataBlock = (DataBlock) method.invoke(searcher, ip);
- String address = dataBlock.getRegion().replace("0|","");
- if(address.charAt(address.length()-1) == '|'){
- address = address.substring(0,address.length() - 1);
- }
- return address.equals(ElAdminConstant.REGION)?"内网IP":address;
- } catch (Exception e) {
- e.printStackTrace();
+ IpInfo ipInfo = IP_SEARCHER.memorySearch(ip);
+ if(ipInfo != null){
+ return ipInfo.getAddress();
}
- return "";
+ return null;
+ }
+
+ /**
+ * 获取浏览器
+ */
+ public static String getBrowser(HttpServletRequest request) {
+ UserAgent ua = UserAgentUtil.parse(request.getHeader("User-Agent"));
+ String browser = ua.getBrowser().toString() + " " + ua.getVersion();
+ return browser.replace(".0.0.0","");
}
/**
* 获得当天是周几
*/
- public static String getWeekDay(){
+ public static String getWeekDay() {
String[] weekDays = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
Calendar cal = Calendar.getInstance();
cal.setTime(new Date());
int w = cal.get(Calendar.DAY_OF_WEEK) - 1;
- if (w < 0){
+ if (w < 0) {
w = 0;
}
return weekDays[w];
}
-}
\ No newline at end of file
+
+ /**
+ * 获取当前机器的IP
+ *
+ * @return /
+ */
+ public static String getLocalIp() {
+ try {
+ InetAddress candidateAddress = null;
+ // 遍历所有的网络接口
+ for (Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); interfaces.hasMoreElements();) {
+ NetworkInterface anInterface = interfaces.nextElement();
+ // 在所有的接口下再遍历IP
+ for (Enumeration inetAddresses = anInterface.getInetAddresses(); inetAddresses.hasMoreElements();) {
+ InetAddress inetAddr = inetAddresses.nextElement();
+ // 排除loopback类型地址
+ if (!inetAddr.isLoopbackAddress()) {
+ if (inetAddr.isSiteLocalAddress()) {
+ // 如果是site-local地址,就是它了
+ return inetAddr.getHostAddress();
+ } else if (candidateAddress == null) {
+ // site-local类型的地址未被发现,先记录候选地址
+ candidateAddress = inetAddr;
+ }
+ }
+ }
+ }
+ if (candidateAddress != null) {
+ return candidateAddress.getHostAddress();
+ }
+ // 如果没有发现 non-loopback地址.只能用最次选的方案
+ InetAddress jdkSuppliedAddress = InetAddress.getLocalHost();
+ if (jdkSuppliedAddress == null) {
+ return "";
+ }
+ return jdkSuppliedAddress.getHostAddress();
+ } catch (Exception e) {
+ return "";
+ }
+ }
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/ThrowableUtil.java b/eladmin-common/src/main/java/me/zhengjie/utils/ThrowableUtil.java
index 3725a1a19..59f1d98f4 100644
--- a/eladmin-common/src/main/java/me/zhengjie/utils/ThrowableUtil.java
+++ b/eladmin-common/src/main/java/me/zhengjie/utils/ThrowableUtil.java
@@ -1,28 +1,37 @@
+/*
+ * 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 me.zhengjie.utils;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
- * 异常工具
+ * 异常工具 2019-01-06
* @author Zheng Jie
- * @date 2019-01-06
*/
public class ThrowableUtil {
/**
* 获取堆栈信息
- * @param throwable
- * @return
*/
public static String getStackTrace(Throwable throwable){
StringWriter sw = new StringWriter();
- PrintWriter pw = new PrintWriter(sw);
- try {
+ try (PrintWriter pw = new PrintWriter(sw)) {
throwable.printStackTrace(pw);
return sw.toString();
- } finally {
- pw.close();
}
}
}
diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/ValidationUtil.java b/eladmin-common/src/main/java/me/zhengjie/utils/ValidationUtil.java
index fa8e7baf7..2e82d6c4a 100644
--- a/eladmin-common/src/main/java/me/zhengjie/utils/ValidationUtil.java
+++ b/eladmin-common/src/main/java/me/zhengjie/utils/ValidationUtil.java
@@ -1,38 +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 me.zhengjie.utils;
+import cn.hutool.core.lang.Validator;
+import cn.hutool.core.util.ObjectUtil;
import me.zhengjie.exception.BadRequestException;
-import java.util.Optional;
/**
* 验证工具
+ *
* @author Zheng Jie
* @date 2018-11-23
*/
-public class ValidationUtil{
+public class ValidationUtil {
/**
* 验证空
- * @param optional
*/
- public static void isNull(Optional optional, String entity,String parameter , Object value){
- if(!optional.isPresent()){
- String msg = entity
- + " 不存在 "
- +"{ "+ parameter +":"+ value.toString() +" }";
+ public static void isNull(Object obj, String entity, String parameter , Object value){
+ if(ObjectUtil.isNull(obj)){
+ String msg = entity + " 不存在: "+ parameter +" is "+ value;
throw new BadRequestException(msg);
}
}
- /**
- * 验证是否为邮箱
- * @param string
- * @return
- */
- public static boolean isEmail(String string) {
- if (string == null){
- return false;
- }
- String regEx1 = "^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$";
- return string.matches(regEx1);
- }
+ /**
+ * 验证是否为邮箱
+ */
+ public static boolean isEmail(String email) {
+ return Validator.isEmail(email);
+ }
}
diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/enums/CodeBiEnum.java b/eladmin-common/src/main/java/me/zhengjie/utils/enums/CodeBiEnum.java
new file mode 100644
index 000000000..2c6fbb6bf
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/utils/enums/CodeBiEnum.java
@@ -0,0 +1,50 @@
+/*
+ * 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 me.zhengjie.utils.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ *
+ * 验证码业务场景
+ *
+ * @author Zheng Jie
+ * @date 2020-05-02
+ */
+@Getter
+@AllArgsConstructor
+public enum CodeBiEnum {
+
+ /* 旧邮箱修改邮箱 */
+ ONE(1, "旧邮箱修改邮箱"),
+
+ /* 通过邮箱修改密码 */
+ TWO(2, "通过邮箱修改密码");
+
+ private final Integer code;
+ private final String description;
+
+ public static CodeBiEnum find(Integer code) {
+ for (CodeBiEnum value : CodeBiEnum.values()) {
+ if (value.getCode().equals(code)) {
+ return value;
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/enums/CodeEnum.java b/eladmin-common/src/main/java/me/zhengjie/utils/enums/CodeEnum.java
new file mode 100644
index 000000000..3993993f1
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/utils/enums/CodeEnum.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 me.zhengjie.utils.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ *
+ * 验证码业务场景对应的 Redis 中的 key
+ *
+ * @author Zheng Jie
+ * @date 2020-05-02
+ */
+@Getter
+@AllArgsConstructor
+public enum CodeEnum {
+
+ /* 通过手机号码重置邮箱 */
+ PHONE_RESET_EMAIL_CODE("phone_reset_email_code_", "通过手机号码重置邮箱"),
+
+ /* 通过旧邮箱重置邮箱 */
+ EMAIL_RESET_EMAIL_CODE("email_reset_email_code_", "通过旧邮箱重置邮箱"),
+
+ /* 通过手机号码重置密码 */
+ PHONE_RESET_PWD_CODE("phone_reset_pwd_code_", "通过手机号码重置密码"),
+
+ /* 通过邮箱重置密码 */
+ EMAIL_RESET_PWD_CODE("email_reset_pwd_code_", "通过邮箱重置密码");
+
+ private final String key;
+ private final String description;
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/enums/DataScopeEnum.java b/eladmin-common/src/main/java/me/zhengjie/utils/enums/DataScopeEnum.java
new file mode 100644
index 000000000..08e41f66d
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/utils/enums/DataScopeEnum.java
@@ -0,0 +1,53 @@
+/*
+ * 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 me.zhengjie.utils.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ *
+ * 数据权限枚举
+ *
+ * @author Zheng Jie
+ * @date 2020-05-07
+ */
+@Getter
+@AllArgsConstructor
+public enum DataScopeEnum {
+
+ /* 全部的数据权限 */
+ ALL("全部", "全部的数据权限"),
+
+ /* 自己部门的数据权限 */
+ THIS_LEVEL("本级", "自己部门的数据权限"),
+
+ /* 自定义的数据权限 */
+ CUSTOMIZE("自定义", "自定义的数据权限");
+
+ private final String value;
+ private final String description;
+
+ public static DataScopeEnum find(String val) {
+ for (DataScopeEnum dataScopeEnum : DataScopeEnum.values()) {
+ if (dataScopeEnum.getValue().equals(val)) {
+ return dataScopeEnum;
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/enums/RequestMethodEnum.java b/eladmin-common/src/main/java/me/zhengjie/utils/enums/RequestMethodEnum.java
new file mode 100644
index 000000000..5d9448350
--- /dev/null
+++ b/eladmin-common/src/main/java/me/zhengjie/utils/enums/RequestMethodEnum.java
@@ -0,0 +1,74 @@
+/*
+ * 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 me.zhengjie.utils.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author Zheng Jie
+ * @website https://eladmin.vip
+ * @description
+ * @date 2020-06-10
+ **/
+@Getter
+@AllArgsConstructor
+public enum RequestMethodEnum {
+
+ /**
+ * 搜寻 @AnonymousGetMapping
+ */
+ GET("GET"),
+
+ /**
+ * 搜寻 @AnonymousPostMapping
+ */
+ POST("POST"),
+
+ /**
+ * 搜寻 @AnonymousPutMapping
+ */
+ PUT("PUT"),
+
+ /**
+ * 搜寻 @AnonymousPatchMapping
+ */
+ PATCH("PATCH"),
+
+ /**
+ * 搜寻 @AnonymousDeleteMapping
+ */
+ DELETE("DELETE"),
+
+ /**
+ * 否则就是所有 Request 接口都放行
+ */
+ ALL("All");
+
+ /**
+ * Request 类型
+ */
+ private final String type;
+
+ public static RequestMethodEnum find(String type) {
+ for (RequestMethodEnum value : RequestMethodEnum.values()) {
+ if (value.getType().equals(type)) {
+ return value;
+ }
+ }
+ return ALL;
+ }
+}
diff --git a/eladmin-common/src/test/java/me/zhengjie/utils/DateUtilsTest.java b/eladmin-common/src/test/java/me/zhengjie/utils/DateUtilsTest.java
new file mode 100644
index 000000000..dfe01e09e
--- /dev/null
+++ b/eladmin-common/src/test/java/me/zhengjie/utils/DateUtilsTest.java
@@ -0,0 +1,26 @@
+package me.zhengjie.utils;
+
+import org.junit.jupiter.api.Test;
+
+import java.time.LocalDateTime;
+import java.util.Date;
+
+public class DateUtilsTest {
+ @Test
+ public void test1() {
+ long l = System.currentTimeMillis() / 1000;
+ LocalDateTime localDateTime = DateUtil.fromTimeStamp(l);
+ System.out.print(DateUtil.localDateTimeFormatyMdHms(localDateTime));
+ }
+
+ @Test
+ public void test2() {
+ LocalDateTime now = LocalDateTime.now();
+ System.out.println(DateUtil.localDateTimeFormatyMdHms(now));
+ Date date = DateUtil.toDate(now);
+ LocalDateTime localDateTime = DateUtil.toLocalDateTime(date);
+ System.out.println(DateUtil.localDateTimeFormatyMdHms(localDateTime));
+ LocalDateTime localDateTime1 = DateUtil.fromTimeStamp(date.getTime() / 1000);
+ System.out.println(DateUtil.localDateTimeFormatyMdHms(localDateTime1));
+ }
+}
diff --git a/eladmin-common/src/test/java/me/zhengjie/utils/EncryptUtilsTest.java b/eladmin-common/src/test/java/me/zhengjie/utils/EncryptUtilsTest.java
index f909d9dbd..3ec737523 100644
--- a/eladmin-common/src/test/java/me/zhengjie/utils/EncryptUtilsTest.java
+++ b/eladmin-common/src/test/java/me/zhengjie/utils/EncryptUtilsTest.java
@@ -1,8 +1,9 @@
package me.zhengjie.utils;
-import org.junit.Test;
-import static org.junit.Assert.*;
+import org.junit.jupiter.api.Test;
+
import static me.zhengjie.utils.EncryptUtils.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
public class EncryptUtilsTest {
diff --git a/eladmin-common/src/test/java/me/zhengjie/utils/FileUtilTest.java b/eladmin-common/src/test/java/me/zhengjie/utils/FileUtilTest.java
index f069c1587..48e06bd7d 100644
--- a/eladmin-common/src/test/java/me/zhengjie/utils/FileUtilTest.java
+++ b/eladmin-common/src/test/java/me/zhengjie/utils/FileUtilTest.java
@@ -1,10 +1,10 @@
package me.zhengjie.utils;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockMultipartFile;
-import static org.junit.Assert.*;
import static me.zhengjie.utils.FileUtil.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
public class FileUtilTest {
diff --git a/eladmin-common/src/test/java/me/zhengjie/utils/StringUtilsTest.java b/eladmin-common/src/test/java/me/zhengjie/utils/StringUtilsTest.java
index f18fb1d92..ffb2cf88b 100644
--- a/eladmin-common/src/test/java/me/zhengjie/utils/StringUtilsTest.java
+++ b/eladmin-common/src/test/java/me/zhengjie/utils/StringUtilsTest.java
@@ -1,22 +1,21 @@
package me.zhengjie.utils;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import java.text.SimpleDateFormat;
-import java.util.*;
+import java.util.Date;
-import static me.zhengjie.utils.StringUtils.*;
-import static org.junit.Assert.*;
+import static me.zhengjie.utils.StringUtils.getIp;
+import static me.zhengjie.utils.StringUtils.getWeekDay;
+import static me.zhengjie.utils.StringUtils.toCamelCase;
+import static me.zhengjie.utils.StringUtils.toCapitalizeCamelCase;
+import static me.zhengjie.utils.StringUtils.toUnderScoreCase;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
public class StringUtilsTest {
- @Test
- public void testInString() {
- assertTrue(inString("?", "?"));
- assertFalse(inString("?", new String[]{}));
- }
-
@Test
public void testToCamelCase() {
assertNull(toCamelCase(null));
@@ -44,6 +43,6 @@ public void testGetWeekDay() {
@Test
public void testGetIP() {
- assertEquals("127.0.0.1", getIP(new MockHttpServletRequest()));
+ assertEquals("127.0.0.1", getIp(new MockHttpServletRequest()));
}
-}
\ No newline at end of file
+}
diff --git a/eladmin-generator/pom.xml b/eladmin-generator/pom.xml
index bf69a3a4b..46b8e757b 100644
--- a/eladmin-generator/pom.xml
+++ b/eladmin-generator/pom.xml
@@ -1,25 +1,24 @@
-
+
eladmin
me.zhengjie
- 2.1
+ 2.7
4.0.0
eladmin-generator
+ 代码生成模块
- 1.9
+ 1.10
me.zhengjie
eladmin-common
- 2.1
+ 2.7
diff --git a/eladmin-generator/src/main/java/me/zhengjie/domain/ColumnInfo.java b/eladmin-generator/src/main/java/me/zhengjie/domain/ColumnInfo.java
new file mode 100644
index 000000000..fddc19935
--- /dev/null
+++ b/eladmin-generator/src/main/java/me/zhengjie/domain/ColumnInfo.java
@@ -0,0 +1,97 @@
+/*
+ * 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 me.zhengjie.domain;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import me.zhengjie.utils.GenUtil;
+import javax.persistence.*;
+import java.io.Serializable;
+
+/**
+ * 列的数据信息
+ * @author Zheng Jie
+ * @date 2019-01-02
+ */
+@Getter
+@Setter
+@Entity
+@NoArgsConstructor
+@Table(name = "code_column")
+public class ColumnInfo implements Serializable {
+
+ @Id
+ @Column(name = "column_id")
+ @ApiModelProperty(value = "ID", hidden = true)
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ApiModelProperty(value = "表名")
+ private String tableName;
+
+ @ApiModelProperty(value = "数据库字段名称")
+ private String columnName;
+
+ @ApiModelProperty(value = "数据库字段类型")
+ private String columnType;
+
+ @ApiModelProperty(value = "数据库字段键类型")
+ private String keyType;
+
+ @ApiModelProperty(value = "字段额外的参数")
+ private String extra;
+
+ @ApiModelProperty(value = "数据库字段描述")
+ private String remark;
+
+ @ApiModelProperty(value = "是否必填")
+ private Boolean notNull;
+
+ @ApiModelProperty(value = "是否在列表显示")
+ private Boolean listShow;
+
+ @ApiModelProperty(value = "是否表单显示")
+ private Boolean formShow;
+
+ @ApiModelProperty(value = "表单类型")
+ private String formType;
+
+ @ApiModelProperty(value = "查询 1:模糊 2:精确")
+ private String queryType;
+
+ @ApiModelProperty(value = "字典名称")
+ private String dictName;
+
+ @ApiModelProperty(value = "日期注解")
+ private String dateAnnotation;
+
+ public ColumnInfo(String tableName, String columnName, Boolean notNull, String columnType, String remark, String keyType, String extra) {
+ this.tableName = tableName;
+ this.columnName = columnName;
+ this.columnType = columnType;
+ this.keyType = keyType;
+ this.extra = extra;
+ this.notNull = notNull;
+ if(GenUtil.PK.equalsIgnoreCase(keyType) && GenUtil.EXTRA.equalsIgnoreCase(extra)){
+ this.notNull = false;
+ }
+ this.remark = remark;
+ this.listShow = true;
+ this.formShow = true;
+ }
+}
diff --git a/eladmin-generator/src/main/java/me/zhengjie/domain/GenConfig.java b/eladmin-generator/src/main/java/me/zhengjie/domain/GenConfig.java
index 1f79a9110..47e87d318 100644
--- a/eladmin-generator/src/main/java/me/zhengjie/domain/GenConfig.java
+++ b/eladmin-generator/src/main/java/me/zhengjie/domain/GenConfig.java
@@ -1,41 +1,78 @@
+/*
+ * 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 me.zhengjie.domain;
-import lombok.Data;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
import javax.persistence.*;
+import javax.validation.constraints.NotBlank;
+import java.io.Serializable;
/**
* 代码生成配置
* @author Zheng Jie
* @date 2019-01-03
*/
-@Data
+@Getter
+@Setter
@Entity
-@Table(name = "gen_config")
-public class GenConfig {
+@NoArgsConstructor
+@Table(name = "code_config")
+public class GenConfig implements Serializable {
+
+ public GenConfig(String tableName) {
+ this.tableName = tableName;
+ }
@Id
+ @Column(name = "config_id")
+ @ApiModelProperty(value = "ID", hidden = true)
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
- /** 包路径 **/
+ @NotBlank
+ @ApiModelProperty(value = "表名")
+ private String tableName;
+
+ @ApiModelProperty(value = "接口名称")
+ private String apiAlias;
+
+ @NotBlank
+ @ApiModelProperty(value = "包路径")
private String pack;
- /** 模块名 **/
- @Column(name = "module_name")
+ @NotBlank
+ @ApiModelProperty(value = "模块名")
private String moduleName;
- /** 前端文件路径 **/
+ @NotBlank
+ @ApiModelProperty(value = "前端文件路径")
private String path;
- /** 前端文件路径 **/
- @Column(name = "api_path")
+ @ApiModelProperty(value = "前端文件路径")
private String apiPath;
- /** 作者 **/
+ @ApiModelProperty(value = "作者")
private String author;
- /** 表前缀 **/
+ @ApiModelProperty(value = "表前缀")
private String prefix;
- /** 是否覆盖 **/
- private Boolean cover;
+ @ApiModelProperty(value = "是否覆盖")
+ private Boolean cover = false;
}
diff --git a/eladmin-generator/src/main/java/me/zhengjie/domain/vo/ColumnInfo.java b/eladmin-generator/src/main/java/me/zhengjie/domain/vo/ColumnInfo.java
deleted file mode 100644
index 5a8475a2b..000000000
--- a/eladmin-generator/src/main/java/me/zhengjie/domain/vo/ColumnInfo.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package me.zhengjie.domain.vo;
-
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-/**
- * 列的数据信息
- * @author Zheng Jie
- * @date 2019-01-02
- */
-@Data
-@AllArgsConstructor
-@NoArgsConstructor
-public class ColumnInfo {
-
- /** 数据库字段名称 **/
- private Object columnName;
-
- /** 允许空值 **/
- private Object isNullable;
-
- /** 数据库字段类型 **/
- private Object columnType;
-
- /** 数据库字段注释 **/
- private Object columnComment;
-
- /** 数据库字段键类型 **/
- private Object columnKey;
-
- /** 额外的参数 **/
- private Object extra;
-
- /** 查询 1:模糊 2:精确 **/
- private String columnQuery;
-
- /** 是否在列表显示 **/
- private String columnShow;
-}
diff --git a/eladmin-generator/src/main/java/me/zhengjie/domain/vo/TableInfo.java b/eladmin-generator/src/main/java/me/zhengjie/domain/vo/TableInfo.java
index 92712d757..c900a6540 100644
--- a/eladmin-generator/src/main/java/me/zhengjie/domain/vo/TableInfo.java
+++ b/eladmin-generator/src/main/java/me/zhengjie/domain/vo/TableInfo.java
@@ -1,5 +1,22 @@
+/*
+ * 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 me.zhengjie.domain.vo;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@@ -14,20 +31,19 @@
@NoArgsConstructor
public class TableInfo {
- /** 表名称 **/
+ @ApiModelProperty(value = "表名称")
private Object tableName;
- /** 创建日期 **/
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
+ @ApiModelProperty(value = "创建日期:yyyy-MM-dd HH:mm:ss")
private Object createTime;
- // 数据库引擎
+ @ApiModelProperty(value = "数据库引擎")
private Object engine;
- // 编码集
+ @ApiModelProperty(value = "编码集")
private Object coding;
- // 备注
+ @ApiModelProperty(value = "备注")
private Object remark;
-
-
}
diff --git a/eladmin-generator/src/main/java/me/zhengjie/repository/ColumnInfoRepository.java b/eladmin-generator/src/main/java/me/zhengjie/repository/ColumnInfoRepository.java
new file mode 100644
index 000000000..7a174d2d2
--- /dev/null
+++ b/eladmin-generator/src/main/java/me/zhengjie/repository/ColumnInfoRepository.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 me.zhengjie.repository;
+
+import me.zhengjie.domain.ColumnInfo;
+import org.springframework.data.jpa.repository.JpaRepository;
+import java.util.List;
+
+/**
+ * @author Zheng Jie
+ * @date 2019-01-14
+ */
+public interface ColumnInfoRepository extends JpaRepository {
+
+ /**
+ * 查询表信息
+ * @param tableName 表格名
+ * @return 表信息
+ */
+ List findByTableNameOrderByIdAsc(String tableName);
+}
diff --git a/eladmin-generator/src/main/java/me/zhengjie/repository/GenConfigRepository.java b/eladmin-generator/src/main/java/me/zhengjie/repository/GenConfigRepository.java
index d72787d06..1513c8102 100644
--- a/eladmin-generator/src/main/java/me/zhengjie/repository/GenConfigRepository.java
+++ b/eladmin-generator/src/main/java/me/zhengjie/repository/GenConfigRepository.java
@@ -1,3 +1,18 @@
+/*
+ * 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 me.zhengjie.repository;
import me.zhengjie.domain.GenConfig;
@@ -8,4 +23,11 @@
* @date 2019-01-14
*/
public interface GenConfigRepository extends JpaRepository {
+
+ /**
+ * 查询表配置
+ * @param tableName 表名
+ * @return /
+ */
+ GenConfig findByTableName(String tableName);
}
diff --git a/eladmin-generator/src/main/java/me/zhengjie/rest/GenConfigController.java b/eladmin-generator/src/main/java/me/zhengjie/rest/GenConfigController.java
index 19c092f85..85a2a4bb3 100644
--- a/eladmin-generator/src/main/java/me/zhengjie/rest/GenConfigController.java
+++ b/eladmin-generator/src/main/java/me/zhengjie/rest/GenConfigController.java
@@ -1,8 +1,25 @@
+/*
+ * 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 me.zhengjie.rest;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
import me.zhengjie.domain.GenConfig;
import me.zhengjie.service.GenConfigService;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
@@ -13,23 +30,22 @@
* @date 2019-01-14
*/
@RestController
-@RequestMapping("api")
+@RequiredArgsConstructor
+@RequestMapping("/api/genConfig")
+@Api(tags = "系统:代码生成器配置管理")
public class GenConfigController {
- @Autowired
- private GenConfigService genConfigService;
+ private final GenConfigService genConfigService;
- /**
- * 查询生成器配置
- * @return
- */
- @GetMapping(value = "/genConfig")
- public ResponseEntity get(){
- return new ResponseEntity(genConfigService.find(), HttpStatus.OK);
+ @ApiOperation("查询")
+ @GetMapping(value = "/{tableName}")
+ public ResponseEntity queryGenConfig(@PathVariable String tableName){
+ return new ResponseEntity<>(genConfigService.find(tableName), HttpStatus.OK);
}
- @PutMapping(value = "/genConfig")
- public ResponseEntity emailConfig(@Validated @RequestBody GenConfig genConfig){
- return new ResponseEntity(genConfigService.update(genConfig),HttpStatus.OK);
+ @PutMapping
+ @ApiOperation("修改")
+ public ResponseEntity updateGenConfig(@Validated @RequestBody GenConfig genConfig){
+ return new ResponseEntity<>(genConfigService.update(genConfig.getTableName(), genConfig),HttpStatus.OK);
}
}
diff --git a/eladmin-generator/src/main/java/me/zhengjie/rest/GeneratorController.java b/eladmin-generator/src/main/java/me/zhengjie/rest/GeneratorController.java
index 65158981d..68148b942 100644
--- a/eladmin-generator/src/main/java/me/zhengjie/rest/GeneratorController.java
+++ b/eladmin-generator/src/main/java/me/zhengjie/rest/GeneratorController.java
@@ -1,15 +1,36 @@
+/*
+ * 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 me.zhengjie.rest;
-import cn.hutool.core.util.PageUtil;
-import me.zhengjie.domain.vo.ColumnInfo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import me.zhengjie.domain.ColumnInfo;
+import me.zhengjie.domain.vo.TableInfo;
import me.zhengjie.exception.BadRequestException;
import me.zhengjie.service.GenConfigService;
import me.zhengjie.service.GeneratorService;
-import org.springframework.beans.factory.annotation.Autowired;
+import me.zhengjie.utils.PageResult;
+import me.zhengjie.utils.PageUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
@@ -17,54 +38,72 @@
* @date 2019-01-02
*/
@RestController
-@RequestMapping("api")
+@RequiredArgsConstructor
+@RequestMapping("/api/generator")
+@Api(tags = "系统:代码生成管理")
public class GeneratorController {
- @Autowired
- private GeneratorService generatorService;
-
- @Autowired
- private GenConfigService genConfigService;
+ private final GeneratorService generatorService;
+ private final GenConfigService genConfigService;
@Value("${generator.enabled}")
private Boolean generatorEnabled;
- /**
- * 查询数据库元数据
- * @param name
- * @param page
- * @param size
- * @return
- */
- @GetMapping(value = "/generator/tables")
- public ResponseEntity getTables(@RequestParam(defaultValue = "") String name,
- @RequestParam(defaultValue = "0")Integer page,
- @RequestParam(defaultValue = "10")Integer size){
- int[] startEnd = PageUtil.transToStartEnd(page+1, size);
- return new ResponseEntity(generatorService.getTables(name,startEnd), HttpStatus.OK);
+ @ApiOperation("查询数据库数据")
+ @GetMapping(value = "/tables/all")
+ public ResponseEntity queryAllTables(){
+ return new ResponseEntity<>(generatorService.getTables(), HttpStatus.OK);
+ }
+
+ @ApiOperation("查询数据库数据")
+ @GetMapping(value = "/tables")
+ public ResponseEntity> queryTables(@RequestParam(defaultValue = "") String name,
+ @RequestParam(defaultValue = "0")Integer page,
+ @RequestParam(defaultValue = "10")Integer size){
+ int[] startEnd = PageUtil.transToStartEnd(page, size);
+ return new ResponseEntity<>(generatorService.getTables(name,startEnd), HttpStatus.OK);
+ }
+
+ @ApiOperation("查询字段数据")
+ @GetMapping(value = "/columns")
+ public ResponseEntity> queryColumns(@RequestParam String tableName){
+ List columnInfos = generatorService.getColumns(tableName);
+ return new ResponseEntity<>(PageUtil.toPage(columnInfos,columnInfos.size()), HttpStatus.OK);
}
- /**
- * 查询表内元数据
- * @param tableName
- * @return
- */
- @GetMapping(value = "/generator/columns")
- public ResponseEntity getTables(@RequestParam String tableName){
- return new ResponseEntity(generatorService.getColumns(tableName), HttpStatus.OK);
+ @ApiOperation("保存字段数据")
+ @PutMapping
+ public ResponseEntity saveColumn(@RequestBody List columnInfos){
+ generatorService.save(columnInfos);
+ return new ResponseEntity<>(HttpStatus.OK);
}
- /**
- * 生成代码
- * @param columnInfos
- * @return
- */
- @PostMapping(value = "/generator")
- public ResponseEntity generator(@RequestBody List columnInfos, @RequestParam String tableName){
- if(!generatorEnabled){
- throw new BadRequestException("此环境不允许生成代码!");
+ @ApiOperation("同步字段数据")
+ @PostMapping(value = "sync")
+ public ResponseEntity syncColumn(@RequestBody List tables){
+ for (String table : tables) {
+ generatorService.sync(generatorService.getColumns(table), generatorService.query(table));
+ }
+ return new ResponseEntity<>(HttpStatus.OK);
+ }
+
+ @ApiOperation("生成代码")
+ @PostMapping(value = "/{tableName}/{type}")
+ public ResponseEntity generatorCode(@PathVariable String tableName, @PathVariable Integer type, HttpServletRequest request, HttpServletResponse response){
+ if(!generatorEnabled && type == 0){
+ throw new BadRequestException("此环境不允许生成代码,请选择预览或者下载查看!");
+ }
+ switch (type){
+ // 生成代码
+ case 0: generatorService.generator(genConfigService.find(tableName), generatorService.getColumns(tableName));
+ break;
+ // 预览
+ case 1: return generatorService.preview(genConfigService.find(tableName), generatorService.getColumns(tableName));
+ // 打包
+ case 2: generatorService.download(genConfigService.find(tableName), generatorService.getColumns(tableName), request, response);
+ break;
+ default: throw new BadRequestException("没有这个选项");
}
- generatorService.generator(columnInfos,genConfigService.find(),tableName);
- return new ResponseEntity(HttpStatus.OK);
+ return new ResponseEntity<>(HttpStatus.OK);
}
}
diff --git a/eladmin-generator/src/main/java/me/zhengjie/service/GenConfigService.java b/eladmin-generator/src/main/java/me/zhengjie/service/GenConfigService.java
index 34276545e..175439aba 100644
--- a/eladmin-generator/src/main/java/me/zhengjie/service/GenConfigService.java
+++ b/eladmin-generator/src/main/java/me/zhengjie/service/GenConfigService.java
@@ -1,30 +1,40 @@
+/*
+ * 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 me.zhengjie.service;
import me.zhengjie.domain.GenConfig;
-import org.springframework.cache.annotation.CacheConfig;
-import org.springframework.cache.annotation.CacheEvict;
-import org.springframework.cache.annotation.CachePut;
-import org.springframework.cache.annotation.Cacheable;
/**
* @author Zheng Jie
* @date 2019-01-14
*/
-@CacheConfig(cacheNames = "genConfig")
public interface GenConfigService {
/**
- * find
- * @return
+ * 查询表配置
+ * @param tableName 表名
+ * @return 表配置
*/
- @Cacheable(key = "'1'")
- GenConfig find();
+ GenConfig find(String tableName);
/**
- * update
- * @param genConfig
- * @return
+ * 更新表配置
+ * @param tableName 表名
+ * @param genConfig 表配置
+ * @return 表配置
*/
- @CacheEvict(allEntries = true)
- GenConfig update(GenConfig genConfig);
+ GenConfig update(String tableName, GenConfig genConfig);
}
diff --git a/eladmin-generator/src/main/java/me/zhengjie/service/GeneratorService.java b/eladmin-generator/src/main/java/me/zhengjie/service/GeneratorService.java
index 8b9158a92..6e59160ae 100644
--- a/eladmin-generator/src/main/java/me/zhengjie/service/GeneratorService.java
+++ b/eladmin-generator/src/main/java/me/zhengjie/service/GeneratorService.java
@@ -1,7 +1,27 @@
+/*
+ * 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 me.zhengjie.service;
import me.zhengjie.domain.GenConfig;
-import me.zhengjie.domain.vo.ColumnInfo;
+import me.zhengjie.domain.ColumnInfo;
+import me.zhengjie.domain.vo.TableInfo;
+import me.zhengjie.utils.PageResult;
+import org.springframework.http.ResponseEntity;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
@@ -12,24 +32,66 @@ public interface GeneratorService {
/**
* 查询数据库元数据
- * @param name
- * @param startEnd
- * @return
+ * @param name 表名
+ * @param startEnd 分页参数
+ * @return /
*/
- Object getTables(String name, int[] startEnd);
+ PageResult getTables(String name, int[] startEnd);
/**
* 得到数据表的元数据
- * @param name
- * @return
+ * @param name 表名
+ * @return /
+ */
+ List getColumns(String name);
+
+ /**
+ * 同步表数据
+ * @param columnInfos /
+ * @param columnInfoList /
+ */
+ void sync(List columnInfos, List columnInfoList);
+
+ /**
+ * 保持数据
+ * @param columnInfos /
+ */
+ void save(List columnInfos);
+
+ /**
+ * 获取所有table
+ * @return /
+ */
+ Object getTables();
+
+ /**
+ * 代码生成
+ * @param genConfig 配置信息
+ * @param columns 字段信息
+ */
+ void generator(GenConfig genConfig, List columns);
+
+ /**
+ * 预览
+ * @param genConfig 配置信息
+ * @param columns 字段信息
+ * @return /
+ */
+ ResponseEntity preview(GenConfig genConfig, List columns);
+
+ /**
+ * 打包下载
+ * @param genConfig 配置信息
+ * @param columns 字段信息
+ * @param request /
+ * @param response /
*/
- Object getColumns(String name);
+ void download(GenConfig genConfig, List columns, HttpServletRequest request, HttpServletResponse response);
/**
- * 生成代码
- * @param columnInfos
- * @param genConfig
- * @param tableName
+ * 查询数据库的表字段数据数据
+ * @param table /
+ * @return /
*/
- void generator(List columnInfos, GenConfig genConfig, String tableName);
+ List query(String table);
}
diff --git a/eladmin-generator/src/main/java/me/zhengjie/service/impl/GenConfigServiceImpl.java b/eladmin-generator/src/main/java/me/zhengjie/service/impl/GenConfigServiceImpl.java
index ec53052ea..5801d983f 100644
--- a/eladmin-generator/src/main/java/me/zhengjie/service/impl/GenConfigServiceImpl.java
+++ b/eladmin-generator/src/main/java/me/zhengjie/service/impl/GenConfigServiceImpl.java
@@ -1,35 +1,67 @@
+/*
+ * 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 me.zhengjie.service.impl;
+import lombok.RequiredArgsConstructor;
import me.zhengjie.domain.GenConfig;
import me.zhengjie.repository.GenConfigRepository;
import me.zhengjie.service.GenConfigService;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
-import java.util.Optional;
+import java.io.File;
/**
* @author Zheng Jie
* @date 2019-01-14
*/
@Service
+@RequiredArgsConstructor
+@SuppressWarnings({"unchecked","all"})
public class GenConfigServiceImpl implements GenConfigService {
- @Autowired
- private GenConfigRepository genConfigRepository;
+ private final GenConfigRepository genConfigRepository;
@Override
- public GenConfig find() {
- Optional genConfig = genConfigRepository.findById(1L);
- if(genConfig.isPresent()){
- return genConfig.get();
- } else {
- return new GenConfig();
+ public GenConfig find(String tableName) {
+ GenConfig genConfig = genConfigRepository.findByTableName(tableName);
+ if(genConfig == null){
+ return new GenConfig(tableName);
}
+ return genConfig;
}
@Override
- public GenConfig update(GenConfig genConfig) {
- genConfig.setId(1L);
+ public GenConfig update(String tableName, GenConfig genConfig) {
+ String separator = File.separator;
+ String[] paths;
+ String symbol = "\\";
+ if (symbol.equals(separator)) {
+ paths = genConfig.getPath().split("\\\\");
+ } else {
+ paths = genConfig.getPath().split(File.separator);
+ }
+ StringBuilder api = new StringBuilder();
+ for (String path : paths) {
+ api.append(path);
+ api.append(separator);
+ if ("src".equals(path)) {
+ api.append("api");
+ break;
+ }
+ }
+ genConfig.setApiPath(api.toString());
return genConfigRepository.save(genConfig);
}
}
diff --git a/eladmin-generator/src/main/java/me/zhengjie/service/impl/GeneratorServiceImpl.java b/eladmin-generator/src/main/java/me/zhengjie/service/impl/GeneratorServiceImpl.java
index 49e4e1c50..fdd0a0ae2 100644
--- a/eladmin-generator/src/main/java/me/zhengjie/service/impl/GeneratorServiceImpl.java
+++ b/eladmin-generator/src/main/java/me/zhengjie/service/impl/GeneratorServiceImpl.java
@@ -1,76 +1,205 @@
+/*
+ * 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 me.zhengjie.service.impl;
+import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.ZipUtil;
+import lombok.RequiredArgsConstructor;
import me.zhengjie.domain.GenConfig;
-import me.zhengjie.domain.vo.ColumnInfo;
+import me.zhengjie.domain.ColumnInfo;
import me.zhengjie.domain.vo.TableInfo;
import me.zhengjie.exception.BadRequestException;
+import me.zhengjie.repository.ColumnInfoRepository;
import me.zhengjie.service.GeneratorService;
-import me.zhengjie.utils.GenUtil;
-import me.zhengjie.utils.PageUtil;
-import me.zhengjie.utils.StringUtils;
+import me.zhengjie.utils.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
import java.io.IOException;
+import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
/**
* @author Zheng Jie
* @date 2019-01-02
*/
@Service
+@RequiredArgsConstructor
+@SuppressWarnings({"unchecked","all"})
public class GeneratorServiceImpl implements GeneratorService {
-
+ private static final Logger log = LoggerFactory.getLogger(GeneratorServiceImpl.class);
@PersistenceContext
private EntityManager em;
+ private final ColumnInfoRepository columnInfoRepository;
+
+ private final String CONFIG_MESSAGE = "请先配置生成器";
@Override
- public Object getTables(String name, int[] startEnd) {
+ public Object getTables() {
// 使用预编译防止sql注入
String sql = "select table_name ,create_time , engine, table_collation, table_comment from information_schema.tables " +
"where table_schema = (select database()) " +
- "and table_name like ? order by create_time desc";
+ "order by create_time desc";
+ Query query = em.createNativeQuery(sql);
+ return query.getResultList();
+ }
+
+ @Override
+ public PageResult getTables(String name, int[] startEnd) {
+ // 使用预编译防止sql注入
+ String sql = "select table_name ,create_time , engine, table_collation, table_comment from information_schema.tables " +
+ "where table_schema = (select database()) " +
+ "and table_name like :table order by create_time desc";
Query query = em.createNativeQuery(sql);
query.setFirstResult(startEnd[0]);
- query.setMaxResults(startEnd[1]-startEnd[0]);
- query.setParameter(1, StringUtils.isNotBlank(name) ? ("%" + name + "%") : "%%");
- List result = query.getResultList();
+ query.setMaxResults(startEnd[1] - startEnd[0]);
+ query.setParameter("table", StringUtils.isNotBlank(name) ? ("%" + name + "%") : "%%");
+ List result = query.getResultList();
List tableInfos = new ArrayList<>();
- for (Object[] obj : result) {
- tableInfos.add(new TableInfo(obj[0],obj[1],obj[2],obj[3], ObjectUtil.isNotEmpty(obj[4])? obj[4] : "-"));
+ for (Object obj : result) {
+ Object[] arr = (Object[]) obj;
+ tableInfos.add(new TableInfo(arr[0], arr[1], arr[2], arr[3], ObjectUtil.isNotEmpty(arr[4]) ? arr[4] : "-"));
+ }
+ String countSql = "select count(1) from information_schema.tables " +
+ "where table_schema = (select database()) and table_name like :table";
+ Query queryCount = em.createNativeQuery(countSql);
+ queryCount.setParameter("table", StringUtils.isNotBlank(name) ? ("%" + name + "%") : "%%");
+ BigInteger totalElements = (BigInteger) queryCount.getSingleResult();
+ return PageUtil.toPage(tableInfos, totalElements.longValue());
+ }
+
+ @Override
+ public List getColumns(String tableName) {
+ List columnInfos = columnInfoRepository.findByTableNameOrderByIdAsc(tableName);
+ if (CollectionUtil.isNotEmpty(columnInfos)) {
+ return columnInfos;
+ } else {
+ columnInfos = query(tableName);
+ return columnInfoRepository.saveAll(columnInfos);
}
- Query query1 = em.createNativeQuery("SELECT COUNT(*) from information_schema.tables where table_schema = (select database())");
- Object totalElements = query1.getSingleResult();
- return PageUtil.toPage(tableInfos,totalElements);
}
@Override
- public Object getColumns(String name) {
+ public List query(String tableName) {
// 使用预编译防止sql注入
String sql = "select column_name, is_nullable, data_type, column_comment, column_key, extra from information_schema.columns " +
"where table_name = ? and table_schema = (select database()) order by ordinal_position";
Query query = em.createNativeQuery(sql);
- query.setParameter(1, StringUtils.isNotBlank(name) ? name : null);
- List result = query.getResultList();
+ query.setParameter(1, tableName);
+ List result = query.getResultList();
List columnInfos = new ArrayList<>();
- for (Object[] obj : result) {
- columnInfos.add(new ColumnInfo(obj[0],obj[1],obj[2],obj[3],obj[4],obj[5],null,"true"));
+ for (Object obj : result) {
+ Object[] arr = (Object[]) obj;
+ columnInfos.add(
+ new ColumnInfo(
+ tableName,
+ arr[0].toString(),
+ "NO".equals(arr[1]),
+ arr[2].toString(),
+ ObjectUtil.isNotNull(arr[3]) ? arr[3].toString() : null,
+ ObjectUtil.isNotNull(arr[4]) ? arr[4].toString() : null,
+ ObjectUtil.isNotNull(arr[5]) ? arr[5].toString() : null)
+ );
+ }
+ return columnInfos;
+ }
+
+ @Override
+ public void sync(List columnInfos, List columnInfoList) {
+ // 第一种情况,数据库类字段改变或者新增字段
+ for (ColumnInfo columnInfo : columnInfoList) {
+ // 根据字段名称查找
+ List columns = columnInfos.stream().filter(c -> c.getColumnName().equals(columnInfo.getColumnName())).collect(Collectors.toList());
+ // 如果能找到,就修改部分可能被字段
+ if (CollectionUtil.isNotEmpty(columns)) {
+ ColumnInfo column = columns.get(0);
+ column.setColumnType(columnInfo.getColumnType());
+ column.setExtra(columnInfo.getExtra());
+ column.setKeyType(columnInfo.getKeyType());
+ if (StringUtils.isBlank(column.getRemark())) {
+ column.setRemark(columnInfo.getRemark());
+ }
+ columnInfoRepository.save(column);
+ } else {
+ // 如果找不到,则保存新字段信息
+ columnInfoRepository.save(columnInfo);
+ }
+ }
+ // 第二种情况,数据库字段删除了
+ for (ColumnInfo columnInfo : columnInfos) {
+ // 根据字段名称查找
+ List columns = columnInfoList.stream().filter(c -> c.getColumnName().equals(columnInfo.getColumnName())).collect(Collectors.toList());
+ // 如果找不到,就代表字段被删除了,则需要删除该字段
+ if (CollectionUtil.isEmpty(columns)) {
+ columnInfoRepository.delete(columnInfo);
+ }
+ }
+ }
+
+ @Override
+ public void save(List columnInfos) {
+ columnInfoRepository.saveAll(columnInfos);
+ }
+
+ @Override
+ public void generator(GenConfig genConfig, List columns) {
+ if (genConfig.getId() == null) {
+ throw new BadRequestException(CONFIG_MESSAGE);
+ }
+ try {
+ GenUtil.generatorCode(columns, genConfig);
+ } catch (IOException e) {
+ log.error(e.getMessage(), e);
+ throw new BadRequestException("生成失败,请手动处理已生成的文件");
+ }
+ }
+
+ @Override
+ public ResponseEntity preview(GenConfig genConfig, List columns) {
+ if (genConfig.getId() == null) {
+ throw new BadRequestException(CONFIG_MESSAGE);
}
- return PageUtil.toPage(columnInfos,columnInfos.size());
+ List> genList = GenUtil.preview(columns, genConfig);
+ return new ResponseEntity<>(genList, HttpStatus.OK);
}
@Override
- public void generator(List columnInfos, GenConfig genConfig, String tableName) {
- if(genConfig.getId() == null){
- throw new BadRequestException("请先配置生成器");
+ public void download(GenConfig genConfig, List columns, HttpServletRequest request, HttpServletResponse response) {
+ if (genConfig.getId() == null) {
+ throw new BadRequestException(CONFIG_MESSAGE);
}
try {
- GenUtil.generatorCode(columnInfos,genConfig,tableName);
+ File file = new File(GenUtil.download(columns, genConfig));
+ String zipPath = file.getPath() + ".zip";
+ ZipUtil.zip(file.getPath(), zipPath);
+ FileUtil.downloadFile(request, response, new File(zipPath), true);
} catch (IOException e) {
- throw new RuntimeException(e);
+ throw new BadRequestException("打包失败");
}
}
}
diff --git a/eladmin-generator/src/main/java/me/zhengjie/utils/ColUtil.java b/eladmin-generator/src/main/java/me/zhengjie/utils/ColUtil.java
index 66026d940..cee76364c 100644
--- a/eladmin-generator/src/main/java/me/zhengjie/utils/ColUtil.java
+++ b/eladmin-generator/src/main/java/me/zhengjie/utils/ColUtil.java
@@ -1,6 +1,23 @@
+/*
+ * 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 me.zhengjie.utils;
import org.apache.commons.configuration.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* sql字段转java
@@ -9,15 +26,18 @@
* @date 2019-01-03
*/
public class ColUtil {
+ private static final Logger log = LoggerFactory.getLogger(ColUtil.class);
/**
* 转换mysql数据类型为java数据类型
- * @param type
- * @return
+ *
+ * @param type 数据库字段类型
+ * @return String
*/
- public static String cloToJava(String type){
+ static String cloToJava(String type) {
Configuration config = getConfig();
- return config.getString(type,"unknowType");
+ assert config != null;
+ return config.getString(type, "unknowType");
}
/**
@@ -25,9 +45,9 @@ public static String cloToJava(String type){
*/
public static PropertiesConfiguration getConfig() {
try {
- return new PropertiesConfiguration("generator.properties" );
+ return new PropertiesConfiguration("gen.properties");
} catch (ConfigurationException e) {
- e.printStackTrace();
+ log.error(e.getMessage(), e);
}
return null;
}
diff --git a/eladmin-generator/src/main/java/me/zhengjie/utils/GenUtil.java b/eladmin-generator/src/main/java/me/zhengjie/utils/GenUtil.java
index ca198b314..bb5a85e43 100644
--- a/eladmin-generator/src/main/java/me/zhengjie/utils/GenUtil.java
+++ b/eladmin-generator/src/main/java/me/zhengjie/utils/GenUtil.java
@@ -1,176 +1,355 @@
+/*
+ * 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 me.zhengjie.utils;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.template.*;
import lombok.extern.slf4j.Slf4j;
import me.zhengjie.domain.GenConfig;
-import me.zhengjie.domain.vo.ColumnInfo;
+import me.zhengjie.domain.ColumnInfo;
import org.springframework.util.ObjectUtils;
+
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.time.LocalDate;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
+
+import static me.zhengjie.utils.FileUtil.SYS_TEM_DIR;
/**
* 代码生成
+ *
* @author Zheng Jie
* @date 2019-01-02
*/
@Slf4j
+@SuppressWarnings({"unchecked", "all"})
public class GenUtil {
private static final String TIMESTAMP = "Timestamp";
private static final String BIGDECIMAL = "BigDecimal";
- private static final String PK = "PRI";
+ public static final String PK = "PRI";
- private static final String EXTRA = "auto_increment";
+ public static final String EXTRA = "auto_increment";
/**
* 获取后端代码模板名称
- * @return
+ *
+ * @return List
*/
- public static List getAdminTemplateNames() {
+ private static List getAdminTemplateNames() {
List templateNames = new ArrayList<>();
templateNames.add("Entity");
templateNames.add("Dto");
templateNames.add("Mapper");
- templateNames.add("Repository");
+ templateNames.add("Controller");
+ templateNames.add("QueryCriteria");
templateNames.add("Service");
templateNames.add("ServiceImpl");
- templateNames.add("QueryCriteria");
- templateNames.add("Controller");
+ templateNames.add("Repository");
return templateNames;
}
/**
* 获取前端代码模板名称
- * @return
+ *
+ * @return List
*/
- public static List getFrontTemplateNames() {
+ private static List getFrontTemplateNames() {
List templateNames = new ArrayList<>();
- templateNames.add("api");
templateNames.add("index");
- templateNames.add("eForm");
+ templateNames.add("api");
return templateNames;
}
- /**
- * 生成代码
- * @param columnInfos 表元数据
- * @param genConfig 生成代码的参数配置,如包路径,作者
- */
- public static void generatorCode(List columnInfos, GenConfig genConfig, String tableName) throws IOException {
- Map map = new HashMap();
- map.put("package",genConfig.getPack());
- map.put("moduleName",genConfig.getModuleName());
- map.put("author",genConfig.getAuthor());
- map.put("date", LocalDate.now().toString());
- map.put("tableName",tableName);
- String className = StringUtils.toCapitalizeCamelCase(tableName);
- String changeClassName = StringUtils.toCamelCase(tableName);
+ public static List> preview(List columns, GenConfig genConfig) {
+ Map genMap = getGenMap(columns, genConfig);
+ List> genList = new ArrayList<>();
+ // 获取后端模版
+ List templates = getAdminTemplateNames();
+ TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH));
+ for (String templateName : templates) {
+ Map map = new HashMap<>(1);
+ Template template = engine.getTemplate("admin/" + templateName + ".ftl");
+ map.put("content", template.render(genMap));
+ map.put("name", templateName);
+ genList.add(map);
+ }
+ // 获取前端模版
+ templates = getFrontTemplateNames();
+ for (String templateName : templates) {
+ Map map = new HashMap<>(1);
+ Template template = engine.getTemplate("front/" + templateName + ".ftl");
+ map.put(templateName, template.render(genMap));
+ map.put("content", template.render(genMap));
+ map.put("name", templateName);
+ genList.add(map);
+ }
+ return genList;
+ }
- // 判断是否去除表前缀
- if (StringUtils.isNotEmpty(genConfig.getPrefix())) {
- className = StringUtils.toCapitalizeCamelCase(StrUtil.removePrefix(tableName,genConfig.getPrefix()));
- changeClassName = StringUtils.toCamelCase(StrUtil.removePrefix(tableName,genConfig.getPrefix()));
- }
- map.put("className", className);
- map.put("upperCaseClassName", className.toUpperCase());
- map.put("changeClassName", changeClassName);
- map.put("hasTimestamp",false);
- map.put("hasBigDecimal",false);
- map.put("hasQuery",false);
- map.put("auto",false);
-
- List> columns = new ArrayList<>();
- List> queryColumns = new ArrayList<>();
- for (ColumnInfo column : columnInfos) {
- Map listMap = new HashMap();
- listMap.put("columnComment",column.getColumnComment());
- listMap.put("columnKey",column.getColumnKey());
-
- String colType = ColUtil.cloToJava(column.getColumnType().toString());
- String changeColumnName = StringUtils.toCamelCase(column.getColumnName().toString());
- String capitalColumnName = StringUtils.toCapitalizeCamelCase(column.getColumnName().toString());
- if(PK.equals(column.getColumnKey())){
- map.put("pkColumnType",colType);
- map.put("pkChangeColName",changeColumnName);
- map.put("pkCapitalColName",capitalColumnName);
- }
- if(TIMESTAMP.equals(colType)){
- map.put("hasTimestamp",true);
- }
- if(BIGDECIMAL.equals(colType)){
- map.put("hasBigDecimal",true);
- }
- if(EXTRA.equals(column.getExtra())){
- map.put("auto",true);
+ public static String download(List columns, GenConfig genConfig) throws IOException {
+ // 拼接的路径:/tmpeladmin-gen-temp/,这个路径在Linux下需要root用户才有权限创建,非root用户会权限错误而失败,更改为: /tmp/eladmin-gen-temp/
+ // String tempPath =SYS_TEM_DIR + "eladmin-gen-temp" + File.separator + genConfig.getTableName() + File.separator;
+ String tempPath = SYS_TEM_DIR + "eladmin-gen-temp" + File.separator + genConfig.getTableName() + File.separator;
+ Map genMap = getGenMap(columns, genConfig);
+ TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH));
+ // 生成后端代码
+ List templates = getAdminTemplateNames();
+ for (String templateName : templates) {
+ Template template = engine.getTemplate("admin/" + templateName + ".ftl");
+ String filePath = getAdminFilePath(templateName, genConfig, genMap.get("className").toString(), tempPath + "eladmin" + File.separator);
+ assert filePath != null;
+ File file = new File(filePath);
+ // 如果非覆盖生成
+ if (!genConfig.getCover() && FileUtil.exist(file)) {
+ continue;
}
- listMap.put("columnType",colType);
- listMap.put("columnName",column.getColumnName());
- listMap.put("isNullable",column.getIsNullable());
- listMap.put("columnShow",column.getColumnShow());
- listMap.put("changeColumnName",changeColumnName);
- listMap.put("capitalColumnName",capitalColumnName);
-
- // 判断是否有查询,如有则把查询的字段set进columnQuery
- if(!StringUtils.isBlank(column.getColumnQuery())){
- listMap.put("columnQuery",column.getColumnQuery());
- map.put("hasQuery",true);
- queryColumns.add(listMap);
+ // 生成代码
+ genFile(file, template, genMap);
+ }
+ // 生成前端代码
+ templates = getFrontTemplateNames();
+ for (String templateName : templates) {
+ Template template = engine.getTemplate("front/" + templateName + ".ftl");
+ String path = tempPath + "eladmin-web" + File.separator;
+ String apiPath = path + "src" + File.separator + "api" + File.separator;
+ String srcPath = path + "src" + File.separator + "views" + File.separator + genMap.get("changeClassName").toString() + File.separator;
+ String filePath = getFrontFilePath(templateName, apiPath, srcPath, genMap.get("changeClassName").toString());
+ assert filePath != null;
+ File file = new File(filePath);
+ // 如果非覆盖生成
+ if (!genConfig.getCover() && FileUtil.exist(file)) {
+ continue;
}
- columns.add(listMap);
+ // 生成代码
+ genFile(file, template, genMap);
}
- map.put("columns",columns);
- map.put("queryColumns",queryColumns);
- TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH));
+ return tempPath;
+ }
+ public static void generatorCode(List columnInfos, GenConfig genConfig) throws IOException {
+ Map