Kaynağa Gözat

动态菜单及获取服务器信息

xusl 2 yıl önce
ebeveyn
işleme
132ae43329
37 değiştirilmiş dosya ile 1380 ekleme ve 688 silme
  1. 16 0
      backend/pom.xml
  2. 11 6
      backend/src/main/java/com/jiayue/ssi/entity/Server.java
  3. 3 2
      backend/src/main/java/com/jiayue/ssi/entity/server/Jvm.java
  4. 2 1
      backend/src/main/java/com/jiayue/ssi/entity/server/Mem.java
  5. 184 0
      backend/src/main/java/com/jiayue/ssi/util/DateUtils.java
  6. 304 28
      backend/src/main/java/com/jiayue/ssi/util/IPUtils.java
  7. 82 0
      backend/src/main/java/oshi/util/Util.java
  8. 44 46
      ui/package.json
  9. 57 0
      ui/src/api/monitor/cache.js
  10. 71 0
      ui/src/api/monitor/job.js
  11. 26 0
      ui/src/api/monitor/jobLog.js
  12. 34 0
      ui/src/api/monitor/logininfor.js
  13. 18 0
      ui/src/api/monitor/online.js
  14. 26 0
      ui/src/api/monitor/operlog.js
  15. 9 0
      ui/src/api/monitor/server.js
  16. 1 2
      ui/src/directive/permission/hasPermi.js
  17. 0 1
      ui/src/layout/components/Sidebar/FixiOSBug.js
  18. 5 1
      ui/src/layout/components/Sidebar/Item.vue
  19. 17 10
      ui/src/layout/components/Sidebar/Link.vue
  20. 18 7
      ui/src/layout/components/Sidebar/Logo.vue
  21. 9 4
      ui/src/layout/components/Sidebar/SidebarItem.vue
  22. 48 90
      ui/src/layout/components/Sidebar/index.vue
  23. 5 287
      ui/src/main.js
  24. 9 4
      ui/src/permission.js
  25. 6 7
      ui/src/router/index.js
  26. 0 6
      ui/src/store/getters.js
  27. 2 3
      ui/src/store/index.js
  28. 133 133
      ui/src/store/modules/permission.js
  29. 22 0
      ui/src/store/modules/user.js
  30. 1 0
      ui/src/utils/menus.js
  31. 123 43
      ui/src/utils/request.js
  32. 87 0
      ui/src/utils/smutil.js
  33. 2 2
      ui/src/views/dashboard/index.vue
  34. 2 2
      ui/src/views/monitor/server/index.vue
  35. 1 1
      ui/src/views/sysManager/userManager/profile/index.vue
  36. 1 1
      ui/src/views/sysManager/userManager/profile/resetPwd.vue
  37. 1 1
      ui/vue.config.js

+ 16 - 0
backend/pom.xml

@@ -192,6 +192,22 @@
             <artifactId>bcprov-jdk15to18</artifactId>
             <version>1.72</version>
         </dependency>
+        <!-- 获取系统信息 -->
+        <dependency>
+            <groupId>com.github.oshi</groupId>
+            <artifactId>oshi-core</artifactId>
+            <version>6.4.0</version>
+        </dependency>
+        <dependency>
+            <groupId>net.java.dev.jna</groupId>
+            <artifactId>jna</artifactId>
+            <version>5.5.0</version>
+        </dependency>
+        <dependency>
+            <groupId>net.java.dev.jna</groupId>
+            <artifactId>jna-platform</artifactId>
+            <version>5.5.0</version>
+        </dependency>
     </dependencies>
     <build>
         <plugins>

+ 11 - 6
backend/src/main/java/com/jiayue/ssi/entity/Server.java

@@ -1,24 +1,29 @@
 package com.jiayue.ssi.entity;
 
+import com.jiayue.ssi.entity.server.*;
 import java.net.UnknownHostException;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Properties;
 
-import com.ruoyi.common.utils.Arith;
-import com.ruoyi.common.utils.ip.IpUtils;
-import com.ruoyi.framework.web.domain.server.*;
+import java.net.UnknownHostException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Properties;
 
+import com.jiayue.ssi.util.Arith;
+import com.jiayue.ssi.util.IPUtils;
 import oshi.SystemInfo;
 import oshi.hardware.CentralProcessor;
+import oshi.hardware.CentralProcessor.TickType;
 import oshi.hardware.GlobalMemory;
 import oshi.hardware.HardwareAbstractionLayer;
-import oshi.hardware.CentralProcessor.TickType;
 import oshi.software.os.FileSystem;
 import oshi.software.os.OSFileStore;
 import oshi.software.os.OperatingSystem;
 import oshi.util.Util;
 
+
 /**
  * 服务器相关信息
  *
@@ -161,8 +166,8 @@ public class Server
     private void setSysInfo()
     {
         Properties props = System.getProperties();
-        sys.setComputerName(IpUtils.getHostName());
-        sys.setComputerIp(IpUtils.getHostIp());
+        sys.setComputerName(IPUtils.getHostName());
+        sys.setComputerIp(IPUtils.getHostIp());
         sys.setOsName(props.getProperty("os.name"));
         sys.setOsArch(props.getProperty("os.arch"));
         sys.setUserDir(props.getProperty("user.dir"));

+ 3 - 2
backend/src/main/java/com/jiayue/ssi/entity/server/Jvm.java

@@ -1,9 +1,10 @@
 package com.jiayue.ssi.entity.server;
 
+import com.jiayue.ssi.util.Arith;
+import com.jiayue.ssi.util.DateUtils;
+
 import java.lang.management.ManagementFactory;
 
-import com.ruoyi.common.utils.Arith;
-import com.ruoyi.common.utils.DateUtils;
 
 /**
  * JVM相关信息

+ 2 - 1
backend/src/main/java/com/jiayue/ssi/entity/server/Mem.java

@@ -1,6 +1,7 @@
 package com.jiayue.ssi.entity.server;
 
-import com.ruoyi.common.utils.Arith;
+
+import com.jiayue.ssi.util.Arith;
 
 /**
  * 內存相关信息

+ 184 - 0
backend/src/main/java/com/jiayue/ssi/util/DateUtils.java

@@ -0,0 +1,184 @@
+package com.jiayue.ssi.util;
+
+import java.lang.management.ManagementFactory;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.*;
+import java.util.Date;
+
+import org.apache.commons.lang3.time.DateFormatUtils;
+
+/**
+ * 时间工具类
+ *
+ * @author ruoyi
+ */
+public class DateUtils extends org.apache.commons.lang3.time.DateUtils
+{
+    public static String YYYY = "yyyy";
+
+    public static String YYYY_MM = "yyyy-MM";
+
+    public static String YYYY_MM_DD = "yyyy-MM-dd";
+
+    public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
+
+    public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
+
+    private static String[] parsePatterns = {
+            "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
+            "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
+            "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
+
+    /**
+     * 获取当前Date型日期
+     *
+     * @return Date() 当前日期
+     */
+    public static Date getNowDate()
+    {
+        return new Date();
+    }
+
+    /**
+     * 获取当前日期, 默认格式为yyyy-MM-dd
+     *
+     * @return String
+     */
+    public static String getDate()
+    {
+        return dateTimeNow(YYYY_MM_DD);
+    }
+
+    public static final String getTime()
+    {
+        return dateTimeNow(YYYY_MM_DD_HH_MM_SS);
+    }
+
+    public static final String dateTimeNow()
+    {
+        return dateTimeNow(YYYYMMDDHHMMSS);
+    }
+
+    public static final String dateTimeNow(final String format)
+    {
+        return parseDateToStr(format, new Date());
+    }
+
+    public static final String dateTime(final Date date)
+    {
+        return parseDateToStr(YYYY_MM_DD, date);
+    }
+
+    public static final String parseDateToStr(final String format, final Date date)
+    {
+        return new SimpleDateFormat(format).format(date);
+    }
+
+    public static final Date dateTime(final String format, final String ts)
+    {
+        try
+        {
+            return new SimpleDateFormat(format).parse(ts);
+        }
+        catch (ParseException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 日期路径 即年/月/日 如2018/08/08
+     */
+    public static final String datePath()
+    {
+        Date now = new Date();
+        return DateFormatUtils.format(now, "yyyy/MM/dd");
+    }
+
+    /**
+     * 日期路径 即年/月/日 如20180808
+     */
+    public static final String dateTime()
+    {
+        Date now = new Date();
+        return DateFormatUtils.format(now, "yyyyMMdd");
+    }
+
+    /**
+     * 日期型字符串转化为日期 格式
+     */
+    public static Date parseDate(Object str)
+    {
+        if (str == null)
+        {
+            return null;
+        }
+        try
+        {
+            return parseDate(str.toString(), parsePatterns);
+        }
+        catch (ParseException e)
+        {
+            return null;
+        }
+    }
+
+    /**
+     * 获取服务器启动时间
+     */
+    public static Date getServerStartDate()
+    {
+        long time = ManagementFactory.getRuntimeMXBean().getStartTime();
+        return new Date(time);
+    }
+
+    /**
+     * 计算相差天数
+     */
+    public static int differentDaysByMillisecond(Date date1, Date date2)
+    {
+        return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24)));
+    }
+
+    /**
+     * 计算两个时间差
+     */
+    public static String getDatePoor(Date endDate, Date nowDate)
+    {
+        long nd = 1000 * 24 * 60 * 60;
+        long nh = 1000 * 60 * 60;
+        long nm = 1000 * 60;
+        // long ns = 1000;
+        // 获得两个时间的毫秒时间差异
+        long diff = endDate.getTime() - nowDate.getTime();
+        // 计算差多少天
+        long day = diff / nd;
+        // 计算差多少小时
+        long hour = diff % nd / nh;
+        // 计算差多少分钟
+        long min = diff % nd % nh / nm;
+        // 计算差多少秒//输出结果
+        // long sec = diff % nd % nh % nm / ns;
+        return day + "天" + hour + "小时" + min + "分钟";
+    }
+
+    /**
+     * 增加 LocalDateTime ==> Date
+     */
+    public static Date toDate(LocalDateTime temporalAccessor)
+    {
+        ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault());
+        return Date.from(zdt.toInstant());
+    }
+
+    /**
+     * 增加 LocalDate ==> Date
+     */
+    public static Date toDate(LocalDate temporalAccessor)
+    {
+        LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0));
+        ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
+        return Date.from(zdt.toInstant());
+    }
+}

