Browse Source

提交版本

李云瑞 1 year ago
parent
commit
8a9925e013
15 changed files with 1438 additions and 0 deletions
  1. 196 0
      jeecg-boot-base-core/src/main/java/org/jeecg/common/util/dynamic/db/FreemarkerParseFactory.java
  2. BIN
      jeecg-boot-base-core/target/classes/org/jeecg/common/util/dynamic/db/FreemarkerParseFactory.class
  3. 107 0
      jeecg-module-demo/src/main/resources/static/bigscreen/template2/js/layer/layim/data/friend.json
  4. 107 0
      jeecg-module-demo/target/classes/static/bigscreen/template2/js/layer/layim/data/friend.json
  5. BIN
      jeecg-module-system/jeecg-system-biz/src/main/resources/static/generic/web/cmaps/GB-EUC-H.bcmap
  6. 89 0
      jeecg-server-cloud/jeecg-cloud-gateway/src/main/java/org/jeecg/config/GatewayRoutersConfig.java
  7. 45 0
      jeecg-server-cloud/jeecg-cloud-gateway/src/main/java/org/jeecg/fallback/sentinel/GatewaySentinelExceptionConfig.java
  8. 21 0
      jeecg-server-cloud/jeecg-cloud-gateway/src/main/java/org/jeecg/loader/vo/GatewayRouteVo.java
  9. 260 0
      jeecg-server-cloud/jeecg-visual/jeecg-cloud-sentinel/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/gateway/GatewayApiController.java
  10. 431 0
      jeecg-server-cloud/jeecg-visual/jeecg-cloud-sentinel/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/gateway/GatewayFlowRuleController.java
  11. 35 0
      jeecg-server-cloud/jeecg-visual/jeecg-cloud-sentinel/src/main/java/com/alibaba/csp/sentinel/dashboard/rule/nacos/gateway/GateWayApiNacosProvider.java
  12. 35 0
      jeecg-server-cloud/jeecg-visual/jeecg-cloud-sentinel/src/main/java/com/alibaba/csp/sentinel/dashboard/rule/nacos/gateway/GateWayApiNacosPublisher.java
  13. 40 0
      jeecg-server-cloud/jeecg-visual/jeecg-cloud-sentinel/src/main/java/com/alibaba/csp/sentinel/dashboard/rule/nacos/gateway/GateWayFlowRulesNacosProvider.java
  14. 41 0
      jeecg-server-cloud/jeecg-visual/jeecg-cloud-sentinel/src/main/java/com/alibaba/csp/sentinel/dashboard/rule/nacos/gateway/GateWayFlowRulesNacosPublisher.java
  15. 31 0
      jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/util/FtlUtil.java

+ 196 - 0
jeecg-boot-base-core/src/main/java/org/jeecg/common/util/dynamic/db/FreemarkerParseFactory.java

@@ -0,0 +1,196 @@
+package org.jeecg.common.util.dynamic.db;
+
+import freemarker.cache.StringTemplateLoader;
+import freemarker.core.ParseException;
+import freemarker.core.TemplateClassResolver;
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.jeecg.common.constant.DataBaseConstant;
+import org.jeecg.common.constant.SymbolConstant;
+import org.jeecgframework.codegenerate.generate.util.SimpleFormat;
+
+import java.io.StringWriter;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * @author 赵俊夫
+ * @version V1.0
+ * @Title:FreemarkerHelper
+ * @description:Freemarker引擎协助类
+ * @date Jul 5, 2013 2:58:29 PM
+ */
+@Slf4j
+public class FreemarkerParseFactory {
+
+    private static final String ENCODE = "utf-8";
+    /**
+     * 参数格式化工具类
+     */
+    private static final String MINI_DAO_FORMAT = "DaoFormat";
+
+    /**
+     * 文件缓存
+     */
+    private static final Configuration TPL_CONFIG = new Configuration();
+    /**
+     * SQL 缓存
+     */
+    private static final Configuration SQL_CONFIG = new Configuration();
+
+    private static StringTemplateLoader stringTemplateLoader = new StringTemplateLoader();
+
+    /**使用内嵌的(?ms)打开单行和多行模式*/
+    private final static Pattern NOTES_PATTERN = Pattern
+            .compile("(?ms)/\\*.*?\\*/|^\\s*//.*?$");
+
+    static {
+        TPL_CONFIG.setClassForTemplateLoading(
+                new FreemarkerParseFactory().getClass(), "/");
+        TPL_CONFIG.setNumberFormat("0.#####################");
+        SQL_CONFIG.setTemplateLoader(stringTemplateLoader);
+        SQL_CONFIG.setNumberFormat("0.#####################");
+        //classic_compatible设置,解决报空指针错误
+        SQL_CONFIG.setClassicCompatible(true);
+
+        //update-begin-author:taoyan date:2022-8-10 for: freemarker模板注入问题 禁止解析ObjectConstructor,Execute和freemarker.template.utility.JythonRuntime。
+        //https://ackcent.com/in-depth-freemarker-template-injection/
+        SQL_CONFIG.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);
+        //update-end-author:taoyan date:2022-8-10 for: freemarker模板注入问题 禁止解析ObjectConstructor,Execute和freemarker.template.utility.JythonRuntime。
+    }
+
+    /**
+     * 判断模板是否存在
+     *
+     * @throws Exception
+     */
+    public static boolean isExistTemplate(String tplName) throws Exception {
+        try {
+            Template mytpl = TPL_CONFIG.getTemplate(tplName, "UTF-8");
+            if (mytpl == null) {
+                return false;
+            }
+        } catch (Exception e) {
+            //update-begin--Author:scott  Date:20180320 for:解决问题 - 错误提示sql文件不存在,实际问题是sql freemarker用法错误-----
+            if (e instanceof ParseException) {
+                log.error(e.getMessage(), e.fillInStackTrace());
+                throw new Exception(e);
+            }
+            log.debug("----isExistTemplate----" + e.toString());
+            //update-end--Author:scott  Date:20180320 for:解决问题 - 错误提示sql文件不存在,实际问题是sql freemarker用法错误------
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 解析ftl模板
+     *
+     * @param tplName 模板名
+     * @param paras   参数
+     * @return
+     */
+    public static String parseTemplate(String tplName, Map<String, Object> paras) {
+        try {
+            log.debug(" minidao sql templdate : " + tplName);
+            StringWriter swriter = new StringWriter();
+            Template mytpl = TPL_CONFIG.getTemplate(tplName, ENCODE);
+            if (paras.containsKey(MINI_DAO_FORMAT)) {
+                throw new RuntimeException("DaoFormat 是 minidao 保留关键字,不允许使用 ,请更改参数定义!");
+            }
+            paras.put(MINI_DAO_FORMAT, new SimpleFormat());
+            mytpl.process(paras, swriter);
+            String sql = getSqlText(swriter.toString());
+            paras.remove(MINI_DAO_FORMAT);
+            return sql;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e.fillInStackTrace());
+            log.error("发送一次的模板key:{ " + tplName + " }");
+            //System.err.println(e.getMessage());
+            //System.err.println("模板名:{ "+ tplName +" }");
+            throw new RuntimeException("解析SQL模板异常");
+        }
+    }
+
+    /**
+     * 解析ftl
+     *
+     * @param tplContent 模板内容
+     * @param paras      参数
+     * @return String 模板解析后内容
+     */
+    public static String parseTemplateContent(String tplContent,Map<String, Object> paras) {
+        return parseTemplateContent(tplContent, paras, false);
+    }
+    public static String parseTemplateContent(String tplContent, Map<String, Object> paras, boolean keepSpace) {
+        try {
+            String sqlUnderline="sql_";
+            StringWriter swriter = new StringWriter();
+            if (stringTemplateLoader.findTemplateSource(sqlUnderline + tplContent.hashCode()) == null) {
+                stringTemplateLoader.putTemplate(sqlUnderline + tplContent.hashCode(), tplContent);
+            }
+            Template mytpl = SQL_CONFIG.getTemplate(sqlUnderline + tplContent.hashCode(), ENCODE);
+            if (paras.containsKey(MINI_DAO_FORMAT)) {
+                throw new RuntimeException("DaoFormat 是 minidao 保留关键字,不允许使用 ,请更改参数定义!");
+            }
+            paras.put(MINI_DAO_FORMAT, new SimpleFormat());
+            mytpl.process(paras, swriter);
+            String sql = getSqlText(swriter.toString(), keepSpace);
+            paras.remove(MINI_DAO_FORMAT);
+            return sql;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e.fillInStackTrace());
+            log.error("发送一次的模板key:{ " + tplContent + " }");
+            //System.err.println(e.getMessage());
+            //System.err.println("模板内容:{ "+ tplContent +" }");
+            throw new RuntimeException("解析SQL模板异常");
+        }
+    }
+
+    /**
+     * 除去无效字段,去掉注释 不然批量处理可能报错 去除无效的等于
+     */
+    private static String getSqlText(String sql) {
+        return getSqlText(sql, false);
+    }
+
+    private static String getSqlText(String sql, boolean keepSpace) {
+        // 将注释替换成""
+        sql = NOTES_PATTERN.matcher(sql).replaceAll("");
+        if (!keepSpace) {
+            sql = sql.replaceAll("\\n", " ").replaceAll("\\t", " ")
+                    .replaceAll("\\s{1,}", " ").trim();
+        }
+        // 去掉 最后是 where这样的问题
+        //where空格 "where "
+        String whereSpace = DataBaseConstant.SQL_WHERE+" ";
+        //"where and"
+        String whereAnd = DataBaseConstant.SQL_WHERE+" and";
+        //", where"
+        String commaWhere = SymbolConstant.COMMA+" "+DataBaseConstant.SQL_WHERE;
+        //", "
+        String commaSpace = SymbolConstant.COMMA + " ";
+        if (sql.endsWith(DataBaseConstant.SQL_WHERE) || sql.endsWith(whereSpace)) {
+            sql = sql.substring(0, sql.lastIndexOf("where"));
+        }
+        // 去掉where and 这样的问题
+        int index = 0;
+        while ((index = StringUtils.indexOfIgnoreCase(sql, whereAnd, index)) != -1) {
+            sql = sql.substring(0, index + 5)
+                    + sql.substring(index + 9, sql.length());
+        }
+        // 去掉 , where 这样的问题
+        index = 0;
+        while ((index = StringUtils.indexOfIgnoreCase(sql, commaWhere, index)) != -1) {
+            sql = sql.substring(0, index)
+                    + sql.substring(index + 1, sql.length());
+        }
+        // 去掉 最后是 ,这样的问题
+        if (sql.endsWith(SymbolConstant.COMMA) || sql.endsWith(commaSpace)) {
+            sql = sql.substring(0, sql.lastIndexOf(","));
+        }
+        return sql;
+    }
+}

