From 57da9f7ffea6a9960984af0dc5e2aadd990e396a Mon Sep 17 00:00:00 2001 From: RuoYi Date: Fri, 23 Jul 2021 18:54:16 +0800 Subject: [PATCH 01/28] =?UTF-8?q?=E5=8D=87=E7=BA=A7oshi=E5=88=B0=E6=9C=80?= =?UTF-8?q?=E6=96=B0=E7=89=88=E6=9C=ACv5.8.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7279a36d1..62467346f 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ 2.1.4 1.3.1 1.2.76 - 5.7.5 + 5.8.0 5.8.0 2.10.0 1.4 From 15c75786929375814044dc26804d7dffb72408eb Mon Sep 17 00:00:00 2001 From: RuoYi Date: Sun, 25 Jul 2021 10:36:42 +0800 Subject: [PATCH 02/28] =?UTF-8?q?=E5=8E=BB=E9=99=A4=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E5=88=86=E9=A1=B5=E5=90=88=E7=90=86=E5=8C=96=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-admin/src/main/resources/application.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index e66fe68e4..b79e9b30f 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -82,7 +82,6 @@ mybatis: # PageHelper分页插件 pagehelper: helperDialect: mysql - reasonable: true supportMethodsArguments: true params: count=countSql From 27277b51d7deb0ac33a96af660005c73ecb66c3b Mon Sep 17 00:00:00 2001 From: RuoYi Date: Wed, 28 Jul 2021 16:37:56 +0800 Subject: [PATCH 03/28] =?UTF-8?q?=E4=BC=98=E5=8C=96XSS=E8=B7=A8=E7=AB=99?= =?UTF-8?q?=E8=84=9A=E6=9C=AC=E8=BF=87=E6=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ruoyi/common/constant/Constants.java | 10 ++ .../com/ruoyi/common/utils/StringUtils.java | 110 +++++++++++++++++- .../java/com/ruoyi/common/xss/XssFilter.java | 33 +----- .../ruoyi/framework/config/FilterConfig.java | 8 +- 4 files changed, 127 insertions(+), 34 deletions(-) diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java index 869997cda..64664e06e 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java @@ -17,6 +17,16 @@ public class Constants */ public static final String GBK = "GBK"; + /** + * http请求 + */ + public static final String HTTP = "http://"; + + /** + * https请求 + */ + public static final String HTTPS = "https://"; + /** * 通用成功标识 */ diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java index 882e94a64..806e4ad9c 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java @@ -1,7 +1,13 @@ package com.ruoyi.common.utils; +import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.Set; +import org.springframework.util.AntPathMatcher; +import com.ruoyi.common.constant.Constants; import com.ruoyi.common.core.text.StrFormatter; /** @@ -256,6 +262,68 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils return StrFormatter.format(template, params); } + /** + * 是否为http(s)://开头 + * + * @param link 链接 + * @return 结果 + */ + public static boolean ishttp(String link) + { + return StringUtils.startsWithAny(link, Constants.HTTP, Constants.HTTPS); + } + + /** + * 字符串转set + * + * @param str 字符串 + * @param sep 分隔符 + * @return set集合 + */ + public static final Set str2Set(String str, String sep) + { + return new HashSet(str2List(str, sep, true, false)); + } + + /** + * 字符串转list + * + * @param str 字符串 + * @param sep 分隔符 + * @param filterBlank 过滤纯空白 + * @param trim 去掉首尾空白 + * @return list集合 + */ + public static final List str2List(String str, String sep, boolean filterBlank, boolean trim) + { + List list = new ArrayList(); + if (StringUtils.isEmpty(str)) + { + return list; + } + + // 过滤空白字符串 + if (filterBlank && StringUtils.isBlank(str)) + { + return list; + } + String[] split = str.split(sep); + for (String string : split) + { + if (filterBlank && StringUtils.isBlank(string)) + { + continue; + } + if (trim) + { + string = string.trim(); + } + list.add(string); + } + + return list; + } + /** * 驼峰转下划线命名 */ @@ -301,6 +369,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils } sb.append(Character.toLowerCase(c)); } + return sb.toString(); } @@ -400,9 +469,48 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils return sb.toString(); } + /** + * 查找指定字符串是否匹配指定字符串列表中的任意一个字符串 + * + * @param str 指定字符串 + * @param strs 需要检查的字符串数组 + * @return 是否匹配 + */ + public static boolean matches(String str, List strs) + { + if (isEmpty(str) || isEmpty(strs)) + { + return false; + } + for (String pattern : strs) + { + if (isMatch(pattern, str)) + { + return true; + } + } + return false; + } + + /** + * 判断url是否与规则配置: + * ? 表示单个字符; + * * 表示一层路径内的任意字符串,不可跨层级; + * ** 表示任意层路径; + * + * @param pattern 匹配规则 + * @param url 需要匹配的url + * @return + */ + public static boolean isMatch(String pattern, String url) + { + AntPathMatcher matcher = new AntPathMatcher(); + return matcher.match(pattern, url); + } + @SuppressWarnings("unchecked") public static T cast(Object obj) { return (T) obj; } -} +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssFilter.java b/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssFilter.java index d307fbc9f..30fd69ce8 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssFilter.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssFilter.java @@ -3,8 +3,6 @@ package com.ruoyi.common.xss; import java.io.IOException; import java.util.ArrayList; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; @@ -27,16 +25,10 @@ public class XssFilter implements Filter */ public List excludes = new ArrayList<>(); - /** - * xss过滤开关 - */ - public boolean enabled = false; - @Override public void init(FilterConfig filterConfig) throws ServletException { String tempExcludes = filterConfig.getInitParameter("excludes"); - String tempEnabled = filterConfig.getInitParameter("enabled"); if (StringUtils.isNotEmpty(tempExcludes)) { String[] url = tempExcludes.split(","); @@ -45,10 +37,6 @@ public class XssFilter implements Filter excludes.add(url[i]); } } - if (StringUtils.isNotEmpty(tempEnabled)) - { - enabled = Boolean.valueOf(tempEnabled); - } } @Override @@ -68,25 +56,14 @@ public class XssFilter implements Filter private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) { - if (!enabled) + String url = request.getServletPath(); + String method = request.getMethod(); + // GET DELETE 不过滤 + if (method == null || method.matches("GET") || method.matches("DELETE")) { return true; } - if (excludes == null || excludes.isEmpty()) - { - return false; - } - String url = request.getServletPath(); - for (String pattern : excludes) - { - Pattern p = Pattern.compile("^" + pattern); - Matcher m = p.matcher(url); - if (m.find()) - { - return true; - } - } - return false; + return StringUtils.matches(url, excludes); } @Override diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java index 92d010e80..b7d10b8f9 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java @@ -4,6 +4,7 @@ import java.util.HashMap; import java.util.Map; import javax.servlet.DispatcherType; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -16,11 +17,9 @@ import com.ruoyi.common.xss.XssFilter; * @author ruoyi */ @Configuration +@ConditionalOnProperty(value = "xss.enabled", havingValue = "true") public class FilterConfig { - @Value("${xss.enabled}") - private String enabled; - @Value("${xss.excludes}") private String excludes; @@ -36,10 +35,9 @@ public class FilterConfig registration.setFilter(new XssFilter()); registration.addUrlPatterns(StringUtils.split(urlPatterns, ",")); registration.setName("xssFilter"); - registration.setOrder(Integer.MAX_VALUE); + registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE); Map initParameters = new HashMap(); initParameters.put("excludes", excludes); - initParameters.put("enabled", enabled); registration.setInitParameters(initParameters); return registration; } From 8b515fe0e3524d10682ccf1c49ecc4ac7439adf6 Mon Sep 17 00:00:00 2001 From: "bo.yang" Date: Thu, 29 Jul 2021 15:55:36 +0800 Subject: [PATCH 04/28] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E6=8A=A5=E9=94=99=20ER?= =?UTF-8?q?ROR=EF=BC=9Aoracle.jdbc.driver.OracleDriver=20is=20deprecated.?= =?UTF-8?q?=20=E5=B0=86oracle=E7=9A=84driverClassName=E6=94=B9=E4=B8=BAora?= =?UTF-8?q?cle.jdbc.OracleDriver?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-admin/src/main/resources/application-druid.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ruoyi-admin/src/main/resources/application-druid.yml b/ruoyi-admin/src/main/resources/application-druid.yml index cdc620e0a..86ea6e820 100644 --- a/ruoyi-admin/src/main/resources/application-druid.yml +++ b/ruoyi-admin/src/main/resources/application-druid.yml @@ -16,7 +16,7 @@ spring: url: jdbc:oracle:thin:@192.168.2.91:1521/toptest username: ds7 password: ds7 - driverClassName: oracle.jdbc.driver.OracleDriver + driverClassName: oracle.jdbc.OracleDriver #SQlServer数据源 sqlsvr: # 从数据源开关/默认关闭 @@ -33,7 +33,7 @@ spring: url: jdbc:oracle:thin:@192.168.2.91:1521/topprod username: ds_report password: ds_report - driverClassName: oracle.jdbc.driver.OracleDriver + driverClassName: oracle.jdbc.OracleDriver # Toptest_ds_report toptestdsreport: @@ -42,7 +42,7 @@ spring: url: jdbc:oracle:thin:@192.168.2.91:1521/toptest username: ds_report password: ds_report - driverClassName: oracle.jdbc.driver.OracleDriver + driverClassName: oracle.jdbc.OracleDriver # 初始连接数 initialSize: 5 From eed4caa44f3238bfb33336b853c221615e8a1bac Mon Sep 17 00:00:00 2001 From: RuoYi Date: Fri, 30 Jul 2021 10:18:37 +0800 Subject: [PATCH 05/28] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=83=A8=E9=97=A8?= =?UTF-8?q?=E9=A1=B6=E7=BA=A7=E8=8A=82=E7=82=B9=E6=8A=A5=E9=94=99=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java index e7035fc8d..98f44bea5 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java @@ -227,7 +227,7 @@ public class SysDeptServiceImpl implements ISysDeptService updateDeptChildren(dept.getDeptId(), newAncestors, oldAncestors); } int result = deptMapper.updateDept(dept); - if (UserConstants.DEPT_NORMAL.equals(dept.getStatus())) + if (UserConstants.DEPT_NORMAL.equals(dept.getStatus()) && StringUtils.isNotEmpty(dept.getAncestors())) { // 如果该部门是启用状态,则启用该部门的所有上级部门 updateParentDeptStatusNormal(dept); From 9d83ffab2adb668164308e4dbe1f32c5629ac0d9 Mon Sep 17 00:00:00 2001 From: RuoYi Date: Fri, 30 Jul 2021 10:19:26 +0800 Subject: [PATCH 06/28] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E7=94=9F=E6=88=90?= =?UTF-8?q?=E5=AF=8C=E6=96=87=E6=9C=AC=E9=BB=98=E8=AE=A4dialogsInBody?= =?UTF-8?q?=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-generator/src/main/resources/vm/html/add.html.vm | 1 + ruoyi-generator/src/main/resources/vm/html/edit.html.vm | 1 + 2 files changed, 2 insertions(+) diff --git a/ruoyi-generator/src/main/resources/vm/html/add.html.vm b/ruoyi-generator/src/main/resources/vm/html/add.html.vm index 85fd47c48..b228b34ee 100644 --- a/ruoyi-generator/src/main/resources/vm/html/add.html.vm +++ b/ruoyi-generator/src/main/resources/vm/html/add.html.vm @@ -238,6 +238,7 @@ $(function() { $('.summernote').summernote({ lang: 'zh-CN', + dialogsInBody: true, callbacks: { onChange: function(contents, $edittable) { $("input[name='" + this.id + "']").val(contents); diff --git a/ruoyi-generator/src/main/resources/vm/html/edit.html.vm b/ruoyi-generator/src/main/resources/vm/html/edit.html.vm index 0d1f8b7cb..174cc0823 100644 --- a/ruoyi-generator/src/main/resources/vm/html/edit.html.vm +++ b/ruoyi-generator/src/main/resources/vm/html/edit.html.vm @@ -245,6 +245,7 @@ $('.summernote').each(function(i) { $('#' + this.id).summernote({ lang: 'zh-CN', + dialogsInBody: true, callbacks: { onChange: function(contents, $edittable) { $("input[name='" + this.id + "']").val(contents); From c7e0292da30bc51b15166826385de78c6dc80774 Mon Sep 17 00:00:00 2001 From: RuoYi Date: Fri, 30 Jul 2021 10:19:46 +0800 Subject: [PATCH 07/28] =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E8=A1=A8=E6=A0=BC?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E5=88=97=E5=80=BC=E5=A2=9E=E5=8A=A0deDuplica?= =?UTF-8?q?tion=E6=98=AF=E5=90=A6=E5=8E=BB=E9=87=8D=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/resources/static/ruoyi/js/ry-ui.js | 881 +++++++++--------- 1 file changed, 442 insertions(+), 439 deletions(-) diff --git a/ruoyi-admin/src/main/resources/static/ruoyi/js/ry-ui.js b/ruoyi-admin/src/main/resources/static/ruoyi/js/ry-ui.js index 82759721b..2c38d8f24 100644 --- a/ruoyi-admin/src/main/resources/static/ruoyi/js/ry-ui.js +++ b/ruoyi-admin/src/main/resources/static/ruoyi/js/ry-ui.js @@ -10,12 +10,12 @@ var table = { options: {}, // 设置实例配置 set: function(id) { - if($.common.getLength(table.config) > 1) { + if($.common.getLength(table.config) > 1) { var tableId = $.common.isEmpty(id) ? $(event.currentTarget).parents(".bootstrap-table").find("table.table").attr("id") : id; if ($.common.isNotEmpty(tableId)) { table.options = table.get(tableId); } - } + } }, // 获取实例配置 get: function(id) { @@ -29,13 +29,13 @@ var table = { (function ($) { $.extend({ - _tree: {}, - bttTable: {}, - // 表格封装处理 - table: { + _tree: {}, + bttTable: {}, + // 表格封装处理 + table: { // 初始化表格参数 init: function(options) { - var defaults = { + var defaults = { id: "bootstrap-table", type: 0, // 0 代表bootstrapTable 1代表bootstrapTreeTable method: 'post', @@ -75,9 +75,9 @@ var table = { queryParams: $.table.queryParams, rowStyle: undefined, }; - var options = $.extend(defaults, options); - table.options = options; - table.config[options.id] = options; + var options = $.extend(defaults, options); + table.options = options; + table.config[options.id] = options; $.table.initEvent(); $('#' + options.id).bootstrapTable({ id: options.id, @@ -154,12 +154,12 @@ var table = { }, // 获取实例ID,如存在多个返回#id1,#id2 delimeter分隔符 getOptionsIds: function(separator) { - var _separator = $.common.isEmpty(separator) ? "," : separator; - var optionsIds = ""; - $.each(table.config, function(key, value){ + var _separator = $.common.isEmpty(separator) ? "," : separator; + var optionsIds = ""; + $.each(table.config, function(key, value){ optionsIds += "#" + key + _separator; - }); - return optionsIds.substring(0, optionsIds.length - 1); + }); + return optionsIds.substring(0, optionsIds.length - 1); }, // 查询条件 queryParams: function(params) { @@ -171,24 +171,24 @@ var table = { orderByColumn: params.sort, isAsc: params.order }; - var currentId = $.common.isEmpty(table.options.formId) ? $('form').attr('id') : table.options.formId; - return $.extend(curParams, $.common.formToJSON(currentId)); + var currentId = $.common.isEmpty(table.options.formId) ? $('form').attr('id') : table.options.formId; + return $.extend(curParams, $.common.formToJSON(currentId)); }, // 请求获取数据后处理回调函数 responseHandler: function(res) { - if (typeof table.get(this.id).responseHandler == "function") { + if (typeof table.get(this.id).responseHandler == "function") { table.get(this.id).responseHandler(res); } if (res.code == web_status.SUCCESS) { if ($.common.isNotEmpty(table.options.sidePagination) && table.options.sidePagination == 'client') { - return res.rows; + return res.rows; } else { - if ($.common.isNotEmpty(table.options.rememberSelected) && table.options.rememberSelected) { + if ($.common.isNotEmpty(table.options.rememberSelected) && table.options.rememberSelected) { var column = $.common.isEmpty(table.options.uniqueId) ? table.options.columns[1].field : table.options.uniqueId; $.each(res.rows, function(i, row) { row.state = $.inArray(row[column], table.rememberSelectedIds[table.options.id]) !== -1; }) - } + } return { rows: res.rows, total: res.total }; } } else { @@ -198,21 +198,21 @@ var table = { }, // 初始化事件 initEvent: function() { - // 实例ID信息 - var optionsIds = $.table.getOptionsIds(); - // 监听事件处理 - $(optionsIds).on(TABLE_EVENTS, function () { + // 实例ID信息 + var optionsIds = $.table.getOptionsIds(); + // 监听事件处理 + $(optionsIds).on(TABLE_EVENTS, function () { table.set($(this).attr("id")); - }); - // 在表格体渲染完成,并在 DOM 中可见后触发(事件) - $(optionsIds).on("post-body.bs.table", function (e, args) { + }); + // 在表格体渲染完成,并在 DOM 中可见后触发(事件) + $(optionsIds).on("post-body.bs.table", function (e, args) { // 浮动提示框特效 $(".table [data-toggle='tooltip']").tooltip(); // 气泡弹出框特效 $('.table [data-toggle="popover"]').popover(); - }); - // 选中、取消、全部选中、全部取消(事件) - $(optionsIds).on("check.bs.table check-all.bs.table uncheck.bs.table uncheck-all.bs.table", function (e, rowsAfter, rowsBefore) { + }); + // 选中、取消、全部选中、全部取消(事件) + $(optionsIds).on("check.bs.table check-all.bs.table uncheck.bs.table uncheck-all.bs.table", function (e, rowsAfter, rowsBefore) { // 复选框分页保留保存选中数组 var rows = $.common.equals("uncheck-all", e.type) ? rowsBefore : rowsAfter; var rowIds = $.table.affectedRowIds(rows); @@ -231,9 +231,9 @@ var table = { table.rememberSelecteds[table.options.id] = _[func]([], rows); } } - }); - // 加载成功、选中、取消、全部选中、全部取消(事件) - $(optionsIds).on("check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table load-success.bs.table", function () { + }); + // 加载成功、选中、取消、全部选中、全部取消(事件) + $(optionsIds).on("check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table load-success.bs.table", function () { var toolbar = table.options.toolbar; var uniqueId = table.options.uniqueId; // 工具栏按钮控制 @@ -242,9 +242,9 @@ var table = { $('#' + toolbar + ' .multiple').toggleClass('disabled', !rows.length); // 非单个禁用 $('#' + toolbar + ' .single').toggleClass('disabled', rows.length!=1); - }); - // 图片预览事件 - $(optionsIds).off("click").on("click", '.img-circle', function() { + }); + // 图片预览事件 + $(optionsIds).off("click").on("click", '.img-circle', function() { var src = $(this).attr('src'); var target = $(this).data('target'); if($.common.equals("self", target)) { @@ -262,8 +262,8 @@ var table = { window.open(src); } }); - // 单击tooltip事件 - $(optionsIds).on("click", '.tooltip-show', function() { + // 单击tooltip事件 + $(optionsIds).on("click", '.tooltip-show', function() { var target = $(this).data('target'); var input = $(this).prev(); if ($.common.equals("copy", target)) { @@ -277,20 +277,20 @@ var table = { btnclass: ['btn btn-primary'], }); } - }); + }); }, // 当所有数据被加载时触发 onLoadSuccess: function(data) { - if (typeof table.options.onLoadSuccess == "function") { + if (typeof table.options.onLoadSuccess == "function") { table.options.onLoadSuccess(data); - } + } }, // 表格销毁 destroy: function (tableId) { - var currentId = $.common.isEmpty(tableId) ? table.options.id : tableId; - $("#" + currentId).bootstrapTable('destroy'); - delete table.rememberSelectedIds[currentId]; - delete table.rememberSelecteds[currentId]; + var currentId = $.common.isEmpty(tableId) ? table.options.id : tableId; + $("#" + currentId).bootstrapTable('destroy'); + delete table.rememberSelectedIds[currentId]; + delete table.rememberSelecteds[currentId]; }, // 序列号生成 serialNumber: function (index, tableId) { @@ -353,10 +353,10 @@ var table = { table.options.formId = $.common.isEmpty(formId) ? $('form').attr('id') : formId; var params = $.common.isEmpty(tableId) ? $("#" + table.options.id).bootstrapTable('getOptions') : $("#" + tableId).bootstrapTable('getOptions'); if ($.common.isNotEmpty(pageNumber)) { - params.pageNumber = pageNumber; + params.pageNumber = pageNumber; } if ($.common.isNotEmpty(pageSize)) { - params.pageSize = pageSize; + params.pageSize = pageSize; } if($.common.isNotEmpty(tableId)){ $("#" + tableId).bootstrapTable('refresh', params); @@ -401,11 +401,11 @@ var table = { }, // 导入数据 importExcel: function(formId, width, height) { - table.set(); - var currentId = $.common.isEmpty(formId) ? 'importTpl' : formId; - var _width = $.common.isEmpty(width) ? "400" : width; + table.set(); + var currentId = $.common.isEmpty(formId) ? 'importTpl' : formId; + var _width = $.common.isEmpty(width) ? "400" : width; var _height = $.common.isEmpty(height) ? "230" : height; - layer.open({ + layer.open({ type: 1, area: [_width + 'px', _height + 'px'], fix: false, @@ -450,22 +450,22 @@ var table = { } }); } - }); + }); }, // 刷新表格 refresh: function(tableId, pageNumber, pageSize, url) { - var currentId = $.common.isEmpty(tableId) ? table.options.id : tableId; - var params = $("#" + currentId).bootstrapTable('getOptions'); - if ($.common.isEmpty(pageNumber)) { - pageNumber = params.pageNumber; - } - if ($.common.isEmpty(pageSize)) { - pageSize = params.pageSize; - } - if ($.common.isEmpty(url)) { - url = $.common.isEmpty(url) ? params.url : url; - } - $("#" + currentId).bootstrapTable('refresh', { + var currentId = $.common.isEmpty(tableId) ? table.options.id : tableId; + var params = $("#" + currentId).bootstrapTable('getOptions'); + if ($.common.isEmpty(pageNumber)) { + pageNumber = params.pageNumber; + } + if ($.common.isEmpty(pageSize)) { + pageSize = params.pageSize; + } + if ($.common.isEmpty(url)) { + url = $.common.isEmpty(url) ? params.url : url; + } + $("#" + currentId).bootstrapTable('refresh', { silent: true, url: url, pageNumber: pageNumber, @@ -477,59 +477,61 @@ var table = { var currentId = $.common.isEmpty(tableId) ? table.options.id : tableId; $("#" + currentId).bootstrapTable('refreshOptions', options); }, - // 查询表格指定列值 - selectColumns: function(column) { - var rows = $.map($("#" + table.options.id).bootstrapTable('getSelections'), function (row) { + // 查询表格指定列值 deDuplication( true去重、false不去重) + selectColumns: function(column, deDuplication) { + var distinct = $.common.isEmpty(deDuplication) ? true : deDuplication; + var rows = $.map($("#" + table.options.id).bootstrapTable('getSelections'), function (row) { return $.common.getItemField(row, column); - }); - if ($.common.isNotEmpty(table.options.rememberSelected) && table.options.rememberSelected) { - var selectedRows = table.rememberSelecteds[table.options.id]; - if($.common.isNotEmpty(selectedRows)) { - rows = $.map(table.rememberSelecteds[table.options.id], function (row) { - return $.common.getItemField(row, column); - }); - } - } - return $.common.uniqueFn(rows); + }); + if ($.common.isNotEmpty(table.options.rememberSelected) && table.options.rememberSelected) { + var selectedRows = table.rememberSelecteds[table.options.id]; + if($.common.isNotEmpty(selectedRows)) { + rows = $.map(table.rememberSelecteds[table.options.id], function (row) { + return $.common.getItemField(row, column); + }); + } + } + return distinct ? $.common.uniqueFn(rows) : rows; }, // 获取当前页选中或者取消的行ID affectedRowIds: function(rows) { - var column = $.common.isEmpty(table.options.uniqueId) ? table.options.columns[1].field : table.options.uniqueId; - var rowIds; - if ($.isArray(rows)) { - rowIds = $.map(rows, function(row) { - return $.common.getItemField(row, column); - }); - } else { - rowIds = [rows[column]]; - } - return rowIds; + var column = $.common.isEmpty(table.options.uniqueId) ? table.options.columns[1].field : table.options.uniqueId; + var rowIds; + if ($.isArray(rows)) { + rowIds = $.map(rows, function(row) { + return $.common.getItemField(row, column); + }); + } else { + rowIds = [rows[column]]; + } + return rowIds; }, - // 查询表格首列值 - selectFirstColumns: function() { - var rows = $.map($("#" + table.options.id).bootstrapTable('getSelections'), function (row) { + // 查询表格首列值deDuplication( true去重、false不去重) + selectFirstColumns: function(deDuplication) { + var distinct = $.common.isEmpty(deDuplication) ? true : deDuplication; + var rows = $.map($("#" + table.options.id).bootstrapTable('getSelections'), function (row) { return $.common.getItemField(row, table.options.columns[1].field); }); - if ($.common.isNotEmpty(table.options.rememberSelected) && table.options.rememberSelected) { + if ($.common.isNotEmpty(table.options.rememberSelected) && table.options.rememberSelected) { var selectedRows = table.rememberSelecteds[table.options.id]; if($.common.isNotEmpty(selectedRows)) { rows = $.map(selectedRows, function (row) { return $.common.getItemField(row, table.options.columns[1].field); }); } - } - return $.common.uniqueFn(rows); + } + return distinct ? $.common.uniqueFn(rows) : rows; }, // 回显数据字典 selectDictLabel: function(datas, value) { - if ($.common.isEmpty(datas) || $.common.isEmpty(value)) { - return ''; - } - var actions = []; + if ($.common.isEmpty(datas) || $.common.isEmpty(value)) { + return ''; + } + var actions = []; $.each(datas, function(index, dict) { if (dict.dictValue == ('' + value)) { - var listClass = $.common.equals("default", dict.listClass) || $.common.isEmpty(dict.listClass) ? "" : "badge badge-" + dict.listClass; - actions.push($.common.sprintf("%s", listClass, dict.dictLabel)); + var listClass = $.common.equals("default", dict.listClass) || $.common.isEmpty(dict.listClass) ? "" : "badge badge-" + dict.listClass; + actions.push($.common.sprintf("%s", listClass, dict.dictLabel)); return false; } }); @@ -537,11 +539,11 @@ var table = { }, // 回显数据字典(字符串数组) selectDictLabels: function(datas, value, separator) { - if ($.common.isEmpty(datas) || $.common.isEmpty(value)) { - return ''; - } - var currentSeparator = $.common.isEmpty(separator) ? "," : separator; - var actions = []; + if ($.common.isEmpty(datas) || $.common.isEmpty(value)) { + return ''; + } + var currentSeparator = $.common.isEmpty(separator) ? "," : separator; + var actions = []; $.each(value.split(currentSeparator), function(i, val) { $.each(datas, function(index, dict) { if (dict.dictValue == ('' + val)) { @@ -555,30 +557,30 @@ var table = { }, // 显示表格指定列 showColumn: function(column, tableId) { - var currentId = $.common.isEmpty(tableId) ? table.options.id : tableId; - $("#" + currentId).bootstrapTable('showColumn', column); + var currentId = $.common.isEmpty(tableId) ? table.options.id : tableId; + $("#" + currentId).bootstrapTable('showColumn', column); }, // 隐藏表格指定列 hideColumn: function(column, tableId) { - var currentId = $.common.isEmpty(tableId) ? table.options.id : tableId; - $("#" + currentId).bootstrapTable('hideColumn', column); + var currentId = $.common.isEmpty(tableId) ? table.options.id : tableId; + $("#" + currentId).bootstrapTable('hideColumn', column); }, // 显示所有表格列 showAllColumns: function(tableId) { - var currentId = $.common.isEmpty(tableId) ? table.options.id : tableId; - $("#" + currentId).bootstrapTable('showAllColumns'); + var currentId = $.common.isEmpty(tableId) ? table.options.id : tableId; + $("#" + currentId).bootstrapTable('showAllColumns'); }, // 隐藏所有表格列 hideAllColumns: function(tableId) { - var currentId = $.common.isEmpty(tableId) ? table.options.id : tableId; - $("#" + currentId).bootstrapTable('hideAllColumns'); + var currentId = $.common.isEmpty(tableId) ? table.options.id : tableId; + $("#" + currentId).bootstrapTable('hideAllColumns'); } }, // 表格树封装处理 treeTable: { // 初始化表格 init: function(options) { - var defaults = { + var defaults = { id: "bootstrap-tree-table", type: 1, // 0 代表bootstrapTable 1代表bootstrapTreeTable height: 0, @@ -593,10 +595,10 @@ var table = { expandAll: true, expandFirst: true }; - var options = $.extend(defaults, options); - table.options = options; - table.config[options.id] = options; - $.table.initEvent(); + var options = $.extend(defaults, options); + table.options = options; + table.config[options.id] = options; + $.table.initEvent(); $.bttTable = $('#' + options.id).bootstrapTreeTable({ code: options.code, // 用于设置父子关系 parentCode: options.parentCode, // 用于设置父子关系 @@ -622,27 +624,28 @@ var table = { }, // 条件查询 search: function(formId) { - var currentId = $.common.isEmpty(formId) ? $('form').attr('id') : formId; - var params = $.common.formToJSON(currentId); + var currentId = $.common.isEmpty(formId) ? $('form').attr('id') : formId; + var params = $.common.formToJSON(currentId); $.bttTable.bootstrapTreeTable('refresh', params); }, // 刷新 refresh: function() { - $.bttTable.bootstrapTreeTable('refresh'); + $.bttTable.bootstrapTreeTable('refresh'); }, - // 查询表格树指定列值 - selectColumns: function(column) { + // 查询表格树指定列值deDuplication( true去重、false不去重) + selectColumns: function(column, deDuplication) { + var distinct = $.common.isEmpty(deDuplication) ? true : deDuplication; var rows = $.map($.bttTable.bootstrapTreeTable('getSelections'), function (row) { return $.common.getItemField(row, column); }); - return $.common.uniqueFn(rows); + return distinct ? $.common.uniqueFn(rows) : rows; }, // 请求获取数据后处理回调函数,校验异常状态提醒 responseHandler: function(res) { - if (typeof table.options.responseHandler == "function") { + if (typeof table.options.responseHandler == "function") { table.options.responseHandler(res); } - if (res.code != undefined && res.code != web_status.SUCCESS) { + if (res.code != undefined && res.code != web_status.SUCCESS) { $.modal.alertWarning(res.msg); return []; } else { @@ -651,36 +654,36 @@ var table = { }, // 当所有数据被加载时触发 onLoadSuccess: function(data) { - if (typeof table.options.onLoadSuccess == "function") { + if (typeof table.options.onLoadSuccess == "function") { table.options.onLoadSuccess(data); - } - $(".table [data-toggle='tooltip']").tooltip(); + } + $(".table [data-toggle='tooltip']").tooltip(); }, }, // 表单封装处理 - form: { + form: { // 表单重置 reset: function(formId, tableId, pageNumber, pageSize) { table.set(tableId); - formId = $.common.isEmpty(formId) ? $('form').attr('id') : formId; - $("#" + formId)[0].reset(); - var tableId = $.common.isEmpty(tableId) ? table.options.id : tableId; - if (table.options.type == table_type.bootstrapTable) { - var params = $("#" + tableId).bootstrapTable('getOptions'); + formId = $.common.isEmpty(formId) ? $('form').attr('id') : formId; + $("#" + formId)[0].reset(); + var tableId = $.common.isEmpty(tableId) ? table.options.id : tableId; + if (table.options.type == table_type.bootstrapTable) { + var params = $("#" + tableId).bootstrapTable('getOptions'); if ($.common.isNotEmpty(pageNumber)) { - params.pageNumber = pageNumber; + params.pageNumber = pageNumber; } if ($.common.isNotEmpty(pageSize)) { - params.pageSize = pageSize; + params.pageSize = pageSize; } $("#" + tableId).bootstrapTable('refresh', params); - } else if (table.options.type == table_type.bootstrapTreeTable) { - $("#" + tableId).bootstrapTreeTable('refresh', []); - } + } else if (table.options.type == table_type.bootstrapTreeTable) { + $("#" + tableId).bootstrapTreeTable('refresh', []); + } }, // 获取选中复选框项 selectCheckeds: function(name) { - var checkeds = ""; + var checkeds = ""; $('input:checkbox[name="' + name + '"]:checked').each(function(i) { if (0 == i) { checkeds = $(this).val(); @@ -692,19 +695,19 @@ var table = { }, // 获取选中下拉框项 selectSelects: function(name) { - var selects = ""; - $('#' + name + ' option:selected').each(function (i) { - if (0 == i) { - selects = $(this).val(); - } else { - selects += ("," + $(this).val()); - } - }); - return selects; + var selects = ""; + $('#' + name + ' option:selected').each(function (i) { + if (0 == i) { + selects = $(this).val(); + } else { + selects += ("," + $(this).val()); + } + }); + return selects; } }, // 弹出层封装处理 - modal: { + modal: { // 显示图标 icon: function(type) { var icon = ""; @@ -721,7 +724,7 @@ var table = { }, // 消息提示 msg: function(content, type) { - if (type != undefined) { + if (type != undefined) { layer.msg(content, { icon: $.modal.icon(type), time: 1000, shift: 5 }); } else { layer.msg(content); @@ -729,15 +732,15 @@ var table = { }, // 错误消息 msgError: function(content) { - $.modal.msg(content, modal_status.FAIL); + $.modal.msg(content, modal_status.FAIL); }, // 成功消息 msgSuccess: function(content) { - $.modal.msg(content, modal_status.SUCCESS); + $.modal.msg(content, modal_status.SUCCESS); }, // 警告消息 msgWarning: function(content) { - $.modal.msg(content, modal_status.WARNING); + $.modal.msg(content, modal_status.WARNING); }, // 弹出提示 alert: function(content, type) { @@ -750,26 +753,26 @@ var table = { }, // 消息提示并刷新父窗体 msgReload: function(msg, type) { - layer.msg(msg, { - icon: $.modal.icon(type), - time: 500, - shade: [0.1, '#8F8F8F'] - }, - function() { - $.modal.reload(); - }); + layer.msg(msg, { + icon: $.modal.icon(type), + time: 500, + shade: [0.1, '#8F8F8F'] + }, + function() { + $.modal.reload(); + }); }, // 错误提示 alertError: function(content) { - $.modal.alert(content, modal_status.FAIL); + $.modal.alert(content, modal_status.FAIL); }, // 成功提示 alertSuccess: function(content) { - $.modal.alert(content, modal_status.SUCCESS); + $.modal.alert(content, modal_status.SUCCESS); }, // 警告提示 alertWarning: function(content) { - $.modal.alert(content, modal_status.WARNING); + $.modal.alert(content, modal_status.WARNING); }, // 关闭窗体 close: function (index) { @@ -797,22 +800,22 @@ var table = { }, // 弹出层指定宽度 open: function (title, url, width, height, callback) { - // 如果是移动端,就使用自适应大小弹窗 - if ($.common.isMobile()) { - width = 'auto'; - height = 'auto'; - } - if ($.common.isEmpty(title)) { + // 如果是移动端,就使用自适应大小弹窗 + if ($.common.isMobile()) { + width = 'auto'; + height = 'auto'; + } + if ($.common.isEmpty(title)) { title = false; } if ($.common.isEmpty(url)) { - url = "/404.html"; + url = "/404.html"; } if ($.common.isEmpty(width)) { - width = 800; + width = 800; } if ($.common.isEmpty(height)) { - height = ($(window).height() - 50); + height = ($(window).height() - 50); } if ($.common.isEmpty(callback)) { callback = function(index, layero) { @@ -820,36 +823,36 @@ var table = { iframeWin.contentWindow.submitHandler(index, layero); } } - layer.open({ - type: 2, - area: [width + 'px', height + 'px'], - fix: false, - //不固定 - maxmin: true, - shade: 0.3, - title: title, - content: url, - btn: ['确定', '关闭'], - // 弹层外区域关闭 - shadeClose: true, - yes: callback, - cancel: function(index) { - return true; - } - }); + layer.open({ + type: 2, + area: [width + 'px', height + 'px'], + fix: false, + //不固定 + maxmin: true, + shade: 0.3, + title: title, + content: url, + btn: ['确定', '关闭'], + // 弹层外区域关闭 + shadeClose: true, + yes: callback, + cancel: function(index) { + return true; + } + }); }, // 弹出层指定参数选项 openOptions: function (options) { - var _url = $.common.isEmpty(options.url) ? "/404.html" : options.url; - var _title = $.common.isEmpty(options.title) ? "系统窗口" : options.title; + var _url = $.common.isEmpty(options.url) ? "/404.html" : options.url; + var _title = $.common.isEmpty(options.title) ? "系统窗口" : options.title; var _width = $.common.isEmpty(options.width) ? "800" : options.width; var _height = $.common.isEmpty(options.height) ? ($(window).height() - 50) : options.height; var _btn = [' 确认', ' 关闭']; - // 如果是移动端,就使用自适应大小弹窗 - if ($.common.isMobile()) { - _width = 'auto'; - _height = 'auto'; - } + // 如果是移动端,就使用自适应大小弹窗 + if ($.common.isMobile()) { + _width = 'auto'; + _height = 'auto'; + } if ($.common.isEmpty(options.yes)) { options.yes = function(index, layero) { options.callBack(index, layero); @@ -886,22 +889,22 @@ var table = { }, // 弹出层全屏 openFull: function (title, url, width, height) { - // 如果是移动端,就使用自适应大小弹窗 - if ($.common.isMobile()) { - width = 'auto'; - height = 'auto'; - } - if ($.common.isEmpty(title)) { + // 如果是移动端,就使用自适应大小弹窗 + if ($.common.isMobile()) { + width = 'auto'; + height = 'auto'; + } + if ($.common.isEmpty(title)) { title = false; } if ($.common.isEmpty(url)) { url = "/404.html"; } if ($.common.isEmpty(width)) { - width = 800; + width = 800; } if ($.common.isEmpty(height)) { - height = ($(window).height() - 50); + height = ($(window).height() - 50); } var index = layer.open({ type: 2, @@ -922,46 +925,46 @@ var table = { cancel: function(index) { return true; } - }); + }); layer.full(index); }, // 选卡页方式打开 openTab: function (title, url, isRefresh) { - createMenuItem(url, title, isRefresh); + createMenuItem(url, title, isRefresh); }, // 选卡页同一页签打开 parentTab: function (title, url) { - var dataId = window.frameElement.getAttribute('data-id'); - createMenuItem(url, title); - closeItem(dataId); + var dataId = window.frameElement.getAttribute('data-id'); + createMenuItem(url, title); + closeItem(dataId); }, // 关闭选项卡 closeTab: function (dataId) { - closeItem(dataId); + closeItem(dataId); }, // 禁用按钮 disable: function() { - var doc = window.top == window.parent ? window.document : window.parent.document; - $("a[class*=layui-layer-btn]", doc).addClass("layer-disabled"); + var doc = window.top == window.parent ? window.document : window.parent.document; + $("a[class*=layui-layer-btn]", doc).addClass("layer-disabled"); }, // 启用按钮 enable: function() { - var doc = window.top == window.parent ? window.document : window.parent.document; - $("a[class*=layui-layer-btn]", doc).removeClass("layer-disabled"); + var doc = window.top == window.parent ? window.document : window.parent.document; + $("a[class*=layui-layer-btn]", doc).removeClass("layer-disabled"); }, // 打开遮罩层 loading: function (message) { - $.blockUI({ message: '
' + message + '
' }); + $.blockUI({ message: '
' + message + '
' }); }, // 关闭遮罩层 closeLoading: function () { - setTimeout(function(){ - $.unblockUI(); - }, 50); + setTimeout(function(){ + $.unblockUI(); + }, 50); }, // 重新加载 reload: function () { - parent.location.reload(); + parent.location.reload(); } }, // 操作封装处理 @@ -987,17 +990,17 @@ var table = { }, // post请求传输 post: function(url, data, callback) { - $.operate.submit(url, "post", "json", data, callback); + $.operate.submit(url, "post", "json", data, callback); }, // get请求传输 get: function(url, callback) { - $.operate.submit(url, "get", "json", "", callback); + $.operate.submit(url, "get", "json", "", callback); }, // 详细信息 detail: function(id, width, height) { - table.set(); - var _url = $.operate.detailUrl(id); - var options = { + table.set(); + var _url = $.operate.detailUrl(id); + var options = { title: table.options.modalName + "详细", width: width, height: height, @@ -1008,231 +1011,231 @@ var table = { layer.close(index); } }; - $.modal.openOptions(options); + $.modal.openOptions(options); }, // 详细信息,以tab页展现 detailTab: function(id) { - table.set(); - $.modal.openTab("详细" + table.options.modalName, $.operate.detailUrl(id)); + table.set(); + $.modal.openTab("详细" + table.options.modalName, $.operate.detailUrl(id)); }, // 详细访问地址 detailUrl: function(id) { - var url = "/404.html"; - if ($.common.isNotEmpty(id)) { - url = table.options.detailUrl.replace("{id}", id); - } else { - var id = $.common.isEmpty(table.options.uniqueId) ? $.table.selectFirstColumns() : $.table.selectColumns(table.options.uniqueId); - if (id.length == 0) { + var url = "/404.html"; + if ($.common.isNotEmpty(id)) { + url = table.options.detailUrl.replace("{id}", id); + } else { + var id = $.common.isEmpty(table.options.uniqueId) ? $.table.selectFirstColumns() : $.table.selectColumns(table.options.uniqueId); + if (id.length == 0) { $.modal.alertWarning("请至少选择一条记录"); return; - } - url = table.options.detailUrl.replace("{id}", id); - } + } + url = table.options.detailUrl.replace("{id}", id); + } return url; }, // 删除信息 remove: function(id) { - table.set(); - $.modal.confirm("确定删除该条" + table.options.modalName + "信息吗?", function() { + table.set(); + $.modal.confirm("确定删除该条" + table.options.modalName + "信息吗?", function() { var url = $.common.isEmpty(id) ? table.options.removeUrl : table.options.removeUrl.replace("{id}", id); if(table.options.type == table_type.bootstrapTreeTable) { - $.operate.get(url); + $.operate.get(url); } else { - var data = { "ids": id }; - $.operate.submit(url, "post", "json", data); + var data = { "ids": id }; + $.operate.submit(url, "post", "json", data); } - }); + }); }, // 批量删除信息 removeAll: function() { - table.set(); - var rows = $.common.isEmpty(table.options.uniqueId) ? $.table.selectFirstColumns() : $.table.selectColumns(table.options.uniqueId); - if (rows.length == 0) { - $.modal.alertWarning("请至少选择一条记录"); - return; - } - $.modal.confirm("确认要删除选中的" + rows.length + "条数据吗?", function() { - var url = table.options.removeUrl; - var data = { "ids": rows.join() }; - $.operate.submit(url, "post", "json", data); - }); + table.set(); + var rows = $.common.isEmpty(table.options.uniqueId) ? $.table.selectFirstColumns() : $.table.selectColumns(table.options.uniqueId); + if (rows.length == 0) { + $.modal.alertWarning("请至少选择一条记录"); + return; + } + $.modal.confirm("确认要删除选中的" + rows.length + "条数据吗?", function() { + var url = table.options.removeUrl; + var data = { "ids": rows.join() }; + $.operate.submit(url, "post", "json", data); + }); }, // 清空信息 clean: function() { - table.set(); - $.modal.confirm("确定清空所有" + table.options.modalName + "吗?", function() { - var url = table.options.cleanUrl; - $.operate.submit(url, "post", "json", ""); - }); + table.set(); + $.modal.confirm("确定清空所有" + table.options.modalName + "吗?", function() { + var url = table.options.cleanUrl; + $.operate.submit(url, "post", "json", ""); + }); }, // 添加信息 add: function(id) { - table.set(); - $.modal.open("添加" + table.options.modalName, $.operate.addUrl(id)); + table.set(); + $.modal.open("添加" + table.options.modalName, $.operate.addUrl(id)); }, // 添加信息,以tab页展现 addTab: function (id) { - table.set(); + table.set(); $.modal.openTab("添加" + table.options.modalName, $.operate.addUrl(id)); }, // 添加信息 全屏 addFull: function(id) { - table.set(); + table.set(); $.modal.openFull("添加" + table.options.modalName, $.operate.addUrl(id)); }, // 添加访问地址 addUrl: function(id) { - var url = $.common.isEmpty(id) ? table.options.createUrl.replace("{id}", "") : table.options.createUrl.replace("{id}", id); + var url = $.common.isEmpty(id) ? table.options.createUrl.replace("{id}", "") : table.options.createUrl.replace("{id}", id); return url; }, // 修改信息 edit: function(id) { - table.set(); - if($.common.isEmpty(id) && table.options.type == table_type.bootstrapTreeTable) { - var row = $("#" + table.options.id).bootstrapTreeTable('getSelections')[0]; - if ($.common.isEmpty(row)) { - $.modal.alertWarning("请至少选择一条记录"); - return; - } - var url = table.options.updateUrl.replace("{id}", row[table.options.uniqueId]); - $.modal.open("修改" + table.options.modalName, url); - } else { - $.modal.open("修改" + table.options.modalName, $.operate.editUrl(id)); - } + table.set(); + if($.common.isEmpty(id) && table.options.type == table_type.bootstrapTreeTable) { + var row = $("#" + table.options.id).bootstrapTreeTable('getSelections')[0]; + if ($.common.isEmpty(row)) { + $.modal.alertWarning("请至少选择一条记录"); + return; + } + var url = table.options.updateUrl.replace("{id}", row[table.options.uniqueId]); + $.modal.open("修改" + table.options.modalName, url); + } else { + $.modal.open("修改" + table.options.modalName, $.operate.editUrl(id)); + } }, // 修改信息,以tab页展现 editTab: function(id) { - table.set(); - $.modal.openTab("修改" + table.options.modalName, $.operate.editUrl(id)); + table.set(); + $.modal.openTab("修改" + table.options.modalName, $.operate.editUrl(id)); }, // 修改信息 全屏 editFull: function(id) { - table.set(); - var url = "/404.html"; - if ($.common.isNotEmpty(id)) { - url = table.options.updateUrl.replace("{id}", id); - } else { - if(table.options.type == table_type.bootstrapTreeTable) { - var row = $("#" + table.options.id).bootstrapTreeTable('getSelections')[0]; - if ($.common.isEmpty(row)) { - $.modal.alertWarning("请至少选择一条记录"); - return; - } - url = table.options.updateUrl.replace("{id}", row[table.options.uniqueId]); - } else { - var row = $.common.isEmpty(table.options.uniqueId) ? $.table.selectFirstColumns() : $.table.selectColumns(table.options.uniqueId); - url = table.options.updateUrl.replace("{id}", row); - } - } - $.modal.openFull("修改" + table.options.modalName, url); + table.set(); + var url = "/404.html"; + if ($.common.isNotEmpty(id)) { + url = table.options.updateUrl.replace("{id}", id); + } else { + if(table.options.type == table_type.bootstrapTreeTable) { + var row = $("#" + table.options.id).bootstrapTreeTable('getSelections')[0]; + if ($.common.isEmpty(row)) { + $.modal.alertWarning("请至少选择一条记录"); + return; + } + url = table.options.updateUrl.replace("{id}", row[table.options.uniqueId]); + } else { + var row = $.common.isEmpty(table.options.uniqueId) ? $.table.selectFirstColumns() : $.table.selectColumns(table.options.uniqueId); + url = table.options.updateUrl.replace("{id}", row); + } + } + $.modal.openFull("修改" + table.options.modalName, url); }, // 修改访问地址 editUrl: function(id) { - var url = "/404.html"; - if ($.common.isNotEmpty(id)) { - url = table.options.updateUrl.replace("{id}", id); - } else { - var id = $.common.isEmpty(table.options.uniqueId) ? $.table.selectFirstColumns() : $.table.selectColumns(table.options.uniqueId); - if (id.length == 0) { - $.modal.alertWarning("请至少选择一条记录"); - return; - } - url = table.options.updateUrl.replace("{id}", id); - } + var url = "/404.html"; + if ($.common.isNotEmpty(id)) { + url = table.options.updateUrl.replace("{id}", id); + } else { + var id = $.common.isEmpty(table.options.uniqueId) ? $.table.selectFirstColumns() : $.table.selectColumns(table.options.uniqueId); + if (id.length == 0) { + $.modal.alertWarning("请至少选择一条记录"); + return; + } + url = table.options.updateUrl.replace("{id}", id); + } return url; }, // 保存信息 刷新表格 save: function(url, data, callback) { - var config = { - url: url, - type: "post", - dataType: "json", - data: data, - beforeSend: function () { - $.modal.loading("正在处理中,请稍后..."); - $.modal.disable(); - }, - success: function(result) { - if (typeof callback == "function") { - callback(result); - } - $.operate.successCallback(result); - } - }; - $.ajax(config) + var config = { + url: url, + type: "post", + dataType: "json", + data: data, + beforeSend: function () { + $.modal.loading("正在处理中,请稍后..."); + $.modal.disable(); + }, + success: function(result) { + if (typeof callback == "function") { + callback(result); + } + $.operate.successCallback(result); + } + }; + $.ajax(config) }, // 保存信息 弹出提示框 saveModal: function(url, data, callback) { - var config = { - url: url, - type: "post", - dataType: "json", - data: data, - beforeSend: function () { - $.modal.loading("正在处理中,请稍后..."); - }, - success: function(result) { - if (typeof callback == "function") { - callback(result); - } - if (result.code == web_status.SUCCESS) { - $.modal.alertSuccess(result.msg) - } else if (result.code == web_status.WARNING) { - $.modal.alertWarning(result.msg) - } else { - $.modal.alertError(result.msg); - } - $.modal.closeLoading(); - } - }; - $.ajax(config) + var config = { + url: url, + type: "post", + dataType: "json", + data: data, + beforeSend: function () { + $.modal.loading("正在处理中,请稍后..."); + }, + success: function(result) { + if (typeof callback == "function") { + callback(result); + } + if (result.code == web_status.SUCCESS) { + $.modal.alertSuccess(result.msg) + } else if (result.code == web_status.WARNING) { + $.modal.alertWarning(result.msg) + } else { + $.modal.alertError(result.msg); + } + $.modal.closeLoading(); + } + }; + $.ajax(config) }, // 保存选项卡信息 saveTab: function(url, data, callback) { - var config = { - url: url, - type: "post", - dataType: "json", - data: data, - beforeSend: function () { - $.modal.loading("正在处理中,请稍后..."); - }, - success: function(result) { - if (typeof callback == "function") { - callback(result); - } - $.operate.successTabCallback(result); - } - }; - $.ajax(config) + var config = { + url: url, + type: "post", + dataType: "json", + data: data, + beforeSend: function () { + $.modal.loading("正在处理中,请稍后..."); + }, + success: function(result) { + if (typeof callback == "function") { + callback(result); + } + $.operate.successTabCallback(result); + } + }; + $.ajax(config) }, // 保存结果弹出msg刷新table表格 ajaxSuccess: function (result) { - if (result.code == web_status.SUCCESS && table.options.type == table_type.bootstrapTable) { - $.modal.msgSuccess(result.msg); - $.table.refresh(); + if (result.code == web_status.SUCCESS && table.options.type == table_type.bootstrapTable) { + $.modal.msgSuccess(result.msg); + $.table.refresh(); } else if (result.code == web_status.SUCCESS && table.options.type == table_type.bootstrapTreeTable) { - $.modal.msgSuccess(result.msg); - $.treeTable.refresh(); + $.modal.msgSuccess(result.msg); + $.treeTable.refresh(); } else if (result.code == web_status.SUCCESS && $.common.isEmpty(table.options.type)) { - $.modal.msgSuccess(result.msg) + $.modal.msgSuccess(result.msg) } else if (result.code == web_status.WARNING) { - $.modal.alertWarning(result.msg) + $.modal.alertWarning(result.msg) } else { - $.modal.alertError(result.msg); + $.modal.alertError(result.msg); } - $.modal.closeLoading(); + $.modal.closeLoading(); }, // 成功结果提示msg(父窗体全局更新) saveSuccess: function (result) { - if (result.code == web_status.SUCCESS) { - $.modal.msgReload("保存成功,正在刷新数据请稍后……", modal_status.SUCCESS); + if (result.code == web_status.SUCCESS) { + $.modal.msgReload("保存成功,正在刷新数据请稍后……", modal_status.SUCCESS); } else if (result.code == web_status.WARNING) { - $.modal.alertWarning(result.msg) + $.modal.alertWarning(result.msg) } else { - $.modal.alertError(result.msg); + $.modal.alertError(result.msg); } - $.modal.closeLoading(); + $.modal.closeLoading(); }, // 成功回调执行事件(父窗体静默更新) successCallback: function(result) { @@ -1260,18 +1263,18 @@ var table = { // 选项卡成功回调执行事件(父窗体静默更新) successTabCallback: function(result) { if (result.code == web_status.SUCCESS) { - var topWindow = $(window.parent.document); - var currentId = $('.page-tabs-content', topWindow).find('.active').attr('data-panel'); - var $contentWindow = $('.RuoYi_iframe[data-id="' + currentId + '"]', topWindow)[0].contentWindow; - $.modal.close(); - $contentWindow.$.modal.msgSuccess(result.msg); - $contentWindow.$(".layui-layer-padding").removeAttr("style"); - if ($contentWindow.table.options.type == table_type.bootstrapTable) { - $contentWindow.$.table.refresh(); - } else if ($contentWindow.table.options.type == table_type.bootstrapTreeTable) { - $contentWindow.$.treeTable.refresh(); - } - $.modal.closeTab(); + var topWindow = $(window.parent.document); + var currentId = $('.page-tabs-content', topWindow).find('.active').attr('data-panel'); + var $contentWindow = $('.RuoYi_iframe[data-id="' + currentId + '"]', topWindow)[0].contentWindow; + $.modal.close(); + $contentWindow.$.modal.msgSuccess(result.msg); + $contentWindow.$(".layui-layer-padding").removeAttr("style"); + if ($contentWindow.table.options.type == table_type.bootstrapTable) { + $contentWindow.$.table.refresh(); + } else if ($contentWindow.table.options.type == table_type.bootstrapTreeTable) { + $contentWindow.$.treeTable.refresh(); + } + $.modal.closeTab(); } else if (result.code == web_status.WARNING) { $.modal.alertWarning(result.msg) } else { @@ -1291,12 +1294,12 @@ var table = { }, // 表单验证 form: function (formId) { - var currentId = $.common.isEmpty(formId) ? $('form').attr('id') : formId; + var currentId = $.common.isEmpty(formId) ? $('form').attr('id') : formId; return $("#" + currentId).validate().form(); }, // 重置表单验证(清除提示信息) reset: function (formId) { - var currentId = $.common.isEmpty(formId) ? $('form').attr('id') : formId; + var currentId = $.common.isEmpty(formId) ? $('form').attr('id') : formId; return $("#" + currentId).validate().resetForm(); } }, @@ -1360,28 +1363,28 @@ var table = { }, // 搜索节点 searchNode: function() { - // 取得输入的关键字的值 - var value = $.common.trim($("#keyword").val()); - if ($.tree._lastValue == value) { - return; - } - // 保存最后一次搜索名称 - $.tree._lastValue = value; - var nodes = $._tree.getNodes(); - // 如果要查空字串,就退出不查了。 - if (value == "") { - $.tree.showAllNode(nodes); - return; - } - $.tree.hideAllNode(nodes); - // 根据搜索值模糊匹配 - $.tree.updateNodes($._tree.getNodesByParamFuzzy("name", value)); + // 取得输入的关键字的值 + var value = $.common.trim($("#keyword").val()); + if ($.tree._lastValue == value) { + return; + } + // 保存最后一次搜索名称 + $.tree._lastValue = value; + var nodes = $._tree.getNodes(); + // 如果要查空字串,就退出不查了。 + if (value == "") { + $.tree.showAllNode(nodes); + return; + } + $.tree.hideAllNode(nodes); + // 根据搜索值模糊匹配 + $.tree.updateNodes($._tree.getNodesByParamFuzzy("name", value)); }, // 根据Id和Name选中指定节点 selectByIdName: function(treeId, node) { - if ($.common.isNotEmpty(treeId) && node && treeId == node.id) { - $._tree.selectNode(node, true); - } + if ($.common.isNotEmpty(treeId) && node && treeId == node.id) { + $._tree.selectNode(node, true); + } }, // 显示所有节点 showAllNode: function(nodes) { @@ -1395,7 +1398,7 @@ var table = { $._tree.showNode(nodes[i]); $.tree.showAllNode(nodes[i].children); } - }, + }, // 隐藏所有节点 hideAllNode: function(nodes) { var tree = $.fn.zTree.getZTreeObj("tree"); @@ -1439,7 +1442,7 @@ var table = { return $.map(nodes, function (row) { return row[_column]; }).join(); - }, + }, // 不允许根父节点选择 notAllowParents: function(_tree) { var nodes = _tree.getSelectedNodes(); @@ -1487,8 +1490,8 @@ var table = { } }, // 通用方法封装处理 - common: { - // 判断字符串是否为空 + common: { + // 判断字符串是否为空 isEmpty: function (value) { if (value == null || this.trim(value) == "") { return true; @@ -1497,7 +1500,7 @@ var table = { }, // 判断一个字符串是否为非空串 isNotEmpty: function (value) { - return !$.common.isEmpty(value); + return !$.common.isEmpty(value); }, // 空对象转字符串 nullToStr: function(value) { @@ -1522,19 +1525,19 @@ var table = { }, // 比较两个字符串(大小写敏感) equals: function (str, that) { - return str == that; + return str == that; }, // 比较两个字符串(大小写不敏感) equalsIgnoreCase: function (str, that) { - return String(str).toUpperCase() === String(that).toUpperCase(); + return String(str).toUpperCase() === String(that).toUpperCase(); }, // 将字符串按指定字符分割 split: function (str, sep, maxLen) { - if ($.common.isEmpty(str)) { - return null; - } - var value = String(str).split(sep); - return maxLen ? value.slice(0, maxLen - 1) : value; + if ($.common.isEmpty(str)) { + return null; + } + var value = String(str).split(sep); + return maxLen ? value.slice(0, maxLen - 1) : value; }, // 字符串格式化(%s ) sprintf: function (str) { @@ -1622,31 +1625,31 @@ var table = { }, // 数组中的所有元素放入一个字符串 join: function(array, separator) { - if ($.common.isEmpty(array)) { - return null; - } + if ($.common.isEmpty(array)) { + return null; + } return array.join(separator); }, // 获取form下所有的字段并转换为json对象 formToJSON: function(formId) { - var json = {}; - $.each($("#" + formId).serializeArray(), function(i, field) { - if(json[field.name]) { - json[field.name] += ("," + field.value); - } else { - json[field.name] = field.value; - } - }); - return json; + var json = {}; + $.each($("#" + formId).serializeArray(), function(i, field) { + if(json[field.name]) { + json[field.name] += ("," + field.value); + } else { + json[field.name] = field.value; + } + }); + return json; }, // 数据字典转下拉框 dictToSelect: function(datas, value, name) { - var actions = []; - actions.push($.common.sprintf("", name)); $.each(datas, function(index, dict) { actions.push($.common.sprintf("", dict.dictLabel)); }); From 42c7c4fdc664830d97b240765c65199cc926323d Mon Sep 17 00:00:00 2001 From: RuoYi Date: Fri, 30 Jul 2021 10:20:01 +0800 Subject: [PATCH 08/28] =?UTF-8?q?=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E5=B1=8F=E8=94=BDhttp(s)=E8=BF=9C=E7=A8=8B=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ruoyi/common/utils/StringUtils.java | 23 +++++++++++++++++++ .../quartz/controller/SysJobController.java | 12 ++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java index 806e4ad9c..73a40cf07 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java @@ -324,6 +324,29 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils return list; } + /** + * 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写 + * + * @param cs 指定字符串 + * @param searchCharSequences 需要检查的字符串数组 + * @return 是否包含任意一个字符串 + */ + public static boolean containsAnyIgnoreCase(CharSequence cs, CharSequence... searchCharSequences) + { + if (isEmpty(cs) || isEmpty(searchCharSequences)) + { + return false; + } + for (CharSequence testStr : searchCharSequences) + { + if (containsIgnoreCase(cs, testStr)) + { + return true; + } + } + return false; + } + /** * 驼峰转下划线命名 */ diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobController.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobController.java index 6803c96fa..3fbfb252d 100644 --- a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobController.java +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobController.java @@ -137,7 +137,11 @@ public class SysJobController extends BaseController } else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI)) { - return AjaxResult.error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi://'调用"); + return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi://'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS })) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)//'调用"); } return toAjax(jobService.insertJob(job)); } @@ -167,7 +171,11 @@ public class SysJobController extends BaseController } else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI)) { - return AjaxResult.error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi://'调用"); + return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi://'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS })) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)//'调用"); } return toAjax(jobService.updateJob(job)); } From e0eac9fe2f6e4844f85b2ee6bbe5ea8d80dbd948 Mon Sep 17 00:00:00 2001 From: "bo.yang" Date: Fri, 30 Jul 2021 10:36:14 +0800 Subject: [PATCH 09/28] =?UTF-8?q?1=E3=80=81=E8=A7=A3=E5=86=B3WARN=E8=AD=A6?= =?UTF-8?q?=E5=91=8A=EF=BC=9A=20Unable=20to=20interpret=20the=20implicit?= =?UTF-8?q?=20parameter=20configuration=202=E3=80=81=E4=BF=AE=E6=94=B9Http?= =?UTF-8?q?Utils,sendPostWithRest=E6=96=B9=E6=B3=95=EF=BC=8C=E5=A6=82?= =?UTF-8?q?=E6=9E=9C=E5=8F=82=E6=95=B0=E4=B8=BAString=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=EF=BC=8C=E6=8E=A8=E9=80=81=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E4=BC=9A=E4=B9=B1=E7=A0=81=EF=BC=8C=E5=9B=A0?= =?UTF-8?q?=E6=AD=A4=E6=94=B9=E4=B8=BAObject=E7=B1=BB=E5=9E=8B=EF=BC=8C?= =?UTF-8?q?=E7=9B=B4=E6=8E=A5=E6=8E=A8=E9=80=81Map=203?= =?UTF-8?q?=E3=80=81=E5=AE=9E=E7=8E=B0=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E6=8E=A8=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/WechatApiController.java | 31 ++++ .../web/controller/tool/TestController.java | 12 +- .../ruoyi/common/utils/http/HttpUtils.java | 4 +- .../system/domain/WechatSendMessage.java | 111 +++++++++++++ .../ruoyi/system/domain/WechatUserInfo.java | 58 ++++--- .../system/service/IWechatApiService.java | 40 +++++ .../service/impl/WechatApiServiceImpl.java | 155 ++++++++++++++---- .../resources/mapper/system/SysUserMapper.xml | 3 + 8 files changed, 346 insertions(+), 68 deletions(-) create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/domain/WechatSendMessage.java diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/WechatApiController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/WechatApiController.java index 5898a1cfa..76d81a07d 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/WechatApiController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/WechatApiController.java @@ -3,6 +3,7 @@ package com.ruoyi.web.controller.system; import com.alibaba.fastjson.JSON; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.framework.shiro.util.CustToken; import com.ruoyi.system.service.IWechatApiService; @@ -21,6 +22,8 @@ import javax.servlet.http.HttpServletResponse; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; import java.util.Map; @Controller @@ -78,4 +81,32 @@ public class WechatApiController extends BaseController { } + @GetMapping("anon/SendTextMessageToWechatUser") + @ResponseBody + public Map SendTextMessageToWechatUser() { + List userIdList = new ArrayList<>(); + userIdList.add("2342343243"); + userIdList.add("erqrqwe"); + userIdList.add("359"); + Map resultMap = wechatApiService.SendTextMessageToWechatUser(userIdList,"哈哈哈!"); + return resultMap; + } + + @GetMapping("anon/SendTextCardMessageToWechatUser") + @ResponseBody + public Map SendTextCardMessageToWechatUser() { + List userIdList = new ArrayList<>(); + userIdList.add("23456667"); + userIdList.add("355354354"); + userIdList.add("359"); + userIdList.add("454"); + userIdList.add("408"); + String title="号外:特大优惠!限时抢购"; + String description="今年仅此一次,苹果手机1000元起!欢迎前来购买!走过路过,不要错过!"; + String dtailUrl="https://item.jd.com/100008348530.html"; + + Map resultMap = wechatApiService.SendTextCardMessageToWechatUser(userIdList,title,description,dtailUrl); + return resultMap; + } + } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/TestController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/TestController.java index 4588a7a53..b609defb0 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/TestController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/TestController.java @@ -47,7 +47,7 @@ public class TestController extends BaseController } @ApiOperation("获取用户详细") - @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path") + @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path",dataTypeClass = Integer.class ) @GetMapping("/{userId}") public AjaxResult getUser(@PathVariable Integer userId) { @@ -63,10 +63,10 @@ public class TestController extends BaseController @ApiOperation("新增用户") @ApiImplicitParams({ - @ApiImplicitParam(name = "userId", value = "用户id", dataType = "Integer"), - @ApiImplicitParam(name = "username", value = "用户名称", dataType = "String"), - @ApiImplicitParam(name = "password", value = "用户密码", dataType = "String"), - @ApiImplicitParam(name = "mobile", value = "用户手机", dataType = "String") + @ApiImplicitParam(name = "userId", value = "用户id", dataType = "Integer", dataTypeClass = Integer.class), + @ApiImplicitParam(name = "username", value = "用户名称", dataType = "String", dataTypeClass = String.class), + @ApiImplicitParam(name = "password", value = "用户密码", dataType = "String", dataTypeClass = String.class), + @ApiImplicitParam(name = "mobile", value = "用户手机", dataType = "String", dataTypeClass = String.class) }) @PostMapping("/save") public AjaxResult save(UserEntity user) @@ -95,7 +95,7 @@ public class TestController extends BaseController } @ApiOperation("删除用户信息") - @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path") + @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class) @DeleteMapping("/{userId}") public AjaxResult delete(@PathVariable Integer userId) { diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java index 8093b0a7f..11c8404b7 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java @@ -272,7 +272,9 @@ public class HttpUtils * @param params 请求参数,请求参数为json的形式。例:params="{\"params\":{\"pagesize\":1000}}" * @return 返回Map, Key="statusCode",接口访问返回状态, key="result":接口返回接果 */ - public static Map sendPostWithRest(String url, String params){ + //public static Map sendPostWithRest(String url, String params){ + //如果参数为String类型,推送企业微信消息会乱码,因此改为Object类型,直接推送Map --yangbo 20210729 + public static Map sendPostWithRest(String url, Object params){ RestTemplate restTemplate=new RestTemplate(); ResponseEntity result=null; int statusCode=0; diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/WechatSendMessage.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/WechatSendMessage.java new file mode 100644 index 000000000..23d504c9c --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/WechatSendMessage.java @@ -0,0 +1,111 @@ +package com.ruoyi.system.domain; + +public class WechatSendMessage { + public String touser; + public String toparty; + public String totag; + public String msgtype; + public Integer agentid; + public String text; + public Integer safe; + public Integer enable_id_trans; + public Integer enable_duplicate_check; + public Integer duplicate_check_interval; + + public String getTouser() { + return touser; + } + + public void setTouser(String touser) { + this.touser = touser; + } + + public String getToparty() { + return toparty; + } + + public void setToparty(String toparty) { + this.toparty = toparty; + } + + public String getTotag() { + return totag; + } + + public void setTotag(String totag) { + this.totag = totag; + } + + public Integer getAgentid() { + return agentid; + } + + public void setAgentid(Integer agentid) { + this.agentid = agentid; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public Integer getSafe() { + return safe; + } + + public void setSafe(Integer safe) { + this.safe = safe; + } + + public Integer getEnable_id_trans() { + return enable_id_trans; + } + + public void setEnable_id_trans(Integer enable_id_trans) { + this.enable_id_trans = enable_id_trans; + } + + public Integer getEnable_duplicate_check() { + return enable_duplicate_check; + } + + public void setEnable_duplicate_check(Integer enable_duplicate_check) { + this.enable_duplicate_check = enable_duplicate_check; + } + + public Integer getDuplicate_check_interval() { + return duplicate_check_interval; + } + + public void setDuplicate_check_interval(Integer duplicate_check_interval) { + this.duplicate_check_interval = duplicate_check_interval; + } + + public String getMsgtype() { + return msgtype; + } + + @Override + public String toString() { + return "WechatSendMessage{" + + "touser='" + touser + '\'' + + ", toparty='" + toparty + '\'' + + ", totag='" + totag + '\'' + + ", msgtype='" + msgtype + '\'' + + ", agentid=" + agentid + + ", text='" + text + '\'' + + ", safe=" + safe + + ", enable_id_trans=" + enable_id_trans + + ", enable_duplicate_check=" + enable_duplicate_check + + ", duplicate_check_interval=" + duplicate_check_interval + + '}'; + } + + public void setMsgtype(String msgtype) { + this.msgtype = msgtype; + } + +} \ No newline at end of file diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/WechatUserInfo.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/WechatUserInfo.java index b66f361d3..b29cdfdc5 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/domain/WechatUserInfo.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/WechatUserInfo.java @@ -3,36 +3,36 @@ package com.ruoyi.system.domain; import java.util.List; public class WechatUserInfo { - String errocode; //返回码 + Integer errcode; //返回码 String errmsg; //返回码描述 String Userid; //成员UserID String name; //成员名称 - String depatrment; //成员所属部门id列表 - String order; //部门内的排序值 + Object department; //成员所属部门id列表 + Object order; //部门内的排序值 String position; //职务信息 String mobile; //手机号码 String gender; //性别,0:未定义,1:男,2:女 String email; //邮箱 - String is_leader_in_dept; //在所在的部门内是否为上级 + Object is_leader_in_dept; //在所在的部门内是否为上级 String avatar; //头像Url String thumb_avatar; //头像缩略图Url String telephone; //座机 String alias; //别名 String address; //地址 String open_userid; //全局唯一id - String main_department; //主部门 - String extattr; //扩展属性 - String Status; //激活状态: 1=已激活,2=已禁用,4=未激活,5=退出企业。 + Integer main_department; //主部门 + Object extattr; //扩展属性 + Integer Status; //激活状态: 1=已激活,2=已禁用,4=未激活,5=退出企业。 String qr_code; //员工个人二维码 String external_position; // 对外职务 - String external_profile; //成员对外属性 + Object external_profile; //成员对外属性 - public String getErrocode() { - return errocode; + public Integer getErrcode() { + return errcode; } - public void setErrocode(String errocode) { - this.errocode = errocode; + public void setErrcode(Integer errcode) { + this.errcode = errcode; } public String getErrmsg() { @@ -59,19 +59,19 @@ public class WechatUserInfo { this.name = name; } - public String getDepatrment() { - return depatrment; + public Object getdepartment() { + return department; } - public void setDepatrment(String depatrment) { - this.depatrment = depatrment; + public void setdepartment(Object department) { + this.department = department; } - public String getOrder() { + public Object getOrder() { return order; } - public void setOrder(String order) { + public void setOrder(Object order) { this.order = order; } @@ -107,11 +107,11 @@ public class WechatUserInfo { this.email = email; } - public String getIs_leader_in_dept() { + public Object getIs_leader_in_dept() { return is_leader_in_dept; } - public void setIs_leader_in_dept(String is_leader_in_dept) { + public void setIs_leader_in_dept(Object is_leader_in_dept) { this.is_leader_in_dept = is_leader_in_dept; } @@ -163,27 +163,27 @@ public class WechatUserInfo { this.open_userid = open_userid; } - public String getMain_department() { + public Integer getMain_department() { return main_department; } - public void setMain_department(String main_department) { + public void setMain_department(Integer main_department) { this.main_department = main_department; } - public String getExtattr() { + public Object getExtattr() { return extattr; } - public void setExtattr(String extattr) { + public void setExtattr(Object extattr) { this.extattr = extattr; } - public String getStatus() { + public Integer getStatus() { return Status; } - public void setStatus(String status) { + public void setStatus(Integer status) { Status = status; } @@ -203,13 +203,11 @@ public class WechatUserInfo { this.external_position = external_position; } - public String getExternal_profile() { + public Object getExternal_profile() { return external_profile; } - public void setExternal_profile(String external_profile) { + public void setExternal_profile(Object external_profile) { this.external_profile = external_profile; } - - } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/IWechatApiService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/IWechatApiService.java index d4968517f..604043ee9 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/IWechatApiService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/IWechatApiService.java @@ -1,9 +1,49 @@ package com.ruoyi.system.service; +import com.ruoyi.system.domain.WechatSendMessage; +import com.ruoyi.system.domain.WechatUserInfo; + +import java.util.List; +import java.util.Map; + public interface IWechatApiService { //获取Access Token public String GetAccessToken(); //根据企业微信登录身份获取本地LoginName public String GetLoginNameWithWechatCode(String code); + + /** + * 根据企业微信登录链接的code用户获取userid; + * @param code + * @return userid + */ + public String GetUseridByWechatLogin(String code); + + /** + * 根据企业微信userid获取用户详细信息 + * @param userId + * @return 用户详细信息 + */ + public WechatUserInfo GetWechatUserInfoDetailByUserId(String userId); + + /** + * 推送text消息到企业微信用户 + * @param toUserList 发送的用户列表 + * @param message 发送的消息内容 + * @return 消息发送结果 + */ + public Map SendTextMessageToWechatUser(List toUserList, String message); + + /** + * 推送文本卡片消息到企业微信用户 + * description参数说明:支持使用br标签或者空格来进行换行处理,也支持使用div标签来使用不同的字体颜色,目前内置了3种文字颜色:灰色(gray)、高亮(highlight)、默认黑色(normal),将其作为div标签的class属性即可 + * 示例:"description" : "
2016年9月26日
恭喜你抽中iPhone 7一台,领奖码:xxxx
请于2016年10月10日前联系行政同事领取
" + * @param toUserList 发送的用户列表 + * @param title 标题 + * @param description 内容描述 + * @param detailUrl 点击详情的Url地址 + * @return 消息发送结果 + */ + public Map SendTextCardMessageToWechatUser(List toUserList, String title, String description, String detailUrl); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/WechatApiServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/WechatApiServiceImpl.java index cc936330a..714f4a9f4 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/WechatApiServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/WechatApiServiceImpl.java @@ -8,15 +8,17 @@ import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.http.HttpUtils; import com.ruoyi.system.domain.WechatAccessToken; +import com.ruoyi.system.domain.WechatSendMessage; +import com.ruoyi.system.domain.WechatUserInfo; import com.ruoyi.system.mapper.WechatAccessTokenMapper; import com.ruoyi.system.service.ISysUserService; import com.ruoyi.system.service.IWechatApiService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import org.springframework.web.bind.annotation.ResponseBody; -import java.util.Date; -import java.util.List; +import java.util.*; @Service public class WechatApiServiceImpl implements IWechatApiService { @@ -111,38 +113,19 @@ public class WechatApiServiceImpl implements IWechatApiService { public String GetLoginNameWithWechatCode(String code) { //获取访问用户身份ID - String url="https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo"; - String param = "access_token="+wechatApiService.GetAccessToken()+"&code="+code; - String userInfo = HttpUtils.sendGet(url,param); //测试已能正常返回UserInfo Json,正式使用时打开 - //String userInfo = "{\"UserId\":\"359\",\"DeviceId\":\"10000589102865WJ\",\"errcode\":0,\"errmsg\":\"ok\"}"; //为避免去微信获取code麻烦,开发调试时打开 - JSONObject jsonObjectUserInfo = JSONObject.parseObject(userInfo); - //如果返回码不为0,则输出错误信息,并返回空值 - if ( Integer.parseInt(jsonObjectUserInfo.getString("errcode")) != 0){ - System.out.println(jsonObjectUserInfo.getString("errmsg")); - return ""; - } - String userId = jsonObjectUserInfo.getString("UserId"); + String userId= GetUseridByWechatLogin(code); //获取用户邮箱与姓名 - url="https://qyapi.weixin.qq.com/cgi-bin/user/get"; - param="access_token="+wechatApiService.GetAccessToken()+"&userid="+userId; - String userInfoDetail=HttpUtils.sendGet(url,param); //获取成员信息 - JSONObject jsonObjectUserInfoDetail=JSONObject.parseObject(userInfoDetail); - //如果返回码不为0,则返回错误信息 - if(Integer.parseInt(jsonObjectUserInfoDetail.getString("errcode")) != 0) - { - System.out.println(jsonObjectUserInfo.getString("errmsg")); - return ""; - } - String userEmail= jsonObjectUserInfoDetail.getString("email"); - String userName= jsonObjectUserInfoDetail.getString("name"); + WechatUserInfo wechatUserInfo = GetWechatUserInfoDetailByUserId(userId); - //根据邮箱名+用户名匹配本地用户对应的邮箱名与用户名 - SysUser sysUser=new SysUser(); - sysUser.setUserName(userName); - sysUser.setEmail(userEmail); - sysUser.setUserType("02"); //只获取从OA同步的用户,保持与企业微信一致。 - List userList= userService.selectUserLists(sysUser); + //根据用户id+邮箱名+用户名匹配本地用户对应的userId+邮箱名与用户名 + SysUser sysUserWechat=new SysUser(); + sysUserWechat.setUserId(Long.parseLong(wechatUserInfo.getUserid())); + sysUserWechat.setUserName(wechatUserInfo.getName()); + sysUserWechat.setEmail(wechatUserInfo.getEmail()); + sysUserWechat.setUserType("02"); //只获取从OA同步的用户,保持与企业微信一致。 + + List userList= userService.selectUserLists(sysUserWechat); int count= userList.size(); if(count <= 0){ return ""; //系统里没有用户,没有从OA同步? 处理逻辑待定 @@ -155,4 +138,114 @@ public class WechatApiServiceImpl implements IWechatApiService { } + /** + * 根据企业微信登录链接的code用户获取userid; + * + * @param code + * @return userid + */ + @Override + public String GetUseridByWechatLogin(String code) { + String url="https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo"; + String param = "access_token="+wechatApiService.GetAccessToken()+"&code="+code; + //String userInfo = HttpUtils.sendGet(url,param); //测试已能正常返回UserInfo Json,正式使用时打开 + String userInfo = "{\"UserId\":\"359\",\"DeviceId\":\"10000589102865WJ\",\"errcode\":0,\"errmsg\":\"ok\"}"; //为避免去微信获取code麻烦,开发调试时打开 + JSONObject jsonObjectUserInfo = JSONObject.parseObject(userInfo); + //如果返回码不为0,则输出错误信息,并返回空值 + if ( Integer.parseInt(jsonObjectUserInfo.getString("errcode")) != 0){ + System.out.println(jsonObjectUserInfo.getString("errmsg")); + return ""; + } + return jsonObjectUserInfo.getString("UserId"); + } + + /** + * 根据企业微信userid获取用户详细信息 + * + * @param userId + * @return 用户详细信息 + */ + @Override + public WechatUserInfo GetWechatUserInfoDetailByUserId(String userId) { + String url="https://qyapi.weixin.qq.com/cgi-bin/user/get"; + String param="access_token="+wechatApiService.GetAccessToken()+"&userid="+userId; + String userInfoDetail=HttpUtils.sendGet(url,param); //获取成员信息 + WechatUserInfo wechatUserInfo = JSONObject.parseObject(userInfoDetail,WechatUserInfo.class); + //如果返回码不为0,则返回空,显示错误信息 + if (wechatUserInfo.getErrcode() !=0) + { + System.out.println(wechatUserInfo.getErrmsg()); + return null; + } + return wechatUserInfo; + } + + private String CovertListToWechatTouserFormat(List toUserList){ + StringBuilder toUser = new StringBuilder(); + for(String user:toUserList){ + toUser.append(user); + if(toUserList.indexOf(user) < toUserList.size()-1){ + toUser.append("|"); + } + } + return toUser.toString(); + } + + /** + * 推送text消息到企业微信用户 + * @param toUserList 发送的用户列表 + * @param message 发送的消息内容 + * @return 消息发送结果 + */ + @Override + public Map SendTextMessageToWechatUser(List toUserList,String message) { + Map resultMap; + Map param = new HashMap<>(16); + param.put("touser", CovertListToWechatTouserFormat(toUserList)); + param.put("msgtype", "text"); + param.put("agentid", agentId); + + Map text = new HashMap<>(16); + text.put("content", message); + param.put("text", text); + + String url="https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token="+wechatApiService.GetAccessToken(); + + //param参数需要直接使用对象,而不能转换成json字符串,否则推送到企业微信中文消息乱码。 + resultMap =HttpUtils.sendPostWithRest(url,param); + return resultMap; + } + + /** + * 推送文本卡片消息到企业微信用户 + * description参数说明:支持使用br标签或者空格来进行换行处理,也支持使用div标签来使用不同的字体颜色,目前内置了3种文字颜色:灰色(gray)、高亮(highlight)、默认黑色(normal),将其作为div标签的class属性即可 + * 示例:"description" : "
2016年9月26日
恭喜你抽中iPhone 7一台,领奖码:xxxx
请于2016年10月10日前联系行政同事领取
" + * + * @param toUserList 发送的用户列表 + * @param title 标题 + * @param description 内容描述 + * @param detailUrl 点击详情的Url地址 + * @return 消息发送结果 + */ + @Override + public Map SendTextCardMessageToWechatUser(List toUserList, String title, String description, String detailUrl) { + Map resultMap; + Map param = new HashMap<>(16); + param.put("touser", CovertListToWechatTouserFormat(toUserList)); + param.put("msgtype", "textcard"); + param.put("agentid", agentId); + + Map textcard=new HashMap<>(); + textcard.put("title",title); + textcard.put("description",description); + textcard.put("url",detailUrl); + param.put("textcard",textcard); + + String url="https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token="+wechatApiService.GetAccessToken(); + + //param参数需要直接使用对象,而不能转换成json字符串,否则推送到企业微信中文消息乱码。 + resultMap =HttpUtils.sendPostWithRest(url,param); + return resultMap; + } + } diff --git a/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml index 827c417b5..9d75a150a 100644 --- a/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml @@ -231,6 +231,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" select user_id, dept_id, login_name, user_name, user_type, email, avatar, phonenumber, password, sex, salt, status, del_flag, login_ip, login_date, create_by, create_time, remark from sys_user where del_flag = '0' - + AND user_id = #{userId} From df712bf2616b8108d16f9b6989f5d8aac399422c Mon Sep 17 00:00:00 2001 From: 18326186802 <862709625@qq.com> Date: Thu, 29 Jul 2021 11:31:32 +0800 Subject: [PATCH 11/28] =?UTF-8?q?=E5=BD=93=E8=B5=84=E6=BA=90=E5=BA=93?= =?UTF-8?q?=E5=90=8C=E6=97=B6=E5=AD=98=E5=9C=A8trans=E5=92=8Cjob=E6=97=B6:?= =?UTF-8?q?=E4=BC=98=E5=8C=96:=E6=96=B0=E5=BB=BA=E8=BD=AC=E6=8D=A2?= =?UTF-8?q?=E7=9A=84=E6=97=B6=E5=80=99=E5=8F=AA=E8=83=BD=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E8=BD=AC=E6=8D=A2,=E6=96=B0=E5=BB=BAjob=E7=9A=84=E6=97=B6?= =?UTF-8?q?=E5=80=99=E5=8F=AA=E8=83=BD=E9=80=89=E6=8B=A9job?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kettle/controller/XRepositoryController.java | 16 ++++++++-------- .../java/com/ruoyi/kettle/repo/XRepoManager.java | 12 ++++++------ .../kettle/service/IXRepositoryService.java | 2 +- .../service/impl/KettleTransServiceImpl.java | 8 +++++++- .../service/impl/XRepositoryServiceImpl.java | 9 ++++----- .../java/com/ruoyi/kettle/tools/KettleUtil.java | 6 ++---- .../templates/kettle/common/repository_tree.html | 7 +++---- .../main/resources/templates/kettle/job/add.html | 3 ++- .../resources/templates/kettle/trans/add.html | 3 ++- 9 files changed, 35 insertions(+), 31 deletions(-) diff --git a/bps-kettle/src/main/java/com/ruoyi/kettle/controller/XRepositoryController.java b/bps-kettle/src/main/java/com/ruoyi/kettle/controller/XRepositoryController.java index 56be3d8c6..cb8f61be1 100644 --- a/bps-kettle/src/main/java/com/ruoyi/kettle/controller/XRepositoryController.java +++ b/bps-kettle/src/main/java/com/ruoyi/kettle/controller/XRepositoryController.java @@ -57,15 +57,15 @@ public class XRepositoryController extends BaseController List list = xRepositoryService.selectXRepositoryList(xRepository); return getDataTable(list); } - @GetMapping(value = { "/selectRepositoryTree", "/selectRepositoryTree/{excludeId}" }) - public String selectRepositoryTree( @PathVariable(value = "excludeId", required = false) String excludeId, ModelMap mmap) + @GetMapping(value = { "/selectRepositoryTree", "/selectRepositoryTree/{type}" }) + public String selectRepositoryTree( @PathVariable(value = "type", required = false) String type, ModelMap mmap) { XRepository r=new XRepository(); List repoTree = xRepositoryService.selectXRepositoryList(r); - XRepository repository=xRepositoryService.selectXRepositoryById(2L); - mmap.put("repository", repository); + //XRepository repository=xRepositoryService.selectXRepositoryById(2L); + //mmap.put("repository", repository); mmap.put("repoTree", repoTree); - mmap.put("excludeId", excludeId); + mmap.put("type", type); return "kettle/common/repository_tree"; } @GetMapping("/repositoryRoot") @@ -75,10 +75,10 @@ public class XRepositoryController extends BaseController List ztrees = xRepositoryService.selectRepoRoot(new XRepository()); return ztrees; } - @PostMapping("/qryRepoSubTree/{id}") + @PostMapping("/qryRepoSubTree/{id}/{type}") @ResponseBody - public List qryRepoSubTree(@PathVariable("id") Long id, ModelMap mmapy) { - List ztrees = xRepositoryService.selectRepoTree(id); + public List qryRepoSubTree(@PathVariable("id") Long id,@PathVariable("type") String type, ModelMap mmapy) { + List ztrees = xRepositoryService.selectRepoTree(id,type); return ztrees; } /** diff --git a/bps-kettle/src/main/java/com/ruoyi/kettle/repo/XRepoManager.java b/bps-kettle/src/main/java/com/ruoyi/kettle/repo/XRepoManager.java index 9775c6d77..ad01cc53b 100644 --- a/bps-kettle/src/main/java/com/ruoyi/kettle/repo/XRepoManager.java +++ b/bps-kettle/src/main/java/com/ruoyi/kettle/repo/XRepoManager.java @@ -26,15 +26,15 @@ public class XRepoManager { public XRepoManager() { } - public static List getAllDirectoryTreeList(String repoId, Repository repository, String path, List allRepositoryTreeList) throws KettleException { - List repositoryTreeList = getJobAndTrans(repoId, repository, path); + public static List getAllDirectoryTreeList(String repoId, Repository repository, String path, List allRepositoryTreeList,String type) throws KettleException { + List repositoryTreeList = getJobAndTrans(repoId, repository, path,type); if (repositoryTreeList.size() != 0) { Iterator var5 = repositoryTreeList.iterator(); while(var5.hasNext()) { RepositoryTree repositoryTree = (RepositoryTree)var5.next(); if (!repositoryTree.isLasted()) { - getAllDirectoryTreeList(repoId, repository, repositoryTree.getPath(), allRepositoryTreeList); + getAllDirectoryTreeList(repoId, repository, repositoryTree.getPath(), allRepositoryTreeList,type); allRepositoryTreeList.add(repositoryTree); } else { allRepositoryTreeList.add(repositoryTree); @@ -44,7 +44,7 @@ public class XRepoManager { return allRepositoryTreeList; } - public static List getJobAndTrans(String repoId, Repository repository, String path) throws KettleException { + public static List getJobAndTrans(String repoId, Repository repository, String path,String type) throws KettleException { RepositoryDirectoryInterface rDirectory = repository.loadRepositoryDirectoryTree().findDirectory(path); List repositoryTreeList = getDirectory(repoId, repository, rDirectory); List li = repository.getJobAndTransformationObjects(rDirectory.getObjectId(), false); @@ -55,7 +55,7 @@ public class XRepoManager { RepositoryElementMetaInterface repel = (RepositoryElementMetaInterface)var6.next(); RepositoryTree repositoryTree; StringBuilder stringBuilder; - if ("job".equals(repel.getObjectType().toString())) { + if ("job".equals(repel.getObjectType().toString()) && type.equals("job")) { repositoryTree = new RepositoryTree(); stringBuilder = new StringBuilder(); stringBuilder.append("job").append(rDirectory.getObjectId().toString()).append("@").append(repel.getObjectId().toString()); @@ -71,7 +71,7 @@ public class XRepoManager { repositoryTree.setLasted(true); repositoryTree.setPath(repel.getRepositoryDirectory().getPath()); repositoryTreeList.add(repositoryTree); - } else if ("transformation".equals(repel.getObjectType().toString())) { + } else if ("transformation".equals(repel.getObjectType().toString())&& type.equals("trans")) { repositoryTree = new RepositoryTree(); stringBuilder = new StringBuilder(); stringBuilder.append("transformation").append(rDirectory.getObjectId().toString()).append("@").append(repel.getObjectId().toString()); diff --git a/bps-kettle/src/main/java/com/ruoyi/kettle/service/IXRepositoryService.java b/bps-kettle/src/main/java/com/ruoyi/kettle/service/IXRepositoryService.java index f8d8289f9..eca79054a 100644 --- a/bps-kettle/src/main/java/com/ruoyi/kettle/service/IXRepositoryService.java +++ b/bps-kettle/src/main/java/com/ruoyi/kettle/service/IXRepositoryService.java @@ -63,7 +63,7 @@ public interface IXRepositoryService */ public int deleteXRepositoryById(Long id); - List selectRepoTree(Long id); + List selectRepoTree(Long id,String type); List selectRepoRoot(XRepository repository); } diff --git a/bps-kettle/src/main/java/com/ruoyi/kettle/service/impl/KettleTransServiceImpl.java b/bps-kettle/src/main/java/com/ruoyi/kettle/service/impl/KettleTransServiceImpl.java index a856baf84..499236de8 100644 --- a/bps-kettle/src/main/java/com/ruoyi/kettle/service/impl/KettleTransServiceImpl.java +++ b/bps-kettle/src/main/java/com/ruoyi/kettle/service/impl/KettleTransServiceImpl.java @@ -177,7 +177,13 @@ public class KettleTransServiceImpl implements IKettleTransService return AjaxResult.success("执行成功!"); } - + /** + * @Description:查询抓换执行日志 + * @Author: Kone.wang + * @Date: 2021/7/28 16:24 + * @param kettleTrans: + * @return: java.util.List + **/ @Override public List queryTransLog(KettleTrans kettleTrans) { List transLogs=kettleTransMapper.queryTransLog(kettleTrans.getTransName()); diff --git a/bps-kettle/src/main/java/com/ruoyi/kettle/service/impl/XRepositoryServiceImpl.java b/bps-kettle/src/main/java/com/ruoyi/kettle/service/impl/XRepositoryServiceImpl.java index 28478b3c3..d103612c3 100644 --- a/bps-kettle/src/main/java/com/ruoyi/kettle/service/impl/XRepositoryServiceImpl.java +++ b/bps-kettle/src/main/java/com/ruoyi/kettle/service/impl/XRepositoryServiceImpl.java @@ -124,11 +124,10 @@ public class XRepositoryServiceImpl implements IXRepositoryService } @Override - public List selectRepoTree(Long id) { + public List selectRepoTree(Long id,String type) { XRepository xrs = xRepositoryMapper.selectXRepositoryById(id); - List repositoryTrees = getRepoTress(xrs); + List repositoryTrees = getRepoTress(xrs,type); List subTrees = new ArrayList<>(); - String type=null; String pId=String.valueOf(xrs.getId()); // try // { @@ -192,7 +191,7 @@ public class XRepositoryServiceImpl implements IXRepositoryService } return ztrees; } - private List getRepoTress(XRepository xr) { + private List getRepoTress(XRepository xr,String jobortrans) { List repositoryTrees = new ArrayList<>(); List xRepositoryList =xRepositoryMapper.selectXRepositoryList(xr); @@ -208,7 +207,7 @@ public class XRepositoryServiceImpl implements IXRepositoryService try { KettleFileRepository repository = (KettleFileRepository) KettleUtil_2. conFileRep(String.valueOf(item.getId()), item.getRepoName(), baseDir); - XRepoManager.getAllDirectoryTreeList(String.valueOf(item.getId()), repository, "/", tmpRepositoryList); + XRepoManager.getAllDirectoryTreeList(String.valueOf(item.getId()), repository, "/", tmpRepositoryList,jobortrans); if (tmpRepositoryList.size() > 0) { RepositoryDirectoryInterface rDirectory = repository.loadRepositoryDirectoryTree().findDirectory("/"); RepositoryTree repositoryTree = new RepositoryTree(); diff --git a/bps-kettle/src/main/java/com/ruoyi/kettle/tools/KettleUtil.java b/bps-kettle/src/main/java/com/ruoyi/kettle/tools/KettleUtil.java index 38a230e1e..c94960847 100644 --- a/bps-kettle/src/main/java/com/ruoyi/kettle/tools/KettleUtil.java +++ b/bps-kettle/src/main/java/com/ruoyi/kettle/tools/KettleUtil.java @@ -43,9 +43,7 @@ public class KettleUtil { public void callTrans(String transPath, String transName, Map namedParams, String[] clParams) throws Exception { KettleEnv.init(); DatabaseMeta databaseMeta=new DatabaseMeta("kettle_trans_log", "mysql", "Native(JDBC)", - "192.168.2.18","bps?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8", "3306", "root", "abc.123"); -// DatabaseMeta databaseMeta=new DatabaseMeta("kettle_trans_log", "mysql", "Native(JDBC)", -// "127.0.0.1","etl?useUniCode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=UTC","3306","root","2129"); + "xxx.xxx.x.xx","bps?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8", "3306", "root", "password"); String msg; KettleFileRepository repo = this.fileRepositoryCon(); @@ -100,7 +98,7 @@ public class KettleUtil { KettleEnv.init(); String msg; DatabaseMeta databaseMeta=new DatabaseMeta("kettle_job_log", "mysql", "Native(JDBC)", - "192.168.2.18","bps?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8", "3306", "root", "abc.123"); + "xxx.xxx.x.xx","bps?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8", "3306", "root", "password"); KettleFileRepository repo = this.fileRepositoryCon(); JobMeta jobMeta = this.loadJob(repo, jobPath, jobName); jobMeta.addDatabase(databaseMeta); diff --git a/bps-kettle/src/main/resources/templates/kettle/common/repository_tree.html b/bps-kettle/src/main/resources/templates/kettle/common/repository_tree.html index f8bf1f2d0..c3b637b95 100644 --- a/bps-kettle/src/main/resources/templates/kettle/common/repository_tree.html +++ b/bps-kettle/src/main/resources/templates/kettle/common/repository_tree.html @@ -32,9 +32,9 @@ diff --git a/ruoyi-admin/src/main/resources/templates/main.html b/ruoyi-admin/src/main/resources/templates/main.html index ef324c721..ab20e2681 100644 --- a/ruoyi-admin/src/main/resources/templates/main.html +++ b/ruoyi-admin/src/main/resources/templates/main.html @@ -9,10 +9,41 @@ + -
+
+
+

[[${wordsContent}]]

+
--  《[[${wordsOrigin}]]》 · [[${wordsAuthor}]]
+

+

[[${oneWordContent}]]

+
--  《[[${oneWordOrigin}]]》
+
+
+ + From 1710eeba9863f0743adfd70f1d3a950568b739ca Mon Sep 17 00:00:00 2001 From: RuoYi Date: Fri, 30 Jul 2021 21:10:17 +0800 Subject: [PATCH 15/28] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E7=94=9F=E6=88=90=E6=A8=A1=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ruoyi/generator/util/VelocityUtils.java | 3 ++- .../main/resources/vm/java/controller.java.vm | 10 ++++----- .../src/main/resources/vm/java/mapper.java.vm | 20 ++++++++--------- .../main/resources/vm/java/service.java.vm | 12 +++++----- .../resources/vm/java/serviceImpl.java.vm | 22 +++++++++---------- .../src/main/resources/vm/xml/mapper.xml.vm | 8 +++---- 6 files changed, 38 insertions(+), 37 deletions(-) diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java index 78297d2be..55a31f275 100644 --- a/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java @@ -303,7 +303,8 @@ public class VelocityUtils */ public static String getParentMenuId(JSONObject paramsObj) { - if (StringUtils.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID)) + if (StringUtils.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID) + && StringUtils.isNotEmpty(paramsObj.getString(GenConstants.PARENT_MENU_ID))) { return paramsObj.getString(GenConstants.PARENT_MENU_ID); } diff --git a/ruoyi-generator/src/main/resources/vm/java/controller.java.vm b/ruoyi-generator/src/main/resources/vm/java/controller.java.vm index aeb20b48e..c837ff8e5 100644 --- a/ruoyi-generator/src/main/resources/vm/java/controller.java.vm +++ b/ruoyi-generator/src/main/resources/vm/java/controller.java.vm @@ -105,7 +105,7 @@ public class ${ClassName}Controller extends BaseController { if (StringUtils.isNotNull(${pkColumn.javaField})) { - mmap.put("${className}", ${className}Service.select${ClassName}ById(${pkColumn.javaField})); + mmap.put("${className}", ${className}Service.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField})); } return prefix + "/add"; } @@ -129,7 +129,7 @@ public class ${ClassName}Controller extends BaseController @GetMapping("/edit/{${pkColumn.javaField}}") public String edit(@PathVariable("${pkColumn.javaField}") ${pkColumn.javaType} ${pkColumn.javaField}, ModelMap mmap) { - ${ClassName} ${className} = ${className}Service.select${ClassName}ById(${pkColumn.javaField}); + ${ClassName} ${className} = ${className}Service.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); mmap.put("${className}", ${className}); return prefix + "/edit"; } @@ -156,7 +156,7 @@ public class ${ClassName}Controller extends BaseController @ResponseBody public AjaxResult remove(String ids) { - return toAjax(${className}Service.delete${ClassName}ByIds(ids)); + return toAjax(${className}Service.delete${ClassName}By${pkColumn.capJavaField}s(ids)); } #elseif($table.tree) /** @@ -168,7 +168,7 @@ public class ${ClassName}Controller extends BaseController @ResponseBody public AjaxResult remove(@PathVariable("${pkColumn.javaField}") ${pkColumn.javaType} ${pkColumn.javaField}) { - return toAjax(${className}Service.delete${ClassName}ById(${pkColumn.javaField})); + return toAjax(${className}Service.delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField})); } #end #if($table.tree) @@ -182,7 +182,7 @@ public class ${ClassName}Controller extends BaseController { if (StringUtils.isNotNull(${pkColumn.javaField})) { - mmap.put("${className}", ${className}Service.select${ClassName}ById(${pkColumn.javaField})); + mmap.put("${className}", ${className}Service.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField})); } return prefix + "/tree"; } diff --git a/ruoyi-generator/src/main/resources/vm/java/mapper.java.vm b/ruoyi-generator/src/main/resources/vm/java/mapper.java.vm index 0c903051b..05df192e5 100644 --- a/ruoyi-generator/src/main/resources/vm/java/mapper.java.vm +++ b/ruoyi-generator/src/main/resources/vm/java/mapper.java.vm @@ -17,10 +17,10 @@ public interface ${ClassName}Mapper /** * 查询${functionName} * - * @param ${pkColumn.javaField} ${functionName}ID + * @param ${pkColumn.javaField} ${functionName}主键 * @return ${functionName} */ - public ${ClassName} select${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField}); + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); /** * 查询${functionName}列表 @@ -49,27 +49,27 @@ public interface ${ClassName}Mapper /** * 删除${functionName} * - * @param ${pkColumn.javaField} ${functionName}ID + * @param ${pkColumn.javaField} ${functionName}主键 * @return 结果 */ - public int delete${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField}); + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); /** * 批量删除${functionName} * - * @param ${pkColumn.javaField}s 需要删除的数据ID + * @param ${pkColumn.javaField}s 需要删除的数据主键集合 * @return 结果 */ - public int delete${ClassName}ByIds(String[] ${pkColumn.javaField}s); + public int delete${ClassName}By${pkColumn.capJavaField}s(String[] ${pkColumn.javaField}s); #if($table.sub) /** * 批量删除${subTable.functionName} * - * @param customerIds 需要删除的数据ID + * @param ${pkColumn.javaField}s 需要删除的数据主键集合 * @return 结果 */ - public int delete${subClassName}By${subTableFkClassName}s(String[] ${pkColumn.javaField}s); + public int delete${subClassName}By${pkColumn.capJavaField}s(String[] ${pkColumn.javaField}s); /** * 批量新增${subTable.functionName} @@ -81,11 +81,11 @@ public interface ${ClassName}Mapper /** - * 通过${functionName}ID删除${subTable.functionName}信息 + * 通过${functionName}主键删除${subTable.functionName}信息 * * @param ${pkColumn.javaField} ${functionName}ID * @return 结果 */ - public int delete${subClassName}By${subTableFkClassName}(${pkColumn.javaType} ${pkColumn.javaField}); + public int delete${subClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); #end } diff --git a/ruoyi-generator/src/main/resources/vm/java/service.java.vm b/ruoyi-generator/src/main/resources/vm/java/service.java.vm index e073cc82b..ab26c46f8 100644 --- a/ruoyi-generator/src/main/resources/vm/java/service.java.vm +++ b/ruoyi-generator/src/main/resources/vm/java/service.java.vm @@ -17,10 +17,10 @@ public interface I${ClassName}Service /** * 查询${functionName} * - * @param ${pkColumn.javaField} ${functionName}ID + * @param ${pkColumn.javaField} ${functionName}主键 * @return ${functionName} */ - public ${ClassName} select${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField}); + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); /** * 查询${functionName}列表 @@ -49,18 +49,18 @@ public interface I${ClassName}Service /** * 批量删除${functionName} * - * @param ids 需要删除的数据ID + * @param ${pkColumn.javaField}s 需要删除的数据主键集合 * @return 结果 */ - public int delete${ClassName}ByIds(String ids); + public int delete${ClassName}By${pkColumn.capJavaField}s(String ${pkColumn.javaField}s); /** * 删除${functionName}信息 * - * @param ${pkColumn.javaField} ${functionName}ID + * @param ${pkColumn.javaField} ${functionName}主键 * @return 结果 */ - public int delete${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField}); + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); #if($table.tree) /** diff --git a/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm b/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm index bb3553319..e7b7e32d3 100644 --- a/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm +++ b/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm @@ -39,13 +39,13 @@ public class ${ClassName}ServiceImpl implements I${ClassName}Service /** * 查询${functionName} * - * @param ${pkColumn.javaField} ${functionName}ID + * @param ${pkColumn.javaField} ${functionName}主键 * @return ${functionName} */ @Override - public ${ClassName} select${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField}) + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) { - return ${className}Mapper.select${ClassName}ById(${pkColumn.javaField}); + return ${className}Mapper.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); } /** @@ -113,34 +113,34 @@ public class ${ClassName}ServiceImpl implements I${ClassName}Service /** * 删除${functionName}对象 * - * @param ids 需要删除的数据ID + * @param ${pkColumn.javaField}s 需要删除的数据主键 * @return 结果 */ #if($table.sub) @Transactional #end @Override - public int delete${ClassName}ByIds(String ids) + public int delete${ClassName}By${pkColumn.capJavaField}s(String ${pkColumn.javaField}s) { #if($table.sub) - ${className}Mapper.delete${subClassName}By${subTableFkClassName}s(Convert.toStrArray(ids)); + ${className}Mapper.delete${subClassName}By${subTableFkClassName}s(Convert.toStrArray(${pkColumn.javaField}s)); #end - return ${className}Mapper.delete${ClassName}ByIds(Convert.toStrArray(ids)); + return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}s(Convert.toStrArray(${pkColumn.javaField}s)); } /** * 删除${functionName}信息 * - * @param ${pkColumn.javaField} ${functionName}ID + * @param ${pkColumn.javaField} ${functionName}主键 * @return 结果 */ @Override - public int delete${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField}) + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) { #if($table.sub) ${className}Mapper.delete${subClassName}By${subTableFkClassName}(${pkColumn.javaField}); #end - return ${className}Mapper.delete${ClassName}ById(${pkColumn.javaField}); + return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); } #if($table.tree) @@ -179,7 +179,7 @@ public class ${ClassName}ServiceImpl implements I${ClassName}Service public void insert${subClassName}(${ClassName} ${className}) { List<${subClassName}> ${subclassName}List = ${className}.get${subClassName}List(); - Long ${pkColumn.javaField} = ${className}.get${pkColumn.capJavaField}(); + ${pkColumn.javaType} ${pkColumn.javaField} = ${className}.get${pkColumn.capJavaField}(); if (StringUtils.isNotNull(${subclassName}List)) { List<${subClassName}> list = new ArrayList<${subClassName}>(); diff --git a/ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm b/ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm index c93aa0b0d..e1f086cff 100644 --- a/ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm +++ b/ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm @@ -64,7 +64,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #end - #if($table.crud) where ${pkColumn.columnName} = #{${pkColumn.javaField}} @@ -113,11 +113,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" where ${pkColumn.columnName} = #{${pkColumn.javaField}} - + delete from ${tableName} where ${pkColumn.columnName} = #{${pkColumn.javaField}} - + delete from ${tableName} where ${pkColumn.columnName} in #{${pkColumn.javaField}} @@ -132,7 +132,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - + delete from ${subTableName} where ${subTableFkName} = #{${subTableFkclassName}} From c7317ee5b2e852c75ea7575250de21bca94a5153 Mon Sep 17 00:00:00 2001 From: RuoYi Date: Fri, 30 Jul 2021 22:16:11 +0800 Subject: [PATCH 16/28] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E7=94=9F=E6=88=90=E6=A8=A1=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-generator/src/main/resources/vm/java/service.java.vm | 2 +- .../src/main/resources/vm/java/serviceImpl.java.vm | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ruoyi-generator/src/main/resources/vm/java/service.java.vm b/ruoyi-generator/src/main/resources/vm/java/service.java.vm index ab26c46f8..c30ec41ca 100644 --- a/ruoyi-generator/src/main/resources/vm/java/service.java.vm +++ b/ruoyi-generator/src/main/resources/vm/java/service.java.vm @@ -49,7 +49,7 @@ public interface I${ClassName}Service /** * 批量删除${functionName} * - * @param ${pkColumn.javaField}s 需要删除的数据主键集合 + * @param ${pkColumn.javaField}s 需要删除的${functionName}主键集合 * @return 结果 */ public int delete${ClassName}By${pkColumn.capJavaField}s(String ${pkColumn.javaField}s); diff --git a/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm b/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm index e7b7e32d3..c23c523a8 100644 --- a/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm +++ b/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm @@ -111,9 +111,9 @@ public class ${ClassName}ServiceImpl implements I${ClassName}Service } /** - * 删除${functionName}对象 + * 批量删除${functionName} * - * @param ${pkColumn.javaField}s 需要删除的数据主键 + * @param ${pkColumn.javaField}s 需要删除的${functionName}主键 * @return 结果 */ #if($table.sub) From a64d57531993e8b795a512c5c704f3bb74d74de5 Mon Sep 17 00:00:00 2001 From: RuoYi Date: Sat, 31 Jul 2021 17:17:53 +0800 Subject: [PATCH 17/28] =?UTF-8?q?=E5=8D=87=E7=BA=A7commons.io=E5=88=B0?= =?UTF-8?q?=E6=9C=80=E6=96=B0=E7=89=88=E6=9C=ACv2.11.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 62467346f..9a2278801 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ 1.2.76 5.8.0 5.8.0 - 2.10.0 + 2.11.0 1.4 4.1.2 1.7 From 0247a2d96543d617feb9eeeee1d7f31a75cb04ea Mon Sep 17 00:00:00 2001 From: RuoYi Date: Sat, 31 Jul 2021 17:44:11 +0800 Subject: [PATCH 18/28] =?UTF-8?q?=E5=AF=8C=E6=96=87=E6=9C=AC=E9=BB=98?= =?UTF-8?q?=E8=AE=A4dialogsInBody=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-admin/src/main/resources/templates/system/notice/edit.html | 1 + 1 file changed, 1 insertion(+) diff --git a/ruoyi-admin/src/main/resources/templates/system/notice/edit.html b/ruoyi-admin/src/main/resources/templates/system/notice/edit.html index 63a3f5e97..311d54bd6 100644 --- a/ruoyi-admin/src/main/resources/templates/system/notice/edit.html +++ b/ruoyi-admin/src/main/resources/templates/system/notice/edit.html @@ -51,6 +51,7 @@ height : 192, lang : 'zh-CN', followingToolbar: false, + dialogsInBody: true, callbacks: { onImageUpload: function (files) { sendFile(files[0], this); From 934b2854aa8fc0d3b10b31d55ca0473dedd87560 Mon Sep 17 00:00:00 2001 From: "bo.yang" Date: Sun, 1 Aug 2021 00:29:14 +0800 Subject: [PATCH 19/28] =?UTF-8?q?=E8=8E=B7=E5=8F=96=E4=BC=81=E4=B8=9A?= =?UTF-8?q?=E5=BE=AE=E4=BF=A1Token=E7=94=B1=E5=AD=98=E6=94=BE=E5=88=B0?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=EF=BC=8C=E6=94=B9=E4=B8=BA=E5=AD=98?= =?UTF-8?q?=E6=94=BE=E5=88=B0EHCache=E7=BC=93=E5=AD=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test/conrtroller/WechatApiController.java | 16 +++-- .../src/main/resources/templates/main.html | 4 +- .../service/impl/WechatApiServiceImpl.java | 72 ++++++++++++++++--- 3 files changed, 73 insertions(+), 19 deletions(-) diff --git a/box-test/src/main/java/com/ruoyi/test/conrtroller/WechatApiController.java b/box-test/src/main/java/com/ruoyi/test/conrtroller/WechatApiController.java index d5347d7d9..7439ebbbf 100644 --- a/box-test/src/main/java/com/ruoyi/test/conrtroller/WechatApiController.java +++ b/box-test/src/main/java/com/ruoyi/test/conrtroller/WechatApiController.java @@ -7,6 +7,7 @@ import com.ruoyi.system.service.IWechatApiService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.ServletException; @@ -24,13 +25,14 @@ public class WechatApiController extends BaseController { @Autowired IWechatApiService wechatApiService; - /* @RequestMapping("anon/getAccessToken") + @RequestMapping("anon/getAccessToken") + @ResponseBody public String getAccessToken() { return wechatApiService.GetAccessToken(); } - */ + @GetMapping("anon/userInfo") - public Map getJSON(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + public Map getJSON(HttpServletRequest request, HttpServletResponse response) throws IOException { BufferedReader streamReader = new BufferedReader( new InputStreamReader(request.getInputStream(), "UTF-8")); StringBuilder responseStrBuilder = new StringBuilder(); @@ -38,8 +40,8 @@ public class WechatApiController extends BaseController { while ((inputStr = streamReader.readLine()) != null) { responseStrBuilder.append(inputStr); } - Map params = JSON.parseObject(responseStrBuilder.toString(), Map.class); - return params; + return JSON.parseObject(responseStrBuilder.toString(), Map.class); + } @@ -51,7 +53,7 @@ public class WechatApiController extends BaseController { userIdList.add("erqrqwe");//错误userId示例 userIdList.add(""); //空UserId示例 userIdList.add("359"); - if(!ShiroUtils.getUserId().equals("359")){ + if(! String.valueOf(ShiroUtils.getUserId()).equals("359")){ userIdList.add(String.valueOf(ShiroUtils.getUserId())); } Map resultMap = wechatApiService.SendTextMessageToWechatUser(userIdList,"哈哈哈!"); @@ -68,7 +70,7 @@ public class WechatApiController extends BaseController { userIdList.add("359"); //userIdList.add("454"); //userIdList.add("408"); - if(!ShiroUtils.getUserId().equals("359")){ + if(!String.valueOf(ShiroUtils.getUserId()).equals("359")){ userIdList.add(String.valueOf(ShiroUtils.getUserId())); } String title="号外:特大优惠!限时抢购"; diff --git a/ruoyi-admin/src/main/resources/templates/main.html b/ruoyi-admin/src/main/resources/templates/main.html index ab20e2681..6ec038b68 100644 --- a/ruoyi-admin/src/main/resources/templates/main.html +++ b/ruoyi-admin/src/main/resources/templates/main.html @@ -4,7 +4,7 @@ - BPS后台管理系统介绍 + BPS后台管理系统 @@ -33,7 +33,7 @@ -
+