+ 304 - 28
backend/src/main/java/com/jiayue/ssi/util/IPUtils.java

@@ -1,6 +1,11 @@
 package com.jiayue.ssi.util;
 
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
 import javax.servlet.http.HttpServletRequest;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
 
 /**
  * IP地址工具
@@ -9,42 +14,313 @@ import javax.servlet.http.HttpServletRequest;
  * @since 2023/02/23
  */
 public class IPUtils {
+    public final static String REGX_0_255 = "(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)";
+    // 匹配 ip
+    public final static String REGX_IP = "((" + REGX_0_255 + "\\.){3}" + REGX_0_255 + ")";
+    public final static String REGX_IP_WILDCARD = "(((\\*\\.){3}\\*)|(" + REGX_0_255 + "(\\.\\*){3})|(" + REGX_0_255
+        + "\\." + REGX_0_255 + ")(\\.\\*){2}" + "|((" + REGX_0_255 + "\\.){3}\\*))";
+    // 匹配网段
+    public final static String REGX_IP_SEG = "(" + REGX_IP + "\\-" + REGX_IP + ")";
+
     /**
-     * 获取IP地址
-     * <p>
-     * 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址
-     * 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址
+     * 获取客户端IP
+     *
+     * @return IP地址
+     */
+    public static String getIpAddr() {
+        return getIpAddr(((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest());
+    }
+
+    /**
+     * 获取客户端IP
+     *
+     * @param request 请求对象
+     * @return IP地址
      */
     public static String getIpAddr(HttpServletRequest request) {
-        String ip = null;
-        try {
-            ip = request.getHeader("x-forwarded-for");
-            if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) { // 多次反向代理后会有多个ip值,第一个ip才是真实ip
-                if (ip.indexOf(",") != -1) {
-                    ip = ip.split(",")[0];
+        if (request == null) {
+            return "unknown";
+        }
+        String ip = request.getHeader("x-forwarded-for");
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("Proxy-Client-IP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("X-Forwarded-For");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("WL-Proxy-Client-IP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("X-Real-IP");
+        }
+
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getRemoteAddr();
+        }
+
+        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip);
+    }
+
+    /**
+     * 检查是否为内部IP地址
+     *
+     * @param ip IP地址
+     * @return 结果
+     */
+    public static boolean internalIp(String ip) {
+        byte[] addr = textToNumericFormatV4(ip);
+        return internalIp(addr) || "127.0.0.1".equals(ip);
+    }
+
+    /**
+     * 检查是否为内部IP地址
+     *
+     * @param addr byte地址
+     * @return 结果
+     */
+    private static boolean internalIp(byte[] addr) {
+        if (RyStringUtils.isNull(addr) || addr.length < 2) {
+            return true;
+        }
+        final byte b0 = addr[0];
+        final byte b1 = addr[1];
+        // 10.x.x.x/8
+        final byte SECTION_1 = 0x0A;
+        // 172.16.x.x/12
+        final byte SECTION_2 = (byte)0xAC;
+        final byte SECTION_3 = (byte)0x10;
+        final byte SECTION_4 = (byte)0x1F;
+        // 192.168.x.x/16
+        final byte SECTION_5 = (byte)0xC0;
+        final byte SECTION_6 = (byte)0xA8;
+        switch (b0) {
+            case SECTION_1:
+                return true;
+            case SECTION_2:
+                if (b1 >= SECTION_3 && b1 <= SECTION_4) {
+                    return true;
                 }
+            case SECTION_5:
+                switch (b1) {
+                    case SECTION_6:
+                        return true;
+                }
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * 将IPv4地址转换成字节
+     *
+     * @param text IPv4地址
+     * @return byte 字节
+     */
+    public static byte[] textToNumericFormatV4(String text) {
+        if (text.length() == 0) {
+            return null;
+        }
+
+        byte[] bytes = new byte[4];
+        String[] elements = text.split("\\.", -1);
+        try {
+            long l;
+            int i;
+            switch (elements.length) {
+                case 1:
+                    l = Long.parseLong(elements[0]);
+                    if ((l < 0L) || (l > 4294967295L)) {
+                        return null;
+                    }
+                    bytes[0] = (byte)(int)(l >> 24 & 0xFF);
+                    bytes[1] = (byte)(int)((l & 0xFFFFFF) >> 16 & 0xFF);
+                    bytes[2] = (byte)(int)((l & 0xFFFF) >> 8 & 0xFF);
+                    bytes[3] = (byte)(int)(l & 0xFF);
+                    break;
+                case 2:
+                    l = Integer.parseInt(elements[0]);
+                    if ((l < 0L) || (l > 255L)) {
+                        return null;
+                    }
+                    bytes[0] = (byte)(int)(l & 0xFF);
+                    l = Integer.parseInt(elements[1]);
+                    if ((l < 0L) || (l > 16777215L)) {
+                        return null;
+                    }
+                    bytes[1] = (byte)(int)(l >> 16 & 0xFF);
+                    bytes[2] = (byte)(int)((l & 0xFFFF) >> 8 & 0xFF);
+                    bytes[3] = (byte)(int)(l & 0xFF);
+                    break;
+                case 3:
+                    for (i = 0; i < 2; ++i) {
+                        l = Integer.parseInt(elements[i]);
+                        if ((l < 0L) || (l > 255L)) {
+                            return null;
+                        }
+                        bytes[i] = (byte)(int)(l & 0xFF);
+                    }
+                    l = Integer.parseInt(elements[2]);
+                    if ((l < 0L) || (l > 65535L)) {
+                        return null;
+                    }
+                    bytes[2] = (byte)(int)(l >> 8 & 0xFF);
+                    bytes[3] = (byte)(int)(l & 0xFF);
+                    break;
+                case 4:
+                    for (i = 0; i < 4; ++i) {
+                        l = Integer.parseInt(elements[i]);
+                        if ((l < 0L) || (l > 255L)) {
+                            return null;
+                        }
+                        bytes[i] = (byte)(int)(l & 0xFF);
+                    }
+                    break;
+                default:
+                    return null;
             }
-            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
-                ip = request.getHeader("Proxy-Client-IP");
-            }
-            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
-                ip = request.getHeader("WL-Proxy-Client-IP");
-            }
-            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
-                ip = request.getHeader("HTTP_CLIENT_IP");
-            }
-            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
-                ip = request.getHeader("HTTP_X_FORWARDED_FOR");
+        } catch (NumberFormatException e) {
+            return null;
+        }
+        return bytes;
+    }
+
+    /**
+     * 获取IP地址
+     *
+     * @return 本地IP地址
+     */
+    public static String getHostIp() {
+        try {
+            return InetAddress.getLocalHost().getHostAddress();
+        } catch (UnknownHostException e) {
+        }
+        return "127.0.0.1";
+    }
+
+    /**
+     * 获取主机名
+     *
+     * @return 本地主机名
+     */
+    public static String getHostName() {
+        try {
+            return InetAddress.getLocalHost().getHostName();
+        } catch (UnknownHostException e) {
+        }
+        return "未知";
+    }
+
+    /**
+     * 从多级反向代理中获得第一个非unknown IP地址
+     *
+     * @param ip 获得的IP地址
+     * @return 第一个非unknown IP地址
+     */
+    public static String getMultistageReverseProxyIp(String ip) {
+        // 多级反向代理检测
+        if (ip != null && ip.indexOf(",") > 0) {
+            final String[] ips = ip.trim().split(",");
+            for (String subIp : ips) {
+                if (false == isUnknown(subIp)) {
+                    ip = subIp;
+                    break;
+                }
             }
-            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
-                ip = request.getHeader("X-Real-IP");
+        }
+        return RyStringUtils.substring(ip, 0, 255);
+    }
+
+    /**
+     * 检测给定字符串是否为未知,多用于检测HTTP请求相关
+     *
+     * @param checkString 被检测的字符串
+     * @return 是否未知
+     */
+    public static boolean isUnknown(String checkString) {
+        return RyStringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString);
+    }
+
+    /**
+     * 是否为IP
+     */
+    public static boolean isIP(String ip) {
+        return RyStringUtils.isNotBlank(ip) && ip.matches(REGX_IP);
+    }
+
+    /**
+     * 是否为IP,或 *为间隔的通配符地址
+     */
+    public static boolean isIpWildCard(String ip) {
+        return RyStringUtils.isNotBlank(ip) && ip.matches(REGX_IP_WILDCARD);
+    }
+
+    /**
+     * 检测参数是否在ip通配符里
+     */
+    public static boolean ipIsInWildCardNoCheck(String ipWildCard, String ip) {
+        String[] s1 = ipWildCard.split("\\.");
+        String[] s2 = ip.split("\\.");
+        boolean isMatchedSeg = true;
+        for (int i = 0; i < s1.length && !s1[i].equals("*"); i++) {
+            if (!s1[i].equals(s2[i])) {
+                isMatchedSeg = false;
+                break;
             }
-            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
-                ip = request.getRemoteAddr();
+        }
+        return isMatchedSeg;
+    }
+
+    /**
+     * 是否为特定格式如:“10.10.10.1-10.10.10.99”的ip段字符串
+     */
+    public static boolean isIPSegment(String ipSeg) {
+        return RyStringUtils.isNotBlank(ipSeg) && ipSeg.matches(REGX_IP_SEG);
+    }
+
+    /**
+     * 判断ip是否在指定网段中
+     */
+    public static boolean ipIsInNetNoCheck(String iparea, String ip) {
+        int idx = iparea.indexOf('-');
+        String[] sips = iparea.substring(0, idx).split("\\.");
+        String[] sipe = iparea.substring(idx + 1).split("\\.");
+        String[] sipt = ip.split("\\.");
+        long ips = 0L, ipe = 0L, ipt = 0L;
+        for (int i = 0; i < 4; ++i) {
+            ips = ips << 8 | Integer.parseInt(sips[i]);
+            ipe = ipe << 8 | Integer.parseInt(sipe[i]);
+            ipt = ipt << 8 | Integer.parseInt(sipt[i]);
+        }
+        if (ips > ipe) {
+            long t = ips;
+            ips = ipe;
+            ipe = t;
+        }
+        return ips <= ipt && ipt <= ipe;
+    }
+
+    /**
+     * 校验ip是否符合过滤串规则
+     *
+     * @param filter 过滤IP列表,支持后缀'*'通配,支持网段如:`10.10.10.1-10.10.10.99`
+     * @param ip 校验IP地址
+     * @return boolean 结果
+     */
+    public static boolean isMatchedIp(String filter, String ip) {
+        if (RyStringUtils.isEmpty(filter) && RyStringUtils.isEmpty(ip)) {
+            return false;
+        }
+        String[] ips = filter.split(";");
+        for (String iStr : ips) {
+            if (isIP(iStr) && iStr.equals(ip)) {
+                return true;
+            } else if (isIpWildCard(iStr) && ipIsInWildCardNoCheck(iStr, ip)) {
+                return true;
+            } else if (isIPSegment(iStr) && ipIsInNetNoCheck(iStr, ip)) {
+                return true;
             }
-        } catch (Exception e) {
-            throw e;
         }
-        return ip;
+        return false;
     }
 }

