ls){
+ for(Area area: areaList){
+ if(area.getId().equals(code)){
+ String pid = area.getPid();
+ ls.add(0,area.getText());
+ getAreaByCode(pid,ls);
+ }
+ }
+ }
+
+ private void initAreaList(){
+ //System.out.println("=====================");
+ if(this.areaList==null || this.areaList.size()==0){
+ this.areaList = new ArrayList ();
+ try {
+ String jsonData = oConvertUtils.readStatic("classpath:static/pca.json");
+ JSONObject baseJson = JSONObject.parseObject(jsonData);
+ //第一层 省
+ JSONObject provinceJson = baseJson.getJSONObject("86");
+ for(String provinceKey: provinceJson.keySet()){
+ //System.out.println("===="+provinceKey);
+ Area province = new Area(provinceKey,provinceJson.getString(provinceKey),"86");
+ this.areaList.add(province);
+ //第二层 市
+ JSONObject cityJson = baseJson.getJSONObject(provinceKey);
+ for(String cityKey:cityJson.keySet()){
+ //System.out.println("-----"+cityKey);
+ Area city = new Area(cityKey,cityJson.getString(cityKey),provinceKey);
+ this.areaList.add(city);
+ //第三层 区
+ JSONObject areaJson = baseJson.getJSONObject(cityKey);
+ if(areaJson!=null){
+ for(String areaKey:areaJson.keySet()){
+ //System.out.println("········"+areaKey);
+ Area area = new Area(areaKey,areaJson.getString(areaKey),cityKey);
+ //update-begin-author:taoyan date:2022-5-24 for:VUEN-1088 online 导入 省市区导入后 导入数据错乱 北京市/市辖区/西城区-->山西省/晋城市/城区
+ area.setAheadText(cityJson.getString(cityKey));
+ //update-end-author:taoyan date:2022-5-24 for:VUEN-1088 online 导入 省市区导入后 导入数据错乱 北京市/市辖区/西城区-->山西省/晋城市/城区
+ this.areaList.add(area);
+ }
+ }
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ }
+
+
+ private String jsonRead(File file){
+ Scanner scanner = null;
+ StringBuilder buffer = new StringBuilder();
+ try {
+ scanner = new Scanner(file, "utf-8");
+ while (scanner.hasNextLine()) {
+ buffer.append(scanner.nextLine());
+ }
+ } catch (Exception e) {
+
+ } finally {
+ if (scanner != null) {
+ scanner.close();
+ }
+ }
+ return buffer.toString();
+ }
+
+ class Area{
+ String id;
+ String text;
+ String pid;
+ // 用于存储上级文本数据,区的上级文本 是市的数据
+ String aheadText;
+
+ public Area(String id,String text,String pid){
+ this.id = id;
+ this.text = text;
+ this.pid = pid;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public String getPid() {
+ return pid;
+ }
+
+ public String getAheadText() {
+ return aheadText;
+ }
+ public void setAheadText(String aheadText) {
+ this.aheadText = aheadText;
+ }
+ }
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/ServiceNameConstants.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/ServiceNameConstants.java
new file mode 100644
index 0000000..0d8711e
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/ServiceNameConstants.java
@@ -0,0 +1,42 @@
+/*
+ *
+ * * Copyright (c) 2019-2020, 冷冷 (wangiegie@gmail.com).
+ * *
+ * * Licensed under the GNU Lesser General Public License 3.0 (the "License");
+ * * you may not use this file except in compliance with the License.
+ * * You may obtain a copy of the License at
+ * *
+ * * https://www.gnu.org/licenses/lgpl.html
+ * *
+ * * 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 org.jeecg.common.constant;
+
+/**
+ * @author scott
+ * @date 2019年05月18日
+ * 服务名称
+ */
+public interface ServiceNameConstants {
+
+ /**
+ * 微服务名:系统管理模块
+ */
+ String SERVICE_SYSTEM = "jeecg-system";
+ /**
+ * 微服务名:Demo模块
+ */
+ String SERVICE_DEMO = "jeecg-demo";
+
+ /**
+ * gateway通过header传递根路径 basePath
+ */
+ String X_GATEWAY_BASE_PATH = "X_GATEWAY_BASE_PATH";
+
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/SymbolConstant.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/SymbolConstant.java
new file mode 100644
index 0000000..e18e6aa
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/SymbolConstant.java
@@ -0,0 +1,119 @@
+package org.jeecg.common.constant;
+
+/**
+ * @Description: 符号和特殊符号常用类
+ * @author: wangshuai
+ * @date: 2022年03月30日 17:44
+ */
+public class SymbolConstant {
+
+ /**
+ * 符号:点
+ */
+ public static final String SPOT = ".";
+
+ /**
+ * 符号:双斜杠
+ */
+ public static final String DOUBLE_BACKSLASH = "\\";
+
+ /**
+ * 符号:冒号
+ */
+ public static final String COLON = ":";
+
+ /**
+ * 符号:逗号
+ */
+ public static final String COMMA = ",";
+
+ /**
+ * 符号:左花括号 }
+ */
+ public static final String LEFT_CURLY_BRACKET = "{";
+
+ /**
+ * 符号:右花括号 }
+ */
+ public static final String RIGHT_CURLY_BRACKET = "}";
+
+ /**
+ * 符号:井号 #
+ */
+ public static final String WELL_NUMBER = "#";
+
+ /**
+ * 符号:单斜杠
+ */
+ public static final String SINGLE_SLASH = "/";
+
+ /**
+ * 符号:双斜杠
+ */
+ public static final String DOUBLE_SLASH = "//";
+
+ /**
+ * 符号:感叹号
+ */
+ public static final String EXCLAMATORY_MARK = "!";
+
+ /**
+ * 符号:下划线
+ */
+ public static final String UNDERLINE = "_";
+
+ /**
+ * 符号:单引号
+ */
+ public static final String SINGLE_QUOTATION_MARK = "'";
+
+ /**
+ * 符号:星号
+ */
+ public static final String ASTERISK = "*";
+
+ /**
+ * 符号:百分号
+ */
+ public static final String PERCENT_SIGN = "%";
+
+ /**
+ * 符号:美元 $
+ */
+ public static final String DOLLAR = "$";
+
+ /**
+ * 符号:和 &
+ */
+ public static final String AND = "&";
+
+ /**
+ * 符号:../
+ */
+ public static final String SPOT_SINGLE_SLASH = "../";
+
+ /**
+ * 符号:..\\
+ */
+ public static final String SPOT_DOUBLE_BACKSLASH = "..\\";
+
+ /**
+ * 系统变量前缀 #{
+ */
+ public static final String SYS_VAR_PREFIX = "#{";
+
+ /**
+ * 符号 {{
+ */
+ public static final String DOUBLE_LEFT_CURLY_BRACKET = "{{";
+
+ /**
+ * 符号:[
+ */
+ public static final String SQUARE_BRACKETS_LEFT = "[";
+ /**
+ * 符号:]
+ */
+ public static final String SQUARE_BRACKETS_RIGHT = "]";
+
+}
\ No newline at end of file
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/VxeSocketConst.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/VxeSocketConst.java
new file mode 100644
index 0000000..cac4d66
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/VxeSocketConst.java
@@ -0,0 +1,33 @@
+package org.jeecg.common.constant;
+
+/**
+ * VXESocket 常量
+ *
+ * update: 【类名改了大小写】 date: 2022-04-18
+ * @author: jeecg-boot
+ */
+public class VxeSocketConst {
+
+ /**
+ * 消息类型
+ */
+ public static final String TYPE = "type";
+ /**
+ * 消息数据
+ */
+ public static final String DATA = "data";
+
+ /**
+ * 消息类型:心跳检测
+ */
+ public static final String TYPE_HB = "heart_beat";
+ /**
+ * 消息类型:通用数据传递
+ */
+ public static final String TYPE_CSD = "common_send_date";
+ /**
+ * 消息类型:更新vxe table数据
+ */
+ public static final String TYPE_UVT = "update_vxe_table";
+
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/WebsocketConst.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/WebsocketConst.java
new file mode 100644
index 0000000..a165e39
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/WebsocketConst.java
@@ -0,0 +1,61 @@
+package org.jeecg.common.constant;
+
+/**
+ * @Description: Websocket常量类
+ * @author: taoyan
+ * @date: 2020年03月23日
+ */
+public class WebsocketConst {
+
+
+ /**
+ * 消息json key:cmd
+ */
+ public static final String MSG_CMD = "cmd";
+
+ /**
+ * 消息json key:msgId
+ */
+ public static final String MSG_ID = "msgId";
+
+ /**
+ * 消息json key:msgTxt
+ */
+ public static final String MSG_TXT = "msgTxt";
+
+ /**
+ * 消息json key:userId
+ */
+ public static final String MSG_USER_ID = "userId";
+
+ /**
+ * 消息类型 heartcheck
+ */
+ public static final String CMD_CHECK = "heartcheck";
+
+ /**
+ * 消息类型 user 用户消息
+ */
+ public static final String CMD_USER = "user";
+
+ /**
+ * 消息类型 topic 系统通知
+ */
+ public static final String CMD_TOPIC = "topic";
+
+ /**
+ * 消息类型 email
+ */
+ public static final String CMD_EMAIL = "email";
+
+ /**
+ * 消息类型 meetingsign 会议签到
+ */
+ public static final String CMD_SIGN = "sign";
+
+ /**
+ * 消息类型 新闻发布/取消
+ */
+ public static final String NEWS_PUBLISH = "publish";
+
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/CgformEnum.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/CgformEnum.java
new file mode 100644
index 0000000..e97c553
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/CgformEnum.java
@@ -0,0 +1,152 @@
+package org.jeecg.common.constant.enums;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * online表单枚举 代码生成器用到
+ * @author: jeecg-boot
+ */
+public enum CgformEnum {
+
+ /**
+ * 单表
+ */
+ ONE(1, "one", "/jeecg/code-template-online", "default.one", "经典风格"),
+ /**
+ * 多表
+ */
+ MANY(2, "many", "/jeecg/code-template-online", "default.onetomany", "经典风格"),
+ /**
+ * 多表(jvxe风格)
+ * */
+ JVXE_TABLE(2, "jvxe", "/jeecg/code-template-online", "jvxe.onetomany", "JVXE风格"),
+
+ /**
+ * 多表
+ */
+ ERP(2, "erp", "/jeecg/code-template-online", "erp.onetomany", "ERP风格"),
+ /**
+ * 多表(内嵌子表风格)
+ */
+ INNER_TABLE(2, "innerTable", "/jeecg/code-template-online", "inner-table.onetomany", "内嵌子表风格"),
+ /**
+ * 多表(tab风格)
+ * */
+ TAB(2, "tab", "/jeecg/code-template-online", "tab.onetomany", "Tab风格"),
+ /**
+ * 树形列表
+ */
+ TREE(3, "tree", "/jeecg/code-template-online", "default.tree", "树形列表");
+
+ /**
+ * 类型 1/单表 2/一对多 3/树
+ */
+ int type;
+ /**
+ * 编码标识
+ */
+ String code;
+ /**
+ * 代码生成器模板路径
+ */
+ String templatePath;
+ /**
+ * 代码生成器模板路径
+ */
+ String stylePath;
+ /**
+ * 模板风格名称
+ */
+ String note;
+
+ /**
+ * 构造器
+ *
+ * @param type 类型 1/单表 2/一对多 3/树
+ * @param code 模板编码
+ * @param templatePath 模板路径
+ * @param stylePath 模板子路径
+ * @param note
+ */
+ CgformEnum(int type, String code, String templatePath, String stylePath, String note) {
+ this.type = type;
+ this.code = code;
+ this.templatePath = templatePath;
+ this.stylePath = stylePath;
+ this.note = note;
+ }
+
+ /**
+ * 根据code获取模板路径
+ *
+ * @param code
+ * @return
+ */
+ public static String getTemplatePathByConfig(String code) {
+ return getCgformEnumByConfig(code).templatePath;
+ }
+
+
+ public int getType() {
+ return type;
+ }
+
+ public void setType(int type) {
+ this.type = type;
+ }
+
+ public String getTemplatePath() {
+ return templatePath;
+ }
+
+ public void setTemplatePath(String templatePath) {
+ this.templatePath = templatePath;
+ }
+
+ public String getStylePath() {
+ return stylePath;
+ }
+
+ public void setStylePath(String stylePath) {
+ this.stylePath = stylePath;
+ }
+
+ /**
+ * 根据code找枚举
+ *
+ * @param code
+ * @return
+ */
+ public static CgformEnum getCgformEnumByConfig(String code) {
+ for (CgformEnum e : CgformEnum.values()) {
+ if (e.code.equals(code)) {
+ return e;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 根据类型找所有
+ *
+ * @param type
+ * @return
+ */
+ public static List> getJspModelList(int type) {
+ List> ls = new ArrayList>();
+ for (CgformEnum e : CgformEnum.values()) {
+ if (e.type == type) {
+ Map map = new HashMap();
+ map.put("code", e.code);
+ map.put("note", e.note);
+ ls.add(map);
+ }
+ }
+ return ls;
+ }
+
+
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/LowAppAopEnum.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/LowAppAopEnum.java
new file mode 100644
index 0000000..81c22aa
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/LowAppAopEnum.java
@@ -0,0 +1,25 @@
+package org.jeecg.common.constant.enums;
+
+/**
+ * LowApp 切面注解枚举
+ * @date 2022-1-5
+ * @author: jeecg-boot
+ */
+public enum LowAppAopEnum {
+
+ /**
+ * 新增方法
+ */
+ ADD,
+ /**
+ * 删除方法(包含单个和批量删除)
+ */
+ DELETE,
+ /** 复制表单操作 */
+ COPY,
+
+ /**
+ * Online表单专用:数据库表转Online表单
+ */
+ CGFORM_DB_IMPORT
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/MessageTypeEnum.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/MessageTypeEnum.java
new file mode 100644
index 0000000..b2e65e0
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/MessageTypeEnum.java
@@ -0,0 +1,68 @@
+package org.jeecg.common.constant.enums;
+
+import org.jeecg.common.system.annotation.EnumDict;
+import org.jeecg.common.system.vo.DictModel;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 消息类型
+ * @author: jeecg-boot
+ */
+@EnumDict("messageType")
+public enum MessageTypeEnum {
+
+ XT("system", "系统消息"),
+ YJ("email", "邮件消息"),
+ DD("dingtalk", "钉钉消息"),
+ QYWX("wechat_enterprise", "企业微信");
+
+ MessageTypeEnum(String type, String note){
+ this.type = type;
+ this.note = note;
+ }
+
+ /**
+ * 消息类型
+ */
+ String type;
+
+ /**
+ * 类型说明
+ */
+ String note;
+
+ public String getNote() {
+ return note;
+ }
+
+ public void setNote(String note) {
+ this.note = note;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+
+ /**
+ * 获取字典数据
+ * @return
+ */
+ public static List getDictList(){
+ List list = new ArrayList<>();
+ DictModel dictModel = null;
+ for(MessageTypeEnum e: MessageTypeEnum.values()){
+ dictModel = new DictModel();
+ dictModel.setValue(e.getType());
+ dictModel.setText(e.getNote());
+ list.add(dictModel);
+ }
+ return list;
+ }
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/ModuleType.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/ModuleType.java
new file mode 100644
index 0000000..5d3aa1e
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/ModuleType.java
@@ -0,0 +1,18 @@
+package org.jeecg.common.constant.enums;
+
+/**
+ * 日志按模块分类
+ * @author: jeecg-boot
+ */
+public enum ModuleType {
+
+ /**
+ * 普通
+ */
+ COMMON,
+
+ /**
+ * online
+ */
+ ONLINE;
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/OperateTypeEnum.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/OperateTypeEnum.java
new file mode 100644
index 0000000..a438b40
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/OperateTypeEnum.java
@@ -0,0 +1,95 @@
+package org.jeecg.common.constant.enums;
+
+import org.jeecg.common.constant.CommonConstant;
+
+/**
+ * @Description: 操作类型
+ * @author: jeecg-boot
+ * @date: 2022/3/31 10:05
+ */
+public enum OperateTypeEnum {
+
+ /**
+ * 列表
+ */
+ LIST(CommonConstant.OPERATE_TYPE_1, "list"),
+
+ /**
+ * 新增
+ */
+ ADD(CommonConstant.OPERATE_TYPE_2, "add"),
+
+ /**
+ * 编辑
+ */
+ EDIT(CommonConstant.OPERATE_TYPE_3, "edit"),
+
+ /**
+ * 删除
+ */
+ DELETE(CommonConstant.OPERATE_TYPE_4, "delete"),
+
+ /**
+ * 导入
+ */
+ IMPORT(CommonConstant.OPERATE_TYPE_5, "import"),
+
+ /**
+ * 导出
+ */
+ EXPORT(CommonConstant.OPERATE_TYPE_6, "export");
+
+ /**
+ * 类型 1列表,2新增,3编辑,4删除,5导入,6导出
+ */
+ int type;
+
+ /**
+ * 编码(请求方式)
+ */
+ String code;
+
+
+ public int getType() {
+ return type;
+ }
+
+ public void setType(int type) {
+ this.type = type;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public void setCode(String code) {
+ this.code = code;
+ }
+
+ /**
+ * 构造器
+ *
+ * @param type 类型
+ * @param code 编码(请求方式)
+ */
+ OperateTypeEnum(int type, String code) {
+ this.type = type;
+ this.code = code;
+ }
+
+
+ /**
+ * 根据请求名称匹配
+ *
+ * @param methodName 请求名称
+ * @return Integer 类型
+ */
+ public static Integer getTypeByMethodName(String methodName) {
+ for (OperateTypeEnum e : OperateTypeEnum.values()) {
+ if (methodName.startsWith(e.getCode())) {
+ return e.getType();
+ }
+ }
+ return CommonConstant.OPERATE_TYPE_1;
+ }
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/RoleIndexConfigEnum.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/RoleIndexConfigEnum.java
new file mode 100644
index 0000000..09d9c10
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/RoleIndexConfigEnum.java
@@ -0,0 +1,93 @@
+package org.jeecg.common.constant.enums;
+
+import org.jeecg.common.util.oConvertUtils;
+
+import java.util.List;
+
+/**
+ * 首页自定义
+ * 通过角色编码与首页组件路径配置
+ * 枚举的顺序有权限高低权重作用(也就是配置多个角色,在前面的角色首页,会优先生效)
+ * @author: jeecg-boot
+ */
+public enum RoleIndexConfigEnum {
+
+ /**首页自定义 admin*/
+ ADMIN("admin", "dashboard/Analysis"),
+ //TEST("test", "dashboard/IndexChart"),
+ /**首页自定义 hr*/
+ HR("hr", "dashboard/IndexBdc");
+ //DM("dm", "dashboard/IndexTask"),
+
+ /**
+ * 角色编码
+ */
+ String roleCode;
+ /**
+ * 路由index
+ */
+ String componentUrl;
+
+ /**
+ * 构造器
+ *
+ * @param roleCode 角色编码
+ * @param componentUrl 首页组件路径(规则跟菜单配置一样)
+ */
+ RoleIndexConfigEnum(String roleCode, String componentUrl) {
+ this.roleCode = roleCode;
+ this.componentUrl = componentUrl;
+ }
+ /**
+ * 根据code找枚举
+ * @param roleCode 角色编码
+ * @return
+ */
+ private static RoleIndexConfigEnum getEnumByCode(String roleCode) {
+ for (RoleIndexConfigEnum e : RoleIndexConfigEnum.values()) {
+ if (e.roleCode.equals(roleCode)) {
+ return e;
+ }
+ }
+ return null;
+ }
+ /**
+ * 根据code找index
+ * @param roleCode 角色编码
+ * @return
+ */
+ private static String getIndexByCode(String roleCode) {
+ for (RoleIndexConfigEnum e : RoleIndexConfigEnum.values()) {
+ if (e.roleCode.equals(roleCode)) {
+ return e.componentUrl;
+ }
+ }
+ return null;
+ }
+
+ public static String getIndexByRoles(List roles) {
+ String[] rolesArray = roles.toArray(new String[roles.size()]);
+ for (RoleIndexConfigEnum e : RoleIndexConfigEnum.values()) {
+ if (oConvertUtils.isIn(e.roleCode,rolesArray)){
+ return e.componentUrl;
+ }
+ }
+ return null;
+ }
+
+ public String getRoleCode() {
+ return roleCode;
+ }
+
+ public void setRoleCode(String roleCode) {
+ this.roleCode = roleCode;
+ }
+
+ public String getComponentUrl() {
+ return componentUrl;
+ }
+
+ public void setComponentUrl(String componentUrl) {
+ this.componentUrl = componentUrl;
+ }
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/desensitization/annotation/SensitiveDecode.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/desensitization/annotation/SensitiveDecode.java
new file mode 100644
index 0000000..698ecba
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/desensitization/annotation/SensitiveDecode.java
@@ -0,0 +1,20 @@
+package org.jeecg.common.desensitization.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 解密注解
+ *
+ * 在方法上定义 将方法返回对象中的敏感字段 解密,需要注意的是,如果没有加密过,解密会出问题,返回原字符串
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD})
+public @interface SensitiveDecode {
+
+ /**
+ * 指明需要脱敏的实体类class
+ * @return
+ */
+ Class entity() default Object.class;
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/desensitization/annotation/SensitiveEncode.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/desensitization/annotation/SensitiveEncode.java
new file mode 100644
index 0000000..eb89d75
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/desensitization/annotation/SensitiveEncode.java
@@ -0,0 +1,20 @@
+package org.jeecg.common.desensitization.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 加密注解
+ *
+ * 在方法上声明 将方法返回对象中的敏感字段 加密/格式化
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD})
+public @interface SensitiveEncode {
+
+ /**
+ * 指明需要脱敏的实体类class
+ * @return
+ */
+ Class entity() default Object.class;
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/desensitization/annotation/SensitiveField.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/desensitization/annotation/SensitiveField.java
new file mode 100644
index 0000000..a887e5a
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/desensitization/annotation/SensitiveField.java
@@ -0,0 +1,21 @@
+package org.jeecg.common.desensitization.annotation;
+
+
+import org.jeecg.common.desensitization.enums.SensitiveEnum;
+
+import java.lang.annotation.*;
+
+/**
+ * 在字段上定义 标识字段存储的信息是敏感的
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface SensitiveField {
+
+ /**
+ * 不同类型处理不同
+ * @return
+ */
+ SensitiveEnum type() default SensitiveEnum.ENCODE;
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/desensitization/aspect/SensitiveDataAspect.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/desensitization/aspect/SensitiveDataAspect.java
new file mode 100644
index 0000000..da69702
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/desensitization/aspect/SensitiveDataAspect.java
@@ -0,0 +1,81 @@
+package org.jeecg.common.desensitization.aspect;
+
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.jeecg.common.desensitization.annotation.SensitiveDecode;
+import org.jeecg.common.desensitization.annotation.SensitiveEncode;
+import org.jeecg.common.desensitization.util.SensitiveInfoUtil;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Method;
+import java.util.List;
+
+/**
+ * 敏感数据切面处理类
+ * @Author taoYan
+ * @Date 2022/4/20 17:45
+ **/
+@Slf4j
+@Aspect
+@Component
+public class SensitiveDataAspect {
+
+ /**
+ * 定义切点Pointcut
+ */
+ @Pointcut("@annotation(org.jeecg.common.desensitization.annotation.SensitiveEncode) || @annotation(org.jeecg.common.desensitization.annotation.SensitiveDecode)")
+ public void sensitivePointCut() {
+ }
+
+ @Around("sensitivePointCut()")
+ public Object around(ProceedingJoinPoint point) throws Throwable {
+ // 处理结果
+ Object result = point.proceed();
+ if(result == null){
+ return result;
+ }
+ Class resultClass = result.getClass();
+ log.debug(" resultClass = {}" , resultClass);
+
+ if(resultClass.isPrimitive()){
+ //是基本类型 直接返回 不需要处理
+ return result;
+ }
+ // 获取方法注解信息:是哪个实体、是加密还是解密
+ boolean isEncode = true;
+ Class entity = null;
+ MethodSignature methodSignature = (MethodSignature) point.getSignature();
+ Method method = methodSignature.getMethod();
+ SensitiveEncode encode = method.getAnnotation(SensitiveEncode.class);
+ if(encode==null){
+ SensitiveDecode decode = method.getAnnotation(SensitiveDecode.class);
+ if(decode!=null){
+ entity = decode.entity();
+ isEncode = false;
+ }
+ }else{
+ entity = encode.entity();
+ }
+
+ long startTime=System.currentTimeMillis();
+ if(resultClass.equals(entity) || entity.equals(Object.class)){
+ // 方法返回实体和注解的entity一样,如果注解没有申明entity属性则认为是(方法返回实体和注解的entity一样)
+ SensitiveInfoUtil.handlerObject(result, isEncode);
+ } else if(result instanceof List){
+ // 方法返回List<实体>
+ SensitiveInfoUtil.handleList(result, entity, isEncode);
+ }else{
+ // 方法返回一个对象
+ SensitiveInfoUtil.handleNestedObject(result, entity, isEncode);
+ }
+ long endTime=System.currentTimeMillis();
+ log.info((isEncode ? "加密操作," : "解密操作,") + "Aspect程序耗时:" + (endTime - startTime) + "ms");
+
+ return result;
+ }
+
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/desensitization/enums/SensitiveEnum.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/desensitization/enums/SensitiveEnum.java
new file mode 100644
index 0000000..bdffc75
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/desensitization/enums/SensitiveEnum.java
@@ -0,0 +1,55 @@
+package org.jeecg.common.desensitization.enums;
+
+/**
+ * 敏感字段信息类型
+ */
+public enum SensitiveEnum {
+
+
+ /**
+ * 加密
+ */
+ ENCODE,
+
+ /**
+ * 中文名
+ */
+ CHINESE_NAME,
+
+ /**
+ * 身份证号
+ */
+ ID_CARD,
+
+ /**
+ * 座机号
+ */
+ FIXED_PHONE,
+
+ /**
+ * 手机号
+ */
+ MOBILE_PHONE,
+
+ /**
+ * 地址
+ */
+ ADDRESS,
+
+ /**
+ * 电子邮件
+ */
+ EMAIL,
+
+ /**
+ * 银行卡
+ */
+ BANK_CARD,
+
+ /**
+ * 公司开户银行联号
+ */
+ CNAPS_CODE;
+
+
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/desensitization/util/SensitiveInfoUtil.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/desensitization/util/SensitiveInfoUtil.java
new file mode 100644
index 0000000..e872437
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/desensitization/util/SensitiveInfoUtil.java
@@ -0,0 +1,362 @@
+package org.jeecg.common.desensitization.util;
+
+import lombok.extern.slf4j.Slf4j;
+import org.jeecg.common.desensitization.annotation.SensitiveField;
+import org.jeecg.common.desensitization.enums.SensitiveEnum;
+import org.jeecg.common.util.encryption.AesEncryptUtil;
+import org.jeecg.common.util.oConvertUtils;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.ParameterizedType;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 敏感信息处理工具类
+ * @author taoYan
+ * @date 2022/4/20 18:01
+ **/
+@Slf4j
+public class SensitiveInfoUtil {
+
+ /**
+ * 处理嵌套对象
+ * @param obj 方法返回值
+ * @param entity 实体class
+ * @param isEncode 是否加密(true: 加密操作 / false:解密操作)
+ * @throws IllegalAccessException
+ */
+ public static void handleNestedObject(Object obj, Class entity, boolean isEncode) throws IllegalAccessException {
+ Field[] fields = obj.getClass().getDeclaredFields();
+ for (Field field : fields) {
+ if(field.getType().isPrimitive()){
+ continue;
+ }
+ if(field.getType().equals(entity)){
+ // 对象里面是实体
+ field.setAccessible(true);
+ Object nestedObject = field.get(obj);
+ handlerObject(nestedObject, isEncode);
+ break;
+ }else{
+ // 对象里面是List<实体>
+ if(field.getGenericType() instanceof ParameterizedType){
+ ParameterizedType pt = (ParameterizedType)field.getGenericType();
+ if(pt.getRawType().equals(List.class)){
+ if(pt.getActualTypeArguments()[0].equals(entity)){
+ field.setAccessible(true);
+ Object nestedObject = field.get(obj);
+ handleList(nestedObject, entity, isEncode);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * 处理Object
+ * @param obj 方法返回值
+ * @param isEncode 是否加密(true: 加密操作 / false:解密操作)
+ * @return
+ * @throws IllegalAccessException
+ */
+ public static Object handlerObject(Object obj, boolean isEncode) throws IllegalAccessException {
+ log.debug(" obj --> "+ obj.toString());
+ long startTime=System.currentTimeMillis();
+ if (oConvertUtils.isEmpty(obj)) {
+ return obj;
+ }
+ // 判断是不是一个对象
+ Field[] fields = obj.getClass().getDeclaredFields();
+ for (Field field : fields) {
+ boolean isSensitiveField = field.isAnnotationPresent(SensitiveField.class);
+ if(isSensitiveField){
+ // 必须有SensitiveField注解 才作处理
+ if(field.getType().isAssignableFrom(String.class)){
+ //必须是字符串类型 才作处理
+ field.setAccessible(true);
+ String realValue = (String) field.get(obj);
+ if(realValue==null || "".equals(realValue)){
+ continue;
+ }
+ SensitiveField sf = field.getAnnotation(SensitiveField.class);
+ if(isEncode==true){
+ //加密
+ String value = SensitiveInfoUtil.getEncodeData(realValue, sf.type());
+ field.set(obj, value);
+ }else{
+ //解密只处理 encode类型的
+ if(sf.type().equals(SensitiveEnum.ENCODE)){
+ String value = SensitiveInfoUtil.getDecodeData(realValue);
+ field.set(obj, value);
+ }
+ }
+ }
+ }
+ }
+ //long endTime=System.currentTimeMillis();
+ //log.info((isEncode ? "加密操作," : "解密操作,") + "当前程序耗时:" + (endTime - startTime) + "ms");
+ return obj;
+ }
+
+ /**
+ * 处理 List<实体>
+ * @param obj
+ * @param entity
+ * @param isEncode(true: 加密操作 / false:解密操作)
+ */
+ public static void handleList(Object obj, Class entity, boolean isEncode){
+ List list = (List)obj;
+ if(list.size()>0){
+ Object first = list.get(0);
+ if(first.getClass().equals(entity)){
+ for(int i=0; i
+ */
+ private static String chineseName(String fullName) {
+ if (oConvertUtils.isEmpty(fullName)) {
+ return "";
+ }
+ return formatRight(fullName, 1);
+ }
+
+ /**
+ * [中文姓名] 只显示第一个汉字,其他隐藏为2个星号
+ * @param familyName 姓
+ * @param firstName 名
+ * @return <例子:李**>
+ */
+ private static String chineseName(String familyName, String firstName) {
+ if (oConvertUtils.isEmpty(familyName) || oConvertUtils.isEmpty(firstName)) {
+ return "";
+ }
+ return chineseName(familyName + firstName);
+ }
+
+ /**
+ * [身份证号] 显示最后四位,其他隐藏。共计18位或者15位。
+ * @param id 身份证号
+ * @return <例子:*************5762>
+ */
+ private static String idCardNum(String id) {
+ if (oConvertUtils.isEmpty(id)) {
+ return "";
+ }
+ return formatLeft(id, 4);
+
+ }
+
+ /**
+ * [固定电话] 后四位,其他隐藏
+ * @param num 固定电话
+ * @return <例子:****1234>
+ */
+ private static String fixedPhone(String num) {
+ if (oConvertUtils.isEmpty(num)) {
+ return "";
+ }
+ return formatLeft(num, 4);
+ }
+
+ /**
+ * [手机号码] 前三位,后四位,其他隐藏
+ * @param num 手机号码
+ * @return <例子:138******1234>
+ */
+ private static String mobilePhone(String num) {
+ if (oConvertUtils.isEmpty(num)) {
+ return "";
+ }
+ int len = num.length();
+ if(len<11){
+ return num;
+ }
+ return formatBetween(num, 3, 4);
+ }
+
+ /**
+ * [地址] 只显示到地区,不显示详细地址;我们要对个人信息增强保护
+ * @param address 地址
+ * @param sensitiveSize 敏感信息长度
+ * @return <例子:北京市海淀区****>
+ */
+ private static String address(String address, int sensitiveSize) {
+ if (oConvertUtils.isEmpty(address)) {
+ return "";
+ }
+ int len = address.length();
+ if(len
+ */
+ private static String email(String email) {
+ if (oConvertUtils.isEmpty(email)) {
+ return "";
+ }
+ int index = email.indexOf("@");
+ if (index <= 1){
+ return email;
+ }
+ String begin = email.substring(0, 1);
+ String end = email.substring(index);
+ String stars = "**";
+ return begin + stars + end;
+ }
+
+ /**
+ * [银行卡号] 前六位,后四位,其他用星号隐藏每位1个星号
+ * @param cardNum 银行卡号
+ * @return <例子:6222600**********1234>
+ */
+ private static String bankCard(String cardNum) {
+ if (oConvertUtils.isEmpty(cardNum)) {
+ return "";
+ }
+ return formatBetween(cardNum, 6, 4);
+ }
+
+ /**
+ * [公司开户银行联号] 公司开户银行联行号,显示前两位,其他用星号隐藏,每位1个星号
+ * @param code 公司开户银行联号
+ * @return <例子:12********>
+ */
+ private static String cnapsCode(String code) {
+ if (oConvertUtils.isEmpty(code)) {
+ return "";
+ }
+ return formatRight(code, 2);
+ }
+
+
+ /**
+ * 将右边的格式化成*
+ * @param str 字符串
+ * @param reservedLength 保留长度
+ * @return 格式化后的字符串
+ */
+ private static String formatRight(String str, int reservedLength){
+ String name = str.substring(0, reservedLength);
+ String stars = String.join("", Collections.nCopies(str.length()-reservedLength, "*"));
+ return name + stars;
+ }
+
+ /**
+ * 将左边的格式化成*
+ * @param str 字符串
+ * @param reservedLength 保留长度
+ * @return 格式化后的字符串
+ */
+ private static String formatLeft(String str, int reservedLength){
+ int len = str.length();
+ String show = str.substring(len-reservedLength);
+ String stars = String.join("", Collections.nCopies(len-reservedLength, "*"));
+ return stars + show;
+ }
+
+ /**
+ * 将中间的格式化成*
+ * @param str 字符串
+ * @param beginLen 开始保留长度
+ * @param endLen 结尾保留长度
+ * @return 格式化后的字符串
+ */
+ private static String formatBetween(String str, int beginLen, int endLen){
+ int len = str.length();
+ String begin = str.substring(0, beginLen);
+ String end = str.substring(len-endLen);
+ String stars = String.join("", Collections.nCopies(len-beginLen-endLen, "*"));
+ return begin + stars + end;
+ }
+
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgBoot401Exception.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgBoot401Exception.java
new file mode 100644
index 0000000..6f7205b
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgBoot401Exception.java
@@ -0,0 +1,23 @@
+package org.jeecg.common.exception;
+
+/**
+ * @Description: jeecg-boot自定义401异常
+ * @author: jeecg-boot
+ */
+public class JeecgBoot401Exception extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ public JeecgBoot401Exception(String message){
+ super(message);
+ }
+
+ public JeecgBoot401Exception(Throwable cause)
+ {
+ super(cause);
+ }
+
+ public JeecgBoot401Exception(String message, Throwable cause)
+ {
+ super(message,cause);
+ }
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgBootException.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgBootException.java
new file mode 100644
index 0000000..fe0706c
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgBootException.java
@@ -0,0 +1,23 @@
+package org.jeecg.common.exception;
+
+/**
+ * @Description: jeecg-boot自定义异常
+ * @author: jeecg-boot
+ */
+public class JeecgBootException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ public JeecgBootException(String message){
+ super(message);
+ }
+
+ public JeecgBootException(Throwable cause)
+ {
+ super(cause);
+ }
+
+ public JeecgBootException(String message,Throwable cause)
+ {
+ super(message,cause);
+ }
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgBootExceptionHandler.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgBootExceptionHandler.java
new file mode 100644
index 0000000..6d5d8ef
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgBootExceptionHandler.java
@@ -0,0 +1,136 @@
+package org.jeecg.common.exception;
+
+import cn.hutool.core.util.ObjectUtil;
+import org.apache.shiro.authz.AuthorizationException;
+import org.apache.shiro.authz.UnauthorizedException;
+import org.jeecg.common.api.vo.Result;
+import org.jeecg.common.enums.SentinelErrorInfoEnum;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.dao.DuplicateKeyException;
+import org.springframework.data.redis.connection.PoolException;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.multipart.MaxUploadSizeExceededException;
+import org.springframework.web.servlet.NoHandlerFoundException;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 异常处理器
+ *
+ * @Author scott
+ * @Date 2019
+ */
+@RestControllerAdvice
+@Slf4j
+public class JeecgBootExceptionHandler {
+
+ /**
+ * 处理自定义异常
+ */
+ @ExceptionHandler(JeecgBootException.class)
+ public Result> handleJeecgBootException(JeecgBootException e){
+ log.error(e.getMessage(), e);
+ return Result.error(e.getMessage());
+ }
+
+ /**
+ * 处理自定义微服务异常
+ */
+ @ExceptionHandler(JeecgCloudException.class)
+ public Result> handleJeecgCloudException(JeecgCloudException e){
+ log.error(e.getMessage(), e);
+ return Result.error(e.getMessage());
+ }
+
+ /**
+ * 处理自定义异常
+ */
+ @ExceptionHandler(JeecgBoot401Exception.class)
+ @ResponseStatus(HttpStatus.UNAUTHORIZED)
+ public Result> handleJeecgBoot401Exception(JeecgBoot401Exception e){
+ log.error(e.getMessage(), e);
+ return new Result(401,e.getMessage());
+ }
+
+ @ExceptionHandler(NoHandlerFoundException.class)
+ public Result> handlerNoFoundException(Exception e) {
+ log.error(e.getMessage(), e);
+ return Result.error(404, "路径不存在,请检查路径是否正确");
+ }
+
+ @ExceptionHandler(DuplicateKeyException.class)
+ public Result> handleDuplicateKeyException(DuplicateKeyException e){
+ log.error(e.getMessage(), e);
+ return Result.error("数据库中已存在该记录");
+ }
+
+ @ExceptionHandler({UnauthorizedException.class, AuthorizationException.class})
+ public Result> handleAuthorizationException(AuthorizationException e){
+ log.error(e.getMessage(), e);
+ return Result.noauth("没有权限,请联系管理员授权");
+ }
+
+ @ExceptionHandler(Exception.class)
+ public Result> handleException(Exception e){
+ log.error(e.getMessage(), e);
+ //update-begin---author:zyf ---date:20220411 for:处理Sentinel限流自定义异常
+ Throwable throwable = e.getCause();
+ SentinelErrorInfoEnum errorInfoEnum = SentinelErrorInfoEnum.getErrorByException(throwable);
+ if (ObjectUtil.isNotEmpty(errorInfoEnum)) {
+ return Result.error(errorInfoEnum.getError());
+ }
+ //update-end---author:zyf ---date:20220411 for:处理Sentinel限流自定义异常
+ return Result.error("操作失败,"+e.getMessage());
+ }
+
+ /**
+ * @Author 政辉
+ * @param e
+ * @return
+ */
+ @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
+ public Result> httpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e){
+ StringBuffer sb = new StringBuffer();
+ sb.append("不支持");
+ sb.append(e.getMethod());
+ sb.append("请求方法,");
+ sb.append("支持以下");
+ String [] methods = e.getSupportedMethods();
+ if(methods!=null){
+ for(String str:methods){
+ sb.append(str);
+ sb.append("、");
+ }
+ }
+ log.error(sb.toString(), e);
+ //return Result.error("没有权限,请联系管理员授权");
+ return Result.error(405,sb.toString());
+ }
+
+ /**
+ * spring默认上传大小100MB 超出大小捕获异常MaxUploadSizeExceededException
+ */
+ @ExceptionHandler(MaxUploadSizeExceededException.class)
+ public Result> handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e) {
+ log.error(e.getMessage(), e);
+ return Result.error("文件大小超出10MB限制, 请压缩或降低文件质量! ");
+ }
+
+ @ExceptionHandler(DataIntegrityViolationException.class)
+ public Result> handleDataIntegrityViolationException(DataIntegrityViolationException e) {
+ log.error(e.getMessage(), e);
+ //【issues/3624】数据库执行异常handleDataIntegrityViolationException提示有误 #3624
+ return Result.error("执行数据库异常,违反了完整性例如:违反惟一约束、违反非空限制、字段内容超出长度等");
+ }
+
+ @ExceptionHandler(PoolException.class)
+ public Result> handlePoolException(PoolException e) {
+ log.error(e.getMessage(), e);
+ return Result.error("Redis 连接异常!");
+ }
+
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/executor/ConstomThreadPool.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/executor/ConstomThreadPool.java
new file mode 100644
index 0000000..95da581
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/executor/ConstomThreadPool.java
@@ -0,0 +1,21 @@
+package org.jeecg.common.executor;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+public class ConstomThreadPool extends ThreadPoolExecutor {
+ /**
+ *
+ * @param corePoolSize 核心线程池
+ * @param maximumPoolSize 线程池最大数量
+ * @param keepAliveTime 线程存活时间
+ * @param unit TimeUnit
+ * @param workQueue 工作队列,自定义大小
+ * @param poolName 线程工厂自定义线程名称
+ */
+ public ConstomThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, String poolName) {
+ super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
+ setThreadFactory(new CustomThreadFactory(poolName, false));
+ }
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/executor/CustomExecutors.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/executor/CustomExecutors.java
new file mode 100644
index 0000000..c51fdcb
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/executor/CustomExecutors.java
@@ -0,0 +1,64 @@
+package org.jeecg.common.executor;
+
+import java.util.concurrent.*;
+
+public final class CustomExecutors {
+ /**
+ * 核心线程数大小
+ */
+ private static final int CORE_POOL_SIZE = 1;
+ /**
+ * 核心线程池大小
+ */
+ private static final int MAX_POOL_SIZE = 5;
+ /**
+ * 线程存活时间
+ */
+ private static final int KEEP_ALIVE_TIME = 60;
+ /**
+ * 工作队列大小
+ */
+ private static final LinkedBlockingQueue queue = new LinkedBlockingQueue<>(1000);
+ /**
+ * 自定义线程池名前缀
+ */
+ private static final String POOL_PREFIX_NAME = "Custom-Common-Pool";
+
+ private CustomExecutors() {
+ //throw new XXXXException("un support create pool!");
+ }
+
+ private static ConstomThreadPool constomThreadPool;
+
+ /**
+ * 静态块初始化只执行一次,不关闭,整个系统公用一个线程池
+ */
+ static {
+ constomThreadPool = new ConstomThreadPool(CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS, queue, POOL_PREFIX_NAME);
+ }
+
+ /**
+ * 单例模式获取线程池
+ *
+ * @return ExecutorService
+ */
+ public static ExecutorService getInstance() {
+ return constomThreadPool;
+ }
+
+ public static Future> submit(Runnable task) {
+ return constomThreadPool.submit(task);
+ }
+
+ public static Future submit(Runnable task, T result) {
+ return constomThreadPool.submit(task, result);
+ }
+
+ public static Future submit(Callable task) {
+ return constomThreadPool.submit(task);
+ }
+
+ public static void execute(Runnable task) {
+ constomThreadPool.execute(task);
+ }
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/executor/CustomThreadFactory.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/executor/CustomThreadFactory.java
new file mode 100644
index 0000000..02d3d92
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/executor/CustomThreadFactory.java
@@ -0,0 +1,45 @@
+package org.jeecg.common.executor;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * 自定义线程工厂
+ */
+public class CustomThreadFactory implements ThreadFactory {
+ /**
+ * 线程前缀,采用AtomicInteger实现线程编号线程安全自增
+ */
+ private final AtomicInteger atomicInteger = new AtomicInteger(1);
+ /**
+ * 线程命名前缀
+ */
+ private final String namePrefix;
+ /**
+ * 线程工厂创建的线程是否是守护线程
+ */
+ private final boolean isDaemon;
+
+ public CustomThreadFactory(String prefix, boolean daemin) {
+ if (StringUtils.isNotBlank(prefix)) {
+ this.namePrefix = prefix;
+ } else {
+ this.namePrefix = "thread_pool";
+ }
+ // 是否是守护线程
+ isDaemon = daemin;
+ }
+
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread thread = new Thread(r, namePrefix + "-" + atomicInteger.getAndIncrement());
+ thread.setDaemon(isDaemon);
+ // 设置线程优先级
+ if (thread.getPriority() != Thread.NORM_PRIORITY) {
+ thread.setPriority(Thread.NORM_PRIORITY);
+ }
+ return thread;
+ }
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/handler/IFillRuleHandler.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/handler/IFillRuleHandler.java
new file mode 100644
index 0000000..7c6478b
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/handler/IFillRuleHandler.java
@@ -0,0 +1,22 @@
+package org.jeecg.common.handler;
+
+import com.alibaba.fastjson.JSONObject;
+
+/**
+ * 填值规则接口
+ *
+ * @author Yan_东
+ * 如需使用填值规则功能,规则实现类必须实现此接口
+ */
+public interface IFillRuleHandler {
+
+ /**
+ * 填值规则
+ * @param params 页面配置固定参数
+ * @param formData 动态表单参数
+ * @return
+ */
+ public Object execute(JSONObject params, JSONObject formData);
+
+}
+
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/annotation/EnumDict.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/annotation/EnumDict.java
new file mode 100644
index 0000000..1ffb760
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/annotation/EnumDict.java
@@ -0,0 +1,19 @@
+package org.jeecg.common.system.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 将枚举类转化成字典数据
+ * @Author taoYan
+ * @Date 2022/7/8 10:34
+ **/
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface EnumDict {
+
+ /**
+ * 作为字典数据的唯一编码
+ */
+ String value() default "";
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/controller/JeecgController.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/controller/JeecgController.java
new file mode 100644
index 0000000..83c52d3
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/controller/JeecgController.java
@@ -0,0 +1,205 @@
+package org.jeecg.common.system.base.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.beanutils.PropertyUtils;
+import org.apache.shiro.SecurityUtils;
+import org.jeecg.common.api.vo.Result;
+import org.jeecg.common.system.query.QueryGenerator;
+import org.jeecg.common.system.vo.LoginUser;
+import org.jeecg.common.util.oConvertUtils;
+import org.jeecgframework.poi.excel.ExcelImportUtil;
+import org.jeecgframework.poi.excel.def.NormalExcelConstants;
+import org.jeecgframework.poi.excel.entity.ExportParams;
+import org.jeecgframework.poi.excel.entity.ImportParams;
+import org.jeecgframework.poi.excel.entity.enmus.ExcelType;
+import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.multipart.MultipartHttpServletRequest;
+import org.springframework.web.servlet.ModelAndView;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @Description: Controller基类
+ * @Author: dangzhenghui@163.com
+ * @Date: 2019-4-21 8:13
+ * @Version: 1.0
+ */
+@Slf4j
+public class JeecgController> {
+ /**issues/2933 JeecgController注入service时改用protected修饰,能避免重复引用service*/
+ @Autowired
+ protected S service;
+
+ @Value("${jeecg.path.upload}")
+ private String upLoadPath;
+ /**
+ * 导出excel
+ *
+ * @param request
+ */
+ protected ModelAndView exportXls(HttpServletRequest request, T object, Class clazz, String title) {
+ // Step.1 组装查询条件
+ QueryWrapper queryWrapper = QueryGenerator.initQueryWrapper(object, request.getParameterMap());
+ LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+
+ // 过滤选中数据
+ String selections = request.getParameter("selections");
+ if (oConvertUtils.isNotEmpty(selections)) {
+ List selectionList = Arrays.asList(selections.split(","));
+ queryWrapper.in("id",selectionList);
+ }
+ // Step.2 获取导出数据
+ List exportList = service.list(queryWrapper);
+
+ // Step.3 AutoPoi 导出Excel
+ ModelAndView mv = new ModelAndView(new JeecgEntityExcelView());
+ //此处设置的filename无效 ,前端会重更新设置一下
+ mv.addObject(NormalExcelConstants.FILE_NAME, title);
+ mv.addObject(NormalExcelConstants.CLASS, clazz);
+ //update-begin--Author:liusq Date:20210126 for:图片导出报错,ImageBasePath未设置--------------------
+ String name = null != sysUser ? sysUser.getRealname() : "";
+ ExportParams exportParams=new ExportParams(title + "报表", "导出人:" + name, title);
+ exportParams.setImageBasePath(upLoadPath);
+ //update-end--Author:liusq Date:20210126 for:图片导出报错,ImageBasePath未设置----------------------
+ mv.addObject(NormalExcelConstants.PARAMS,exportParams);
+ mv.addObject(NormalExcelConstants.DATA_LIST, exportList);
+ return mv;
+ }
+ /**
+ * 根据每页sheet数量导出多sheet
+ *
+ * @param request
+ * @param object 实体类
+ * @param clazz 实体类class
+ * @param title 标题
+ * @param exportFields 导出字段自定义
+ * @param pageNum 每个sheet的数据条数
+ * @param request
+ */
+ protected ModelAndView exportXlsSheet(HttpServletRequest request, T object, Class clazz, String title,String exportFields,Integer pageNum) {
+ // Step.1 组装查询条件
+ QueryWrapper queryWrapper = QueryGenerator.initQueryWrapper(object, request.getParameterMap());
+ LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+ // Step.2 计算分页sheet数据
+ double total = service.count();
+ int count = (int)Math.ceil(total/pageNum);
+ //update-begin-author:liusq---date:20220629--for: 多sheet导出根据选择导出写法调整 ---
+ // Step.3 过滤选中数据
+ String selections = request.getParameter("selections");
+ if (oConvertUtils.isNotEmpty(selections)) {
+ List selectionList = Arrays.asList(selections.split(","));
+ queryWrapper.in("id",selectionList);
+ }
+ //update-end-author:liusq---date:20220629--for: 多sheet导出根据选择导出写法调整 ---
+ // Step.4 多sheet处理
+ List> listMap = new ArrayList>();
+ for (int i = 1; i <=count ; i++) {
+ Page page = new Page(i, pageNum);
+ IPage pageList = service.page(page, queryWrapper);
+ List exportList = pageList.getRecords();
+ Map map = new HashMap<>(5);
+ String name = null != sysUser ? sysUser.getRealname() : "";
+ ExportParams exportParams=new ExportParams(title + "报表", "导出人:" + name, title+i,upLoadPath);
+ exportParams.setType(ExcelType.XSSF);
+ //map.put("title",exportParams);
+ //表格Title
+ map.put(NormalExcelConstants.PARAMS,exportParams);
+ //表格对应实体
+ map.put(NormalExcelConstants.CLASS,clazz);
+ //数据集合
+ map.put(NormalExcelConstants.DATA_LIST, exportList);
+ listMap.add(map);
+ }
+ // Step.4 AutoPoi 导出Excel
+ ModelAndView mv = new ModelAndView(new JeecgEntityExcelView());
+ //此处设置的filename无效 ,前端会重更新设置一下
+ mv.addObject(NormalExcelConstants.FILE_NAME, title);
+ mv.addObject(NormalExcelConstants.MAP_LIST, listMap);
+ return mv;
+ }
+
+
+ /**
+ * 根据权限导出excel,传入导出字段参数
+ *
+ * @param request
+ */
+ protected ModelAndView exportXls(HttpServletRequest request, T object, Class clazz, String title,String exportFields) {
+ ModelAndView mv = this.exportXls(request,object,clazz,title);
+ mv.addObject(NormalExcelConstants.EXPORT_FIELDS,exportFields);
+ return mv;
+ }
+
+ /**
+ * 获取对象ID
+ *
+ * @return
+ */
+ private String getId(T item) {
+ try {
+ return PropertyUtils.getProperty(item, "id").toString();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * 通过excel导入数据
+ *
+ * @param request
+ * @param response
+ * @return
+ */
+ protected Result> importExcel(HttpServletRequest request, HttpServletResponse response, Class clazz) {
+ MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
+ Map fileMap = multipartRequest.getFileMap();
+ for (Map.Entry entity : fileMap.entrySet()) {
+ // 获取上传文件对象
+ MultipartFile file = entity.getValue();
+ ImportParams params = new ImportParams();
+ params.setTitleRows(2);
+ params.setHeadRows(1);
+ params.setNeedSave(true);
+ try {
+ List list = ExcelImportUtil.importExcel(file.getInputStream(), clazz, params);
+ //update-begin-author:taoyan date:20190528 for:批量插入数据
+ long start = System.currentTimeMillis();
+ service.saveBatch(list);
+ //400条 saveBatch消耗时间1592毫秒 循环插入消耗时间1947毫秒
+ //1200条 saveBatch消耗时间3687毫秒 循环插入消耗时间5212毫秒
+ log.info("消耗时间" + (System.currentTimeMillis() - start) + "毫秒");
+ //update-end-author:taoyan date:20190528 for:批量插入数据
+ return Result.ok("文件导入成功!数据行数:" + list.size());
+ } catch (Exception e) {
+ //update-begin-author:taoyan date:20211124 for: 导入数据重复增加提示
+ String msg = e.getMessage();
+ log.error(msg, e);
+ if(msg!=null && msg.indexOf("Duplicate entry")>=0){
+ return Result.error("文件导入失败:有重复数据!");
+ }else{
+ return Result.error("文件导入失败:" + e.getMessage());
+ }
+ //update-end-author:taoyan date:20211124 for: 导入数据重复增加提示
+ } finally {
+ try {
+ file.getInputStream().close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ return Result.error("文件导入失败!");
+ }
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/entity/JeecgEntity.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/entity/JeecgEntity.java
new file mode 100644
index 0000000..2eebaff
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/entity/JeecgEntity.java
@@ -0,0 +1,68 @@
+package org.jeecg.common.system.base.entity;
+
+import java.io.Serializable;
+
+import org.jeecgframework.poi.excel.annotation.Excel;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * @Description: Entity基类
+ * @Author: dangzhenghui@163.com
+ * @Date: 2019-4-28
+ * @Version: 1.1
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+public class JeecgEntity implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * ID
+ */
+ @TableId(type = IdType.ASSIGN_ID)
+ @ApiModelProperty(value = "ID")
+ private java.lang.String id;
+
+ /**
+ * 创建人
+ */
+ @ApiModelProperty(value = "创建人")
+ @Excel(name = "创建人", width = 15)
+ private java.lang.String createBy;
+
+ /**
+ * 创建时间
+ */
+ @ApiModelProperty(value = "创建时间")
+ @Excel(name = "创建时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
+ @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private java.util.Date createTime;
+
+ /**
+ * 更新人
+ */
+ @ApiModelProperty(value = "更新人")
+ @Excel(name = "更新人", width = 15)
+ private java.lang.String updateBy;
+
+ /**
+ * 更新时间
+ */
+ @ApiModelProperty(value = "更新时间")
+ @Excel(name = "更新时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
+ @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private java.util.Date updateTime;
+
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/service/JeecgService.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/service/JeecgService.java
new file mode 100644
index 0000000..919f3ff
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/service/JeecgService.java
@@ -0,0 +1,12 @@
+package org.jeecg.common.system.base.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * @Description: Service基类
+ * @Author: dangzhenghui@163.com
+ * @Date: 2019-4-21 8:13
+ * @Version: 1.0
+ */
+public interface JeecgService extends IService {
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/service/impl/JeecgServiceImpl.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/service/impl/JeecgServiceImpl.java
new file mode 100644
index 0000000..f19ba84
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/service/impl/JeecgServiceImpl.java
@@ -0,0 +1,19 @@
+package org.jeecg.common.system.base.service.impl;
+
+import org.jeecg.common.system.base.entity.JeecgEntity;
+import org.jeecg.common.system.base.service.JeecgService;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @Description: ServiceImpl基类
+ * @Author: dangzhenghui@163.com
+ * @Date: 2019-4-21 8:13
+ * @Version: 1.0
+ */
+@Slf4j
+public class JeecgServiceImpl, T extends JeecgEntity> extends ServiceImpl implements JeecgService {
+
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/query/MatchTypeEnum.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/query/MatchTypeEnum.java
new file mode 100644
index 0000000..d19342d
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/query/MatchTypeEnum.java
@@ -0,0 +1,45 @@
+package org.jeecg.common.system.query;
+
+import org.jeecg.common.util.oConvertUtils;
+
+/**
+ * 查询链接规则
+ *
+ * @Author Sunjianlei
+ */
+public enum MatchTypeEnum {
+
+ /**查询链接规则 AND*/
+ AND("AND"),
+ /**查询链接规则 OR*/
+ OR("OR");
+
+ private String value;
+
+ MatchTypeEnum(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public static MatchTypeEnum getByValue(Object value) {
+ if (oConvertUtils.isEmpty(value)) {
+ return null;
+ }
+ return getByValue(value.toString());
+ }
+
+ public static MatchTypeEnum getByValue(String value) {
+ if (oConvertUtils.isEmpty(value)) {
+ return null;
+ }
+ for (MatchTypeEnum val : values()) {
+ if (val.getValue().toLowerCase().equals(value.toLowerCase())) {
+ return val;
+ }
+ }
+ return null;
+ }
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/query/QueryCondition.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/query/QueryCondition.java
new file mode 100644
index 0000000..566280d
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/query/QueryCondition.java
@@ -0,0 +1,73 @@
+package org.jeecg.common.system.query;
+
+import java.io.Serializable;
+
+/**
+ * @Description: QueryCondition
+ * @author: jeecg-boot
+ */
+public class QueryCondition implements Serializable {
+
+ private static final long serialVersionUID = 4740166316629191651L;
+
+ private String field;
+ /** 组件的类型(例如:input、select、radio) */
+ private String type;
+ /**
+ * 对应的数据库字段的类型
+ * 支持:int、bigDecimal、short、long、float、double、boolean
+ */
+ private String dbType;
+ private String rule;
+ private String val;
+
+ public String getField() {
+ return field;
+ }
+
+ public void setField(String field) {
+ this.field = field;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getDbType() {
+ return dbType;
+ }
+
+ public void setDbType(String dbType) {
+ this.dbType = dbType;
+ }
+
+ public String getRule() {
+ return rule;
+ }
+
+ public void setRule(String rule) {
+ this.rule = rule;
+ }
+
+ public String getVal() {
+ return val;
+ }
+
+ public void setVal(String val) {
+ this.val = val;
+ }
+
+ @Override
+ public String toString(){
+ StringBuffer sb =new StringBuffer();
+ if(field == null || "".equals(field)){
+ return "";
+ }
+ sb.append(this.field).append(" ").append(this.rule).append(" ").append(this.type).append(" ").append(this.dbType).append(" ").append(this.val);
+ return sb.toString();
+ }
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/query/QueryGenerator.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/query/QueryGenerator.java
new file mode 100644
index 0000000..4a1411f
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/query/QueryGenerator.java
@@ -0,0 +1,1231 @@
+package org.jeecg.common.system.query;
+
+import java.beans.PropertyDescriptor;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Field;
+import java.math.BigDecimal;
+import java.net.URLDecoder;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import org.apache.commons.beanutils.PropertyUtils;
+import org.jeecg.common.constant.CommonConstant;
+import org.jeecg.common.constant.DataBaseConstant;
+import org.jeecg.common.constant.SymbolConstant;
+import org.jeecg.common.exception.JeecgBootException;
+import org.jeecg.common.system.util.JeecgDataAutorUtils;
+import org.jeecg.common.system.util.JwtUtil;
+import org.jeecg.common.system.vo.SysPermissionDataRuleModel;
+import org.jeecg.common.util.CommonUtils;
+import org.jeecg.common.util.DateUtils;
+import org.jeecg.common.util.SqlInjectionUtil;
+import org.jeecg.common.util.oConvertUtils;
+import org.springframework.util.NumberUtils;
+
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @Description: 查询生成器
+ * @author: jeecg-boot
+ */
+@Slf4j
+public class QueryGenerator {
+ public static final String SQL_RULES_COLUMN = "SQL_RULES_COLUMN";
+
+ private static final String BEGIN = "_begin";
+ private static final String END = "_end";
+ /**
+ * 数字类型字段,拼接此后缀 接受多值参数
+ */
+ private static final String MULTI = "_MultiString";
+ private static final String STAR = "*";
+ private static final String COMMA = ",";
+ /**
+ * 查询 逗号转义符 相当于一个逗号【作废】
+ */
+ public static final String QUERY_COMMA_ESCAPE = "++";
+ private static final String NOT_EQUAL = "!";
+ /**页面带有规则值查询,空格作为分隔符*/
+ private static final String QUERY_SEPARATE_KEYWORD = " ";
+ /**高级查询前端传来的参数名*/
+ private static final String SUPER_QUERY_PARAMS = "superQueryParams";
+ /** 高级查询前端传来的拼接方式参数名 */
+ private static final String SUPER_QUERY_MATCH_TYPE = "superQueryMatchType";
+ /** 单引号 */
+ public static final String SQL_SQ = "'";
+ /**排序列*/
+ private static final String ORDER_COLUMN = "column";
+ /**排序方式*/
+ private static final String ORDER_TYPE = "order";
+ private static final String ORDER_TYPE_ASC = "ASC";
+
+ /**mysql 模糊查询之特殊字符下划线 (_、\)*/
+ public static final String LIKE_MYSQL_SPECIAL_STRS = "_,%";
+
+ /**日期格式化yyyy-MM-dd*/
+ public static final String YYYY_MM_DD = "yyyy-MM-dd";
+
+ /**to_date*/
+ public static final String TO_DATE = "to_date";
+
+ /**时间格式化 */
+ private static final ThreadLocal LOCAL = new ThreadLocal();
+ private static SimpleDateFormat getTime(){
+ SimpleDateFormat time = LOCAL.get();
+ if(time == null){
+ time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ LOCAL.set(time);
+ }
+ return time;
+ }
+
+ /**
+ * 获取查询条件构造器QueryWrapper实例 通用查询条件已被封装完成
+ * @param searchObj 查询实体
+ * @param parameterMap request.getParameterMap()
+ * @return QueryWrapper实例
+ */
+ public static QueryWrapper initQueryWrapper(T searchObj,Map parameterMap){
+ long start = System.currentTimeMillis();
+ QueryWrapper queryWrapper = new QueryWrapper();
+ installMplus(queryWrapper, searchObj, parameterMap);
+ log.debug("---查询条件构造器初始化完成,耗时:"+(System.currentTimeMillis()-start)+"毫秒----");
+ return queryWrapper;
+ }
+
+ /**
+ * 组装Mybatis Plus 查询条件
+ * 使用此方法 需要有如下几点注意:
+ * 1.使用QueryWrapper 而非LambdaQueryWrapper;
+ * 2.实例化QueryWrapper时不可将实体传入参数
+ * 错误示例:如QueryWrapper queryWrapper = new QueryWrapper(jeecgDemo);
+ * 正确示例:QueryWrapper queryWrapper = new QueryWrapper();
+ * 3.也可以不使用这个方法直接调用 {@link #initQueryWrapper}直接获取实例
+ */
+ private static void installMplus(QueryWrapper> queryWrapper,Object searchObj,Map parameterMap) {
+
+ /*
+ * 注意:权限查询由前端配置数据规则 当一个人有多个所属部门时候 可以在规则配置包含条件 orgCode 包含 #{sys_org_code}
+ 但是不支持在自定义SQL中写orgCode in #{sys_org_code}
+ 当一个人只有一个部门 就直接配置等于条件: orgCode 等于 #{sys_org_code} 或者配置自定义SQL: orgCode = '#{sys_org_code}'
+ */
+
+ //区间条件组装 模糊查询 高级查询组装 简单排序 权限查询
+ PropertyDescriptor[] origDescriptors = PropertyUtils.getPropertyDescriptors(searchObj);
+ Map ruleMap = getRuleMap();
+
+ //权限规则自定义SQL表达式
+ for (String c : ruleMap.keySet()) {
+ if(oConvertUtils.isNotEmpty(c) && c.startsWith(SQL_RULES_COLUMN)){
+ queryWrapper.and(i ->i.apply(getSqlRuleValue(ruleMap.get(c).getRuleValue())));
+ }
+ }
+
+ String name, type, column;
+ // update-begin--Author:taoyan Date:20200923 for:issues/1671 如果字段加注解了@TableField(exist = false),不走DB查询-------
+ //定义实体字段和数据库字段名称的映射 高级查询中 只能获取实体字段 如果设置TableField注解 那么查询条件会出问题
+ Map fieldColumnMap = new HashMap<>(5);
+ for (int i = 0; i < origDescriptors.length; i++) {
+ //aliasName = origDescriptors[i].getName(); mybatis 不存在实体属性 不用处理别名的情况
+ name = origDescriptors[i].getName();
+ type = origDescriptors[i].getPropertyType().toString();
+ try {
+ if (judgedIsUselessField(name)|| !PropertyUtils.isReadable(searchObj, name)) {
+ continue;
+ }
+
+ Object value = PropertyUtils.getSimpleProperty(searchObj, name);
+ column = getTableFieldName(searchObj.getClass(), name);
+ if(column==null){
+ //column为null只有一种情况 那就是 添加了注解@TableField(exist = false) 后续都不用处理了
+ continue;
+ }
+ fieldColumnMap.put(name,column);
+ //数据权限查询
+ if(ruleMap.containsKey(name)) {
+ addRuleToQueryWrapper(ruleMap.get(name), column, origDescriptors[i].getPropertyType(), queryWrapper);
+ }
+ //区间查询
+ doIntervalQuery(queryWrapper, parameterMap, type, name, column);
+ //判断单值 参数带不同标识字符串 走不同的查询
+ //TODO 这种前后带逗号的支持分割后模糊查询(多选字段查询生效) 示例:,1,3,
+ if (null != value && value.toString().startsWith(COMMA) && value.toString().endsWith(COMMA)) {
+ String multiLikeval = value.toString().replace(",,", COMMA);
+ String[] vals = multiLikeval.substring(1, multiLikeval.length()).split(COMMA);
+ final String field = oConvertUtils.camelToUnderline(column);
+ if(vals.length>1) {
+ queryWrapper.and(j -> {
+ log.info("---查询过滤器,Query规则---field:{}, rule:{}, value:{}", field, "like", vals[0]);
+ j = j.like(field,vals[0]);
+ for (int k=1;k j.like(field,vals[0]));
+ }
+ }else {
+ //根据参数值带什么关键字符串判断走什么类型的查询
+ QueryRuleEnum rule = convert2Rule(value);
+ value = replaceValue(rule,value);
+ // add -begin 添加判断为字符串时设为全模糊查询
+ //if( (rule==null || QueryRuleEnum.EQ.equals(rule)) && "class java.lang.String".equals(type)) {
+ // 可以设置左右模糊或全模糊,因人而异
+ //rule = QueryRuleEnum.LIKE;
+ //}
+ // add -end 添加判断为字符串时设为全模糊查询
+ addEasyQuery(queryWrapper, column, rule, value);
+ }
+
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ }
+ }
+ // 排序逻辑 处理
+ doMultiFieldsOrder(queryWrapper, parameterMap, fieldColumnMap.keySet());
+
+ //高级查询
+ doSuperQuery(queryWrapper, parameterMap, fieldColumnMap);
+ // update-end--Author:taoyan Date:20200923 for:issues/1671 如果字段加注解了@TableField(exist = false),不走DB查询-------
+
+ }
+
+
+ /**
+ * 区间查询
+ * @param queryWrapper query对象
+ * @param parameterMap 参数map
+ * @param type 字段类型
+ * @param filedName 字段名称
+ * @param columnName 列名称
+ */
+ private static void doIntervalQuery(QueryWrapper> queryWrapper, Map parameterMap, String type, String filedName, String columnName) throws ParseException {
+ // 添加 判断是否有区间值
+ String endValue = null,beginValue = null;
+ if (parameterMap != null && parameterMap.containsKey(filedName + BEGIN)) {
+ beginValue = parameterMap.get(filedName + BEGIN)[0].trim();
+ addQueryByRule(queryWrapper, columnName, type, beginValue, QueryRuleEnum.GE);
+
+ }
+ if (parameterMap != null && parameterMap.containsKey(filedName + END)) {
+ endValue = parameterMap.get(filedName + END)[0].trim();
+ addQueryByRule(queryWrapper, columnName, type, endValue, QueryRuleEnum.LE);
+ }
+ //多值查询
+ if (parameterMap != null && parameterMap.containsKey(filedName + MULTI)) {
+ endValue = parameterMap.get(filedName + MULTI)[0].trim();
+ addQueryByRule(queryWrapper, columnName.replace(MULTI,""), type, endValue, QueryRuleEnum.IN);
+ }
+ }
+
+ private static void doMultiFieldsOrder(QueryWrapper> queryWrapper,Map parameterMap, Set allFields) {
+ String column=null,order=null;
+ if(parameterMap!=null&& parameterMap.containsKey(ORDER_COLUMN)) {
+ column = parameterMap.get(ORDER_COLUMN)[0];
+ }
+ if(parameterMap!=null&& parameterMap.containsKey(ORDER_TYPE)) {
+ order = parameterMap.get(ORDER_TYPE)[0];
+ }
+ log.debug("排序规则>>列:" + column + ",排序方式:" + order);
+ if (oConvertUtils.isNotEmpty(column) && oConvertUtils.isNotEmpty(order)) {
+ //字典字段,去掉字典翻译文本后缀
+ if(column.endsWith(CommonConstant.DICT_TEXT_SUFFIX)) {
+ column = column.substring(0, column.lastIndexOf(CommonConstant.DICT_TEXT_SUFFIX));
+ }
+
+ //update-begin-author:taoyan date:2022-5-16 for: issues/3676 获取系统用户列表时,使用SQL注入生效
+ //判断column是不是当前实体的
+ log.info("当前字段有:"+ allFields);
+ if (!allColumnExist(column, allFields)) {
+ throw new JeecgBootException("请注意,将要排序的列字段不存在:" + column);
+ }
+ //update-end-author:taoyan date:2022-5-16 for: issues/3676 获取系统用户列表时,使用SQL注入生效
+
+ //SQL注入check
+ SqlInjectionUtil.filterContent(column);
+
+ //update-begin--Author:scott Date:20210531 for:36 多条件排序无效问题修正-------
+ // 排序规则修改
+ // 将现有排序 _ 前端传递排序条件{....,column: 'column1,column2',order: 'desc'} 翻译成sql "column1,column2 desc"
+ // 修改为 _ 前端传递排序条件{....,column: 'column1,column2',order: 'desc'} 翻译成sql "column1 desc,column2 desc"
+ if (order.toUpperCase().indexOf(ORDER_TYPE_ASC)>=0) {
+ //queryWrapper.orderByAsc(oConvertUtils.camelToUnderline(column));
+ String columnStr = oConvertUtils.camelToUnderline(column);
+ String[] columnArray = columnStr.split(",");
+ queryWrapper.orderByAsc(Arrays.asList(columnArray));
+ } else {
+ //queryWrapper.orderByDesc(oConvertUtils.camelToUnderline(column));
+ String columnStr = oConvertUtils.camelToUnderline(column);
+ String[] columnArray = columnStr.split(",");
+ queryWrapper.orderByDesc(Arrays.asList(columnArray));
+ }
+ //update-end--Author:scott Date:20210531 for:36 多条件排序无效问题修正-------
+ }
+ }
+
+ //update-begin-author:taoyan date:2022-5-23 for: issues/3676 获取系统用户列表时,使用SQL注入生效
+ /**
+ * 多字段排序 判断所传字段是否存在
+ * @return
+ */
+ private static boolean allColumnExist(String columnStr, Set allFields){
+ boolean exist = true;
+ if(columnStr.indexOf(COMMA)>=0){
+ String[] arr = columnStr.split(COMMA);
+ for(String column: arr){
+ if(!allFields.contains(column)){
+ exist = false;
+ break;
+ }
+ }
+ }else{
+ exist = allFields.contains(columnStr);
+ }
+ return exist;
+ }
+ //update-end-author:taoyan date:2022-5-23 for: issues/3676 获取系统用户列表时,使用SQL注入生效
+
+ /**
+ * 高级查询
+ * @param queryWrapper 查询对象
+ * @param parameterMap 参数对象
+ * @param fieldColumnMap 实体字段和数据库列对应的map
+ */
+ private static void doSuperQuery(QueryWrapper> queryWrapper,Map parameterMap, Map fieldColumnMap) {
+ if(parameterMap!=null&& parameterMap.containsKey(SUPER_QUERY_PARAMS)){
+ String superQueryParams = parameterMap.get(SUPER_QUERY_PARAMS)[0];
+ String superQueryMatchType = parameterMap.get(SUPER_QUERY_MATCH_TYPE) != null ? parameterMap.get(SUPER_QUERY_MATCH_TYPE)[0] : MatchTypeEnum.AND.getValue();
+ MatchTypeEnum matchType = MatchTypeEnum.getByValue(superQueryMatchType);
+ // update-begin--Author:sunjianlei Date:20200325 for:高级查询的条件要用括号括起来,防止和用户的其他条件冲突 -------
+ try {
+ superQueryParams = URLDecoder.decode(superQueryParams, "UTF-8");
+ List conditions = JSON.parseArray(superQueryParams, QueryCondition.class);
+ if (conditions == null || conditions.size() == 0) {
+ return;
+ }
+ // update-begin-author:sunjianlei date:20220119 for: 【JTC-573】 过滤空条件查询,防止 sql 拼接多余的 and
+ List filterConditions = conditions.stream().filter(
+ rule -> oConvertUtils.isNotEmpty(rule.getField())
+ && oConvertUtils.isNotEmpty(rule.getRule())
+ && oConvertUtils.isNotEmpty(rule.getVal())
+ ).collect(Collectors.toList());
+ if (filterConditions.size() == 0) {
+ return;
+ }
+ // update-end-author:sunjianlei date:20220119 for: 【JTC-573】 过滤空条件查询,防止 sql 拼接多余的 and
+ log.info("---高级查询参数-->" + filterConditions);
+
+ queryWrapper.and(andWrapper -> {
+ for (int i = 0; i < filterConditions.size(); i++) {
+ QueryCondition rule = filterConditions.get(i);
+ if (oConvertUtils.isNotEmpty(rule.getField())
+ && oConvertUtils.isNotEmpty(rule.getRule())
+ && oConvertUtils.isNotEmpty(rule.getVal())) {
+
+ log.debug("SuperQuery ==> " + rule.toString());
+
+ //update-begin-author:taoyan date:20201228 for: 【高级查询】 oracle 日期等于查询报错
+ Object queryValue = rule.getVal();
+ if("date".equals(rule.getType())){
+ queryValue = DateUtils.str2Date(rule.getVal(),DateUtils.date_sdf.get());
+ }else if("datetime".equals(rule.getType())){
+ queryValue = DateUtils.str2Date(rule.getVal(), DateUtils.datetimeFormat.get());
+ }
+ // update-begin--author:sunjianlei date:20210702 for:【/issues/I3VR8E】高级查询没有类型转换,查询参数都是字符串类型 ----
+ String dbType = rule.getDbType();
+ if (oConvertUtils.isNotEmpty(dbType)) {
+ try {
+ String valueStr = String.valueOf(queryValue);
+ switch (dbType.toLowerCase().trim()) {
+ case "int":
+ queryValue = Integer.parseInt(valueStr);
+ break;
+ case "bigdecimal":
+ queryValue = new BigDecimal(valueStr);
+ break;
+ case "short":
+ queryValue = Short.parseShort(valueStr);
+ break;
+ case "long":
+ queryValue = Long.parseLong(valueStr);
+ break;
+ case "float":
+ queryValue = Float.parseFloat(valueStr);
+ break;
+ case "double":
+ queryValue = Double.parseDouble(valueStr);
+ break;
+ case "boolean":
+ queryValue = Boolean.parseBoolean(valueStr);
+ break;
+ default:
+ }
+ } catch (Exception e) {
+ log.error("高级查询值转换失败:", e);
+ }
+ }
+ // update-begin--author:sunjianlei date:20210702 for:【/issues/I3VR8E】高级查询没有类型转换,查询参数都是字符串类型 ----
+ addEasyQuery(andWrapper, fieldColumnMap.get(rule.getField()), QueryRuleEnum.getByValue(rule.getRule()), queryValue);
+ //update-end-author:taoyan date:20201228 for: 【高级查询】 oracle 日期等于查询报错
+
+ // 如果拼接方式是OR,就拼接OR
+ if (MatchTypeEnum.OR == matchType && i < (filterConditions.size() - 1)) {
+ andWrapper.or();
+ }
+ }
+ }
+ //return andWrapper;
+ });
+ } catch (UnsupportedEncodingException e) {
+ log.error("--高级查询参数转码失败:" + superQueryParams, e);
+ } catch (Exception e) {
+ log.error("--高级查询拼接失败:" + e.getMessage());
+ e.printStackTrace();
+ }
+ // update-end--Author:sunjianlei Date:20200325 for:高级查询的条件要用括号括起来,防止和用户的其他条件冲突 -------
+ }
+ //log.info(" superQuery getCustomSqlSegment: "+ queryWrapper.getCustomSqlSegment());
+ }
+ /**
+ * 根据所传的值 转化成对应的比较方式
+ * 支持><= like in !
+ * @param value
+ * @return
+ */
+ public static QueryRuleEnum convert2Rule(Object value) {
+ // 避免空数据
+ // update-begin-author:taoyan date:20210629 for: 查询条件输入空格导致return null后续判断导致抛出null异常
+ if (value == null) {
+ return QueryRuleEnum.EQ;
+ }
+ String val = (value + "").toString().trim();
+ if (val.length() == 0) {
+ return QueryRuleEnum.EQ;
+ }
+ // update-end-author:taoyan date:20210629 for: 查询条件输入空格导致return null后续判断导致抛出null异常
+ QueryRuleEnum rule =null;
+
+ //update-begin--Author:scott Date:20190724 for:initQueryWrapper组装sql查询条件错误 #284-------------------
+ //TODO 此处规则,只适用于 le lt ge gt
+ // step 2 .>= =<
+ int length2 = 2;
+ int length3 = 3;
+ if (rule == null && val.length() >= length3) {
+ if(QUERY_SEPARATE_KEYWORD.equals(val.substring(length2, length3))){
+ rule = QueryRuleEnum.getByValue(val.substring(0, 2));
+ }
+ }
+ // step 1 .> <
+ if (rule == null && val.length() >= length2) {
+ if(QUERY_SEPARATE_KEYWORD.equals(val.substring(1, length2))){
+ rule = QueryRuleEnum.getByValue(val.substring(0, 1));
+ }
+ }
+ //update-end--Author:scott Date:20190724 for:initQueryWrapper组装sql查询条件错误 #284---------------------
+
+ // step 3 like
+ //update-begin-author:taoyan for: /issues/3382 默认带*就走模糊,但是如果只有一个*,那么走等于查询
+ if(rule == null && val.equals(STAR)){
+ rule = QueryRuleEnum.EQ;
+ }
+ //update-end-author:taoyan for: /issues/3382 默认带*就走模糊,但是如果只有一个*,那么走等于查询
+ if (rule == null && val.contains(STAR)) {
+ if (val.startsWith(STAR) && val.endsWith(STAR)) {
+ rule = QueryRuleEnum.LIKE;
+ } else if (val.startsWith(STAR)) {
+ rule = QueryRuleEnum.LEFT_LIKE;
+ } else if(val.endsWith(STAR)){
+ rule = QueryRuleEnum.RIGHT_LIKE;
+ }
+ }
+
+ // step 4 in
+ if (rule == null && val.contains(COMMA)) {
+ //TODO in 查询这里应该有个bug 如果一字段本身就是多选 此时用in查询 未必能查询出来
+ rule = QueryRuleEnum.IN;
+ }
+ // step 5 !=
+ if(rule == null && val.startsWith(NOT_EQUAL)){
+ rule = QueryRuleEnum.NE;
+ }
+ // step 6 xx+xx+xx 这种情况适用于如果想要用逗号作精确查询 但是系统默认逗号走in 所以可以用++替换【此逻辑作废】
+ if(rule == null && val.indexOf(QUERY_COMMA_ESCAPE)>0){
+ rule = QueryRuleEnum.EQ_WITH_ADD;
+ }
+
+ //update-begin--Author:taoyan Date:20201229 for:initQueryWrapper组装sql查询条件错误 #284---------------------
+ //特殊处理:Oracle的表达式to_date('xxx','yyyy-MM-dd')含有逗号,会被识别为in查询,转为等于查询
+ if(rule == QueryRuleEnum.IN && val.indexOf(YYYY_MM_DD)>=0 && val.indexOf(TO_DATE)>=0){
+ rule = QueryRuleEnum.EQ;
+ }
+ //update-end--Author:taoyan Date:20201229 for:initQueryWrapper组装sql查询条件错误 #284---------------------
+
+ return rule != null ? rule : QueryRuleEnum.EQ;
+ }
+
+ /**
+ * 替换掉关键字字符
+ *
+ * @param rule
+ * @param value
+ * @return
+ */
+ private static Object replaceValue(QueryRuleEnum rule, Object value) {
+ if (rule == null) {
+ return null;
+ }
+ if (! (value instanceof String)){
+ return value;
+ }
+ String val = (value + "").toString().trim();
+ //update-begin-author:taoyan date:20220302 for: 查询条件的值为等号(=)bug #3443
+ if(QueryRuleEnum.EQ.getValue().equals(val)){
+ return val;
+ }
+ //update-end-author:taoyan date:20220302 for: 查询条件的值为等号(=)bug #3443
+ if (rule == QueryRuleEnum.LIKE) {
+ value = val.substring(1, val.length() - 1);
+ //mysql 模糊查询之特殊字符下划线 (_、\)
+ value = specialStrConvert(value.toString());
+ } else if (rule == QueryRuleEnum.LEFT_LIKE || rule == QueryRuleEnum.NE) {
+ value = val.substring(1);
+ //mysql 模糊查询之特殊字符下划线 (_、\)
+ value = specialStrConvert(value.toString());
+ } else if (rule == QueryRuleEnum.RIGHT_LIKE) {
+ value = val.substring(0, val.length() - 1);
+ //mysql 模糊查询之特殊字符下划线 (_、\)
+ value = specialStrConvert(value.toString());
+ } else if (rule == QueryRuleEnum.IN) {
+ value = val.split(",");
+ } else if (rule == QueryRuleEnum.EQ_WITH_ADD) {
+ value = val.replaceAll("\\+\\+", COMMA);
+ }else {
+ //update-begin--Author:scott Date:20190724 for:initQueryWrapper组装sql查询条件错误 #284-------------------
+ if(val.startsWith(rule.getValue())){
+ //TODO 此处逻辑应该注释掉-> 如果查询内容中带有查询匹配规则符号,就会被截取的(比如:>=您好)
+ value = val.replaceFirst(rule.getValue(),"");
+ }else if(val.startsWith(rule.getCondition()+QUERY_SEPARATE_KEYWORD)){
+ value = val.replaceFirst(rule.getCondition()+QUERY_SEPARATE_KEYWORD,"").trim();
+ }
+ //update-end--Author:scott Date:20190724 for:initQueryWrapper组装sql查询条件错误 #284-------------------
+ }
+ return value;
+ }
+
+ private static void addQueryByRule(QueryWrapper> queryWrapper,String name,String type,String value,QueryRuleEnum rule) throws ParseException {
+ if(oConvertUtils.isNotEmpty(value)) {
+ //update-begin--Author:sunjianlei Date:20220104 for:【JTC-409】修复逗号分割情况下没有转换类型,导致类型严格的数据库查询报错 -------------------
+ // 针对数字类型字段,多值查询
+ if(value.contains(COMMA)){
+ Object[] temp = Arrays.stream(value.split(COMMA)).map(v -> {
+ try {
+ return QueryGenerator.parseByType(v, type, rule);
+ } catch (ParseException e) {
+ e.printStackTrace();
+ return v;
+ }
+ }).toArray();
+ addEasyQuery(queryWrapper, name, rule, temp);
+ return;
+ }
+ Object temp = QueryGenerator.parseByType(value, type, rule);
+ addEasyQuery(queryWrapper, name, rule, temp);
+ //update-end--Author:sunjianlei Date:20220104 for:【JTC-409】修复逗号分割情况下没有转换类型,导致类型严格的数据库查询报错 -------------------
+ }
+ }
+
+ /**
+ * 根据类型转换给定的值
+ * @param value
+ * @param type
+ * @param rule
+ * @return
+ * @throws ParseException
+ */
+ private static Object parseByType(String value, String type, QueryRuleEnum rule) throws ParseException {
+ Object temp;
+ switch (type) {
+ case "class java.lang.Integer":
+ temp = Integer.parseInt(value);
+ break;
+ case "class java.math.BigDecimal":
+ temp = new BigDecimal(value);
+ break;
+ case "class java.lang.Short":
+ temp = Short.parseShort(value);
+ break;
+ case "class java.lang.Long":
+ temp = Long.parseLong(value);
+ break;
+ case "class java.lang.Float":
+ temp = Float.parseFloat(value);
+ break;
+ case "class java.lang.Double":
+ temp = Double.parseDouble(value);
+ break;
+ case "class java.util.Date":
+ temp = getDateQueryByRule(value, rule);
+ break;
+ default:
+ temp = value;
+ break;
+ }
+ return temp;
+ }
+
+ /**
+ * 获取日期类型的值
+ * @param value
+ * @param rule
+ * @return
+ * @throws ParseException
+ */
+ private static Date getDateQueryByRule(String value,QueryRuleEnum rule) throws ParseException {
+ Date date = null;
+ int length = 10;
+ if(value.length()==length) {
+ if(rule==QueryRuleEnum.GE) {
+ //比较大于
+ date = getTime().parse(value + " 00:00:00");
+ }else if(rule==QueryRuleEnum.LE) {
+ //比较小于
+ date = getTime().parse(value + " 23:59:59");
+ }
+ //TODO 日期类型比较特殊 可能oracle下不一定好使
+ }
+ if(date==null) {
+ date = getTime().parse(value);
+ }
+ return date;
+ }
+
+ /**
+ * 根据规则走不同的查询
+ * @param queryWrapper QueryWrapper
+ * @param name 字段名字
+ * @param rule 查询规则
+ * @param value 查询条件值
+ */
+ public static void addEasyQuery(QueryWrapper> queryWrapper, String name, QueryRuleEnum rule, Object value) {
+ if (value == null || rule == null || oConvertUtils.isEmpty(value)) {
+ return;
+ }
+ name = oConvertUtils.camelToUnderline(name);
+ log.info("---查询过滤器,Query规则---field:{}, rule:{}, value:{}",name,rule.getValue(),value);
+ switch (rule) {
+ case GT:
+ queryWrapper.gt(name, value);
+ break;
+ case GE:
+ queryWrapper.ge(name, value);
+ break;
+ case LT:
+ queryWrapper.lt(name, value);
+ break;
+ case LE:
+ queryWrapper.le(name, value);
+ break;
+ case EQ:
+ case EQ_WITH_ADD:
+ queryWrapper.eq(name, value);
+ break;
+ case NE:
+ queryWrapper.ne(name, value);
+ break;
+ case IN:
+ if(value instanceof String) {
+ queryWrapper.in(name, (Object[])value.toString().split(COMMA));
+ }else if(value instanceof String[]) {
+ queryWrapper.in(name, (Object[]) value);
+ }
+ //update-begin-author:taoyan date:20200909 for:【bug】in 类型多值查询 不适配postgresql #1671
+ else if(value.getClass().isArray()) {
+ queryWrapper.in(name, (Object[])value);
+ }else {
+ queryWrapper.in(name, value);
+ }
+ //update-end-author:taoyan date:20200909 for:【bug】in 类型多值查询 不适配postgresql #1671
+ break;
+ case LIKE:
+ queryWrapper.like(name, value);
+ break;
+ case LEFT_LIKE:
+ queryWrapper.likeLeft(name, value);
+ break;
+ case RIGHT_LIKE:
+ queryWrapper.likeRight(name, value);
+ break;
+ default:
+ log.info("--查询规则未匹配到---");
+ break;
+ }
+ }
+ /**
+ *
+ * @param name
+ * @return
+ */
+ private static boolean judgedIsUselessField(String name) {
+ return "class".equals(name) || "ids".equals(name)
+ || "page".equals(name) || "rows".equals(name)
+ || "sort".equals(name) || "order".equals(name);
+ }
+
+
+
+ /**
+ * 获取请求对应的数据权限规则 TODO 相同列权限多个 有问题
+ * @return
+ */
+ public static Map getRuleMap() {
+ Map ruleMap = new HashMap<>(5);
+ List list =JeecgDataAutorUtils.loadDataSearchConditon();
+ if(list != null&&list.size()>0){
+ if(list.get(0)==null){
+ return ruleMap;
+ }
+ for (SysPermissionDataRuleModel rule : list) {
+ String column = rule.getRuleColumn();
+ if(QueryRuleEnum.SQL_RULES.getValue().equals(rule.getRuleConditions())) {
+ column = SQL_RULES_COLUMN+rule.getId();
+ }
+ ruleMap.put(column, rule);
+ }
+ }
+ return ruleMap;
+ }
+
+ private static void addRuleToQueryWrapper(SysPermissionDataRuleModel dataRule, String name, Class propertyType, QueryWrapper> queryWrapper) {
+ QueryRuleEnum rule = QueryRuleEnum.getByValue(dataRule.getRuleConditions());
+ if(rule.equals(QueryRuleEnum.IN) && ! propertyType.equals(String.class)) {
+ String[] values = dataRule.getRuleValue().split(",");
+ Object[] objs = new Object[values.length];
+ for (int i = 0; i < values.length; i++) {
+ objs[i] = NumberUtils.parseNumber(values[i], propertyType);
+ }
+ addEasyQuery(queryWrapper, name, rule, objs);
+ }else {
+ if (propertyType.equals(String.class)) {
+ addEasyQuery(queryWrapper, name, rule, converRuleValue(dataRule.getRuleValue()));
+ }else if (propertyType.equals(Date.class)) {
+ String dateStr =converRuleValue(dataRule.getRuleValue());
+ int length = 10;
+ if(dateStr.length()==length){
+ addEasyQuery(queryWrapper, name, rule, DateUtils.str2Date(dateStr,DateUtils.date_sdf.get()));
+ }else{
+ addEasyQuery(queryWrapper, name, rule, DateUtils.str2Date(dateStr,DateUtils.datetimeFormat.get()));
+ }
+ }else {
+ addEasyQuery(queryWrapper, name, rule, NumberUtils.parseNumber(dataRule.getRuleValue(), propertyType));
+ }
+ }
+ }
+
+ public static String converRuleValue(String ruleValue) {
+ String value = JwtUtil.getUserSystemData(ruleValue,null);
+ return value!= null ? value : ruleValue;
+ }
+
+ /**
+ * @author: scott
+ * @Description: 去掉值前后单引号
+ * @date: 2020/3/19 21:26
+ * @param ruleValue:
+ * @Return: java.lang.String
+ */
+ public static String trimSingleQuote(String ruleValue) {
+ if (oConvertUtils.isEmpty(ruleValue)) {
+ return "";
+ }
+ if (ruleValue.startsWith(QueryGenerator.SQL_SQ)) {
+ ruleValue = ruleValue.substring(1);
+ }
+ if (ruleValue.endsWith(QueryGenerator.SQL_SQ)) {
+ ruleValue = ruleValue.substring(0, ruleValue.length() - 1);
+ }
+ return ruleValue;
+ }
+
+ public static String getSqlRuleValue(String sqlRule){
+ try {
+ Set varParams = getSqlRuleParams(sqlRule);
+ for(String var:varParams){
+ String tempValue = converRuleValue(var);
+ sqlRule = sqlRule.replace("#{"+var+"}",tempValue);
+ }
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ }
+ return sqlRule;
+ }
+
+ /**
+ * 获取sql中的#{key} 这个key组成的set
+ */
+ public static Set getSqlRuleParams(String sql) {
+ if(oConvertUtils.isEmpty(sql)){
+ return null;
+ }
+ Set varParams = new HashSet();
+ String regex = "\\#\\{\\w+\\}";
+
+ Pattern p = Pattern.compile(regex);
+ Matcher m = p.matcher(sql);
+ while(m.find()){
+ String var = m.group();
+ varParams.add(var.substring(var.indexOf("{")+1,var.indexOf("}")));
+ }
+ return varParams;
+ }
+
+ /**
+ * 获取查询条件
+ * @param field
+ * @param alias
+ * @param value
+ * @param isString
+ * @return
+ */
+ public static String getSingleQueryConditionSql(String field,String alias,Object value,boolean isString) {
+ return getSingleQueryConditionSql(field, alias, value, isString,null);
+ }
+
+ /**
+ * 报表获取查询条件 支持多数据源
+ * @param field
+ * @param alias
+ * @param value
+ * @param isString
+ * @param dataBaseType
+ * @return
+ */
+ public static String getSingleQueryConditionSql(String field,String alias,Object value,boolean isString, String dataBaseType) {
+ if (value == null) {
+ return "";
+ }
+ field = alias+oConvertUtils.camelToUnderline(field);
+ QueryRuleEnum rule = QueryGenerator.convert2Rule(value);
+ return getSingleSqlByRule(rule, field, value, isString, dataBaseType);
+ }
+
+ /**
+ * 获取单个查询条件的值
+ * @param rule
+ * @param field
+ * @param value
+ * @param isString
+ * @param dataBaseType
+ * @return
+ */
+ private static String getSingleSqlByRule(QueryRuleEnum rule,String field,Object value,boolean isString, String dataBaseType) {
+ String res = "";
+ switch (rule) {
+ case GT:
+ res =field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
+ break;
+ case GE:
+ res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
+ break;
+ case LT:
+ res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
+ break;
+ case LE:
+ res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
+ break;
+ case EQ:
+ res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
+ break;
+ case EQ_WITH_ADD:
+ res = field+" = "+getFieldConditionValue(value, isString, dataBaseType);
+ break;
+ case NE:
+ res = field+" <> "+getFieldConditionValue(value, isString, dataBaseType);
+ break;
+ case IN:
+ res = field + " in "+getInConditionValue(value, isString);
+ break;
+ case LIKE:
+ res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.LIKE);
+ break;
+ case LEFT_LIKE:
+ res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.LEFT_LIKE);
+ break;
+ case RIGHT_LIKE:
+ res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.RIGHT_LIKE);
+ break;
+ default:
+ res = field+" = "+getFieldConditionValue(value, isString, dataBaseType);
+ break;
+ }
+ return res;
+ }
+
+
+ /**
+ * 获取单个查询条件的值
+ * @param rule
+ * @param field
+ * @param value
+ * @param isString
+ * @return
+ */
+ private static String getSingleSqlByRule(QueryRuleEnum rule,String field,Object value,boolean isString) {
+ return getSingleSqlByRule(rule, field, value, isString, null);
+ }
+
+ /**
+ * 获取查询条件的值
+ * @param value
+ * @param isString
+ * @param dataBaseType
+ * @return
+ */
+ private static String getFieldConditionValue(Object value,boolean isString, String dataBaseType) {
+ String str = value.toString().trim();
+ if(str.startsWith(SymbolConstant.EXCLAMATORY_MARK)) {
+ str = str.substring(1);
+ }else if(str.startsWith(QueryRuleEnum.GE.getValue())) {
+ str = str.substring(2);
+ }else if(str.startsWith(QueryRuleEnum.LE.getValue())) {
+ str = str.substring(2);
+ }else if(str.startsWith(QueryRuleEnum.GT.getValue())) {
+ str = str.substring(1);
+ }else if(str.startsWith(QueryRuleEnum.LT.getValue())) {
+ str = str.substring(1);
+ }else if(str.indexOf(QUERY_COMMA_ESCAPE)>0) {
+ str = str.replaceAll("\\+\\+", COMMA);
+ }
+ if(dataBaseType==null){
+ dataBaseType = getDbType();
+ }
+ if(isString) {
+ if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(dataBaseType)){
+ return " N'"+str+"' ";
+ }else{
+ return " '"+str+"' ";
+ }
+ }else {
+ // 如果不是字符串 有一种特殊情况 popup调用都走这个逻辑 参数传递的可能是“‘admin’”这种格式的
+ if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(dataBaseType) && str.endsWith(SymbolConstant.SINGLE_QUOTATION_MARK) && str.startsWith(SymbolConstant.SINGLE_QUOTATION_MARK)){
+ return " N"+str;
+ }
+ return value.toString();
+ }
+ }
+
+ private static String getInConditionValue(Object value,boolean isString) {
+ //update-begin-author:taoyan date:20210628 for: 查询条件如果输入,导致sql报错
+ String[] temp = value.toString().split(",");
+ if(temp.length==0){
+ return "('')";
+ }
+ if(isString) {
+ List res = new ArrayList<>();
+ for (String string : temp) {
+ if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
+ res.add("N'"+string+"'");
+ }else{
+ res.add("'"+string+"'");
+ }
+ }
+ return "("+String.join("," ,res)+")";
+ }else {
+ return "("+value.toString()+")";
+ }
+ //update-end-author:taoyan date:20210628 for: 查询条件如果输入,导致sql报错
+ }
+
+ /**
+ * 先根据值判断 走左模糊还是右模糊
+ * 最后如果值不带任何标识(*或者%),则再根据ruleEnum判断
+ * @param value
+ * @param ruleEnum
+ * @return
+ */
+ private static String getLikeConditionValue(Object value, QueryRuleEnum ruleEnum) {
+ String str = value.toString().trim();
+ if(str.startsWith(SymbolConstant.ASTERISK) && str.endsWith(SymbolConstant.ASTERISK)) {
+ if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
+ return "N'%"+str.substring(1,str.length()-1)+"%'";
+ }else{
+ return "'%"+str.substring(1,str.length()-1)+"%'";
+ }
+ }else if(str.startsWith(SymbolConstant.ASTERISK)) {
+ if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
+ return "N'%"+str.substring(1)+"'";
+ }else{
+ return "'%"+str.substring(1)+"'";
+ }
+ }else if(str.endsWith(SymbolConstant.ASTERISK)) {
+ if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
+ return "N'"+str.substring(0,str.length()-1)+"%'";
+ }else{
+ return "'"+str.substring(0,str.length()-1)+"%'";
+ }
+ }else {
+ if(str.indexOf(SymbolConstant.PERCENT_SIGN)>=0) {
+ if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
+ if(str.startsWith(SymbolConstant.SINGLE_QUOTATION_MARK) && str.endsWith(SymbolConstant.SINGLE_QUOTATION_MARK)){
+ return "N"+str;
+ }else{
+ return "N"+"'"+str+"'";
+ }
+ }else{
+ if(str.startsWith(SymbolConstant.SINGLE_QUOTATION_MARK) && str.endsWith(SymbolConstant.SINGLE_QUOTATION_MARK)){
+ return str;
+ }else{
+ return "'"+str+"'";
+ }
+ }
+ }else {
+
+ //update-begin-author:taoyan date:2022-6-30 for: issues/3810 数据权限规则问题
+ // 走到这里说明 value不带有任何模糊查询的标识(*或者%)
+ if (ruleEnum == QueryRuleEnum.LEFT_LIKE) {
+ if (DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())) {
+ return "N'%" + str + "'";
+ } else {
+ return "'%" + str + "'";
+ }
+ } else if (ruleEnum == QueryRuleEnum.RIGHT_LIKE) {
+ if (DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())) {
+ return "N'" + str + "%'";
+ } else {
+ return "'" + str + "%'";
+ }
+ } else {
+ if (DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())) {
+ return "N'%" + str + "%'";
+ } else {
+ return "'%" + str + "%'";
+ }
+ }
+ //update-end-author:taoyan date:2022-6-30 for: issues/3810 数据权限规则问题
+
+ }
+ }
+ }
+
+ /**
+ * 根据权限相关配置生成相关的SQL 语句
+ * @param clazz
+ * @return
+ */
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public static String installAuthJdbc(Class> clazz) {
+ StringBuffer sb = new StringBuffer();
+ //权限查询
+ Map ruleMap = getRuleMap();
+ PropertyDescriptor[] origDescriptors = PropertyUtils.getPropertyDescriptors(clazz);
+ String sqlAnd = " and ";
+ for (String c : ruleMap.keySet()) {
+ if(oConvertUtils.isNotEmpty(c) && c.startsWith(SQL_RULES_COLUMN)){
+ sb.append(sqlAnd+getSqlRuleValue(ruleMap.get(c).getRuleValue()));
+ }
+ }
+ String name, column;
+ for (int i = 0; i < origDescriptors.length; i++) {
+ name = origDescriptors[i].getName();
+ if (judgedIsUselessField(name)) {
+ continue;
+ }
+ if(ruleMap.containsKey(name)) {
+ column = getTableFieldName(clazz, name);
+ if(column==null){
+ continue;
+ }
+ SysPermissionDataRuleModel dataRule = ruleMap.get(name);
+ QueryRuleEnum rule = QueryRuleEnum.getByValue(dataRule.getRuleConditions());
+ Class propType = origDescriptors[i].getPropertyType();
+ boolean isString = propType.equals(String.class);
+ Object value;
+ if(isString) {
+ value = converRuleValue(dataRule.getRuleValue());
+ }else {
+ value = NumberUtils.parseNumber(dataRule.getRuleValue(),propType);
+ }
+ String filedSql = getSingleSqlByRule(rule, oConvertUtils.camelToUnderline(column), value,isString);
+ sb.append(sqlAnd+filedSql);
+ }
+ }
+ log.info("query auth sql is:"+sb.toString());
+ return sb.toString();
+ }
+
+ /**
+ * 根据权限相关配置 组装mp需要的权限
+ * @param queryWrapper
+ * @param clazz
+ * @return
+ */
+ public static void installAuthMplus(QueryWrapper> queryWrapper,Class> clazz) {
+ //权限查询
+ Map ruleMap = getRuleMap();
+ PropertyDescriptor[] origDescriptors = PropertyUtils.getPropertyDescriptors(clazz);
+ for (String c : ruleMap.keySet()) {
+ if(oConvertUtils.isNotEmpty(c) && c.startsWith(SQL_RULES_COLUMN)){
+ queryWrapper.and(i ->i.apply(getSqlRuleValue(ruleMap.get(c).getRuleValue())));
+ }
+ }
+ String name, column;
+ for (int i = 0; i < origDescriptors.length; i++) {
+ name = origDescriptors[i].getName();
+ if (judgedIsUselessField(name)) {
+ continue;
+ }
+ column = getTableFieldName(clazz, name);
+ if(column==null){
+ continue;
+ }
+ if(ruleMap.containsKey(name)) {
+ addRuleToQueryWrapper(ruleMap.get(name), column, origDescriptors[i].getPropertyType(), queryWrapper);
+ }
+ }
+ }
+
+ /**
+ * 转换sql中的系统变量
+ * @param sql
+ * @return
+ */
+ public static String convertSystemVariables(String sql){
+ return getSqlRuleValue(sql);
+ }
+
+ /**
+ * 获取所有配置的权限 返回sql字符串 不受字段限制 配置什么就拿到什么
+ * @return
+ */
+ public static String getAllConfigAuth() {
+ StringBuffer sb = new StringBuffer();
+ //权限查询
+ Map ruleMap = getRuleMap();
+ String sqlAnd = " and ";
+ for (String c : ruleMap.keySet()) {
+ SysPermissionDataRuleModel dataRule = ruleMap.get(c);
+ String ruleValue = dataRule.getRuleValue();
+ if(oConvertUtils.isEmpty(ruleValue)){
+ continue;
+ }
+ if(oConvertUtils.isNotEmpty(c) && c.startsWith(SQL_RULES_COLUMN)){
+ sb.append(sqlAnd+getSqlRuleValue(ruleValue));
+ }else{
+ boolean isString = false;
+ ruleValue = ruleValue.trim();
+ if(ruleValue.startsWith("'") && ruleValue.endsWith("'")){
+ isString = true;
+ ruleValue = ruleValue.substring(1,ruleValue.length()-1);
+ }
+ QueryRuleEnum rule = QueryRuleEnum.getByValue(dataRule.getRuleConditions());
+ String value = converRuleValue(ruleValue);
+ String filedSql = getSingleSqlByRule(rule, c, value,isString);
+ sb.append(sqlAnd+filedSql);
+ }
+ }
+ log.info("query auth sql is = "+sb.toString());
+ return sb.toString();
+ }
+
+
+
+ /**
+ * 获取系统数据库类型
+ */
+ private static String getDbType(){
+ return CommonUtils.getDatabaseType();
+ }
+
+
+ /**
+ * 获取class的 包括父类的
+ * @param clazz
+ * @return
+ */
+ private static List getClassFields(Class> clazz) {
+ List list = new ArrayList();
+ Field[] fields;
+ do{
+ fields = clazz.getDeclaredFields();
+ for(int i = 0;i clazz, String name) {
+ try {
+ //如果字段加注解了@TableField(exist = false),不走DB查询
+ Field field = null;
+ try {
+ field = clazz.getDeclaredField(name);
+ } catch (NoSuchFieldException e) {
+ //e.printStackTrace();
+ }
+
+ //如果为空,则去父类查找字段
+ if (field == null) {
+ List allFields = getClassFields(clazz);
+ List searchFields = allFields.stream().filter(a -> a.getName().equals(name)).collect(Collectors.toList());
+ if(searchFields!=null && searchFields.size()>0){
+ field = searchFields.get(0);
+ }
+ }
+
+ if (field != null) {
+ TableField tableField = field.getAnnotation(TableField.class);
+ if (tableField != null){
+ if(tableField.exist() == false){
+ //如果设置了TableField false 这个字段不需要处理
+ return null;
+ }else{
+ String column = tableField.value();
+ //如果设置了TableField value 这个字段是实体字段
+ if(!"".equals(column)){
+ return column;
+ }
+ }
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return name;
+ }
+
+ /**
+ * mysql 模糊查询之特殊字符下划线 (_、\)
+ *
+ * @param value:
+ * @Return: java.lang.String
+ */
+ private static String specialStrConvert(String value) {
+ if (DataBaseConstant.DB_TYPE_MYSQL.equals(getDbType()) || DataBaseConstant.DB_TYPE_MARIADB.equals(getDbType())) {
+ String[] specialStr = QueryGenerator.LIKE_MYSQL_SPECIAL_STRS.split(",");
+ for (String str : specialStr) {
+ if (value.indexOf(str) !=-1) {
+ value = value.replace(str, "\\" + str);
+ }
+ }
+ }
+ return value;
+ }
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/query/QueryRuleEnum.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/query/QueryRuleEnum.java
new file mode 100644
index 0000000..c31bf2b
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/query/QueryRuleEnum.java
@@ -0,0 +1,86 @@
+package org.jeecg.common.system.query;
+
+import org.jeecg.common.util.oConvertUtils;
+
+/**
+ * Query 规则 常量
+ * @Author Scott
+ * @Date 2019年02月14日
+ */
+public enum QueryRuleEnum {
+
+ /**查询规则 大于*/
+ GT(">","gt","大于"),
+ /**查询规则 大于等于*/
+ GE(">=","ge","大于等于"),
+ /**查询规则 小于*/
+ LT("<","lt","小于"),
+ /**查询规则 小于等于*/
+ LE("<=","le","小于等于"),
+ /**查询规则 等于*/
+ EQ("=","eq","等于"),
+ /**查询规则 不等于*/
+ NE("!=","ne","不等于"),
+ /**查询规则 包含*/
+ IN("IN","in","包含"),
+ /**查询规则 全模糊*/
+ LIKE("LIKE","like","全模糊"),
+ /**查询规则 左模糊*/
+ LEFT_LIKE("LEFT_LIKE","left_like","左模糊"),
+ /**查询规则 右模糊*/
+ RIGHT_LIKE("RIGHT_LIKE","right_like","右模糊"),
+ /**查询规则 带加号等于*/
+ EQ_WITH_ADD("EQWITHADD","eq_with_add","带加号等于"),
+ /**查询规则 多词模糊匹配*/
+ LIKE_WITH_AND("LIKEWITHAND","like_with_and","多词模糊匹配————暂时未用上"),
+ /**查询规则 自定义SQL片段*/
+ SQL_RULES("USE_SQL_RULES","ext","自定义SQL片段");
+
+ private String value;
+
+ private String condition;
+
+ private String msg;
+
+ QueryRuleEnum(String value, String condition, String msg){
+ this.value = value;
+ this.condition = condition;
+ this.msg = msg;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public String getMsg() {
+ return msg;
+ }
+
+ public void setMsg(String msg) {
+ this.msg = msg;
+ }
+
+ public String getCondition() {
+ return condition;
+ }
+
+ public void setCondition(String condition) {
+ this.condition = condition;
+ }
+
+ public static QueryRuleEnum getByValue(String value){
+ if(oConvertUtils.isEmpty(value)) {
+ return null;
+ }
+ for(QueryRuleEnum val :values()){
+ if (val.getValue().equals(value) || val.getCondition().equals(value)){
+ return val;
+ }
+ }
+ return null;
+ }
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/util/JeecgDataAutorUtils.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/util/JeecgDataAutorUtils.java
new file mode 100644
index 0000000..ed6aa3c
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/util/JeecgDataAutorUtils.java
@@ -0,0 +1,160 @@
+package org.jeecg.common.system.util;
+
+import org.apache.shiro.SecurityUtils;
+import org.jeecg.common.constant.CommonConstant;
+import org.jeecg.common.system.vo.LoginUser;
+import org.jeecg.common.system.vo.SysPermissionDataRuleModel;
+import org.jeecg.common.system.vo.SysUserCacheInfo;
+import org.jeecg.common.util.SpringContextUtils;
+import org.springframework.util.StringUtils;
+
+import javax.servlet.http.HttpServletRequest;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @ClassName: JeecgDataAutorUtils
+ * @Description: 数据权限查询规则容器工具类
+ * @Author: 张代浩
+ * @Date: 2012-12-15 下午11:27:39
+ */
+public class JeecgDataAutorUtils {
+
+ public static final String MENU_DATA_AUTHOR_RULES = "MENU_DATA_AUTHOR_RULES";
+
+ public static final String MENU_DATA_AUTHOR_RULE_SQL = "MENU_DATA_AUTHOR_RULE_SQL";
+
+ public static final String SYS_USER_INFO = "SYS_USER_INFO";
+
+
+ /**
+ * 根据用户类型返回对应实体
+ *
+ * @param obj
+ * @return
+ */
+ public static synchronized Object initDateAutorByUserType(Object obj) {
+ LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+
+ if (sysUser.getUserIdentity() != null) {
+ try {
+ if (sysUser.getUserIdentity().equals(CommonConstant.USER_IDENTITY_2)) {
+ Field field =null;
+ try {
+ field = obj.getClass().getDeclaredField("sysOrgCode");
+ } catch (Exception ex) {
+ field = obj.getClass().getSuperclass().getDeclaredField("sysOrgCode");
+ }
+ if (field != null) {
+ field.setAccessible(true);
+ //赋值
+ field.set(obj, sysUser.getSysOrgCode());
+ }
+
+
+ } else if (sysUser.getUserIdentity().equals(CommonConstant.USER_IDENTITY_4) || sysUser.getUserIdentity().equals(CommonConstant.USER_IDENTITY_5)) {
+ Field field =null;
+
+ try {
+ field = obj.getClass().getDeclaredField("canteenId");
+ } catch (Exception ex) {
+ field = obj.getClass().getSuperclass().getDeclaredField("canteenId");
+ }
+
+ if (field != null) {
+ field.setAccessible(true);
+ //赋值
+ field.set(obj, sysUser.getCanteenId());
+ }
+ }
+ } catch (Exception ex) {
+
+ }
+ }
+
+ return obj;
+
+ }
+
+ /**
+ * 往链接请求里面,传入数据查询条件
+ *
+ * @param request
+ * @param dataRules
+ */
+ public static synchronized void installDataSearchConditon(HttpServletRequest request, List dataRules) {
+ @SuppressWarnings("unchecked")
+ List list = (List) loadDataSearchConditon();// 1.先从request获取MENU_DATA_AUTHOR_RULES,如果存则获取到LIST
+ if (list == null) {
+ // 2.如果不存在,则new一个list
+ list = new ArrayList();
+ }
+ for (SysPermissionDataRuleModel tsDataRule : dataRules) {
+ list.add(tsDataRule);
+ }
+ request.setAttribute(MENU_DATA_AUTHOR_RULES, list); // 3.往list里面增量存指
+ }
+
+ /**
+ * 获取请求对应的数据权限规则
+ *
+ * @return
+ */
+ @SuppressWarnings("unchecked")
+ public static synchronized List loadDataSearchConditon() {
+ return (List) SpringContextUtils.getHttpServletRequest().getAttribute(MENU_DATA_AUTHOR_RULES);
+
+ }
+
+ /**
+ * 获取请求对应的数据权限SQL
+ *
+ * @return
+ */
+ public static synchronized String loadDataSearchConditonSQLString() {
+ return (String) SpringContextUtils.getHttpServletRequest().getAttribute(MENU_DATA_AUTHOR_RULE_SQL);
+ }
+
+ /**
+ * 往链接请求里面,传入数据查询条件
+ *
+ * @param request
+ * @param sql
+ */
+ public static synchronized void installDataSearchConditon(HttpServletRequest request, String sql) {
+ String ruleSql = (String) loadDataSearchConditonSQLString();
+ if (!StringUtils.hasText(ruleSql)) {
+ request.setAttribute(MENU_DATA_AUTHOR_RULE_SQL, sql);
+ }
+ }
+
+ /**
+ * 将用户信息存到request
+ *
+ * @param request
+ * @param userinfo
+ */
+ public static synchronized void installUserInfo(HttpServletRequest request, SysUserCacheInfo userinfo) {
+ request.setAttribute(SYS_USER_INFO, userinfo);
+ }
+
+ /**
+ * 将用户信息存到request
+ *
+ * @param userinfo
+ */
+ public static synchronized void installUserInfo(SysUserCacheInfo userinfo) {
+ SpringContextUtils.getHttpServletRequest().setAttribute(SYS_USER_INFO, userinfo);
+ }
+
+ /**
+ * 从request获取用户信息
+ *
+ * @return
+ */
+ public static synchronized SysUserCacheInfo loadUserInfo() {
+ return (SysUserCacheInfo) SpringContextUtils.getHttpServletRequest().getAttribute(SYS_USER_INFO);
+
+ }
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/util/JwtUtil.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/util/JwtUtil.java
new file mode 100644
index 0000000..15a749d
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/util/JwtUtil.java
@@ -0,0 +1,306 @@
+package org.jeecg.common.system.util;
+
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.JWTVerifier;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.exceptions.JWTDecodeException;
+import com.auth0.jwt.interfaces.DecodedJWT;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.base.Joiner;
+import org.apache.shiro.SecurityUtils;
+import org.jeecg.common.api.vo.Result;
+import org.jeecg.common.constant.CommonConstant;
+import org.jeecg.common.constant.DataBaseConstant;
+import org.jeecg.common.exception.JeecgBootException;
+import org.jeecg.common.system.vo.LoginUser;
+import org.jeecg.common.system.vo.SysUserCacheInfo;
+import org.jeecg.common.util.DateUtils;
+import org.jeecg.common.util.SpringContextUtils;
+import org.jeecg.common.util.oConvertUtils;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Date;
+
+/**
+ * @Author Scott
+ * @Date 2018-07-12 14:23
+ * @Desc JWT工具类
+ **/
+public class JwtUtil {
+
+
+ // Token过期时间2小时(用户登录过期时间是此时间的两倍,以token在reids缓存时间为准)
+ public static final long EXPIRE_TIME = 2 * 60 * 60 * 1000;
+
+ /**
+ * @param response
+ * @param code
+ * @param errorMsg
+ */
+ public static void responseError(ServletResponse response, Integer code, String errorMsg) {
+ HttpServletResponse httpServletResponse = (HttpServletResponse) response;
+ Result jsonResult = new Result(code, errorMsg);
+ OutputStream os = null;
+ try {
+ os = httpServletResponse.getOutputStream();
+ httpServletResponse.setCharacterEncoding("UTF-8");
+ httpServletResponse.setStatus(401);
+ os.write(new ObjectMapper().writeValueAsString(jsonResult).getBytes("UTF-8"));
+ os.flush();
+ os.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 校验token是否正确
+ *
+ * @param token 密钥
+ * @param secret 用户的密码
+ * @return 是否正确
+ */
+ public static boolean verify(String token, String username, String secret) {
+ try {
+ // 根据密码生成JWT效验器
+ Algorithm algorithm = Algorithm.HMAC256(secret);
+ JWTVerifier verifier = JWT.require(algorithm).withClaim("username", username).build();
+ // 效验TOKEN
+ DecodedJWT jwt = verifier.verify(token);
+ return true;
+ } catch (Exception exception) {
+ return false;
+ }
+ }
+
+ /**
+ * 获得token中的信息无需secret解密也能获得
+ *
+ * @return token中包含的用户名
+ */
+ public static String getUserId(String token) {
+ try {
+ DecodedJWT jwt = JWT.decode(token);
+ return jwt.getClaim("userId").asString();
+ } catch (JWTDecodeException e) {
+ return null;
+ }
+ }
+
+ public static String getUserId() throws JeecgBootException {
+ HttpServletRequest request =
+ ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
+ try {
+ String accessToken = request.getHeader("X-Access-Token");
+ DecodedJWT jwt = JWT.decode(accessToken);
+ return jwt.getClaim("userId").asString();
+ } catch (JWTDecodeException e) {
+ return null;
+ }
+ }
+
+ public static String getCanteenId() throws JeecgBootException {
+ HttpServletRequest request =
+ ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
+ try {
+ String accessToken = request.getHeader("X-Access-Token");
+ DecodedJWT jwt = JWT.decode(accessToken);
+ return jwt.getClaim("canteenId").asString();
+ } catch (JWTDecodeException e) {
+ return null;
+ }
+ }
+
+
+ public static String getType(String accessToken) throws JeecgBootException {
+ try {
+ DecodedJWT jwt = JWT.decode(accessToken);
+ return jwt.getClaim("type").asString();
+ } catch (JWTDecodeException e) {
+ return null;
+ }
+ }
+
+ /**
+ * 小程序获得签名专用
+ *
+ * @param userId
+ * @param canteenId
+ * @param secret
+ * @return
+ */
+ public static String signUserId(String userId, String canteenId, String secret,String type) {
+ Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
+ Algorithm algorithm = Algorithm.HMAC256(secret);
+ // openId
+ return JWT.create().withClaim("userId", userId).withClaim("canteenId", canteenId).withClaim("type",type).
+ //withExpiresAt(date).
+ sign(algorithm);
+
+ }
+
+ /**
+ * 生成签名,
+ *
+ * @param userId 用户id
+ * @param secret 用户的密码
+ * @return 加密的token
+ */
+ public static String sign(String userId, String secret) {
+ Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
+ Algorithm algorithm = Algorithm.HMAC256(secret);
+ // 附带username信息
+ return JWT.create().withClaim("userId", userId).withExpiresAt(date).sign(algorithm);
+
+ }
+
+
+ /**
+ * 根据request中的token获取用户账号
+ *
+ * @param request
+ * @return
+ * @throws JeecgBootException
+ */
+ public static String getUserIdByToken(HttpServletRequest request) throws JeecgBootException {
+ String accessToken = request.getHeader("X-Access-Token");
+ String userId = getUserId(accessToken);
+ if (oConvertUtils.isEmpty(userId)) {
+ throw new JeecgBootException("未获取到用户");
+ }
+ return userId;
+ }
+
+ /**
+ * 从session中获取变量
+ *
+ * @param key
+ * @return
+ */
+ public static String getSessionData(String key) {
+ //${myVar}%
+ //得到${} 后面的值
+ String moshi = "";
+ if (key.indexOf("}") != -1) {
+ moshi = key.substring(key.indexOf("}") + 1);
+ }
+ String returnValue = null;
+ if (key.contains("#{")) {
+ key = key.substring(2, key.indexOf("}"));
+ }
+ if (oConvertUtils.isNotEmpty(key)) {
+ HttpSession session = SpringContextUtils.getHttpServletRequest().getSession();
+ returnValue = (String) session.getAttribute(key);
+ }
+ //结果加上${} 后面的值
+ if (returnValue != null) {
+ returnValue = returnValue + moshi;
+ }
+ return returnValue;
+ }
+
+ /**
+ * 从当前用户中获取变量
+ *
+ * @param ruleValue
+ * @param user
+ * @return
+ */
+ //硬编码加密密钥-中危.(命名的关键字key被扫描到误报,修改命名ruleValue)
+// public static String getUserSystemData(String key, SysUserCacheInfo user) {
+ public static String getUserSystemData(String ruleValue, SysUserCacheInfo user) {
+ if (user == null) {
+ user = JeecgDataAutorUtils.loadUserInfo();
+ }
+ //#{sys_user_code}%
+
+ // 获取登录用户信息
+ LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+
+ String moshi = "";
+ if (ruleValue.indexOf("}") != -1) {
+ moshi = ruleValue.substring(ruleValue.indexOf("}") + 1);
+ }
+ String returnValue = null;
+ //针对特殊标示处理#{sysOrgCode},判断替换
+ if (ruleValue.contains("#{")) {
+ ruleValue = ruleValue.substring(2, ruleValue.indexOf("}"));
+ } else {
+ ruleValue = ruleValue;
+ }
+ //替换为系统登录用户帐号
+ if (ruleValue.equals(DataBaseConstant.SYS_USER_CODE) || ruleValue.toLowerCase().equals(DataBaseConstant.SYS_USER_CODE_TABLE)) {
+ if (user == null) {
+ returnValue = sysUser.getUsername();
+ } else {
+ returnValue = user.getSysUserCode();
+ }
+ }
+ //替换为系统登录用户真实名字
+ else if (ruleValue.equals(DataBaseConstant.SYS_USER_NAME) || ruleValue.toLowerCase().equals(DataBaseConstant.SYS_USER_NAME_TABLE)) {
+ if (user == null) {
+ returnValue = sysUser.getRealname();
+ } else {
+ returnValue = user.getSysUserName();
+ }
+ }
+
+ //替换为系统用户登录所使用的机构编码
+ else if (ruleValue.equals(DataBaseConstant.SYS_ORG_CODE) || ruleValue.toLowerCase().equals(DataBaseConstant.SYS_ORG_CODE_TABLE)) {
+ if (user == null) {
+ returnValue = sysUser.getSysOrgCode();
+ } else {
+ returnValue = user.getSysOrgCode();
+ }
+ }
+ //替换为系统用户所拥有的所有机构编码
+ else if (ruleValue.equals(DataBaseConstant.SYS_MULTI_ORG_CODE) || ruleValue.toLowerCase().equals(DataBaseConstant.SYS_MULTI_ORG_CODE_TABLE)) {
+ if (user == null) {
+ //TODO 暂时使用用户登录部门,存在逻辑缺陷,不是用户所拥有的部门
+ returnValue = sysUser.getSysOrgCode();
+ } else {
+ if (user.isOneDepart()) {
+ returnValue = user.getSysMultiOrgCode().get(0);
+ } else {
+ returnValue = Joiner.on(",").join(user.getSysMultiOrgCode());
+ }
+ }
+ }
+ //替换为当前系统时间(年月日)
+ else if (ruleValue.equals(DataBaseConstant.SYS_DATE) || ruleValue.toLowerCase().equals(DataBaseConstant.SYS_DATE_TABLE)) {
+ returnValue = DateUtils.formatDate();
+ }
+ //替换为当前系统时间(年月日时分秒)
+ else if (ruleValue.equals(DataBaseConstant.SYS_TIME) || ruleValue.toLowerCase().equals(DataBaseConstant.SYS_TIME_TABLE)) {
+ returnValue = DateUtils.now();
+ }
+ //流程状态默认值(默认未发起)
+ else if (ruleValue.equals(DataBaseConstant.BPM_STATUS) || ruleValue.toLowerCase().equals(DataBaseConstant.BPM_STATUS_TABLE)) {
+ returnValue = "1";
+ }
+ //update-begin-author:taoyan date:20210330 for:多租户ID作为系统变量
+ else if (ruleValue.equals(DataBaseConstant.TENANT_ID) || ruleValue.toLowerCase().equals(DataBaseConstant.TENANT_ID_TABLE)) {
+ returnValue = sysUser.getRelTenantIds();
+ if (oConvertUtils.isEmpty(returnValue) || (returnValue != null && returnValue.indexOf(",") > 0)) {
+ returnValue = SpringContextUtils.getHttpServletRequest().getHeader(CommonConstant.TENANT_ID);
+ }
+ }
+ //update-end-author:taoyan date:20210330 for:多租户ID作为系统变量
+ if (returnValue != null) {
+ returnValue = returnValue + moshi;
+ }
+ return returnValue;
+ }
+
+ public static void main(String[] args) {
+ String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NTgzOTc0NjAsInVzZXJuYW1lIjoic3lzdGVtIn0.ABrRxlh-wkuDppbhiwmkobN2YSUhu4GMHtyW6bPQCtQ";
+ System.out.println(JwtUtil.getUserId(token));
+ }
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/util/ResourceUtil.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/util/ResourceUtil.java
new file mode 100644
index 0000000..d3260c6
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/util/ResourceUtil.java
@@ -0,0 +1,111 @@
+package org.jeecg.common.system.util;
+
+import lombok.extern.slf4j.Slf4j;
+import org.jeecg.common.system.annotation.EnumDict;
+import org.jeecg.common.system.vo.DictModel;
+import org.jeecg.common.util.oConvertUtils;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+import org.springframework.util.ClassUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 资源加载工具类
+ * @Author taoYan
+ * @Date 2022/7/8 10:40
+ **/
+@Slf4j
+public class ResourceUtil {
+
+
+ /**
+ * 枚举字典数据
+ */
+ private final static Map> enumDictData = new HashMap<>(5);
+
+ /**
+ * 所有java类
+ */
+ private final static String CLASS_PATTERN="/**/*.class";
+
+ /**
+ * 包路径 org.jeecg
+ */
+ private final static String BASE_PACKAGE = "org.jeecg";
+
+ /**
+ * 枚举类中获取字典数据的方法名
+ */
+ private final static String METHOD_NAME = "getDictList";
+
+ /**
+ * 获取枚举类对应的字典数据 SysDictServiceImpl#queryAllDictItems()
+ * @return
+ */
+ public static Map> getEnumDictData(){
+ if(enumDictData.keySet().size()>0){
+ return enumDictData;
+ }
+ ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
+ String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(BASE_PACKAGE) + CLASS_PATTERN;
+ try {
+ Resource[] resources = resourcePatternResolver.getResources(pattern);
+ MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
+ for (Resource resource : resources) {
+ MetadataReader reader = readerFactory.getMetadataReader(resource);
+ String classname = reader.getClassMetadata().getClassName();
+ Class> clazz = Class.forName(classname);
+ EnumDict enumDict = clazz.getAnnotation(EnumDict.class);
+ if (enumDict != null) {
+ EnumDict annotation = clazz.getAnnotation(EnumDict.class);
+ String key = annotation.value();
+ if(oConvertUtils.isNotEmpty(key)){
+ List list = (List) clazz.getDeclaredMethod(METHOD_NAME).invoke(null);
+ enumDictData.put(key, list);
+ }
+ }
+ }
+ }catch (Exception e){
+ log.error("获取枚举类字典数据异常", e.getMessage());
+ // e.printStackTrace();
+ }
+ return enumDictData;
+ }
+
+ /**
+ * 用于后端字典翻译 SysDictServiceImpl#queryManyDictByKeys(java.util.List, java.util.List)
+ * @param dictCodeList
+ * @param keys
+ * @return
+ */
+ public static Map> queryManyDictByKeys(List dictCodeList, List keys){
+ if(enumDictData.keySet().size()==0){
+ getEnumDictData();
+ }
+ Map> map = new HashMap<>();
+ for (String code : enumDictData.keySet()) {
+ if(dictCodeList.indexOf(code)>=0){
+ List dictItemList = enumDictData.get(code);
+ for(DictModel dm: dictItemList){
+ String value = dm.getValue();
+ if(keys.indexOf(value)>=0){
+ List list = new ArrayList<>();
+ list.add(new DictModel(value, dm.getText()));
+ map.put(code,list);
+ break;
+ }
+ }
+ }
+ }
+ return map;
+ }
+
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/ComboModel.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/ComboModel.java
new file mode 100644
index 0000000..663cdb9
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/ComboModel.java
@@ -0,0 +1,40 @@
+package org.jeecg.common.system.vo;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+
+/**
+ * @Description: 文档管理
+ * @author: jeecg-boot
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ComboModel implements Serializable {
+ private String id;
+ private String title;
+ /**文档管理 表单table默认选中*/
+ private boolean checked;
+ /**文档管理 表单table 用户账号*/
+ private String username;
+ /**文档管理 表单table 用户邮箱*/
+ private String email;
+ /**文档管理 表单table 角色编码*/
+ private String roleCode;
+
+ public ComboModel(){
+
+ };
+
+ public ComboModel(String id,String title,boolean checked,String username){
+ this.id = id;
+ this.title = title;
+ this.checked = false;
+ this.username = username;
+ };
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/DictModel.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/DictModel.java
new file mode 100644
index 0000000..b407146
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/DictModel.java
@@ -0,0 +1,53 @@
+package org.jeecg.common.system.vo;
+
+import java.io.Serializable;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * @Description: 字典类
+ * @author: jeecg-boot
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class DictModel implements Serializable{
+ private static final long serialVersionUID = 1L;
+
+ public DictModel() {
+ }
+
+ public DictModel(String value, String text) {
+ this.value = value;
+ this.text = text;
+ }
+
+ /**
+ * 字典value
+ */
+ private String value;
+ /**
+ * 字典文本
+ */
+ private String text;
+
+ /**
+ * 特殊用途: JgEditableTable
+ * @return
+ */
+ public String getTitle() {
+ return this.text;
+ }
+ /**
+ * 特殊用途: vue3 Select组件
+ */
+ public String getLabel() {
+ return this.text;
+ }
+
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/DictModelMany.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/DictModelMany.java
new file mode 100644
index 0000000..cc6a34f
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/DictModelMany.java
@@ -0,0 +1,19 @@
+package org.jeecg.common.system.vo;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 查询多个字典时用到
+ * @author: jeecg-boot
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class DictModelMany extends DictModel {
+
+ /**
+ * 字典code,根据多个字段code查询时才用到,用于区分不同的字典选项
+ */
+ private String dictCode;
+
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/DictQuery.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/DictQuery.java
new file mode 100644
index 0000000..b24ede8
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/DictQuery.java
@@ -0,0 +1,35 @@
+package org.jeecg.common.system.vo;
+
+import lombok.Data;
+
+/**
+ * 字典查询参数实体
+ * @author: jeecg-boot
+ */
+@Data
+public class DictQuery {
+ /**
+ * 表名
+ */
+ private String table;
+ /**
+ * 存储列
+ */
+ private String code;
+
+ /**
+ * 显示列
+ */
+ private String text;
+
+ /**
+ * 关键字查询
+ */
+ private String keyword;
+
+ /**
+ * 存储列的值 用于回显查询
+ */
+ private String codeValue;
+
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/DynamicDataSourceModel.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/DynamicDataSourceModel.java
new file mode 100644
index 0000000..303ab17
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/DynamicDataSourceModel.java
@@ -0,0 +1,58 @@
+package org.jeecg.common.system.vo;
+
+import lombok.Data;
+import org.springframework.beans.BeanUtils;
+
+/**
+ * @Description: 数据源
+ * @author: jeecg-boot
+ */
+@Data
+public class DynamicDataSourceModel {
+
+ public DynamicDataSourceModel() {
+
+ }
+
+ public DynamicDataSourceModel(Object dbSource) {
+ if (dbSource != null) {
+ BeanUtils.copyProperties(dbSource, this);
+ }
+ }
+
+ /**
+ * id
+ */
+ private java.lang.String id;
+ /**
+ * 数据源编码
+ */
+ private java.lang.String code;
+ /**
+ * 数据库类型
+ */
+ private java.lang.String dbType;
+ /**
+ * 驱动类
+ */
+ private java.lang.String dbDriver;
+ /**
+ * 数据源地址
+ */
+ private java.lang.String dbUrl;
+
+// /**
+// * 数据库名称
+// */
+// private java.lang.String dbName;
+
+ /**
+ * 用户名
+ */
+ private java.lang.String dbUsername;
+ /**
+ * 密码
+ */
+ private java.lang.String dbPassword;
+
+}
\ No newline at end of file
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/LoginUser.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/LoginUser.java
new file mode 100644
index 0000000..7c208ad
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/LoginUser.java
@@ -0,0 +1,129 @@
+package org.jeecg.common.system.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ *
+ * 在线用户信息
+ *
+ *
+ * @Author scott
+ * @since 2018-12-20
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+public class LoginUser {
+
+ /**
+ * 登录人id
+ */
+ private String id;
+
+ /**
+ * 登录人账号
+ */
+ private String username;
+
+ /**
+ * 登录人名字
+ */
+ private String realname;
+
+ /**
+ * 登录人密码
+ */
+ private String password;
+
+ /**
+ * 当前登录部门code
+ */
+ private String orgCode;
+
+ /**
+ * 当前登录部门code
+ */
+ private String sysOrgCode;
+ /**
+ * 头像
+ */
+ private String avatar;
+
+ /**
+ * 生日
+ */
+ @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
+ @DateTimeFormat(pattern = "yyyy-MM-dd")
+ private Date birthday;
+
+ /**
+ * 性别(1:男 2:女)
+ */
+ private Integer sex;
+
+ /**
+ * 电子邮件
+ */
+ private String email;
+
+ /**
+ * 电话
+ */
+ private String phone;
+
+ /**
+ * 状态(1:正常 2:冻结 )
+ */
+ private Integer status;
+
+ private Integer delFlag;
+ /**
+ * 同步工作流引擎1同步0不同步
+ */
+ private Integer activitiSync;
+
+ /**
+ * 创建时间
+ */
+ private Date createTime;
+
+ /**
+ * 用户类型 (0:超级管理员(系统内置-内部使用) 1:admin 2:二级管理员 3:平台用户 4:食堂管理员 5:食堂职工 6:员工(就餐人员))
+ */
+ private Integer userIdentity;
+
+ /**
+ * 管理部门ids
+ */
+ private String departIds;
+
+ /**
+ * 职务,关联职务表
+ */
+ private String post;
+
+ /**
+ * 座机号
+ */
+ private String telephone;
+
+ /**多租户id配置,编辑用户的时候设置*/
+ private String relTenantIds;
+
+ /**设备id uniapp推送用*/
+ private String clientId;
+
+ /**食堂id*/
+ private String canteenId;
+
+ /**用户管理食堂*/
+ private List canteenIdList;
+
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/ScreenVo.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/ScreenVo.java
new file mode 100644
index 0000000..339d120
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/ScreenVo.java
@@ -0,0 +1,24 @@
+package org.jeecg.common.system.vo;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+public class ScreenVo {
+ @Schema(title = "食堂id")
+ private String id;
+
+ @Schema(title = "食堂名称")
+ private String restName;
+
+ public ScreenVo(String id) {
+ this.id = id;
+ }
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/SelectTreeModel.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/SelectTreeModel.java
new file mode 100644
index 0000000..5201267
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/SelectTreeModel.java
@@ -0,0 +1,32 @@
+package org.jeecg.common.system.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 下拉树 model
+ *
+ * @author jeecg-boot
+ */
+@Data
+public class SelectTreeModel implements Serializable {
+
+ private String key;
+ private String title;
+ private String value;
+ /**
+ * 父Id
+ */
+ private String parentId;
+ /**
+ * 是否是叶节点
+ */
+ private boolean isLeaf;
+ /**
+ * 子节点
+ */
+ private List children;
+
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/SysCategoryModel.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/SysCategoryModel.java
new file mode 100644
index 0000000..5499bef
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/SysCategoryModel.java
@@ -0,0 +1,52 @@
+package org.jeecg.common.system.vo;
+
+import org.jeecgframework.poi.excel.annotation.Excel;
+
+/**
+ * @Author qinfeng
+ * @Date 2020/2/19 12:01
+ * @Description:
+ * @Version 1.0
+ */
+public class SysCategoryModel {
+ /**主键*/
+ private java.lang.String id;
+ /**父级节点*/
+ private java.lang.String pid;
+ /**类型名称*/
+ private java.lang.String name;
+ /**类型编码*/
+ private java.lang.String code;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getPid() {
+ return pid;
+ }
+
+ public void setPid(String pid) {
+ this.pid = pid;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public void setCode(String code) {
+ this.code = code;
+ }
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/SysDepartModel.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/SysDepartModel.java
new file mode 100644
index 0000000..41dca9d
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/SysDepartModel.java
@@ -0,0 +1,148 @@
+package org.jeecg.common.system.vo;
+
+/**
+ * 部门机构model
+ * @author: lvdandan
+ */
+public class SysDepartModel {
+ /**ID*/
+ private String id;
+ /**父机构ID*/
+ private String parentId;
+ /**机构/部门名称*/
+ private String departName;
+ /**英文名*/
+ private String departNameEn;
+ /**缩写*/
+ private String departNameAbbr;
+ /**排序*/
+ private Integer departOrder;
+ /**描述*/
+ private String description;
+ /**机构类别 1组织机构,2岗位*/
+ private String orgCategory;
+ /**机构类型*/
+ private String orgType;
+ /**机构编码*/
+ private String orgCode;
+ /**手机号*/
+ private String mobile;
+ /**传真*/
+ private String fax;
+ /**地址*/
+ private String address;
+ /**备注*/
+ private String memo;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getParentId() {
+ return parentId;
+ }
+
+ public void setParentId(String parentId) {
+ this.parentId = parentId;
+ }
+
+ public String getDepartName() {
+ return departName;
+ }
+
+ public void setDepartName(String departName) {
+ this.departName = departName;
+ }
+
+ public String getDepartNameEn() {
+ return departNameEn;
+ }
+
+ public void setDepartNameEn(String departNameEn) {
+ this.departNameEn = departNameEn;
+ }
+
+ public String getDepartNameAbbr() {
+ return departNameAbbr;
+ }
+
+ public void setDepartNameAbbr(String departNameAbbr) {
+ this.departNameAbbr = departNameAbbr;
+ }
+
+ public Integer getDepartOrder() {
+ return departOrder;
+ }
+
+ public void setDepartOrder(Integer departOrder) {
+ this.departOrder = departOrder;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getOrgCategory() {
+ return orgCategory;
+ }
+
+ public void setOrgCategory(String orgCategory) {
+ this.orgCategory = orgCategory;
+ }
+
+ public String getOrgType() {
+ return orgType;
+ }
+
+ public void setOrgType(String orgType) {
+ this.orgType = orgType;
+ }
+
+ public String getOrgCode() {
+ return orgCode;
+ }
+
+ public void setOrgCode(String orgCode) {
+ this.orgCode = orgCode;
+ }
+
+ public String getMobile() {
+ return mobile;
+ }
+
+ public void setMobile(String mobile) {
+ this.mobile = mobile;
+ }
+
+ public String getFax() {
+ return fax;
+ }
+
+ public void setFax(String fax) {
+ this.fax = fax;
+ }
+
+ public String getAddress() {
+ return address;
+ }
+
+ public void setAddress(String address) {
+ this.address = address;
+ }
+
+ public String getMemo() {
+ return memo;
+ }
+
+ public void setMemo(String memo) {
+ this.memo = memo;
+ }
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/SysPermissionDataRuleModel.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/SysPermissionDataRuleModel.java
new file mode 100644
index 0000000..f9204d6
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/SysPermissionDataRuleModel.java
@@ -0,0 +1,151 @@
+package org.jeecg.common.system.vo;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ *
+ * 菜单权限规则表
+ *
+ *
+ * @Author huangzhilin
+ * @since 2019-03-29
+ */
+public class SysPermissionDataRuleModel {
+
+ /**
+ * id
+ */
+ private String id;
+
+ /**
+ * 对应的菜单id
+ */
+ private String permissionId;
+
+ /**
+ * 规则名称
+ */
+ private String ruleName;
+
+ /**
+ * 字段
+ */
+ private String ruleColumn;
+
+ /**
+ * 条件
+ */
+ private String ruleConditions;
+
+ /**
+ * 规则值
+ */
+ private String ruleValue;
+
+ /**
+ * 创建时间
+ */
+ private Date createTime;
+
+ /**
+ * 创建人
+ */
+ private String createBy;
+
+ /**
+ * 修改时间
+ */
+ private Date updateTime;
+
+ /**
+ * 修改人
+ */
+ private String updateBy;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getPermissionId() {
+ return permissionId;
+ }
+
+ public void setPermissionId(String permissionId) {
+ this.permissionId = permissionId;
+ }
+
+ public String getRuleName() {
+ return ruleName;
+ }
+
+ public void setRuleName(String ruleName) {
+ this.ruleName = ruleName;
+ }
+
+ public String getRuleColumn() {
+ return ruleColumn;
+ }
+
+ public void setRuleColumn(String ruleColumn) {
+ this.ruleColumn = ruleColumn;
+ }
+
+ public String getRuleConditions() {
+ return ruleConditions;
+ }
+
+ public void setRuleConditions(String ruleConditions) {
+ this.ruleConditions = ruleConditions;
+ }
+
+ public String getRuleValue() {
+ return ruleValue;
+ }
+
+ public void setRuleValue(String ruleValue) {
+ this.ruleValue = ruleValue;
+ }
+
+ public Date getCreateTime() {
+ return createTime;
+ }
+
+ public void setCreateTime(Date createTime) {
+ this.createTime = createTime;
+ }
+
+ public String getCreateBy() {
+ return createBy;
+ }
+
+ public void setCreateBy(String createBy) {
+ this.createBy = createBy;
+ }
+
+ public Date getUpdateTime() {
+ return updateTime;
+ }
+
+ public void setUpdateTime(Date updateTime) {
+ this.updateTime = updateTime;
+ }
+
+ public String getUpdateBy() {
+ return updateBy;
+ }
+
+ public void setUpdateBy(String updateBy) {
+ this.updateBy = updateBy;
+ }
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/SysUserCacheInfo.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/SysUserCacheInfo.java
new file mode 100644
index 0000000..a6e117a
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/SysUserCacheInfo.java
@@ -0,0 +1,71 @@
+package org.jeecg.common.system.vo;
+
+import java.util.List;
+
+import org.jeecg.common.util.DateUtils;
+
+/**
+ * @Description: 用户缓存信息
+ * @author: jeecg-boot
+ */
+public class SysUserCacheInfo {
+
+ private String sysUserCode;
+
+ private String sysUserName;
+
+ private String sysOrgCode;
+
+ private List sysMultiOrgCode;
+
+ private boolean oneDepart;
+
+ public boolean isOneDepart() {
+ return oneDepart;
+ }
+
+ public void setOneDepart(boolean oneDepart) {
+ this.oneDepart = oneDepart;
+ }
+
+ public String getSysDate() {
+ return DateUtils.formatDate();
+ }
+
+ public String getSysTime() {
+ return DateUtils.now();
+ }
+
+ public String getSysUserCode() {
+ return sysUserCode;
+ }
+
+ public void setSysUserCode(String sysUserCode) {
+ this.sysUserCode = sysUserCode;
+ }
+
+ public String getSysUserName() {
+ return sysUserName;
+ }
+
+ public void setSysUserName(String sysUserName) {
+ this.sysUserName = sysUserName;
+ }
+
+ public String getSysOrgCode() {
+ return sysOrgCode;
+ }
+
+ public void setSysOrgCode(String sysOrgCode) {
+ this.sysOrgCode = sysOrgCode;
+ }
+
+ public List getSysMultiOrgCode() {
+ return sysMultiOrgCode;
+ }
+
+ public void setSysMultiOrgCode(List sysMultiOrgCode) {
+ this.sysMultiOrgCode = sysMultiOrgCode;
+ }
+
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/Base64Util.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/Base64Util.java
new file mode 100644
index 0000000..6068262
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/Base64Util.java
@@ -0,0 +1,182 @@
+
+package org.jeecg.common.util;
+
+import org.springframework.util.StringUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Base64;
+
+/**
+ * Base64工具
+ */
+public class Base64Util extends org.springframework.util.Base64Utils {
+
+ /**
+ * 编码
+ *
+ * @param value 字符串
+ * @return {String}
+ */
+ public static String encode(String value) {
+ return Base64Util.encode(value, Charsets.UTF_8);
+ }
+
+ /**
+ * 编码
+ *
+ * @param value 字符串
+ * @param charset 字符集
+ * @return {String}
+ */
+ public static String encode(String value, java.nio.charset.Charset charset) {
+ byte[] val = value.getBytes(charset);
+ return new String(Base64Util.encode(val), charset);
+ }
+
+ /**
+ * 编码URL安全
+ *
+ * @param value 字符串
+ * @return {String}
+ */
+ public static String encodeUrlSafe(String value) {
+ return Base64Util.encodeUrlSafe(value, Charsets.UTF_8);
+ }
+
+ /**
+ * 编码URL安全
+ *
+ * @param value 字符串
+ * @param charset 字符集
+ * @return {String}
+ */
+ public static String encodeUrlSafe(String value, java.nio.charset.Charset charset) {
+ byte[] val = value.getBytes(charset);
+ return new String(Base64Util.encodeUrlSafe(val), charset);
+ }
+
+ /**
+ * 解码
+ *
+ * @param value 字符串
+ * @return {String}
+ */
+ public static String decode(String value) {
+ return Base64Util.decode(value, Charsets.UTF_8);
+ }
+
+ /**
+ * 解码
+ *
+ * @param value 字符串
+ * @param charset 字符集
+ * @return {String}
+ */
+ public static String decode(String value, java.nio.charset.Charset charset) {
+ byte[] val = value.getBytes(charset);
+ byte[] decodedValue = Base64Util.decode(val);
+ return new String(decodedValue, charset);
+ }
+
+ /**
+ * 解码URL安全
+ *
+ * @param value 字符串
+ * @return {String}
+ */
+ public static String decodeUrlSafe(String value) {
+ return Base64Util.decodeUrlSafe(value, Charsets.UTF_8);
+ }
+
+ /**
+ * 解码URL安全
+ *
+ * @param value 字符串
+ * @param charset 字符集
+ * @return {String}
+ */
+ public static String decodeUrlSafe(String value, java.nio.charset.Charset charset) {
+ byte[] val = value.getBytes(charset);
+ byte[] decodedValue = Base64Util.decodeUrlSafe(val);
+ return new String(decodedValue, charset);
+ }
+
+ public static String base64Process(String base64Str) {
+ if (!StringUtils.isEmpty(base64Str)) {
+ String photoBase64 = base64Str.substring(0, 30).toLowerCase();
+ int indexOf = photoBase64.indexOf("base64,");
+ if (indexOf > 0) {
+ base64Str = base64Str.substring(indexOf + 7);
+ }
+ base64Str = base64Str.replaceAll(" ", "+");
+ base64Str = base64Str.replaceAll("\r|\n", "");
+ return base64Str;
+ }
+ return "";
+ }
+
+ public static byte[] base64ToBytes(String base64) {
+ if (StringUtils.isEmpty(base64)) {
+ return null;
+ }
+ String base64Process = base64Process(base64);
+
+ byte[] decode = Base64.getDecoder().decode(base64Process);
+ return decode;
+
+
+ }
+ //资源未释放:流 - 质量问题 -中危
+// public static String imageToBase64(String img){
+// InputStream in = null;
+// byte[] date = null;
+// try{
+// in = new FileInputStream(img);
+// date = new byte[in.available()];
+// in.read();
+// in.close();
+// }catch (Exception e){
+// e.printStackTrace();
+// }
+// return Base64.getEncoder().encodeToString(date);
+// }
+
+ /**
+ *
+ * @param img
+ * @return
+ */
+ public static String netImageToBase64(String img){
+ ByteArrayOutputStream date = new ByteArrayOutputStream();
+ InputStream is = null;
+ try{
+ URL url = new URL(img);
+ byte[] by = new byte[1024];
+ HttpURLConnection connection = (HttpURLConnection)url.openConnection();
+ connection.setRequestMethod("GET");
+ connection.setConnectTimeout(5000);
+ is = connection.getInputStream();
+ int len = -1;
+ while ((len = is.read(by))!=-1){
+ date.write(by,0,len);
+ }
+ is.close();
+ }catch (Exception e){
+
+ } finally{
+ //资源未释放:流 - 质量问题 -中危
+ if(is!=null){
+ try {
+ is.close();
+ } catch (IOException e) {
+ System.out.println("关闭流异常【】");
+ }
+ }
+ }
+ return Base64.getEncoder().encodeToString(date.toByteArray());
+ }
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/BigDecimalSerialize.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/BigDecimalSerialize.java
new file mode 100644
index 0000000..943730f
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/BigDecimalSerialize.java
@@ -0,0 +1,23 @@
+package org.jeecg.common.util;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+
+/**
+ * @author Ls.
+ * @date 2021/7/22.
+ */
+public class BigDecimalSerialize extends JsonSerializer {
+ @Override
+ public void serialize(BigDecimal value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException {
+ if (value != null && !"".equals(value)) {
+ gen.writeString(value.setScale(2, BigDecimal.ROUND_HALF_DOWN) + "");
+ } else {
+ gen.writeString(value + "");
+ }
+ }
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/BrowserType.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/BrowserType.java
new file mode 100644
index 0000000..c080c61
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/BrowserType.java
@@ -0,0 +1,13 @@
+package org.jeecg.common.util;
+
+/**
+ *
+ * @Author 张代浩
+ *
+ */
+public enum BrowserType {
+ /**
+ * 浏览类型 IE11,IE10,IE9,IE8,IE7,IE6,Firefox,Safari,Chrome,Opera,Camino,Gecko
+ */
+ IE11,IE10,IE9,IE8,IE7,IE6,Firefox,Safari,Chrome,Opera,Camino,Gecko
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/BrowserUtils.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/BrowserUtils.java
new file mode 100644
index 0000000..08b3fbc
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/BrowserUtils.java
@@ -0,0 +1,212 @@
+package org.jeecg.common.util;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ *
+ * @Author 张代浩
+ *
+ */
+public class BrowserUtils {
+
+ /**
+ * 判断是否是IE
+ * @param request
+ * @return
+ */
+ public static boolean isIe(HttpServletRequest request) {
+ return (request.getHeader("USER-AGENT").toLowerCase().indexOf("msie") > 0 || request
+ .getHeader("USER-AGENT").toLowerCase().indexOf("rv:11.0") > 0) ? true
+ : false;
+ }
+
+ /**
+ * 获取IE版本
+ *
+ * @param request
+ * @return
+ */
+ public static Double getIeVersion(HttpServletRequest request) {
+ Double version = 0.0;
+ if (getBrowserType(request, IE11)) {
+ version = 11.0;
+ } else if (getBrowserType(request, IE10)) {
+ version = 10.0;
+ } else if (getBrowserType(request, IE9)) {
+ version = 9.0;
+ } else if (getBrowserType(request, IE8)) {
+ version = 8.0;
+ } else if (getBrowserType(request, IE7)) {
+ version = 7.0;
+ } else if (getBrowserType(request, IE6)) {
+ version = 6.0;
+ }
+ return version;
+ }
+
+ /**
+ * 获取浏览器类型
+ *
+ * @param request
+ * @return
+ */
+ public static BrowserType getBrowserType(HttpServletRequest request) {
+ BrowserType browserType = null;
+ if (getBrowserType(request, IE11)) {
+ browserType = BrowserType.IE11;
+ }
+ if (getBrowserType(request, IE10)) {
+ browserType = BrowserType.IE10;
+ }
+ if (getBrowserType(request, IE9)) {
+ browserType = BrowserType.IE9;
+ }
+ if (getBrowserType(request, IE8)) {
+ browserType = BrowserType.IE8;
+ }
+ if (getBrowserType(request, IE7)) {
+ browserType = BrowserType.IE7;
+ }
+ if (getBrowserType(request, IE6)) {
+ browserType = BrowserType.IE6;
+ }
+ if (getBrowserType(request, FIREFOX)) {
+ browserType = BrowserType.Firefox;
+ }
+ if (getBrowserType(request, SAFARI)) {
+ browserType = BrowserType.Safari;
+ }
+ if (getBrowserType(request, CHROME)) {
+ browserType = BrowserType.Chrome;
+ }
+ if (getBrowserType(request, OPERA)) {
+ browserType = BrowserType.Opera;
+ }
+ if (getBrowserType(request, CAMINO)) {
+ browserType = BrowserType.Camino;
+ }
+ return browserType;
+ }
+
+ private static boolean getBrowserType(HttpServletRequest request,
+ String brosertype) {
+ return request.getHeader("USER-AGENT").toLowerCase()
+ .indexOf(brosertype) > 0 ? true : false;
+ }
+
+ private final static String IE11 = "rv:11.0";
+ private final static String IE10 = "MSIE 10.0";
+ private final static String IE9 = "MSIE 9.0";
+ private final static String IE8 = "MSIE 8.0";
+ private final static String IE7 = "MSIE 7.0";
+ private final static String IE6 = "MSIE 6.0";
+ private final static String MAXTHON = "Maxthon";
+ private final static String QQ = "QQBrowser";
+ private final static String GREEN = "GreenBrowser";
+ private final static String SE360 = "360SE";
+ private final static String FIREFOX = "Firefox";
+ private final static String OPERA = "Opera";
+ private final static String CHROME = "Chrome";
+ private final static String SAFARI = "Safari";
+ private final static String OTHER = "其它";
+ private final static String CAMINO = "Camino";
+
+ public static String checkBrowse(HttpServletRequest request) {
+ String userAgent = request.getHeader("USER-AGENT");
+ if (regex(OPERA, userAgent)) {
+ return OPERA;
+ }
+ if (regex(CHROME, userAgent)) {
+ return CHROME;
+ }
+ if (regex(FIREFOX, userAgent)) {
+ return FIREFOX;
+ }
+ if (regex(SAFARI, userAgent)) {
+ return SAFARI;
+ }
+ if (regex(SE360, userAgent)) {
+ return SE360;
+ }
+ if (regex(GREEN, userAgent)) {
+ return GREEN;
+ }
+ if (regex(QQ, userAgent)) {
+ return QQ;
+ }
+ if (regex(MAXTHON, userAgent)) {
+ return MAXTHON;
+ }
+ if (regex(IE11, userAgent)) {
+ return IE11;
+ }
+ if (regex(IE10, userAgent)) {
+ return IE10;
+ }
+ if (regex(IE9, userAgent)) {
+ return IE9;
+ }
+ if (regex(IE8, userAgent)) {
+ return IE8;
+ }
+ if (regex(IE7, userAgent)) {
+ return IE7;
+ }
+ if (regex(IE6, userAgent)) {
+ return IE6;
+ }
+ return OTHER;
+ }
+
+ public static boolean regex(String regex, String str) {
+ Pattern p = Pattern.compile(regex, Pattern.MULTILINE);
+ Matcher m = p.matcher(str);
+ return m.find();
+ }
+
+
+ private static Map langMap = new HashMap();
+ private final static String ZH = "zh";
+ private final static String ZH_CN = "zh-cn";
+
+ private final static String EN = "en";
+ private final static String EN_US = "en";
+
+
+ static
+ {
+ langMap.put(ZH, ZH_CN);
+ langMap.put(EN, EN_US);
+ }
+
+ public static String getBrowserLanguage(HttpServletRequest request) {
+
+ String browserLang = request.getLocale().getLanguage();
+ String browserLangCode = (String)langMap.get(browserLang);
+
+ if(browserLangCode == null)
+ {
+ browserLangCode = EN_US;
+ }
+ return browserLangCode;
+ }
+
+ /** 判断请求是否来自电脑端 */
+ public static boolean isDesktop(HttpServletRequest request) {
+ return !isMobile(request);
+ }
+
+ /** 判断请求是否来自移动端 */
+ public static boolean isMobile(HttpServletRequest request) {
+ String ua = request.getHeader("User-Agent").toLowerCase();
+ String type = "(phone|pad|pod|iphone|ipod|ios|ipad|android|mobile|blackberry|iemobile|mqqbrowser|juc|fennec|wosbrowser|browserng|webos|symbian|windows phone)";
+ Pattern pattern = Pattern.compile(type);
+ return pattern.matcher(ua).find();
+ }
+
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/Charsets.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/Charsets.java
new file mode 100644
index 0000000..61a7caf
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/Charsets.java
@@ -0,0 +1,45 @@
+
+package org.jeecg.common.util;
+
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.charset.UnsupportedCharsetException;
+
+/**
+ * 字符集工具类
+ */
+public class Charsets {
+
+ /**
+ * 字符集ISO-8859-1
+ */
+ public static final Charset ISO_8859_1 = StandardCharsets.ISO_8859_1;
+ public static final String ISO_8859_1_NAME = ISO_8859_1.name();
+
+ /**
+ * 字符集GBK
+ */
+ public static final Charset GBK = Charset.forName(StringPool.GBK);
+ public static final String GBK_NAME = GBK.name();
+
+ /**
+ * 字符集utf-8
+ */
+ public static final Charset UTF_8 = StandardCharsets.UTF_8;
+ public static final String UTF_8_NAME = UTF_8.name();
+
+ /**
+ * 转换为Charset对象
+ *
+ * @param charsetName 字符集,为空则返回默认字符集
+ * @return Charsets
+ * @throws UnsupportedCharsetException 编码不支持
+ */
+ public static Charset charset(String charsetName) throws UnsupportedCharsetException {
+ return StringUtils.isBlank(charsetName) ? Charset.defaultCharset() : Charset.forName(charsetName);
+ }
+
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/CommonUtils.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/CommonUtils.java
new file mode 100644
index 0000000..cf3bce9
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/CommonUtils.java
@@ -0,0 +1,422 @@
+package org.jeecg.common.util;
+
+import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
+import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
+import com.baomidou.mybatisplus.annotation.DbType;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.FilenameUtils;
+import org.jeecg.common.constant.CommonConstant;
+import org.jeecg.common.constant.DataBaseConstant;
+import org.jeecg.common.util.filter.FileTypeFilter;
+import org.jeecg.common.util.oss.OssBootUtil;
+import org.jeecgframework.poi.util.PoiPublicUtil;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.jdbc.datasource.DriverManagerDataSource;
+import org.springframework.stereotype.Component;
+import org.springframework.util.FileCopyUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.sql.DataSource;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Slf4j
+@Component
+public class CommonUtils {
+
+ private static String uploadType;
+
+ //中文正则
+ private static Pattern ZHONGWEN_PATTERN = Pattern.compile("[\u4e00-\u9fa5]");
+
+ public static String uploadOnlineImage(byte[] data,String basePath,String bizPath,String uploadType){
+ String dbPath = null;
+ String fileName = null;
+ try {
+ //不安全的随机数-中危
+ SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
+ fileName = "image" + Math.round(random.nextDouble() * 100000000000L);
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ }
+ fileName += "." + PoiPublicUtil.getFileExtendName(data);
+ try {
+ if(CommonConstant.UPLOAD_TYPE_LOCAL.equals(uploadType)){
+ String path = basePath + File.separator + bizPath + File.separator;
+// File file = new File(basePath + File.separator + bizPath + File.separator );
+ //解决路径遍历漏洞-路径 -高危
+ String filePath = FilenameUtils.getFullPath(path);
+ File file = new File(XssUtils.checkTraversal(filePath));
+
+ if (!file.exists()) {
+ file.mkdirs();// 创建文件根目录
+ }
+ String savePath = file.getPath() + File.separator + fileName;
+ File savefile = new File(savePath);
+ FileCopyUtils.copy(data, savefile);
+ dbPath = bizPath + File.separator + fileName;
+ }else {
+ InputStream in = new ByteArrayInputStream(data);
+ String relativePath = bizPath+"/"+fileName;
+ if(CommonConstant.UPLOAD_TYPE_MINIO.equals(uploadType)){
+ dbPath = MinioUtil.upload(in,relativePath);
+ }if(CommonConstant.UPLOAD_MY_UPLOAD.equals(uploadType)){
+ dbPath = MyUploadUtil.upload(in,bizPath,fileName);
+ }else if(CommonConstant.UPLOAD_TYPE_OSS.equals(uploadType)){
+ dbPath = OssBootUtil.upload(in,relativePath);
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return dbPath;
+ }
+
+ /**
+ * 判断文件名是否带盘符,重新处理
+ * @param fileName
+ * @return
+ */
+ public static String getFileName(String fileName){
+ //判断是否带有盘符信息
+ // Check for Unix-style path
+ int unixSep = fileName.lastIndexOf('/');
+ // Check for Windows-style path
+ int winSep = fileName.lastIndexOf('\\');
+ // Cut off at latest possible point
+ int pos = (winSep > unixSep ? winSep : unixSep);
+ if (pos != -1) {
+ // Any sort of path separator found...
+ fileName = fileName.substring(pos + 1);
+ }
+ //替换上传文件名字的特殊字符
+ fileName = fileName.replace("=","").replace(",","").replace("&","")
+ .replace("#", "").replace("“", "").replace("”", "");
+ //替换上传文件名字中的空格
+ fileName=fileName.replaceAll("\\s","");
+ return fileName;
+ }
+
+ // java 判断字符串里是否包含中文字符
+ public static boolean ifContainChinese(String str) {
+ if(str.getBytes().length == str.length()){
+ return false;
+ }else{
+ Matcher m = ZHONGWEN_PATTERN.matcher(str);
+ if (m.find()) {
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * 统一全局上传
+ * @Return: java.lang.String
+ */
+ public static String upload(MultipartFile file, String bizPath, String uploadType) {
+ String url = "";
+ if(CommonConstant.UPLOAD_TYPE_MINIO.equals(uploadType)){
+ url = MinioUtil.upload(file,bizPath);
+ }else if(CommonConstant.UPLOAD_MY_UPLOAD.equals(uploadType)){
+ url = MyUploadUtil.upload(file,bizPath);
+ }else{
+ url = OssBootUtil.upload(file,bizPath);
+ }
+ return url;
+ }
+
+ /**
+ * 统一全局上传
+ * @Return: java.lang.String
+ */
+ public static String upload(InputStream inputStream,String bizPath,String fileName){
+ String url = "";
+ try {
+ if(CommonConstant.UPLOAD_MY_UPLOAD.equals(uploadType)){
+ url = MyUploadUtil.upload(inputStream,bizPath,fileName);
+ }else if(CommonConstant.UPLOAD_TYPE_MINIO.equals(uploadType)){
+ url = MinioUtil.upload(inputStream,bizPath+File.separator+fileName);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ log.error("上传minio失败");
+ }
+ return url;
+ }
+
+ /**
+ * 统一全局上传 带是否需要上传缩略图
+ * @Return: java.lang.String
+ */
+ public static String upload(MultipartFile file, String bizPath, String uploadType, Boolean needThumbnailFlag) {
+ String url = "";
+ if(CommonConstant.UPLOAD_TYPE_MINIO.equals(uploadType)){
+ url = MinioUtil.upload(file, bizPath);
+ }else if(CommonConstant.UPLOAD_MY_UPLOAD.equals(uploadType)){
+ url = MyUploadUtil.upload(file, bizPath, needThumbnailFlag);
+ }else{
+ url = OssBootUtil.upload(file, bizPath);
+ }
+ return url;
+ }
+ /**
+ * 上传文件 固定目录 带缩略图(同名覆盖)
+ * @Return: java.lang.String
+ */
+ public static String uploadFixedDirectory(MultipartFile file, String bizPath, String uploadType, Boolean needThumbnailFlag) {
+ String url = "";
+ if(CommonConstant.UPLOAD_TYPE_MINIO.equals(uploadType)){
+ url = MinioUtil.upload(file, bizPath);
+ }else if(CommonConstant.UPLOAD_MY_UPLOAD.equals(uploadType)){
+ url = MyUploadUtil.uploadFixedDirectory(file, bizPath, needThumbnailFlag);
+ }else{
+ url = OssBootUtil.upload(file, bizPath);
+ }
+ return url;
+ }
+
+ /**
+ * 本地文件上传
+ * @param mf 文件
+ * @param bizPath 自定义路径
+ * @return
+ */
+ public static String uploadLocal(MultipartFile mf,String bizPath,String uploadpath){
+ try {
+ //update-begin-author:liusq date:20210809 for: 过滤上传文件类型
+ FileTypeFilter.fileTypeFilter(mf);
+ //update-end-author:liusq date:20210809 for: 过滤上传文件类型
+ String fileName = null;
+
+ File file = new File(XssUtils.checkTraversal(uploadpath + File.separator + bizPath + File.separator ));
+ if (!file.exists()) {
+ file.mkdirs();// 创建文件根目录
+ }
+ String orgName = mf.getOriginalFilename();// 获取文件名
+ orgName = CommonUtils.getFileName(orgName);
+ if(orgName.indexOf(".")!=-1){
+ fileName = orgName.substring(0, orgName.lastIndexOf(".")) + "_" + System.currentTimeMillis() + orgName.substring(orgName.lastIndexOf("."));
+ }else{
+ fileName = orgName+ "_" + System.currentTimeMillis();
+ }
+ String savePath = file.getPath() + File.separator + fileName;
+ File savefile = new File(savePath);
+ FileCopyUtils.copy(mf.getBytes(), savefile);
+ String dbpath = null;
+ if(oConvertUtils.isNotEmpty(bizPath)){
+ dbpath = bizPath + File.separator + fileName;
+ }else{
+ dbpath = fileName;
+ }
+ if (dbpath.contains("\\")) {
+ dbpath = dbpath.replace("\\", "/");
+ }
+ return dbpath;
+ } catch (IOException e) {
+ log.error(e.getMessage(), e);
+ }catch (Exception e) {
+ log.error(e.getMessage(), e);
+ }
+ return "";
+ }
+
+ /**
+ * 统一全局上传 带桶
+ * @Return: java.lang.String
+ */
+ public static String upload(MultipartFile file, String bizPath, String uploadType, String customBucket) {
+ String url = "";
+ if(CommonConstant.UPLOAD_TYPE_MINIO.equals(uploadType)){
+ url = MinioUtil.upload(file,bizPath,customBucket);
+ }else if(CommonConstant.UPLOAD_MY_UPLOAD.equals(uploadType)){
+ url = MyUploadUtil.upload(file,bizPath);
+ }
+ return url;
+ }
+
+ /** 当前系统数据库类型 */
+ private static String DB_TYPE = "";
+ private static DbType dbTypeEnum = null;
+
+ /**
+ * 全局获取平台数据库类型(作废了)
+ * @return
+ */
+ @Deprecated
+ public static String getDatabaseType() {
+ if(oConvertUtils.isNotEmpty(DB_TYPE)){
+ return DB_TYPE;
+ }
+ DataSource dataSource = SpringContextUtils.getApplicationContext().getBean(DataSource.class);
+ try {
+ return getDatabaseTypeByDataSource(dataSource);
+ } catch (SQLException e) {
+ //e.printStackTrace();
+ log.warn(e.getMessage(),e);
+ return "";
+ }
+ }
+
+ /**
+ * 全局获取平台数据库类型(对应mybaisPlus枚举)
+ * @return
+ */
+ public static DbType getDatabaseTypeEnum() {
+ return null;
+ //资源未释放:数据库-中危
+// if (oConvertUtils.isNotEmpty(dbTypeEnum)) {
+// return dbTypeEnum;
+// }
+// try {
+// DataSource dataSource = SpringContextUtils.getApplicationContext().getBean(DataSource.class);
+// dbTypeEnum = JdbcUtils.getDbType(dataSource.getConnection().getMetaData().getURL());
+// return dbTypeEnum;
+// } catch (SQLException e) {
+// log.warn(e.getMessage(), e);
+// return null;
+// }
+ }
+
+ /**
+ * 根据数据源key获取DataSourceProperty
+ * @param sourceKey
+ * @return
+ */
+ public static DataSourceProperty getDataSourceProperty(String sourceKey){
+ DynamicDataSourceProperties prop = SpringContextUtils.getApplicationContext().getBean(DynamicDataSourceProperties.class);
+ Map map = prop.getDatasource();
+ DataSourceProperty db = (DataSourceProperty)map.get(sourceKey);
+ return db;
+ }
+
+ /**
+ * 根据sourceKey 获取数据源连接
+ * @param sourceKey
+ * @return
+ * @throws SQLException
+ */
+ public static Connection getDataSourceConnect(String sourceKey) throws SQLException {
+ if (oConvertUtils.isEmpty(sourceKey)) {
+ sourceKey = "master";
+ }
+ DynamicDataSourceProperties prop = SpringContextUtils.getApplicationContext().getBean(DynamicDataSourceProperties.class);
+ Map map = prop.getDatasource();
+ DataSourceProperty db = (DataSourceProperty)map.get(sourceKey);
+ if(db==null){
+ return null;
+ }
+ DriverManagerDataSource ds = new DriverManagerDataSource ();
+ ds.setDriverClassName(db.getDriverClassName());
+ ds.setUrl(db.getUrl());
+ ds.setUsername(db.getUsername());
+ ds.setPassword(db.getPassword());
+ return ds.getConnection();
+ }
+
+ /**
+ * 获取数据库类型
+ * @param dataSource
+ * @return
+ * @throws SQLException
+ */
+ private static String getDatabaseTypeByDataSource(DataSource dataSource) throws SQLException{
+ if("".equals(DB_TYPE)) {
+ Connection connection = dataSource.getConnection();
+ try {
+ DatabaseMetaData md = connection.getMetaData();
+ String dbType = md.getDatabaseProductName().toLowerCase();
+ if(dbType.indexOf("mysql")>=0) {
+ DB_TYPE = DataBaseConstant.DB_TYPE_MYSQL;
+ }else if(dbType.indexOf("oracle")>=0 ||dbType.indexOf("dm")>=0) {
+ DB_TYPE = DataBaseConstant.DB_TYPE_ORACLE;
+ }else if(dbType.indexOf("sqlserver")>=0||dbType.indexOf("sql server")>=0) {
+ DB_TYPE = DataBaseConstant.DB_TYPE_SQLSERVER;
+ }else if(dbType.indexOf("postgresql")>=0) {
+ DB_TYPE = DataBaseConstant.DB_TYPE_POSTGRESQL;
+ }else if(dbType.indexOf("mariadb")>=0) {
+ DB_TYPE = DataBaseConstant.DB_TYPE_MARIADB;
+ }else {
+ log.error("数据库类型:[" + dbType + "]不识别!");
+ //throw new JeecgBootException("数据库类型:["+dbType+"]不识别!");
+ }
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ }finally {
+ connection.close();
+ }
+ }
+ return DB_TYPE;
+
+ }
+ /**
+ * 获取服务器地址
+ *
+ * @param request
+ * @return
+ */
+ public static String getBaseUrl(HttpServletRequest request) {
+ //1.【兼容】兼容微服务下的 base path-------
+ String x_gateway_base_path = request.getHeader("X_GATEWAY_BASE_PATH");
+ if(oConvertUtils.isNotEmpty(x_gateway_base_path)){
+ log.info("x_gateway_base_path = "+ x_gateway_base_path);
+ return x_gateway_base_path;
+ }
+ //2.【兼容】SSL认证之后,request.getScheme()获取不到https的问题
+ // https://blog.csdn.net/weixin_34376986/article/details/89767950
+ String scheme = request.getHeader("X-Forwarded-Scheme");
+ if(oConvertUtils.isEmpty(scheme)){
+ scheme = request.getScheme();
+ }
+
+ //3.常规操作
+ String serverName = request.getServerName();
+ int serverPort = request.getServerPort();
+ String contextPath = request.getContextPath();
+
+ //返回 host domain
+ String baseDomainPath = null;
+ if(80 == serverPort){
+ baseDomainPath = scheme + "://" + serverName + contextPath ;
+ }else{
+ baseDomainPath = scheme + "://" + serverName + ":" + serverPort + contextPath ;
+ }
+ log.info("-----Common getBaseUrl----- : " + baseDomainPath);
+ return baseDomainPath;
+ }
+
+
+ public static List> splitList(List list, int size) {
+ List> lists = new ArrayList<>();
+ int listSize = list.size();
+ int numFullChunks = listSize / size;
+ for (int i = 0; i < numFullChunks; i++) {
+ int fromIndex = i * size;
+ int toIndex = Math.min((i + 1) * size, listSize);
+ lists.add(list.subList(fromIndex, toIndex));
+ }
+
+ // 如果列表大小不是size的整数倍,添加剩余的元素
+ if (listSize % size != 0) {
+ int fromIndex = numFullChunks * size;
+ lists.add(list.subList(fromIndex, listSize));
+ }
+
+ return lists;
+ }
+
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/DateUtils.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/DateUtils.java
new file mode 100644
index 0000000..cd98e6c
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/DateUtils.java
@@ -0,0 +1,697 @@
+package org.jeecg.common.util;
+
+import java.beans.PropertyEditorSupport;
+import java.sql.Timestamp;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+import org.jeecg.common.constant.SymbolConstant;
+import org.springframework.util.StringUtils;
+
+/**
+ * 类描述:时间操作定义类
+ *
+ * @Author: 张代浩
+ * @Date:2012-12-8 12:15:03
+ * @Version 1.0
+ */
+public class DateUtils extends PropertyEditorSupport {
+
+ public static ThreadLocal date_sdf = new ThreadLocal() {
+ @Override
+ protected SimpleDateFormat initialValue() {
+ return new SimpleDateFormat("yyyy-MM-dd");
+ }
+ };
+ public static ThreadLocal yyyyMMdd = new ThreadLocal() {
+ @Override
+ protected SimpleDateFormat initialValue() {
+ return new SimpleDateFormat("yyyyMMdd");
+ }
+ };
+ public static ThreadLocal date_sdf_wz = new ThreadLocal() {
+ @Override
+ protected SimpleDateFormat initialValue() {
+ return new SimpleDateFormat("yyyy年MM月dd日");
+ }
+ };
+ public static ThreadLocal time_sdf = new ThreadLocal() {
+ @Override
+ protected SimpleDateFormat initialValue() {
+ return new SimpleDateFormat("yyyy-MM-dd HH:mm");
+ }
+ };
+ public static ThreadLocal yyyymmddhhmmss = new ThreadLocal() {
+ @Override
+ protected SimpleDateFormat initialValue() {
+ return new SimpleDateFormat("yyyyMMddHHmmss");
+ }
+ };
+ public static ThreadLocal short_time_sdf = new ThreadLocal() {
+ @Override
+ protected SimpleDateFormat initialValue() {
+ return new SimpleDateFormat("HH:mm");
+ }
+ };
+ public static ThreadLocal datetimeFormat = new ThreadLocal() {
+ @Override
+ protected SimpleDateFormat initialValue() {
+ return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ }
+ };
+
+ /**
+ * 以毫秒表示的时间
+ */
+ private static final long DAY_IN_MILLIS = 24 * 3600 * 1000;
+ private static final long HOUR_IN_MILLIS = 3600 * 1000;
+ private static final long MINUTE_IN_MILLIS = 60 * 1000;
+ private static final long SECOND_IN_MILLIS = 1000;
+
+ /**
+ * 指定模式的时间格式
+ * @param pattern
+ * @return
+ */
+ private static SimpleDateFormat getSdFormat(String pattern) {
+ return new SimpleDateFormat(pattern);
+ }
+
+ /**
+ * 当前日历,这里用中国时间表示
+ *
+ * @return 以当地时区表示的系统当前日历
+ */
+ public static Calendar getCalendar() {
+ return Calendar.getInstance();
+ }
+
+ /**
+ * 指定毫秒数表示的日历
+ *
+ * @param millis 毫秒数
+ * @return 指定毫秒数表示的日历
+ */
+ public static Calendar getCalendar(long millis) {
+ Calendar cal = Calendar.getInstance();
+ // --------------------cal.setTimeInMillis(millis);
+ cal.setTime(new Date(millis));
+ return cal;
+ }
+
+ // ////////////////////////////////////////////////////////////////////////////
+ // getDate
+ // 各种方式获取的Date
+ // ////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * 当前日期
+ *
+ * @return 系统当前时间
+ */
+ public static Date getDate() {
+ return new Date();
+ }
+
+ /**
+ * 指定毫秒数表示的日期
+ *
+ * @param millis 毫秒数
+ * @return 指定毫秒数表示的日期
+ */
+ public static Date getDate(long millis) {
+ return new Date(millis);
+ }
+
+ /**
+ * 时间戳转换为字符串
+ *
+ * @param time
+ * @return
+ */
+ public static String timestamptoStr(Timestamp time) {
+ Date date = null;
+ if (null != time) {
+ date = new Date(time.getTime());
+ }
+ return date2Str(date_sdf.get());
+ }
+
+ /**
+ * 字符串转换时间戳
+ *
+ * @param str
+ * @return
+ */
+ public static Timestamp str2Timestamp(String str) {
+ Date date = str2Date(str, date_sdf.get());
+ return new Timestamp(date.getTime());
+ }
+
+ /**
+ * 字符串转换成日期
+ *
+ * @param str
+ * @param sdf
+ * @return
+ */
+ public static Date str2Date(String str, SimpleDateFormat sdf) {
+ if (null == str || "".equals(str)) {
+ return null;
+ }
+ Date date = null;
+ try {
+ date = sdf.parse(str);
+ return date;
+ } catch (ParseException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * 日期转换为字符串
+ *
+ * @param dateSdf 日期格式
+ * @return 字符串
+ */
+ public static String date2Str(SimpleDateFormat dateSdf) {
+ synchronized (dateSdf) {
+ Date date = getDate();
+ if (null == date) {
+ return null;
+ }
+ return dateSdf.format(date);
+ }
+ }
+
+ /**
+ * 格式化时间
+ *
+ * @param date
+ * @param format
+ * @return
+ */
+ public static String dateformat(String date, String format) {
+ SimpleDateFormat sformat = new SimpleDateFormat(format);
+ Date nowDate = null;
+ try {
+ nowDate = sformat.parse(date);
+ } catch (ParseException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return sformat.format(nowDate);
+ }
+
+ /**
+ * 日期转换为字符串
+ *
+ * @param date 日期
+ * @param dateSdf 日期格式
+ * @return 字符串
+ */
+ public static String date2Str(Date date, SimpleDateFormat dateSdf) {
+ synchronized (dateSdf) {
+ if (null == date) {
+ return null;
+ }
+ return dateSdf.format(date);
+ }
+ }
+
+ /**
+ * 日期转换为字符串
+ *
+ * @param format 日期格式
+ * @return 字符串
+ */
+ public static String getDate(String format) {
+ Date date = new Date();
+ if (null == date) {
+ return null;
+ }
+ SimpleDateFormat sdf = new SimpleDateFormat(format);
+ return sdf.format(date);
+ }
+
+ /**
+ * 指定毫秒数的时间戳
+ *
+ * @param millis 毫秒数
+ * @return 指定毫秒数的时间戳
+ */
+ public static Timestamp getTimestamp(long millis) {
+ return new Timestamp(millis);
+ }
+
+ /**
+ * 以字符形式表示的时间戳
+ *
+ * @param time 毫秒数
+ * @return 以字符形式表示的时间戳
+ */
+ public static Timestamp getTimestamp(String time) {
+ return new Timestamp(Long.parseLong(time));
+ }
+
+ /**
+ * 系统当前的时间戳
+ *
+ * @return 系统当前的时间戳
+ */
+ public static Timestamp getTimestamp() {
+ return new Timestamp(System.currentTimeMillis());
+ }
+
+ /**
+ * 当前时间,格式 yyyy-MM-dd HH:mm:ss
+ *
+ * @return 当前时间的标准形式字符串
+ */
+ public static String now() {
+ return datetimeFormat.get().format(getCalendar().getTime());
+ }
+
+ /**
+ * 指定日期的时间戳
+ *
+ * @param date 指定日期
+ * @return 指定日期的时间戳
+ */
+ public static Timestamp getTimestamp(Date date) {
+ return new Timestamp(date.getTime());
+ }
+
+ /**
+ * 指定日历的时间戳
+ *
+ * @param cal 指定日历
+ * @return 指定日历的时间戳
+ */
+ public static Timestamp getCalendarTimestamp(Calendar cal) {
+ // ---------------------return new Timestamp(cal.getTimeInMillis());
+ return new Timestamp(cal.getTime().getTime());
+ }
+
+ public static Timestamp gettimestamp() {
+ Date dt = new Date();
+ DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ String nowTime = df.format(dt);
+ java.sql.Timestamp buydate = java.sql.Timestamp.valueOf(nowTime);
+ return buydate;
+ }
+
+ // ////////////////////////////////////////////////////////////////////////////
+ // getMillis
+ // 各种方式获取的Millis
+ // ////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * 系统时间的毫秒数
+ *
+ * @return 系统时间的毫秒数
+ */
+ public static long getMillis() {
+ return System.currentTimeMillis();
+ }
+
+ /**
+ * 指定日历的毫秒数
+ *
+ * @param cal 指定日历
+ * @return 指定日历的毫秒数
+ */
+ public static long getMillis(Calendar cal) {
+ // --------------------return cal.getTimeInMillis();
+ return cal.getTime().getTime();
+ }
+
+ /**
+ * 指定日期的毫秒数
+ *
+ * @param date 指定日期
+ * @return 指定日期的毫秒数
+ */
+ public static long getMillis(Date date) {
+ return date.getTime();
+ }
+
+ /**
+ * 指定时间戳的毫秒数
+ *
+ * @param ts 指定时间戳
+ * @return 指定时间戳的毫秒数
+ */
+ public static long getMillis(Timestamp ts) {
+ return ts.getTime();
+ }
+
+ // ////////////////////////////////////////////////////////////////////////////
+ // formatDate
+ // 将日期按照一定的格式转化为字符串
+ // ////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * 默认方式表示的系统当前日期,具体格式:年-月-日
+ *
+ * @return 默认日期按“年-月-日“格式显示
+ */
+ public static String formatDate() {
+ return date_sdf.get().format(getCalendar().getTime());
+ }
+
+ /**
+ * 默认方式表示的系统当前日期,具体格式:yyyy-MM-dd HH:mm:ss
+ *
+ * @return 默认日期按“yyyy-MM-dd HH:mm:ss“格式显示
+ */
+ public static String formatDateTime() {
+ return datetimeFormat.get().format(getCalendar().getTime());
+ }
+
+ /**
+ * 获取时间字符串
+ */
+ public static String getDataString(SimpleDateFormat formatstr) {
+ synchronized (formatstr) {
+ return formatstr.format(getCalendar().getTime());
+ }
+ }
+
+ /**
+ * 指定日期的默认显示,具体格式:年-月-日
+ *
+ * @param cal 指定的日期
+ * @return 指定日期按“年-月-日“格式显示
+ */
+ public static String formatDate(Calendar cal) {
+ return date_sdf.get().format(cal.getTime());
+ }
+
+ /**
+ * 指定日期的默认显示,具体格式:年-月-日
+ *
+ * @param date 指定的日期
+ * @return 指定日期按“年-月-日“格式显示
+ */
+ public static String formatDate(Date date) {
+ return date_sdf.get().format(date);
+ }
+
+ /**
+ * 指定毫秒数表示日期的默认显示,具体格式:年-月-日
+ *
+ * @param millis 指定的毫秒数
+ * @return 指定毫秒数表示日期按“年-月-日“格式显示
+ */
+ public static String formatDate(long millis) {
+ return date_sdf.get().format(new Date(millis));
+ }
+
+ /**
+ * 默认日期按指定格式显示
+ *
+ * @param pattern 指定的格式
+ * @return 默认日期按指定格式显示
+ */
+ public static String formatDate(String pattern) {
+ return getSdFormat(pattern).format(getCalendar().getTime());
+ }
+
+ /**
+ * 指定日期按指定格式显示
+ *
+ * @param cal 指定的日期
+ * @param pattern 指定的格式
+ * @return 指定日期按指定格式显示
+ */
+ public static String formatDate(Calendar cal, String pattern) {
+ return getSdFormat(pattern).format(cal.getTime());
+ }
+
+ /**
+ * 指定日期按指定格式显示
+ *
+ * @param date 指定的日期
+ * @param pattern 指定的格式
+ * @return 指定日期按指定格式显示
+ */
+ public static String formatDate(Date date, String pattern) {
+ return getSdFormat(pattern).format(date);
+ }
+
+ // ////////////////////////////////////////////////////////////////////////////
+ // formatTime
+ // 将日期按照一定的格式转化为字符串
+ // ////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * 默认方式表示的系统当前日期,具体格式:年-月-日 时:分
+ *
+ * @return 默认日期按“年-月-日 时:分“格式显示
+ */
+ public static String formatTime() {
+ return time_sdf.get().format(getCalendar().getTime());
+ }
+
+ /**
+ * 指定毫秒数表示日期的默认显示,具体格式:年-月-日 时:分
+ *
+ * @param millis 指定的毫秒数
+ * @return 指定毫秒数表示日期按“年-月-日 时:分“格式显示
+ */
+ public static String formatTime(long millis) {
+ return time_sdf.get().format(new Date(millis));
+ }
+
+ /**
+ * 指定日期的默认显示,具体格式:年-月-日 时:分
+ *
+ * @param cal 指定的日期
+ * @return 指定日期按“年-月-日 时:分“格式显示
+ */
+ public static String formatTime(Calendar cal) {
+ return time_sdf.get().format(cal.getTime());
+ }
+
+ /**
+ * 指定日期的默认显示,具体格式:年-月-日 时:分
+ *
+ * @param date 指定的日期
+ * @return 指定日期按“年-月-日 时:分“格式显示
+ */
+ public static String formatTime(Date date) {
+ return time_sdf.get().format(date);
+ }
+
+ // ////////////////////////////////////////////////////////////////////////////
+ // formatShortTime
+ // 将日期按照一定的格式转化为字符串
+ // ////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * 默认方式表示的系统当前日期,具体格式:时:分
+ *
+ * @return 默认日期按“时:分“格式显示
+ */
+ public static String formatShortTime() {
+ return short_time_sdf.get().format(getCalendar().getTime());
+ }
+
+ /**
+ * 指定毫秒数表示日期的默认显示,具体格式:时:分
+ *
+ * @param millis 指定的毫秒数
+ * @return 指定毫秒数表示日期按“时:分“格式显示
+ */
+ public static String formatShortTime(long millis) {
+ return short_time_sdf.get().format(new Date(millis));
+ }
+
+ /**
+ * 指定日期的默认显示,具体格式:时:分
+ *
+ * @param cal 指定的日期
+ * @return 指定日期按“时:分“格式显示
+ */
+ public static String formatShortTime(Calendar cal) {
+ return short_time_sdf.get().format(cal.getTime());
+ }
+
+ /**
+ * 指定日期的默认显示,具体格式:时:分
+ *
+ * @param date 指定的日期
+ * @return 指定日期按“时:分“格式显示
+ */
+ public static String formatShortTime(Date date) {
+ return short_time_sdf.get().format(date);
+ }
+
+ // ////////////////////////////////////////////////////////////////////////////
+ // parseDate
+ // parseCalendar
+ // parseTimestamp
+ // 将字符串按照一定的格式转化为日期或时间
+ // ////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * 根据指定的格式将字符串转换成Date 如输入:2003-11-19 11:20:20将按照这个转成时间
+ *
+ * @param src 将要转换的原始字符窜
+ * @param pattern 转换的匹配格式
+ * @return 如果转换成功则返回转换后的日期
+ * @throws ParseException
+ */
+ public static Date parseDate(String src, String pattern) throws ParseException {
+ return getSdFormat(pattern).parse(src);
+
+ }
+
+ /**
+ * 根据指定的格式将字符串转换成Date 如输入:2003-11-19 11:20:20将按照这个转成时间
+ *
+ * @param src 将要转换的原始字符窜
+ * @param pattern 转换的匹配格式
+ * @return 如果转换成功则返回转换后的日期
+ * @throws ParseException
+ */
+ public static Calendar parseCalendar(String src, String pattern) throws ParseException {
+
+ Date date = parseDate(src, pattern);
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(date);
+ return cal;
+ }
+
+ public static String formatAddDate(String src, String pattern, int amount) throws ParseException {
+ Calendar cal;
+ cal = parseCalendar(src, pattern);
+ cal.add(Calendar.DATE, amount);
+ return formatDate(cal);
+ }
+
+ /**
+ * 根据指定的格式将字符串转换成Date 如输入:2003-11-19 11:20:20将按照这个转成时间
+ *
+ * @param src 将要转换的原始字符窜
+ * @param pattern 转换的匹配格式
+ * @return 如果转换成功则返回转换后的时间戳
+ * @throws ParseException
+ */
+ public static Timestamp parseTimestamp(String src, String pattern) throws ParseException {
+ Date date = parseDate(src, pattern);
+ return new Timestamp(date.getTime());
+ }
+
+ // ////////////////////////////////////////////////////////////////////////////
+ // dateDiff
+ // 计算两个日期之间的差值
+ // ////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * 计算两个时间之间的差值,根据标志的不同而不同
+ *
+ * @param flag 计算标志,表示按照年/月/日/时/分/秒等计算
+ * @param calSrc 减数
+ * @param calDes 被减数
+ * @return 两个日期之间的差值
+ */
+ public static int dateDiff(char flag, Calendar calSrc, Calendar calDes) {
+
+ long millisDiff = getMillis(calSrc) - getMillis(calDes);
+ char year = 'y';
+ char day = 'd';
+ char hour = 'h';
+ char minute = 'm';
+ char second = 's';
+
+ if (flag == year) {
+ return (calSrc.get(Calendar.YEAR) - calDes.get(Calendar.YEAR));
+ }
+
+ if (flag == day) {
+ return (int) (millisDiff / DAY_IN_MILLIS);
+ }
+
+ if (flag == hour) {
+ return (int) (millisDiff / HOUR_IN_MILLIS);
+ }
+
+ if (flag == minute) {
+ return (int) (millisDiff / MINUTE_IN_MILLIS);
+ }
+
+ if (flag == second) {
+ return (int) (millisDiff / SECOND_IN_MILLIS);
+ }
+
+ return 0;
+ }
+
+ public static Long getCurrentTimestamp() {
+ return Long.valueOf(DateUtils.yyyymmddhhmmss.get().format(new Date()));
+ }
+
+ /**
+ * String类型 转换为Date, 如果参数长度为10 转换格式”yyyy-MM-dd“ 如果参数长度为19 转换格式”yyyy-MM-dd
+ * HH:mm:ss“ * @param text String类型的时间值
+ */
+ @Override
+ public void setAsText(String text) throws IllegalArgumentException {
+ if (StringUtils.hasText(text)) {
+ try {
+ int length10 = 10;
+ int length19 = 19;
+ if (text.indexOf(SymbolConstant.COLON) == -1 && text.length() == length10) {
+ setValue(DateUtils.date_sdf.get().parse(text));
+ } else if (text.indexOf(SymbolConstant.COLON) > 0 && text.length() == length19) {
+ setValue(DateUtils.datetimeFormat.get().parse(text));
+ } else {
+ throw new IllegalArgumentException("Could not parse date, date format is error ");
+ }
+ } catch (ParseException ex) {
+ IllegalArgumentException iae = new IllegalArgumentException("Could not parse date: " + ex.getMessage());
+ iae.initCause(ex);
+ throw iae;
+ }
+ } else {
+ setValue(null);
+ }
+ }
+
+ public static int getYear() {
+ GregorianCalendar calendar = new GregorianCalendar();
+ calendar.setTime(getDate());
+ return calendar.get(Calendar.YEAR);
+ }
+
+ // 定义格式化器(线程安全)
+ private static final DateTimeFormatter FORMATTER =
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+ /**
+ * 将秒级时间戳转换为字符串(默认系统时区)
+ */
+ public static String timestampToString(long timestampSeconds) {
+ // 1. 将秒级时间戳转换为 Instant(Java 8+ 的时间戳表示)
+ Instant instant = Instant.ofEpochSecond(timestampSeconds);
+
+ // 2. 将 Instant 转换为带时区的 LocalDateTime
+ LocalDateTime localDateTime = instant.atZone(ZoneId.systemDefault()).toLocalDateTime();
+
+ // 3. 格式化为字符串
+ return localDateTime.format(FORMATTER);
+ }
+
+
+}
\ No newline at end of file
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/DownFileUtil.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/DownFileUtil.java
new file mode 100644
index 0000000..f677c34
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/DownFileUtil.java
@@ -0,0 +1,548 @@
+package org.jeecg.common.util;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang3.concurrent.BasicThreadFactory;
+import org.springframework.stereotype.Component;
+
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.UnknownHostException;
+import java.nio.channels.FileChannel;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Slf4j
+@Component
+public class DownFileUtil {
+
+ public static final String linuxPath = "/usr/local/src/zhst/appPackage/";//linux环境下文件下载的目录(nginx文件代理配置的目录)
+//--------------------------------------------------------
+ /**
+ * 文件下载单本地方法
+ * @param urlString 文件外网全路径
+ * @param downloadPath 下载文件本地nginx代理地址(需要保存完整链接传此参数,返回全路径;为空默认返回半路径)
+ * @param orgUrl 文件原链接
+ * @return 返回文件路径(传参数:downloadPath 返回全路径;为空默认返回半路径))
+ */
+ public static String download(String urlString,String downloadPath,String orgUrl) {
+ InputStream is = null;
+ FileOutputStream os = null;
+ try {
+ // 构造URL
+ URL url = new URL(urlString);
+ // 打开连接
+ URLConnection con = url.openConnection();
+ // 输入流
+ is = con.getInputStream();
+ // 1K的数据缓冲
+ byte[] bs = new byte[8192];
+ // 下载网络文件
+ int bytesum = 0;
+ int byteread = 0;
+ // 输出的文件流
+ String filename = "";
+ if(StringUtils.isNotBlank(downloadPath)){
+ filename = getFilename(true,urlString);
+ } else {
+ filename = getFilename(false,orgUrl);
+ }
+
+ File file = new File(XssUtils.checkTraversal(filename));
+ if(!file.getParentFile().exists()){
+ file.getParentFile().mkdirs();
+ }
+ os = new FileOutputStream(file, true);
+ while ((byteread = is.read(bs)) != -1) {
+ bytesum += byteread;
+// log.warn(bytesum);
+ os.write(bs, 0, byteread);
+ }
+ if(StringUtils.isNotBlank(downloadPath)){
+ String files = urlString.substring(urlString.lastIndexOf("/") + 1);
+ return getFilenameByServe(files,downloadPath);
+ } else {
+ return orgUrl;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ // 完毕,关闭所有链接
+ try {
+ if (null != os) {
+ os.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ try {
+ if (null != is) {
+ is.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ //从微信下载图片时如果没有id对应的图片则下载一个空图片,不会存在返回为null的情况
+ return null;
+ }
+
+ /**
+ * 文件下载单本地方法
+ * @param urlString 文件外网全路径
+ * @param downloadPath 下载文件本地nginx代理地址(需要保存完整链接传此参数,返回全路径;为空默认返回半路径)
+ * @param orgUrl 文件原链接
+ * @return 返回文件路径(传参数:downloadPath 返回全路径;为空默认返回半路径))
+ */
+ public static String downloadV2(String urlString,String downloadPath,String orgUrl) {
+ InputStream is = null;
+ FileOutputStream os = null;
+ try {
+ // 1K的数据缓冲
+ byte[] bs = new byte[1024];
+ // 下载网络文件
+ int byteread = 0;
+ // 输出的文件流
+ String filename = "";
+ if(StringUtils.isNotBlank(downloadPath)){
+ filename = getFilename(true,urlString);
+ } else {
+ filename = getFilename(false,orgUrl);
+ }
+
+ File file = new File(XssUtils.checkTraversal(filename));
+ if(!file.getParentFile().exists()){
+ file.getParentFile().mkdirs();
+ }
+ // 打开连接
+ os = new FileOutputStream(file, true);
+ // 输入流
+ is = new URL(urlString).openConnection().getInputStream();
+ while ((byteread = is.read(bs)) != -1) {
+ os.write(bs, 0, byteread);
+ }
+ if(StringUtils.isNotBlank(downloadPath)){
+ String files = urlString.substring(urlString.lastIndexOf("/") + 1);
+ return getFilenameByServe(files,downloadPath);
+ } else {
+ return orgUrl;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ // 完毕,关闭所有链接
+ try {
+ if (null != os) {
+ os.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ try {
+ if (null != is) {
+ is.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ //从微信下载图片时如果没有id对应的图片则下载一个空图片,不会存在返回为null的情况
+ return null;
+ }
+
+ /**
+ * 多线程下载网络文件到本地方法
+ * @param pathurl 文件外网全路径
+ * @param downloadPath 下载文件本地nginx代理地址(需要保存完整链接传此参数,返回全路径;为空默认返回半路径)
+ * @param orgUrl 文件原链接
+ * @return 返回文件路径(传参数:downloadPath 返回全路径;为空默认返回半路径))
+ */
+ public static String multiThreadDownload(String pathurl,String downloadPath,String orgUrl) throws IOException {
+ //是否删除分片的目录
+ boolean delete = true;
+ //获取后缀名
+ String suffix;
+ if (pathurl.contains("?")) {
+ String substring = pathurl.substring(0, pathurl.lastIndexOf('?'));
+ suffix = substring.substring(substring.lastIndexOf('.') + 1, substring.length());
+ } else {
+ suffix = pathurl.substring(pathurl.lastIndexOf('.') + 1, pathurl.length());
+ }
+ //指定文件的名字
+ String name = "";
+ log.warn("后缀名:" + suffix);
+ URL url = new URL(pathurl);
+
+ String filename = "";
+ if(StringUtils.isNotBlank(downloadPath)){//定义最终合成文件的存放路径(目录+文件名)
+ filename = DownFileUtil.getFilename(true,pathurl);
+ } else {
+ filename = DownFileUtil.getFilename(false,orgUrl);
+ }
+ //从连接获取文件
+ long start = System.currentTimeMillis();
+ String data = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
+ String path;
+ if(System.getProperty("os.name").toLowerCase().contains("win")){//定义临时分片文件存放目录,安装系统环境进行区分
+ path = FileShowUtil.getOuternet() + "\\"+start + data;
+ } else{
+ path = DownFileUtil.linuxPath + "/"+start+ data;
+ }
+
+ //多线程网络文件下载
+ spilt3(url, path);
+ merge(path, filename, delete);
+
+ //本地文件传输
+ log.warn("下载文件名:" + name + data + "." + suffix);
+ log.warn("耗时时间:" + (System.currentTimeMillis() - start)/60000+"min");
+ if(StringUtils.isNotBlank(downloadPath)){
+ String files = pathurl.substring(pathurl.lastIndexOf("/") + 1);
+ return DownFileUtil.getFilenameByServe(files,downloadPath);
+ } else {
+ return orgUrl;
+ }
+ }
+
+ public static void spilt3(URL url, String to) throws IOException {
+ HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
+ urlConnection.setRequestMethod("GET");
+ //设置允许接收消息
+ urlConnection.setDoInput(true);
+ urlConnection.connect();
+
+ File file = new File(XssUtils.checkTraversal(to));
+ if (!file.exists()) {
+ file.mkdirs();
+ }
+
+ int fileSize = urlConnection.getContentLength();//大小;
+ log.warn("文件总共大小:" + fileSize + "字节");
+ int size = 20;//默认1m
+
+ long mb100 = 1024 * 1024 * 100; //100mb
+ long gb1 = 1024 * 1024 * 1024; //1gb
+ long gb10 = 10L * 1024 * 1024 * 1024; //10gb
+
+ if (mb100 <= fileSize && gb1 > fileSize) {
+ size = 10;
+ } else if (gb1 <= fileSize && gb10 > fileSize) {
+ size = 100;
+ } else if (gb10 <= fileSize){
+ size = 1024;
+ }
+
+ // 将MB单位转为为字节B
+ int m = size * 1024 * 1024;
+ double m1 = m;
+
+ // 计算最终会分成几个文件
+ int count = (int) Math.ceil(fileSize / m1);
+ log.warn("文件分配:" + count + "块");
+
+// ExecutorService service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2 - 1);
+ ScheduledExecutorService service = new ScheduledThreadPoolExecutor(Runtime.getRuntime().availableProcessors() * 2 - 1,
+ new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());
+ try {
+ CountDownLatch countDown = new CountDownLatch(count);
+ for (int i = 0; i < count; i++) {
+ int index = i;
+ service.execute(() -> {
+ int beginPoint = index * m;
+ int endPoint;
+ if (index == (count - 1)) {
+ endPoint = fileSize;
+ } else {
+ endPoint = beginPoint + m;
+ }
+ InputStream inputStream = null;
+ FileOutputStream fileOutputStream = null;
+ try {
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ conn.setRequestMethod("GET");
+ //设置允许接收消息
+ conn.setDoInput(true);
+ conn.connect();
+ inputStream = conn.getInputStream();
+ /**inputStream = urlConnection.getInputStream(); **/
+ inputStream.skip(beginPoint);
+
+ int start = beginPoint;
+ fileOutputStream = new FileOutputStream(to + "/" + index);
+ byte[] bytes = new byte[m];
+ int len;
+ log.warn("第" + index + "分片开始下载: 起始字节数:" + beginPoint + ",结束字节数:" + endPoint);
+ while (start < endPoint) {
+ len = inputStream.read(bytes);
+ if ((start + len) >= endPoint) {
+ len = endPoint - start;
+ start = endPoint;
+ } else {
+ start += len;
+ }
+ if (len < 0) {
+ log.warn("len=" + len + ",index=" + index);
+ start = endPoint;
+ break;
+ }
+// log.warn("线程名:"+Thread.currentThread().getName()+
+// "--bytef="+bytes.length+",i="+ finalI +",len="+len);
+ fileOutputStream.write(bytes, 0, len);
+ }
+ log.warn("------------------第" + index + "分片,下载完成-------------------------");
+ countDown.countDown();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ try {
+ if(null != inputStream){
+ inputStream.close();
+ }
+ if (null != fileOutputStream) {
+ fileOutputStream.flush();
+ fileOutputStream.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+ countDown.await();
+ service.shutdown();
+ log.warn("分解成功");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void merge(String from, String to, Boolean delete) throws IOException {
+ File t = new File(to);
+ if(!t.getParentFile().exists()){
+ t.getParentFile().mkdirs();
+ }
+ FileInputStream in = null;
+ FileChannel inChannel = null;
+
+ FileOutputStream out = new FileOutputStream(t, true);
+ FileChannel outChannel = out.getChannel();
+
+ try {
+ File f = new File(from);
+ // 获取目录下的每一个文件名,再将每个文件一次写入目标文件
+ if (f.isDirectory()) {
+ List list = getAllFileAndSort(from);
+ // 记录新文件最后一个数据的位置
+ long start = 0;
+ for (File file : list) {
+
+ in = new FileInputStream(file);
+ inChannel = in.getChannel();
+
+ // 从inChannel中读取file.length()长度的数据,写入outChannel的start处
+ outChannel.transferFrom(inChannel, start, file.length());
+ start += file.length();
+ in.close();
+ inChannel.close();
+ }
+ }
+ if (delete) {
+ deleteFile(f);
+ }
+ log.warn("合成成功");
+ out.close();
+ outChannel.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ //资源未释放:流 - 质量问题 -中危
+ if(out!=null){
+ try {
+ out.close();
+ } catch (IOException e) {
+ System.out.println("关闭流异常out【org.jeecg.common.util.DownFileUtil.merge】");
+ }
+ }
+ if(in!=null){
+ try {
+ in.close();
+ } catch (IOException e) {
+ System.out.println("关闭流异常in【org.jeecg.common.util.DownFileUtil.merge】");
+ }
+ }
+ }
+ }
+
+ private static List getAllFileAndSort(String dirPath) {
+ File dirFile = new File(dirPath);
+ File[] listFiles = dirFile.listFiles();
+ List list = Arrays.asList(listFiles);
+ Collections.sort(list, (o1, o2) -> {
+ return Integer.parseInt(o1.getName()) - Integer.parseInt(o2.getName());
+ });
+ return list;
+ }
+
+
+ //删除File对象中抽象的路径方法
+ private static void deleteFile(File dir) {
+ //将file封装的路径下对象转换为数组/food/stUserFoodInfo/list
+ File[] files = dir.listFiles();
+ //判断这个数组为不为空,如果不为空,就执行内部代码
+ if (files != null) {
+ for (File file : files) {
+ //判断是否为文件
+ if (file.isFile()) {
+ //如果为文件,执行删除
+ file.delete();
+ } else {
+ //如果不为文件,就(递归)进入这个文件夹,删除文件
+ deleteFile(file);
+ }
+ }
+ //删除全部文件后删除空文件夹,最后删除自己
+ dir.delete();
+ }
+ }
+
+
+
+ /**
+ * 获取文件扩展名
+ * @return
+ */
+ public static String ext(String filename) {
+ int index = filename.lastIndexOf(".");
+
+ if (index == -1) {
+ return null;
+ }
+ String result = filename.substring(index + 1);
+ return result;
+ }
+
+
+
+ /**
+ * 获取文件名
+ * @param flag
+ * true : 从全路径截取文件名称
+ * false: 获取原目录和文件名
+ * location /{
+ * alias D:/file/;
+ * autoindex on;
+ * }
+ * @param filename
+ * @return
+ * @throws UnsupportedEncodingException
+ */
+ public static String getFilename(boolean flag,String filename) throws UnsupportedEncodingException {
+ String fileNameAll;
+ String filenames = "";
+ if(flag){//从全路径截取文件名称
+ filenames = filename.substring(filename.lastIndexOf("/") + 1);
+ } else {//获取原目录和文件名
+ filenames = filename;
+ }
+ if(System.getProperty("os.name").toLowerCase().contains("win")){
+ fileNameAll = FileShowUtil.getOuternet() + filenames;//保存到本地路径(部署时,配置nginx文件转发的映射地址)
+ } else{
+ fileNameAll = linuxPath + filenames;//保存到linux服务器路径(部署时,配置nginx文件转发的映射地址)
+ }
+ return fileNameAll;
+ }
+
+
+ /**
+ * 获取本地文件nginx转发下载地址
+ * 8888 为本地配置的nginx文件代理服务器端口
+ * @return String
+ */
+ public static String getFilenameByServe(String filename,String downloadPath) throws UnknownHostException {
+ String filenames = filename.substring(filename.lastIndexOf("/") + 1);
+ return downloadPath+filenames;
+ }
+
+
+ /**
+ * 将网络文件进行Base64位编码
+ * @param imageUrl
+ * @return
+ * @throws IOException
+ */
+ public static String getBase64(String imageUrl) throws IOException {
+ File file = null;
+ String fileName = imageUrl.substring(imageUrl.lastIndexOf("."),imageUrl.length());
+ URL urlfile;
+ InputStream inputStream = null;
+ OutputStream outputStream= null;
+ try {
+ file = File.createTempFile("face_image", fileName);
+ //下载
+ urlfile = new URL(imageUrl);
+ inputStream = urlfile.openStream();
+ outputStream= new FileOutputStream(file);
+
+ int bytesRead = 0;
+ byte[] buffer = new byte[8192];
+ while ((bytesRead = inputStream.read(buffer, 0, 8192)) != -1) {
+ outputStream.write(buffer, 0, bytesRead);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }finally {
+ try {
+ if (null != outputStream) {
+ outputStream.close();
+ }
+ if (null != inputStream) {
+ inputStream.close();
+ }
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ return getBase64ceshi(file);
+ }
+
+ public static String getBase64ceshi(File file) throws IOException {
+ String base64Str = null;
+ FileInputStream inputStream = null;
+ try {
+ Base64.Encoder encoder = Base64.getEncoder();
+ inputStream = new FileInputStream(file);
+ int available = inputStream.available();
+ byte[] bytes = new byte[available];
+ inputStream.read(bytes);
+ base64Str = encoder.encodeToString(bytes);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }finally {
+ if(null != inputStream){
+ inputStream.close();
+ }
+ }
+ return replaceEnter(base64Str);
+ }
+
+ /**
+ * 替换编码后字符串中的空格
+ * @param str
+ * @return
+ */
+ public static String replaceEnter(String str){
+ String reg ="[\n-\r]";
+ Pattern p = Pattern.compile(reg);
+ Matcher m = p.matcher(str);
+ return m.replaceAll("");
+ }
+
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/DySmsEnum.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/DySmsEnum.java
new file mode 100644
index 0000000..b06460a
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/DySmsEnum.java
@@ -0,0 +1,77 @@
+package org.jeecg.common.util;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * @Description: 短信枚举类
+ * @author: jeecg-boot
+ */
+public enum DySmsEnum {
+
+ /**登录短信模板编码*/
+ LOGIN_TEMPLATE_CODE("SMS_175435174","JEECG","code"),
+ /**忘记密码短信模板编码*/
+ FORGET_PASSWORD_TEMPLATE_CODE("SMS_175435174","JEECG","code"),
+ /**注册账号短信模板编码*/
+ REGISTER_TEMPLATE_CODE("SMS_175430166","JEECG","code"),
+ /**会议通知*/
+ MEET_NOTICE_TEMPLATE_CODE("SMS_201480469","H5活动之家","username,title,minute,time"),
+ /**我的计划通知*/
+ PLAN_NOTICE_TEMPLATE_CODE("SMS_201470515","H5活动之家","username,title,time");
+
+ /**
+ * 短信模板编码
+ */
+ private String templateCode;
+ /**
+ * 签名
+ */
+ private String signName;
+ /**
+ * 短信模板必需的数据名称,多个key以逗号分隔,此处配置作为校验
+ */
+ private String keys;
+
+ private DySmsEnum(String templateCode,String signName,String keys) {
+ this.templateCode = templateCode;
+ this.signName = signName;
+ this.keys = keys;
+ }
+
+ public String getTemplateCode() {
+ return templateCode;
+ }
+
+ public void setTemplateCode(String templateCode) {
+ this.templateCode = templateCode;
+ }
+
+ public String getSignName() {
+ return signName;
+ }
+
+ public void setSignName(String signName) {
+ this.signName = signName;
+ }
+
+ public String getKeys() {
+ return keys;
+ }
+
+ public void setKeys(String keys) {
+ this.keys = keys;
+ }
+
+ public static DySmsEnum toEnum(String templateCode) {
+ if(StringUtils.isEmpty(templateCode)){
+ return null;
+ }
+ for(DySmsEnum item : DySmsEnum.values()) {
+ if(item.getTemplateCode().equals(templateCode)) {
+ return item;
+ }
+ }
+ return null;
+ }
+}
+
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/Exceptions.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/Exceptions.java
new file mode 100644
index 0000000..d20cef5
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/Exceptions.java
@@ -0,0 +1,65 @@
+
+package org.jeecg.common.util;
+
+import cn.hutool.core.io.FastStringWriter;
+
+import java.io.PrintWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.UndeclaredThrowableException;
+
+/**
+ * 异常处理工具类
+ */
+public class Exceptions {
+
+ /**
+ * 将CheckedException转换为UncheckedException.
+ *
+ * @param e Throwable
+ * @return {RuntimeException}
+ */
+ public static RuntimeException unchecked(Throwable e) {
+ if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException
+ || e instanceof NoSuchMethodException) {
+ return new IllegalArgumentException(e);
+ } else if (e instanceof InvocationTargetException) {
+ return new RuntimeException(((InvocationTargetException) e).getTargetException());
+ } else if (e instanceof RuntimeException) {
+ return (RuntimeException) e;
+ } else {
+ return new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 代理异常解包
+ *
+ * @param wrapped 包装过得异常
+ * @return 解包后的异常
+ */
+ public static Throwable unwrap(Throwable wrapped) {
+ Throwable unwrapped = wrapped;
+ while (true) {
+ if (unwrapped instanceof InvocationTargetException) {
+ unwrapped = ((InvocationTargetException) unwrapped).getTargetException();
+ } else if (unwrapped instanceof UndeclaredThrowableException) {
+ unwrapped = ((UndeclaredThrowableException) unwrapped).getUndeclaredThrowable();
+ } else {
+ return unwrapped;
+ }
+ }
+ }
+
+ /**
+ * 将ErrorStack转化为String.
+ *
+ * @param ex Throwable
+ * @return {String}
+ */
+ public static String getStackTraceAsString(Throwable ex) {
+ FastStringWriter stringWriter = new FastStringWriter();
+ ex.printStackTrace(new PrintWriter(stringWriter));
+ return stringWriter.toString();
+ }
+
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/FileShowUtil.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/FileShowUtil.java
new file mode 100644
index 0000000..8504bb2
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/FileShowUtil.java
@@ -0,0 +1,151 @@
+package org.jeecg.common.util;
+
+import org.apache.commons.lang3.StringUtils;
+import org.jeecg.common.constant.CommonConstant;
+import org.jeecg.config.FileShowConfig;
+import org.springframework.stereotype.Component;
+
+/**
+ * 图片展示
+ */
+@Component
+public class FileShowUtil {
+
+
+ /**
+ * 内网client文件访问域名前缀(nacos修改实时刷新无需重启项目)
+ * @return
+ */
+ public static String getIntranet() {
+ FileShowConfig jeecgBaseConfig = SpringContextUtils.getBean(FileShowConfig.class);
+ return jeecgBaseConfig.intranet;
+ }
+
+ /**
+ * 内网client下载文件盘符目录(nacos修改实时刷新无需重启项目)
+ * @return
+ */
+ public static String getOuternet() {
+ FileShowConfig jeecgBaseConfig = SpringContextUtils.getBean(FileShowConfig.class);
+ return jeecgBaseConfig.outernet;
+ }
+ /**
+ * 内网client下载外网文件域名前缀(nacos修改实时刷新无需重启项目)
+ * @return
+ */
+ public static String getFinalurlOut() {
+ FileShowConfig jeecgBaseConfig = SpringContextUtils.getBean(FileShowConfig.class);
+ return jeecgBaseConfig.finalurlOut;
+ }
+
+ /**
+ * 对外接口闭餐后发送就餐统计数据(nacos修改实时刷新无需重启项目)
+ * @return
+ */
+ public static String getExternalApi() {
+ FileShowConfig jeecgBaseConfig = SpringContextUtils.getBean(FileShowConfig.class);
+ return jeecgBaseConfig.externalApi;
+ }
+
+ /**
+ * 补全图片链接
+ * @param path
+ * @return
+ */
+ public static String getDownUrl(String path) {
+
+ if (StringUtils.isEmpty(path)) {
+ return getFinalurlOut()+"default.png";
+ }
+ if(path.contains("https") || path.contains("http")){
+ if(path.contains("temp")){
+ path = path.substring(path.indexOf("temp"));
+ }else if(path.contains("down")){
+ path = path.substring(path.indexOf("down/") + "down/".length());
+ }else {
+ return path;
+ }
+ }
+ path = path.replaceAll("\\\\","/");
+ String allpath = getFinalurlOut() + path;
+ return allpath;
+ }
+
+ public static String getShowUrl(String path) {
+
+
+ if (StringUtils.isEmpty(path)) {
+ return getIntranet()+"default.png";
+ }
+ if(path.contains("https") || path.contains("http")){
+ if(path.contains("temp")){
+ path = path.substring(path.indexOf("temp"));
+ }else if(path.contains("down")){
+ path = path.substring(path.indexOf("down/") + "down/".length());
+ }else {
+ return path;
+ }
+ }
+ path = path.replaceAll("\\\\","/");
+ String allpath = getIntranet() + path;
+ return allpath;
+ }
+
+
+
+
+ public static String getShowUrlSmall(String path) {
+ if (StringUtils.isEmpty(path)) {
+ return getIntranet()+"default.png";
+ }
+ if(path.contains("http")){
+ if(path.contains("temp")){
+ path = path.substring(path.indexOf("temp"));
+ }else if(path.contains("down")){
+ path = path.substring(path.indexOf("down/") + "down/".length());
+ }else {
+ return path;
+ }
+ }
+ path = path.replaceAll("\\\\","/");
+ String allpath = getIntranet() + path;
+ String imageFileName = allpath.substring(allpath.lastIndexOf("/")+1);
+ String str1 = allpath.substring(0, allpath.lastIndexOf("/")+1);
+ return str1+ CommonConstant.THUMBNAIL_PREFIX+imageFileName;
+ }
+
+
+ public static String getShowUrlSmallByClient(String path) {
+ if (StringUtils.isEmpty(path)) {
+ return getFinalurlOut()+"default.png";
+ }
+ if(path.contains("http")){
+ if(path.contains("temp")){
+ path = path.substring(path.indexOf("temp"));
+ }else if(path.contains("down")){
+ path = path.substring(path.indexOf("down/") + "down/".length());
+ }else {
+ return path;
+ }
+ }
+ path = path.replaceAll("\\\\","/");
+ String allpath = getFinalurlOut() + path;
+ String imageFileName = allpath.substring(allpath.lastIndexOf("/")+1);
+ String str1 = allpath.substring(0, allpath.lastIndexOf("/")+1);
+ return str1+ CommonConstant.THUMBNAIL_PREFIX+imageFileName;
+ }
+
+
+ /**
+ * 截取全路径
+ * @param url
+ * @return
+ */
+ public static String clearShowUrl(String url){
+ String showUrl = getIntranet();
+ if(StringUtils.isNotBlank(url) && url.contains(showUrl)){
+ return url.replace(showUrl, "");
+ }
+ return url;
+ }
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/FillRuleUtil.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/FillRuleUtil.java
new file mode 100644
index 0000000..ae2c1ee
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/FillRuleUtil.java
@@ -0,0 +1,57 @@
+package org.jeecg.common.util;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.jeecg.common.handler.IFillRuleHandler;
+
+
+/**
+ * 规则值自动生成工具类
+ *
+ * @author qinfeng
+ * @举例: 自动生成订单号;自动生成当前日期
+ */
+@Slf4j
+public class FillRuleUtil {
+
+ /**
+ * @param ruleCode ruleCode
+ * @return
+ */
+ @SuppressWarnings("unchecked")
+ public static Object executeRule(String ruleCode, JSONObject formData) {
+ if (!StringUtils.isEmpty(ruleCode)) {
+ try {
+ // 获取 Service
+ ServiceImpl impl = (ServiceImpl) SpringContextUtils.getBean("sysFillRuleServiceImpl");
+ // 根据 ruleCode 查询出实体
+ QueryWrapper queryWrapper = new QueryWrapper();
+ queryWrapper.eq("rule_code", ruleCode);
+ JSONObject entity = JSON.parseObject(JSON.toJSONString(impl.getOne(queryWrapper)));
+ if (entity == null) {
+ log.warn("填值规则:" + ruleCode + " 不存在");
+ return null;
+ }
+ // 获取必要的参数
+ String ruleClass = entity.getString("ruleClass");
+ JSONObject params = entity.getJSONObject("ruleParams");
+ if (params == null) {
+ params = new JSONObject();
+ }
+ if (formData == null) {
+ formData = new JSONObject();
+ }
+ // 通过反射执行配置的类里的方法
+ IFillRuleHandler ruleHandler = (IFillRuleHandler) Class.forName(ruleClass).newInstance();
+ return ruleHandler.execute(params, formData);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ return null;
+ }
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/HTMLUtils.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/HTMLUtils.java
new file mode 100644
index 0000000..e84d6f3
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/HTMLUtils.java
@@ -0,0 +1,32 @@
+package org.jeecg.common.util;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.web.util.HtmlUtils;
+
+/**
+ * HTML 工具类
+ * @author: jeecg-boot
+ * @date: 2022/3/30 14:43
+ */
+@SuppressWarnings("AlibabaClassNamingShouldBeCamel")
+public class HTMLUtils {
+
+ /**
+ * 获取HTML内的文本,不包含标签
+ *
+ * @param html HTML 代码
+ */
+ public static String getInnerText(String html) {
+ if (StringUtils.isNotBlank(html)) {
+ //去掉 html 的标签
+ String content = html.replaceAll("?[^>]+>", "");
+ // 将多个空格合并成一个空格
+ content = content.replaceAll("( )+", " ");
+ // 反向转义字符
+ content = HtmlUtils.htmlUnescape(content);
+ return content.trim();
+ }
+ return "";
+ }
+
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/ImportExcelUtil.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/ImportExcelUtil.java
new file mode 100644
index 0000000..ead7ee6
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/ImportExcelUtil.java
@@ -0,0 +1,97 @@
+package org.jeecg.common.util;
+
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.extension.service.IService;
+import lombok.extern.slf4j.Slf4j;
+import org.jeecg.common.api.vo.Result;
+import org.jeecg.common.constant.CommonConstant;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * 导出返回信息
+ * @author: jeecg-boot
+ */
+@Slf4j
+public class ImportExcelUtil {
+
+ public static Result> imporReturnRes(int errorLines,int successLines,List errorMessage) throws IOException {
+ if (errorLines == 0) {
+ return Result.ok("共" + successLines + "行数据全部导入成功!");
+ } else {
+ JSONObject result = new JSONObject(5);
+ int totalCount = successLines + errorLines;
+ result.put("totalCount", totalCount);
+ result.put("errorCount", errorLines);
+ result.put("successCount", successLines);
+ result.put("msg", "总上传行数:" + totalCount + ",已导入行数:" + successLines + ",错误行数:" + errorLines);
+ String fileUrl = PmsUtil.saveErrorTxtByList(errorMessage, "userImportExcelErrorLog");
+ int lastIndex = fileUrl.lastIndexOf(File.separator);
+ String fileName = fileUrl.substring(lastIndex + 1);
+ result.put("fileUrl", "/sys/common/static/" + fileUrl);
+ result.put("fileName", fileName);
+ Result res = Result.ok(result);
+ res.setCode(201);
+ res.setMessage("文件导入成功,但有错误。");
+ return res;
+ }
+ }
+
+ public static List importDateSave(List> list, Class serviceClass, List errorMessage, String errorFlag) {
+ IService bean =(IService) SpringContextUtils.getBean(serviceClass);
+ for (int i = 0; i < list.size(); i++) {
+ try {
+ boolean save = bean.save(list.get(i));
+ if(!save){
+ throw new Exception(errorFlag);
+ }
+ } catch (Exception e) {
+ String message = e.getMessage().toLowerCase();
+ int lineNumber = i + 1;
+ // 通过索引名判断出错信息
+ if (message.contains(CommonConstant.SQL_INDEX_UNIQ_SYS_ROLE_CODE)) {
+ errorMessage.add("第 " + lineNumber + " 行:角色编码已经存在,忽略导入。");
+ } else if (message.contains(CommonConstant.SQL_INDEX_UNIQ_JOB_CLASS_NAME)) {
+ errorMessage.add("第 " + lineNumber + " 行:任务类名已经存在,忽略导入。");
+ }else if (message.contains(CommonConstant.SQL_INDEX_UNIQ_CODE)) {
+ errorMessage.add("第 " + lineNumber + " 行:职务编码已经存在,忽略导入。");
+ }else if (message.contains(CommonConstant.SQL_INDEX_UNIQ_DEPART_ORG_CODE)) {
+ errorMessage.add("第 " + lineNumber + " 行:部门编码已经存在,忽略导入。");
+ }else {
+ errorMessage.add("第 " + lineNumber + " 行:未知错误,忽略导入");
+ log.error(e.getMessage(), e);
+ }
+ }
+ }
+ return errorMessage;
+ }
+
+ public static List importDateSaveOne(Object obj, Class serviceClass,List errorMessage,int i,String errorFlag) {
+ IService bean =(IService) SpringContextUtils.getBean(serviceClass);
+ try {
+ boolean save = bean.save(obj);
+ if(!save){
+ throw new Exception(errorFlag);
+ }
+ } catch (Exception e) {
+ String message = e.getMessage().toLowerCase();
+ int lineNumber = i + 1;
+ // 通过索引名判断出错信息
+ if (message.contains(CommonConstant.SQL_INDEX_UNIQ_SYS_ROLE_CODE)) {
+ errorMessage.add("第 " + lineNumber + " 行:角色编码已经存在,忽略导入。");
+ } else if (message.contains(CommonConstant.SQL_INDEX_UNIQ_JOB_CLASS_NAME)) {
+ errorMessage.add("第 " + lineNumber + " 行:任务类名已经存在,忽略导入。");
+ }else if (message.contains(CommonConstant.SQL_INDEX_UNIQ_CODE)) {
+ errorMessage.add("第 " + lineNumber + " 行:职务编码已经存在,忽略导入。");
+ }else if (message.contains(CommonConstant.SQL_INDEX_UNIQ_DEPART_ORG_CODE)) {
+ errorMessage.add("第 " + lineNumber + " 行:部门编码已经存在,忽略导入。");
+ }else {
+ errorMessage.add("第 " + lineNumber + " 行:未知错误,忽略导入");
+ log.error(e.getMessage(), e);
+ }
+ }
+ return errorMessage;
+ }
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/IpUtils.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/IpUtils.java
new file mode 100644
index 0000000..9954cf7
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/IpUtils.java
@@ -0,0 +1,61 @@
+package org.jeecg.common.util;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * IP地址
+ *
+ * @Author scott
+ * @email jeecgos@163.com
+ * @Date 2019年01月14日
+ */
+public class IPUtils {
+ private static Logger logger = LoggerFactory.getLogger(IPUtils.class);
+
+ /**
+ * 获取IP地址
+ *
+ * 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址
+ * 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址
+ */
+ public static String getIpAddr(HttpServletRequest request) {
+ String ip = null;
+ try {
+ ip = request.getHeader("x-forwarded-for");
+ if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
+ ip = request.getHeader("X-Client-IP");
+ }
+ if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
+ ip = request.getHeader("Proxy-Client-IP");
+ }
+ if (StringUtils.isEmpty(ip) || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+ ip = request.getHeader("WL-Proxy-Client-IP");
+ }
+ if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
+ ip = request.getHeader("HTTP_CLIENT_IP");
+ }
+ if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
+ ip = request.getHeader("HTTP_X_FORWARDED_FOR");
+ }
+ if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
+ ip = request.getRemoteAddr();
+ }
+ } catch (Exception e) {
+ logger.error("IPUtils ERROR ", e);
+ }
+
+// //使用代理,则获取第一个IP地址
+// if(StringUtils.isEmpty(ip) && ip.length() > 15) {
+// if(ip.indexOf(",") > 0) {
+// ip = ip.substring(0, ip.indexOf(","));
+// }
+// }
+
+ return ip;
+ }
+
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/LocalUploadUtil.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/LocalUploadUtil.java
new file mode 100644
index 0000000..b77fef0
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/LocalUploadUtil.java
@@ -0,0 +1,78 @@
+package org.jeecg.common.util;
+
+import cn.hutool.core.io.IoUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.FilenameUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.util.FileCopyUtils;
+
+import java.io.*;
+
+/**
+ * 本地文件上传工具类
+ */
+@Slf4j
+public class LocalUploadUtil {
+
+
+ private static String uploadpath;
+ /**
+ * 文件上传根目录 设置(这里设置的路径要和file-server.application-dev.yml的file.path保持一致)
+ * @return
+ */
+ public static String getUploadpath() {
+ return uploadpath;
+ }
+
+ public static void setUploadpath(String uploadpath) {
+ LocalUploadUtil.uploadpath = uploadpath;
+ }
+
+ /**
+ * 本地文件上传
+ * @param mf 文件
+ * @param bizPath 自定义路径
+ * @return
+ */
+ public static String uploadLocal(InputStream mf,String bizPath,String orgName){
+ try {
+ String ctxPath = uploadpath;
+ String fileName = null;
+ String paths = ctxPath + File.separator + bizPath + File.separator;
+ String pathNew= FilenameUtils.getFullPath(paths);// 输出: C:\Users\Example\Documents\
+ System.out.println(pathNew);
+ File file = new File(XssUtils.checkTraversal(pathNew));
+ if (!file.exists()) {
+ file.mkdirs();// 创建文件根目录
+ }
+ orgName = CommonUtils.getFileName(orgName);
+ if(orgName.indexOf(".")!=-1){
+ fileName = orgName.substring(0, orgName.lastIndexOf(".")) + "_" + System.currentTimeMillis() + orgName.substring(orgName.lastIndexOf("."));
+ }else{
+ fileName = orgName+ "_" + System.currentTimeMillis();
+ }
+ //解决路径遍历漏洞-路径:高危
+// String savePath = file.getPath() + File.separator + fileName;
+ fileName = FilenameUtils.getName(fileName);//// 输出: file.txt
+ String normalized = pathNew.replace("\\", "/");
+ String path = XssUtils.checkTraversal(normalized);
+ String savePath = path + fileName;
+ File savefile = new File(savePath);
+ FileCopyUtils.copy(IoUtil.readBytes(mf), savefile);
+ String dbpath = null;
+ if(oConvertUtils.isNotEmpty(bizPath)){
+ dbpath = bizPath + File.separator + fileName;
+ }else{
+ dbpath = fileName;
+ }
+ if (dbpath.contains("\\")) {
+ dbpath = dbpath.replace("\\", "/");
+ }
+ return dbpath;
+ } catch (IOException e) {
+ log.error(e.getMessage(), e);
+ }
+ return "";
+ }
+
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/Md5Util.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/Md5Util.java
new file mode 100644
index 0000000..cf70fa9
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/Md5Util.java
@@ -0,0 +1,43 @@
+package org.jeecg.common.util;
+
+import java.security.MessageDigest;
+
+public class MD5Util {
+
+ public static String byteArrayToHexString(byte b[]) {
+ StringBuffer resultSb = new StringBuffer();
+ for (int i = 0; i < b.length; i++){
+ resultSb.append(byteToHexString(b[i]));
+ }
+ return resultSb.toString();
+ }
+
+ private static String byteToHexString(byte b) {
+ int n = b;
+ if (n < 0) {
+ n += 256;
+ }
+ int d1 = n / 16;
+ int d2 = n % 16;
+ return hexDigits[d1] + hexDigits[d2];
+ }
+
+ public static String MD5Encode(String origin, String charsetname) {
+ String resultString = null;
+ try {
+ resultString = new String(origin);
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ if (charsetname == null || "".equals(charsetname)) {
+ resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
+ } else {
+ resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname)));
+ }
+ } catch (Exception exception) {
+ }
+ return resultString;
+ }
+
+ private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
+ "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
+
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/MinioUtil.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/MinioUtil.java
new file mode 100644
index 0000000..3a36cbe
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/MinioUtil.java
@@ -0,0 +1,222 @@
+package org.jeecg.common.util;
+
+import io.minio.*;
+import io.minio.http.Method;
+import lombok.extern.slf4j.Slf4j;
+import org.jeecg.common.constant.SymbolConstant;
+import org.jeecg.common.util.filter.FileTypeFilter;
+import org.jeecg.common.util.filter.StrAttackFilter;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.InputStream;
+import java.net.URLDecoder;
+
+/**
+ * minio文件上传工具类
+ * @author: jeecg-boot
+ */
+@Slf4j
+public class MinioUtil {
+ private static String minioUrl;
+ private static String minioName;
+ private static String minioPass;
+ private static String bucketName;
+
+ public static void setMinioUrl(String minioUrl) {
+ MinioUtil.minioUrl = minioUrl;
+ }
+
+ public static void setMinioName(String minioName) {
+ MinioUtil.minioName = minioName;
+ }
+
+ public static void setMinioPass(String minioPass) {
+ MinioUtil.minioPass = minioPass;
+ }
+
+ public static void setBucketName(String bucketName) {
+ MinioUtil.bucketName = bucketName;
+ }
+
+ public static String getMinioUrl() {
+ return minioUrl;
+ }
+
+ public static String getBucketName() {
+ return bucketName;
+ }
+
+ private static MinioClient minioClient = null;
+
+ /**
+ * 上传文件
+ * @param file
+ * @return
+ */
+ public static String upload(MultipartFile file, String bizPath, String customBucket) {
+ String fileUrl = "";
+ //update-begin-author:wangshuai date:20201012 for: 过滤上传文件夹名特殊字符,防止攻击
+ bizPath=StrAttackFilter.filter(bizPath);
+ //update-end-author:wangshuai date:20201012 for: 过滤上传文件夹名特殊字符,防止攻击
+ String newBucket = bucketName;
+ if(oConvertUtils.isNotEmpty(customBucket)){
+ newBucket = customBucket;
+ }
+ try {
+ initMinio(minioUrl, minioName,minioPass);
+ // 检查存储桶是否已经存在
+ if(minioClient.bucketExists(BucketExistsArgs.builder().bucket(newBucket).build())) {
+ log.info("Bucket already exists.");
+ } else {
+ // 创建一个名为ota的存储桶
+ minioClient.makeBucket(MakeBucketArgs.builder().bucket(newBucket).build());
+ log.info("create a new bucket.");
+ }
+ //update-begin-author:liusq date:20210809 for: 过滤上传文件类型
+ FileTypeFilter.fileTypeFilter(file);
+ //update-end-author:liusq date:20210809 for: 过滤上传文件类型
+ InputStream stream = file.getInputStream();
+ // 获取文件名
+ String orgName = file.getOriginalFilename();
+ if("".equals(orgName)){
+ orgName=file.getName();
+ }
+ orgName = CommonUtils.getFileName(orgName);
+ String objectName = bizPath+"/"
+ +( orgName.indexOf(".")==-1
+ ?orgName + "_" + System.currentTimeMillis()
+ :orgName.substring(0, orgName.lastIndexOf(".")) + "_" + System.currentTimeMillis() + orgName.substring(orgName.lastIndexOf("."))
+ );
+
+ // 使用putObject上传一个本地文件到存储桶中。
+ if(objectName.startsWith(SymbolConstant.SINGLE_SLASH)){
+ objectName = objectName.substring(1);
+ }
+ PutObjectArgs objectArgs = PutObjectArgs.builder().object(objectName)
+ .bucket(newBucket)
+ .contentType("application/octet-stream")
+ .stream(stream,stream.available(),-1).build();
+ minioClient.putObject(objectArgs);
+ stream.close();
+ fileUrl = minioUrl+newBucket+"/"+objectName;
+ }catch (Exception e){
+ log.error(e.getMessage(), e);
+ }
+ return fileUrl;
+ }
+
+ /**
+ * 文件上传
+ * @param file
+ * @param bizPath
+ * @return
+ */
+ public static String upload(MultipartFile file, String bizPath) {
+ return upload(file,bizPath,null);
+ }
+
+ /**
+ * 获取文件流
+ * @param bucketName
+ * @param objectName
+ * @return
+ */
+ public static InputStream getMinioFile(String bucketName,String objectName){
+ InputStream inputStream = null;
+ try {
+ initMinio(minioUrl, minioName, minioPass);
+ GetObjectArgs objectArgs = GetObjectArgs.builder().object(objectName)
+ .bucket(bucketName).build();
+ inputStream = minioClient.getObject(objectArgs);
+ } catch (Exception e) {
+ log.info("文件获取失败" + e.getMessage());
+ }
+ return inputStream;
+ }
+
+ /**
+ * 删除文件
+ * @param bucketName
+ * @param objectName
+ * @throws Exception
+ */
+ public static void removeObject(String bucketName, String objectName) {
+ try {
+ initMinio(minioUrl, minioName,minioPass);
+ RemoveObjectArgs objectArgs = RemoveObjectArgs.builder().object(objectName)
+ .bucket(bucketName).build();
+ minioClient.removeObject(objectArgs);
+ }catch (Exception e){
+ log.info("文件删除失败" + e.getMessage());
+ }
+ }
+
+ /**
+ * 获取文件外链
+ * @param bucketName
+ * @param objectName
+ * @param expires
+ * @return
+ */
+ public static String getObjectUrl(String bucketName, String objectName, Integer expires) {
+ initMinio(minioUrl, minioName,minioPass);
+ try{
+ //update-begin---author:liusq Date:20220121 for:获取文件外链报错提示method不能为空,导致文件下载和预览失败----
+ GetPresignedObjectUrlArgs objectArgs = GetPresignedObjectUrlArgs.builder().object(objectName)
+ .bucket(bucketName)
+ .expiry(expires).method(Method.GET).build();
+ //update-begin---author:liusq Date:20220121 for:获取文件外链报错提示method不能为空,导致文件下载和预览失败----
+ String url = minioClient.getPresignedObjectUrl(objectArgs);
+ return URLDecoder.decode(url,"UTF-8");
+ }catch (Exception e){
+ log.info("文件路径获取失败" + e.getMessage());
+ }
+ return null;
+ }
+
+ /**
+ * 初始化客户端
+ * @param minioUrl
+ * @param minioName
+ * @param minioPass
+ * @return
+ */
+ private static MinioClient initMinio(String minioUrl, String minioName,String minioPass) {
+ if (minioClient == null) {
+ try {
+ minioClient = MinioClient.builder()
+ .endpoint(minioUrl)
+ .credentials(minioName, minioPass)
+ .build();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ return minioClient;
+ }
+
+ /**
+ * 上传文件到minio
+ * @param stream
+ * @param relativePath
+ * @return
+ */
+ public static String upload(InputStream stream,String relativePath) throws Exception {
+ initMinio(minioUrl, minioName,minioPass);
+ if(minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
+ log.info("Bucket already exists.");
+ } else {
+ // 创建一个名为ota的存储桶
+ minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
+ log.info("create a new bucket.");
+ }
+ PutObjectArgs objectArgs = PutObjectArgs.builder().object(relativePath)
+ .bucket(bucketName)
+ .contentType("application/octet-stream")
+ .stream(stream,stream.available(),-1).build();
+ minioClient.putObject(objectArgs);
+ stream.close();
+ return minioUrl+bucketName+"/"+relativePath;
+ }
+
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/MyClassLoader.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/MyClassLoader.java
new file mode 100644
index 0000000..20289b6
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/MyClassLoader.java
@@ -0,0 +1,105 @@
+package org.jeecg.common.util;
+
+import org.jeecg.common.constant.SymbolConstant;
+
+/**
+ * @Author 张代浩
+ */
+public class MyClassLoader extends ClassLoader {
+ public static Class getClassByScn(String className) {
+ Class myclass = null;
+ try {
+ myclass = Class.forName(className);
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ throw new RuntimeException(className+" not found!");
+ }
+ return myclass;
+ }
+
+ /**
+ * 获得类的全名,包括包名
+ * @param object
+ * @return
+ */
+ public static String getPackPath(Object object) {
+ // 检查用户传入的参数是否为空
+ if (object == null) {
+ throw new java.lang.IllegalArgumentException("参数不能为空!");
+ }
+ // 获得类的全名,包括包名
+ String clsName = object.getClass().getName();
+ return clsName;
+ }
+
+ public static String getAppPath(Class cls) {
+ // 检查用户传入的参数是否为空
+ if (cls == null) {
+ throw new java.lang.IllegalArgumentException("参数不能为空!");
+ }
+ ClassLoader loader = cls.getClassLoader();
+ // 获得类的全名,包括包名
+ String clsName = cls.getName() + ".class";
+ // 获得传入参数所在的包
+ Package pack = cls.getPackage();
+ String path = "";
+ // 如果不是匿名包,将包名转化为路径
+ if (pack != null) {
+ String packName = pack.getName();
+ String javaSpot="java.";
+ String javaxSpot="javax.";
+ // 此处简单判定是否是Java基础类库,防止用户传入JDK内置的类库
+ if (packName.startsWith(javaSpot) || packName.startsWith(javaxSpot)) {
+ throw new java.lang.IllegalArgumentException("不要传送系统类!");
+ }
+ // 在类的名称中,去掉包名的部分,获得类的文件名
+ clsName = clsName.substring(packName.length() + 1);
+ // 判定包名是否是简单包名,如果是,则直接将包名转换为路径,
+ if (packName.indexOf(SymbolConstant.SPOT) < 0) {
+ path = packName + "/";
+ } else {
+ // 否则按照包名的组成部分,将包名转换为路径
+ int start = 0, end = 0;
+ end = packName.indexOf(".");
+ StringBuilder pathBuilder = new StringBuilder();
+ while (end != -1) {
+ pathBuilder.append(packName, start, end).append("/");
+ start = end + 1;
+ end = packName.indexOf(".", start);
+ }
+ if(oConvertUtils.isNotEmpty(pathBuilder.toString())){
+ path = pathBuilder.toString();
+ }
+ path = path + packName.substring(start) + "/";
+ }
+ }
+ // 调用ClassLoader的getResource方法,传入包含路径信息的类文件名
+ java.net.URL url = loader.getResource(path + clsName);
+ // 从URL对象中获取路径信息
+ String realPath = url.getPath();
+ // 去掉路径信息中的协议名"file:"
+ int pos = realPath.indexOf("file:");
+ if (pos > -1) {
+ realPath = realPath.substring(pos + 5);
+ }
+ // 去掉路径信息最后包含类文件信息的部分,得到类所在的路径
+ pos = realPath.indexOf(path + clsName);
+ realPath = realPath.substring(0, pos - 1);
+ // 如果类文件被打包到JAR等文件中时,去掉对应的JAR等打包文件名
+ if (realPath.endsWith(SymbolConstant.EXCLAMATORY_MARK)) {
+ realPath = realPath.substring(0, realPath.lastIndexOf("/"));
+ }
+ /*------------------------------------------------------------
+ ClassLoader的getResource方法使用了utf-8对路径信息进行了编码,当路径
+ 中存在中文和空格时,他会对这些字符进行转换,这样,得到的往往不是我们想要
+ 的真实路径,在此,调用了URLDecoder的decode方法进行解码,以便得到原始的
+ 中文及空格路径
+ -------------------------------------------------------------*/
+ try {
+ realPath = java.net.URLDecoder.decode(realPath, "utf-8");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ return realPath;
+ }// getAppPath定义结束
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/MyUploadUtil.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/MyUploadUtil.java
new file mode 100644
index 0000000..d9b5333
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/MyUploadUtil.java
@@ -0,0 +1,542 @@
+package org.jeecg.common.util;
+
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.lang.UUID;
+import lombok.extern.slf4j.Slf4j;
+import net.coobird.thumbnailator.Thumbnails;
+import org.apache.http.Consts;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.mime.HttpMultipartMode;
+import org.apache.http.entity.mime.MultipartEntityBuilder;
+import org.apache.http.entity.mime.content.StringBody;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.protocol.HTTP;
+import org.jeecg.common.constant.CommonConstant;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * minio文件上传工具类
+ */
+@Slf4j
+public class MyUploadUtil {
+ private static String muUrl;
+ private static String muToken;
+ private static String muFileExistPath;
+ private static String muFileLengthPath;
+ private static String muFileUploadPath;
+ private static String muFileListUploadPath;
+ private static String muDownPath;
+
+ private static int timeout = 3000;
+
+ public static String getMuUrl() {
+ return muUrl;
+ }
+
+ public static void setMuUrl(String muUrl) {
+ MyUploadUtil.muUrl = muUrl;
+ }
+
+ public static String getMuToken() {
+ return muToken;
+ }
+
+ public static void setMuToken(String muToken) {
+ MyUploadUtil.muToken = muToken;
+ }
+
+ public String getMuFileExistPath() {
+ return muFileExistPath;
+ }
+
+ public static void setMuFileExistPath(String muFileExistPath) {
+ MyUploadUtil.muFileExistPath = muFileExistPath;
+ }
+
+ public static String getMuFileLengthPath() {
+ return muFileLengthPath;
+ }
+
+ public static void setMuFileLengthPath(String muFileLengthPath) {
+ MyUploadUtil.muFileLengthPath = muFileLengthPath;
+ }
+
+ public static String getMuFileUploadPath() {
+ return muFileUploadPath;
+ }
+
+ public static void setMuFileUploadPath(String muFileUploadPath) {
+ MyUploadUtil.muFileUploadPath = muFileUploadPath;
+ }
+
+ public static String getMuFileListUploadPath() {
+ return muFileListUploadPath;
+ }
+
+ public static void setMuFileListUploadPath(String muFileListUploadPath) {
+ MyUploadUtil.muFileListUploadPath = muFileListUploadPath;
+ }
+
+ public static String getMuDownPath() {
+ return muDownPath;
+ }
+
+ public static void setMuDownPath(String muDownPath) {
+ MyUploadUtil.muDownPath = muDownPath;
+ }
+
+ /**
+ * 上传文件
+ *
+ * @param file
+ * @return
+ */
+ public static String upload(MultipartFile file, String bizPath) {
+ String fileUrl = "";
+ String fileName = file.getOriginalFilename();// 获取文件名
+ try {
+ fileUrl = upload(file.getInputStream(), bizPath, fileName);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return fileUrl;
+ }
+
+ /**
+ * 上传文件 带缩略图
+ *
+ * @param file
+ * @return
+ */
+ public static String upload(MultipartFile file, String bizPath, Boolean needThumbnailFlag) {
+ String fileUrl = "";
+ String fileName = file.getOriginalFilename();// 获取文件名
+ try {
+ fileUrl = upload(file.getInputStream(), bizPath, fileName, needThumbnailFlag);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return fileUrl;
+ }
+
+ /**
+ * 上传文件到myupload
+ *
+ * @param stream
+ * @return
+ */
+ public static String upload(InputStream stream, String bizPath, String fileName) throws Exception {
+ //避免重复文件覆盖,这里处理一下
+ if (fileName.indexOf(".") > -1) {
+ String fileNameLeft = fileName.substring(0, fileName.lastIndexOf("."));
+ fileName = fileNameLeft + "_" + System.currentTimeMillis() + fileName.substring(fileName.lastIndexOf("."));
+ } else {
+ fileName += "_" + System.currentTimeMillis();
+ }
+ String nowday = new SimpleDateFormat("yyyyMMdd").format(new Date());
+ String path = bizPath + File.separator + nowday;
+ String url = muUrl + "/" + muFileUploadPath;
+ String fileUrl = "";
+ //创建post方法连接实例,在post方法中传入待连接地址
+ CloseableHttpClient httpClient = HttpClients.createDefault();
+ CloseableHttpResponse httpResponse = null;
+ try {
+ HttpPost httppost = new HttpPost(url);
+ RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(timeout).setConnectTimeout(timeout).build();
+ httppost.setConfig(requestConfig);
+ httppost.addHeader("token", muToken);
+ MultipartEntityBuilder builder = MultipartEntityBuilder.create();
+ //设置浏览器兼容模式
+ builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
+ //设置请求的编码格式
+ builder.setCharset(Consts.UTF_8);
+ builder.addBinaryBody("file", stream, ContentType.APPLICATION_OCTET_STREAM, fileName);
+ ContentType contentType = ContentType.create(HTTP.PLAIN_TEXT_TYPE, HTTP.UTF_8);
+ StringBody stringBody = new StringBody(fileName, contentType);
+ builder.addPart("fileName", stringBody);
+ stringBody = new StringBody(path, contentType);
+ builder.addPart("path", stringBody);
+// builder.addTextBody("fileName", fileName);
+// builder.addTextBody("path", path);
+ HttpEntity reqEntity = builder.build();
+ httppost.setEntity(reqEntity);
+
+ httpResponse = httpClient.execute(httppost);
+ int backCode = httpResponse.getStatusLine().getStatusCode();
+ if (backCode != HttpStatus.SC_OK) {
+ log.error("上传文件失败");
+ } else {
+ fileUrl = path + File.separator + fileName;
+ }
+ } catch (IOException e) {
+ log.error("文件服务器连接失败");
+ } finally {
+ //释放资源
+ try {
+ httpClient.close();
+ if (httpResponse != null) {
+ httpResponse.close();
+ }
+ } catch (IOException e) {
+// e.printStackTrace();
+ }
+ }
+ return fileUrl;
+ }
+
+ /**
+ * 上传文件到myupload
+ *
+ * @param stream
+ * @return
+ */
+ public static String upload(InputStream stream, String bizPath, String fileName, Boolean needThumbnailFlag) throws Exception {
+ //避免重复文件覆盖,这里处理一下
+ if (fileName.indexOf(".") > -1) {
+ //String fileNameLeft = fileName.substring(0, fileName.lastIndexOf("."));
+ String fileNameLeft = UUID.randomUUID().toString();
+ fileName = fileNameLeft + "_" + System.currentTimeMillis() + fileName.substring(fileName.lastIndexOf("."));
+ } else {
+ fileName += "_" + System.currentTimeMillis();
+ }
+ String nowday = new SimpleDateFormat("yyyyMMdd").format(new Date());
+ String path = bizPath + File.separator + nowday;
+ String url = muUrl + "/" + muFileUploadPath;
+ String fileUrl = "";
+ //创建post方法连接实例,在post方法中传入待连接地址
+ CloseableHttpClient httpClient = HttpClients.createDefault();
+ CloseableHttpResponse httpResponse = null;
+
+ byte[] bytes = IoUtil.readBytes(stream);
+
+ try {
+ HttpPost httppost = new HttpPost(url);
+ RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(timeout).setConnectTimeout(timeout).build();
+ httppost.setConfig(requestConfig);
+ httppost.addHeader("token", muToken);
+ MultipartEntityBuilder builder = MultipartEntityBuilder.create();
+ //设置浏览器兼容模式
+ builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
+ //设置请求的编码格式
+ builder.setCharset(Consts.UTF_8);
+ builder.addBinaryBody("file", new ByteArrayInputStream(bytes), ContentType.APPLICATION_OCTET_STREAM, fileName);
+
+ ContentType contentType = ContentType.create(HTTP.PLAIN_TEXT_TYPE, HTTP.UTF_8);
+ StringBody stringBody = new StringBody(fileName, contentType);
+ builder.addPart("fileName", stringBody);
+ stringBody = new StringBody(path, contentType);
+ builder.addPart("path", stringBody);
+// builder.addTextBody("fileName", fileName);
+// builder.addTextBody("path", path);
+ HttpEntity reqEntity = builder.build();
+ httppost.setEntity(reqEntity);
+
+ httpResponse = httpClient.execute(httppost);
+
+
+ int backCode = httpResponse.getStatusLine().getStatusCode();
+ if (backCode != HttpStatus.SC_OK) {
+ log.error("上传文件失败");
+ } else {
+ fileUrl = path + File.separator + fileName;
+ }
+ } catch (IOException e) {
+ log.error("文件服务器连接失败");
+ } finally {
+ //释放资源
+ try {
+ httpClient.close();
+ if (httpResponse != null) {
+ httpResponse.close();
+ }
+ } catch (IOException e) {
+// e.printStackTrace();
+ }
+ }
+
+// if (needThumbnailFlag) {
+// uploadThumbnail(url, bytes, fileName, path);
+// }
+
+ return fileUrl;
+ }
+
+
+ /**
+ * 上传缩略图到myupload
+ *
+ * @return
+ */
+// private static void uploadThumbnail(String url, byte[] bytes, String fileName, String path) {
+// //创建post方法连接实例,在post方法中传入待连接地址
+// CloseableHttpClient httpClient = HttpClients.createDefault();
+// CloseableHttpResponse httpResponse = null;
+// try {
+// String thumbnailName = CommonConstant.THUMBNAIL_PREFIX + fileName;
+//
+// BufferedImage bufferedImage = Thumbnails.of(new ByteArrayInputStream(bytes))
+// .scale(CommonConstant.THUMBNAIL_SCALE)
+// .asBufferedImage();
+//
+// ByteArrayOutputStream os = new ByteArrayOutputStream();
+// ImageIO.write(bufferedImage, "png", os);
+// InputStream is = new ByteArrayInputStream(os.toByteArray());
+//
+// HttpPost httppost = new HttpPost(url);
+// RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(timeout).setConnectTimeout(timeout).build();
+// httppost.setConfig(requestConfig);
+// httppost.addHeader("token", muToken);
+// MultipartEntityBuilder builder = MultipartEntityBuilder.create();
+// //设置浏览器兼容模式
+// builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
+// //设置请求的编码格式
+// builder.setCharset(Consts.UTF_8);
+//
+// builder.addBinaryBody("file", is, ContentType.APPLICATION_OCTET_STREAM, thumbnailName);
+//
+// ContentType contentType = ContentType.create(HTTP.PLAIN_TEXT_TYPE, HTTP.UTF_8);
+// StringBody stringBody = new StringBody(thumbnailName, contentType);
+// builder.addPart("fileName", stringBody);
+// stringBody = new StringBody(path, contentType);
+// builder.addPart("path", stringBody);
+// HttpEntity reqEntity = builder.build();
+// httppost.setEntity(reqEntity);
+//
+// httpResponse = httpClient.execute(httppost);
+//
+// int backCode = httpResponse.getStatusLine().getStatusCode();
+// if (backCode != HttpStatus.SC_OK) {
+// log.error("上传缩略图失败");
+// }
+// } catch (IOException e) {
+// log.error("文件服务器连接失败");
+// } finally {
+// //释放资源
+// try {
+// httpClient.close();
+// if (httpResponse != null) {
+// httpResponse.close();
+// }
+// } catch (IOException e) {
+// }
+// }
+// }
+
+ /**
+ * 上传文件 固定目录 带缩略图(同名覆盖)
+ *
+ * @param file
+ * @return
+ */
+ public static String uploadFixedDirectory(MultipartFile file, String bizPath, Boolean needThumbnailFlag) {
+ String fileUrl = "";
+ String fileName = file.getOriginalFilename();// 获取文件名
+ try {
+ fileUrl = fixedDirectory(file.getInputStream(), bizPath, fileName, needThumbnailFlag);
+ file.getInputStream().close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }finally{
+ //资源未释放:流 - 质量问题 -中危
+ if(file!=null){
+ try {
+ file.getInputStream().close();
+ } catch (IOException e) {
+ System.out.println("关闭流异常【org.jeecg.common.util.MyUploadUtil.upload(org.springframework.web.multipart.MultipartFile, java.lang.String, java.lang.Boolean)】");
+ }
+ }
+ }
+ return fileUrl;
+ }
+
+ /**
+ * 上传文件到myupload
+ *
+ * @param stream
+ * @return
+ */
+ public static String fixedDirectory(InputStream stream, String bizPath, String fileName, Boolean needThumbnailFlag) throws Exception {
+ String path = bizPath;
+ String url = muUrl + "/" + muFileUploadPath;
+ String fileUrl = "";
+ //创建post方法连接实例,在post方法中传入待连接地址
+ CloseableHttpClient httpClient = HttpClients.createDefault();
+ CloseableHttpResponse httpResponse = null;
+
+ byte[] bytes = IoUtil.readBytes(stream);
+
+ try {
+ HttpPost httppost = new HttpPost(url);
+ RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(timeout).setConnectTimeout(timeout).build();
+ httppost.setConfig(requestConfig);
+ httppost.addHeader("token", muToken);
+ MultipartEntityBuilder builder = MultipartEntityBuilder.create();
+ //设置浏览器兼容模式
+ builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
+ //设置请求的编码格式
+ builder.setCharset(Consts.UTF_8);
+ builder.addBinaryBody("file", new ByteArrayInputStream(bytes), ContentType.APPLICATION_OCTET_STREAM, fileName);
+
+ ContentType contentType = ContentType.create(HTTP.PLAIN_TEXT_TYPE, HTTP.UTF_8);
+ StringBody stringBody = new StringBody(fileName, contentType);
+ builder.addPart("fileName", stringBody);
+ stringBody = new StringBody(path, contentType);
+ builder.addPart("path", stringBody);
+// builder.addTextBody("fileName", fileName);
+// builder.addTextBody("path", path);
+ HttpEntity reqEntity = builder.build();
+ httppost.setEntity(reqEntity);
+
+ httpResponse = httpClient.execute(httppost);
+
+
+ int backCode = httpResponse.getStatusLine().getStatusCode();
+ if (backCode != HttpStatus.SC_OK) {
+ log.error("上传文件失败");
+ } else {
+ fileUrl = path + File.separator + fileName;
+ }
+ } catch (IOException e) {
+ log.error("文件服务器连接失败");
+ } finally {
+ //释放资源
+ try {
+ httpClient.close();
+ if (httpResponse != null) {
+ httpResponse.close();
+ }
+ } catch (IOException e) {
+// e.printStackTrace();
+ }
+ }
+
+ if (needThumbnailFlag) {
+ uploadThumbnailV2(url, bytes, fileName, path);
+ }
+
+ return fileUrl;
+ }
+
+
+ /**
+ * 上传缩略图到myupload
+ * 限定压缩图片指定大小100kb,原图大于进行自动调节精度(经验数值)压缩,小于不压缩
+ * @return
+ */
+ private static void uploadThumbnailV2(String url, byte[] imageBytes, String fileName, String path) {
+ CloseableHttpResponse httpResponse = null;
+ CloseableHttpClient httpClient = HttpClients.createDefault();
+ try {
+ //压缩后的图片byte[]
+ byte[] compressedBytes = compressPicForScale(imageBytes,100,fileName);
+ InputStream is = new ByteArrayInputStream(compressedBytes);
+ //压缩后的图片名称
+ String thumbnailName = CommonConstant.THUMBNAIL_PREFIX + fileName;
+
+
+ HttpPost httppost = new HttpPost(url);
+ RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(timeout).setConnectTimeout(timeout).build();
+ httppost.setConfig(requestConfig);
+ httppost.addHeader("token", muToken);
+ MultipartEntityBuilder builder = MultipartEntityBuilder.create();
+ //设置浏览器兼容模式
+ builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
+ //设置请求的编码格式
+ builder.setCharset(Consts.UTF_8);
+
+ builder.addBinaryBody("file", is, ContentType.APPLICATION_OCTET_STREAM, thumbnailName);
+
+ ContentType contentType = ContentType.create(HTTP.PLAIN_TEXT_TYPE, HTTP.UTF_8);
+ StringBody stringBody = new StringBody(thumbnailName, contentType);
+ builder.addPart("fileName", stringBody);
+ stringBody = new StringBody(path, contentType);
+ builder.addPart("path", stringBody);
+ HttpEntity reqEntity = builder.build();
+ httppost.setEntity(reqEntity);
+
+ httpResponse = httpClient.execute(httppost);
+
+ int backCode = httpResponse.getStatusLine().getStatusCode();
+ if (backCode != HttpStatus.SC_OK) {
+ log.error("上传缩略图失败");
+ }
+ } catch (IOException e) {
+ log.error("文件服务器连接失败");
+ } finally {
+ //释放资源
+ try {
+ httpClient.close();
+ if (httpResponse != null) {
+ httpResponse.close();
+ }
+ } catch (IOException e) {
+ }
+ }
+
+ }
+
+
+ /**
+ * 根据指定大小压缩图片
+ *
+ * @param imageBytes 源图片字节数组
+ * @param desFileSize 指定图片大小,单位kb
+ * @param imageId 影像编号
+ * @return 压缩质量后的图片字节数组
+ */
+ public static byte[] compressPicForScale(byte[] imageBytes, long desFileSize, String imageId) {
+ if (imageBytes == null || imageBytes.length <= 0 || imageBytes.length < desFileSize * 1024) {
+ return imageBytes;
+ }
+ long srcSize = imageBytes.length;
+ double accuracy = getAccuracy(srcSize / 1024);
+ try {
+ while (imageBytes.length > desFileSize * 1024) {
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(imageBytes);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream(imageBytes.length);
+ Thumbnails.of(inputStream)
+ .scale(accuracy)
+ .outputQuality(accuracy)
+ .toOutputStream(outputStream);
+ imageBytes = outputStream.toByteArray();
+ }
+ log.info("【图片压缩】imageId={" + imageId + "} | 图片原大小={" + srcSize / 1024 + "}kb | 压缩后大小={" + imageBytes.length / 1024 + "}kb");
+ } catch (Exception e) {
+ log.error("【图片压缩】msg=图片压缩失败!", e);
+ }
+ return imageBytes;
+ }
+
+ /**
+ * 自动调节精度(经验数值)
+ *
+ * @param size 源图片大小
+ * @return 图片压缩质量比
+ */
+ private static double getAccuracy(long size) {
+ double accuracy;
+ if (size < 900) {
+ accuracy = 0.85;
+ } else if (size < 2047) {
+ accuracy = 0.6;
+ } else if (size < 3275) {
+ accuracy = 0.44;
+ } else {
+ accuracy = 0.4;
+ }
+ return accuracy;
+ }
+
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/PasswordUtil.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/PasswordUtil.java
new file mode 100644
index 0000000..aaae509
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/PasswordUtil.java
@@ -0,0 +1,191 @@
+package org.jeecg.common.util;
+
+import java.security.Key;
+import java.security.SecureRandom;
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+
+/**
+ * @Description: 密码工具类
+ * @author: jeecg-boot
+ */
+public class PasswordUtil {
+
+ /**
+ * JAVA6支持以下任意一种算法 PBEWITHMD5ANDDES PBEWITHMD5ANDTRIPLEDES
+ * PBEWITHSHAANDDESEDE PBEWITHSHA1ANDRC2_40 PBKDF2WITHHMACSHA1
+ * */
+
+ /**
+ * 定义使用的算法为:PBEWITHMD5andDES算法
+ * 加密算法
+ */
+ public static final String ALGORITHM = "PBEWithMD5AndDES";
+
+ /**
+ * 定义使用的算法为:PBEWITHMD5andDES算法
+ * 密钥
+ */
+ public static final String SALT = "63293188";
+
+ /**
+ * 定义迭代次数为1000次
+ */
+ private static final int ITERATIONCOUNT = 1000;
+
+ /**
+ * 获取加密算法中使用的盐值,解密中使用的盐值必须与加密中使用的相同才能完成操作. 盐长度必须为8字节
+ *
+ * @return byte[] 盐值
+ * */
+ public static byte[] getSalt() throws Exception {
+ // 实例化安全随机数
+ SecureRandom random = new SecureRandom();
+ // 产出盐
+ return random.generateSeed(8);
+ }
+
+ public static byte[] getStaticSalt() {
+ // 产出盐
+ return SALT.getBytes();
+ }
+
+ /**
+ * 根据PBE密码生成一把密钥
+ *
+ * @param password
+ * 生成密钥时所使用的密码
+ * @return Key PBE算法密钥
+ * */
+ private static Key getPbeKey(String password) {
+ // 实例化使用的算法
+ SecretKeyFactory keyFactory;
+ SecretKey secretKey = null;
+ try {
+ keyFactory = SecretKeyFactory.getInstance(ALGORITHM);
+ // 设置PBE密钥参数
+ PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
+ // 生成密钥
+ secretKey = keyFactory.generateSecret(keySpec);
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ return secretKey;
+ }
+
+ /**
+ * 加密明文字符串
+ *
+ * @param plaintext
+ * 待加密的明文字符串
+ * @param password
+ * 生成密钥时所使用的密码
+ * @param salt
+ * 盐值
+ * @return 加密后的密文字符串
+ * @throws Exception
+ */
+ public static String encrypt(String plaintext, String password, String salt) {
+
+ Key key = getPbeKey(password);
+ byte[] encipheredData = null;
+ PBEParameterSpec parameterSpec = new PBEParameterSpec(salt.getBytes(), ITERATIONCOUNT);
+ try {
+ Cipher cipher = Cipher.getInstance(ALGORITHM);
+
+ cipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec);
+ //update-begin-author:sccott date:20180815 for:中文作为用户名时,加密的密码windows和linux会得到不同的结果 gitee/issues/IZUD7
+ encipheredData = cipher.doFinal(plaintext.getBytes("utf-8"));
+ //update-end-author:sccott date:20180815 for:中文作为用户名时,加密的密码windows和linux会得到不同的结果 gitee/issues/IZUD7
+ } catch (Exception e) {
+ }
+ return bytesToHexString(encipheredData);
+ }
+
+ /**
+ * 解密密文字符串
+ *
+ * @param ciphertext
+ * 待解密的密文字符串
+ * @param password
+ * 生成密钥时所使用的密码(如需解密,该参数需要与加密时使用的一致)
+ * @param salt
+ * 盐值(如需解密,该参数需要与加密时使用的一致)
+ * @return 解密后的明文字符串
+ * @throws Exception
+ */
+ public static String decrypt(String ciphertext, String password, String salt) {
+
+ Key key = getPbeKey(password);
+ byte[] passDec = null;
+ PBEParameterSpec parameterSpec = new PBEParameterSpec(salt.getBytes(), ITERATIONCOUNT);
+ try {
+ Cipher cipher = Cipher.getInstance(ALGORITHM);
+
+ cipher.init(Cipher.DECRYPT_MODE, key, parameterSpec);
+
+ passDec = cipher.doFinal(hexStringToBytes(ciphertext));
+ }
+
+ catch (Exception e) {
+ // TODO: handle exception
+ }
+ return new String(passDec);
+ }
+
+ /**
+ * 将字节数组转换为十六进制字符串
+ *
+ * @param src
+ * 字节数组
+ * @return
+ */
+ public static String bytesToHexString(byte[] src) {
+ StringBuilder stringBuilder = new StringBuilder("");
+ if (src == null || src.length <= 0) {
+ return null;
+ }
+ for (int i = 0; i < src.length; i++) {
+ int v = src[i] & 0xFF;
+ String hv = Integer.toHexString(v);
+ if (hv.length() < 2) {
+ stringBuilder.append(0);
+ }
+ stringBuilder.append(hv);
+ }
+ return stringBuilder.toString();
+ }
+
+ /**
+ * 将十六进制字符串转换为字节数组
+ *
+ * @param hexString
+ * 十六进制字符串
+ * @return
+ */
+ public static byte[] hexStringToBytes(String hexString) {
+ if (hexString == null || "".equals(hexString)) {
+ return null;
+ }
+ hexString = hexString.toUpperCase();
+ int length = hexString.length() / 2;
+ char[] hexChars = hexString.toCharArray();
+ byte[] d = new byte[length];
+ for (int i = 0; i < length; i++) {
+ int pos = i * 2;
+ d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
+ }
+ return d;
+ }
+
+ private static byte charToByte(char c) {
+ return (byte) "0123456789ABCDEF".indexOf(c);
+ }
+
+
+}
\ No newline at end of file
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/PmsUtil.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/PmsUtil.java
new file mode 100644
index 0000000..0926202
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/PmsUtil.java
@@ -0,0 +1,65 @@
+package org.jeecg.common.util;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @Description: PmsUtil
+ * @author: jeecg-boot
+ */
+@Slf4j
+@Component
+public class PmsUtil {
+
+
+ private static String uploadPath;
+
+ @Value("${jeecg.path.upload}")
+ public void setUploadPath(String uploadPath) {
+ PmsUtil.uploadPath = uploadPath;
+ }
+
+ public static String saveErrorTxtByList(List msg, String name) {
+ Date d = new Date();
+ String saveDir = "logs" + File.separator + DateUtils.yyyyMMdd.get().format(d) + File.separator;
+ String saveFullDir = uploadPath + File.separator + saveDir;
+
+ File saveFile = new File(saveFullDir);
+ if (!saveFile.exists()) {
+ saveFile.mkdirs();
+ }
+ name += DateUtils.yyyymmddhhmmss.get().format(d) + Math.round(Math.random() * 10000);
+ String saveFilePath = saveFullDir + name + ".txt";
+
+ try {
+ //封装目的地
+ BufferedWriter bw = new BufferedWriter(new FileWriter(saveFilePath));
+ //遍历集合
+ for (String s : msg) {
+ //写数据
+ if (s.indexOf("_") > 0) {
+ String[] arr = s.split("_");
+ bw.write("第" + arr[0] + "行:" + arr[1]);
+ } else {
+ bw.write(s);
+ }
+ //bw.newLine();
+ bw.write("\r\n");
+ }
+ //释放资源
+ bw.flush();
+ bw.close();
+ } catch (Exception e) {
+ log.info("excel导入生成错误日志文件异常:" + e.getMessage());
+ }
+ return saveDir + name + ".txt";
+ }
+
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/RandImageUtil.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/RandImageUtil.java
new file mode 100644
index 0000000..3b59a89
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/RandImageUtil.java
@@ -0,0 +1,148 @@
+package org.jeecg.common.util;
+
+import javax.imageio.ImageIO;
+import javax.servlet.http.HttpServletResponse;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Base64;
+
+/**
+ * 登录验证码工具类
+ */
+public class RandImageUtil {
+
+ public static final String key = "JEECG_LOGIN_KEY";
+
+ /**
+ * 定义图形大小
+ */
+ private static final int width = 105;
+ /**
+ * 定义图形大小
+ */
+ private static final int height = 35;
+
+ /**
+ * 定义干扰线数量
+ */
+ private static final int count = 200;
+
+ /**
+ * 干扰线的长度=1.414*lineWidth
+ */
+ private static final int lineWidth = 2;
+
+ /**
+ * 图片格式
+ */
+ private static final String IMG_FORMAT = "JPEG";
+
+ /**
+ * base64 图片前缀
+ */
+ private static final String BASE64_PRE = "data:image/jpg;base64,";
+
+ /**
+ * 直接通过response 返回图片
+ * @param response
+ * @param resultCode
+ * @throws IOException
+ */
+ public static void generate(HttpServletResponse response, String resultCode) throws IOException {
+ BufferedImage image = getImageBuffer(resultCode);
+ // 输出图象到页面
+ ImageIO.write(image, IMG_FORMAT, response.getOutputStream());
+ }
+
+ /**
+ * 生成base64字符串
+ * @param resultCode
+ * @return
+ * @throws IOException
+ */
+ public static String generate(String resultCode) throws IOException {
+ BufferedImage image = getImageBuffer(resultCode);
+
+ ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+ //写入流中
+ ImageIO.write(image, IMG_FORMAT, byteStream);
+ //转换成字节
+ byte[] bytes = byteStream.toByteArray();
+ //转换成base64串
+ String base64 = Base64.getEncoder().encodeToString(bytes).trim();
+ base64 = base64.replaceAll("\n", "").replaceAll("\r", "");//删除 \r\n
+
+ //写到指定位置
+ //ImageIO.write(bufferedImage, "png", new File(""));
+
+ return BASE64_PRE+base64;
+ }
+
+ private static BufferedImage getImageBuffer(String resultCode){
+ // 在内存中创建图象
+ final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+ // 获取图形上下文
+ final Graphics2D graphics = (Graphics2D) image.getGraphics();
+ // 设定背景颜色
+ graphics.setColor(Color.WHITE); // ---1
+ graphics.fillRect(0, 0, width, height);
+ // 设定边框颜色
+// graphics.setColor(getRandColor(100, 200)); // ---2
+ graphics.drawRect(0, 0, width - 1, height - 1);
+
+ try {
+ // 不安全的随机数-中危
+ final SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
+ // 随机产生干扰线,使图象中的认证码不易被其它程序探测到
+ for (int i = 0; i < count; i++) {
+ graphics.setColor(getRandColor(150, 200)); // ---3
+
+ final int x = random.nextInt(width - lineWidth - 1) + 1; // 保证画在边框之内
+ final int y = random.nextInt(height - lineWidth - 1) + 1;
+ final int xl = random.nextInt(lineWidth);
+ final int yl = random.nextInt(lineWidth);
+ graphics.drawLine(x, y, x + xl, y + yl);
+ }
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ }
+ // 取随机产生的认证码
+ for (int i = 0; i < resultCode.length(); i++) {
+ // 将认证码显示到图象中,调用函数出来的颜色相同,可能是因为种子太接近,所以只能直接生成
+ // graphics.setColor(new Color(20 + random.nextInt(130), 20 + random
+ // .nextInt(130), 20 + random.nextInt(130)));
+ // 设置字体颜色
+ graphics.setColor(Color.BLACK);
+ // 设置字体样式
+// graphics.setFont(new Font("Arial Black", Font.ITALIC, 18));
+ graphics.setFont(new Font("Times New Roman", Font.BOLD, 24));
+ // 设置字符,字符间距,上边距
+ graphics.drawString(String.valueOf(resultCode.charAt(i)), (23 * i) + 8, 26);
+ }
+ // 图象生效
+ graphics.dispose();
+ return image;
+ }
+
+ private static Color getRandColor(int fc, int bc) throws NoSuchAlgorithmException { // 取得给定范围随机颜色
+// final Random random = new Random();
+ // 不安全的随机数-中危
+ final SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
+ if (fc > 255) {
+ fc = 255;
+ }
+ if (bc > 255) {
+ bc = 255;
+ }
+
+ final int r = fc + random.nextInt(bc - fc);
+ final int g = fc + random.nextInt(bc - fc);
+ final int b = fc + random.nextInt(bc - fc);
+
+ return new Color(r, g, b);
+ }
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/RedisUtil.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/RedisUtil.java
new file mode 100644
index 0000000..a690178
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/RedisUtil.java
@@ -0,0 +1,652 @@
+package org.jeecg.common.util;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.*;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * redis 工具类
+ * @Author Scott
+ *
+ */
+@Component
+public class RedisUtil {
+
+ @Autowired
+ private RedisTemplate redisTemplate;
+ @Autowired
+ private StringRedisTemplate stringRedisTemplate;
+
+ /**
+ * 指定缓存失效时间
+ *
+ * @param key 键
+ * @param time 时间(秒)
+ * @return
+ */
+ public boolean expire(String key, long time) {
+ try {
+ if (time > 0) {
+ redisTemplate.expire(key, time, TimeUnit.SECONDS);
+ }
+ return true;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ /**
+ * 根据key 获取过期时间
+ *
+ * @param key 键 不能为null
+ * @return 时间(秒) 返回0代表为永久有效
+ */
+ public long getExpire(String key) {
+ return redisTemplate.getExpire(key, TimeUnit.SECONDS);
+ }
+
+ /**
+ * 判断key是否存在
+ *
+ * @param key 键
+ * @return true 存在 false不存在
+ */
+ public boolean hasKey(String key) {
+ try {
+ return redisTemplate.hasKey(key);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ /**
+ * 删除缓存
+ *
+ * @param key 可以传一个值 或多个
+ */
+ @SuppressWarnings("unchecked")
+ public void del(String... key) {
+ if (key != null && key.length > 0) {
+ if (key.length == 1) {
+ redisTemplate.delete(key[0]);
+ } else {
+ redisTemplate.delete((Collection) CollectionUtils.arrayToList(key));
+ }
+ }
+ }
+
+ // ============================String=============================
+ /**
+ * 普通缓存获取
+ *
+ * @param key 键
+ * @return 值
+ */
+ public Object get(String key) {
+ return key == null ? null : redisTemplate.opsForValue().get(key);
+ }
+
+ /**
+ * 普通缓存放入
+ *
+ * @param key 键
+ * @param value 值
+ * @return true成功 false失败
+ */
+ public boolean set(String key, Object value) {
+ try {
+ redisTemplate.opsForValue().set(key, value);
+ return true;
+ } catch (Exception e) {
+ e.printStackTrace();
+ 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) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ /**
+ * 递增
+ *
+ * @param key 键
+ * @param delta 要增加几(大于0)
+ * @return
+ */
+ public long incr(String key, long delta) {
+ if (delta < 0) {
+ throw new RuntimeException("递增因子必须大于0");
+ }
+ return redisTemplate.opsForValue().increment(key, delta);
+ }
+
+ /**
+ * 递减
+ *
+ * @param key 键
+ * @param delta 要减少几(小于0)
+ * @return
+ */
+ public long decr(String key, long delta) {
+ if (delta < 0) {
+ throw new RuntimeException("递减因子必须大于0");
+ }
+ return redisTemplate.opsForValue().increment(key, -delta);
+ }
+
+ // ================================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) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ /**
+ * HashSet 并设置时间
+ *
+ * @param key 键
+ * @param map 对应多个键值
+ * @param time 时间(秒)
+ * @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) {
+ e.printStackTrace();
+ 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) {
+ e.printStackTrace();
+ 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) {
+ e.printStackTrace();
+ 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) {
+ e.printStackTrace();
+ 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) {
+ e.printStackTrace();
+ 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) {
+ e.printStackTrace();
+ 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) {
+ e.printStackTrace();
+ return 0;
+ }
+ }
+
+ /**
+ * 获取set缓存的长度
+ *
+ * @param key 键
+ * @return
+ */
+ public long sGetSetSize(String key) {
+ try {
+ return redisTemplate.opsForSet().size(key);
+ } catch (Exception e) {
+ e.printStackTrace();
+ 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) {
+ e.printStackTrace();
+ 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) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * 截取集合元素长度,保留长度内的数据。
+ *
+ * @param key 键
+ * @param start 开始 下标
+ * @param end 结束 -1代表 start 前的所有值
+ * @return 0 到 -1代表所有值
+ */
+ public void lTrim(String key, long start, long end) {
+ try {
+ redisTemplate.opsForList().trim(key, start, end);;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ /**
+ * 获取list缓存的长度
+ *
+ * @param key 键
+ * @return
+ */
+ public long lGetListSize(String key) {
+ try {
+ return redisTemplate.opsForList().size(key);
+ } catch (Exception e) {
+ e.printStackTrace();
+ 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) {
+ e.printStackTrace();
+ 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) {
+ e.printStackTrace();
+ 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) {
+ e.printStackTrace();
+ 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) {
+ e.printStackTrace();
+ 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) {
+ e.printStackTrace();
+ 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) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ /**
+ * 移除N个值为value
+ *
+ * @param key 键
+ * @param count 移除多少个
+ * @param value 值
+ * @return 移除的个数
+ */
+ public long lRemove(String key, long count, Object value) {
+ try {
+ Long remove = redisTemplate.opsForList().remove(key, count, value);
+ return remove;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return 0;
+ }
+ }
+
+ /**
+ * 获取指定前缀的一系列key
+ * 使用scan命令代替keys, Redis是单线程处理,keys命令在KEY数量较多时,
+ * 操作效率极低【时间复杂度为O(N)】,该命令一旦执行会严重阻塞线上其它命令的正常请求
+ * @param keyPrefix
+ * @return
+ */
+ private Set keys(String keyPrefix) {
+ String realKey = keyPrefix + "*";
+
+ try {
+ return redisTemplate.execute((RedisCallback>) connection -> {
+ Set binaryKeys = new HashSet<>();
+ Cursor cursor = connection.scan(ScanOptions.scanOptions().match(realKey).count(Integer.MAX_VALUE).build());
+ while (cursor.hasNext()) {
+ binaryKeys.add(new String(cursor.next()));
+ }
+
+ return binaryKeys;
+ });
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ /**
+ * 删除指定前缀的一系列key
+ * @param keyPrefix
+ */
+ public void removeAll(String keyPrefix) {
+ try {
+ Set keys = keys(keyPrefix);
+ redisTemplate.delete(keys);
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+
+ public Long releaseRedisLock(String lockKey, String lockValue) {
+ // Lua脚本:只有锁的值匹配时才删除
+ String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] " +
+ "then return redis.call('del', KEYS[1]) " +
+ "else return 0 end";
+
+ DefaultRedisScript redisScript = new DefaultRedisScript<>();
+ redisScript.setScriptText(luaScript);
+ redisScript.setResultType(Long.class);
+
+ // 执行Lua脚本(原子操作)
+
+ Long result = stringRedisTemplate.execute(
+ redisScript,
+ Collections.singletonList(lockKey),
+ lockValue
+ );
+ return result;
+ }
+
+ public static void main(String[] args) {
+ RedisUtil redisUtil=new RedisUtil();
+ redisUtil.get("111");
+ }
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/ReflectHelper.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/ReflectHelper.java
new file mode 100644
index 0000000..3e843ee
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/ReflectHelper.java
@@ -0,0 +1,255 @@
+package org.jeecg.common.util;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.regex.Pattern;
+
+/**
+ * @author 张代浩
+ * @desc 通过反射来动态调用get 和 set 方法
+ */
+@Slf4j
+public class ReflectHelper {
+
+ private Class cls;
+
+ /**
+ * 传过来的对象
+ */
+ private Object obj;
+
+ /**
+ * 存放get方法
+ */
+ private Hashtable getMethods = null;
+ /**
+ * 存放set方法
+ */
+ private Hashtable setMethods = null;
+
+ /**
+ * 定义构造方法 -- 一般来说是个pojo
+ *
+ * @param o 目标对象
+ */
+ public ReflectHelper(Object o) {
+ obj = o;
+ initMethods();
+ }
+
+ /**
+ * @desc 初始化
+ */
+ public void initMethods() {
+ getMethods = new Hashtable();
+ setMethods = new Hashtable();
+ cls = obj.getClass();
+ Method[] methods = cls.getMethods();
+ // 定义正则表达式,从方法中过滤出getter / setter 函数.
+ String gs = "get(\\w+)";
+ Pattern getM = Pattern.compile(gs);
+ String ss = "set(\\w+)";
+ Pattern setM = Pattern.compile(ss);
+ // 把方法中的"set" 或者 "get" 去掉
+ String rapl = "$1";
+ String param;
+ for (int i = 0; i < methods.length; ++i) {
+ Method m = methods[i];
+ String methodName = m.getName();
+ if (Pattern.matches(gs, methodName)) {
+ param = getM.matcher(methodName).replaceAll(rapl).toLowerCase();
+ getMethods.put(param, m);
+ } else if (Pattern.matches(ss, methodName)) {
+ param = setM.matcher(methodName).replaceAll(rapl).toLowerCase();
+ setMethods.put(param, m);
+ } else {
+ // logger.info(methodName + " 不是getter,setter方法!");
+ }
+ }
+ }
+
+ /**
+ * @desc 调用set方法
+ */
+ public boolean setMethodValue(String property, Object object) {
+ Method m = setMethods.get(property.toLowerCase());
+ if (m != null) {
+ try {
+ // 调用目标类的setter函数
+ m.invoke(obj, object);
+ return true;
+ } catch (Exception ex) {
+ log.info("invoke getter on " + property + " error: " + ex.toString());
+ return false;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @desc 调用set方法
+ */
+ public Object getMethodValue(String property) {
+ Object value = null;
+ Method m = getMethods.get(property.toLowerCase());
+ if (m != null) {
+ try {
+ /*
+ * 调用obj类的setter函数
+ */
+ value = m.invoke(obj, new Object[]{});
+
+ } catch (Exception ex) {
+ log.info("invoke getter on " + property + " error: " + ex.toString());
+ }
+ }
+ return value;
+ }
+
+ /**
+ * 把map中的内容全部注入到obj中
+ *
+ * @param data
+ * @return
+ */
+ public Object setAll(Map data) {
+ if (data == null || data.keySet().size() <= 0) {
+ return null;
+ }
+ for (Entry entry : data.entrySet()) {
+ this.setMethodValue(entry.getKey(), entry.getValue());
+ }
+ return obj;
+ }
+
+ /**
+ * 把map中的内容全部注入到obj中
+ *
+ * @param o
+ * @param data
+ * @return
+ */
+ public static Object setAll(Object o, Map data) {
+ ReflectHelper reflectHelper = new ReflectHelper(o);
+ reflectHelper.setAll(data);
+ return o;
+ }
+
+ /**
+ * 把map中的内容全部注入到新实例中
+ *
+ * @param clazz
+ * @param data
+ * @return
+ */
+ @SuppressWarnings("unchecked")
+ public static T setAll(Class clazz, Map data) {
+ T o = null;
+ try {
+ o = clazz.newInstance();
+ } catch (Exception e) {
+ e.printStackTrace();
+ o = null;
+ return o;
+ }
+ return (T) setAll(o, data);
+ }
+
+ /**
+ * 根据传入的class将mapList转换为实体类list
+ *
+ * @param mapist
+ * @param clazz
+ * @return
+ */
+ public static List transList2Entrys(List> mapist, Class