[[${wordsContent}]]

--  《[[${wordsOrigin}]]》 · [[${wordsAuthor}]]
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/WechatApiServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/WechatApiServiceImpl.java index 4be152a3c..52bad6719 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/WechatApiServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/WechatApiServiceImpl.java @@ -2,23 +2,25 @@ package com.ruoyi.system.service.impl; import com.alibaba.fastjson.JSONObject; import com.google.gson.Gson; -import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.utils.CacheUtils; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.http.HttpUtils; import com.ruoyi.system.domain.WechatAccessToken; -import com.ruoyi.system.domain.WechatSendMessage; import com.ruoyi.system.domain.WechatUserInfo; import com.ruoyi.system.mapper.WechatAccessTokenMapper; import com.ruoyi.system.service.ISysUserService; import com.ruoyi.system.service.IWechatApiService; +import org.apache.shiro.cache.Cache; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -import org.springframework.web.bind.annotation.ResponseBody; -import java.util.*; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; @Service public class WechatApiServiceImpl implements IWechatApiService { @@ -45,6 +47,31 @@ public class WechatApiServiceImpl implements IWechatApiService { */ @Override public String GetAccessToken() { + String cacheName = "wechatToke:" + corpId + "," + agentId; + + //获取缓存中accessToken与缓存的时间信息 + Cache cache = CacheUtils.getCache(cacheName); + String accessToken = (String) cache.get("accessToken"); + Date getTokenTime = ObjectToDate(cache.get("getTokenTime")); + Integer expires_in = ObjectToInteger(cache.get("expires_in")); + //如果没有获取到cache或者accessToken为空或者cache即将过期,则重新获取并返回新的accessToken; + if (StringUtils.isEmpty(accessToken) || null == getTokenTime ? true : (differenceSecond(DateUtils.getNowDate(), getTokenTime) + 1000 > expires_in ? true : false)) { + //清空wechatAccessTokenCache + CacheUtils.removeAll(cacheName); + //从企业微信获取新的accessToken + WechatAccessToken wechatAccessToken = getAccessTokenFromWechat(corpId, secret, agentId); + //将Token写入缓存 + CacheUtils.put(cacheName, "accessToken", wechatAccessToken.getAccess_token()); + CacheUtils.put(cacheName, "expires_in", wechatAccessToken.getExpires_in()); + CacheUtils.put(cacheName, "getTokenTime", DateUtils.getNowDate()); + //返回token + return wechatAccessToken.getAccess_token(); + } + return accessToken; + } + + /* + public String GetAccessToken() { //获取本地数据库中的Token WechatAccessToken wat = new WechatAccessToken(); wat.setCorpId(corpId); @@ -55,6 +82,7 @@ public class WechatApiServiceImpl implements IWechatApiService { if(list.isEmpty() || list.size() <=0) { returnWat= getAccessTokenFromWechat(corpId,secret,agentId); + //将accessToken写入数据库 wechatAccessTokenMapper.insertWechatAccessToken(returnWat); return returnWat.getAccess_token(); } @@ -83,6 +111,34 @@ public class WechatApiServiceImpl implements IWechatApiService { //如果以上情况皆不是,则返回本地数据库的token return list.get(0).getAccess_token(); + }*/ + + //将对象转化为日期,如果无法转换则返回空值,避免抛出异常。 + private Date ObjectToDate(Object obj){ + try { + return (Date) obj; + }catch (Exception e){ + return null; + } + } + + //将对象转化为Integer,如果对象为空或无法转换返回0, + private Integer ObjectToInteger(Object obj){ + //如果为空返回零 + if(null==obj){ + return 0; + } + try{ + return Integer.parseInt(obj.toString()); + }catch (Exception e){ + //发生异常返回0 + return 0; + } + } + + //获取相两个日期相差秒数 + private int differenceSecond(Date minuendDate, Date subtractionDate ) { + return (int)((minuendDate.getTime() - subtractionDate.getTime()) / 1000); } //根据corpId与corpSecret获取Token @@ -97,12 +153,6 @@ public class WechatApiServiceImpl implements IWechatApiService { return wechatAccessToken; } - //获取相两个日期相差秒数 - private int differenceSecond(Date minuendDate, Date subtractionDate ) { - return (int)((minuendDate.getTime() - subtractionDate.getTime()) / 1000); - } - - /** * 根据企业微信登录身份获取本地LoginName * @@ -201,6 +251,8 @@ public class WechatApiServiceImpl implements IWechatApiService { /** * 推送text消息到企业微信用户 + * 其中text参数的content字段可以支持换行、以及A标签,即可打开自定义的网页 + * 示例:"content" : "你的快递已到,请携带工卡前往邮件中心领取。\n出发前可查看邮件中心视频实况,聪明避开排队。" * @param toUserList 发送的用户列表 * @param message 发送的消息内容 * @return 消息发送结果 From 188a53bf7fff2ee1f31e1e9c21507acfe6c8aadb Mon Sep 17 00:00:00 2001 From: "bo.yang" Date: Mon, 2 Aug 2021 00:58:21 +0800 Subject: [PATCH 20/28] =?UTF-8?q?1=E3=80=81=E8=B0=83=E6=95=B4=E5=90=8C?= =?UTF-8?q?=E6=AD=A5OA=E4=BA=BA=E5=91=98=E3=80=81=E9=83=A8=E9=97=A8?= =?UTF-8?q?=E7=9A=84=E9=80=BB=E8=BE=91=202=E3=80=81=E4=BA=BA=E5=91=98?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E4=BF=9D=E7=95=99=E7=B3=BB=E7=BB=9F=E5=8E=9F?= =?UTF-8?q?=E6=9C=89=E5=A4=B4=E5=83=8F=EF=BC=8C=E5=AF=86=E7=A0=81=E7=AD=89?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=203=E3=80=81=E5=BD=93=E9=83=A8=E9=97=A8?= =?UTF-8?q?=E5=8F=82=E8=AE=BE=E7=BD=AE=E4=B8=BA=E4=BB=8EOA=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E6=97=B6=EF=BC=8C=E9=9A=90=E8=97=8F=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E3=80=81=E7=BC=96=E8=BE=91=E3=80=81=E5=88=A0=E9=99=A4=E6=8C=89?= =?UTF-8?q?=E9=92=AE=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GetEcologyInfoTestController.java | 18 +++----- .../controller/system/SysDeptController.java | 20 ++++----- .../controller/system/SysUserController.java | 19 ++++---- .../src/main/resources/templates/main.html | 2 +- .../resources/templates/system/dept/dept.html | 29 ++++++++++--- .../resources/templates/system/user/edit.html | 35 +++++++++------ .../resources/templates/system/user/user.html | 7 ++- .../ruoyi/system/service/ISysDeptService.java | 4 +- .../ruoyi/system/service/ISysUserService.java | 4 +- .../service/impl/SysDeptServiceImpl.java | 38 ++++++++++++++-- .../service/impl/SysUserServiceImpl.java | 43 +++++++++++++++++-- 11 files changed, 156 insertions(+), 63 deletions(-) diff --git a/box-test/src/main/java/com/ruoyi/test/conrtroller/GetEcologyInfoTestController.java b/box-test/src/main/java/com/ruoyi/test/conrtroller/GetEcologyInfoTestController.java index faf741d0d..0d52737dc 100644 --- a/box-test/src/main/java/com/ruoyi/test/conrtroller/GetEcologyInfoTestController.java +++ b/box-test/src/main/java/com/ruoyi/test/conrtroller/GetEcologyInfoTestController.java @@ -2,6 +2,7 @@ package com.ruoyi.test.conrtroller; import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.system.service.ISysDeptService; import com.ruoyi.system.service.ISysUserService; import org.springframework.beans.factory.annotation.Autowired; @@ -16,27 +17,20 @@ public class GetEcologyInfoTestController extends BaseController { private ISysUserService userService; @RequestMapping("/anon/getEcologyDept") - public String getEcologyDept() throws Exception { + public AjaxResult getEcologyDept() throws Exception { String url="http://192.168.2.85:90/api/hrm/resful/getHrmdepartmentWithPage"; String params="{\"params\":{\"pagesize\":999999}}"; //return sendPost(url,params); - int result = deptService.syncEcologyDept(url,params); - if(result==200){ - return "同步成功"; - } - return "同步失败"; + return deptService.syncEcologyDept(url,params); + } @RequestMapping("/anon/getEcologyUser") - public String getEcologyUser(){ + public AjaxResult getEcologyUser(){ String url="http://192.168.2.85:90/api/hrm/resful/getHrmUserInfoWithPage"; String params="{\"params\":{\"pagesize\":999999}}"; - int result = userService.syncEcologyUser(url,params); - if(result==200){ - return "同步成功"; - } - return "同步失败"; + return userService.syncEcologyUser(url,params); } /* *//*public Map sendPostWithRest(String url,String params){ diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java index e123bee99..f751e33b2 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java @@ -1,6 +1,8 @@ package com.ruoyi.web.controller.system; import java.util.List; + +import com.ruoyi.system.service.ISysConfigService; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; @@ -37,6 +39,9 @@ public class SysDeptController extends BaseController @Autowired private ISysDeptService deptService; + @Autowired + ISysConfigService configService; + @RequiresPermissions("system:dept:view") @GetMapping() public String dept() @@ -157,7 +162,7 @@ public class SysDeptController extends BaseController /** * 选择部门树 - * + * * @param deptId 部门ID * @param excludeId 排除ID */ @@ -212,14 +217,9 @@ public class SysDeptController extends BaseController @RequiresPermissions("system:dept:sync") @PostMapping("/syncDept") @ResponseBody - public AjaxResult syncDept() - { - String url="http://192.168.2.85:90/api/hrm/resful/getHrmdepartmentWithPage"; - String params="{\"params\":{\"pagesize\":999999}}"; - int result = deptService.syncEcologyDept(url,params); - if(result==200){ - return AjaxResult.success("同步Ecology部门成功,返回状态码:"+result); - } - return AjaxResult.error("同步Ecology部门失败,返回状态码:"+result); + public AjaxResult syncDept() { + String url = "http://192.168.2.85:90/api/hrm/resful/getHrmdepartmentWithPage"; + String params = "{\"params\":{\"pagesize\":999999}}"; + return deptService.syncEcologyDept(url, params); } } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java index 1a6a487fc..cd51e33ff 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java @@ -2,6 +2,8 @@ package com.ruoyi.web.controller.system; import java.util.List; import java.util.stream.Collectors; + +import com.ruoyi.system.service.ISysConfigService; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; @@ -52,6 +54,9 @@ public class SysUserController extends BaseController @Autowired private SysPasswordService passwordService; + @Autowired + private ISysConfigService configService; + @RequiresPermissions("system:user:view") @GetMapping() public String user() @@ -293,14 +298,10 @@ public class SysUserController extends BaseController @RequiresPermissions("system:user:sync") @PostMapping("/syncUser") @ResponseBody - public AjaxResult syncDept() - { - String url="http://192.168.2.85:90/api/hrm/resful/getHrmUserInfoWithPage"; - String params="{\"params\":{\"pagesize\":999999}}"; - int result = userService.syncEcologyUser(url,params); - if(result==200){ - return AjaxResult.success("Ecology人员同步成功,返回状态码:"+result); - } - return AjaxResult.error("Ecology人员同步失败,返回状态码:"+result); + public AjaxResult syncUser() { + String url = "http://192.168.2.85:90/api/hrm/resful/getHrmUserInfoWithPage"; + String params = "{\"params\":{\"pagesize\":999999}}"; + return userService.syncEcologyUser(url, params); } + } \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/main.html b/ruoyi-admin/src/main/resources/templates/main.html index 6ec038b68..3b5096ee6 100644 --- a/ruoyi-admin/src/main/resources/templates/main.html +++ b/ruoyi-admin/src/main/resources/templates/main.html @@ -33,7 +33,7 @@ -
+

[[${wordsContent}]]

--  《[[${wordsOrigin}]]》 · [[${wordsAuthor}]]
diff --git a/ruoyi-admin/src/main/resources/templates/system/dept/dept.html b/ruoyi-admin/src/main/resources/templates/system/dept/dept.html index 566ba20a8..4cc4b0948 100644 --- a/ruoyi-admin/src/main/resources/templates/system/dept/dept.html +++ b/ruoyi-admin/src/main/resources/templates/system/dept/dept.html @@ -35,7 +35,7 @@ 新增 - + 修改 @@ -96,6 +96,7 @@ { title: '操作', align: 'left', + visible: showAction(), formatter: function(value, row, index) { if (row.parentId != 0) { var actions = []; @@ -112,30 +113,44 @@ $.treeTable.init(options); }); + function showAction(){ + if(deptSyncType == "1"){ + return false; + } + return true; + } + + function editDept(){ + if(deptSyncType =="0") { + $.operate.edit(); + }else { + alert("已启用同步Ecology部门信息,禁止手动修改部门!") + } + } function addDept(){ if(deptSyncType =="0") { $.operate.add(999999); }else { - alert("系统参数已启用同步Ecology部门信息,禁止手动新增部门!") + alert("已启用同步Ecology部门信息,禁止手动新增部门!") } } function syncDept() { - if(deptSyncType =="1") { + /*if(deptSyncType =="1") {*/ $.ajax({ type: 'POST', url: ctx + "system/dept/syncDept", - data: JSON.stringify(""), //beauty是字符串 + data: JSON.stringify(""), contentType: "application/json", dataType: "json", success: function (message) { alert(JSON.stringify(message)); //将JSON对象转换为字符串 } }); - }else { - alert("系统参数未启用同步Ecology部门!") - } + /*}else { + alert("系统未启用同步Ecology部门!"); + }*/ }; diff --git a/ruoyi-admin/src/main/resources/templates/system/user/edit.html b/ruoyi-admin/src/main/resources/templates/system/user/edit.html index 3d598dbd4..afd2919ad 100644 --- a/ruoyi-admin/src/main/resources/templates/system/user/edit.html +++ b/ruoyi-admin/src/main/resources/templates/system/user/edit.html @@ -15,7 +15,7 @@
- +
@@ -37,7 +37,7 @@
- +
@@ -48,7 +48,7 @@
- +
@@ -91,8 +91,8 @@
- +
@@ -133,6 +133,7 @@ diff --git a/ruoyi-admin/src/main/resources/templates/system/user/user.html b/ruoyi-admin/src/main/resources/templates/system/user/user.html index f7a58d70d..10ae93007 100644 --- a/ruoyi-admin/src/main/resources/templates/system/user/user.html +++ b/ruoyi-admin/src/main/resources/templates/system/user/user.html @@ -96,6 +96,7 @@ var editFlag = [[${@permission.hasPermi('system:user:edit')}]]; var removeFlag = [[${@permission.hasPermi('system:user:remove')}]]; var resetPwdFlag = [[${@permission.hasPermi('system:user:resetPwd')}]]; + var userSyncType = [[${#strings.defaultString(@config.getKey('sys.user.sync'), 0)}]]; var prefix = ctx + "system/user"; $(function() { @@ -191,7 +192,7 @@ }; $.table.init(options); } - + function queryDeptTree() { var url = ctx + "system/dept/treeData"; @@ -267,6 +268,7 @@ } function syncUser() { + /*if(userSyncType=="1") {*/ $.ajax({ type: 'POST', url: ctx + "system/user/syncUser", @@ -277,6 +279,9 @@ alert(JSON.stringify(message)); //将JSON对象转换为字符串 } }); + /*}else { + alert("系统未启用同步Ecology部门!"); + }*/ }; diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDeptService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDeptService.java index 7deaf4055..258f75511 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDeptService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDeptService.java @@ -1,6 +1,8 @@ package com.ruoyi.system.service; import java.util.List; + +import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.Ztree; import com.ruoyi.common.core.domain.entity.SysDept; import com.ruoyi.common.core.domain.entity.SysRole; @@ -111,5 +113,5 @@ public interface ISysDeptService /** * Ecology部门信息同步 */ - public int syncEcologyDept(String url,String params); + public AjaxResult syncEcologyDept(String url, String params); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java index 0492ec362..e4c214331 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java @@ -1,6 +1,8 @@ package com.ruoyi.system.service; import java.util.List; + +import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.system.domain.SysUserRole; @@ -208,7 +210,7 @@ public interface ISysUserService /** * Ecology人员信息同步 */ - public int syncEcologyUser(String url,String params); + public AjaxResult syncEcologyUser(String url, String params); /** * 查询用户列表 diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java index 3a6bc28ba..1bcd2f9ff 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java @@ -4,16 +4,22 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.ruoyi.common.annotation.DataScope; import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.Ztree; import com.ruoyi.common.core.domain.entity.SysDept; import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.exception.BusinessException; +import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.http.HttpUtils; import com.ruoyi.system.domain.EcologyDept; import com.ruoyi.system.mapper.SysDeptMapper; +import com.ruoyi.system.service.ISysConfigService; import com.ruoyi.system.service.ISysDeptService; +import com.ruoyi.system.service.ISysUserService; +import com.ruoyi.system.service.IWechatApiService; import org.apache.commons.lang3.ArrayUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -35,6 +41,15 @@ public class SysDeptServiceImpl implements ISysDeptService @Autowired private SysDeptMapper deptMapper; + @Autowired + private ISysConfigService sysconfig; + + @Autowired + private IWechatApiService wechatApiService; + + @Autowired + ISysUserService userService; + /** * 查询部门管理数据 * @@ -66,7 +81,7 @@ public class SysDeptServiceImpl implements ISysDeptService /** * 查询部门管理树(排除下级) * - * @param deptId 部门ID + * @param dept 部门 * @return 所有部门信息 */ @Override @@ -320,9 +335,24 @@ public class SysDeptServiceImpl implements ISysDeptService * Ecology部门信息同步 */ @Override - public int syncEcologyDept(String url,String params) { - int result= deptSync(HttpUtils.sendPostWithRest(url,params)); - return result; + public AjaxResult syncEcologyDept(String url, String params) { + String msg="同步OA部门失败!"; + List userList=new ArrayList<>(); + userList.add(ShiroUtils.getLoginName().equals("admin")?"359":String.valueOf(ShiroUtils.getUserId())); + if( ! sysconfig.selectConfigByKey("sys.dept.sync").equals("1")){ + msg="OA部门同步失败!系统未开启OA部门同步!"; + wechatApiService.SendTextMessageToWechatUser(userList,msg); + return AjaxResult.success(msg,"false"); + } + int result =deptSync(HttpUtils.sendPostWithRest(url,params)); + if( result==200) + { + return AjaxResult.success("OA部门同步成功!",result); + } + + wechatApiService.SendTextMessageToWechatUser(userList,msg+"result:"+result); + return AjaxResult.error(msg,result); + } @SuppressWarnings("unchecked") diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java index eaf08e3ab..5045313b8 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java @@ -6,10 +6,12 @@ import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.ruoyi.common.annotation.DataScope; import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.exception.BusinessException; +import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.http.HttpUtils; import com.ruoyi.common.utils.security.Md5Utils; @@ -17,6 +19,7 @@ import com.ruoyi.system.domain.*; import com.ruoyi.system.mapper.*; import com.ruoyi.system.service.ISysConfigService; import com.ruoyi.system.service.ISysUserService; +import com.ruoyi.system.service.IWechatApiService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -56,6 +59,9 @@ public class SysUserServiceImpl implements ISysUserService @Autowired private ISysConfigService configService; + @Autowired + private IWechatApiService wechatApiService; + /** * 根据条件分页查询用户列表 * @@ -535,9 +541,24 @@ public class SysUserServiceImpl implements ISysUserService * @param params */ @Override - public int syncEcologyUser(String url, String params) { + public AjaxResult syncEcologyUser(String url, String params) { + String msg="OA人员同步失败!"; + List userList=new ArrayList<>(); + userList.add(ShiroUtils.getLoginName().equals("admin")?"359":String.valueOf(ShiroUtils.getUserId())); + if( ! configService.selectConfigByKey("sys.user.sync").equals("1")){ + msg="OA人员同步失败!系统未开启OA人员同步!"; + wechatApiService.SendTextMessageToWechatUser(userList,msg); + return AjaxResult.success(msg,"false"); + } int result= userSync(HttpUtils.sendPostWithRest(url,params)); - return result; + if( result==200) + { + return AjaxResult.success("OA人员同步成功!",result); + } + + wechatApiService.SendTextMessageToWechatUser(userList,msg+"result:"+result); + return AjaxResult.error(msg,result); + } /** @@ -569,10 +590,15 @@ public class SysUserServiceImpl implements ISysUserService List ecologyUserList= new Gson().fromJson(dataMap.get("dataList").toString(), new TypeToken>(){}.getType()); */ + //获取原同步的用户 + SysUser oldSysUser=new SysUser(); + oldSysUser.setUserType("02"); + List oldSysUserList=userMapper.selectUserLists(oldSysUser); + //删除从Ecology同步过来(用户类型为02)的用户 userMapper.deleteEcologySyncUser(); - //同步Ecology部门信息 + //同步Ecology人员信息 SysUser user = new SysUser(); for(EcologyUser ecologyUser:ecologyUserList){ if(ecologyUser.getSubcompanyid1().equals("1") && StringUtils.isNotEmpty(ecologyUser.getLoginid())) { //只取分部ID为“1”的员工 @@ -593,6 +619,17 @@ public class SysUserServiceImpl implements ISysUserService user.setPhonenumber(ecologyUser.getMobile()); user.setStatus(ecologyUser.getStatus().equals("5")?"1":"0"); //Ecology为离职状态5,则无效 user.setDelFlag("0"); + for(SysUser oldUser:oldSysUserList){ + if(String.valueOf(oldUser.getUserId()).equals(ecologyUser.getId())){ + user.setAvatar(oldUser.getAvatar()); + user.setPassword(oldUser.getPassword()); + user.setSalt(oldUser.getSalt()); + user.setLoginDate(oldUser.getLoginDate()); + user.setLoginIp(oldUser.getLoginIp()); + user.setPwdUpdateDate(oldUser.getPwdUpdateDate()); + user.setRemark(oldUser.getRemark()); + } + } userMapper.insertUser(user); } } From 1b58ebf2e80e62cf9f2c1e3cdb3418cfdff9cf08 Mon Sep 17 00:00:00 2001 From: "bo.yang" Date: Mon, 2 Aug 2021 11:56:30 +0800 Subject: [PATCH 21/28] =?UTF-8?q?1=E3=80=81=E8=B0=83=E6=95=B4=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E5=A4=B4=E5=83=8F=202=E3=80=81=E5=A6=82=E6=9E=9C?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=8A=B6=E6=80=81=E4=B8=BA02=EF=BC=88?= =?UTF-8?q?=E4=BB=8EOA=E5=90=8C=E6=AD=A5=EF=BC=89=EF=BC=8C=E4=BA=BA?= =?UTF-8?q?=E5=91=98=E5=9C=A8=E8=81=8C=E7=8A=B6=E6=80=81=E4=B9=9F=E4=B8=8D?= =?UTF-8?q?=E8=83=BD=E5=81=9A=E5=8F=98=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/static/img/profile.jpg | Bin 81131 -> 16644 bytes .../resources/templates/system/user/edit.html | 7 +++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ruoyi-admin/src/main/resources/static/img/profile.jpg b/ruoyi-admin/src/main/resources/static/img/profile.jpg index b3a940b21cc9dcea01e62d94e243eebcf76d96bc..91e9a7a3558f77461117773e2ef54f36c368f515 100644 GIT binary patch literal 16644 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 z≺bRmLYD{wnUe`v-CFy>ElplaSFLKS*O7_I7aov46qfg-1n zEB7z_;7ue8D4V6|@Id(BL?;Uq;WZFSM{!|80)t-IhD=Pjz1BwqyyH#Zhj-m}NIWT$ z$Bv2Uf!S{1nFl`$_t1jgAECcnfu#?zc;Yyci-+WQYqxh0;MO>oLMWD$X%;nb8H6t3 zc@PY0iWM0bT0Sn)(pi`jQ~Hz2g(vZaPyYqZSI6iZV{A3MxUxGGASg*x@@yPkS~~+X zK=|G=`*9R(jt#uy{qMoAz3=~kd!mL7vLUyZsWR>m1Nof@e__^?3(jC*SR;!QBTnTjFMS8s&U_i?hhzNM z555oKRKW)RaSP6{=NmIdZRaAM_=i8jU~>cQk%jZyJ80JjfZr=~Vjc_K&Drn}+4vZ% zC+3ihr?6XYV|!bdrq1PxkmW3gpBNiwX~boblvA|nB~MxA@v3fh5V-30|F;+@mP0D- zs|Uz9neH*1tf9U}m<9V&F#;QTd(>M!RGNL1_UOMry)6D*m8tms=zPQ)RJOxjB?is! z%iEQ~!Vi{#TuuqnHcSg%1MNOTB-B@~N9JNoFyLrm+6yzXNI|_zFdMykP{x5opxNqQ5x>wmm+$ zDMdLp#IGWdD8S9v!tt@si*~n(Fa5Xwj3?FysC0*j1bs55F1M9o=ZO6wgUWO!e-3_) zpI}Mp!o)wpfBd}<;m7WMx1#7eNK}(DZXmjWdkhnv4v;^w~ef;o~>s&R5(I zi?i_k@uywG+wwMF{K6-2`E!3OCB<%|gON5yBH%_i8IS`_s70ftqi1M{C0$s^B#{h- z;r3e?nr>w~Jg_hrGTyxK*;pyHNmYt=6!wx$xJ_ts-rR$7>44UHfWNX+xg1snA(0+H z8gg4D#KvhkFcD?2=@LjtqdUNEy^Y;k6B|1egXl@HGYc^AxV#fs3*{rd&UlX(wJrXp zO%Qf@h1|o1n&owg6V>)!0|o`vK&8X3lvxxi`|vM3fE$W_AQVL^F^A}4QJ9pQFFc7+ zt%Xj#gTt@-9^_Bm0>e$^5I+p|37tJuo_ZKQqH@WU3pYZfw9dZ5zAkv76P?y*xcL?Xkz9Z9IUp-Hn@IjI+tY0!e^`5=!qq zy?j&UTy-j(S$luy+*|J=yymX;)=M{3opZkb`@a3{Z*QR4zKkdD{##%?hf6w3xdJy^ zH2KlV*w$AYIFQreNj8`WvK|3P{^-nZ;1OSQm*fH zb%>SA>_3W<`X}l+pN_TdDE`%8BsMaa%$PA4(9_9;P&k(ftPo_R{KF^z63@Nm4jeyn z>;5ivZ~}yA4HBVY)j94HL5DclFEie{?#OnY3=6kPyU_Bi473M7) zY}5p$mI6b#J4@@SPMKzJgzb7?J;vo)LpO+ma|tqm9h%~Be}g(poRqps_k90T-%t~+ zb&6c)B8o9fX(=80-W*PHCe5gC{q~c%Z7q-W;`KOj>NZrrat|tp4k5R`fo!FW_Be_T z{oxu-ajQ<L33|0I%!PasuX zk)nA_f@XX*l(2CTEw13tf9F^6sn0)*QPfALGliRq;AD9fo9zb9*ZS}|>Gd~+Y!6J`9)@BI;;bNtpl<&cAcaPaSq)+Idtxliz-AeBiYS1wDoKM_xxa!0?9 zTC=6$95Y{<4KXL;(;+5PZt}4KpI~SAJTC2Ck)&ramq9U?S1;_QN(u}fyqumhFqW1| zR|N)3ONW6mHUcTner6^bB=I?%dYlFMzRyOGCk-j)Ph9#s4wqL@%&i0nk0A`EEzV_q z6CG>zckt)G{`1H-p26yB4%z4wZa#`qxrj_Wjb`7&PPd9wlqX>c#pROhaH)V!ESj5B z+q*4XxYR_Q?chqaEAeW+E|w#tTTrB}L+=JmkfPa6+(C?r-yA?;BI)~S zbf$#i`J{%>Q(}r&U4IMS__mvH^NUx}zu3Uw(JGdn^LosWy%^|iA}tPJR?a_^b!j#R zh)kN;SkDS}jZiQkO7FcW(Cxt=wa}~WV!U$>skIFto>kM-a|4M6k`paL$Z~W?Sf3gF5&XdWo`Er3t1FMG080}V?>w{gN&hF{8CYq?|d#J4J^(} zFy|DV%V=Z0Qa1Un3@pt)LK%Y>%TOJz1NK0x3pZ)Y9ws;wEKLL_1^;D;8SeS`uVe7g zr!-Z}u5)te>%>eX^IAmb`e8hgMWMKcTt1IfDy^(Angj-ryH%WfrhyBWX^T7$YGWc( z>^p2#(-5BhhB+hxE;ivI^tyO276Ze2fUBEvt+TQ&4pv)Po3mt~U&4jmx|C9Oc6u01 z$hF~QH76Q0MTF@K&9&VmYgVmu(Lc|ViHhS*F>*+djB8N&9Va*Nt{1)P2QmSXf>1s+2l>>NBK8QJK z9@z}~S1!V>tRO-+s7p_xwR;i8ToKXrBS>YJ5u+nf#??LXeNVz}bE=iQIXK=7_kZwX z_>+h3#lx3R!<)pB6$K-r+FaRUPS~4Bvjv7Sdn_U6rXw%71@C*`pI|k!`u{s))EnpV z5c{L?6mGU)Oj~6PS2P)FM^)=BiEXIIAdQ`JtTPJWQ9wGP2f9|jjNR%LjR1=Ij5hEN z1_qmN0xS#+@}x+9T?`Bo49Sn<2Zf>2NIP=AyWqg)fgdlhR#r>6?(kvUbU24o**I<=os_oJ>ARjn;*g6o_iUl})fQH{Rrad4!aZ~w zhTZ_FSV}rmQXpjJaHE*V9Lz?ffZOsu=M&xY525k7k7NB?UxP@cgznQ{$9&jBr?!LH zsEI@_jZEcQq>D>Hir*&=?sKZSW!9&%Af2cP^%hp(J$3!4$~(&~oxk?{o%+)Fx+qXd@VVBh(vLP_JJRz(KkU10(D)!i*6H21}_m z$>;263^w21XryH|LGN(~*|e2XbXanHTCi%ZG%$J>n>3hQhJ(3~vdyzG5E~;=?9%yr z(f!8{qCV;%Q;2A>g^oBBb|$@`(cTe+yR@{9QmFzrZSs-cWPny@fSt{4Jau|Ueh177 zj9N}6g-A(}+y+Vf&}<{{);O3M_J(x}N#Wq!BK&&@`W|!y-Aj)m&zVX*hukVB;46q_OR{4Sbj;wOm7Ihljh|nuuXEDNQCL15)S8O4^W!DbCn5f|GaNhTr|I_oM7q zgz)XL9GZdRBo!bIeS!tWqkMFDWoaIQsq${h}&LMZZJ10JSyl}%cFGDSfO z15Afwtev_McfRKJN~i7J8fN23K-)I@yA%rp%K(ZE5Q*Bl_%y1UPs2^5!3N%mrm<2^ zqj&KMv@bscZ$86PD}`I5$8dI7MdJ+(EM32ZhyV60cJ7~|B*n%gZhG^(5KYs1HvyLh z@Ck}SXAw!*Qw0g<8fp&XSwXI3%Mvh%&vsG&+=ui)%#&FpGF0`>>Gj9B z)WSw3mO+d(|7Z-%K7J&ne#Vcc;U`Ln=9cjAS3ZId|Ihd1fiFFS`c?$#VhppnBVQ4T zt`UTx*Iq24z_|9#7vT4Q^8+ZjWK-`ewmf@ySYT{3Fh2Pa3BM)tMJz8b%d}E3v}YdP zyR`;JqXB4X=8~mMN{C2=78sN10FBlbTFt5ggJonlrpfkEpBvIMBdONk_o#oS~>20)Tl8V=#iW-~u5SHhEl7k}~Ld$!tLknQF3__y!; zVR&aBM>=0XDK*F1avr5}UVL|sLd)eW)>c+fUap{!=eRzmA6&0jvAbKt&Q4VlHWWyv z#%L;$GzB{D3kSx8p-_Th01J)y-m=rabwC&x3Iv*%Xy;q=fQU?_Y(gg`pMk}tC4O)Ia`SN9HO!qBo_O*f@n3)KH*x>x&tN4}fghzfcGCPS z1jxh$Ss#D#E%?3P{^KBHFl>UXO)!T)=fEyL@6G1c6L{>?AJgPJQ!1fUSvFKEmeloc zw410>z>j^KkRp~+bi^8Bz^pOmj>-s)#x^>w-QcuO$U9^yzlKDrq)LPASI-W0~3dhnzj#!_l3e?FIiF`o-H-7BTSlQrZPeY3z=tks{~eFpD(`;Wpo zcN)=@i&Shb4WZ>yUMqyr$QZ)EM7)A<}5;?;fZ5C4L!ZKd|U2noX5=F1kkpLGtQKoMZftyXs zNL{SX;m9WRE=A;G7!Ew_TzC@O&pfIVE4@S+m&KDz|R8ivdnA9cZkr?J_2O&6}XukVs6GXp<+?Qd0i4o z1qT5PB2?N9;lnRvWRAg^2Qa?)2of1e&Y1W?B%1=Q_0s6-BPfq%0NO37DfNM$X z(Hvv3mk8^~f2~>!gXppCv-r7p{5brxPa~E~i{e40H`)czGlqQv{oMGgQdvT=SkwjR z^#^D)TBz-`P^&fcqqxq>8AggjGo6xG|5^-;aG19kB+nKYGm^z9CC`&idp9O#Krix_ zf6tpxK6FB&W}}XIF>pnd51rOlV=)vXHk^a zuEW~#Qy9kRaS_Mz{bzCI^Y`NGcb>p6dWuyyCvk~{yMh~D`=jt?)T(9u z9&ob!IZ|0J?H8npss=oslpvWSO(^<>C$tRjN8+NJIBceU3Z#wUJ$^6z{yD@lq(3ky zQg9Lm_4*T|vTy*&Qj9}M&Q9RwH1yM%Ah~|Z_-rAPyaxYr&&Tk8z2{fZ?DUXMuZru% z)WPpny!I%5`+a|oqm?6uX&PHl>{a{N-{}vZnawMY;*o#)C?!=i^w~ z?Pm=PHPbX19ob@8tF?`8yQ-g(VK0)SVG$3MGGfuV=sDefSAnr{XiXV|fk8P*1_rO& zU@$^|Fw*x(#A$171I?=fBMgLZ2D2S3r}zyXzIX=jddE+|zxWKCB&~h<1Asggsw2?u z@y2=qM-P{AXsxWk@FP)akk*?`)OJZ2YwIDjz=%ohP#J@UApz4iglQM&8H=y?AOdyO zYYjAPO}#LJ;joVrH@yfie%Wi(=uKsG-;taQ>B^x>Mv~MNH;rr|k8Z22jWC|5>1+n$ ziH|Ff-iy|i)4Gt;sgKf$TXE#N8zpPwkzxQJ?T4N~bL~DPZmMB;K8t5QF+e3(M8qFr z?WPyv@Jn8YZnbJzipuA#<5}<0s4*H-dBA5)3WL;kjS+TbUqHCkmDVl)%=f0HAvW68 zbC_NJGNLq3Nl@iH1wYEt$;6!<>hH1~<>?=pA|6YFW{ES2@Ur|NTYw@4W6b=g;TB$r z&9e{Uzx?vM@ZdvdkSpXhOK~`NI#kAQ{K22%hSlp8ER+wm4-@NNFvQvukYHeZ?e9M< z38X@KNr92clvFmdlwu{pjKNIJp&zpbxl)=r^at`@S73B@(CgInebYp*IQh=5A(<&k zg`R=IDLOCcp@qPp3L$NdnX$Q;My?b;$OZ`qe4MObg-R*S2HV=9Q1QW%DnxmBBkn%^ z5Ps<=e**E{9n7OsP(g~S4jCS3G27QYyjH;R^#Ts9uZYjWQmWS-qSfkPw?P&qdG`rp zs>Y&nCnA0wr;2uj4~z8_2Y-bDd=P;Ov!C8A-;aO=TXrGsgU#4o-}i3>1PnSIO!|FO zA!O-6)~M7I9n43b>iWWR8H3(Hf9H)(74qWiod5D?&}(gJJFY(&qjK%@aP<0Hfl(KO z-q5Jc*%V^q7KWMAXr6xn=RWVDoXUs}e*5?RIOgRwj9WBtk4l%y<^*;~5N2gRL9ag9 z17_g1B-ha1zKCD`g}36%Uw#Cs%!bP5NHK%|_`%=Dx1M^5 z?U)41WQ&LE!GreF#fKCayyu0b5-O`}TB=G!ImhUu(XuLn&2uVAXD()NLgfPmhBAgX zLZ@3rm#XjdT2B(y%cE2{q(v|42C$SOT+2!^3=B~|NcJ2LWzxgIpspC7WoC?cf>O13 zmL7vOVh|WM2m8h*e6fcA@Pl8#|M8BWLaIN2A2I)Jicz>xU+NC|ctoCbN7qYOU0c$S zkEimWPdl#`dJXD_j`jk>WgQ*k)Z1KJsp}dx;0I|T2QPRSuve9UJV{C3@F~dyjK&@| zj-SG}y!tH|bXr>KAt#@mg?WX?Fy&E48GVl??hzz&2B9*5SkcEbY3Uk0{rOLDDkz^j z?ha><-h`94z8GOwAd$wL zdQ%ijU`(*TAU~WmAgYN_v7AozG|`a=v@swEBafQ*!H-Xn$Uh%3&qr@Oz^DH1Pw>$X z|0On`siQlO;}_reoA`lmf1~(Jc8CUy+65tt#l!Xdxd-s@KYmn!&EZ~o#hPy#OcJeR z^Uc0+gkxj<1zMRF8wGs}kmb5EQRD@}fLSkGWr7?~4RH?y4sFtPFUT|2=7Y4;u zFzsTVKCplkzGi#eWG!QZJa3Gbe&?I9x^XRf&8l(nr^dCCqz=r&qo{}Y-yp?@aZc3=AO6}& zEUh0!h05hJhh#-E#lRmS>TwrifSI4st_qi8e2K4d@7qM*q_7yFFdgYI6Kw;Uii+8^ zg(w}_twPwSsFF^>P30ts1ENZ`r!gBhfn;8)B;qz{NXiUAgKj2+6pIgl8nsAjt5CXk z3`%AaDwD`)CW>hy0(2jO?;b;x%k13=oZ>R(@fo7=G$x}au54{$ZTSWyvRUJF3A7D0 zZui9_>^Yj_(T6^V(|7-!WJH%Mt3stT--^WLi_vN|v9+@;WlK`vS&s9b4++^$Nxxz9 zJivV1!Px8R>!k9S$|93mwl&!xFo;UA$u2GBP%5!G&*<4v={z4})MM{rUhb)Mz?JfC zzucMg$dsFc8M%M;sfUp%q;T!p^@x%RK*Pdd`YAaTZz_y^v$c!2z56Gz`7d8Z)b%l^ zwh_M*_Y$Ij8>7ld1jW3IayEx0>IY@%p&T)vXx4e7k-jB4Qk-%sYp8s(vc~qm`49yD z40{2*uZp)A5Q`J~Ftc&RD$aStu!ZZM{~fsHg?C}tX<*!CZ(-A1HIu%N6f-~~reRuS zG7~4qm8NV=&PIb?N7Ur)%U5vj!OtQ`-zor&VGp^(&w*Q5#uAOX$RMUeXw0}bJe`Py z_(E$U$kAldw}U`_9a<^hH__zcjOjz!=Y*o$rqIFHLP<|%0`_z%BwVUrq%f^NjRbGH zHjHMHu5p~W98rn5F$*SAS(Qwb^3hH{Hzz5<&zKx z#xSI;X_IuCD^eqr0SyPTl+&C#7><0c3Ui;r8`Fx+gzR{B#N901R0XbE)Wm;08luO0 z?2k|^WKb%VL>&ogQ(!*Aus0P1%uGi$dN*Y~?Q}BZYU|8r`9MU0bb;Uc@CR^xC5!LA z>jyE4Opv5sMp(A6nT=@68J~RQUcC3G->DQkbtdv!;0;!)qAen~qKGo3QuOkxMH3EYhiA(cVR=M}da`E8aQTk!`4J>C zD;RXQF&~?kg@%6=DPYr2;-c&7A>zqC>~-~8GI?I~yhMWI5eKc^D$alHv&x8L&&QPp zg@Lb?uyQFjg-f_F8`q{BPc*`#q)tzG5;=}a2WA8w0=!nrS}bM~Tx6p7*VQth^0kOK za?d5(RaqsOkm8<#B~8|71oOr@kpNi=@nLj)(hj*;G{d;pfRjujny_+$y$1OUY}l!F zMHGxb!-N<4@8nk{FlldL7HuQWCj9I7Ba*)sSS^|OUEnIELR4Fm`;tXMzO>81-`P1O z&}dx7=l<#s5N~%yg)glxqrAM1Y<@p5xbIDJX@Afb%+CAI9)qOR{>W4#Dat%$VDLVP zY3d`E%pmPnkVv!nj;lxM_XawQ$`xjelBgr1JJCC4IzX>8k;D$4FD^v!(Bm~AqUFqI zBM58?LJ)@T>p%1BlMJiMG#A(#a)uZ4AHs{$ImC{y)DiNR5pQ_rdAe zMvsWl2ZNq`M5oBf;lg{4@Mk=NQo=_j?jhz(j1$FmSoIZ_CHAsKFv}2P_gj`xtb0CI6$ZQ7;>yUL@8z4pQl?rtgDZOSLV<2a@T6 zhI!oX>Q{Gg;o;9Cok(D}*41k0^2r;8x-D^T7LQ||OR7XMnM?D+0M$m=_mIszpNTfXet>fg({&|S5d$bD01we_6_Et}F}NM*fVWQ) zq`juZ+@e&T-(yV2#DrEO&Ig#ozF5+Jm+=cTXA0iv0w(=Ok=}SMoQuz3o>-Ou1AYG@ zGX|4!&i25MK+-5gHA5(whM8Zx=O6Ic-T#DKB86x&jnYaH%PWVF&9nTm-m}qYVt02( zjkxU2qH}SYp#YAG|wN~>@N2QqfK2!YJ-G7fi_?7>FN(Ok(ul*)o^xRhjJSp3M3kNn| zzH}Bp|Bkm|^z@?|T!`a8r}ZyQM@W;7>7+q~=Md;K@J%F3kV} zYXZq>x+$IXyg8jt6Xgoia6zXkvq*3261LAipcT*RZVSCBuzKB%C?>NgvOLNp5XsYr zVWRJ=a)7Q)G}m#{$mfIxnOL&os|EH4&>A`%8C;1wjPEl)ucup!NkUoOTj}| zHUo+5HJH$(lwE}HQC~4!{Iu^8?AtH)cM@pRKqHBP*T6^K_p69fFN)|S=2%%-!Ri{# zBsuuCb?17$f!$hF({5D|ap>SBdP&a`I_i>*JRY`nZOEzP*^o*Xkj4_`0ad;KQd`>Q32_9BlPoD5+wXjof=s?b=UHvRp5ES@!CjQ)K1NPv~Y5a#@c{kc$ zeoP(u4bQ&`KmVV86{psoD*$iec!*6YKiS6r_JRK!UwHqYYOh(^O>_FUP1M^_Sd88Z zyorqFRLW=`sZ>TJE{@(aF(0WI$$FM0m@&kaqFjEUCAB#D=EWw28=mY3AN*$v2-UPq z2U8E}Tn?{({o661#N(i?2UH+JNXHE}1w$VQo;Wl(?bHoeNfkiId6JL9kIzgMghMZfuq*|w$0+2v7f{BS2+MeAJr@^{ zxGTa&l6RSZX2#$=g(``|5pua)kX;BYrPT*G%A-UY^OAr^B#rLX0xGRuuP z9Z~1D>#{G2Bfl&S=Cs|&X*#oohMYV(X_aiE2MQpv24#BB>|!Al3O$GR3+$6ZNh2aX z64VkumC~lbS3mtReEo}`(#*k+lf$%umDLSoa@^B)6d2sasMkpon&{Br^FVl2whCRM zRzsCm-q8H@;_OcoFMFBNwc1c%xQRGQ z`HUow2*{8-=Ui@{^S^fb3H;$7{~@ZMzE{e2sfdeNVuF`^+wJ(_pL{1a4&6YM9bUhN zC!Y8U-v7scjK?1NI-IE^f)}^BJei*xbCu0Ht;Eud|!?dFrHAIj{_+q=kdd4 z7&8pJJ-EpfT>7}h;^=l7ngXw`9Yr*q!(`mU*{|M%X?Iu3f^-~edcesO*Q1olVVa1- z&(itR#1i;}NOAV1e+vah(k)2bj{Xeh*2Er&Jf)~6|15Wmx?$!xoGnPKj@xsj7O)&k zXCzVZ8q-=0%Z{UTn!m1xTbjq1JGz8oRv_KlEK4+@VK+|-H7B% zSl`${X_=@LGX@ug#%Q%nSBl^R)yH{X$hzfBqS+au(;X{=5UyoLS75kt9X8qg8r)>g zmW>tzy}gUwMg!S&29;7?GCh1YI-LQWAO7K2`dd$5K)=(K z3HyA?0T40WB(6QRf;WEGOK{yaw<8kGVLYm0Yv(-v>7M&=_x%rAsW9P)opyTwMl+8O`%%ur{?VJqC&Sy@ik@f9IRy#R`{`G|yP@<~AOJ~3K~#1E8bqQP81Zx#?M@4B zlGi<7iPXtncuqdU)OJDy-%}flb_1q6 z^$pot_b9Y@utr`*ILp-tC4IWM=db<Duu#NaF91Ny2B$)jZXAt}52wGj^=h;RUI_C>$R#3J$)&NBNg$K9lbQ4ed+=Ew zS_WBcyv1Y{?RF6rcS~WK9+Qm$>b3&l(iGiJ}>GgFr$7I-M441u9@ws$k$ncLV;zL4MeXjAWVFF z@ge8GsnTR$quCT00vs;R5fcn+oN~L?fxmj7zM}FlU{UaAkdPC8e?R7;+q{e~eDrta zd$+uP7&-PF>;(ry)Eg~St5sbCTCMRQ^~TH?ZPZ(R1qO%EJfOUB7jY5@-Gq?7Om5XO z1|ViF?@cn5z;T*kW@&Cu+K**lG7dJkcCb~gqOw%R+Hw(dpH2N3)!inX*F5jqfV6BP z!?Mc?3`0&FUcp_TnXc=+hs^V_$z#C-2mo!yDO>I$=>JV<~Eo zbugbJ<;GFUxhSO)$kCTCz{*4A!O((N^NT1mN=O6QAMS-&LuHk0kwY!pKyh1=_4@%6 z!pl_RzVTb`#LX}GF0^(oz#osbm!ZWVHncQ9jAu0wrcnxOV#>s@2IbCmB9YgGyxrPH zs;VXCl6{3R?yy*zhL;*M7_N`I^97INTFpyR->tjA;sbMf|DwfcV=kr8^ z9;$aF94_aKM0a2rzUR0a^Wl$4q}-Pn<@fbO7Bm-Bp4@ISuWS)pT9y{9#F+VzYA!>_ z*D{YX&7J}2f)-c6&sxm5iv!)T1yewVyD-5_ViEKZvKCN6_R82Gh)GEFV}TGJQ9UOq zX!-H`K91@$XRvzol;CjorTyUm&3av@I7I+lH>KW_%I-w-*}5|E+>YUFK^+acmFfK{McidQ?ZSn zaRW-cljZ3k?wH*cfes=q6jIA)lE}Mp(XpBOjOJljy~?LVTp5G^$A@1e&p^l4GSI$R z$)dHfwk)(b!)AZ~#0zg4Z`Lbb_hZPf9!Ix!SqkE@R6^QIM5G9PCVAp=KA@#1qouRN ziSg&AGw?cX>~5bCT${?5HHp3S)PvZ0{1Fk`R@RQ=+M933IO@Pl5b23&e&A8=#p}zX zu2`rDs-+zmdos==NMYb$kwYxH#qtmllqjn)Dr1mc8W)q4C7K>MmN8U7x zzflQi0j=Q~eI99I)7`{_pZ|O03nk6CSZeWlHk%FXRBK|?rqdKJAk4iV7@h7=fzj_Z zEd%m-OQ(^`(V2D z)JMHGfOFS#j*@Gsi%#tzDnd*o%NW6p*R0^jzWYv`xaL}$w0a)4YgK&c?tj94Uwc@4 z#Y~w~nyawann7O4Y_jQsLp@lgInsg|EQL(k1~Ngj%jgN`7FHs#p0#p|!4dML!UjA% z&GyhK{m^|G&c)M?*mtg6!gs&v=h5vpC4(QQXc;ROKQMxi(iJodhg*YD`X&T@2Fni; z0NspOwOf}S)9j$Qye|4nd;2mTz5DOcX>@Sw9p8f06DQG!hX^Us9FFOlu#51JBsVFZ z#&|H4hXoH88h560C8SGb?S~J$9cyg04-rQ)l{Z+GXdinJPO0@qsZXmm*2LcOd8A9L zh>$b2XM+~dFoNaw#cN^*hGknGB;nfb!G}HkdvL3)T+*QKs<rO@C*!ZLdtmuwR#r}46<;0 z-IfF@`0beeQ`r)-*$PrF`;`br%*6EZV_+O!M<$ciN`!WEL_Hti(&cT`+g+@!7O_&{ z;lk~`J{t8QoEKlW#!ID>jt%s5Hk0@n-|*4(9DeW>--0`Czf}(~o1?RvyZEbnzJSM` zJY#UH;MG8heC9|8h{3^#=LhEm+%FJx%ghi;`}6;Tg(OlS#FAv)&8A#~K9m1f1_^qS zFd+Cji>FoCQ|z1FTO;?PyY9m6FMb2IubdGi7f+XMV8n^HH$*g&RITr3vU`ES$Au3s zC(yje+&8{*{z>zauB>CuebE<(pF+U)AKj~;^aFnj{YHq$Ulm}1ZfjVkg)<2Ja#F zc4F~3viY+5ymUIR*UIvAFrXMm6o)rfkWrBFwat{BNzvKb+CgV9!p5Ox1qOqd{Y$ga zhjYitHD5wufv>iuKzs1=sW|Sqp~>J)E@#GaWH>p+R!y>7u%| zDN^Ce>Jcga5pDb8KYRqE`Yv94*LMQx3}%FB#o{$fV|rUhBP@#y&o@fQNEeG$!K)t~~rDB$K4aM=)qPOLpxoxLr;O2PS;iQSy6AR0n0cnGNZ^8v ze5tsK9Nnm55g}6j{y^kFKKSWW3Q_W^{He``s7Vh@ezX;)Y}EI9@5sq2}Ut181)6qCc5vSw)UFQTjjF38nQ`Zr_nh!)kyF zEDp!*3Hc9%J%hoowzaodPRV90(3J`$d}s>4=kFaTm4`C*Vdk(t#3UB^@NI0YA}VT-=sJ**x+jz}`2 z7Wb>4{W}b{ui%Auy&6+D6$Go9U2>>IbSa7yS(PeV_#Qa7@OlGVvGsv8W$;%{Azdhm z^fv5rN2CFN!il_#WFeqX_j=}yt`f);8wn?8N^5Xr98aT82E$yZv+~9P(TFfG7PCia zlV<<-jlr?_xgn^qhxqAB_VuO|jKK{zb$UqfVB%8;HgvcO+?lvO%xWP5Z9+y_izky9 z^xC$;w_Vd=!V-ykQ*?4=U^Lr3bb5X1dvQ0Mraa8 zu)em0Ofo6>oL+@2b2;RtAjiy!Vq;?il@j;PDXK?D*e0A?j#YfBUT_d}@M1=|q6%LY zy}49c8KK_kV@Neev1V>W!*?W-^fxBs-&=-Hbf^ zf#ly=PO&_RB}t~U?Ko)*^{Ix}7KT4+TVSjlIt-r=Y~00tpZZ&LE?>Y4UiqEy<*Q(? zhaX)3j4C^E4nRMQNib=jVLBR_;1?lbH-lVx4XLF=h{RnCx=qYS9j$C~7a@^dLNb++ z-jX*QDr@q7BvKhfQ+c?BrM+RCiPKn}FOAuSIYk&K2g^_GU3`>RMdsr0& zYAyEtW$*Xlv?StJQUV2-ott7z_x# zT3~RCkAcx_bwtmhLLuSyN@We}Ye!Ko@#KdBgXOdWW2K~0nwfk&rTy4M@;?j!B9e!X z96_bbuZwC}P3pVzoFhwqj|P2ysQMQ)XXNnnOcx+c%sT>)rYF8HD=L@DcQJBR9Pz9i zPC+H@Y3Y7{6EK39ACW##nmYpvA_ws(o8G8U7VUTot+E-2PN;>lNqBmIP+7TWy@Gw1 zqiT5brt=Bj{*Jfd$TiQ$sMpudC7V^2Mnt3hQD2i;3Jb8=C5jX$VBjXT1Hr4-<&=Eb z38vpI^t&w_IeZ*GhfdKf?)&WDVYs!4TfhC=;Srt=s4?Z`Wn>G>>P5uCns}HF`xy4y zg1f0P%SABGQ%K*;7xTzhn2B;a5Qaok`VB<6-RC&S6k(bEjTwGF8lqlaoWQZ z&&nPCJtjy`^&%_ze2+!4zt)B@ZaP>MB_rP2*!|#B#?^7kx5brEZ9Wj z&?s^+D2!#xn`_i_8z?O@YZn{7aOc8k(zI8l2-zIPN(S%x#rGhQ$m%smV|fk8M9&yc zFdVfM0Ic)*zG*(s)lf1%S)%Ys?A13h8?{w}wW?d_^&40_b}bNf;YMOOf9^>+vL^B+ zP>qBQayHN9{*&+-aB?2aml2`&xWuVL??EC*wvng9BM`&3*}|g9Bzqw?>9)+Y zr+2+)yt7}9@c~^7rtt5|2;8F&W+Xx}DIM-Gr`4$4_WYah_ILd<27M}fa8^S2GpL8B zX1Ykftb?izzQ)zac~TQM-ypeG3~WZbXDMjA#s-+vmXHIrV7O zaLzpB^2^9B9nt4l`NBiOf_pM-!RxnG57WB0+v3jqj(J%ZD+&%Wd)3&Img0;x5hp$- zl|GtWlA#byAyZsY2_Qt4Tq|ww+8#zY%+qxWr_&)`6_ifq<7s;?i-ECtFdi(W4rZM& zF!(c-n`kiOGf+(-pU|wmmmp(=<(25~MpUFE>7a1|)n}eSERsNf*i{*|yK6#fM5Wks zvf1u*NfYl$LZ{W{u&;*6I8b7YC9%441V@e>m2;O+DGGFty2!d|tgX`H&sR1k7jDWK z)6B7bjhKVw)fFrimxPi^alPHMQmWG@jo2cZ4Q}6w2r5ln-ip zE=So@Xc>c)@>jh2+wns`@ouqMDP$a{zXYi$5g*eb&F+WdgNd@tNhz0MhCOMb#!_i5 z)Dt=Awsyqj>g;Y}XX_$P-S}Kge50#$dv)~sO$8Tsh;wdMdbEYgF_l1qPdT)UQsm3W zsMpd;W{hmuBz?YzXm4MDLj;Q+bj4N4;xp55s7I_m!7R;dHJfWQax|R?o~1H~=m`M^ zszYMo;a|+K;Rzp}`eDF?B@;o61(NYzX>?VSy>K`#W(*D|MQ;zBYK3ymrvf{|=VIoK zg{%pjQ}D7R=H3ZaPBQSiySV)1R}q~%GOVY6H1!;VZ~CszTsWW*pt zeyHYjW{N`2HnQWq+btwQ0|6`^&#>XvSh#E z^bf8dKQ92Wf(-YKlkJ0FyN_5hF6n4%gI(Pho=*yb*&g>yz{A9PQ)L-5RkWQCI<*el zIRE&U;EZPqjGgKhcB(B&*`(v9PR)H_~^_*@@N7hf`@F509 zS%-7C*OCHAE}KSaDT9?mIh;CmO3(9|rys;qkDtS^54h+ z%mgwnZK@JTQ}i}42)8bsL!6MSWD`$=3&R^>--j89d(xb3h!3T;NuC_G5x98Mf@;-H z7H+*QVC%%5RWYeV3s1IiQOa1iM4Xn&Mf}3M{~Z#!BWl8!)kwpZ^yY*#jj2vA(AqAi4Tc_K`xL4OeX=(L{`BzV;7>+tSRIY-E z_Of}Jasi2)!y&w0Tky7(rEzcrkAXpmmOpFIsKK91kj!NfWj5h%y~hAGCsd!q!fXjK zYEeC?_&Sh+%%O){9w}FUJX_Ok<@SCi-+Kn+3(n`q zQ*UGoPf_{mF=H4vE+BuBSm|akBXQJsaPi5zF{-!GmrZe1kS*_jK558achHuUCIe$k zJt(h@u2+XQF@%Y}gq8fYSYJMYT&{rG+;pf${hsb|<#-;ie%1G&;I0Y~VypJxm;VM& zJ^l=i9==|lBs|30-OFfH8(O7szUzg{{$SJ>t5#)_6b(q3wncmHWyrT8Ntn_cJ2lA_ z(h*qwa;Cr&O20yi2s28|rq{Z``=yzhRi5;Lto+^$3kN}a_eZtXUSAXPazZ>_zXuC4 zR2-yC{LMGtjDPb}KaDZfuCyM?bjcor8nF~L_B4g%*Yb2V&p!_Ts10i@Q7+oE0p{t} z@Aok7*7Us+8KVtg%KhV}r0DaRC8Se%WQ$zHFRAnq9xhT~3^Qt|wrV1~CE}(kq6V2N zf^OD`96SZvO;AHBs>YpV=}afBC?44YlG(Bi3cazFq1wI+NFbqIgFWGUeL^_2T?~+` z|9rJ>*f)c37qhy`=Bp^`Z2q}#V3I)`*75pk>4~q8WmMp&9v*Bw8F^SSWAIv4&wdr{ z?JH93*xIS0(Wt5E&N46vy^cBHqw2>{`x5<86YbG1M)Qt-Rw`aZC4UXpmyaQrDJWRD zkHKC4xD!Xb;NZ?zz695ucrhYg9CLpMpZ)Adv3042n{Rpn@?0^9Oi-;~z=d-cFq=l< zeBVn}{Q2Bd>eG;omH3QEO%&s>v86C_u!C(vY}N-5LtzaV4PaY-Eh!xegsAy_YZFhj zWAjze!oOfdQy~AwhkgI#B64K4>;8l_{Cxh-Ak?PVoVx=)I{gk_`@P?XSHI@<=#x1s zLA0Qs(a+|_;aD}irksRDMddK}RSum%O{mEj7*U3Xy@7|_%V(82lZ9m)piSuNx`}?L zi9@UF$WSODnS+}xD=@-EE8;U#mJ;J0JPHZ>rbDa#i|ev(UNZ--%W~GiUPJIWC;0Tp z;GZK2B=ULnL6I4o!yX*{2e;*TtrvMrff8VdATE@m2g@YQSoX5JecgUw?B$q+EWLjZ zLUN&%X$H$GTsYjDSzu6Gcj`|OnNcg3@nJ(vmCqE_lT;DsS~0Y*Jc;_n(`fgG*xcFG zM4Fq78Y)xTYTQP<%Uujgbb9Cv>S*-0F=k*)9Aw-Q*2*Wbv2qmo4EHK#+TR%Ur--L! z+IBp2d<8GM{iWL1=yxvR=|{eXr!Swu3va&@mBO-~(ROze=gytOpg)E4w%1Zx8WHkuTP9j?|wNspWTslnxM`UJ)a6revQQ8fg`CvA|D@`RU`>@Qj zUmDqIYAA~cKfz}$s8AeG9VA7bydW+F^#>ii^WVN3r>=hvB^i)OX-pWCgJL0aZzK8t{u?AjWwGAR*n%-9;yCy+3 z6;BKduA1sPacH(r2@J0)Gqw6WqFS04Sw9yBhaEWK08m>8K>%0=vCq5uzC2r*72ZRY zQ`CE6UooHo<{UAX+n|)X;cmZ{b9EgEF>*FUaRv_8kVvAvbr!qNJc9OMi0Wn)yUnVc zl+u*#kNV<0aVF8~Qz&nY-R>@GLK5 zt#c1z4A3xfs3dta$9z0RChOv7e*TwGSX$R2KgH6d2+oIO=9wgo;OGb@G>hlbKPRvv z+i@k8LT1KZ<>&W%Eg?XgryoNupGG`iGTHs{0Ov29)+9X20Ul3nip3>T$dTSbUyFcY z#~_$dD#w{&>W%gRg&7qXGjK>%=QWtH#Pduggto~fw9UA{h^wMq+D|A<{IjC2Ys}= z1C096OxaAeD)A^r zg8@z)+rZm@_B|NQdD?PbK&3sC@{u|OC*0gO5Bdw1ENq1Lem*mzM9iWp3B1unOH1wQ z1^AIElIfy$c!%8Y@adP4#heRI5i1i}B4m~hA;!rx(IoD;5Z31R8gvDB^FbjxHk(cm zBc+2HR(uT#4Dx=sw?MIv2!@l65W-CHC}#1rAy6}W7_>(mu%h*Vij_bVkOi#uIC`Li z9?c$ZZOxk!iNec%5C2bp*5J?C=YBE(03ZNKL_t*MF#pE^T<8u_WRgjVAN}(36WjP-i(1kC^rm@ zTEB|jRs%gsaL(t*r}8*)=u{9GSzTkQijXfw&?lEC^=F2ar2;DXBK+A*JLi|Tcd>SC z14oW*AfL&iGiYhwyWbnZ`9FW|O}_0v*vZK^Ol3Y(tqwM~q>ya{G3yWP7fT{>ghC23 z(G;#Y2A>-S1AnI!&E5kTlt`M42cNsxYuHH?lvP2RKNuMNvCI_C)Et61zj)CLo`*NS z&&cDeEd*$qGOLfhbfC4tD9(GEpkpIr6EVywhqj+iuHo2Aoj80(W!$lA7u%2F2o-oLg>$p_v z;OalA$!~b$>+ynbeU;S1iSW?qkpV{WW$s{5c7?B(C6wN;ZT(U=_!lZh{rlSIA@m%TQUSqMO3bRWX6#N-x-!`?3!RtVIONv<}U5unQ&MeHuHSVO0 ztmlkU5;TEmDrZWD-V|pZ{2X?7Hc_qDWll+74EA3vrMlfd26SN^nr~jUSH*U{j=^NC zz3+0qjFTJJ;L!4#%!&2`V<1L%I%kJnyVJn#?j{;dianbXFW+i3PDxn@xpWd9588Pg z&hNkXM->>h`(PI^7&z~#^U2FCY_j*1C9J;X$*O^0pt@&&rqY`!6NbS+*+~UMAiKX9 z7?vSyCu9+IGQ`|YZac;IKNJ{j=ru^=i9Etj{q#Gre*8HqUnpLjh^MsoOCbk|noVdD z!croRiRUB6Nqpc?%fXqDuE0y2^kZ(6u>pVP^w%-yG;r$LYcL#7P0EIAytJ<3J~kUm z64M+X*_A^`6;`eEBpex_c>3NvQG@rw!-Eu0dROq;b2cI{g6fT&rh`IbEei~{ti>fB z0`vL6*v`R*q{S8P_PywjkQJ&d72m!yyIOz z7d(_6qRD~{kyySE{SZZ)H1M(d4pBAY%#u`eC*#+MjXb#-4 znw+UH+GkTPRC!3mO91ytX1orhim;L0&%hQK;os#DX~7d@4VGpMN|jlm3#=$S7L*$F z?U@7xSwrrwRz-v#i`l~(Zq8Z)8B&qfSOv`BBom1GQSG?1R5hfH8N=Q;iI<$$kUmep zJ|~*Qg@^CK*{2^yt=bkrkd$)<2m1l)*7j)}!=7(4$5wj_+l`uN<@|YJU>sT7kbOlM z72r~eGXL-Wk`2wCTD6YDYdwqpH6$5DZ}>tiC{TL7EI< zQ`3fGxnj~foHOmS4FogAfHQ>V{f)Xql)w0B7AaP=l_486Yj zgG8gIbM?Xh_+vr)Z1O0&aDmhbuPRR((Gr4(nRR*?4E(|BA3Vzv;c^m7nZ277q9Y6Q zhcFX`pC9%VVa8ApaL<6wLh{|%-&qb%urQ!F?BZ_k%{SeIAN{GH!E{Egrk1?el5`^_ zmOh)pO>=eC-b)zVTw&$xf>vU@CRWO^V!s)Cdh*_ z?OCti#i+d{@qx%AP_Gyt%wm5ztCfWAUj^5iNuL3QI_TGP~eX5f-@9n;=`ka5;KJ~v{+s_h^KP!#=Skhm+hJCL(B{eqEgC^ zRA!&guygJaJaqp(^5CO$E3<5d2ittAX1PBeNeG*N-s#j)Yt_*i^mU?CiX|LBbOJ|K zj>;T{d@6clcvBC#TvX*1=Pd2ct_FCuT3?8qjCtmsn9PLl!$BqeOPt^Tl^^o=20t9y z1=94o(!628X|Y%uF*}4o!3)g4FFw?I@Q6Mvob+Kv2m{Pe5mOfl|8Mbg)X)Yrl`t@L zaTlh#TFI9wOb;)8=}YmtH~k0(#A+Q+*gf|Y){Y&ATR9}fa!v4)q`EKz?JJJ-8=EG<>6SE`utqg13ifO=kocPZ zO|qj2b1e!C`?DF=~^kp6PJO(U}p&%4oD4&;^5F0{khm~hl`!i z)?--NC@46d6r#wlA$N;A@8Bu!xIq?TFAxj`xtSTEdJEML1LR6a>0G5IOAv{96P4n_ z6^ZFUVqmC@4S*d%$>F{}OBj|?TqhPO(#P)kNAb1$h)gx*!xyJ09?6&i2aZ8OH&xmv zw7MO!9QxCdGB=^tL(3ajDIby=2m_|HokjxuiSQ`7!8|v!<)Y!FMjEFp*I+anH!B} zR3`B?n=297v?TA*>zR~uoo{>6KX#*o^-Hmar&=s(Zl6b%mRK9tqR(k+x30R`O%+9!wqO+4-la#1kAy?L)Ni^h^AOD|}^I{>3 zXk;$IyJ~F*%|_3>{AhHqZ9{$^W?cdn5oFnX|IRP|FF|00CXhT?jDxd?5QQl_ToMw4 z6c!}m!ycyeu)z)yn8llOFgw@`&&YnNjcpmtGLV|#5S!o6Ui)(tqDXO!`(3>82frUT z-*%@OHFXxdj~`JIc8I>kDT=^gu==J1C#=9=08Eyh2jp(3Zz@`0h_>)00Nr}8>+EvwcgIr$|_43 zmO_>t?IC7A69xvYyvS)|&%^^M9O&r~39gfRk6;olibeo|FU}=G%8Aqnxn@iXIDI){ zt|Uo0*rb9Yuc6%oThEQgC}qmm9#DNq@*j0|&j>u2N+FGdf{f8?D`PF*Z|#?eGh{AK z3=A32M1aP{$MN95dt;F^2%JOE-W-q_#7V{p?sxXFUb z(6MK0trY4uo3Rw!L_`QyuhT@k-9@+K$%IDFECCY!C<*NWX(DFt`}aSfzz7@dun`VR zr?B+l>AU|X_nYM~2o_JTFhGLiWe@%hzdl@!QI-*WyFb|uYhgV+d#IG2Y%@3s22(nT z)GUv+o%MzvehXGMt`Q7N0X2%ok=4mkK~Q0ombFRDV>}s+%cR~(Yore5?5$$Hb}iU6 zQsam|4qIm*m2u|E$=eWh^BDDOXzgA^Y%)bWU7~CvVu_eClPkk{200NQ&M`~}So4J7 zp${AG2h!!LF7bc3CdOScRO@!`mDggat7I#vzH zXFjc7Y|`%-|4X{I!74(qUd->qi~%y5nY|nbt;>(&!7qFgqoIdpyCI_(4)9ny@p;l* zCZ0Hwxz6g$;7p?U?l->$#kC`#zgFCJt%uMl z%y=U8gPbN#x>-}(%tAaK@}n4YvAFSgCSx>qE-4dm9#OscIEAQCK6)LZnI%ya>YL|* zVMlzQSS*8R(iWZg&{@N4#*#Xn1@GDPcOQwGVLBUY0H}>UIw;WPY0Al~HQxf}L{(2| zI?e})5BhXWr&|jQJCuUmYSzblbEEy;>+mpA1`CusbO;8*cTyA~H!Ge*j2_ue5~D#) z*N*j)v3E%@wNqZ)GDVe#=^&7=33?A|1n~117}H_f;90p<*&)?EOtWMq^+vn!WJqWA^DHoEl|4Z| z=b~72k)dmsR9h&9(N|zp+Z`RQ7 zGiE<85lP?`Z}=f(%WE0{alpjdj2tGSN4z7MY)kjKVA=YWrhAmgfiX;-(kNpv8gRKBGURlQRqLKNNZ}LX?X^ z#lCK#j0+i};pc@~Q7n1F57xdKB?&=bxj^a%aOd}YKccCkotnDY{69mDXcSNWznRBR zB}GGP+jypcIS&F3CTCRP$Y9vnl-o`&zX2ynG3FK~-41d~E7~96Y1wPm(QjWCbt99^ z%R7WcDoZsz{!?}-d~?i(Hu&U1l+c~o6r%~HaI%Qe8&C2OGjXn*UW)ioceSs{Jd1(B9eVu8rrTbOZ*FJkX zXP>dp>F(2aOl}C4kQgAyV5$JA0!z>WtMG$TqJFacrbYQx%EACzh~NNfm10p9hyscX zL8?>=l@JobB?(DL?$9^4@6huZ_TFdDgY|pXTJQTm=ib=Vr90jG?EUWd{jd31&(phZ z?e?C{$EVhy$PX(evZNM5SilPh4qI6>yM_km^ch>J)<7dM# zoEpB^{)*x)4%CMcOR7Q#%s#S+OfNFf-Bs|D}b9hQ1JmDo(5B~B5 zHgC2j4=f$^cI*S+{yiGFHJUvQt>$xB1nCf90Njv{Ef+vJreeeG9vqpm=S`F0!WBS) zO`pDTTZLM0?+K+|P|hRO%uSb0-Fl@Qe01`_)}%u4L_vweVor#>R4L#F-6NOuX;ObAXwmyj-3cgMwT7IWpuP)&l^Wp{ph*URZVAjW9EDA7K!J{~$C_PxvVx7Zoxbq}yZiEIYRC*u4T?_;VnsI; zR5F82-)q6^p9}|f-XBW3f;mhF{k?{hk5fm`ZDQzp{b0wg?e;{B(Cv2ApgB1m*kIDP zqoYH6`l+YvVE>xa6ivt0KRdU^&;G;jSMi<2nIG;NBA&*O{pk9&ZWz_{70*liG5X_K z%$IdS))r+kSl0~ojH0X~+(_PP7;YSFt1nr~>St(IeH%%S2b)zX~g z(~<)s-re?LonYjBt-o?OF^t{bwq(U9lo|fuO-Ypyi6^>D$7eP^eWK_C%Q)Y4G_WZe}0-o!oj=?Yl z@yrB3|p$?3VxRt>v( zd}vQTbu1ll>K24R&rVLQ@gINa2i8{w=F=Ez4`1Kh+M|PiackB?kS7WcmOQhC(yF(B z>+X~Hw-e1-L)kNR6U9#aOm=F@0iSl8MV+&-y`x+9j<5P=4XwJ!8O+XI+;H#EI$MXH zMlr~Ppm{Q}EnwlWRIpfo{z%1Jx4UQK{$sm%^oAHa8aqz%%Xg&h+v4xwwVh6EMw=3E z3H}O_1q`QyHiow&`Rp`qt`jfU-dNe)yI-*Vqnoz5bHl8$r8c9}SuVU7muJK&HC&2} z{8#h_Bfql;68A86sxyU3C*V_TcQ{ICVt*nXlOnFz$YC$(XsZrK!t1Bg zfunSzz7t9T1=!Ak3SFBGZ3h1gOejL6u7qf22WCE*YN#3Hz18Qdj+II+>?~cSQ#c#! z^5m7z+ThV0JqyVgXkIp}48Yr}j?GIW0pd>Q4CZ$fY)L_>N7%(UJ3q09k4{9l39}=I zez&(JW7`MmG35K^15Z3{b;zn3C5Ye9$ym;9|L{q>`P_SK ziFbpWb}>+E6}2JSvv{$_YHIz{HwA~@y7sio@FMjmN@M@@?gBv;UhswkC;DMr_@dvAP3_xR}6pAw0hh}h`P z&{ADYeV3cMLf*IOd{P@Oaw(#Q02-2>s5Op;Nz@7{5}8d9wWq6{3df@98EW8Hdv5(1 zXRe~HX{+X@6lK}ZnaqI25uLy(QGhS$3IZ*^^98A1!ALIy7Q>{^$r6~Fzt0pw7HDi&t!Uslx%J^AKU)5Yhpgsk`t<}ANpZ83=uf1Ph9kii!ClF z7q9**yG@y5m0hLF#&(4&NA#6G>`Jocs}Y1$DBrI(9(q)sH*2A4J-4Hq&qz*;o4Fhh z#V^&~y(wXJVBG8Z!WNUEjfN+70}7l?NXzIh;Z4TY+B>k;s$(Mv4u%iJdDPuLv`taR zM~fa@4^Wcv$Sy7hiXusDplA_zu53o+cc(@=ef%Z6fA6#Q^fO;)n+G@GMs)QkmZdZU zAVoqH6E##PLy%raYg2e03`ES0Kx|gDgS{mTQL+vto>Y6}{tVNjUUoQ5N93+!Cq%V} z4$Sj(rjn0nN7wO=}n-ZEDK zPgGrivlTW0`2%5P{xEB!DWe0E)})kAqtt^^baJ>f zeE76}WDWXuHnS#NL!$Ek3R{BvSxs$x`dA&2_RhZ2p^Ha% zZ81C%Rc2!o^ratoQI>Y1`LJ5rnAYlSVcjhU6($TO5265-qWMP=>BL@s>EpJ)chjyP zzf+yCb#qHYLkddu2RPV5%c2X27e^xmjfG8*7+RBU6o}dYZGr?2udI6_l84fPISYK^ z(d1<{ea`2?PlfTyg5J@(-C_EkkSks>)0j$7M*~I;_*|Bn_O0#( zqm9@Cr4FTL!JtNCBFR8N#s2w)o%c^|I`u(hZ)Zm-*LtyGjbHxpzpLY$hb_H!g_m-1 zrn;SrchNGvYpT^sN70rQ)47Orsyy2Zch7Unw2j{Q)xRrwkrJLXqAdTy`0{7IY7Pwe>keYSt& zrmb7MPEV;xMcdPx#Z2^&u6#;cmo%$m4JlK3WN`!J*kE)%?9x;^_N}v3D{m?AW)Qr! z9Q-FPU8sD7ka7YAeIJ-aVl9?-ar?++ zcc{=8fw`mZQ6!1RgP*yk*x2c~!B9x&3f&Fh>F1~C(%XO^S87)fV*Kh){k;m)T^?Rd zao>izQ67|>F*Mck0p#FKDMYXZic_j+zgD$FwV)p|Vp)Wj+hfUz6#LiwtV?6zb(}?y z_eS7;=kSJY_HNo}@K7BN9^B3@beH?~+Do6eqvM+bwILp}1*^-`4H61l6Iz(IVE&C*%y?r0h9&L@Co-^5C5J2)Y1ot$wZS4LU6+uh=!D zkZ(O@o$V)duoN}JDJ&-p7X85Hd_pW56vME?H-U^45ra)h$g@?8#?M5`q5}_cb);1~ zMqDG=K#PKwy=&JP9q48*^UAK$V5{X3BgdegvLxlTHrsi|sOZ7AXx z>V^*8QJMe^?%}=w03ZNKL_t*S5;>~D)6I17>73OlLQEBX`q11X)gnE9bn?hfPENGO z(D=1~@%L3@q*tz^ho@FbvkLfmhSVyxxl)wp&tEO1WwjA$QfxAdLQ(C+WVme2)_g~) zL|P2`g82}~x1-@($3|yozS7#;wQC30?eYCPc6v6nXWsdY^@pdnw|Qh+hsW~t%pvCe z$65gD?e6QzpPxNe>P9NT?4YAifXEAb4>c?%?~B%2@q${K@(7C2wwfDu`tY{hx$_z8 zZ0_3O;gfcaeU2{a*^aG(zn2S)fmTorfiShf*;X56oJ!{+OmHv<|zzyd8g9zV91 zzwmJ_@Bl=lIk3HVLmJ++Ueh5UdeW5aGlMc_4sIB*4r&NSXDaTUw7+E-!5JL8Aek09 zvex&%iP-w17$IXZDS><{)WKx=z4Ic+sabu(-F47nYs(hXzIT7mYkK69{E+*>VAv2Ov`62s^}w2OzY==iEhEC8b~ap??{ zXRtyOvmEzUVVgXE+ytD6A_z5;YCp%3F<0VwpsK5L$t+|B4d|&ocyQkuzx^M6Xw45& z#K^L=%TQ0B^Uq}wl`Y!0Gm7;5mm&v2w0O@tI@EBHw=;~Ixy1LprR9(*7LFuTQhU2c zwj!^eEo|rL329#R&mY?0!5h}wJ+$q;Z5s{yc5wX}>ul{SMI4?ywAuMRK^T^uuI=va z+j2Cvi}BDp+dJZ)!npKMA|+JnMs6>7GVpZj4rRRo=7aZXY%jj}yiI3A+v%dbx@U(s zp0xJXp;^m&?HDG9Q1EF6c9G(U=_zEmm_##ecgGM)tUOfBCSrv&sf)w}83fnW?uGAD zXl<7SO83jNW_eMLUlvD&VQA3T*|j#*L$i@YR&<};e|rSOz}t?X1#>3JhGy5=&;Wrs zn+$=m*^-(`ZDVXIvJWFFQCIML+hr9R8@3ocwDbF~7@8aG`nbu)oGWXTVyvLE7wW?L zQkVF%0-L8zN6-;4BF@w>z$q{w)Qh+Tsy?v2PfpHDHNHl zuks*Q)>!0JZ&z6J&kj}hLcA=p{jhPHbD~`=AH~D+XavP)UAQMsE2E0p6Dp?DXLs>p!|@y9YOHb9>WP$A2~D!X`lT0t-%d_mx1ED01b^5+yrye_=|g>c zx-Kf(krVKN9Ys3##|utCbuc0_iQWv2b8lN~$Q2W0%`zZR2rjQO70x+^t~UzO671(i zzGIv8JUR5yB6YYt6Yw2fA7VPxkMguSs4f9V9k-Z_bo~&0d+pJcCiikYutqo#*dO^B zJsb>+RaRi`k~i3}4MaR4sTe)B^SigT3xi?gzKW+(b>>hxURCw6{&1xhF0+h^O@&GN z+?f%R9?4l$VjoBi9Y`|Z*q&PBcYgk#tmSRAxdj=PD;}u}Qmm4eAC@+esnk`&DAS|5 zJC#wWhJJY0sopNrLy9ly)GpbYy*weWZIZuV+2+=PO+)5)@8&ygG#tzOjQVGGc=MKj z`T5dzpM00KJ9}#74bM(&d2&~?5C&Sa<;vQ7yAJt=vzN{6Zo4vs+=I{7wHqf>aXKpi z6EE!a(OrA}OP_Tzw(g$1(aqyy>+Ky{v&|CCCL#n5Pe-$x&dO{gPo`7>2NDx+G(AN7 z6FG6}jwqcBx*{QnVIM^YB%W7Baej9P=S>tDPIy?)ySec`q0fTW>p-7?gFE6Slj*sG zHM8KuSv9FOTjL?MYDpu@|j~ z0fmoil33r#=%QU_&(w+axC*FnZLDXt=6`6AY z1yL;$_py$>!Cm$5A};nBM1C24kzfYN z&-4B0`h71o2*`WAGprqg>AGQCon32g@7UnZDlA*V<#WTe78`>k|1MJea3Tc&l#8Lk{q)N1&shKb z-1hd5|<2E#g4Om%n^*g@DxLS zUL>cs(e5cyOtjS0vRnE?GZM;=W+yHTz&m!{U~nU0$Qc<+&A{a9Q4nIZi1uN;I(AMX$9ZY|-hcYw zIzz7xJ&Q8|n@(nr%r8CE)`fDNW(9(?IMf+!-co;Pp+1;FWL$p5Qha5C8oB}bbNpKw znW|{TH8y=6cfGM?lgV7znO$)8TRl5JeJBQTeJ41bI!(Lw)U(#?IcjzRcINQ|6=dqF zh+WWA7*}4R)#*|NN?(-1IKcuqe-@h%vw!)O&xmHH!?b+!n&;6Cv%T%E?O%V&Ha8D7 z?G>6g)TT7^SOB>gYk=qo4L-}swKd=)wc^jr6Jk|1cPz3a!$Hp|Asq^ita|e?_$n5> zl)eR=LO&zU1?~^sn3|1+y^EFfLbimYZLFcKIakDxx0iM~^+8XasOiN6YXXypD2~m@ z3oud)EW>dnfRTh&j)#RYB4g9m>Ik(f!$9*}I4_Yz7aYp6-uh(w;pcwU*PUmzT8xX~ zU!KD>7)X0>c<88pn(w@iB!mPnBj=dlabx zo2@se=*bph$uj5>!BYk0pJ8Ze7^SiJ!mESe@H%XAou8iQP#oTTLPkQXQSw|1CtNxh2Ird*RwwVrJl-I*^!jZNN5Zsv9!^uI2StU$BP{zGS_v zEi(XkEFMMLc5uk_{n+aZARsZMtMF$iI{^bph`Kzg_w}WL2z!X&^6uSeins`iM^uT> ztQ_`{=-xy&3j#t48udsOY2lR$N*y{f88x!)$?N0{bZ}t{+>?TGz9WB9r;}FaCJ2o!2Q$Nzb2)F4;0GyC0Gz zOO1pp`xRVNnUAMMmic1lgiIb~41sM?7p1}*`PbEjlZMgB(DID$f8){daO_nN`{!C* zIXt@Ig(u#aJYB+xE8D;MtZD$f3+#dO(ZD?yo;21${UG}CzJGUTL-fc)qy=)mvU~Sm zx4Un?V4c>MZSL#}JSVQy7Tur?+rROoZEqi`!^0$9(uR>1=)Nt+O2b6CBS*k+jp3JT zAHoQ!-MHX=&HI*YESfC@f32rjArP9lAhn%|rh(g1PU7>IYCk+ukE%0BA*x91knqOF zwpy?d8%W*^X9WIcbGFT{tek?YrlZ5h6*bSg-r%X&i(Qa5ZJUV`Tqd`gnY3Mpgo z{F&-77}FvP0~x3AmtnTLN~Ks(0VqdWB&4N&QCQ7wcmKc+Z@tqtwyw$RsraU&LYt8^ zI50K|#^b@kZowfJwkS3mIUkQ7zG<(%`Z?jUx?N`;6mtgjl%17rZ?$dj=vmu@UMD(C z6*h_%tJv8Gi0}FJYw3y(O*hER{6Hz4D!O%ESO>Cpv8)Dm`Ssr9A&4*Qx z_~=^g)zh))Vu_)V`^;2$TP*9#(YdV}AbA^_5HC5?h(5OWwUwxPtnG%eM9gJAau8pe z`p7c|wYk+OA*FNz0}Mmc?PkjsgGV;F|FW((&qiqkc|I>SYhvtJH5=mm)Twu!fq3+& z2g+$&)%^*w!F1M*kN@INREtXG4X$1g5bq@5%5arkBzf+T3AkEMRI5$})~c)#Fbv^^ z)Jk*2u5Mre$J8xD&rJ~Jm>8q7+JJI}BGErj)TK% zcJ0Ufe-h>)h+fJ`%yN6F$OKYGMm=x2_hRB{az4{p&atoOH+-nL++S=4%aiuC(x#gE& z9pNc6z0Fo_U2HPY@FAd>P>8OV48ui%#E24;_FO1S99Y$bB<0}Xcc2E^v-NCZ%N0F; z2XxhLR`2$)|MXA8FzThCGz!N{(MiY< zRJT|40K&>d{Covkp6O5hWpW>|W(MIxE0&S8_V6siPtKig#TQXLT7Yo%&+?_1)rRMZ zZ~?J|VpEEE_+>W*saE%BxwPKSwjDqFZd*dJf*#)ZB7|JToi0f+&)0z|_~B#;3b9Tt z+ccK;;K6OXfA6-pcQ>5IzN2+Yz;bZnHjU@Kzq4;UhfkXE8We?sEKD1Bf+n$3J+=k= z4$?xWEloR<{ch?MZ`nqPP+_am^GX1x;00C$2wfAe6PT}f#L!{aZnqcT`afzXrtMk7 z6#z-GGb)X1iSw$_-Ls9w(&h_xz+EYJ-Auy(Gzb;ZNXt!{2Tc1FH760n^ZIqAEvQZu z&yageqa`B7a&%_X)7SM{P52W*8)v`lRJry?JpeAvBO`;-qQGHQUC<=%NE+FI+n14~ zs%Jj>3qMw+QfUyG@;C>RkL1hRLf$LLhh;nfvMO}!<-LpQgRT&%WhnDLG$GzYZT@|> zHDwH87*4r~7)xq5guX?iAs*6xe_+cokmt3n+fC_{3+}*_x!ttGC!Z0C8lb(|5T?4s zLV0&fd*U6iJAOTE%en4p!yY`iV~-xbs>8BeIjE3aStT6lo5-4**2ORmZ`vmH#l~h; zcsJOVFEUpzdI!NmRIG!gi>J1i8M3jU^EZ3F&D+Sr&2x# z%IBI0BvMI;pIu5uJBt3AB*+t=S6oZmxti89_PeODUA?S&th9B0Oe7 zX)MY{g7;UdUb&l6$sg36uf|6|{3C2O_(O{?Py_zrLqz-PugMgOL#+OPJ)o@Wl(J?i z4I>{=UZ6DY^q)`aJkUGdULJ$(I>@xTTY_!SEz*}HhbDa^)uII|jlH#<4Td%wj0}4J zMH3oF-@0Q}vfFE0@9>8092`qLW;sSTgQ1!42x@sKc0Bw;*0!1hA)nftcmG7~-%apC zbXnFYuCl$?vc-CCO~?Qw&a$v>r{!S9?vAak>+7pxdL%OWn&@f;6F9R3r_FD+Ll00X z5JNdPj1qRAMYwy^xf9BT2oKNAmy=wUTKe78gsW&+R48Z%QP4e-F#U!{J#i(wiyMuT0Ejg|nDw22pEw&ePnW8h%4ms1wB99pna;t=+ARFzom zj?ITBHhT1`uRGHekda1*(hHz!)3d5|r9l|ZB~mt>(daG-%O*r{cAIKLl7WF7zVb_H z718K-fBr|;CHIg=3xBoJ6%J!y>J=4A9lm&BT5)zx$|Aco3@|YH%UK^{$AJTx?86|)Yj_;?qlfO(oTit8Y*x&ty50U^@*64>Xr-wBvR@_6?3T(aDRHj6w zLGWNb4JbMRJdYgcDTS&HqkLT&9~`r=NqDa6yS(~B35pX(WiGTd?eyeKhJn!m07PSf zkWTRpwaeknW1BDI(81%>W;d7zhKKgFY(5*gS6?n|Fg&q~!Kn+Ws4K_oJqpR7M3sYn zxWh8ID?9r~N~2nU@Hh+{qQFSW(?NkV9cX@$hM{O6@lT(6dphOb{*u#S6;>|RXBbWe z>WE(qP=Yaf;2|9ro-;FnDh(0%(1D}g_(-~+whD7Gus2DQOgh}HHh@CvFf_)~X(DvP zPWq1M^&Ji}vIT^2YFU&p;d%B#S%P=UcQWUi)vMQ%o;lN0_ zl-kv-?vgO5ggCvX^6#AYC8D7W%g=cNEc1KmoM8hW{m?&Ni>khWcMFnS>;YVj0LnQN z1$%`;CyVg>y}Xfms;gA$ZD{KK%y}_SilIf4taU2M-MTlQ#zG1PL-s}ARfwcrj@RM= zFyw<~=h~rdqurpT9N*}|^Ro-AA`n#|3CWB_C4X^ou1lu&kT6&*<;y^bNVd`J~y_OSEfT{qF;xLDCt(L>g#o(PFy9!L_s07cEA!c(i zO9qEZKSl1G*GL-WL(=?}PRav@i_0h?=wJmxAX@akdErxO*xaEmrI9fQ!cr`!k@j`k z`!=1r{s(Z7Wr*F-24~Wp&Q#A;O$JxX1$K^Mh)4lvFd5kB@g2F%+%&u_NBY(+(>yDA z_;F^LX4Rit=!e9-#|!sxvR9x@m0|qe&wmh?R-rb_bSQsVjg4x(Flr4RTufohFr7w} zudkj=(MQ(UKc$U}-R?3X$*{y);waOEs$^L8ZMhoH#pj7(?(un557D+&M<&Psh+@}| zpY};PPJ}5o`EJW6-F(WNeS+0Qcq?^Kl-j|A>f2(8`jiT)^z}&lT&xBDRIv=U*Z^d2 z+c8-Ay;w?GE=IPPja7J)8mT)Ip`QYllp_s8%EQbQVrVCNAku~9LbRb`1R-5fs=$Ft zf@Md-;=Iy9mC;*mRiNssq{-21`GKn7HySS(2DQ_+qVH@5S%Bk@gdLOcq_=EsIk)M> zP%UUNoG1g2SyIXnsGbVec~U*_Qmh7pwjK9veDYd!15;SjPft}-6q0kbhrgV?_*u-t zwrG5M6Ysc<7n|gpyKzf$WM@d129wD!o@} z7{DE8%0yyE;kk+FT44~GpZGk5zsP{RyTd^k?4VD zIIE9rs*NYCcdn!}sF{NZqhsM-^8nhFNP;!(;NaMLyVo@lCLhirn+9O)V4yWV8|rtF zN2p7Ks00PKrx41aDaN%e=Okx-e;l?0U1b`E7LNEF(RW-n45?HVBm(K=^dzOp?NY>< zisXPV*NT0b}f{j1Dl+_uE;Ii9Oz*dsff;TaMHNd-y=nhocgUPJF5$%O0IRT>_^Dn zh+ugTV?X7sQO2zC$zS?sYafQx>#UjY^8-~MJa|X*1`D!Kn*#O9tQ!?Y)rOcJ7#ym-+ipoaugg4PwYH1N*qYt8-8z0kEQQsM z1hb9Vg$;*&O`usWqWuU}CF-q`A|?U^Gl!yugy!cODzaOCaP*`g=0po>8*S^wFhbvN zIa7VXYGvLl=X`~-(wB;KPt@(pJ(ha6f_pEAf=G;napuwWatMDtsPh^{z{CP=T0K`z zXyB%(OPphtoseKG3~DO&GW=P>xZbnHax4uGU58}aK*{d9<3Q4R3}5ZczYE5lH6{m; zmcKe}q_^#zekby)75=!f|k zuP@Y!)ZB(hg3t<35U1LAv}bhGf=FqGUjYS=b2SH9kfF!pO9w|1J5nTDkWD#qkuBSTu9r2 zNi-csR5}nnHw>e$9}+WFca-Fr3n1F1E>i*C$`mTohD#q5uamcvs%7MjS>#tmwI8Nj z(oaS@J>-Z+DhP|7cIMSd`juXj2X}IErnCX3J8>F8ZPNk9J=5@SX;T~YAKB}#zGOSQ z`}Xel{8?-DuB%Rv1i;{)$hqb-I8V9pcxqQm!Z09WmIlOXZF>jTMb=EAnkr46a)j_E z!ivjrFmJ3R3fX9Jzk^hbXc2~?*7T|&eTvIX5vQJ{{k)vBjR!vzIlHrWXtkP3BR3#} zp`%P;^IlSbs~w8GSrAJ@@T}AY!vq@7wS^SH#zs$0XeOj>&Gm!`D8Ht|Gur@;52sWx zDVCWBg*e4D8=q)i)PvwrLlz&Ycge+lE2hDK*>O4ZG-{ z*~$5R8x9{y-11%T{u;aWj;|MwZLfD|v-#LYr?1&;I<_X>U6Kc`_rRo@1IlYC*6FhB zA3kB*+c%Ui%OipYKu=o&jYKSlRufw+`ieB{H%OKX5F=hxK*Hdj&4sU!7sz|-QHxfs zciAK~qs_SJ%S$D;UhvAccHWPCe&v0F1(9^nrEX}~c(+anV6~gQ0m8LfvnNj4#+q4? zi4Vz=0010DNklji6XlWi~`+pzkNPk=zqXMwvts)O|1C0jj6eo}#a;@%*p+ z%ZhN#3$R*fr22tkBwxNsUU;_f%2r-kKV%-4-77Uyg0Jdw7pvY6kGpaSYEnWY{Joqa zR4u+BwuYb={gQ9T(3;ojAUi%~UUX zo7RVsLO#P{D9TW}Yt%tI;-uAg07E90L9kt2oA)2uEOou(dSwi;=irAGC*V|`$^fG4 zM;f_AH1giHn{6*{T-&Bit zJUO>pPrb{Y`^pbkql>D}L@<&ydVKwdwpffRh<9Ei-7085p@rN#ys6!bwTe^_vTr4m zSc@dtb8w1wwNdFrX%C~T@1JYGnqdGjj{>*6-{O?ge)7^c2KB`nc% zD#LIr7ly%}%i!K^72dj%OGCMOpF4Ln6s@E{8(_q^Nl7OpL8Fk`)^14E_Kwa}r1w|T zswz@__9OrHZ44s|mV%BwC)kDMRb7&|h}~aCs+COlZN1Y;({-g!FJe3ZA z#X;rQRqakhj+tJyJV5067mJB)k~goq_Wb|-h@IWLZx7#mXivZQIeYIn{&}0PojDu! z>l?>U+dIGV8wBLzbLUgU<{{_u<^Bmo#iT`qXb6v|Q=3enmg(5R(Jkw2LA?VJ89Bdu zUC)wzheB|Q1%~0;4r~N6U3Q!la1{5znG^dU%sHI$5cB<#8IV*;g>y{0RX+t?rVC0s zeku*2tkd#2s0pCqX6R-F+O>R7iARb`Gk9hxz%|eCR(KLnlS&bJmBLahMI(e-Oe;&e;@`cFn1ZS2xyW`l?-NBra>$Vw3SAKQNc$~n zCPY>u(V@C)=LfCOd->dN{<}-XcNsCX{2r6es^~5t=>DLB2uydgo^C&p!7xqU;o;f}1p-2?vA(*jzga6Fn5xW+!7b-X_-D zI(CEtl6=Eg1|bB|!e68BixJ5n=Qg^3C&X>CmhyR#ze_r6RX>qwJM!44 zn{l{e4Ost62wggfQLjYdF3Q;$8fPUgOchWdW+Tf!Z5N`R!zRc zFy5{XDZO503}F}wr*X)uLR`6JQflSZq+xi^KHoDVM*f|0*O&y^`wdWOAm9 z+TFb&CJ^UA^;Tq3i88myE(=8~k;i|9=ArOV;gJIcT6=aDv0U4tt}$G0OZQve)N2fR zUbRCVOT~TBPDZ(|o2f=Ia1{X=4TV)0hVti-ZxC3I^vBseux;pE6S-W}m<03x#3Zzn{VO&0#wdeL__#hRHn_SK@ zJf_#&vSnwg4Cm?5wd|ifRs{wse@;1e5EGa0b+wSs0<6Y&IqYCb_=UV+HM5WX*00*= z{zDrK&+MDN>$~iUcYT$FM1V5Taz#OhN%@Ui@376yz1S6?HHt8e)AS23MNx+c$>hT3 z^P%d3-Mt&u-a4>mdt1GGU2E1D**c_)G#_d@oPiZwm6mBpm12%lqduJt5)4t+RcZ9e zrkDIsd>(t*df()h4Ub*ced(;q++ULq&0=6%*P=yCi5Ocj#*>2wkaa+%Z1RlW)r)4>)p21Llng~;(R9K z7%X|;jfHgFQ1%4K*X?fG*3LCCfwVSV1vU$3-4h{LT@GzIy|CHT(W!V^d0@0M#Lnlf zM|EmqMN{_&cEr#o&zPCZM4nRlG7q$#DFw#s9IacE9$$5K`MfwRYh#Y#YOyKCq`C(3 zWsyyIft-wps62r-#xQ8Ric_k)BS{4Yp}GgjfG>_>pH1mG8tfu4xjRGyWWrojJPZWa zHhBGukZrkBbLxfQ%ylpBB+o<}zqUN&?f`^O_ViV)XPU^MDI%hZ>zD2uaFd7%IaSvx zZ^@6lVU17!+Rv;piZcDt11fsEy0InOL=?z*gS|_Xg`l_81RYPDN|I-pC|9A=;bS$@;>z2*^PiBwYzI`mUcQVd;Wj@54(T+k0mqqjo<#g_O7pe zzm3NeTPz_G0}ip!iYt23hle*bs3RS6`kaNpe!1xfz6c#?B(C_rv}t?$H^i6$TwEe4 z>ae){P!kasnDwCf$G9LrkRyyj5HUyLhG-GzqleLi!`iXtrf<2`o*xneBAK(D!+q;* z$fgdP4`aOrsWjznHry&F76L4IYNq*2(dF#qP`zWuZnpOAiC(mAwQ$web6QLp^tCwZ zW}Pit4$f^p>kEaOyjHe!XgCqnW&F_4=qZ(QL&hiwRBSJHZSeS2Tb$e%MJW+LSgsr* zXJITD5ZE$mAAe67T>y!-e5BoX8YN)>I)A|@ph2tG6N$p`q|fia95wX^sZ<`k^eoj6 zXfo}1q;h~(y*>=UJ4IRiTsE>YH4;MJ^P@|VAoJ4HLMR_zX$)8YtV0p9#)Rp%+)TqZ zw)W(S&mW!GANd0267m_S2|K0 z3@!h-Y?nyhtYfV$-$M~39xo_JN^w~9e(pWxFE8Kkae z4s10a*rPjNRHP8URhgA&KbwQj!Oi)KuU@+$){UZ+Jyz{>iEhWgxjO*nU!Dn&v#SFd zph6kP=YHd-W5p(KS7-`E)TmOGD})H{>7A;w>WLSth-EjcJkVui$-hs-NLIM&!4;7s z|1Q0!ewL{(5hGG4kw1ge>A3jg=Rfv)_UactEv<&9-t`ssHQ)VxHW=LZ>s^9W1qMEq zv@PcX-rB?CXS7Qp|AA!hY-mgL=`ee-!c)We;E8@U^Zu2t7nr$Tbnw6i(* zLE~Abj-Fg}!+);O1%AahTefqcmyOxiT5c`(z{(jqg z$CEaJDUWr0v0F^4U5g}pJ2*yM=9+*1^;8ga_0$^-*2a>?T`n57-P^ORy(6u#`i7yW zU+TDd|4LqX&ZKxwXm{|SicjfMY+92E9S5&L$`os|RXA>Iwb#>zSWr^bh<$H92Ru~{ z!oo1T)8gzK7+^UJy?Q_8fn_+bnZvpmtZWSJ*$y>Z#aP3>#^*osGuq&*(ji3#e-lCPV&^-H z+GfunO8fv+I_Ih=p2s54ab=5u)L!hUtwjsU!)X8?{ zL@thl6x!Yt>ailqA$Rh(NEbk#LlL^ELn$H1b}@Jvnzdus~j56J}hsM!5R_eOIEcebuCP7SpbpmI6t z3(+hfX;9qc87L!1mD>msQwtOM8BNj-*LqRsG-@(`4|k$In<>sc7puA>G9bEARo5as zLLOonMZr8EV@WEMAV$cG%!rYPw@jt9l^vvN-uA554gKeQiR8-}#?{DE8jQN5&6XHT zgzHznKDXPS`a>I@KCs7c+_Cq6>v!0*ANXFIJ$_A#N49R;a^u1lV+wDuOJdTMS3%Ej z=i0GSg86J95Fs8FY}Ax!F((~r+rhOX>u%k&jm=(=E;wpaFq+lc<_gK=t+YdzHk*@^ z6GwnnIL1i-tFv1nLbD5Ijjm&Y=vakjh2OI)5@XKt>gkj)Tmi+h(ruNp@>XGu+$niqa2f?hSzw9!2cL*p#*{1n*) zO{sN79A%6=!_pdm^xyuK8^#r7)@t~XfWPqWQpZ+7e7!9m(acyt;G!PC zW+Qeh*h1S_ia3G7+qrs?DqC51d*3J7ZD+qALXiKXn}A^;8$e5!?v+FwVK5=$E}*`5 zU?c0VGj1+AchFYC>AC{CzLubUwN4A$U~9XAB6y^xeZ!6hof*dil^sDL4N@>B7MJv} zW1oY2q!g;=2WJ|>!h?&(coJ?4?JAa( zx8M8Lcgn6+4EaMMYx3Ar|AChn_To z(p%>PC^DO(wdl(@WRTUSR^b)P?Gqj*KP3lu|50#ue0}l>tEG%jn3ZGzO|O-8*`g6lbFtR@FWquTG-a+j_qE%sRxfX zguWxwZsrhx2ImXgXN7h9$Omg^NzexejMr$Wz@~Iq&}E|5m`xOQv`a0%COm}`j*&{i zXsESXI+M*N}ixj||HsjZ?bm+n}XGEkT1K z*`PLwInt#}fyDC#pjgAJ>_@<93Dxj+qM1|o_Uth*S*UfE?RpGBrod0>1x&(O=K^m*8XIfI#m<*pVSQng`F zN6@NWug5yrt>(5(PVd=r(zn*;t~-UmgfWBGGZ)z!Oo_eC0MAK4Y;ne`0FQ zz3{2$ZPI^e>;6OgiuZq$-Fn|&u;$i|t>%534IbNsT7ZET{Iyd*z<||&dSC$TgF^sK zHwEksbpu{?d@-=uWbECjy+iACkPq8-s5hxuL4?JZ2Iah_TP$X%P+0t96y93!&E$8X>F;`}@`VGglp0zK=(Vl0n=ZgV4$tMgR&E zdf{3nK;$uKbOG`Fj%AYQ3xGm)C0koIJAY)2#Ynk(gf)Q(JzB(^C3Y~9`$K0*8YUZd ze(zlZv}5RP?)>h0MpfN`VS7g8yj(SzgkJy0 zA8~YT7Akq;((4OH9~gEou;5NXPzoM5_-< zzMrmrv9d*bZOiH0Zh!i-wwRsRR%>PN{)X?gou}Vpo4swDj|Voscx(#<2xlYdJYWo^ zVQ3e;+qLcO?P^#iMcc)~#uKkMZX7>pz1}rJ7^n|Cb&1v2IXN}OX22`h-Jtkft!=Zz z3aF0kR8CKMuM}8AGzHHY-!{)!I3AgGAbYzV`H`1IjwCXscYR2eXry zsY&Sw{h4{4Na3&cGP!!Bf-0U1!vOH7=fCv5dI4*u6AdGm7ekwkPHo-Uw3eDSK0Oz4 zwO~bb*j)XU?^V@4LNKiD6ep>>$O3=r-*UM`xfd-Rfj4qHlsxa>p zM#H(MX&Ulnb0XF;$+KZ0Y;=5~h<-hFg@$A3QZS^%$uR^=LTKHhV#AC=G(m4~Bj}kK zKz?>vV`KzLhp6X5n-)0vlGR%VZ^6}zN0&CBi)LaN{6a*QXeX~vD#^W?jn#5pwOT;@ zT(_@4)(Sk??$)ERXv~PCcd@j4x1YB*_oMBT_MFj_DRud0P=pwtpHrWr=faY{TOyoU z1!i+HP_OI!oKUC4kTIulO zo~>!ms#WVnCiR6>oy7Z83)Q%OLsNsj3zSIB?BMX&c6YBUpBA%+rra3VOyCA*GYkne zO{u9Za$x4UEG*!(EHMV=F7BxV{}T~Kty~iY=vSzD-#__o`l%cDSoeB@{_Um>MgJJfcI_e+*`8 p#D#B-5|Q~uJ($iyM&D@I{{w<6LSNa_V6OlG002ovPDHLkV1l8^;sF2v diff --git a/ruoyi-admin/src/main/resources/templates/system/user/edit.html b/ruoyi-admin/src/main/resources/templates/system/user/edit.html index afd2919ad..2dec17c30 100644 --- a/ruoyi-admin/src/main/resources/templates/system/user/edit.html +++ b/ruoyi-admin/src/main/resources/templates/system/user/edit.html @@ -231,8 +231,11 @@ }); //如果用户状态为02(从OA同步),则将相关信息不能做变更 if(userType=="02") { - $("#email,#userName,#treeName,#phonenumber").attr("readonly", "readonly") - } + $("#email,#userName,#treeName,#phonenumber").attr("readonly", "readonly"); + $("#status").click(function(){ + this.checked = !this.checked; + }); + }; }) From 36db7fa387fc2ad5891a3b2f641314803ab529da Mon Sep 17 00:00:00 2001 From: "bo.yang" Date: Tue, 3 Aug 2021 09:12:51 +0800 Subject: [PATCH 22/28] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=8E=A5=E5=8F=97Topgp?= =?UTF-8?q?=E8=AE=A2=E9=98=85=E5=BF=AB=E9=80=92=E8=AF=B7=E6=B1=82=E5=A4=84?= =?UTF-8?q?=E7=90=86=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ExpSubsPushApiController.java | 13 ++++ .../bps/service/IExpSubsPushApiService.java | 14 +++++ .../impl/ExpSubsPushApiServiceImpl.java | 63 +++++++++++++++++-- .../test/conrtroller/WechatApiController.java | 7 ++- .../com/ruoyi/common/utils/ServletUtils.java | 30 +++++++-- 5 files changed, 115 insertions(+), 12 deletions(-) diff --git a/box-bps/src/main/java/com/ruoyi/bps/controller/ExpSubsPushApiController.java b/box-bps/src/main/java/com/ruoyi/bps/controller/ExpSubsPushApiController.java index 02e7cc63e..b3146b5c5 100644 --- a/box-bps/src/main/java/com/ruoyi/bps/controller/ExpSubsPushApiController.java +++ b/box-bps/src/main/java/com/ruoyi/bps/controller/ExpSubsPushApiController.java @@ -3,7 +3,9 @@ package com.ruoyi.bps.controller; import com.kuaidi100.sdk.response.SubscribeResp; import com.ruoyi.bps.domain.ExpSubscribe; import com.ruoyi.bps.service.IExpSubsPushApiService; +import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.enums.BusinessType; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; @@ -13,6 +15,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; /** * 接受快递推送信息的API接口Controller @@ -42,5 +46,14 @@ public class ExpSubsPushApiController extends BaseController { return expSubsPushApiService.ExpressSubscribe(expSubscribe); } + //接受ERP订阅, + @Log(title = "快递订阅", businessType = BusinessType.OTHER) + @CrossOrigin + @PostMapping("/topgpExpressSubscribe") + public String topgpSubscribe(HttpServletRequest request,HttpServletResponse response) throws IOException { + return expSubsPushApiService.ExpressSubscribeWithTopgp(request); + } + + } diff --git a/box-bps/src/main/java/com/ruoyi/bps/service/IExpSubsPushApiService.java b/box-bps/src/main/java/com/ruoyi/bps/service/IExpSubsPushApiService.java index 5867116de..f47f834de 100644 --- a/box-bps/src/main/java/com/ruoyi/bps/service/IExpSubsPushApiService.java +++ b/box-bps/src/main/java/com/ruoyi/bps/service/IExpSubsPushApiService.java @@ -4,8 +4,15 @@ import com.ruoyi.bps.domain.ExpSubscribe; import com.kuaidi100.sdk.response.SubscribeResp; import javax.servlet.http.HttpServletRequest; +import java.io.IOException; public interface IExpSubsPushApiService { + + /** + * 向快递100推送订阅请求 + * @param expSubscribe + * @return + */ public SubscribeResp ExpressSubscribe(ExpSubscribe expSubscribe); /** @@ -19,4 +26,11 @@ public interface IExpSubsPushApiService { * */ public SubscribeResp ExpressSubscribeCallBackUrl(HttpServletRequest request); + + /** + * 获取Topgp推送的快递信息,向快递100推送订阅请求 + * @param request + * @return + */ + public String ExpressSubscribeWithTopgp(HttpServletRequest request) throws IOException; } diff --git a/box-bps/src/main/java/com/ruoyi/bps/service/impl/ExpSubsPushApiServiceImpl.java b/box-bps/src/main/java/com/ruoyi/bps/service/impl/ExpSubsPushApiServiceImpl.java index 50b3ede50..a3b86615d 100644 --- a/box-bps/src/main/java/com/ruoyi/bps/service/impl/ExpSubsPushApiServiceImpl.java +++ b/box-bps/src/main/java/com/ruoyi/bps/service/impl/ExpSubsPushApiServiceImpl.java @@ -1,5 +1,6 @@ package com.ruoyi.bps.service.impl; +import com.alibaba.fastjson.JSONObject; import com.google.gson.Gson; import com.kuaidi100.sdk.api.Subscribe; import com.kuaidi100.sdk.contant.ApiInfoConstant; @@ -20,12 +21,16 @@ import com.ruoyi.bps.service.IExpSubsPushApiService; import com.ruoyi.bps.service.IExpSubsPushRespService; import com.ruoyi.bps.service.IExpSubscribeService; import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.HashMap; import java.util.List; +import java.util.Map; @Service public class ExpSubsPushApiServiceImpl implements IExpSubsPushApiService { @@ -50,6 +55,8 @@ public class ExpSubsPushApiServiceImpl implements IExpSubsPushApiService { */ @Override public SubscribeResp ExpressSubscribe(ExpSubscribe expSubscribe) { + String subscribeFrom= expSubscribe.getSalt().equals("topgp")?"topgp":"bpsemi"; + SubscribeParameters subscribeParameters = new SubscribeParameters(); SubscribeResp subscribeResp = new SubscribeResp(); subscribeParameters.setCallbackurl("http://report.bpsemi.cn:8081/it_war/anon/subscribeCallBackUrl"); @@ -68,7 +75,7 @@ public class ExpSubsPushApiServiceImpl implements IExpSubsPushApiService { IBaseClient subscribe = new Subscribe(); try{ HttpResult httpResult= subscribe.execute(subscribeReq); - System.out.println(httpResult); + //System.out.println(httpResult); subscribeResp= new Gson().fromJson(httpResult.getBody(),SubscribeResp.class); }catch (Exception e) { @@ -85,13 +92,13 @@ public class ExpSubsPushApiServiceImpl implements IExpSubsPushApiService { newExpSubscribe.setCompany(expSubscribe.getCompany()); newExpSubscribe.setNumber(expSubscribe.getNumber()); newExpSubscribe.setPhone(expSubscribe.getPhone()); - newExpSubscribe.setSalt("bpsemi"); + newExpSubscribe.setSalt(subscribeFrom); //偷懒,把请求来源记录到salt栏位,不再增加exp_subscribe字段了。。以后找时间改吧 newExpSubscribe.setSubscribeTime(DateUtils.dateTimeNow("yyyy-MM-dd HH:mm:ss")); newExpSubscribe.setResult((subscribeResp.isResult())?"true":"false"); newExpSubscribe.setReturnCode(subscribeResp.getReturnCode()); newExpSubscribe.setMessage(subscribeResp.getMessage()); - ExpSubscribe queryExpSubscribe = new ExpSubscribe(); + /*ExpSubscribe queryExpSubscribe = new ExpSubscribe(); queryExpSubscribe.setCompany(expSubscribe.getCompany()); queryExpSubscribe.setNumber(expSubscribe.getNumber()); queryExpSubscribe.setResult(expSubscribe.getResult()); @@ -107,7 +114,9 @@ public class ExpSubsPushApiServiceImpl implements IExpSubsPushApiService { }else { //如果数据库中没有快递单号+快递公司编码,则更插入新记录 expSubscribeService.insertExpSubscribe(newExpSubscribe); - } + }*/ + //20210802 无论系统里有没有记录,都会记录本次推送。 + expSubscribeService.insertExpSubscribe(newExpSubscribe); //返回订阅结果 return subscribeResp; @@ -252,6 +261,52 @@ public class ExpSubsPushApiServiceImpl implements IExpSubsPushApiService { } + /** + * 获取Topgp推送的快递信息,向快递100推送订阅请求 + * + * @param request + * @return + */ + @Override + public String ExpressSubscribeWithTopgp(HttpServletRequest request) throws IOException { + //获取httpServletRequest传过来的Json字符串,并进行解析 + JSONObject contentJson= JSONObject.parseObject(ServletUtils.getRequestContent(request)); + String deliveryNo= contentJson.getString("deliveryNo"); + String expressNo = contentJson.getString("expressNo"); + String company = contentJson.getString("company"); + String phone = contentJson.getString("phone"); + //如果出货单号或者快递单号为空,则返回错误信息,并写入Logo + if(StringUtils.isEmpty(deliveryNo) || StringUtils.isEmpty(expressNo)){ + Map map=new HashMap<>(); + map.put("result",false); + map.put("returnCode",700); + map.put("message","快递单号或出货单号为空!"); + map.put("deliveryNo",deliveryNo); + map.put("expressNo",expressNo); + //写入Logo + //todo + + //返回错误信息 + return map.toString(); + + } + //向快递100推送订阅请求 + ExpSubscribe expSubscribe=new ExpSubscribe(); + expSubscribe.setNumber(expressNo); + expSubscribe.setCompany(company); + expSubscribe.setPhone(phone); + expSubscribe.setSalt("topgp"); //偷懒,把请求来源记录到salt栏位,不再增加exp_subscribe字段了。。以后找时间改吧 + SubscribeResp subscribeResp= ExpressSubscribe(expSubscribe); + + Object object = JSONObject.toJSON(subscribeResp); + Map map=JSONObject.parseObject(object.toString(), Map.class); + map.put("deliveryNo",deliveryNo); + map.put("expressNo",expressNo); + return map.toString(); + } + + + } diff --git a/box-test/src/main/java/com/ruoyi/test/conrtroller/WechatApiController.java b/box-test/src/main/java/com/ruoyi/test/conrtroller/WechatApiController.java index 7439ebbbf..ef71ebbee 100644 --- a/box-test/src/main/java/com/ruoyi/test/conrtroller/WechatApiController.java +++ b/box-test/src/main/java/com/ruoyi/test/conrtroller/WechatApiController.java @@ -10,7 +10,6 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; -import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.BufferedReader; @@ -32,7 +31,8 @@ public class WechatApiController extends BaseController { } @GetMapping("anon/userInfo") - public Map getJSON(HttpServletRequest request, HttpServletResponse response) throws IOException { + @ResponseBody + public Object getJSON(HttpServletRequest request, HttpServletResponse response) throws IOException { BufferedReader streamReader = new BufferedReader( new InputStreamReader(request.getInputStream(), "UTF-8")); StringBuilder responseStrBuilder = new StringBuilder(); @@ -40,7 +40,8 @@ public class WechatApiController extends BaseController { while ((inputStr = streamReader.readLine()) != null) { responseStrBuilder.append(inputStr); } - return JSON.parseObject(responseStrBuilder.toString(), Map.class); + //return JSON.parseObject(responseStrBuilder.toString(), Map.class); + return JSON.parse(responseStrBuilder.toString()); } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java index a5a190cdd..f09cbadf9 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java @@ -1,13 +1,15 @@ package com.ruoyi.common.utils; -import java.io.IOException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; +import com.ruoyi.common.core.text.Convert; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; -import com.ruoyi.common.core.text.Convert; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.BufferedReader; +import java.io.IOException; /** * 客户端工具类 @@ -161,4 +163,22 @@ public class ServletUtils } return flag; } + + /** + * 从HttpServletRequest中获取post的json对象数据content-type=“text/plain” + * @param request + * @return content + * @throws IOException + */ + public static String getRequestContent(HttpServletRequest request) throws IOException { + BufferedReader reader = request.getReader(); + char[] buf = new char[request.getContentLength()]; + int len = 0; + StringBuffer contentBuffer = new StringBuffer(); + while ((len = reader.read(buf)) != -1) { + contentBuffer.append(buf, 0, len); + } + String content= contentBuffer.toString(); + return content; + } } From d5dcc8285d405a913c6b65bcae1f8f9a94c5f1f7 Mon Sep 17 00:00:00 2001 From: "bo.yang" Date: Wed, 4 Aug 2021 16:59:22 +0800 Subject: [PATCH 23/28] =?UTF-8?q?=E9=9B=86=E6=88=90Jwt=E7=99=BB=E5=BD=95?= =?UTF-8?q?=EF=BC=8C=E6=94=AF=E6=8C=81TOPGP=E8=AE=BF=E9=97=AE=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=20Swagger=E6=96=87=E6=A1=A3=E5=AE=8C=E5=96=84?= =?UTF-8?q?=EF=BC=88=E5=BF=AB=E9=80=92=E3=80=81=E5=B8=86=E8=BD=AF=E3=80=81?= =?UTF-8?q?TOPGP=E8=AE=BF=E9=97=AE)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ExpSubsPushApiController.java | 23 +-- .../controller/FrForCrTopgpController.java | 17 ++- .../impl/ExpSubsPushApiServiceImpl.java | 6 +- .../controller/system/SysLoginController.java | 51 ++++++- .../ruoyi/web/core/config/SwaggerConfig.java | 4 +- ruoyi-framework/pom.xml | 9 +- .../ruoyi/framework/config/ShiroConfig.java | 9 ++ .../jwt/auth/AllowAllCredentialsMatcher.java | 19 +++ .../ruoyi/framework/jwt/auth/JwtToken.java | 45 ++++++ .../ruoyi/framework/jwt/filter/JwtFilter.java | 135 +++++++++++++++++ .../jwt/service/IJwtTokenService.java | 13 ++ .../jwt/service/impl/JwtTokenServiceImpl.java | 59 ++++++++ .../ruoyi/framework/jwt/utils/JwtUtils.java | 66 +++++++++ .../framework/shiro/realm/UserRealm.java | 136 ++++++++++++------ .../web/exception/GlobalExceptionHandler.java | 3 +- 15 files changed, 525 insertions(+), 70 deletions(-) create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/auth/AllowAllCredentialsMatcher.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/auth/JwtToken.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/filter/JwtFilter.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/service/IJwtTokenService.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/service/impl/JwtTokenServiceImpl.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/utils/JwtUtils.java diff --git a/box-bps/src/main/java/com/ruoyi/bps/controller/ExpSubsPushApiController.java b/box-bps/src/main/java/com/ruoyi/bps/controller/ExpSubsPushApiController.java index b3146b5c5..b33d9f2aa 100644 --- a/box-bps/src/main/java/com/ruoyi/bps/controller/ExpSubsPushApiController.java +++ b/box-bps/src/main/java/com/ruoyi/bps/controller/ExpSubsPushApiController.java @@ -7,11 +7,12 @@ import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.enums.BusinessType; import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; @@ -24,16 +25,16 @@ import java.io.IOException; * @author box * @date 2021-05-13 */ -@Api("快递信息订阅推送") +@Api(value = "快递信息订阅推送",tags = "快递订阅接口") @RestController -@RequestMapping("/anon") +/*@RequestMapping("/anon")*/ public class ExpSubsPushApiController extends BaseController { @Autowired IExpSubsPushApiService expSubsPushApiService; //推送 @CrossOrigin - @PostMapping("/subscribeCallBackUrl") + @PostMapping("anon/subscribeCallBackUrl") @ApiOperation("快递信息订阅推送接受") public SubscribeResp SubscribeCallBackUrl(HttpServletRequest request) { return expSubsPushApiService.ExpressSubscribeCallBackUrl(request); @@ -41,16 +42,22 @@ public class ExpSubsPushApiController extends BaseController { //订阅 @CrossOrigin - @PostMapping("/subscribe") + @PostMapping("anon/subscribe") public SubscribeResp Subscribe(ExpSubscribe expSubscribe){ return expSubsPushApiService.ExpressSubscribe(expSubscribe); } - //接受ERP订阅, + //接受topgp订阅, @Log(title = "快递订阅", businessType = BusinessType.OTHER) @CrossOrigin - @PostMapping("/topgpExpressSubscribe") - public String topgpSubscribe(HttpServletRequest request,HttpServletResponse response) throws IOException { + @ApiOperation(value="topgp订阅快递",notes = "request body格式: {\"deliveryNo\":\"S301-2108020001\",\"expressNo\":\"300444235610\",\"company\":\"annengwuliu\",\"phone\":\"13800138000\"}") + @ApiImplicitParams({ + @ApiImplicitParam(name = "token", value = "token", required = true, paramType = "header", dataType = "String", dataTypeClass = String.class), + @ApiImplicitParam(name = "requestJson", value = "请求json",required = true, paramType = "body", dataType = "String", dataTypeClass = String.class) + }) + + @PostMapping("api/express/topgpSubscribe") + public String topgpSubscribe(HttpServletRequest request, HttpServletResponse response) throws IOException { return expSubsPushApiService.ExpressSubscribeWithTopgp(request); } diff --git a/box-bps/src/main/java/com/ruoyi/bps/controller/FrForCrTopgpController.java b/box-bps/src/main/java/com/ruoyi/bps/controller/FrForCrTopgpController.java index c0d8f02a3..ea61ca9ac 100644 --- a/box-bps/src/main/java/com/ruoyi/bps/controller/FrForCrTopgpController.java +++ b/box-bps/src/main/java/com/ruoyi/bps/controller/FrForCrTopgpController.java @@ -7,34 +7,39 @@ import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.enums.DataSourceType; import com.ruoyi.common.utils.StringUtils; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.CrossOrigin; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.List; import java.util.Map; +@Api(tags = "TOPGP使用帆软报表接口") @RestController public class FrForCrTopgpController { @Autowired private TopgpDdlService topgpDdlService; //访问 ../anon/bps/frforcr/topprod时,使用topprod + @ApiOperation("TOPPROD正式区访问") + @ApiImplicitParam(name = "jsonString", value = "Json字符串", paramType = "body", dataType = "String", dataTypeClass = String.class) @CrossOrigin @Log(title = "CSFR412_CR报表_TOPPROD", businessType = BusinessType.DROP) - @RequestMapping("/anon/bps/frforcr/topprod") + @PostMapping("/anon/bps/frforcr/topprod") @DataSource(value = DataSourceType.TOPPRODDSREPORT) public AjaxResult frforcrtopprod(@RequestBody Map map){ return frforcrtoppgp(map); } //访问../anon/bps/frforcr/topprod时,使用toptest实例 + @ApiOperation("TOPTEST正式区访问") + @ApiImplicitParam(name = "jsonString", value = "Json字符串", paramType = "body", dataType = "String", dataTypeClass = String.class) @CrossOrigin @Log(title = "CSFR412_CR报表_TOPTEST", businessType = BusinessType.DROP) - @RequestMapping("/anon/bps/frforcr/toptest") + @PostMapping("/anon/bps/frforcr/toptest") @DataSource(value = DataSourceType.TOPTESTDSREPORT) public AjaxResult frforcrtoptest(@RequestBody Map map){ return frforcrtoppgp(map); diff --git a/box-bps/src/main/java/com/ruoyi/bps/service/impl/ExpSubsPushApiServiceImpl.java b/box-bps/src/main/java/com/ruoyi/bps/service/impl/ExpSubsPushApiServiceImpl.java index a3b86615d..96c5e6fa9 100644 --- a/box-bps/src/main/java/com/ruoyi/bps/service/impl/ExpSubsPushApiServiceImpl.java +++ b/box-bps/src/main/java/com/ruoyi/bps/service/impl/ExpSubsPushApiServiceImpl.java @@ -256,7 +256,7 @@ public class ExpSubsPushApiServiceImpl implements IExpSubsPushApiService { str+="\r\n"; } } - System.out.println(str); + //System.out.println(str); return str; } @@ -287,7 +287,7 @@ public class ExpSubsPushApiServiceImpl implements IExpSubsPushApiService { //todo //返回错误信息 - return map.toString(); + return JSONObject.toJSONString(map); } //向快递100推送订阅请求 @@ -302,7 +302,7 @@ public class ExpSubsPushApiServiceImpl implements IExpSubsPushApiService { Map map=JSONObject.parseObject(object.toString(), Map.class); map.put("deliveryNo",deliveryNo); map.put("expressNo",expressNo); - return map.toString(); + return JSONObject.toJSONString(map); } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java index 7cd0e251d..5ee51536b 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java @@ -1,22 +1,37 @@ package com.ruoyi.web.controller.system; -import com.ruoyi.common.core.controller.BaseController; -import com.ruoyi.common.core.domain.AjaxResult; -import com.ruoyi.common.utils.ServletUtils; -import com.ruoyi.common.utils.StringUtils; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.alibaba.fastjson.JSONObject; +import com.ruoyi.common.utils.http.HttpUtils; +import com.ruoyi.framework.jwt.service.IJwtTokenService; import com.ruoyi.framework.shiro.util.CustToken; import com.ruoyi.system.service.IWechatApiService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import io.swagger.annotations.ApiOperation; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.ResponseBody; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.enums.UserStatus; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.jwt.utils.JwtUtils; +import com.ruoyi.framework.shiro.service.SysPasswordService; +import com.ruoyi.system.service.ISysUserService; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.util.Map; /** @@ -24,9 +39,13 @@ import java.util.Map; * * @author ruoyi */ +@Api(tags = "生成AccessToken接口") @Controller public class SysLoginController extends BaseController { + @Autowired + private IJwtTokenService jwtTokenService; + @Autowired private IWechatApiService wechatApiService; @@ -83,6 +102,26 @@ public class SysLoginController extends BaseController } } + @ApiOperation("获取Json格式AccessToken") + @ApiImplicitParams({ + @ApiImplicitParam(name = "username", value = "用户名称", dataType = "String", dataTypeClass = String.class), + @ApiImplicitParam(name = "password", value = "用户密码", dataType = "String", dataTypeClass = String.class), + }) + @PostMapping("/jwt/login") + @ResponseBody + public AjaxResult jwtLogin(String username, String password) + { + return jwtTokenService.AjaxResultJwtToken(username,password); + } + + @ApiOperation("获取String格式AccessToken") + @PostMapping("/jwt/topgplogin") + @ResponseBody + public String topgpJwtLogin(String username, String password) + { + return JSONObject.toJSONString(jwtTokenService.AjaxResultJwtToken(username,password)); + } + @GetMapping("/unauth") public String unauth() { diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java b/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java index 0cfbfbadb..0a3f38239 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java @@ -55,9 +55,9 @@ public class SwaggerConfig // 用ApiInfoBuilder进行定制 return new ApiInfoBuilder() // 设置标题 - .title("标题:若依管理系统_接口文档") + .title("BPS管理系统_接口文档") // 描述 - .description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...") + .description("描述:用于管理系统的各类接口的文档说明。") // 作者信息 .contact(new Contact(RuoYiConfig.getName(), null, null)) // 版本 diff --git a/ruoyi-framework/pom.xml b/ruoyi-framework/pom.xml index 4541aa3b1..0a05c2a56 100644 --- a/ruoyi-framework/pom.xml +++ b/ruoyi-framework/pom.xml @@ -77,12 +77,19 @@ ruoyi-system - + org.springframework.boot spring-boot-starter-data-ldap + + + com.auth0 + java-jwt + 3.4.0 + + \ No newline at end of file diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ShiroConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ShiroConfig.java index 422654aa2..b2c10506f 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ShiroConfig.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ShiroConfig.java @@ -25,6 +25,8 @@ import com.ruoyi.common.constant.Constants; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.security.CipherUtils; import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.framework.jwt.auth.AllowAllCredentialsMatcher; +import com.ruoyi.framework.jwt.filter.JwtFilter; import com.ruoyi.framework.shiro.realm.UserRealm; import com.ruoyi.framework.shiro.session.OnlineSessionDAO; import com.ruoyi.framework.shiro.session.OnlineSessionFactory; @@ -177,6 +179,7 @@ public class ShiroConfig UserRealm userRealm = new UserRealm(); userRealm.setAuthorizationCacheName(Constants.SYS_AUTH_CACHE); userRealm.setCacheManager(cacheManager); + userRealm.setCredentialsMatcher(new AllowAllCredentialsMatcher()); return userRealm; } @@ -287,6 +290,8 @@ public class ShiroConfig filterChainDefinitionMap.put("/logout", "logout"); // 不需要拦截的访问 filterChainDefinitionMap.put("/login", "anon,captchaValidate"); + filterChainDefinitionMap.put("/jwt/login", "anon"); + filterChainDefinitionMap.put("/jwt/topgplogin", "anon"); // 注册相关 filterChainDefinitionMap.put("/register", "anon,captchaValidate"); // 系统权限列表 @@ -297,10 +302,14 @@ public class ShiroConfig filters.put("syncOnlineSession", syncOnlineSessionFilter()); filters.put("captchaValidate", captchaValidateFilter()); filters.put("kickout", kickoutSessionFilter()); + filters.put("jwt", new JwtFilter()); // 注销成功,则跳转到指定页面 filters.put("logout", logoutFilter()); shiroFilterFactoryBean.setFilters(filters); + // jwt 请求单独验证 + filterChainDefinitionMap.put("/api/**", "jwt"); + // 所有请求需要认证 filterChainDefinitionMap.put("/**", "user,kickout,onlineSession,syncOnlineSession"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/auth/AllowAllCredentialsMatcher.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/auth/AllowAllCredentialsMatcher.java new file mode 100644 index 000000000..9131fdcfe --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/auth/AllowAllCredentialsMatcher.java @@ -0,0 +1,19 @@ +package com.ruoyi.framework.jwt.auth; + +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.credential.SimpleCredentialsMatcher; + +/** + * 无需验证密码 + * + * @author ruoyi + */ +public class AllowAllCredentialsMatcher extends SimpleCredentialsMatcher +{ + @Override + public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) + { + return true; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/auth/JwtToken.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/auth/JwtToken.java new file mode 100644 index 000000000..3dfc19eac --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/auth/JwtToken.java @@ -0,0 +1,45 @@ +package com.ruoyi.framework.jwt.auth; + +import org.apache.shiro.authc.UsernamePasswordToken; + +/** + * 自定义登录Token + * + * @author ruoyi + */ +public class JwtToken extends UsernamePasswordToken +{ + private static final long serialVersionUID = 1L; + + private String token; + + public JwtToken() + { + } + + public JwtToken(String username, String password, boolean rememberMe) + { + super(username, password, rememberMe); + } + + public JwtToken(String username, String password) + { + super(username, password, false); + } + + public JwtToken(String token) + { + super("", "", false); + this.token = token; + } + + public String getToken() + { + return token; + } + + public void setToken(String token) + { + this.token = token; + } +} \ No newline at end of file diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/filter/JwtFilter.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/filter/JwtFilter.java new file mode 100644 index 000000000..980a8ae4f --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/filter/JwtFilter.java @@ -0,0 +1,135 @@ +package com.ruoyi.framework.jwt.filter; + +import java.io.IOException; +import java.io.PrintWriter; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.web.filter.AccessControlFilter; +import org.apache.shiro.web.util.WebUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.RequestMethod; +import com.alibaba.fastjson.JSON; +import com.auth0.jwt.exceptions.JWTVerificationException; +import com.auth0.jwt.exceptions.TokenExpiredException; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.jwt.auth.JwtToken; + +/** + * jwt 自定义拦截器 + * + * @author ruoyi + */ +public class JwtFilter extends AccessControlFilter +{ + private static final Logger LOGGER = LoggerFactory.getLogger(JwtFilter.class); + + private static final String AUTHZ_HEADER = "token"; + + private final ThreadLocal MSG_HOLDER = new ThreadLocal<>(); + + @Override + public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception + { + return super.onPreHandle(request, response, mappedValue); + } + + @Override + protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception + { + return this.executeLogin(request, response); + } + + /** + * 执行登录方法(UserRealm判断,异常返回false) + */ + protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception + { + String token = WebUtils.toHttp(request).getHeader(AUTHZ_HEADER); + if (StringUtils.isEmpty(token)) + { + MSG_HOLDER.set("消息头不正确,header需要携带token参数"); + return false; + } + try + { + // 断是否有权限 + JwtToken jwtToken = new JwtToken(token); + this.getSubject(request, response).login(jwtToken); + return true; + } + catch (AuthenticationException e) + { + if (e.getCause() instanceof TokenExpiredException) + { + MSG_HOLDER.set("token已过期"); + } + else if (e.getCause() instanceof JWTVerificationException) + { + MSG_HOLDER.set("用户密码错误"); + } + else + { + MSG_HOLDER.set("用户信息验证失败:" + e.getMessage()); + } + return false; + } + } + + /** + * 请求前处理,处理跨域 + */ + @Override + protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception + { + HttpServletRequest httpServletRequest = (HttpServletRequest) request; + HttpServletResponse httpServletResponse = (HttpServletResponse) response; + httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin")); + httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE"); + httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers")); + // 跨域时,option请求直接返回正常状态 + if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) + { + httpServletResponse.setStatus(HttpStatus.OK.value()); + return false; + } + return super.preHandle(request, response); + } + + /** + * 异常处理 + */ + @Override + protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception + { + this.jwtFail(request, response, 401, "对不起,您无权限进行操作!"); + return false; + } + + /** + * 认证失败,异常返回 + */ + protected void jwtFail(ServletRequest request, ServletResponse response, int code, String message) + { + HttpServletResponse httpResponse = WebUtils.toHttp(response); + String contentType = "application/json;charset=UTF-8"; + httpResponse.setStatus(401); + httpResponse.setContentType(contentType); + try + { + String msg = StringUtils.isNotEmpty(MSG_HOLDER.get()) ? MSG_HOLDER.get() : message; + AjaxResult ajaxResult = new AjaxResult().put(AjaxResult.CODE_TAG, code).put(AjaxResult.MSG_TAG, msg); + PrintWriter printWriter = httpResponse.getWriter(); + printWriter.append(JSON.toJSONString(ajaxResult)); + } + catch (IOException e) + { + LOGGER.error("sendChallenge error,can not resolve httpServletResponse"); + } + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/service/IJwtTokenService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/service/IJwtTokenService.java new file mode 100644 index 000000000..79c383efa --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/service/IJwtTokenService.java @@ -0,0 +1,13 @@ +package com.ruoyi.framework.jwt.service; + +import com.ruoyi.common.core.domain.AjaxResult; + +public interface IJwtTokenService { + /** + * 获取AjaxResult格式的jwt token + * @param username 用户名 + * @param password 密码 + * @return jwtToken + */ + public AjaxResult AjaxResultJwtToken(String username, String password); +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/service/impl/JwtTokenServiceImpl.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/service/impl/JwtTokenServiceImpl.java new file mode 100644 index 000000000..65e6649b6 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/service/impl/JwtTokenServiceImpl.java @@ -0,0 +1,59 @@ +package com.ruoyi.framework.jwt.service.impl; + +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.enums.UserStatus; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.jwt.service.IJwtTokenService; +import com.ruoyi.framework.jwt.utils.JwtUtils; +import com.ruoyi.framework.shiro.service.SysPasswordService; +import com.ruoyi.system.service.ISysUserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class JwtTokenServiceImpl implements IJwtTokenService { + @Autowired + private ISysUserService userService; + + @Autowired + private SysPasswordService passwordService; + /** + * 获取AjaxResult格式的jwt token + * + * @param username 用户名 + * @param password 密码 + * @return jwtToken + */ + @Override + public AjaxResult AjaxResultJwtToken(String username, String password) { + if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) + { + return AjaxResult.error("账号和密码不能为空!"); + } + + SysUser user = userService.selectUserByLoginName(username); + if (user == null) + { + return AjaxResult.error("用户不存在/密码错误!"); + } + + if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) + { + return AjaxResult.error("对不起,您的账号已被删除!"); + } + + if (UserStatus.DISABLE.getCode().equals(user.getStatus())) + { + return AjaxResult.error("用户已封禁,请联系管理员!"); + } + + if (!passwordService.matches(user, password)) + { + return AjaxResult.error("用户不存在/密码错误!"); + } + + String token = JwtUtils.createToken(username, user.getPassword()); + return AjaxResult.success("登录成功,请妥善保管您的token信息").put("token", token); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/utils/JwtUtils.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/utils/JwtUtils.java new file mode 100644 index 000000000..c0ce30121 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/utils/JwtUtils.java @@ -0,0 +1,66 @@ +package com.ruoyi.framework.jwt.utils; + +import java.util.Date; +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.exceptions.JWTVerificationException; +import com.auth0.jwt.exceptions.TokenExpiredException; +import com.auth0.jwt.interfaces.DecodedJWT; + +/** + * jwt 工具类 + * + * @author ruoyi + */ +public class JwtUtils +{ + private static final long EXPIRE_TIME = 30 * 60 * 1000; + + private static final String CLAIM_NAME = "username"; + + public static String createToken(String username, String password) + { + return createToken(username, password, EXPIRE_TIME); + } + + public static String createToken(String username, String password, long expireTime) + { + Date date = new Date(System.currentTimeMillis() + expireTime); + // 加密处理密码 + Algorithm algorithm = Algorithm.HMAC256(password); + return JWT.create().withClaim(CLAIM_NAME, username).withExpiresAt(date).sign(algorithm); + } + + public static void verify(String username, String dbPwd, String token) + { + Algorithm algorithm = Algorithm.HMAC256(dbPwd); + JWTVerifier jwtVerifier = JWT.require(algorithm).withClaim(CLAIM_NAME, username).build(); + try + { + jwtVerifier.verify(token); + } + catch (TokenExpiredException e) + { + throw new TokenExpiredException("token已过期"); + } + catch (JWTVerificationException e) + { + throw new JWTVerificationException("token验证失败"); + } + } + + public static String getUserName(String token) + { + try + { + DecodedJWT jwt = JWT.decode(token); + return jwt.getClaim(CLAIM_NAME).asString(); + } + catch (JWTDecodeException e) + { + return null; + } + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/realm/UserRealm.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/realm/UserRealm.java index 88ad4d249..5723523a0 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/realm/UserRealm.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/realm/UserRealm.java @@ -4,6 +4,7 @@ import java.util.HashSet; import java.util.Set; import com.ruoyi.framework.shiro.util.CustToken; +import org.apache.shiro.authc.AccountException; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; @@ -23,16 +24,21 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.enums.UserStatus; import com.ruoyi.common.exception.user.CaptchaException; import com.ruoyi.common.exception.user.RoleBlockedException; import com.ruoyi.common.exception.user.UserBlockedException; +import com.ruoyi.common.exception.user.UserDeleteException; import com.ruoyi.common.exception.user.UserNotExistsException; import com.ruoyi.common.exception.user.UserPasswordNotMatchException; import com.ruoyi.common.exception.user.UserPasswordRetryLimitExceedException; import com.ruoyi.common.utils.ShiroUtils; +import com.ruoyi.framework.jwt.auth.JwtToken; +import com.ruoyi.framework.jwt.utils.JwtUtils; import com.ruoyi.framework.shiro.service.SysLoginService; import com.ruoyi.system.service.ISysMenuService; import com.ruoyi.system.service.ISysRoleService; +import com.ruoyi.system.service.ISysUserService; /** * 自定义Realm 处理登录 权限 @@ -52,6 +58,9 @@ public class UserRealm extends AuthorizingRealm @Autowired private SysLoginService loginService; + @Autowired + private ISysUserService userService; + /** * 授权 */ @@ -86,55 +95,96 @@ public class UserRealm extends AuthorizingRealm * 登录认证 */ @Override - protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) + throws AuthenticationException { - //UsernamePasswordToken upToken = (UsernamePasswordToken) token; - CustToken upToken= (CustToken) token; - String loginType = upToken.getLoginType(); - String username = upToken.getUsername(); - String password = ""; + if (authenticationToken instanceof JwtToken) + { + JwtToken jwtToken = (JwtToken) authenticationToken; + String token = jwtToken.getToken(); + String username = JwtUtils.getUserName(token); + if (username == null) + { + throw new AccountException("token 验证失败"); + } + SysUser user = userService.selectUserByLoginName(username); + if (user == null) + { + throw new AuthenticationException("用户数据不存在"); + } - if (upToken.getPassword() != null) - { - password = new String(upToken.getPassword()); - } + try + { + JwtUtils.verify(username, user.getPassword(), jwtToken.getToken()); - SysUser user = null; - try - { - user = loginService.login(username, password,loginType); + if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) + { + throw new UserDeleteException(); + } + + if (UserStatus.DISABLE.getCode().equals(user.getStatus())) + { + throw new UserBlockedException(); + } + } + catch (Exception e) + { + log.info("对用户[" + username + "]进行jwt登录验证..验证未通过{}", e.getMessage()); + throw new AuthenticationException(e.getMessage(), e); + } + + return new SimpleAuthenticationInfo(user, null, getName()); } - catch (CaptchaException e) + else { - throw new AuthenticationException(e.getMessage(), e); + //UsernamePasswordToken upToken = (UsernamePasswordToken) authenticationToken; + CustToken upToken= (CustToken) authenticationToken; + String loginType = upToken.getLoginType(); + String username = upToken.getUsername(); + String password = ""; + + if (upToken.getPassword() != null) + { + password = new String(upToken.getPassword()); + } + + SysUser user = null; + try + { + user = loginService.login(username, password,loginType); + } + catch (CaptchaException e) + { + throw new AuthenticationException(e.getMessage(), e); + } + catch (UserNotExistsException e) + { + throw new UnknownAccountException(e.getMessage(), e); + } + catch (UserPasswordNotMatchException e) + { + throw new IncorrectCredentialsException(e.getMessage(), e); + } + catch (UserPasswordRetryLimitExceedException e) + { + throw new ExcessiveAttemptsException(e.getMessage(), e); + } + catch (UserBlockedException e) + { + throw new LockedAccountException(e.getMessage(), e); + } + catch (RoleBlockedException e) + { + throw new LockedAccountException(e.getMessage(), e); + } + catch (Exception e) + { + log.info("对用户[" + username + "]进行登录验证..验证未通过{}", e.getMessage()); + throw new AuthenticationException(e.getMessage(), e); + } + SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName()); + return info; } - catch (UserNotExistsException e) - { - throw new UnknownAccountException(e.getMessage(), e); - } - catch (UserPasswordNotMatchException e) - { - throw new IncorrectCredentialsException(e.getMessage(), e); - } - catch (UserPasswordRetryLimitExceedException e) - { - throw new ExcessiveAttemptsException(e.getMessage(), e); - } - catch (UserBlockedException e) - { - throw new LockedAccountException(e.getMessage(), e); - } - catch (RoleBlockedException e) - { - throw new LockedAccountException(e.getMessage(), e); - } - catch (Exception e) - { - log.info("对用户[" + username + "]进行登录验证..验证未通过{}", e.getMessage()); - throw new AuthenticationException(e.getMessage(), e); - } - SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName()); - return info; } /** diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java index d9812d8a4..21a9c876d 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java @@ -13,6 +13,7 @@ import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.exception.BusinessException; import com.ruoyi.common.exception.DemoModeException; import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.security.PermissionUtils; /** @@ -32,7 +33,7 @@ public class GlobalExceptionHandler public Object handleAuthorizationException(HttpServletRequest request, AuthorizationException e) { log.error(e.getMessage(), e); - if (ServletUtils.isAjaxRequest(request)) + if (ServletUtils.isAjaxRequest(request) || StringUtils.isNotEmpty(request.getHeader("token"))) { return AjaxResult.error(PermissionUtils.getMsg(e.getMessage())); } From e44e212af59f598df1d53e8cb1419cff28b6cc57 Mon Sep 17 00:00:00 2001 From: "bo.yang" Date: Wed, 4 Aug 2021 17:33:15 +0800 Subject: [PATCH 24/28] =?UTF-8?q?=E9=9B=86=E6=88=90Jwt=E7=99=BB=E5=BD=95?= =?UTF-8?q?=EF=BC=8C=E6=94=AF=E6=8C=81TOPGP=E8=AE=BF=E9=97=AE=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=20Swagger=E6=96=87=E6=A1=A3=E5=AE=8C=E5=96=84?= =?UTF-8?q?=EF=BC=88=E5=BF=AB=E9=80=92=E3=80=81=E5=B8=86=E8=BD=AF=E3=80=81?= =?UTF-8?q?TOPGP=E8=AE=BF=E9=97=AE)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-admin/src/main/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index 3ccd384ad..ecdd5485c 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -145,6 +145,6 @@ swagger: wechat: corpId: ww4ed3771457e5f463 agentId: 1000080 - secret: drtHKYabI9_EgjJQ8aqDPTQkY1WUYeWUTMkYw7D_z64 + secret: drtHKYabI9_EgjJQ8aqDPRpt3XiM4d1znaVODMCSdvc token: 111 aesKey: 111 \ No newline at end of file From 83bce07713fcea1d72578fb078b5e23b4b6bc631 Mon Sep 17 00:00:00 2001 From: "bo.yang" Date: Wed, 4 Aug 2021 20:30:37 +0800 Subject: [PATCH 25/28] =?UTF-8?q?=E5=BF=AB=E9=80=92100=E6=8E=88=E6=9D=83?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=EF=BC=8C=E6=94=B9=E5=88=B0yml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/ExpSubsPushApiServiceImpl.java | 11 +++++--- box-bps/src/main/resources/account.properties | 12 ++++----- .../src/main/resources/application.yml | 26 ++++++++++++++++--- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/box-bps/src/main/java/com/ruoyi/bps/service/impl/ExpSubsPushApiServiceImpl.java b/box-bps/src/main/java/com/ruoyi/bps/service/impl/ExpSubsPushApiServiceImpl.java index 96c5e6fa9..c0e782aa9 100644 --- a/box-bps/src/main/java/com/ruoyi/bps/service/impl/ExpSubsPushApiServiceImpl.java +++ b/box-bps/src/main/java/com/ruoyi/bps/service/impl/ExpSubsPushApiServiceImpl.java @@ -24,6 +24,7 @@ import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.StringUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import javax.servlet.http.HttpServletRequest; @@ -34,14 +35,16 @@ import java.util.Map; @Service public class ExpSubsPushApiServiceImpl implements IExpSubsPushApiService { - String key = PropertiesReader.get("key"); + /*String key = PropertiesReader.get("key"); String customer = PropertiesReader.get("customer"); String secret = PropertiesReader.get("secret"); String siid = PropertiesReader.get("siid"); String userid = PropertiesReader.get("userid"); String tid = PropertiesReader.get("tid"); String secret_key = PropertiesReader.get("secret_key"); - String secret_secret = PropertiesReader.get("secret_secret"); + String secret_secret = PropertiesReader.get("secret_secret"); */ + @Value("${express.key}") + private String key; @Autowired private IExpSubsPushRespService expSubsPushRespService; @@ -55,7 +58,9 @@ public class ExpSubsPushApiServiceImpl implements IExpSubsPushApiService { */ @Override public SubscribeResp ExpressSubscribe(ExpSubscribe expSubscribe) { - String subscribeFrom= expSubscribe.getSalt().equals("topgp")?"topgp":"bpsemi"; + + String loginFrom= expSubscribe.getSalt(); + String subscribeFrom= StringUtils.isNotEmpty(loginFrom)?loginFrom.equals("topgp")?"topgp":"localhost":"localhost"; SubscribeParameters subscribeParameters = new SubscribeParameters(); SubscribeResp subscribeResp = new SubscribeResp(); diff --git a/box-bps/src/main/resources/account.properties b/box-bps/src/main/resources/account.properties index c7f7dc6a0..eca321a27 100644 --- a/box-bps/src/main/resources/account.properties +++ b/box-bps/src/main/resources/account.properties @@ -1,9 +1,9 @@ #快递100的基础账号信息,可以在这里获取 # https://poll.kuaidi100.com/manager/page/myinfo/enterprise -key = Jydbrxsm2311 -customer = 2DD48B3469B82F2B7700569093AB792B -secret = 8781ed9b35a7438499eb02fee915915a -userid = 2a62da2192c24d17a943ff78ee64f8c6 +#key = kzuyKyAE3985 +#customer = 6774D6F41D773B17027EEBE5CC902C9E +#secret = 4fc7633a027c4fe1a68b68237c236d6e +#userid = bfc0389a986f45c4b36e27d9b18b7bd3 #电子面单快递公司账号信息(非必填) partnerId = @@ -20,6 +20,6 @@ secret_key = secret_secret = #是否记录快递100接口返回结果,建议记录日志或者入库,方便后期有问题双方排查(true:启用 false: 关闭 ) -log.return.record = true +#log.return.record = true #日志记录位置,建议根据自身情况配置 -logPath = logs \ No newline at end of file +#logPath = logs \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index ecdd5485c..7ec588bd5 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -144,7 +144,27 @@ swagger: #企业微信 wechat: corpId: ww4ed3771457e5f463 - agentId: 1000080 - secret: drtHKYabI9_EgjJQ8aqDPRpt3XiM4d1znaVODMCSdvc + agentId: 1000082 + secret: PqTYlveYQc54T13QS-cDyuAesDaGgyMSgpZLXBNJ-Uc token: 111 - aesKey: 111 \ No newline at end of file + aesKey: 111 + +#快递100 +express: + #快递100的基础账号信息,可以在这里获取 + # https://poll.kuaidi100.com/manager/page/myinfo/enterprise + key: kzuyKyAE3985 + customer: 6774D6F41D773B17027EEBE5CC902C9E + secret: 4fc7633a027c4fe1a68b68237c236d6e + userid: bfc0389a986f45c4b36e27d9b18b7bd3 + #电子面单快递公司账号信息(非必填) + partnerId: + partnerKey: + net: + siid: + #短信模板id(非必填) + tid: + #云平台相关(非必填) + #登录云平台 https://cloud.kuaidi100.com/buyer/user/info + secret_key: + secret_secret: \ No newline at end of file From 69d476e62854c6630a9fb499e019a29720369d3a Mon Sep 17 00:00:00 2001 From: Bo Date: Sat, 7 Aug 2021 22:28:47 +0800 Subject: [PATCH 26/28] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=BF=AB=E9=80=92?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/ExpSubsPushApiServiceImpl.java | 16 ++++++++++------ .../service/impl/ExpressInfoServiceImpl.java | 17 +++++++++-------- .../bps/service/impl/ExpressServiceImpl.java | 12 ++++++++++-- 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/box-bps/src/main/java/com/ruoyi/bps/service/impl/ExpSubsPushApiServiceImpl.java b/box-bps/src/main/java/com/ruoyi/bps/service/impl/ExpSubsPushApiServiceImpl.java index c0e782aa9..ac794b775 100644 --- a/box-bps/src/main/java/com/ruoyi/bps/service/impl/ExpSubsPushApiServiceImpl.java +++ b/box-bps/src/main/java/com/ruoyi/bps/service/impl/ExpSubsPushApiServiceImpl.java @@ -13,7 +13,6 @@ import com.kuaidi100.sdk.response.SubscribePushData; import com.kuaidi100.sdk.response.SubscribePushParamResp; import com.kuaidi100.sdk.response.SubscribePushResult; import com.kuaidi100.sdk.response.SubscribeResp; -import com.kuaidi100.sdk.utils.PropertiesReader; import com.kuaidi100.sdk.utils.SignUtils; import com.ruoyi.bps.domain.ExpSubsPushResp; import com.ruoyi.bps.domain.ExpSubscribe; @@ -282,10 +281,13 @@ public class ExpSubsPushApiServiceImpl implements IExpSubsPushApiService { String phone = contentJson.getString("phone"); //如果出货单号或者快递单号为空,则返回错误信息,并写入Logo if(StringUtils.isEmpty(deliveryNo) || StringUtils.isEmpty(expressNo)){ + SubscribeResp subscribeResp=new SubscribeResp(); + subscribeResp.setMessage("快递单号或出货单号为空"); + subscribeResp.setResult(false); + subscribeResp.setReturnCode("700"); + Map map=new HashMap<>(); - map.put("result",false); - map.put("returnCode",700); - map.put("message","快递单号或出货单号为空!"); + map.put("subscribeResp",subscribeResp); map.put("deliveryNo",deliveryNo); map.put("expressNo",expressNo); //写入Logo @@ -303,8 +305,10 @@ public class ExpSubsPushApiServiceImpl implements IExpSubsPushApiService { expSubscribe.setSalt("topgp"); //偷懒,把请求来源记录到salt栏位,不再增加exp_subscribe字段了。。以后找时间改吧 SubscribeResp subscribeResp= ExpressSubscribe(expSubscribe); - Object object = JSONObject.toJSON(subscribeResp); - Map map=JSONObject.parseObject(object.toString(), Map.class); + /*Object object = JSONObject.toJSON(subscribeResp); + Map map=JSONObject.parseObject(object.toString(), Map.class);*/ + Map map= new HashMap<>(); + map.put("expSubscribe",subscribeResp); map.put("deliveryNo",deliveryNo); map.put("expressNo",expressNo); return JSONObject.toJSONString(map); diff --git a/box-bps/src/main/java/com/ruoyi/bps/service/impl/ExpressInfoServiceImpl.java b/box-bps/src/main/java/com/ruoyi/bps/service/impl/ExpressInfoServiceImpl.java index 6e1f80630..e9f6093cf 100644 --- a/box-bps/src/main/java/com/ruoyi/bps/service/impl/ExpressInfoServiceImpl.java +++ b/box-bps/src/main/java/com/ruoyi/bps/service/impl/ExpressInfoServiceImpl.java @@ -21,6 +21,7 @@ import com.kuaidi100.sdk.response.QueryTrackResp; import com.kuaidi100.sdk.utils.PropertiesReader; import com.kuaidi100.sdk.utils.SignUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.util.ArrayList; @@ -37,13 +38,6 @@ import java.util.List; public class ExpressInfoServiceImpl implements IExpressInfoService { /* - String key = "Jydbrxsm2311"; - String customer = "2DD48B3469B82F2B7700569093AB792B"; - String secret = "8781ed9b35a7438499eb02fee915915a"; - String userid = "2a62da2192c24d17a943ff78ee64f8c6"; - - */ - String key = PropertiesReader.get("key"); String customer = PropertiesReader.get("customer"); String secret = PropertiesReader.get("secret"); @@ -52,6 +46,13 @@ public class ExpressInfoServiceImpl implements IExpressInfoService String tid = PropertiesReader.get("tid"); String secret_key = PropertiesReader.get("secret_key"); String secret_secret = PropertiesReader.get("secret_secret"); + */ + @Value("${express.key}") + private String key; + + @Value("${express.customer}") + private String customer; + @@ -132,7 +133,7 @@ public class ExpressInfoServiceImpl implements IExpressInfoService //如果没有输入快递公司编号,则查询快递公司编号 if(StringUtils.isEmpty(com)){ if(AutoGetExpressCom(nu)==null){ - callbackExpressInfo.setData("根据快递单号查询不到快递公司,请确认快递单号是否正确!"); + callbackExpressInfo.setData("请提供要查询的快递所属物流公司编号!"); return callbackExpressInfo; } com=AutoGetExpressCom(nu).getComCode(); diff --git a/box-bps/src/main/java/com/ruoyi/bps/service/impl/ExpressServiceImpl.java b/box-bps/src/main/java/com/ruoyi/bps/service/impl/ExpressServiceImpl.java index 7be4e0eb5..b71b0370a 100644 --- a/box-bps/src/main/java/com/ruoyi/bps/service/impl/ExpressServiceImpl.java +++ b/box-bps/src/main/java/com/ruoyi/bps/service/impl/ExpressServiceImpl.java @@ -14,6 +14,7 @@ import com.kuaidi100.sdk.utils.SignUtils; import com.ruoyi.bps.service.IExpressService; import org.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.util.ArrayList; @@ -27,14 +28,21 @@ public class ExpressServiceImpl implements IExpressService { String secret = "8781ed9b35a7438499eb02fee915915a"; String userid = "2a62da2192c24d17a943ff78ee64f8c6"; */ - String key = PropertiesReader.get("key"); + /*String key = PropertiesReader.get("key"); String customer = PropertiesReader.get("customer"); String secret = PropertiesReader.get("secret"); String siid = PropertiesReader.get("siid"); String userid = PropertiesReader.get("userid"); String tid = PropertiesReader.get("tid"); String secret_key = PropertiesReader.get("secret_key"); - String secret_secret = PropertiesReader.get("secret_secret"); + String secret_secret = PropertiesReader.get("secret_secret");*/ + + @Value("${express.key}") + private String key; + + @Value("${express.customer}") + private String customer; + String msg=""; @Autowired IExpressService expressService; From 2723b5cd14f3af9215f1fe52b212a98c8c1942e6 Mon Sep 17 00:00:00 2001 From: Bo Date: Mon, 9 Aug 2021 10:29:30 +0800 Subject: [PATCH 27/28] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=AE=BF=E9=97=AETopgp?= =?UTF-8?q?=20Webservice=E6=8E=A5=E5=8F=A3Demo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../conrtroller/XmlWebserviceController.java | 28 ++++++ ruoyi-common/pom.xml | 7 ++ .../java/com/ruoyi/common/utils/XmlUtils.java | 73 ++++++++++++++++ .../ruoyi/common/utils/http/HttpUtils.java | 86 +++++++++++++++---- 4 files changed, 177 insertions(+), 17 deletions(-) create mode 100644 box-test/src/main/java/com/ruoyi/test/conrtroller/XmlWebserviceController.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/XmlUtils.java diff --git a/box-test/src/main/java/com/ruoyi/test/conrtroller/XmlWebserviceController.java b/box-test/src/main/java/com/ruoyi/test/conrtroller/XmlWebserviceController.java new file mode 100644 index 000000000..5efb3a233 --- /dev/null +++ b/box-test/src/main/java/com/ruoyi/test/conrtroller/XmlWebserviceController.java @@ -0,0 +1,28 @@ +package com.ruoyi.test.conrtroller; + +import com.ruoyi.common.utils.XmlUtils; +import com.ruoyi.common.utils.http.HttpUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +@RestController + +public class XmlWebserviceController { + //private static final Logger log = LoggerFactory.getLogger(HttpUtils.class); + + @PostMapping("/anon/sendXml") + public String SendXml() { + Map map = new HashMap<>(); + map.put("responseInfo", "此处为测试消息"); + String param = XmlUtils.GetTopgpRequestXml("express_testRequest", map); + String url = "http://192.168.2.81:85/web/ws/r/aws_ttsrv2_toptest"; + String returnXml = HttpUtils.sendXmlPost(url,param); + return XmlUtils.GetStatusFromTopgpResponse(returnXml).toString(); + + } +} diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml index edd31141d..bf5675cd1 100644 --- a/ruoyi-common/pom.xml +++ b/ruoyi-common/pom.xml @@ -101,6 +101,13 @@ javax.servlet-api + + org.json + json + 20160810 + compile + + \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/XmlUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/XmlUtils.java new file mode 100644 index 000000000..2278c008c --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/XmlUtils.java @@ -0,0 +1,73 @@ +package com.ruoyi.common.utils; + +import com.ruoyi.common.utils.http.HttpUtils; +import org.json.JSONObject; +import org.json.XML; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; + +public class XmlUtils { + private static final Logger log = LoggerFactory.getLogger(HttpUtils.class); + + /** + * 组合TOPGP所需的XML格式 + * @param tip 调用TOPGP的Webservice的方法名 如:express_testRequest + * @param mapInfo XML中Filed对应的键值对Map + * @return xml字符串 + */ + public static String GetTopgpRequestXml(String tip, Map mapInfo){ + log.info("=======生成xml======"); + StringBuffer stringBuffer = new StringBuffer(); + stringBuffer.append("\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " <Request>\n" + + " <Access>\n" + + " <Authentication user='topgui' password='' />\n" + + " <Connection application='bps' source='www.bpsemi.com' />\n" + + " <Organization name='SYSTEM' />\n" + + " <Locale language='zh_cn' />\n" + + " </Access>\n" + + " <RequestContent>\n" + + " <Parameter>\n" + + " <Record>\n" + + " <Field name="); + for(String key:mapInfo.keySet()){ + stringBuffer.append("'"+key+"' value='" +mapInfo.get(key).toString().replaceAll("&","&")+"' />\n"); + } + stringBuffer.append(" </Record>\n" + + " </Parameter>\n" + + " <Document/>\n" + + " </RequestContent>\n" + + " </Request>\n" + + " \n" + + " \n" + + " \n" + + ""); + log.info("=======生成xml结束======"); + return stringBuffer.toString(); + } + + /** + * 将TOPGP返回的XML转化为Json,并提出返回Status + * @param TopgpResonseXml 调用TOPGP的Webservice的方法名 如:express_testRequest + * @return Status JsonObject + */ + public static JSONObject GetStatusFromTopgpResponse(String TopgpResonseXml) { + JSONObject jsonObject = XML.toJSONObject(TopgpResonseXml); + + JSONObject envelope = jsonObject.getJSONObject("SOAP-ENV:Envelope"); + JSONObject body = envelope.getJSONObject("SOAP-ENV:Body"); + JSONObject express_testResponse = body.getJSONObject("fjs1:express_testResponse"); + JSONObject fjs1Response = express_testResponse.getJSONObject("fjs1:response"); + JSONObject response = fjs1Response.getJSONObject("Response"); + JSONObject execution = response.getJSONObject("Execution"); + return execution.getJSONObject("Status"); + } + + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java index 11c8404b7..dcaa8c5a2 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java @@ -1,10 +1,14 @@ package com.ruoyi.common.utils.http; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.PrintWriter; +import com.ruoyi.common.constant.Constants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import javax.net.ssl.*; +import java.io.*; import java.net.ConnectException; import java.net.SocketTimeoutException; import java.net.URL; @@ -12,18 +16,6 @@ import java.net.URLConnection; import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.Map; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSession; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import com.ruoyi.common.constant.Constants; -import org.springframework.http.ResponseEntity; -import org.springframework.web.client.RestClientException; -import org.springframework.web.client.RestTemplate; /** * 通用http发送方法 @@ -265,12 +257,72 @@ public class HttpUtils } } + /** + * 向指定 URL 发送xml POST方法的请求 + * + * @param url 发送请求的 URL + * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @return 所代表远程资源的响应结果 + *Author yangbo + */ + public static String sendXmlPost(String url, String param) { + PrintWriter out = null; + BufferedReader in = null; + StringBuilder result = new StringBuilder(); + try { + String urlNameString = url; + log.info("sendPost - {}", urlNameString); + URL realUrl = new URL(urlNameString); + URLConnection conn = realUrl.openConnection(); + conn.setRequestProperty("accept", "*/*"); + conn.setRequestProperty("connection", "Keep-Alive"); + conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + conn.setRequestProperty("Accept-Charset", "utf-8"); + conn.setRequestProperty("Content-Type", "text/xml; charset=UTF-8"); //发送xml需加上此请求头 + conn.addRequestProperty("SOAPAction", "\"\""); //向topgp发送xml必须加上该Name=“SOAPAction", Value="\"\"" ,否则会报415错误,不能识别XML. + conn.setDoOutput(true); + conn.setDoInput(true); + out = new PrintWriter(conn.getOutputStream()); + out.print(param); + out.flush(); + in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8")); + String line; + while ((line = in.readLine()) != null) { + result.append(line); + } + log.info("recv - {}", result); + } catch (ConnectException e) { + log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e); + } catch (SocketTimeoutException e) { + log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e); + } catch (IOException e) { + log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e); + } catch (Exception e) { + log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e); + } finally { + try { + if (out != null) { + out.close(); + } + if (in != null) { + in.close(); + } + } catch (IOException ex) { + log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); + } + } + return result.toString().replace("<","<").replace(">",">"); + } + + /** * 向指定 Restful接口 发送POST方法的请求 * * @param url 发送请求的 URL * @param params 请求参数,请求参数为json的形式。例:params="{\"params\":{\"pagesize\":1000}}" * @return 返回Map, Key="statusCode",接口访问返回状态, key="result":接口返回接果 + * + * author yangbo */ //public static Map sendPostWithRest(String url, String params){ //如果参数为String类型,推送企业微信消息会乱码,因此改为Object类型,直接推送Map --yangbo 20210729 From 051a7695da94135960cc36b93a3700e7820280f6 Mon Sep 17 00:00:00 2001 From: Bo Date: Mon, 9 Aug 2021 11:50:32 +0800 Subject: [PATCH 28/28] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=AE=BF=E9=97=AETopgp?= =?UTF-8?q?=20Webservice=E6=8E=A5=E5=8F=A3Demo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ruoyi/test/conrtroller/XmlWebserviceController.java | 8 +++----- .../common/utils/{XmlUtils.java => TopgpXmlUtils.java} | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) rename ruoyi-common/src/main/java/com/ruoyi/common/utils/{XmlUtils.java => TopgpXmlUtils.java} (99%) diff --git a/box-test/src/main/java/com/ruoyi/test/conrtroller/XmlWebserviceController.java b/box-test/src/main/java/com/ruoyi/test/conrtroller/XmlWebserviceController.java index 5efb3a233..831579088 100644 --- a/box-test/src/main/java/com/ruoyi/test/conrtroller/XmlWebserviceController.java +++ b/box-test/src/main/java/com/ruoyi/test/conrtroller/XmlWebserviceController.java @@ -1,9 +1,7 @@ package com.ruoyi.test.conrtroller; -import com.ruoyi.common.utils.XmlUtils; +import com.ruoyi.common.utils.TopgpXmlUtils; import com.ruoyi.common.utils.http.HttpUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; @@ -19,10 +17,10 @@ public class XmlWebserviceController { public String SendXml() { Map map = new HashMap<>(); map.put("responseInfo", "此处为测试消息"); - String param = XmlUtils.GetTopgpRequestXml("express_testRequest", map); + String param = TopgpXmlUtils.GetTopgpRequestXml("express_testRequest", map); String url = "http://192.168.2.81:85/web/ws/r/aws_ttsrv2_toptest"; String returnXml = HttpUtils.sendXmlPost(url,param); - return XmlUtils.GetStatusFromTopgpResponse(returnXml).toString(); + return TopgpXmlUtils.GetStatusFromTopgpResponse(returnXml).toString(); } } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/XmlUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/TopgpXmlUtils.java similarity index 99% rename from ruoyi-common/src/main/java/com/ruoyi/common/utils/XmlUtils.java rename to ruoyi-common/src/main/java/com/ruoyi/common/utils/TopgpXmlUtils.java index 2278c008c..d5dc2ae38 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/XmlUtils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/TopgpXmlUtils.java @@ -8,7 +8,7 @@ import org.slf4j.LoggerFactory; import java.util.Map; -public class XmlUtils { +public class TopgpXmlUtils { private static final Logger log = LoggerFactory.getLogger(HttpUtils.class); /**