+ 82 - 0
backend/src/main/java/oshi/util/Util.java

@@ -0,0 +1,82 @@
+package oshi.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.sun.jna.Memory;
+import com.sun.jna.Pointer;
+
+import oshi.annotation.concurrent.ThreadSafe;
+
+/**
+ * General utility methods
+ */
+@ThreadSafe
+public final class Util {
+    private static final Logger LOG = LoggerFactory.getLogger(Util.class);
+
+    private Util() {
+    }
+
+    /**
+     * Sleeps for the specified number of milliseconds.
+     *
+     * @param ms How long to sleep
+     */
+    public static void sleep(long ms) {
+        try {
+            LOG.trace("Sleeping for {} ms", ms);
+            Thread.sleep(ms);
+        } catch (InterruptedException e) { // NOSONAR squid:S2142
+            LOG.warn("Interrupted while sleeping for {} ms: {}", ms, e.getMessage());
+            Thread.currentThread().interrupt();
+        }
+    }
+
+    /**
+     * Tests if a String matches another String with a wildcard pattern.
+     *
+     * @param text    The String to test
+     * @param pattern The String containing a wildcard pattern where ? represents a single character and * represents
+     *                any number of characters. If the first character of the pattern is a carat (^) the test is
+     *                performed against the remaining characters and the result of the test is the opposite.
+     * @return True if the String matches or if the first character is ^ and the remainder of the String does not match.
+     */
+    public static boolean wildcardMatch(String text, String pattern) {
+        if (pattern.length() > 0 && pattern.charAt(0) == '^') {
+            return !wildcardMatch(text, pattern.substring(1));
+        }
+        return text.matches(pattern.replace("?", ".?").replace("*", ".*?"));
+    }
+
+    /**
+     * Tests if a String is either null or empty.
+     *
+     * @param s The string to test
+     * @return True if the String is either null or empty.
+     */
+    public static boolean isBlank(String s) {
+        return s == null || s.isEmpty();
+    }
+
+    /**
+     * Tests if a String is either null or empty or the unknown constant.
+     *
+     * @param s The string to test
+     * @return True if the String is either null or empty or the unknown constant.
+     */
+    public static boolean isBlankOrUnknown(String s) {
+        return isBlank(s) || Constants.UNKNOWN.equals(s);
+    }
+
+    /**
+     * If the given Pointer is of class Memory, executes the close method on it to free its native allocation
+     *
+     * @param p A pointer
+     */
+    public static void freeMemory(Pointer p) {
+        if (p instanceof Memory) {
+            ((Memory) p).clear();
+        }
+    }
+}

+ 44 - 46
ui/package.json