BIN
jeecg-boot-base-core/target/classes/org/jeecg/common/util/dynamic/db/FreemarkerParseFactory.class


+ 107 - 0
jeecg-module-demo/src/main/resources/static/bigscreen/template2/js/layer/layim/data/friend.json

@@ -0,0 +1,107 @@
+{
+    "status": 1,
+    "msg": "ok",
+    "data": [
+        {
+            "name": "销售部",
+            "nums": 36,
+            "id": 1,
+            "item": [
+                {
+                    "id": "100001",
+                    "name": "郭敬明",
+                    "face": "img/a5.jpg"
+                },
+                {
+                    "id": "100002",
+                    "name": "作家崔成浩",
+                    "face": "img/a6.jpg"
+                },
+                {
+                    "id": "1000022",
+                    "name": "韩寒",
+                    "face": "img/a7.jpg"
+                },
+                {
+                    "id": "10000222",
+                    "name": "范爷",
+                    "face": "img/a8.jpg"
+                },
+                {
+                    "id": "100002222",
+                    "name": "小马哥",
+                    "face": "img/a9.jpg"
+                }
+            ]
+        },
+        {
+            "name": "大学同窗",
+            "nums": 16,
+            "id": 2,
+            "item": [
+                {
+                    "id": "1000033",
+                    "name": "苏醒",
+                    "face": "img/a9.jpg"
+                },
+                {
+                    "id": "10000333",
+                    "name": "马云",
+                    "face": "img/a8.jpg"
+                },
+                {
+                    "id": "100003",
+                    "name": "鬼脚七",
+                    "face": "img/a7.jpg"
+                },
+                {
+                    "id": "100004",
+                    "name": "谢楠",
+                    "face": "img/a6.jpg"
+                },
+                {
+                    "id": "100005",
+                    "name": "徐峥",
+                    "face": "img/a5.jpg"
+                }
+            ]
+        },
+        {
+            "name": "H+后台主题",
+            "nums": 38,
+            "id": 3,
+            "item": [
+                {
+                    "id": "100006",
+                    "name": "柏雪近在它香",
+                    "face": "img/a4.jpg"
+                },
+                {
+                    "id": "100007",
+                    "name": "罗昌平",
+                    "face": "img/a3.jpg"
+                },
+                {
+                    "id": "100008",
+                    "name": "Crystal影子",
+                    "face": "img/a2.jpg"
+                },
+                {
+                    "id": "100009",
+                    "name": "艺小想",
+                    "face": "img/a1.jpg"
+                },
+                {
+                    "id": "100010",
+                    "name": "天猫",
+                    "face": "img/a8.jpg"
+                },
+                {
+                    "id": "100011",
+                    "name": "张泉灵",
+                    "face": "img/a7.jpg"
+                }
+            ]
+        }
+    ]
+}

+ 107 - 0
jeecg-module-demo/target/classes/static/bigscreen/template2/js/layer/layim/data/friend.json