@@ -17,57 +17,55 @@
   },
   "dependencies": {
     "@riophae/vue-treeselect": "0.4.0",
-    "axios": "0.18.1",
-    "echarts": "^4.9.0",
-    "element-ui": "2.13.0",
-    "font-awesome": "^4.7.0",
-    "js-cookie": "2.2.0",
-    "lodash": "^4.17.21",
-    "lodash-es": "^4.17.21",
-    "moment": "^2.24.0",
-    "node-sass": "^6.0.1",
-    "normalize.css": "7.0.0",
+    "axios": "0.24.0",
+    "clipboard": "2.0.8",
+    "core-js": "3.25.3",
+    "echarts": "5.4.0",
+    "element-ui": "2.15.12",
+    "file-saver": "2.0.5",
+    "fuse.js": "6.4.3",
+    "highlight.js": "9.18.5",
+    "js-beautify": "1.13.0",
+    "js-cookie": "3.0.1",
+    "jsencrypt": "3.0.0-rc.1",
     "nprogress": "0.2.0",
-    "particles.js": "^2.0.0",
-    "path-to-regexp": "2.4.0",
-    "qrcodejs2": "0.0.2",
-    "sm-crypto": "^0.3.12",
-    "sortablejs": "^1.10.2",
-    "vue": "2.6.10",
-    "vue-router": "3.0.6",
-    "vuedraggable": "^2.23.2",
-    "vuex": "3.1.0",
-    "vuex-persistedstate": "^4.1.0",
+    "quill": "1.3.7",
+    "screenfull": "5.0.2",
+    "sortablejs": "1.10.2",
+    "vue": "2.6.12",
+    "vue-count-to": "1.0.13",
+    "vue-cropper": "0.5.5",
+    "vue-meta": "2.4.0",
+    "vue-router": "3.4.9",
+    "vuedraggable": "2.24.3",
+    "vuex": "3.6.0",
     "vxe-table": "^2.9.18",
-    "xe-utils": "^2.7.5"
+    "xe-utils": "^2.7.5",
+    "sm-crypto": "^0.3.12",
+    "moment": "^2.24.0",
+    "normalize.css": "7.0.0",
+    "lodash": "^4.17.21",
+    "font-awesome": "^4.7.0",
+    "vuex-persistedstate": "^4.1.0"
   },
   "devDependencies": {
-    "@babel/core": "7.0.0",
-    "@babel/register": "7.0.0",
-    "@vue/cli-plugin-babel": "3.6.0",
-    "@vue/cli-plugin-eslint": "^3.9.1",
-    "@vue/cli-plugin-unit-jest": "3.6.3",
-    "@vue/cli-service": "3.6.0",
-    "@vue/test-utils": "1.0.0-beta.29",
-    "autoprefixer": "^9.5.1",
-    "babel-core": "7.0.0-bridge.0",
-    "babel-eslint": "10.0.1",
-    "babel-jest": "23.6.0",
-    "chalk": "2.4.2",
+    "@vue/cli-plugin-babel": "4.4.6",
+    "@vue/cli-plugin-eslint": "4.4.6",
+    "@vue/cli-service": "4.4.6",
+    "babel-eslint": "10.1.0",
+    "babel-plugin-dynamic-import-node": "2.3.3",
+    "chalk": "4.1.0",
+    "compression-webpack-plugin": "5.0.2",
     "connect": "3.6.6",
-    "eslint": "5.15.3",
-    "eslint-plugin-vue": "5.2.2",
-    "html-webpack-plugin": "3.2.0",
-    "mockjs": "1.0.1-beta3",
-    "pulldown": "^1.1.0",
-    "runjs": "^4.3.2",
-    "sass-loader": "^10.0.1",
-    "script-ext-html-webpack-plugin": "2.1.3",
-    "script-loader": "0.7.2",
-    "serve-static": "^1.13.2",
-    "svg-sprite-loader": "4.1.3",
-    "svgo": "1.2.2",
-    "vue-template-compiler": "2.6.10"
+    "eslint": "7.15.0",
+    "eslint-plugin-vue": "7.2.0",
+    "lint-staged": "10.5.3",
+    "runjs": "4.4.2",
+    "sass": "1.32.13",
+    "sass-loader": "10.1.1",
+    "script-ext-html-webpack-plugin": "2.1.5",
+    "svg-sprite-loader": "5.1.1",
+    "vue-template-compiler": "2.6.12"
   },
   "engines": {
     "node": ">=8.9",

+ 57 - 0
ui/src/api/monitor/cache.js

@@ -0,0 +1,57 @@
+import request from '@/utils/request'
+
+// 查询缓存详细
+export function getCache() {
+  return request({
+    url: '/monitor/cache',
+    method: 'get'
+  })
+}
+
+// 查询缓存名称列表
+export function listCacheName() {
+  return request({
+    url: '/monitor/cache/getNames',
+    method: 'get'
+  })
+}
+
+// 查询缓存键名列表
+export function listCacheKey(cacheName) {
+  return request({
+    url: '/monitor/cache/getKeys/' + cacheName,
+    method: 'get'
+  })
+}
+
+// 查询缓存内容
+export function getCacheValue(cacheName, cacheKey) {
+  return request({
+    url: '/monitor/cache/getValue/' + cacheName + '/' + cacheKey,
+    method: 'get'
+  })
+}
+
+// 清理指定名称缓存
+export function clearCacheName(cacheName) {
+  return request({
+    url: '/monitor/cache/clearCacheName/' + cacheName,
+    method: 'delete'
+  })
+}
+
+// 清理指定键名缓存
+export function clearCacheKey(cacheKey) {
+  return request({
+    url: '/monitor/cache/clearCacheKey/' + cacheKey,
+    method: 'delete'
+  })
+}
+
+// 清理全部缓存
+export function clearCacheAll() {
+  return request({
+    url: '/monitor/cache/clearCacheAll',
+    method: 'delete'
+  })
+}

+ 71 - 0
ui/src/api/monitor/job.js

@@ -0,0 +1,71 @@
+import request from '@/utils/request'
+
+// 查询定时任务调度列表
+export function listJob(query) {
+  return request({
+    url: '/monitor/job/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询定时任务调度详细
+export function getJob(jobId) {
+  return request({
+    url: '/monitor/job/' + jobId,
+    method: 'get'
+  })
+}
+
+// 新增定时任务调度
+export function addJob(data) {
+  return request({
+    url: '/monitor/job',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改定时任务调度
+export function updateJob(data) {
+  return request({
+    url: '/monitor/job',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除定时任务调度
+export function delJob(jobId) {
+  return request({
+    url: '/monitor/job/' + jobId,
+    method: 'delete'
+  })
+}
+
+// 任务状态修改
+export function changeJobStatus(jobId, status) {
+  const data = {
+    jobId,
+    status
+  }
+  return request({
+    url: '/monitor/job/changeStatus',
+    method: 'put',
+    data: data
+  })
+}
+
+
+// 定时任务立即执行一次
+export function runJob(jobId, jobGroup) {
+  const data = {
+    jobId,
+    jobGroup
+  }
+  return request({
+    url: '/monitor/job/run',
+    method: 'put',
+    data: data
+  })
+}

+ 26 - 0
ui/src/api/monitor/jobLog.js

@@ -0,0 +1,26 @@
+import request from '@/utils/request'
+
+// 查询调度日志列表
+export function listJobLog(query) {
+  return request({
+    url: '/monitor/jobLog/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 删除调度日志
+export function delJobLog(jobLogId) {
+  return request({
+    url: '/monitor/jobLog/' + jobLogId,
+    method: 'delete'
+  })
+}
+
+// 清空调度日志
+export function cleanJobLog() {
+  return request({
+    url: '/monitor/jobLog/clean',
+    method: 'delete'
+  })
+}

+ 34 - 0
ui/src/api/monitor/logininfor.js

@@ -0,0 +1,34 @@
+import request from '@/utils/request'
+
+// 查询登录日志列表
+export function list(query) {
+  return request({
+    url: '/monitor/logininfor/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 删除登录日志
+export function delLogininfor(infoId) {
+  return request({
+    url: '/monitor/logininfor/' + infoId,
+    method: 'delete'
+  })
+}
+
+// 解锁用户登录状态
+export function unlockLogininfor(userName) {
+  return request({
+    url: '/monitor/logininfor/unlock/' + userName,
+    method: 'get'
+  })
+}
+
+// 清空登录日志
+export function cleanLogininfor() {
+  return request({
+    url: '/monitor/logininfor/clean',
+    method: 'delete'
+  })
+}

+ 18 - 0
ui/src/api/monitor/online.js

@@ -0,0 +1,18 @@
+import request from '@/utils/request'
+
+// 查询在线用户列表
+export function list(query) {
+  return request({
+    url: '/monitor/online/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 强退用户
+export function forceLogout(tokenId) {
+  return request({
+    url: '/monitor/online/' + tokenId,
+    method: 'delete'
+  })
+}

+ 26 - 0
ui/src/api/monitor/operlog.js

@@ -0,0 +1,26 @@
+import request from '@/utils/request'
+
+// 查询操作日志列表
+export function list(query) {
+  return request({
+    url: '/monitor/operlog/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 删除操作日志
+export function delOperlog(operId) {
+  return request({
+    url: '/monitor/operlog/' + operId,
+    method: 'delete'
+  })
+}
+
+// 清空操作日志
+export function cleanOperlog() {
+  return request({
+    url: '/monitor/operlog/clean',
+    method: 'delete'
+  })
+}

+ 9 - 0
ui/src/api/monitor/server.js

@@ -0,0 +1,9 @@
+import request from '@/utils/request'
+
+// 获取服务信息
+export function getServer() {
+  return request({
+    url: '/monitor/server',
+    method: 'get'
+  })
+}

+ 1 - 2
ui/src/directive/permission/hasPermi.js

@@ -9,8 +9,7 @@ export default {
   inserted(el, binding, vnode) {
     const { value } = binding
     const all_permission = "*:*:*";
-    // const permissions = store.getters && store.getters.permissions
-    const permissions = ["0","1","3","4"]
+    const permissions = store.getters && store.getters.permissions
     if (value && value instanceof Array && value.length > 0) {
       const permissionFlag = value
 

+ 0 - 1
ui/src/layout/components/Sidebar/FixiOSBug.js

@@ -6,7 +6,6 @@ export default {
   },
   mounted() {
     // In order to fix the click on menu on the ios device will trigger the mouseleave bug
-    // https://github.com/PanJiaChen/vue-element-admin/issues/1135
     this.fixBugIniOS()
   },
   methods: {

+ 5 - 1
ui/src/layout/components/Sidebar/Item.vue

@@ -21,7 +21,11 @@ export default {
     }
 
     if (title) {
-      vnodes.push(<span slot='title'>{(title)}</span>)
+      if (title.length > 5) {
+        vnodes.push(<span slot='title' title={(title)}>{(title)}</span>)
+      } else {
+        vnodes.push(<span slot='title'>{(title)}</span>)
+      }
     }
     return vnodes
   }

+ 17 - 10
ui/src/layout/components/Sidebar/Link.vue

@@ -1,7 +1,5 @@
-
 <template>
-  <!-- eslint-disable vue/require-component-is -->
-  <component v-bind="linkProps(to)">
+  <component :is="type" v-bind="linkProps(to)">
     <slot />
   </component>
 </template>
@@ -12,23 +10,32 @@ import { isExternal } from '@/utils/validate'
 export default {
   props: {
     to: {
-      type: String,
+      type: [String, Object],
       required: true
     }
   },
+  computed: {
+    isExternal() {
+      return isExternal(this.to)
+    },
+    type() {
+      if (this.isExternal) {
+        return 'a'
+      }
+      return 'router-link'
+    }
+  },
   methods: {
-    linkProps(url) {
-      if (isExternal(url)) {
+    linkProps(to) {
+      if (this.isExternal) {
         return {
-          is: 'a',
-          href: url,
+          href: to,
           target: '_blank',
           rel: 'noopener'
         }
       }
       return {
-        is: 'router-link',
-        to: url
+        to: to
       }
     }
   }

+ 18 - 7
ui/src/layout/components/Sidebar/Logo.vue

@@ -1,19 +1,22 @@
 <template>
-  <div class="sidebar-logo-container" :class="{'collapse':collapse}">
+  <div class="sidebar-logo-container" :class="{'collapse':collapse}" :style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }">
     <transition name="sidebarLogoFade">
       <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
-        <img v-if="logo" :src="logo" class="sidebar-logo">
-        <h1 v-else class="sidebar-title">{{ title }} </h1>
+        <img v-if="logo" :src="logo" class="sidebar-logo" />
+        <h1 v-else class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1>
       </router-link>
       <router-link v-else key="expand" class="sidebar-logo-link" to="/">
-        <img v-if="logo" :src="logo" class="sidebar-logo">
-        <h1 class="sidebar-title">{{ title }} </h1>
+        <img v-if="logo" :src="logo" class="sidebar-logo" />
+        <h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1>
       </router-link>
     </transition>
   </div>
 </template>
 
 <script>
+import logoImg from '@/assets/logo/logo.png'
+import variables from '@/assets/styles/variables.scss'
+
 export default {
   name: 'SidebarLogo',
   props: {
@@ -22,10 +25,18 @@ export default {
       required: true
     }
   },
+  computed: {
+    variables() {
+      return variables;
+    },
+    sideTheme() {
+      return this.$store.state.settings.sideTheme
+    }
+  },
   data() {
     return {
-      title: 'Vue Admin Template',
-      logo: 'https://wpimg.wallstcn.com/69a1c46c-eb1c-4b46-8bd4-e9e686ef5251.png'
+      title: '若依管理系统',
+      logo: logoImg
     }
   }
 }

+ 9 - 4
ui/src/layout/components/Sidebar/SidebarItem.vue

@@ -1,7 +1,7 @@
 <template>
   <div v-if="!item.hidden">
     <template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
-      <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
+      <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)">
         <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
           <item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" />
         </el-menu-item>
@@ -51,13 +51,14 @@ export default {
     }
   },
   data() {
-    // To fix https://github.com/PanJiaChen/vue-admin-template/issues/237
-    // TODO: refactor with render function
     this.onlyOneChild = null
     return {}
   },
   methods: {
     hasOneShowingChild(children = [], parent) {
+      if (!children) {
+        children = [];
+      }
       const showingChildren = children.filter(item => {
         if (item.hidden) {
           return false
@@ -81,13 +82,17 @@ export default {
 
       return false
     },
-    resolvePath(routePath) {
+    resolvePath(routePath, routeQuery) {
       if (isExternal(routePath)) {
         return routePath
       }
       if (isExternal(this.basePath)) {
         return this.basePath
       }
+      if (routeQuery) {
+        let query = JSON.parse(routeQuery);
+        return { path: path.resolve(this.basePath, routePath), query: query }
+      }
       return path.resolve(this.basePath, routePath)
     }
   }

+ 48 - 90
ui/src/layout/components/Sidebar/index.vue

@@ -1,99 +1,57 @@
 <template>
-  <div class="elmenu" :class="{'has-logo':showLogo}">
-    <logo v-if="showLogo" :collapse="isCollapse"/>
-    <el-scrollbar wrap-class="scrollbar-wrapper">
-
-      <el-menu
-        :default-active="activeMenu"
-        :collapse="isCollapse"
-        :background-color="variables.menuBg"
-        :text-color="variables.menuText"
-        :unique-opened="false"
-        :active-text-color="variables.menuActiveText"
-        :collapse-transition="false"
-        mode="vertical"
-      >
-        <sidebar-item v-for="route in allRoute" :key="route.path" :item="route" :base-path="route.path"/>
-      </el-menu>
-    </el-scrollbar>
-  </div>
+    <div :class="{'has-logo':showLogo}" :style="{ backgroundColor: settings.sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }">
+        <logo v-if="showLogo" :collapse="isCollapse" />
+        <el-scrollbar :class="settings.sideTheme" wrap-class="scrollbar-wrapper">
+            <el-menu
+                :default-active="activeMenu"
+                :collapse="isCollapse"
+                :background-color="settings.sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground"
+                :text-color="settings.sideTheme === 'theme-dark' ? variables.menuColor : variables.menuLightColor"
+                :unique-opened="true"
+                :active-text-color="settings.theme"
+                :collapse-transition="false"
+                mode="vertical"
+            >
+                <sidebar-item
+                    v-for="(route, index) in sidebarRouters"
+                    :key="route.path  + index"
+                    :item="route"
+                    :base-path="route.path"
+                />
+            </el-menu>
+        </el-scrollbar>
+    </div>
 </template>
 
 <script>
-  import {mapGetters} from 'vuex'
-  import Logo from './Logo'
-  import SidebarItem from './SidebarItem'
-  import variables from '@/styles/variables.scss'
-  import generateMenus from '@/utils/menus'
-  export default {
-    components: {SidebarItem, Logo},
-    data() {
-      return {
-        allRoute: {},
-        fileUploadNodeShowSysValue: '1',
-      }
-    },
-    created() {
-      this.allRoute = this.$router.options.routes
-      // this.getMenu()
-    },
-    destoryed() {
-      this.allRoute = {}
-    },
-    mounted(){
-      // this.getCookie()
-
-    },
-    methods: {
-      getMenu() {
-        this.$axios.get('/sysMenuController/').then(res => {
-
-          this.$router.addRoutes(generateMenus(res.data))
-          // this.allRoute = this.$router.Route
-        }).catch(e => {
-          this.$message.error('查询菜单异常:' + e)
-        })
-      }
-    },
+import { mapGetters, mapState } from "vuex";
+import Logo from "./Logo";
+import SidebarItem from "./SidebarItem";
+import variables from "@/assets/styles/variables.scss";
 
+export default {
+    components: { SidebarItem, Logo },
     computed: {
-      ...
-        mapGetters([
-          'sidebar'
-        ]),
-      activeMenu() {
-        const route = this.$route
-        const {meta, path} = route
-        // if set path, the sidebar will highlight the path you set
-        if (meta.activeMenu) {
-          return meta.activeMenu
-        }
-        return path
-      }
-      ,
-      showLogo() {
-        return this.$store.state.settings.sidebarLogo
-      }
-      ,
-      variables() {
-        return variables
-      }
-      ,
-      isCollapse() {
-        if (!this.sidebar.opened) {
-          this.variables.menuBg = 'rgb(0,0,0)'
-        } else {
-          this.variables.menuBg = 'rgba(48, 65, 86,0)'
+        ...mapState(["settings"]),
+        ...mapGetters(["sidebarRouters", "sidebar"]),
+        activeMenu() {
+            const route = this.$route;
+            const { meta, path } = route;
+            // if set path, the sidebar will highlight the path you set
+            if (meta.activeMenu) {
+                return meta.activeMenu;
+            }
+            return path;
+        },
+        showLogo() {
+            return this.$store.state.settings.sidebarLogo;
+        },
+        variables() {
+            return variables;
+        },
+        isCollapse() {
+            return !this.sidebar.opened;
         }
-        return !this.sidebar.opened
-      }
     }
-  }
+};
 </script>
-<style scoped>
-  /*.elmenu {*/
-  /*  background: url('../../img/pageBg.png');*/
-  /*  border: 1px solid #ffffff;*/
-  /*  height: 100%;*/
-  /*}*/
-</style>

+ 5 - 287
ui/src/main.js

@@ -6,6 +6,7 @@ import 'element-ui/lib/theme-chalk/index.css'
 import locale from 'element-ui/lib/locale/lang/zh-CN' // lang i18n
 import 'font-awesome/scss/font-awesome.scss'
 import '@/styles/index.scss' // global css
+import 'xe-utils'
 import moment from 'moment'
 import plugins from './plugins' // plugins
 import App from './App'
@@ -15,17 +16,16 @@ import router, {resetRouter} from './router'
 import echarts from 'echarts'
 import '@/icons' // icon
 import '@/permission' // permission control
-import axios from 'axios'
-import 'xe-utils'
+// import axios from 'axios'
 import VXETable from 'vxe-table'
 import 'vxe-table/lib/index.css'
-
+import service from './utils/request'
 import {removeToken} from './utils/auth'
 // import { resetForm} from "@/utils/index"
 import { parseTime, resetForm, addDateRange, selectDictLabel, selectDictLabels, handleTree } from "@/utils/ruoyi"
-import {sm2 as sm5, sm2 as sm4, sm2 as sm3, sm2} from "sm-crypto"
 Vue.prototype.$moment = moment
 Vue.prototype.$echarts = echarts
+Vue.prototype.$axios = service
 Vue.use(VXETable)
 Vue.use(plugins)
 Vue.prototype.handleTree = handleTree
@@ -48,217 +48,13 @@ Vue.use(ElementUI, {locale})
 // Vue.use(ElementUI)
 Vue.prototype.$fpath = require('path')
 Vue.config.productionTip = false
-Vue.prototype.$axios = axios.create({
-  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
-  // withCredentials: true, // send cookies when cross-domain requests
-  timeout: 1000 * 60 * 10 // request timeout
-})
+
 VXETable.setup({
   validArgs: 'obsolete' // 将自定义校验参数还原为 Function(rule, cellValue, callback)
 })
 
 Vue.prototype.resetForm = resetForm
 Vue.prototype.parseTime = parseTime
-// 公钥Q
-let publicKey2 = '041967638ca43d4577d8dba166bff4437fde944270101f398a95b846ec2f8177d09f8abc5d62b6cd2c7216274d7abe0c8e04b0bb691207a32dd2e12d6bd2798672'
-// 私钥D
-let privateKey1 = '27ce6eec39dbf3b564a77c4da1e129fe1ba01a92f6d61055a33ed14ffcbc949e'
-
-Vue.prototype.$axios.interceptors.request.use(
-  config => {
-    const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
-    // get请求映射params参数
-    if (config.method === 'get' && config.params) {
-      // 参数加密
-      let encryptParam = doEncrypt(JSON.stringify(config.params))
-      // 参数签名
-      let paramSign = doSign(JSON.stringify(config.params))
-      let result = 'secretData=' + encryptParam + '&paramSign=' + paramSign
-      config.params = result
-    }
-
-    if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put' || config.method === 'delete')) {
-      if (config.url != '/getMailCode') {
-        if (config.data !== undefined) {
-          // 参数加密
-          let encryptParam = doEncrypt(JSON.stringify(config.data))
-          // 参数签名
-          let paramSign = doSign(JSON.stringify(config.data))
-          const param = {
-            secretData: encryptParam,
-            paramSign: paramSign
-          }
-          config.data = param
-          if (config.url!='/user/login'){
-            config.headers.post['Content-Type'] = 'application/json';
-          }
-        }
-        else{
-          Message({
-            message: '请求拦截器检测出config.data是undefined不能提交!',
-            type: 'error',
-            duration: 5 * 1000
-          })
-          return
-        }
-      }
-    }
-
-    if (sessionStorage.getItem("token")!=="undefined" && sessionStorage.getItem("token")!==undefined && sessionStorage.getItem("token")!=null) { // 判断是否存在token,如果存在的话,则每个http header都加上token
-      let tokenStr = doEncrypt(sessionStorage.getItem("token"))
-      config.headers['Authorization'] = tokenStr
-      config.headers['TokenSign'] = doSign(sessionStorage.getItem("token"))
-    }
-    return config
-  },
-  error => {
-    // do something with request error
-    // console.log(error) // for debug
-    return Promise.reject(error)
-  }
-)
-
-// 是否正在刷新的标记
-let isRefreshing = false
-//重试队列
-let requests = []
-
-// response interceptor
-Vue.prototype.$axios.interceptors.response.use(
-  /**
-   * Determine the request status by custom code
-   * Here is just an example
-   * You can also judge the status by HTTP Status Code
-   */
-  response => {
-      const res = response.data
-      let returnStr = res.split("&")
-      let returnData = returnStr[0].split("=")[1]
-      let returnSign = returnStr[1].split("=")[1]
-      // 解密
-      let decData = doDecryptStr(returnData)
-      // 验签
-      let verifyResult = doVerifySignature(decData, returnSign)
-      if (!verifyResult) {
-        return Promise.reject(new Error('返回数据验签失败' || 'Error'))
-      }
-      let data = JSON.parse(decData)
-      // if the custom code is not 20000, it is judged as an error.
-      //console.log(res.code)
-      if (data.code > 1) {
-        // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
-        if (data.code === 50008 || data.code === 50012 || data.code === 50014) {
-          // to re-login
-          MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
-            confirmButtonText: 'Re-Login',
-            cancelButtonText: 'Cancel',
-            type: 'warning'
-          }).then(() => {
-            store.dispatch('user/resetToken').then(() => {
-              location.reload()
-            })
-          })
-        }
-        return Promise.reject(new Error(data.message || 'Error'))
-      } else {
-        return data
-      }
-  },
-  async error => {
-    if (error.response) {
-      switch (error.response.status) {
-        case 401:
-          console.log('用户验证失败!')
-          // 返回 401 清除token信息并跳转到登录页面
-          removeToken()
-          resetRouter()
-          Message({
-            message: error.response.data,
-            type: 'error',
-            duration: 5 * 1000
-          })
-          break
-        case 403:
-          console.log('登录超时!')
-          // 返回 401 清除token信息并跳转到登录页面
-          removeToken()
-          resetRouter()
-          router.push('/login')
-          Message({
-            message: error.response.data,
-            type: 'error',
-            duration: 5 * 1000
-          })
-          return
-          break
-        case 500:
-          Message({
-            message: '服务器关闭了!请联系相关工作人员',
-            type: 'error',
-            duration: 5 * 1000
-          })
-          removeToken()
-          resetRouter()
-          router.push('/login')
-          break
-        case 504:
-          console.log('服务器关闭了!')
-          resetRouter()
-          break
-        case 410:
-          Message({
-            message: error.response.data,
-            type: 'error',
-            duration: 5 * 1000
-          })
-          break
-        case 409:
-            if (!isRefreshing) {
-              isRefreshing = true
-              //调用刷新token的接口
-              return await  Vue.prototype.$axios.post(
-                  '/refreshToken'
-                ).then((res) => {
-                  // const { token } = res.data
-                  // 替换token
-                  sessionStorage.setItem('token', res.data)
-                  // Vue.prototype.$axios(error.response.config)
-                  // 重新请求接口 前过期的接口
-                  error.config.headers.Authorization = res.data;
-                  requests.length > 0 && requests.map((cb) => {
-                    cb();
-                  });
-                  requests = [];  //注意要清空
-                  return Vue.prototype.$axios.request(error.config);
-                  // return error.response.config
-                }).catch(err => {
-                  console.log(err)
-                  //跳到登录页
-                  removeToken()
-                  router.push('/login')
-                  // return Promise.reject(err)
-                }).finally(() => {
-                  isRefreshing = false
-                })
-            }
-            else {
-              // 正在刷新token ,把后来的接口缓冲起来
-              return new Promise((resolve) => {
-                requests.push(() => {
-                  error.config.headers.Authorization = sessionStorage.getItem('token');
-                  resolve(Vue.prototype.$axios.request(error.config));
-                });
-              })
-            }
-            break
-          // return Promise.reject(error.response.data)
-      }
-    }
-    else{
-      return Promise.reject(error)
-    }
-  }
-)
 
 new Vue({
   el: '#app',
@@ -267,82 +63,4 @@ new Vue({
   render: h => h(App)
 })
 
-// 加密:
-export function doEncrypt(msgString) {
-  let sm2 = require('sm-crypto').sm2;
-  // 1 - C1C3C2;	0 - C1C2C3;	默认为1
-  let cipherMode = 1
-
-  // 加密结果
-  let encryptData = sm2.doEncrypt(msgString, publicKey2, cipherMode);
-  // 加密后的密文前需要添加04,后端才能正常解密
-  // let encrypt = '04' + encryptData;
-  return encryptData;
-}
-
-// 解密
-export function doDecryptStr(enStr) {
-  let sm2 = require('sm-crypto').sm2;
-  // 1 - C1C3C2;	0 - C1C2C3;	默认为1
-  let cipherMode = 1
-  // 解密结果
-  let doDecrypt = sm2.doDecrypt(enStr, privateKey1, cipherMode);
-  // 解密后类型转换
-  // let objData = JSON.parse(doDecrypt)
-  return doDecrypt;
-}
-
-// 签名
-export function doSign(msgString) {
-  let sm2 = require('sm-crypto').sm2;
-  // 1 - C1C3C2;	0 - C1C2C3;	默认为1
-  let cipherMode = 1
-  // 签名
-  let sign = sm2.doSignature(msgString, privateKey1, {hash: true, der: true})
-  return sign;
-}
-
-// 验签
-export function doVerifySignature(msgString, sigValueHex) {
-  let sm2 = require('sm-crypto').sm2;
-  // 1 - C1C3C2;	0 - C1C2C3;	默认为1
-  let cipherMode = 1
-  // 签名
-  let verifyResult = sm2.doVerifySignature(msgString, sigValueHex, publicKey2, {hash: true, der: true}) // 验签结果
-  return verifyResult;
-}
-
-// 前端user信息加密
-export function userinfoEncrypt(str) {
-  let sm2 = require('sm-crypto').sm2;
-  let cipherMode = 1
-  // 加密
-  let encryptData = sm2.doEncrypt(str, '0460ff8c8c306fe62f6f9d11c5c82c30d10bbbc703da094e423072cac7dc663c97fad52eccb34f311f47a07f280de157ba4f2aa659cabe749121384b9376ea2ed2', cipherMode);
-  return encryptData
-}
-// 前端user信息解密
-export function userinfoDecrypt(str) {
-  let sm2 = require('sm-crypto').sm2;
-  let cipherMode = 1
-  // 加密
-  let decryptData = sm2.doDecrypt(str, "27ce6eec39dbf3b564a77c4da1e129fe1ba01a92f6d61055a33ed14ffcbc949e", cipherMode);
-  return decryptData
-}
 
-export function test() {
-  let sm2 = require('sm-crypto').sm2;
-  let cipherMode = 1
-  // 加密
-  let encryptData = sm2.doEncrypt('1122加密', '0460ff8c8c306fe62f6f9d11c5c82c30d10bbbc703da094e423072cac7dc663c97fad52eccb34f311f47a07f280de157ba4f2aa659cabe749121384b9376ea2ed2', cipherMode);
-  let sm3 = require('sm-crypto').sm2;
-  // 签名
-  let sign = sm3.doSignature('1122加密', '6155d63ee27cbeca07f3e40c4f8856f1be8119fcbda1aadc7e0e595e52bad7bd')
-  // 解密
-  let sm4 = require('sm-crypto').sm2;
-  let doDecrypt = sm4.doDecrypt(encryptData, privateKey1, cipherMode);
-  alert('解密:' + doDecrypt)
-  // 验签
-  let sm5 = require('sm-crypto').sm2;
-  let verifyResult = sm5.doVerifySignature(doDecrypt, sign, publicKey2) // 验签结果
-  alert('验签:' + verifyResult)
-}

+ 9 - 4
ui/src/permission.js

@@ -5,7 +5,7 @@ import NProgress from 'nprogress' // progress bar
 import 'nprogress/nprogress.css' // progress bar style
 import { getBrowserToken } from './utils/commonFuc' // get token from cookie
 import getPageTitle from '@/utils/get-page-title'
-import {userinfoDecrypt} from "@/main";
+import {doEncrypt, doDecryptStr,doSign,doVerifySignature,userinfoEncrypt,userinfoDecrypt} from '@/utils/smutil'
 
 NProgress.configure({ showSpinner: false }) // NProgress Configuration
 
@@ -24,6 +24,11 @@ router.beforeEach(async(to, from, next) => {
       next({ path: '/' })
       NProgress.done()
     } else {
+      store.dispatch('GenerateRoutes').then(accessRoutes => {
+        // 根据roles权限生成可访问的路由表
+        router.addRoutes(accessRoutes) // 动态添加可访问路由表
+        // next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
+      })
       // 验证初始密码是否修改了或者超过30天
       if (to.path !=='/dashboard' && to.path !=='/user/profile'){
         // 用户信息解密
@@ -58,12 +63,12 @@ router.beforeEach(async(to, from, next) => {
       }
     }
   } else {
-    /* has no token*/
+    // 没有token
     if (whiteList.indexOf(to.path) !== -1) {
-      // in the free login whitelist, go directly
+      // 在免登录白名单,直接进入
       next()
     } else {
-      // other pages that do not have permission to access are redirected to the login page.
+      // 否则全部重定向到登录页
       next(`/login?redirect=${to.path}`)
       NProgress.done()
     }

+ 6 - 7
ui/src/router/index.js

@@ -99,13 +99,12 @@ export const constantRoutes = [
         meta: {title: '个人中心', icon: 'user'}
       }
     ]
-  },
-  // consoleRouter,
-  // systemRouter,
-  // dataExchangeRouter,
-  // uploadRouter,
-  // 404 page must be placed at the end !!!
-  {path: '*', redirect: '/404', hidden: true}
+  }
+]
+
+// 动态路由,基于用户权限动态去加载
+export const dynamicRoutes = [
+
 ]
 
 const createRouter = () => new Router({

+ 0 - 6
ui/src/store/getters.js

@@ -2,14 +2,8 @@ const getters = {
   sidebar: state => state.app.sidebar,
   size: state => state.app.size,
   device: state => state.app.device,
-  dict: state => state.dict.dict,
   visitedViews: state => state.tagsView.visitedViews,
   cachedViews: state => state.tagsView.cachedViews,
-  token: state => state.user.token,
-  avatar: state => state.user.avatar,
-  name: state => state.user.name,
-  introduction: state => state.user.introduction,
-  roles: state => state.user.roles,
   permissions: state => state.user.permissions,
   permission_routes: state => state.permission.routes,
   topbarRouters:state => state.permission.topbarRouters,

+ 2 - 3
ui/src/store/index.js

@@ -5,9 +5,8 @@ import app from './modules/app'
 import settings from './modules/settings'
 import user from './modules/user'
 import tagsView from './modules/tagsView'
+import permission from './modules/permission'
 import createPersistedState from 'vuex-persistedstate'
-// import permission from './modules/permission'
-
 
 Vue.use(Vuex)
 
@@ -17,7 +16,7 @@ const store = new Vuex.Store({
     settings,
     user,
     tagsView,
-    // permission
+    permission
   },
   getters,
   plugins:[

+ 133 - 133
ui/src/store/modules/permission.js

@@ -1,133 +1,133 @@
-// import auth from '@/plugins/auth'
-// import router, { constantRoutes, dynamicRoutes } from '@/router'
-// import { getRouters } from '@/api/menu'
-// import Layout from '@/layout/index'
-// import ParentView from '@/components/ParentView'
-// import InnerLink from '@/layout/components/InnerLink'
-//
-// const permission = {
-//   state: {
-//     routes: [],
-//     addRoutes: [],
-//     defaultRoutes: [],
-//     topbarRouters: [],
-//     sidebarRouters: []
-//   },
-//   mutations: {
-//     SET_ROUTES: (state, routes) => {
-//       state.addRoutes = routes
-//       state.routes = constantRoutes.concat(routes)
-//     },
-//     SET_DEFAULT_ROUTES: (state, routes) => {
-//       state.defaultRoutes = constantRoutes.concat(routes)
-//     },
-//     SET_TOPBAR_ROUTES: (state, routes) => {
-//       state.topbarRouters = routes
-//     },
-//     SET_SIDEBAR_ROUTERS: (state, routes) => {
-//       state.sidebarRouters = routes
-//     },
-//   },
-//   actions: {
-//     // 生成路由
-//     GenerateRoutes({ commit }) {
-//       return new Promise(resolve => {
-//         // 向后端请求路由数据
-//         getRouters().then(res => {
-//           const sdata = JSON.parse(JSON.stringify(res.data))
-//           const rdata = JSON.parse(JSON.stringify(res.data))
-//           const sidebarRoutes = filterAsyncRouter(sdata)
-//           const rewriteRoutes = filterAsyncRouter(rdata, false, true)
-//           const asyncRoutes = filterDynamicRoutes(dynamicRoutes);
-//           rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true })
-//           router.addRoutes(asyncRoutes);
-//           commit('SET_ROUTES', rewriteRoutes)
-//           commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))
-//           commit('SET_DEFAULT_ROUTES', sidebarRoutes)
-//           commit('SET_TOPBAR_ROUTES', sidebarRoutes)
-//           resolve(rewriteRoutes)
-//         })
-//       })
-//     }
-//   }
-// }
-//
-// // 遍历后台传来的路由字符串,转换为组件对象
-// function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
-//   return asyncRouterMap.filter(route => {
-//     if (type && route.children) {
-//       route.children = filterChildren(route.children)
-//     }
-//     if (route.component) {
-//       // Layout ParentView 组件特殊处理
-//       if (route.component === 'Layout') {
-//         route.component = Layout
-//       } else if (route.component === 'ParentView') {
-//         route.component = ParentView
-//       } else if (route.component === 'InnerLink') {
-//         route.component = InnerLink
-//       } else {
-//         route.component = loadView(route.component)
-//       }
-//     }
-//     if (route.children != null && route.children && route.children.length) {
-//       route.children = filterAsyncRouter(route.children, route, type)
-//     } else {
-//       delete route['children']
-//       delete route['redirect']
-//     }
-//     return true
-//   })
-// }
-//
-// function filterChildren(childrenMap, lastRouter = false) {
-//   var children = []
-//   childrenMap.forEach((el, index) => {
-//     if (el.children && el.children.length) {
-//       if (el.component === 'ParentView' && !lastRouter) {
-//         el.children.forEach(c => {
-//           c.path = el.path + '/' + c.path
-//           if (c.children && c.children.length) {
-//             children = children.concat(filterChildren(c.children, c))
-//             return
-//           }
-//           children.push(c)
-//         })
-//         return
-//       }
-//     }
-//     if (lastRouter) {
-//       el.path = lastRouter.path + '/' + el.path
-//     }
-//     children = children.concat(el)
-//   })
-//   return children
-// }
-//
-// // 动态路由遍历,验证是否具备权限
-// export function filterDynamicRoutes(routes) {
-//   const res = []
-//   routes.forEach(route => {
-//     if (route.permissions) {
-//       if (auth.hasPermiOr(route.permissions)) {
-//         res.push(route)
-//       }
-//     } else if (route.roles) {
-//       if (auth.hasRoleOr(route.roles)) {
-//         res.push(route)
-//       }
-//     }
-//   })
-//   return res
-// }
-//
-// // export const loadView = (view) => {
-// //   if (process.env.NODE_ENV === 'development') {
-// //     return (resolve) => require([`@/views/${view}`], resolve)
-// //   } else {
-// //     // 使用 import 实现生产环境的路由懒加载
-// //     return () => import(`@/views/${view}`)
-// //   }
-// // }
-//
-// export default permission
+import auth from '@/plugins/auth'
+import router, { constantRoutes, dynamicRoutes } from '@/router'
+import Layout from '@/layout/index'
+import ParentView from '@/components/ParentView'
+import InnerLink from '@/layout/components/InnerLink'
+import request from '@/utils/request'
+
+const permission = {
+  state: {
+    routes: [],
+    addRoutes: [],
+    defaultRoutes: [],
+    topbarRouters: [],
+    sidebarRouters: []
+  },
+  mutations: {
+    SET_ROUTES: (state, routes) => {
+      state.addRoutes = routes
+      state.routes = constantRoutes.concat(routes)
+    },
+    SET_DEFAULT_ROUTES: (state, routes) => {
+      state.defaultRoutes = constantRoutes.concat(routes)
+    },
+    SET_TOPBAR_ROUTES: (state, routes) => {
+      state.topbarRouters = routes
+    },
+    SET_SIDEBAR_ROUTERS: (state, routes) => {
+      state.sidebarRouters = routes
+    },
+  },
+  actions: {
+    // 生成路由
+    GenerateRoutes({ commit }) {
+      return new Promise(resolve => {
+        // 向后端请求路由数据
+        request.get('/getRouters').then((res) => {
+          const sdata = JSON.parse(JSON.stringify(res.data))
+          const rdata = JSON.parse(JSON.stringify(res.data))
+          const sidebarRoutes = filterAsyncRouter(sdata)
+          const rewriteRoutes = filterAsyncRouter(rdata, false, true)
+          const asyncRoutes = filterDynamicRoutes(dynamicRoutes);
+          rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true })
+          router.addRoutes(asyncRoutes);
+          commit('SET_ROUTES', rewriteRoutes)
+          commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))
+          commit('SET_DEFAULT_ROUTES', sidebarRoutes)
+          commit('SET_TOPBAR_ROUTES', sidebarRoutes)
+          resolve(rewriteRoutes)
+        })
+      })
+    }
+  }
+}
+
+// 遍历后台传来的路由字符串,转换为组件对象
+function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
+  return asyncRouterMap.filter(route => {
+    if (type && route.children) {
+      route.children = filterChildren(route.children)
+    }
+    if (route.component) {
+      // Layout ParentView 组件特殊处理
+      if (route.component === 'Layout') {
+        route.component = Layout
+      } else if (route.component === 'ParentView') {
+        route.component = ParentView
+      } else if (route.component === 'InnerLink') {
+        route.component = InnerLink
+      } else {
+        route.component = loadView(route.component)
+      }
+    }
+    if (route.children != null && route.children && route.children.length) {
+      route.children = filterAsyncRouter(route.children, route, type)
+    } else {
+      delete route['children']
+      delete route['redirect']
+    }
+    return true
+  })
+}
+
+function filterChildren(childrenMap, lastRouter = false) {
+  var children = []
+  childrenMap.forEach((el, index) => {
+    if (el.children && el.children.length) {
+      if (el.component === 'ParentView' && !lastRouter) {
+        el.children.forEach(c => {
+          c.path = el.path + '/' + c.path
+          if (c.children && c.children.length) {
+            children = children.concat(filterChildren(c.children, c))
+            return
+          }
+          children.push(c)
+        })
+        return
+      }
+    }
+    if (lastRouter) {
+      el.path = lastRouter.path + '/' + el.path
+    }
+    children = children.concat(el)
+  })
+  return children
+}
+
+// 动态路由遍历,验证是否具备权限
+export function filterDynamicRoutes(routes) {
+  const res = []
+  routes.forEach(route => {
+    if (route.permissions) {
+      if (auth.hasPermiOr(route.permissions)) {
+        res.push(route)
+      }
+    } else if (route.roles) {
+      if (auth.hasRoleOr(route.roles)) {
+        res.push(route)
+      }
+    }
+  })
+  return res
+}
+
+export const loadView = (view) => {
+  if (process.env.NODE_ENV === 'development') {
+    return (resolve) => require([`@/views/${view}`], resolve)
+  } else {
+    //使用 import 实现生产环境的路由懒加载
+    return () => import(`@/views/${view}`)
+  }
+}
+
+export default permission

+ 22 - 0
ui/src/store/modules/user.js

@@ -1,12 +1,33 @@
 
 const state = {
   userinfo: ''
+  // token: getToken(),
+  // name: '',
+  // avatar: '',
+  // roles: [],
+  // permissions: []
 }
 
 const mutations = {
   updateUser: (state, user) => {
     state.userinfo = user
+    console.log(state.userinfo)
   }
+  // SET_TOKEN: (state, token) => {
+  //   state.token = token
+  // },
+  // SET_NAME: (state, name) => {
+  //   state.name = name
+  // },
+  // SET_AVATAR: (state, avatar) => {
+  //   state.avatar = avatar
+  // },
+  // SET_ROLES: (state, roles) => {
+  //   state.roles = roles
+  // },
+  // SET_PERMISSIONS: (state, permissions) => {
+  //   state.permissions = permissions
+  // }
 }
 
 const actions = {
@@ -27,4 +48,5 @@ export default {
   actions
 }
 
+// export default user
 

+ 1 - 0
ui/src/utils/menus.js

@@ -10,6 +10,7 @@ import Layout from '@/layout'
  * @param {JSON} menus
  */
 export default function generateMenus(menus) {
+
   // vue-router的菜单配置数据
   for (const menu of menus) {
     // 菜单类型(0:目录|1:菜单),目录则转换为布局组件

+ 123 - 43
ui/src/utils/request.js

@@ -1,61 +1,100 @@
 import axios from 'axios'
-import { MessageBox, Message } from 'element-ui'
-import store from '@/store'
-import { getToken, removeToken } from '@/utils/auth'
-import router, { resetRouter } from '@/router'
+import Vue from "vue";
+import {Message, MessageBox} from "element-ui";
 
-// create an axios instance
+import store from "@/store";
+import {removeToken} from "@/utils/auth";
+import router, {resetRouter} from "@/router";
+import {doEncrypt, doDecryptStr,doSign,doVerifySignature,userinfoEncrypt,userinfoDecrypt} from '@/utils/smutil'
 const service = axios.create({
   baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
   // withCredentials: true, // send cookies when cross-domain requests
   timeout: 1000 * 60 * 10 // request timeout
 })
 
-// request interceptor
 service.interceptors.request.use(
   config => {
-    alert(1)
-    // do something before request is sent
-    if (store.getters.token) {
-      // let each request carry token
-      // ['X-Token'] is a custom headers key
-      // please modify it according to the actual situation
-      config.headers['Authorization'] = getToken()
+    const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
+    // get请求映射params参数
+    if (config.method === 'get' && config.params) {
+      // 参数加密
+      let encryptParam = doEncrypt(JSON.stringify(config.params))
+      // 参数签名
+      let paramSign = doSign(JSON.stringify(config.params))
+      let result = 'secretData=' + encryptParam + '&paramSign=' + paramSign
+      config.params = result
+    }
+
+    if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put' || config.method === 'delete')) {
+      if (config.url != '/getMailCode') {
+        if (config.data !== undefined) {
+          // 参数加密
+          let encryptParam = doEncrypt(JSON.stringify(config.data))
+          // 参数签名
+          let paramSign = doSign(JSON.stringify(config.data))
+          const param = {
+            secretData: encryptParam,
+            paramSign: paramSign
+          }
+          config.data = param
+          if (config.url!='/user/login'){
+            config.headers.post['Content-Type'] = 'application/json';
+          }
+        }
+        else{
+          Message({
+            message: '请求拦截器检测出config.data是undefined不能提交!',
+            type: 'error',
+            duration: 5 * 1000
+          })
+          return
+        }
+      }
+    }
+
+    if (sessionStorage.getItem("token")!=="undefined" && sessionStorage.getItem("token")!==undefined && sessionStorage.getItem("token")!=null) { // 判断是否存在token,如果存在的话,则每个http header都加上token
+      let tokenStr = doEncrypt(sessionStorage.getItem("token"))
+      config.headers['Authorization'] = tokenStr
+      config.headers['TokenSign'] = doSign(sessionStorage.getItem("token"))
     }
     return config
   },
   error => {
     // do something with request error
-    console.log(error) // for debug
+    // console.log(error) // for debug
     return Promise.reject(error)
   }
 )
 
+// 是否正在刷新的标记
+let isRefreshing = false
+//重试队列
+let requests = []
 // response interceptor
 service.interceptors.response.use(
   /**
-   * If you want to get http information such as headers or status
-   * Please return  response => response
-  */
-
-  /**
    * Determine the request status by custom code
    * Here is just an example
    * You can also judge the status by HTTP Status Code
    */
   response => {
     const res = response.data
-    alert('request.js')
+    let returnStr = res.split("&")
+    let returnData = returnStr[0].split("=")[1]
+    let returnSign = returnStr[1].split("=")[1]
+    // 解密
+    let decData = doDecryptStr(returnData)
+    // 验签
+    let verifyResult = doVerifySignature(decData, returnSign)
+    if (!verifyResult) {
+      return Promise.reject(new Error('返回数据验签失败' || 'Error'))
+    }
+    let data = JSON.parse(decData)
     // if the custom code is not 20000, it is judged as an error.
-    if (res.code !== 0) {
-      Message({
-        message: res.message || 'Error',
-        type: 'error',
-        duration: 5 * 1000
-      })
-
+    //console.log(res.code)
+    if (data.code > 1) {
       // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
-      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
+      if (data.code === 50008 || data.code === 50012 || data.code === 50014) {
         // to re-login
         MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
           confirmButtonText: 'Re-Login',
@@ -67,24 +106,21 @@ service.interceptors.response.use(
           })
         })
       }
-      return Promise.reject(new Error(res.message || 'Error'))
+      return Promise.reject(new Error(data.message || 'Error'))
     } else {
-      return res
+      return data
     }
   },
-  error => {
+  async error => {
     if (error.response) {
       switch (error.response.status) {
         case 401:
-          console.log(error)
-          console.log(1111)
           console.log('用户验证失败!')
           // 返回 401 清除token信息并跳转到登录页面
           removeToken()
           resetRouter()
-          router.push('/login')
           Message({
-            message: error.response.data.data,
+            message: error.response.data,
             type: 'error',
             duration: 5 * 1000
           })
@@ -96,10 +132,11 @@ service.interceptors.response.use(
           resetRouter()
           router.push('/login')
           Message({
-            message: '登录超时',
+            message: error.response.data,
             type: 'error',
             duration: 5 * 1000
           })
+          return
           break
         case 500:
           Message({
@@ -115,15 +152,58 @@ service.interceptors.response.use(
           console.log('服务器关闭了!')
           resetRouter()
           break
+        case 410:
+          Message({
+            message: error.response.data,
+            type: 'error',
+            duration: 5 * 1000
+          })
+          break
+        case 409:
+          if (!isRefreshing) {
+            isRefreshing = true
+            //调用刷新token的接口
+            return await  Vue.prototype.$axios.post(
+              '/refreshToken'
+            ).then((res) => {
+              // const { token } = res.data
+              // 替换token
+              sessionStorage.setItem('token', res.data)
+              // Vue.prototype.$axios(error.response.config)
+              // 重新请求接口 前过期的接口
+              error.config.headers.Authorization = res.data;
+              requests.length > 0 && requests.map((cb) => {
+                cb();
+              });
+              requests = [];  //注意要清空
+              return Vue.prototype.$axios.request(error.config);
+              // return error.response.config
+            }).catch(err => {
+              console.log(err)
+              //跳到登录页
+              removeToken()
+              router.push('/login')
+              // return Promise.reject(err)
+            }).finally(() => {
+              isRefreshing = false
+            })
+          }
+          else {
+            // 正在刷新token ,把后来的接口缓冲起来
+            return new Promise((resolve) => {
+              requests.push(() => {
+                error.config.headers.Authorization = sessionStorage.getItem('token');
+                resolve(Vue.prototype.$axios.request(error.config));
+              });
+            })
+          }
+          break
+        // return Promise.reject(error.response.data)
       }
     }
-    /*    console.log('err' + error) // for debug
-    Message({
-      message: error.message,
-      type: 'error',
-      duration: 5 * 1000
-    })*/
-    return Promise.reject(error)
+    else{
+      return Promise.reject(error)
+    }
   }
 )
 

+ 87 - 0
ui/src/utils/smutil.js

@@ -0,0 +1,87 @@
+import {sm2 as sm5, sm2 as sm4, sm2 as sm3, sm2} from "sm-crypto"
+
+// 公钥Q
+let publicKey2 = '041967638ca43d4577d8dba166bff4437fde944270101f398a95b846ec2f8177d09f8abc5d62b6cd2c7216274d7abe0c8e04b0bb691207a32dd2e12d6bd2798672'
+// 私钥D
+let privateKey1 = '27ce6eec39dbf3b564a77c4da1e129fe1ba01a92f6d61055a33ed14ffcbc949e'
+
+
+// 加密:
+export function doEncrypt(msgString) {
+  let sm2 = require('sm-crypto').sm2;
+  // 1 - C1C3C2;	0 - C1C2C3;	默认为1
+  let cipherMode = 1
+
+  // 加密结果
+  let encryptData = sm2.doEncrypt(msgString, publicKey2, cipherMode);
+  // 加密后的密文前需要添加04,后端才能正常解密
+  // let encrypt = '04' + encryptData;
+  return encryptData;
+}
+
+// 解密
+export function doDecryptStr(enStr) {
+  let sm2 = require('sm-crypto').sm2;
+  // 1 - C1C3C2;	0 - C1C2C3;	默认为1
+  let cipherMode = 1
+  // 解密结果
+  let doDecrypt = sm2.doDecrypt(enStr, privateKey1, cipherMode);
+  // 解密后类型转换
+  // let objData = JSON.parse(doDecrypt)
+  return doDecrypt;
+}
+
+// 签名
+export function doSign(msgString) {
+  let sm2 = require('sm-crypto').sm2;
+  // 1 - C1C3C2;	0 - C1C2C3;	默认为1
+  let cipherMode = 1
+  // 签名
+  let sign = sm2.doSignature(msgString, privateKey1, {hash: true, der: true})
+  return sign;
+}
+
+// 验签
+export function doVerifySignature(msgString, sigValueHex) {
+  let sm2 = require('sm-crypto').sm2;
+  // 1 - C1C3C2;	0 - C1C2C3;	默认为1
+  let cipherMode = 1
+  // 签名
+  let verifyResult = sm2.doVerifySignature(msgString, sigValueHex, publicKey2, {hash: true, der: true}) // 验签结果
+  return verifyResult;
+}
+
+// 前端user信息加密
+export function userinfoEncrypt(str) {
+  let sm2 = require('sm-crypto').sm2;
+  let cipherMode = 1
+  // 加密
+  let encryptData = sm2.doEncrypt(str, '0460ff8c8c306fe62f6f9d11c5c82c30d10bbbc703da094e423072cac7dc663c97fad52eccb34f311f47a07f280de157ba4f2aa659cabe749121384b9376ea2ed2', cipherMode);
+  return encryptData
+}
+// 前端user信息解密
+export function userinfoDecrypt(str) {
+  let sm2 = require('sm-crypto').sm2;
+  let cipherMode = 1
+  // 加密
+  let decryptData = sm2.doDecrypt(str, "27ce6eec39dbf3b564a77c4da1e129fe1ba01a92f6d61055a33ed14ffcbc949e", cipherMode);
+  return decryptData
+}
+
+export function test() {
+  let sm2 = require('sm-crypto').sm2;
+  let cipherMode = 1
+  // 加密
+  let encryptData = sm2.doEncrypt('1122加密', '0460ff8c8c306fe62f6f9d11c5c82c30d10bbbc703da094e423072cac7dc663c97fad52eccb34f311f47a07f280de157ba4f2aa659cabe749121384b9376ea2ed2', cipherMode);
+  let sm3 = require('sm-crypto').sm2;
+  // 签名
+  let sign = sm3.doSignature('1122加密', '6155d63ee27cbeca07f3e40c4f8856f1be8119fcbda1aadc7e0e595e52bad7bd')
+  // 解密
+  let sm4 = require('sm-crypto').sm2;
+  let doDecrypt = sm4.doDecrypt(encryptData, privateKey1, cipherMode);
+  alert('解密:' + doDecrypt)
+  // 验签
+  let sm5 = require('sm-crypto').sm2;
+  let verifyResult = sm5.doVerifySignature(doDecrypt, sign, publicKey2) // 验签结果
+  alert('验签:' + verifyResult)
+}

+ 2 - 2
ui/src/views/dashboard/index.vue

@@ -6,7 +6,7 @@
 
 
 import {mapGetters} from "vuex";
-import {userinfoEncrypt} from "@/main";
+import {doEncrypt, doDecryptStr,doSign,doVerifySignature,userinfoEncrypt,userinfoDecrypt} from '@/utils/smutil'
 
 export default {
   computed: {
@@ -29,7 +29,7 @@ export default {
         // 用户信息加密存储
         let encryptUserInfo = userinfoEncrypt(JSON.stringify(res.data))
         this.$store.dispatch('user/changeSetting',encryptUserInfo)
-
+        alert('首页加密用户信息存储')
         var user = res.data
         if (user.lastUpdatePwdTime==null){
             this.$message({

+ 2 - 2
ui/src/views/monitor/server/index.vue

@@ -319,10 +319,10 @@ export default {
   methods: {
     /** 查询服务器信息 */
     getList() {
-      this.$axios.get('/monitor/server').then((res) => {
+      getServer().then(response => {
         this.server = response.data;
         this.$modal.closeLoading();
-      })
+      });
     },
     // 打开加载层
     openLoading() {

+ 1 - 1
ui/src/views/sysManager/userManager/profile/index.vue

@@ -44,7 +44,7 @@
 
 import resetPwd from "./resetPwd";
 import { mapGetters } from 'vuex'
-import {userinfoDecrypt} from "@/main";
+import {doEncrypt, doDecryptStr,doSign,doVerifySignature,userinfoEncrypt,userinfoDecrypt} from '@/utils/smutil'
 export default {
   computed: {
     ...mapGetters([

+ 1 - 1
ui/src/views/sysManager/userManager/profile/resetPwd.vue

@@ -18,7 +18,7 @@
 
 <script>
 import { mapGetters } from 'vuex'
-import {userinfoDecrypt} from "@/main";
+import {doEncrypt, doDecryptStr,doSign,doVerifySignature,userinfoEncrypt,userinfoDecrypt} from '@/utils/smutil'
 import { debounce } from 'lodash'
 export default {
   computed: {

+ 1 - 1
ui/vue.config.js

@@ -28,7 +28,7 @@ module.exports = {
   outputDir: 'dist',
   assetsDir: 'assets',
   // lintOnSave: process.env.NODE_ENV === 'development',
-  lintOnSave: process.env.NODE_ENV === 'development',
+  lintOnSave: false,
   productionSourceMap: false,
   pages: {
     index: {