@@ -0,0 +1,107 @@
+{
+    "status": 1,
+    "msg": "ok",
+    "data": [
+        {
+            "name": "销售部",
+            "nums": 36,
+            "id": 1,
+            "item": [
+                {
+                    "id": "100001",
+                    "name": "郭敬明",
+                    "face": "img/a5.jpg"
+                },
+                {
+                    "id": "100002",
+                    "name": "作家崔成浩",
+                    "face": "img/a6.jpg"
+                },
+                {
+                    "id": "1000022",
+                    "name": "韩寒",
+                    "face": "img/a7.jpg"
+                },
+                {
+                    "id": "10000222",
+                    "name": "范爷",
+                    "face": "img/a8.jpg"
+                },
+                {
+                    "id": "100002222",
+                    "name": "小马哥",
+                    "face": "img/a9.jpg"
+                }
+            ]
+        },
+        {
+            "name": "大学同窗",
+            "nums": 16,
+            "id": 2,
+            "item": [
+                {
+                    "id": "1000033",
+                    "name": "苏醒",
+                    "face": "img/a9.jpg"
+                },
+                {
+                    "id": "10000333",
+                    "name": "马云",
+                    "face": "img/a8.jpg"
+                },
+                {
+                    "id": "100003",
+                    "name": "鬼脚七",
+                    "face": "img/a7.jpg"
+                },
+                {
+                    "id": "100004",
+                    "name": "谢楠",
+                    "face": "img/a6.jpg"
+                },
+                {
+                    "id": "100005",
+                    "name": "徐峥",
+                    "face": "img/a5.jpg"
+                }
+            ]
+        },
+        {
+            "name": "H+后台主题",
+            "nums": 38,
+            "id": 3,
+            "item": [
+                {
+                    "id": "100006",
+                    "name": "柏雪近在它香",
+                    "face": "img/a4.jpg"
+                },
+                {
+                    "id": "100007",
+                    "name": "罗昌平",
+                    "face": "img/a3.jpg"
+                },
+                {
+                    "id": "100008",
+                    "name": "Crystal影子",
+                    "face": "img/a2.jpg"
+                },
+                {
+                    "id": "100009",
+                    "name": "艺小想",
+                    "face": "img/a1.jpg"
+                },
+                {
+                    "id": "100010",
+                    "name": "天猫",
+                    "face": "img/a8.jpg"
+                },
+                {
+                    "id": "100011",
+                    "name": "张泉灵",
+                    "face": "img/a7.jpg"
+                }
+            ]
+        }
+    ]
+}

BIN
jeecg-module-system/jeecg-system-biz/src/main/resources/static/generic/web/cmaps/GB-EUC-H.bcmap


+ 89 - 0
jeecg-server-cloud/jeecg-cloud-gateway/src/main/java/org/jeecg/config/GatewayRoutersConfig.java

@@ -0,0 +1,89 @@
+package org.jeecg.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author scott
+ * @date 2020/05/26
+ * 路由配置信息
+ */
+@Configuration
+@RefreshScope
+public class GatewayRoutersConfig {
+    /**
+     * 路由配置方式:database,yml,nacos
+     */
+    public String dataType;
+    public String serverAddr;
+    public String namespace;
+    public String dataId;
+    public String routeGroup;
+    public String username;
+    public String password;
+
+    @Value("${jeecg.route.config.data-type:#{null}}")
+    public void setDataType(String dataType) {
+        this.dataType = dataType;
+    }
+    
+    @Value("${jeecg.route.config.data-id:#{null}}")
+    public void setRouteDataId(String dataId) {
+        this.dataId = dataId + ".json";
+    }
+
+    @Value("${jeecg.route.config.group:DEFAULT_GROUP:#{null}}")
+    public void setRouteGroup(String routeGroup) {
+        this.routeGroup = routeGroup;
+    }
+
+    @Value("${spring.cloud.nacos.discovery.server-addr}")
+    public void setServerAddr(String serverAddr) {
+        this.serverAddr = serverAddr;
+    }
+
+    @Value("${spring.cloud.nacos.discovery.namespace:#{null}}")
+    public void setNamespace(String namespace) {
+        this.namespace = namespace;
+    }
+
+    @Value("${spring.cloud.nacos.config.username:#{null}}")
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    @Value("${spring.cloud.nacos.config.password:#{null}}")
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public String getDataType() {
+        return dataType;
+    }
+
+    public String getServerAddr() {
+        return serverAddr;
+    }
+
+    public String getNamespace() {
+        return namespace;
+    }
+
+    public String getDataId() {
+        return dataId;
+    }
+
+    public String getRouteGroup() {
+        return routeGroup;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+}

+ 45 - 0
jeecg-server-cloud/jeecg-cloud-gateway/src/main/java/org/jeecg/fallback/sentinel/GatewaySentinelExceptionConfig.java

@@ -0,0 +1,45 @@
+package org.jeecg.fallback.sentinel;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
+import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
+import org.jeecg.common.enums.SentinelErrorInfoEnum;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.web.reactive.function.BodyInserters;
+import org.springframework.web.reactive.function.server.ServerResponse;
+
+import javax.annotation.PostConstruct;
+import java.util.HashMap;
+
+/**
+ * @Description: 自定义Sentinel全局异常(需要启动Sentinel客户端)
+ * @author: zyf
+ * @date: 2022/02/18
+ * @version: V1.0
+ */
+@Configuration
+public class GatewaySentinelExceptionConfig {
+
+    @PostConstruct
+    public void init() {
+
+        BlockRequestHandler blockRequestHandler = (serverWebExchange, ex) -> {
+            String msg;
+            SentinelErrorInfoEnum errorInfoEnum = SentinelErrorInfoEnum.getErrorByException(ex);
+            if (ObjectUtil.isNotEmpty(errorInfoEnum)) {
+                msg = errorInfoEnum.getError();
+            } else {
+                msg = "未知限流降级";
+            }
+            HashMap<String, String> map = new HashMap(5);
+            map.put("code", HttpStatus.TOO_MANY_REQUESTS.toString());
+            map.put("message", msg);
+            //自定义异常处理
+            return ServerResponse.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(map));
+        };
+
+        GatewayCallbackManager.setBlockHandler(blockRequestHandler);
+    }
+}

+ 21 - 0
jeecg-server-cloud/jeecg-cloud-gateway/src/main/java/org/jeecg/loader/vo/GatewayRouteVo.java

@@ -0,0 +1,21 @@
+package org.jeecg.loader.vo;
+
+import lombok.Data;
+
+/**
+ * 路由参数模型
+ * @author zyf
+ * @date: 2022/4/21 10:55
+ */
+@Data
+public class GatewayRouteVo {
+    private String id;
+    private String name;
+    private String uri;
+    private String predicates;
+    private String filters;
+    private Integer stripPrefix;
+    private Integer retryable;
+    private Integer persist;
+    private Integer status;
+}

+ 260 - 0
jeecg-server-cloud/jeecg-visual/jeecg-cloud-sentinel/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/gateway/GatewayApiController.java

@@ -0,0 +1,260 @@
+package com.alibaba.csp.sentinel.dashboard.controller.gateway;
+
+import com.alibaba.csp.sentinel.dashboard.auth.AuthAction;
+import com.alibaba.csp.sentinel.dashboard.auth.AuthService;
+import com.alibaba.csp.sentinel.dashboard.controller.base.BaseRuleController;
+import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiDefinitionEntity;
+import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiPredicateItemEntity;
+import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo;
+import com.alibaba.csp.sentinel.dashboard.domain.Result;
+import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.api.AddApiReqVo;
+import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.api.ApiPredicateItemVo;
+import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.api.UpdateApiReqVo;
+import com.alibaba.csp.sentinel.dashboard.repository.gateway.InMemApiDefinitionStore;
+import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
+import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
+import com.alibaba.csp.sentinel.util.StringUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.*;
+
+import static com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants.*;
+
+/**
+ * 网关API规则控制器
+ *
+ * @author zyf
+ * @date 2022-04-13
+ */
+@RestController
+@RequestMapping(value = "/gateway/api")
+public class GatewayApiController extends BaseRuleController {
+
+    private final Logger logger = LoggerFactory.getLogger(GatewayApiController.class);
+
+    @Autowired
+    private InMemApiDefinitionStore repository;
+
+
+    @Autowired
+    @Qualifier("gateWayApiNacosProvider")
+    private DynamicRuleProvider<List<ApiDefinitionEntity>> apiProvider;
+
+    @Autowired
+    @Qualifier("gateWayApiNacosPublisher")
+    private DynamicRulePublisher<List<ApiDefinitionEntity>> apiPublisher;
+
+    @GetMapping("/list.json")
+    @AuthAction(AuthService.PrivilegeType.READ_RULE)
+    public Result<List<ApiDefinitionEntity>> queryApis(String app, String ip, Integer port) {
+        if (StringUtil.isEmpty(app)) {
+            return Result.ofFail(-1, "app can't be null or empty");
+        }
+        if (StringUtil.isEmpty(ip)) {
+            return Result.ofFail(-1, "ip can't be null or empty");
+        }
+        if (port == null) {
+            return Result.ofFail(-1, "port can't be null");
+        }
+
+        try {
+            List<ApiDefinitionEntity> apis = apiProvider.getRules(app);
+            repository.saveAll(apis);
+            return Result.ofSuccess(apis);
+        } catch (Throwable throwable) {
+            logger.error("queryApis error:", throwable);
+            return Result.ofThrowable(-1, throwable);
+        }
+    }
+
+    @PostMapping("/new.json")
+    @AuthAction(AuthService.PrivilegeType.WRITE_RULE)
+    public Result<ApiDefinitionEntity> addApi(HttpServletRequest request, @RequestBody AddApiReqVo reqVo) {
+
+        String app = reqVo.getApp();
+        if (StringUtil.isBlank(app)) {
+            return Result.ofFail(-1, "app can't be null or empty");
+        }
+
+        ApiDefinitionEntity entity = new ApiDefinitionEntity();
+        entity.setApp(app.trim());
+
+        String ip = reqVo.getIp();
+        if (StringUtil.isBlank(ip)) {
+            return Result.ofFail(-1, "ip can't be null or empty");
+        }
+        entity.setIp(ip.trim());
+
+        Integer port = reqVo.getPort();
+        if (port == null) {
+            return Result.ofFail(-1, "port can't be null");
+        }
+        entity.setPort(port);
+
+        // API名称
+        String apiName = reqVo.getApiName();
+        if (StringUtil.isBlank(apiName)) {
+            return Result.ofFail(-1, "apiName can't be null or empty");
+        }
+        entity.setApiName(apiName.trim());
+
+        // 匹配规则列表
+        List<ApiPredicateItemVo> predicateItems = reqVo.getPredicateItems();
+        if (CollectionUtils.isEmpty(predicateItems)) {
+            return Result.ofFail(-1, "predicateItems can't empty");
+        }
+
+        List<ApiPredicateItemEntity> predicateItemEntities = new ArrayList<>();
+        for (ApiPredicateItemVo predicateItem : predicateItems) {
+            ApiPredicateItemEntity predicateItemEntity = new ApiPredicateItemEntity();
+
+            // 匹配模式
+            Integer matchStrategy = predicateItem.getMatchStrategy();
+            if (!Arrays.asList(URL_MATCH_STRATEGY_EXACT, URL_MATCH_STRATEGY_PREFIX, URL_MATCH_STRATEGY_REGEX).contains(matchStrategy)) {
+                return Result.ofFail(-1, "invalid matchStrategy: " + matchStrategy);
+            }
+            predicateItemEntity.setMatchStrategy(matchStrategy);
+
+            // 匹配串
+            String pattern = predicateItem.getPattern();
+            if (StringUtil.isBlank(pattern)) {
+                return Result.ofFail(-1, "pattern can't be null or empty");
+            }
+            predicateItemEntity.setPattern(pattern);
+
+            predicateItemEntities.add(predicateItemEntity);
+        }
+        entity.setPredicateItems(new LinkedHashSet<>(predicateItemEntities));
+
+        // 检查API名称不能重复
+        List<ApiDefinitionEntity> allApis = repository.findAllByMachine(MachineInfo.of(app.trim(), ip.trim(), port));
+        if (allApis.stream().map(o -> o.getApiName()).anyMatch(o -> o.equals(apiName.trim()))) {
+            return Result.ofFail(-1, "apiName exists: " + apiName);
+        }
+
+        Date date = new Date();
+        entity.setGmtCreate(date);
+        entity.setGmtModified(date);
+
+        try {
+            entity = repository.save(entity);
+        } catch (Throwable throwable) {
+            logger.error("add gateway api error:", throwable);
+            return Result.ofThrowable(-1, throwable);
+        }
+
+        if (!publishApis(app, ip, port)) {
+            logger.warn("publish gateway apis fail after add");
+        }
+
+        return Result.ofSuccess(entity);
+    }
+
+    @PostMapping("/save.json")
+    @AuthAction(AuthService.PrivilegeType.WRITE_RULE)
+    public Result<ApiDefinitionEntity> updateApi(@RequestBody UpdateApiReqVo reqVo) {
+        String app = reqVo.getApp();
+        if (StringUtil.isBlank(app)) {
+            return Result.ofFail(-1, "app can't be null or empty");
+        }
+
+        Long id = reqVo.getId();
+        if (id == null) {
+            return Result.ofFail(-1, "id can't be null");
+        }
+
+        ApiDefinitionEntity entity = repository.findById(id);
+        if (entity == null) {
+            return Result.ofFail(-1, "api does not exist, id=" + id);
+        }
+
+        // 匹配规则列表
+        List<ApiPredicateItemVo> predicateItems = reqVo.getPredicateItems();
+        if (CollectionUtils.isEmpty(predicateItems)) {
+            return Result.ofFail(-1, "predicateItems can't empty");
+        }
+
+        List<ApiPredicateItemEntity> predicateItemEntities = new ArrayList<>();
+        for (ApiPredicateItemVo predicateItem : predicateItems) {
+            ApiPredicateItemEntity predicateItemEntity = new ApiPredicateItemEntity();
+
+            // 匹配模式
+            int matchStrategy = predicateItem.getMatchStrategy();
+            if (!Arrays.asList(URL_MATCH_STRATEGY_EXACT, URL_MATCH_STRATEGY_PREFIX, URL_MATCH_STRATEGY_REGEX).contains(matchStrategy)) {
+                return Result.ofFail(-1, "Invalid matchStrategy: " + matchStrategy);
+            }
+            predicateItemEntity.setMatchStrategy(matchStrategy);
+
+            // 匹配串
+            String pattern = predicateItem.getPattern();
+            if (StringUtil.isBlank(pattern)) {
+                return Result.ofFail(-1, "pattern can't be null or empty");
+            }
+            predicateItemEntity.setPattern(pattern);
+
+            predicateItemEntities.add(predicateItemEntity);
+        }
+        entity.setPredicateItems(new LinkedHashSet<>(predicateItemEntities));
+
+        Date date = new Date();
+        entity.setGmtModified(date);
+
+        try {
+            entity = repository.save(entity);
+        } catch (Throwable throwable) {
+            logger.error("update gateway api error:", throwable);
+            return Result.ofThrowable(-1, throwable);
+        }
+
+        if (!publishApis(app, entity.getIp(), entity.getPort())) {
+            logger.warn("publish gateway apis fail after update");
+        }
+
+        return Result.ofSuccess(entity);
+    }
+
+    @PostMapping("/delete.json")
+    @AuthAction(AuthService.PrivilegeType.DELETE_RULE)
+    public Result<Long> deleteApi(Long id) {
+        if (id == null) {
+            return Result.ofFail(-1, "id can't be null");
+        }
+
+        ApiDefinitionEntity oldEntity = repository.findById(id);
+        if (oldEntity == null) {
+            return Result.ofSuccess(null);
+        }
+
+        try {
+            repository.delete(id);
+        } catch (Throwable throwable) {
+            logger.error("delete gateway api error:", throwable);
+            return Result.ofThrowable(-1, throwable);
+        }
+
+        if (!publishApis(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) {
+            logger.warn("publish gateway apis fail after delete");
+        }
+        return Result.ofSuccess(id);
+    }
+
+    private boolean publishApis(String app, String ip, Integer port) {
+        List<ApiDefinitionEntity> apis = repository.findAllByApp(app);
+        try {
+            apiPublisher.publish(app, apis);
+            //延迟加载
+            delayTime();
+            return true;
+        } catch (Exception e) {
+            logger.error("publish api error!");
+            e.printStackTrace();
+            return false;
+        }
+    }
+}

+ 431 - 0
jeecg-server-cloud/jeecg-visual/jeecg-cloud-sentinel/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/gateway/GatewayFlowRuleController.java

@@ -0,0 +1,431 @@
+package com.alibaba.csp.sentinel.dashboard.controller.gateway;
+
+import com.alibaba.csp.sentinel.dashboard.auth.AuthAction;
+import com.alibaba.csp.sentinel.dashboard.auth.AuthService;
+import com.alibaba.csp.sentinel.dashboard.controller.base.BaseRuleController;
+import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayFlowRuleEntity;
+import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayParamFlowItemEntity;
+import com.alibaba.csp.sentinel.dashboard.domain.Result;
+import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.rule.AddFlowRuleReqVo;
+import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.rule.GatewayParamFlowItemVo;
+import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.rule.UpdateFlowRuleReqVo;
+import com.alibaba.csp.sentinel.dashboard.repository.gateway.InMemGatewayFlowRuleStore;
+import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
+import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
+import com.alibaba.csp.sentinel.util.StringUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import static com.alibaba.csp.sentinel.slots.block.RuleConstant.*;
+import static com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants.*;
+import static com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayFlowRuleEntity.*;
+
+/**
+ * 网关限流规则控制器
+ *
+ * @author zyf
+ * @date 2022-04-13
+ */
+@RestController
+@RequestMapping(value = "/gateway/flow")
+public class GatewayFlowRuleController extends BaseRuleController {
+
+    private final Logger logger = LoggerFactory.getLogger(GatewayFlowRuleController.class);
+
+    @Autowired
+    private InMemGatewayFlowRuleStore repository;
+
+    @Autowired
+    @Qualifier("gateWayFlowRulesNacosProvider")
+    private DynamicRuleProvider<List<GatewayFlowRuleEntity>> ruleProvider;
+
+    @Autowired
+    @Qualifier("gateWayFlowRulesNacosPublisher")
+    private DynamicRulePublisher<List<GatewayFlowRuleEntity>> rulePublisher;
+
+    @GetMapping("/list.json")
+    @AuthAction(AuthService.PrivilegeType.READ_RULE)
+    public Result<List<GatewayFlowRuleEntity>> queryFlowRules(String app, String ip, Integer port) {
+
+        if (StringUtil.isEmpty(app)) {
+            return Result.ofFail(-1, "app can't be null or empty");
+        }
+        if (StringUtil.isEmpty(ip)) {
+            return Result.ofFail(-1, "ip can't be null or empty");
+        }
+        if (port == null) {
+            return Result.ofFail(-1, "port can't be null");
+        }
+
+        try {
+            List<GatewayFlowRuleEntity> rules = ruleProvider.getRules(app);
+            repository.saveAll(rules);
+            return Result.ofSuccess(rules);
+        } catch (Throwable throwable) {
+            logger.error("query gateway flow rules error:", throwable);
+            return Result.ofThrowable(-1, throwable);
+        }
+    }
+
+    @PostMapping("/new.json")
+    @AuthAction(AuthService.PrivilegeType.WRITE_RULE)
+    public Result<GatewayFlowRuleEntity> addFlowRule(@RequestBody AddFlowRuleReqVo reqVo) {
+
+        String app = reqVo.getApp();
+        if (StringUtil.isBlank(app)) {
+            return Result.ofFail(-1, "app can't be null or empty");
+        }
+
+        GatewayFlowRuleEntity entity = new GatewayFlowRuleEntity();
+        entity.setApp(app.trim());
+
+        String ip = reqVo.getIp();
+        if (StringUtil.isBlank(ip)) {
+            return Result.ofFail(-1, "ip can't be null or empty");
+        }
+        entity.setIp(ip.trim());
+
+        Integer port = reqVo.getPort();
+        if (port == null) {
+            return Result.ofFail(-1, "port can't be null");
+        }
+        entity.setPort(port);
+
+        // API类型, Route ID或API分组
+        Integer resourceMode = reqVo.getResourceMode();
+        if (resourceMode == null) {
+            return Result.ofFail(-1, "resourceMode can't be null");
+        }
+        if (!Arrays.asList(RESOURCE_MODE_ROUTE_ID, RESOURCE_MODE_CUSTOM_API_NAME).contains(resourceMode)) {
+            return Result.ofFail(-1, "invalid resourceMode: " + resourceMode);
+        }
+        entity.setResourceMode(resourceMode);
+
+        // API名称
+        String resource = reqVo.getResource();
+        if (StringUtil.isBlank(resource)) {
+            return Result.ofFail(-1, "resource can't be null or empty");
+        }
+        entity.setResource(resource.trim());
+
+        // 针对请求属性
+        GatewayParamFlowItemVo paramItem = reqVo.getParamItem();
+        if (paramItem != null) {
+            GatewayParamFlowItemEntity itemEntity = new GatewayParamFlowItemEntity();
+            entity.setParamItem(itemEntity);
+
+            // 参数属性 0-ClientIP 1-Remote Host 2-Header 3-URL参数 4-Cookie
+            Integer parseStrategy = paramItem.getParseStrategy();
+            if (!Arrays.asList(PARAM_PARSE_STRATEGY_CLIENT_IP, PARAM_PARSE_STRATEGY_HOST, PARAM_PARSE_STRATEGY_HEADER
+                    , PARAM_PARSE_STRATEGY_URL_PARAM, PARAM_PARSE_STRATEGY_COOKIE).contains(parseStrategy)) {
+                return Result.ofFail(-1, "invalid parseStrategy: " + parseStrategy);
+            }
+            itemEntity.setParseStrategy(paramItem.getParseStrategy());
+
+            // 当参数属性为2-Header 3-URL参数 4-Cookie时,参数名称必填
+            if (Arrays.asList(PARAM_PARSE_STRATEGY_HEADER, PARAM_PARSE_STRATEGY_URL_PARAM, PARAM_PARSE_STRATEGY_COOKIE).contains(parseStrategy)) {
+                // 参数名称
+                String fieldName = paramItem.getFieldName();
+                if (StringUtil.isBlank(fieldName)) {
+                    return Result.ofFail(-1, "fieldName can't be null or empty");
+                }
+                itemEntity.setFieldName(paramItem.getFieldName());
+            }
+
+            String pattern = paramItem.getPattern();
+            // 如果匹配串不为空,验证匹配模式
+            if (StringUtil.isNotEmpty(pattern)) {
+                itemEntity.setPattern(pattern);
+                Integer matchStrategy = paramItem.getMatchStrategy();
+                if (!Arrays.asList(PARAM_MATCH_STRATEGY_EXACT, PARAM_MATCH_STRATEGY_CONTAINS, PARAM_MATCH_STRATEGY_REGEX).contains(matchStrategy)) {
+                    return Result.ofFail(-1, "invalid matchStrategy: " + matchStrategy);
+                }
+                itemEntity.setMatchStrategy(matchStrategy);
+            }
+        }
+
+        // 阈值类型 0-线程数 1-QPS
+        Integer grade = reqVo.getGrade();
+        if (grade == null) {
+            return Result.ofFail(-1, "grade can't be null");
+        }
+        if (!Arrays.asList(FLOW_GRADE_THREAD, FLOW_GRADE_QPS).contains(grade)) {
+            return Result.ofFail(-1, "invalid grade: " + grade);
+        }
+        entity.setGrade(grade);
+
+        // QPS阈值
+        Double count = reqVo.getCount();
+        if (count == null) {
+            return Result.ofFail(-1, "count can't be null");
+        }
+        if (count < 0) {
+            return Result.ofFail(-1, "count should be at lease zero");
+        }
+        entity.setCount(count);
+
+        // 间隔
+        Long interval = reqVo.getInterval();
+        if (interval == null) {
+            return Result.ofFail(-1, "interval can't be null");
+        }
+        if (interval <= 0) {
+            return Result.ofFail(-1, "interval should be greater than zero");
+        }
+        entity.setInterval(interval);
+
+        // 间隔单位
+        Integer intervalUnit = reqVo.getIntervalUnit();
+        if (intervalUnit == null) {
+            return Result.ofFail(-1, "intervalUnit can't be null");
+        }
+        if (!Arrays.asList(INTERVAL_UNIT_SECOND, INTERVAL_UNIT_MINUTE, INTERVAL_UNIT_HOUR, INTERVAL_UNIT_DAY).contains(intervalUnit)) {
+            return Result.ofFail(-1, "Invalid intervalUnit: " + intervalUnit);
+        }
+        entity.setIntervalUnit(intervalUnit);
+
+        // 流控方式 0-快速失败 2-匀速排队
+        Integer controlBehavior = reqVo.getControlBehavior();
+        if (controlBehavior == null) {
+            return Result.ofFail(-1, "controlBehavior can't be null");
+        }
+        if (!Arrays.asList(CONTROL_BEHAVIOR_DEFAULT, CONTROL_BEHAVIOR_RATE_LIMITER).contains(controlBehavior)) {
+            return Result.ofFail(-1, "invalid controlBehavior: " + controlBehavior);
+        }
+        entity.setControlBehavior(controlBehavior);
+
+        if (CONTROL_BEHAVIOR_DEFAULT == controlBehavior) {
+            // 0-快速失败, 则Burst size必填
+            Integer burst = reqVo.getBurst();
+            if (burst == null) {
+                return Result.ofFail(-1, "burst can't be null");
+            }
+            if (burst < 0) {
+                return Result.ofFail(-1, "invalid burst: " + burst);
+            }
+            entity.setBurst(burst);
+        } else if (CONTROL_BEHAVIOR_RATE_LIMITER == controlBehavior) {
+            // 1-匀速排队, 则超时时间必填
+            Integer maxQueueingTimeoutMs = reqVo.getMaxQueueingTimeoutMs();
+            if (maxQueueingTimeoutMs == null) {
+                return Result.ofFail(-1, "maxQueueingTimeoutMs can't be null");
+            }
+            if (maxQueueingTimeoutMs < 0) {
+                return Result.ofFail(-1, "invalid maxQueueingTimeoutMs: " + maxQueueingTimeoutMs);
+            }
+            entity.setMaxQueueingTimeoutMs(maxQueueingTimeoutMs);
+        }
+
+        Date date = new Date();
+        entity.setGmtCreate(date);
+        entity.setGmtModified(date);
+
+        try {
+            entity = repository.save(entity);
+        } catch (Throwable throwable) {
+            logger.error("add gateway flow rule error:", throwable);
+            return Result.ofThrowable(-1, throwable);
+        }
+
+        if (!publishRules(app, ip, port)) {
+            logger.warn("publish gateway flow rules fail after add");
+        }
+
+        return Result.ofSuccess(entity);
+    }
+
+    @PostMapping("/save.json")
+    @AuthAction(AuthService.PrivilegeType.WRITE_RULE)
+    public Result<GatewayFlowRuleEntity> updateFlowRule(@RequestBody UpdateFlowRuleReqVo reqVo) {
+
+        String app = reqVo.getApp();
+        if (StringUtil.isBlank(app)) {
+            return Result.ofFail(-1, "app can't be null or empty");
+        }
+
+        Long id = reqVo.getId();
+        if (id == null) {
+            return Result.ofFail(-1, "id can't be null");
+        }
+
+        GatewayFlowRuleEntity entity = repository.findById(id);
+        if (entity == null) {
+            return Result.ofFail(-1, "gateway flow rule does not exist, id=" + id);
+        }
+
+        // 针对请求属性
+        GatewayParamFlowItemVo paramItem = reqVo.getParamItem();
+        if (paramItem != null) {
+            GatewayParamFlowItemEntity itemEntity = new GatewayParamFlowItemEntity();
+            entity.setParamItem(itemEntity);
+
+            // 参数属性 0-ClientIP 1-Remote Host 2-Header 3-URL参数 4-Cookie
+            Integer parseStrategy = paramItem.getParseStrategy();
+            if (!Arrays.asList(PARAM_PARSE_STRATEGY_CLIENT_IP, PARAM_PARSE_STRATEGY_HOST, PARAM_PARSE_STRATEGY_HEADER
+                    , PARAM_PARSE_STRATEGY_URL_PARAM, PARAM_PARSE_STRATEGY_COOKIE).contains(parseStrategy)) {
+                return Result.ofFail(-1, "invalid parseStrategy: " + parseStrategy);
+            }
+            itemEntity.setParseStrategy(paramItem.getParseStrategy());
+
+            // 当参数属性为2-Header 3-URL参数 4-Cookie时,参数名称必填
+            if (Arrays.asList(PARAM_PARSE_STRATEGY_HEADER, PARAM_PARSE_STRATEGY_URL_PARAM, PARAM_PARSE_STRATEGY_COOKIE).contains(parseStrategy)) {
+                // 参数名称
+                String fieldName = paramItem.getFieldName();
+                if (StringUtil.isBlank(fieldName)) {
+                    return Result.ofFail(-1, "fieldName can't be null or empty");
+                }
+                itemEntity.setFieldName(paramItem.getFieldName());
+            }
+
+            String pattern = paramItem.getPattern();
+            // 如果匹配串不为空,验证匹配模式
+            if (StringUtil.isNotEmpty(pattern)) {
+                itemEntity.setPattern(pattern);
+                Integer matchStrategy = paramItem.getMatchStrategy();
+                if (!Arrays.asList(PARAM_MATCH_STRATEGY_EXACT, PARAM_MATCH_STRATEGY_CONTAINS, PARAM_MATCH_STRATEGY_REGEX).contains(matchStrategy)) {
+                    return Result.ofFail(-1, "invalid matchStrategy: " + matchStrategy);
+                }
+                itemEntity.setMatchStrategy(matchStrategy);
+            }
+        } else {
+            entity.setParamItem(null);
+        }
+
+        // 阈值类型 0-线程数 1-QPS
+        Integer grade = reqVo.getGrade();
+        if (grade == null) {
+            return Result.ofFail(-1, "grade can't be null");
+        }
+        if (!Arrays.asList(FLOW_GRADE_THREAD, FLOW_GRADE_QPS).contains(grade)) {
+            return Result.ofFail(-1, "invalid grade: " + grade);
+        }
+        entity.setGrade(grade);
+
+        // QPS阈值
+        Double count = reqVo.getCount();
+        if (count == null) {
+            return Result.ofFail(-1, "count can't be null");
+        }
+        if (count < 0) {
+            return Result.ofFail(-1, "count should be at lease zero");
+        }
+        entity.setCount(count);
+
+        // 间隔
+        Long interval = reqVo.getInterval();
+        if (interval == null) {
+            return Result.ofFail(-1, "interval can't be null");
+        }
+        if (interval <= 0) {
+            return Result.ofFail(-1, "interval should be greater than zero");
+        }
+        entity.setInterval(interval);
+
+        // 间隔单位
+        Integer intervalUnit = reqVo.getIntervalUnit();
+        if (intervalUnit == null) {
+            return Result.ofFail(-1, "intervalUnit can't be null");
+        }
+        if (!Arrays.asList(INTERVAL_UNIT_SECOND, INTERVAL_UNIT_MINUTE, INTERVAL_UNIT_HOUR, INTERVAL_UNIT_DAY).contains(intervalUnit)) {
+            return Result.ofFail(-1, "Invalid intervalUnit: " + intervalUnit);
+        }
+        entity.setIntervalUnit(intervalUnit);
+
+        // 流控方式 0-快速失败 2-匀速排队
+        Integer controlBehavior = reqVo.getControlBehavior();
+        if (controlBehavior == null) {
+            return Result.ofFail(-1, "controlBehavior can't be null");
+        }
+        if (!Arrays.asList(CONTROL_BEHAVIOR_DEFAULT, CONTROL_BEHAVIOR_RATE_LIMITER).contains(controlBehavior)) {
+            return Result.ofFail(-1, "invalid controlBehavior: " + controlBehavior);
+        }
+        entity.setControlBehavior(controlBehavior);
+
+        if (CONTROL_BEHAVIOR_DEFAULT == controlBehavior) {
+            // 0-快速失败, 则Burst size必填
+            Integer burst = reqVo.getBurst();
+            if (burst == null) {
+                return Result.ofFail(-1, "burst can't be null");
+            }
+            if (burst < 0) {
+                return Result.ofFail(-1, "invalid burst: " + burst);
+            }
+            entity.setBurst(burst);
+        } else if (CONTROL_BEHAVIOR_RATE_LIMITER == controlBehavior) {
+            // 2-匀速排队, 则超时时间必填
+            Integer maxQueueingTimeoutMs = reqVo.getMaxQueueingTimeoutMs();
+            if (maxQueueingTimeoutMs == null) {
+                return Result.ofFail(-1, "maxQueueingTimeoutMs can't be null");
+            }
+            if (maxQueueingTimeoutMs < 0) {
+                return Result.ofFail(-1, "invalid maxQueueingTimeoutMs: " + maxQueueingTimeoutMs);
+            }
+            entity.setMaxQueueingTimeoutMs(maxQueueingTimeoutMs);
+        }
+
+        Date date = new Date();
+        entity.setGmtModified(date);
+
+        try {
+            entity = repository.save(entity);
+        } catch (Throwable throwable) {
+            logger.error("update gateway flow rule error:", throwable);
+            return Result.ofThrowable(-1, throwable);
+        }
+
+        if (!publishRules(app, entity.getIp(), entity.getPort())) {
+            logger.warn("publish gateway flow rules fail after update");
+        }
+
+        return Result.ofSuccess(entity);
+    }
+
+
+    @PostMapping("/delete.json")
+    @AuthAction(AuthService.PrivilegeType.DELETE_RULE)
+    public Result<Long> deleteFlowRule(Long id) {
+
+        if (id == null) {
+            return Result.ofFail(-1, "id can't be null");
+        }
+
+        GatewayFlowRuleEntity oldEntity = repository.findById(id);
+        if (oldEntity == null) {
+            return Result.ofSuccess(null);
+        }
+
+        try {
+            repository.delete(id);
+        } catch (Throwable throwable) {
+            logger.error("delete gateway flow rule error:", throwable);
+            return Result.ofThrowable(-1, throwable);
+        }
+
+        if (!publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) {
+            logger.warn("publish gateway flow rules fail after delete");
+        }
+
+        return Result.ofSuccess(id);
+    }
+
+    private boolean publishRules(String app, String ip, Integer port) {
+        List<GatewayFlowRuleEntity> rules = repository.findAllByApp(app);
+        try {
+            rulePublisher.publish(app, rules);
+            //延迟加载
+            delayTime();
+            return true;
+        } catch (Exception e) {
+            logger.error("publish rules error!");
+            e.printStackTrace();
+            return false;
+        }
+    }
+}

+ 35 - 0
jeecg-server-cloud/jeecg-visual/jeecg-cloud-sentinel/src/main/java/com/alibaba/csp/sentinel/dashboard/rule/nacos/gateway/GateWayApiNacosProvider.java

@@ -0,0 +1,35 @@
+package com.alibaba.csp.sentinel.dashboard.rule.nacos.gateway;
+
+import com.alibaba.csp.sentinel.dashboard.constants.SentinelConStants;
+import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiDefinitionEntity;
+import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
+import com.alibaba.csp.sentinel.datasource.Converter;
+import com.alibaba.csp.sentinel.util.StringUtil;
+import com.alibaba.nacos.api.config.ConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+/**
+ * 网关API规则拉取
+ *
+ * @author zyf
+ * @date 2022-04-13
+ */
+@Component("gateWayApiNacosProvider")
+public class GateWayApiNacosProvider implements DynamicRuleProvider<List<ApiDefinitionEntity>> {
+    @Autowired
+    private ConfigService configService;
+    @Autowired
+    private Converter<String , List<ApiDefinitionEntity>> converter;
+    @Override
+    public List<ApiDefinitionEntity> getRules(String appName) throws Exception {
+        String rules = configService.getConfig(appName+ SentinelConStants.GETEWAY_API_DATA_ID_POSTFIX
+                , SentinelConStants.GROUP_ID,3000);
+        if(StringUtil.isEmpty(rules)){
+            return new ArrayList<>();
+        }
+        return converter.convert(rules);
+    }
+}

+ 35 - 0
jeecg-server-cloud/jeecg-visual/jeecg-cloud-sentinel/src/main/java/com/alibaba/csp/sentinel/dashboard/rule/nacos/gateway/GateWayApiNacosPublisher.java

@@ -0,0 +1,35 @@
+package com.alibaba.csp.sentinel.dashboard.rule.nacos.gateway;
+
+
+import com.alibaba.csp.sentinel.dashboard.constants.SentinelConStants;
+import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiDefinitionEntity;
+import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
+import com.alibaba.csp.sentinel.datasource.Converter;
+import com.alibaba.csp.sentinel.util.AssertUtil;
+import com.alibaba.nacos.api.config.ConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+/**
+ * 网关API规则推送
+ *
+ * @author zyf
+ * @date 2022-04-13
+ */
+@Component("gateWayApiNacosPublisher")
+public class GateWayApiNacosPublisher implements DynamicRulePublisher<List<ApiDefinitionEntity>> {
+    @Autowired
+    private ConfigService configService;
+    @Autowired
+    private Converter<List<ApiDefinitionEntity>, String> converter;
+    @Override
+    public void publish(String app, List<ApiDefinitionEntity> rules) throws Exception {
+        AssertUtil.notEmpty(app, "app name cannot be empty");
+        if (rules == null) {
+            return;
+        }
+        configService.publishConfig(app+ SentinelConStants.GETEWAY_API_DATA_ID_POSTFIX,
+                SentinelConStants.GROUP_ID,converter.convert(rules));
+    }
+}

+ 40 - 0
jeecg-server-cloud/jeecg-visual/jeecg-cloud-sentinel/src/main/java/com/alibaba/csp/sentinel/dashboard/rule/nacos/gateway/GateWayFlowRulesNacosProvider.java

@@ -0,0 +1,40 @@
+package com.alibaba.csp.sentinel.dashboard.rule.nacos.gateway;
+
+import com.alibaba.csp.sentinel.dashboard.constants.SentinelConStants;
+import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayFlowRuleEntity;
+import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
+import com.alibaba.csp.sentinel.dashboard.rule.nacos.SentinelConfig;
+import com.alibaba.csp.sentinel.datasource.Converter;
+import com.alibaba.csp.sentinel.util.StringUtil;
+import com.alibaba.nacos.api.config.ConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 网关流控规则拉取
+ *
+ * @author zyf
+ * @date 2022-04-13
+ */
+@Component("gateWayFlowRulesNacosProvider")
+public class GateWayFlowRulesNacosProvider implements DynamicRuleProvider<List<GatewayFlowRuleEntity>> {
+
+    @Autowired
+    private ConfigService configService;
+    @Autowired
+    private Converter<String, List<GatewayFlowRuleEntity>> converter;
+
+    @Override
+    public List<GatewayFlowRuleEntity> getRules(String appName) throws Exception {
+        String rules = configService.getConfig(appName + SentinelConStants.GETEWAY_FLOW_DATA_ID_POSTFIX,
+                SentinelConStants.GROUP_ID, 3000);
+        if (StringUtil.isEmpty(rules)) {
+            return new ArrayList<>();
+        }
+        return converter.convert(rules);
+    }
+
+}

+ 41 - 0
jeecg-server-cloud/jeecg-visual/jeecg-cloud-sentinel/src/main/java/com/alibaba/csp/sentinel/dashboard/rule/nacos/gateway/GateWayFlowRulesNacosPublisher.java

@@ -0,0 +1,41 @@
+package com.alibaba.csp.sentinel.dashboard.rule.nacos.gateway;
+
+
+import java.util.List;
+
+import com.alibaba.csp.sentinel.dashboard.constants.SentinelConStants;
+import com.alibaba.csp.sentinel.dashboard.rule.nacos.SentinelConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayFlowRuleEntity;
+import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
+import com.alibaba.csp.sentinel.datasource.Converter;
+import com.alibaba.csp.sentinel.util.AssertUtil;
+import com.alibaba.nacos.api.config.ConfigService;
+
+/**
+ * 网关流控规则推送
+ *
+ * @author zyf
+ * @date 2022-04-13
+ */
+@Component("gateWayFlowRulesNacosPublisher")
+public class GateWayFlowRulesNacosPublisher implements DynamicRulePublisher<List<GatewayFlowRuleEntity>> {
+
+    @Autowired
+    private ConfigService configService;
+    @Autowired
+    private Converter<List<GatewayFlowRuleEntity>, String> converter;
+
+
+    @Override
+    public void publish(String app, List<GatewayFlowRuleEntity> rules) throws Exception {
+        AssertUtil.notEmpty(app, "app name cannot be empty");
+        if (rules == null) {
+            return;
+        }
+        configService.publishConfig(app + SentinelConStants.GETEWAY_FLOW_DATA_ID_POSTFIX,
+                SentinelConStants.GROUP_ID, converter.convert(rules));
+    }
+}

+ 31 - 0
jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/util/FtlUtil.java

@@ -0,0 +1,31 @@
+package com.xxl.job.admin.core.util;
+
+import freemarker.ext.beans.BeansWrapper;
+import freemarker.ext.beans.BeansWrapperBuilder;
+import freemarker.template.Configuration;
+import freemarker.template.TemplateHashModel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * ftl util
+ *
+ * @author xuxueli 2018-01-17 20:37:48
+ */
+public class FtlUtil {
+    private static Logger logger = LoggerFactory.getLogger(FtlUtil.class);
+
+    private static BeansWrapper wrapper = new BeansWrapperBuilder(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS).build();     //BeansWrapper.getDefaultInstance();
+
+    public static TemplateHashModel generateStaticModel(String packageName) {
+        try {
+            TemplateHashModel staticModels = wrapper.getStaticModels();
+            TemplateHashModel fileStatics = (TemplateHashModel) staticModels.get(packageName);
+            return fileStatics;
+        } catch (Exception e) {
+            logger.error(e.getMessage(), e);
+        }
+        return null;
+    }
+
+}