diff --git "a/magic-api-plugins/magic-api-plugin-nebula/nebula\346\217\222\344\273\266.md" "b/magic-api-plugins/magic-api-plugin-nebula/nebula\346\217\222\344\273\266.md" new file mode 100644 index 0000000000000000000000000000000000000000..62317085e299a444334fefe0770a800a67593f3c --- /dev/null +++ "b/magic-api-plugins/magic-api-plugin-nebula/nebula\346\217\222\344\273\266.md" @@ -0,0 +1,99 @@ +--- +title: nebula插件 +date: 2023-08-16 09:16:55 +--- + +### 引入依赖 + +```xml + + + org.ssssssss + magic-api-plugin-nebula + magic-api-lastest-version + +``` + +### 配置 + +```yml +nebula: + hostAddress: ${NEBULA_HOSTADDRESS:localhost:9669} + userName: ${NEBULA_USERNAME:root} + password: ${NEBULA_PASSWORD:nebula} + +``` + +### 使用 + +```js +import nebula; +var ngsl = + """" + USE db_name;MATCH p_=(p:`assignee`)-[*3]-(p2:`transferor`) where id(p2) == "阿里巴巴" or id(p)== "阿里巴巴" RETURN p_ limit 1000' + """ +var resultJson = nebula.executeJson(ngsl) +nebula.convert(resultJson) + + +nebula.executeNebulaModel(ngsl) + +其他支持的方法不太常用, 这里不再一一列举, 可参考源码 +org.ssssssss.magicapi.nebula.NebulaModule +``` + +#### 返回的数据格式为: +``` + 该结构的数据可被很多前端组件库支持进行可视化展示 +``` +如: [angv G6](http://antv-2018.alipay.com/zh-cn/g6/3.x/demo/index.html) + + +```json +{ + "code": 0, + "message": "success", + "data": { + "nodes": [ + { + "edgeSize": 1, + "assignee.name": "中航纽赫融资租赁(上海)有限公司", + "type": "vertex", + "assignee.addr": "上海市中国(上海)自由贸易试验区正定路530号A5库区集中辅助区三层318室", + "assignee.legal_person": "周勇", + "registrant.addr": "上海市浦东新区南泉路1261号", + "registrant.name": "中航国际租赁有限公司", + "id": "中航纽赫融资租赁(上海)有限公司", + "assignee.type": "企业" + }, + { + "edgeSize": 15, + "type": "vertex", + "transferor.name": "陕西海富融资租赁有限公司", + "transferor.legal_person": "刘子瑜", + "transferor.type": "企业", + "transferor.addr": "陕西省西安市西安经济技术开发区未央路170号赛高城市广场2号楼企业总部大厦26层05单元", + "registrant.addr": "广东省深圳市前海深港合作区南山街道梦海大厦5035号前海华润金融中心T5写字楼1808", + "registrant.name": "深圳前海盈峰商业保理有限公司", + "id": "陕西海富融资租赁有限公司" + }, ... + ], + "edges": [ + { + "dst": "陕西海富融资租赁有限公司", + "src": "中航纽赫融资租赁(上海)有限公司", + "source": "中航纽赫融资租赁(上海)有限公司", + "label": "trans_with", + "type": "edge", + "target": "陕西海富融资租赁有限公司", + "name": "trans_with", + "ranking": 0, + "value": 0 + },... + ] + }, + "timestamp": 1692149280167, + "requestTime": 1692149280143, + "executeTime": 24 +} +``` \ No newline at end of file diff --git a/magic-api-plugins/magic-api-plugin-nebula/pom.xml b/magic-api-plugins/magic-api-plugin-nebula/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..43e906d013bf7dd3023e6ab4ed0af49014edd248 --- /dev/null +++ b/magic-api-plugins/magic-api-plugin-nebula/pom.xml @@ -0,0 +1,33 @@ + + 4.0.0 + + org.ssssssss + magic-api-plugins + 2.1.1 + + + magic-api-plugin-nebula + jar + + magic-api-plugin-nebula + + + UTF-8 + 3.5.0 + + + + + com.vesoft + client + ${vesoft.version} + + + guava + com.google.guava + + + + + diff --git a/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/MagicNebulaConfiguration.java b/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/MagicNebulaConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..6837fb39f20f775dce9d06f18e4c057d49b3e755 --- /dev/null +++ b/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/MagicNebulaConfiguration.java @@ -0,0 +1,104 @@ +package org.ssssssss.magicapi.nebula; + + +import com.vesoft.nebula.client.graph.NebulaPoolConfig; +import com.vesoft.nebula.client.graph.data.HostAddress; +import com.vesoft.nebula.client.graph.net.NebulaPool; +import com.vesoft.nebula.client.graph.net.Session; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.ssssssss.magicapi.core.config.MagicAPIProperties; +import org.ssssssss.magicapi.core.config.MagicPluginConfiguration; +import org.ssssssss.magicapi.core.model.Plugin; +import org.ssssssss.magicapi.utils.Assert; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * Nebula自动配置类 + */ + +@Configuration +@EnableConfigurationProperties(NebulaPoolProperties.class) +public class MagicNebulaConfiguration implements MagicPluginConfiguration { + private static final Logger logger = LoggerFactory.getLogger(MagicNebulaConfiguration.class); + + private NebulaPoolProperties nebulaPoolProperties; + + private final MagicAPIProperties properties; + + public MagicNebulaConfiguration(MagicAPIProperties properties, NebulaPoolProperties nebulaPoolProperties) { + this.properties = properties; + this.nebulaPoolProperties = nebulaPoolProperties; + } + + /** + * 创建nebula pool + * @param nebulaPoolProperties + * @return + */ + @Bean + public NebulaPool nebulaPool(@Autowired NebulaPoolProperties nebulaPoolProperties) { + Session session = null; + try { + + NebulaPoolConfig nebulaPoolConfig = buildNebulaPoolConfig(nebulaPoolProperties); + Assert.isNotBlank(nebulaPoolProperties.getHostAddress(), "nebula.hostAddress 不能为空, 格式为 ip:port,ip:port 配置多个地址用逗号分隔"); + String[] hostAddress = nebulaPoolProperties.getHostAddress().split(","); + List addresses = Arrays.stream(hostAddress).map(address -> { + String[] ipAndPort = address.split(":"); + Assert.isTrue(ipAndPort.length == 2, "nebula.hostAddress 格式错误, 格式为 ip:port,ip:port 配置多个地址用逗号分隔"); + return new HostAddress(ipAndPort[0], Integer.parseInt(ipAndPort[1])); + }).collect(Collectors.toList()); + + NebulaPool pool = new NebulaPool(); + pool.init(addresses, nebulaPoolConfig); + session = pool.getSession(nebulaPoolProperties.getUserName(), nebulaPoolProperties.getPassword(), nebulaPoolProperties.isReconnect()); + return pool; + } catch (Exception e) { + logger.error("初始化nebula pool 异常", e); + throw new RuntimeException(e); + } finally { + logger.info("初始化nebula pool 完成"); + Optional.ofNullable(session).ifPresent(Session::release); + } + } + + /** + * 注入模块 + * @return + */ + @Bean + public NebulaModule nebulaModule() { + return new NebulaModule(); + } + + @Override + public Plugin plugin() { + return new Plugin("Nebula"); + } + + + public NebulaPoolConfig buildNebulaPoolConfig(NebulaPoolProperties nebulaPoolProperties) { + + NebulaPoolConfig nebulaPoolConfig = new NebulaPoolConfig(); + //将nebulaPoolProperties的同名属性赋值到nebulaPoolConfig + nebulaPoolConfig.setMinConnSize(nebulaPoolProperties.getMinConnsSize()); + nebulaPoolConfig.setSslParam(nebulaPoolProperties.getSslParam()); + nebulaPoolConfig.setWaitTime(nebulaPoolProperties.getWaitTime()); + nebulaPoolConfig.setTimeout(nebulaPoolProperties.getTimeout()); + nebulaPoolConfig.setMaxConnSize(nebulaPoolProperties.getMaxConnsSize()); + nebulaPoolConfig.setIntervalIdle(nebulaPoolProperties.getIntervalIdle()); + nebulaPoolConfig.setMinClusterHealthRate(nebulaPoolProperties.getMinClusterHealthRate()); + nebulaPoolConfig.setIdleTime(nebulaPoolProperties.getIdleTime()); + nebulaPoolConfig.setEnableSsl(nebulaPoolProperties.isEnableSsl()); + return nebulaPoolConfig; + } +} diff --git a/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/NebulaModule.java b/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/NebulaModule.java new file mode 100644 index 0000000000000000000000000000000000000000..4d3aa3face6ddcbfee0c297fbb8fa6ff8ba93440 --- /dev/null +++ b/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/NebulaModule.java @@ -0,0 +1,157 @@ +package org.ssssssss.magicapi.nebula; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.vesoft.nebula.client.graph.data.ResultSet; +import com.vesoft.nebula.client.graph.net.NebulaPool; +import com.vesoft.nebula.client.graph.net.Session; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.annotation.Autowired; +import org.ssssssss.magicapi.core.annotation.MagicModule; +import org.ssssssss.magicapi.nebula.model.Edge; +import org.ssssssss.magicapi.nebula.model.NebulaModel; +import org.ssssssss.magicapi.nebula.model.Node; +import org.ssssssss.magicapi.nebula.response.*; +import org.ssssssss.script.annotation.Comment; + +import java.util.HashMap; +import java.util.List; +import java.util.Optional; + + +@MagicModule("nebula") +public class NebulaModule { + + @Autowired + private NebulaPool nebulaPool; + + @Autowired + private NebulaPoolProperties nebulaPoolProperties; + + private static final Logger logger = LoggerFactory.getLogger(NebulaModule.class); + + + /** + * 执行ngsl脚本, 返回json格式结果 + * + * @param script + * @return + */ + @Comment("执行ngsl脚本, 返回json格式结果") + public Object executeJson(String script) { + Session session = getNebulaSession(); + try { + String json = session.executeJson(script); + return json; + } catch (Exception e) { + logger.error("执行Nebula脚本异常, script: {}", script, e); + throw new RuntimeException(e); + } finally { + Optional.ofNullable(session).ifPresent(Session::release); + } + } + + + /** + * 执行ngsl脚本, 并解析为可视化格式 + * + * @param script + * @return + */ + @Comment("执行ngsl脚本, 返回json格式结果, 并解析为可视化格式") + public NebulaModel executeNebulaModel(String script) { + Session session = getNebulaSession(); + try { + String json = session.executeJson(script); + return convert(json); + } catch (Exception e) { + logger.error("执行Nebula脚本异常, script: {}", script, e); + throw new RuntimeException(e); + } finally { + Optional.ofNullable(session).ifPresent(Session::release); + } + } + + + /** + * 执行ngsl脚本, 返回ResultSet格式结果, 不可直接使用 + * + * @param script + * @return + */ + @Comment("执行ngsl脚本, 返回ResultSet格式结果, 无法直接使用") + public Object execute(String script) { + Session session = getNebulaSession(); + try { + ResultSet resultSet = session.execute(script); + return resultSet; + } catch (Exception e) { + logger.error("执行Nebula脚本异常, script: {}", script, e); + throw new RuntimeException(e); + } finally { + Optional.ofNullable(session).ifPresent(Session::release); + } + } + + public Session getNebulaSession() { + try { + return nebulaPool.getSession(nebulaPoolProperties.getUserName(), nebulaPoolProperties.getPassword(), nebulaPoolProperties.isReconnect()); + } catch (NoSuchBeanDefinitionException e) { + throw new RuntimeException(String.format("NebulaPool 未初始化, 或初始化异常, 请检查配置文件")); + } catch (Exception e) { + logger.error("获取nebula session 异常", e); + throw new RuntimeException(e); + } + } + + @Comment("解析nebula结果为可视化格式") + public NebulaModel convert(String json) throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); + NebulaJsonBody response = objectMapper.readValue(json, NebulaJsonBody.class); + + //状态码不为0则为异常, 解析提示异常信息 + if (response.getErrorCode() != 0) { + logger.error("执行Nebula脚本异常, script: {}, errorMsg: {}", json, response.getErrorMsg()); + throw new RuntimeException(response.getErrorMsg()); + } + + NebulaModel nebulaModel = new NebulaModel(); + HashMap nodeEdges = new HashMap<>(); + List datas = response.getResults().get(0).getData(); + for (int index = 0; index < datas.size(); index++) { + List> meta = datas.get(index).getMeta(); + List>> row = datas.get(index).getRow(); + for (int i = 0; i < meta.get(0).size(); i++) { + Element element = meta.get(0).get(i); + HashMap elementDetail = row.get(0).get(i); + Node node = new Node(); + Edge edge = new Edge(); + + if (element instanceof Vertex) { + node.setId(((Vertex) element).getId()); + node.getProp().putAll(elementDetail); + nebulaModel.addNode(node); + + } else if (element instanceof EdgeElement) { + edge.getProp().putAll(elementDetail); + EdgeId id = ((EdgeElement) element).getId(); + edge.setTarget(id.getDst()); + edge.setSource(id.getSrc()); + edge.setLabel(id.getName()); + edge.setValue(id.getRanking()); + nebulaModel.getEdges().add(edge); + + nodeEdges.put(id.getDst(), nodeEdges.getOrDefault(id.getDst(), 0) + 1); + nodeEdges.put(id.getSrc(), nodeEdges.getOrDefault(id.getSrc(), 0) + 1); + } + } + // 补充节点边的数量值 + for (Node node : nebulaModel.getNodes()) { + node.setEdgeSize(nodeEdges.getOrDefault(node.getId(), 0)); + } + } + return nebulaModel; + } + +} diff --git a/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/NebulaPoolProperties.java b/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/NebulaPoolProperties.java new file mode 100644 index 0000000000000000000000000000000000000000..7f4c28a294ec306ca6387cbd2a3135cc7910b98a --- /dev/null +++ b/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/NebulaPoolProperties.java @@ -0,0 +1,141 @@ +package org.ssssssss.magicapi.nebula; + +import com.vesoft.nebula.client.graph.data.SSLParam; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.context.properties.ConfigurationProperties; + + +@ConfigurationProperties(prefix = "nebula") +public class NebulaPoolProperties { + + /** nebula 服务地址, 多个则逗号分割, 格式为 ip:port */ + private String hostAddress; + /** nebula 用户名 */ + private String userName; + /** nebula 密码 */ + private String password; + + private boolean reconnect = true; + /** nebula 连接池最小连接数 */ + private int minConnsSize = 0; + /** nebula 连接池最大连接数 */ + private int maxConnsSize = 10; + /** nebula 连接池最大等待时间 */ + private int timeout = 0; + /** nebula 连接池空闲时间 */ + private int idleTime = 0; + /** nebula 连接池心跳间隔 */ + private int intervalIdle = -1; + + private int waitTime = 0; + + private double minClusterHealthRate = 1.0; + + private boolean enableSsl = false; + + private SSLParam sslParam = null; + + public String getHostAddress() { + return hostAddress; + } + + public void setHostAddress(String hostAddress) { + this.hostAddress = hostAddress; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public boolean isReconnect() { + return reconnect; + } + + public void setReconnect(boolean reconnect) { + this.reconnect = reconnect; + } + + public int getMinConnsSize() { + return minConnsSize; + } + + public void setMinConnsSize(int minConnsSize) { + this.minConnsSize = minConnsSize; + } + + public int getMaxConnsSize() { + return maxConnsSize; + } + + public void setMaxConnsSize(int maxConnsSize) { + this.maxConnsSize = maxConnsSize; + } + + public int getTimeout() { + return timeout; + } + + public void setTimeout(int timeout) { + this.timeout = timeout; + } + + public int getIdleTime() { + return idleTime; + } + + public void setIdleTime(int idleTime) { + this.idleTime = idleTime; + } + + public int getIntervalIdle() { + return intervalIdle; + } + + public void setIntervalIdle(int intervalIdle) { + this.intervalIdle = intervalIdle; + } + + public int getWaitTime() { + return waitTime; + } + + public void setWaitTime(int waitTime) { + this.waitTime = waitTime; + } + + public double getMinClusterHealthRate() { + return minClusterHealthRate; + } + + public void setMinClusterHealthRate(double minClusterHealthRate) { + this.minClusterHealthRate = minClusterHealthRate; + } + + public boolean isEnableSsl() { + return enableSsl; + } + + public void setEnableSsl(boolean enableSsl) { + this.enableSsl = enableSsl; + } + + public SSLParam getSslParam() { + return sslParam; + } + + public void setSslParam(SSLParam sslParam) { + this.sslParam = sslParam; + } +} diff --git a/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/model/Edge.java b/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/model/Edge.java new file mode 100644 index 0000000000000000000000000000000000000000..8ef9b90e63df8d629d2fd2d8634c1c7e69687586 --- /dev/null +++ b/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/model/Edge.java @@ -0,0 +1,69 @@ +package org.ssssssss.magicapi.nebula.model; + +import java.util.HashMap; + +/** + * 描述node的方向的边 + */ +public class Edge { + + /** + * 起始节点的id + */ + private String source; + + /** + * 终止节点的id + */ + private String target; + + /** + * 边描述 + */ + private String label; + + + private String value; + + private HashMap prop = new HashMap<>(); + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public String getTarget() { + return target; + } + + public void setTarget(String target) { + this.target = target; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public HashMap getProp() { + return prop; + } + + public void setProp(HashMap prop) { + this.prop = prop; + } +} diff --git a/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/model/NebulaModel.java b/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/model/NebulaModel.java new file mode 100644 index 0000000000000000000000000000000000000000..72d9a38e0a044d2892ddf85973ac9d014c78cc79 --- /dev/null +++ b/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/model/NebulaModel.java @@ -0,0 +1,69 @@ +package org.ssssssss.magicapi.nebula.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.ssssssss.script.annotation.Comment; + +import java.util.*; + +/** + * 经过加工后的nebula数据结构, 用于前端数据展示 + * 目前很多前端组件库支持这种数据, 并可视化展示, 如ntV G6等 + * @see AntV G6 + */ +public class NebulaModel { + + @JsonIgnore + private List nodeIds = new ArrayList<>(); + + /** + * 包含的节点集合 + */ + @Comment("包含的节点集合") + private List nodes = new ArrayList<>(); + + /** + * 包含的边集合 + */ + + @Comment("包含的边集合") + private List edges = new ArrayList<>(); + + public List getNodeIds() { + return nodeIds; + } + + public void setNodeIds(List nodeIds) { + this.nodeIds = nodeIds; + } + + public List getNodes() { + return nodes; + } + + public void setNodes(List nodes) { + this.nodes = nodes; + } + + public List getEdges() { + return edges; + } + + public void setEdges(List edges) { + this.edges = edges; + } + + /** + * 添加节点, 根据id去重 + * @param node + */ + @Comment("添加节点, 根据id去重") + public void addNode(Node node) { + String nodeId = Objects.toString(node.getId(), null); + if (nodeIds.contains(nodeId)) { + return; + } + nodeIds.add(nodeId); + nodes.add(node); + } + +} \ No newline at end of file diff --git a/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/model/Node.java b/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/model/Node.java new file mode 100644 index 0000000000000000000000000000000000000000..7faf2b5480e9024065531f7c9a06542fd55dc4ca --- /dev/null +++ b/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/model/Node.java @@ -0,0 +1,36 @@ +package org.ssssssss.magicapi.nebula.model; + +import java.util.HashMap; + +public class Node { + + private String id; + + private int EdgeSize; + + private HashMap prop = new HashMap<>(); + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public int getEdgeSize() { + return EdgeSize; + } + + public void setEdgeSize(int edgeSize) { + EdgeSize = edgeSize; + } + + public HashMap getProp() { + return prop; + } + + public void setProp(HashMap prop) { + this.prop = prop; + } +} diff --git a/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/response/EdgeElement.java b/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/response/EdgeElement.java new file mode 100644 index 0000000000000000000000000000000000000000..864dc6e89966f2f492a5d18fd537d9f3397863da --- /dev/null +++ b/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/response/EdgeElement.java @@ -0,0 +1,14 @@ +package org.ssssssss.magicapi.nebula.response; + +public class EdgeElement extends Element { + + private EdgeId id; + + public EdgeId getId() { + return id; + } + + public void setId(EdgeId id) { + this.id = id; + } +} diff --git a/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/response/EdgeId.java b/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/response/EdgeId.java new file mode 100644 index 0000000000000000000000000000000000000000..1c706278b509a4ab0ec300b17172bd6b28cf5e4c --- /dev/null +++ b/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/response/EdgeId.java @@ -0,0 +1,50 @@ +package org.ssssssss.magicapi.nebula.response; + +public class EdgeId { + + private String ranking; + private String name; + private Integer type; + private String dst; + private String src; + + public String getRanking() { + return ranking; + } + + public void setRanking(String ranking) { + this.ranking = ranking; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getType() { + return type; + } + + public void setType(Integer type) { + this.type = type; + } + + public String getDst() { + return dst; + } + + public void setDst(String dst) { + this.dst = dst; + } + + public String getSrc() { + return src; + } + + public void setSrc(String src) { + this.src = src; + } +} diff --git a/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/response/Element.java b/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/response/Element.java new file mode 100644 index 0000000000000000000000000000000000000000..69642344a5c26f07c73f7d4ef24fb724b758717d --- /dev/null +++ b/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/response/Element.java @@ -0,0 +1,26 @@ +package org.ssssssss.magicapi.nebula.response; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + property = "type") +@JsonSubTypes(value = { + @JsonSubTypes.Type(value = EdgeElement.class, name = "edge"), + @JsonSubTypes.Type(value = Vertex.class, name = "vertex") +}) +public abstract class Element { + + protected String type; + + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + +} diff --git a/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/response/NebulaJsonBody.java b/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/response/NebulaJsonBody.java new file mode 100644 index 0000000000000000000000000000000000000000..03652c54316c7d11665e93a5b31c02024cc850d8 --- /dev/null +++ b/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/response/NebulaJsonBody.java @@ -0,0 +1,132 @@ +package org.ssssssss.magicapi.nebula.response; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.HashMap; +import java.util.List; + +public class NebulaJsonBody { + private List errors; + private List results; + + public List getErrors() { + return errors; + } + + public void setErrors(List errors) { + this.errors = errors; + } + + + public int getErrorCode() { + return this.errors.get(0).getCode(); + } + + public String getErrorMsg() { + return this.errors.get(0).getMessage(); + } + + public List getResults() { + return results; + } + + public void setResults(List results) { + this.results = results; + } + + + public static class NebulaError { + private int code; + private String message; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + } + + public static class Result { + @JsonProperty("spaceName") + private String spaceName; + private List data; + private List columns; + private NebulaError errors; + @JsonProperty("latencyInUs") + private long latencyInUs; + + public String getSpaceName() { + return spaceName; + } + + public void setSpaceName(String spaceName) { + this.spaceName = spaceName; + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + + public List getColumns() { + return columns; + } + + public void setColumns(List columns) { + this.columns = columns; + } + + public NebulaError getErrors() { + return errors; + } + + public void setErrors(NebulaError errors) { + this.errors = errors; + } + + public long getLatencyInUs() { + return latencyInUs; + } + + public void setLatencyInUs(long latencyInUs) { + this.latencyInUs = latencyInUs; + } + } + + public static class Data { + private List> meta; + private List>> row; + + public List> getMeta() { + return meta; + } + + public void setMeta(List> meta) { + this.meta = meta; + } + + public List>> getRow() { + return row; + } + + public void setRow(List>> row) { + this.row = row; + } + } +} + + + diff --git a/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/response/Vertex.java b/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/response/Vertex.java new file mode 100644 index 0000000000000000000000000000000000000000..43dbded6ef02e18dea5b20b0b12c185e8aff947b --- /dev/null +++ b/magic-api-plugins/magic-api-plugin-nebula/src/main/java/org/ssssssss/magicapi/nebula/response/Vertex.java @@ -0,0 +1,14 @@ +package org.ssssssss.magicapi.nebula.response; + +public class Vertex extends Element { + + private String id; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } +} diff --git a/magic-api-plugins/magic-api-plugin-nebula/src/main/resources/META-INF/spring.factories b/magic-api-plugins/magic-api-plugin-nebula/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000000000000000000000000000000000..5c0648a4220c56f721e70d4911c69eead156ec43 --- /dev/null +++ b/magic-api-plugins/magic-api-plugin-nebula/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.ssssssss.magicapi.nebula.MagicNebulaConfiguration \ No newline at end of file diff --git a/magic-api-plugins/magic-api-plugin-nebula/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/magic-api-plugins/magic-api-plugin-nebula/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000000000000000000000000000000000..e77bfd5fd58dd3dc52681078b8b6cbacdf057bfb --- /dev/null +++ b/magic-api-plugins/magic-api-plugin-nebula/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +org.ssssssss.magicapi.nebula.MagicNebulaConfiguration \ No newline at end of file diff --git a/magic-api-plugins/magic-api-plugin-springdoc/src/main/java/org/ssssssss/magicapi/springdoc/MagicSpringDocConfiguration.java b/magic-api-plugins/magic-api-plugin-springdoc/src/main/java/org/ssssssss/magicapi/springdoc/MagicSpringDocConfiguration.java index b071e758005161fba0ab841740216583760266aa..f370a02a6102ec98be105fca300b9af6d2caf506 100644 --- a/magic-api-plugins/magic-api-plugin-springdoc/src/main/java/org/ssssssss/magicapi/springdoc/MagicSpringDocConfiguration.java +++ b/magic-api-plugins/magic-api-plugin-springdoc/src/main/java/org/ssssssss/magicapi/springdoc/MagicSpringDocConfiguration.java @@ -87,7 +87,7 @@ public class MagicSpringDocConfiguration implements MagicPluginConfiguration { } else { urls = new HashSet<>(urls); } - urls.add(new SwaggerUrl(springDocConfig.getGroupName(), springDocConfig.getLocation(), null)); + urls.add(new SwaggerUrl(springDocConfig.getGroupName(), servletContext.getContextPath() + springDocConfig.getLocation(), null)); params.put("urls", urls); return params; } @@ -96,7 +96,6 @@ public class MagicSpringDocConfiguration implements MagicPluginConfiguration { private void createSwaggerProvider(ObjectProvider requestMagicDynamicRegistryObjectProvider, MagicResourceService magicResourceService, ServletContext servletContext) throws NoSuchMethodException { - Mapping mapping = Mapping.create(requestMappingHandlerMapping); RequestMappingInfo requestMappingInfo = mapping.paths(springDocConfig.getLocation()).build(); SwaggerEntity.License license = new SwaggerEntity.License("MIT", "https://gitee.com/ssssssss-team/magic-api/blob/master/LICENSE"); diff --git a/magic-api-plugins/magic-api-plugin-springdoc/src/main/java/org/ssssssss/magicapi/springdoc/entity/SwaggerProvider.java b/magic-api-plugins/magic-api-plugin-springdoc/src/main/java/org/ssssssss/magicapi/springdoc/entity/SwaggerProvider.java index 7a69584eddcde712afff71bebcee8af7afff829e..f930fc0d59812ab4d5539e95ef4a649208eac703 100644 --- a/magic-api-plugins/magic-api-plugin-springdoc/src/main/java/org/ssssssss/magicapi/springdoc/entity/SwaggerProvider.java +++ b/magic-api-plugins/magic-api-plugin-springdoc/src/main/java/org/ssssssss/magicapi/springdoc/entity/SwaggerProvider.java @@ -4,10 +4,7 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.ResponseBody; import org.ssssssss.magicapi.core.config.MagicConfiguration; -import org.ssssssss.magicapi.core.model.ApiInfo; -import org.ssssssss.magicapi.core.model.BaseDefinition; -import org.ssssssss.magicapi.core.model.DataType; -import org.ssssssss.magicapi.core.model.Path; +import org.ssssssss.magicapi.core.model.*; import org.ssssssss.magicapi.core.service.MagicResourceService; import org.ssssssss.magicapi.core.service.impl.RequestMagicDynamicRegistry; import org.ssssssss.magicapi.utils.JsonUtils; @@ -73,7 +70,7 @@ public class SwaggerProvider { swaggerEntity.addSecurity(securityMap); for (ApiInfo info : infos) { - String groupName = magicResourceService.getGroupName(info.getGroupId()).replace("/", "-"); + String groupName = getRootGroupName(info.getGroupId()); String requestPath = PathUtils.replaceSlash(this.prefix + magicResourceService.getGroupPath(info.getGroupId()) + "/" + info.getPath()); SwaggerEntity.Path path = new SwaggerEntity.Path(info.getId()); path.addTag(groupName); @@ -123,6 +120,14 @@ public class SwaggerProvider { return swaggerEntity; } + private String getRootGroupName(String groupId) { + Group group = magicResourceService.getGroup(groupId); + if (!Objects.equals(group.getParentId(), "0")) { + return getRootGroupName(group.getParentId()); + } + return group.getName(); + } + private List> parseParameters(ApiInfo info) { List> parameters = new ArrayList<>(); info.getParameters().forEach(it -> parameters.add(SwaggerEntity.createParameter(it.isRequired(), it.getName(), VAR_NAME_QUERY, it.getDataType().getJavascriptType(), it.getDescription(), it.getValue()))); diff --git a/magic-api-plugins/pom.xml b/magic-api-plugins/pom.xml index f63a46e206143ab9de224aea3c7bb197e69f4724..2522a3a66674886d4b13665f69a9d5062139e26b 100644 --- a/magic-api-plugins/pom.xml +++ b/magic-api-plugins/pom.xml @@ -23,6 +23,7 @@ magic-api-plugin-elasticsearch magic-api-plugin-cluster magic-api-plugin-git + magic-api-plugin-nebula diff --git a/magic-api/src/main/java/org/ssssssss/magicapi/core/model/Options.java b/magic-api/src/main/java/org/ssssssss/magicapi/core/model/Options.java index 6ae1fb2e9d1d02209b1cc00b433a511616a8ee3c..21c2653824a5228b09420b53a6f0048efc1f0c2a 100644 --- a/magic-api/src/main/java/org/ssssssss/magicapi/core/model/Options.java +++ b/magic-api/src/main/java/org/ssssssss/magicapi/core/model/Options.java @@ -33,9 +33,9 @@ public enum Options { REQUIRE_LOGIN("该接口需要登录才允许访问", "require_login", "true"), /** - * 该接口需要不登录也可访问 + * 该接口不需要登录也可访问 */ - ANONYMOUS("该接口需要不登录也可访问", "anonymous", "true"), + ANONYMOUS("该接口不需要登录也可访问", "anonymous", "true"), /** * 不接收未经定义的参数 diff --git a/magic-api/src/main/java/org/ssssssss/magicapi/core/web/MagicWorkbenchController.java b/magic-api/src/main/java/org/ssssssss/magicapi/core/web/MagicWorkbenchController.java index 705bae42d5ce2f94488bf1ae9e27bb9172f12527..2311adcb9b6970bfcb645250567d3cdd1c100c4a 100644 --- a/magic-api/src/main/java/org/ssssssss/magicapi/core/web/MagicWorkbenchController.java +++ b/magic-api/src/main/java/org/ssssssss/magicapi/core/web/MagicWorkbenchController.java @@ -80,8 +80,15 @@ public class MagicWorkbenchController extends MagicController implements MagicEx @GetMapping("/config.json") @Valid(requireLogin = false) @ResponseBody - public MagicAPIProperties readConfig() { - return properties; + public Map readConfig() { + Map configJson = new HashMap<>(); + configJson.put("persistenceResponseBody", properties.isPersistenceResponseBody()); + configJson.put("version", properties.getVersion()); + configJson.put("web", properties.getWeb()); + configJson.put("prefix", properties.getPrefix()); + configJson.put("autoImportModuleList", properties.getAutoImportModuleList()); + configJson.put("autoImportPackage", properties.getAutoImportPackage()); + return configJson; } @GetMapping(value = "/classes.txt", produces = "text/plain") @@ -252,6 +259,7 @@ public class MagicWorkbenchController extends MagicController implements MagicEx } @RequestMapping(value = "/config-js") + @ResponseBody @Valid(requireLogin = false) public void configJs(MagicHttpServletResponse response) throws IOException { response.setContentType("application/javascript"); @@ -263,9 +271,10 @@ public class MagicWorkbenchController extends MagicController implements MagicEx if (path.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX)) { path = path.substring(ResourceUtils.CLASSPATH_URL_PREFIX.length()); bytes = IoUtils.bytes(new ClassPathResource(path).getInputStream()); + } else { + File file = ResourceUtils.getFile(configuration.getEditorConfig()); + bytes = Files.readAllBytes(Paths.get(file.toURI())); } - File file = ResourceUtils.getFile(configuration.getEditorConfig()); - bytes = Files.readAllBytes(Paths.get(file.toURI())); } catch (IOException e) { logger.warn("读取编辑器配置文件{}失败", configuration.getEditorConfig()); } @@ -277,6 +286,7 @@ public class MagicWorkbenchController extends MagicController implements MagicEx } @RequestMapping("/download") + @ResponseBody @Valid(authorization = Authorization.DOWNLOAD) public void download(String groupId, @RequestBody(required = false) List resources, MagicHttpServletRequest request, MagicHttpServletResponse response) throws IOException { isTrue(allowVisit(request, Authorization.DOWNLOAD), PERMISSION_INVALID); diff --git a/magic-api/src/main/java/org/ssssssss/magicapi/modules/db/BoundSql.java b/magic-api/src/main/java/org/ssssssss/magicapi/modules/db/BoundSql.java index bad6ae168df4bae16d70253df1ba4a0d2aa91cf3..6d47e29f0744a987615c6ac3c916967d83680600 100644 --- a/magic-api/src/main/java/org/ssssssss/magicapi/modules/db/BoundSql.java +++ b/magic-api/src/main/java/org/ssssssss/magicapi/modules/db/BoundSql.java @@ -1,5 +1,7 @@ package org.ssssssss.magicapi.modules.db; +import org.springframework.jdbc.core.SqlOutParameter; +import org.springframework.jdbc.core.SqlParameter; import org.ssssssss.magicapi.core.context.RequestContext; import org.ssssssss.magicapi.core.context.RequestEntity; import org.ssssssss.magicapi.modules.db.inteceptor.SQLInterceptor; @@ -8,9 +10,11 @@ import org.ssssssss.magicapi.modules.db.mybatis.SqlNode; import org.ssssssss.magicapi.modules.db.mybatis.TextSqlNode; import org.ssssssss.script.runtime.RuntimeContext; +import java.sql.Types; import java.util.*; import java.util.function.Supplier; import java.util.regex.Pattern; +import java.util.stream.Collectors; /** * SQL参数处理 @@ -137,6 +141,20 @@ public class BoundSql { return parameters.toArray(); } + public List getDeclareParameters(){ + return this.parameters.stream() + .map(it -> { + if(it instanceof SqlParameter){ + SqlParameter p = (SqlParameter) it; + if(p.getName() != null){ + return new SqlOutParameter(p.getName(), p.getSqlType()); + } + } + return new SqlParameter(Types.NULL); + }) + .collect(Collectors.toList()); + } + /** * 设置要执行的参数 */ diff --git a/magic-api/src/main/java/org/ssssssss/magicapi/modules/db/SQLModule.java b/magic-api/src/main/java/org/ssssssss/magicapi/modules/db/SQLModule.java index f6cb2268707b0e5134c6b531c8fe9ca9c84a54b2..4ee5cc0b0f4a414cc6c35f30b95a1b9dd4bddf0b 100644 --- a/magic-api/src/main/java/org/ssssssss/magicapi/modules/db/SQLModule.java +++ b/magic-api/src/main/java/org/ssssssss/magicapi/modules/db/SQLModule.java @@ -3,37 +3,34 @@ package org.ssssssss.magicapi.modules.db; import org.apache.commons.lang3.StringUtils; import org.springframework.jdbc.core.*; import org.springframework.jdbc.support.GeneratedKeyHolder; -import org.ssssssss.magicapi.core.model.Options; -import org.ssssssss.magicapi.modules.DynamicModule; -import org.ssssssss.magicapi.modules.db.dialect.DialectAdapter; -import org.ssssssss.magicapi.datasource.model.MagicDynamicDataSource; -import org.ssssssss.magicapi.datasource.model.MagicDynamicDataSource.DataSourceNode; import org.ssssssss.magicapi.core.annotation.MagicModule; import org.ssssssss.magicapi.core.context.RequestContext; import org.ssssssss.magicapi.core.context.RequestEntity; +import org.ssssssss.magicapi.core.interceptor.ResultProvider; +import org.ssssssss.magicapi.core.model.Options; +import org.ssssssss.magicapi.datasource.model.MagicDynamicDataSource; +import org.ssssssss.magicapi.datasource.model.MagicDynamicDataSource.DataSourceNode; +import org.ssssssss.magicapi.modules.DynamicModule; import org.ssssssss.magicapi.modules.db.cache.SqlCache; import org.ssssssss.magicapi.modules.db.dialect.Dialect; +import org.ssssssss.magicapi.modules.db.dialect.DialectAdapter; import org.ssssssss.magicapi.modules.db.inteceptor.NamedTableInterceptor; import org.ssssssss.magicapi.modules.db.inteceptor.SQLInterceptor; import org.ssssssss.magicapi.modules.db.model.Page; +import org.ssssssss.magicapi.modules.db.model.SqlTypes; import org.ssssssss.magicapi.modules.db.provider.PageProvider; import org.ssssssss.magicapi.modules.db.table.NamedTable; -import org.ssssssss.magicapi.core.interceptor.ResultProvider; import org.ssssssss.magicapi.utils.ScriptManager; import org.ssssssss.script.MagicScriptContext; import org.ssssssss.script.annotation.Comment; import org.ssssssss.script.functions.DynamicAttribute; import org.ssssssss.script.parsing.ast.statement.ClassConverter; -import org.ssssssss.script.reflection.JavaReflection; import org.ssssssss.script.runtime.RuntimeContext; import java.beans.Transient; -import java.lang.reflect.Field; import java.sql.*; import java.util.*; import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; /** * 数据库查询模块 @@ -44,9 +41,6 @@ import java.util.stream.Stream; public class SQLModule implements DynamicAttribute, DynamicModule { static { try { - Field[] fields = Types.class.getFields(); - Map mappings = Stream.of(fields) - .collect(Collectors.toMap(field -> field.getName().toLowerCase(), field -> (Integer) JavaReflection.getFieldValue(Types.class, field))); ClassConverter.register("sql", (value, params) -> { if (params == null || params.length == 0) { return value; @@ -58,7 +52,7 @@ public class SQLModule implements DynamicAttribute, Dynami if (StringUtils.isBlank(target)) { return value; } - Integer sqlType = mappings.get(target.toLowerCase()); + Integer sqlType = SqlTypes.getSqlType(target); return sqlType == null ? value : new SqlParameterValue(sqlType, target, value); }); } catch (Exception ignored) { @@ -81,6 +75,7 @@ public class SQLModule implements DynamicAttribute, Dynami private long ttl; private String logicDeleteColumn; private String logicDeleteValue; + public static List params; public SQLModule() { @@ -780,4 +775,32 @@ public class SQLModule implements DynamicAttribute, Dynami } } + @Comment("调用存储过程") + public Object call(RuntimeContext runtimeContext, + @Comment(name = "sqlOrXml", value = "`SQL`语句或`xml`") String sqlOrXml) { + assertDatasourceNotNull(); + BoundSql boundSql = new BoundSql(runtimeContext, sqlOrXml, Collections.emptyMap(), this); + return boundSql.execute(this.sqlInterceptors, () -> this.dataSourceNode.getJdbcTemplate().call( + con -> { + CallableStatement statement = con.prepareCall(boundSql.getSql()); + Object[] parameters = boundSql.getParameters(); + for (int i = 0, size = parameters.length; i < size; i++) { + Object parameter = parameters[i]; + if (parameter instanceof SqlOutParameter) { + SqlOutParameter sop = (SqlOutParameter)parameter; + statement.registerOutParameter(i + 1, sop.getSqlType()); + } else if(parameter instanceof SqlParameterValue){ + SqlParameterValue spv = (SqlParameterValue)parameter; + if(spv.getName() != null){ + statement.registerOutParameter(i + 1, spv.getSqlType()); + } + statement.setObject(i + 1, spv.getValue()); + } else { + statement.setObject(i + 1, parameter); + } + } + return statement; + }, boundSql.getDeclareParameters())); + } + } diff --git a/magic-api/src/main/java/org/ssssssss/magicapi/modules/db/model/SqlTypes.java b/magic-api/src/main/java/org/ssssssss/magicapi/modules/db/model/SqlTypes.java new file mode 100644 index 0000000000000000000000000000000000000000..86f1195387a43e63b3ea65d39cbfdb25c5dc4312 --- /dev/null +++ b/magic-api/src/main/java/org/ssssssss/magicapi/modules/db/model/SqlTypes.java @@ -0,0 +1,33 @@ +package org.ssssssss.magicapi.modules.db.model; + +import org.ssssssss.script.reflection.JavaReflection; + +import java.lang.reflect.Field; +import java.sql.Types; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class SqlTypes { + + private static final Map SQL_TYPE_MAPPINGS; + + static { + Field[] fields = Types.class.getFields(); + SQL_TYPE_MAPPINGS = Stream.of(fields) + .collect(Collectors.toMap(field -> field.getName().toLowerCase(), field -> (Integer) JavaReflection.getFieldValue(Types.class, field))); + } + + + public static Integer getSqlType(String type){ + return getSqlType(type, true); + } + + public static Integer getSqlType(String type, boolean defaultNull){ + Integer value = SQL_TYPE_MAPPINGS.get(type.toLowerCase()); + if(value == null && defaultNull){ + return Types.NULL; + } + return value; + } +} diff --git a/magic-api/src/main/java/org/ssssssss/magicapi/modules/db/mybatis/MybatisParser.java b/magic-api/src/main/java/org/ssssssss/magicapi/modules/db/mybatis/MybatisParser.java index c24df0b2622ee9aa50dee9dcdb1e78bf4d4cc653..6e552fc87dd9fb2de7089424e7c049b4ce2bbede 100644 --- a/magic-api/src/main/java/org/ssssssss/magicapi/modules/db/mybatis/MybatisParser.java +++ b/magic-api/src/main/java/org/ssssssss/magicapi/modules/db/mybatis/MybatisParser.java @@ -83,9 +83,9 @@ public class MybatisParser { Node node = stream.consume(); ForeachSqlNode foreachSqlNode = new ForeachSqlNode(); foreachSqlNode.setCollection(getNodeAttributeValue(node, "collection")); - foreachSqlNode.setSeparator(getNodeAttributeValue(node, "separator")); - foreachSqlNode.setClose(getNodeAttributeValue(node, "close")); - foreachSqlNode.setOpen(getNodeAttributeValue(node, "open")); + foreachSqlNode.setSeparator(getNodeAttributeValue(node, "separator", ",")); + foreachSqlNode.setClose(getNodeAttributeValue(node, "close", ")")); + foreachSqlNode.setOpen(getNodeAttributeValue(node, "open", "(")); foreachSqlNode.setItem(getNodeAttributeValue(node, "item")); foreachSqlNode.setIndex(getNodeAttributeValue(node, "index")); return processChildren(foreachSqlNode, node); @@ -118,8 +118,12 @@ public class MybatisParser { return processChildren(new WhereSqlNode(), stream.consume()); } - private static String getNodeAttributeValue(Node node, String attributeKey) { + private static String getNodeAttributeValue(Node node, String attributeKey, String defaultValue) { Node item = node.getAttributes().getNamedItem(attributeKey); - return item != null ? item.getNodeValue() : null; + return item != null ? item.getNodeValue() : defaultValue; + } + + private static String getNodeAttributeValue(Node node, String attributeKey) { + return getNodeAttributeValue(node, attributeKey, null); } } diff --git a/magic-api/src/main/java/org/ssssssss/magicapi/modules/db/mybatis/TextSqlNode.java b/magic-api/src/main/java/org/ssssssss/magicapi/modules/db/mybatis/TextSqlNode.java index 7e198e83d6b00db7d4b1cb2c626029eaebecaa69..b5b669d9fc187fbc868599abf5433a064170dc3a 100644 --- a/magic-api/src/main/java/org/ssssssss/magicapi/modules/db/mybatis/TextSqlNode.java +++ b/magic-api/src/main/java/org/ssssssss/magicapi/modules/db/mybatis/TextSqlNode.java @@ -1,13 +1,21 @@ package org.ssssssss.magicapi.modules.db.mybatis; +import org.springframework.jdbc.core.SqlInOutParameter; +import org.springframework.jdbc.core.SqlOutParameter; +import org.springframework.jdbc.core.SqlParameterValue; +import org.ssssssss.magicapi.modules.db.SQLModule; +import org.ssssssss.magicapi.modules.db.model.SqlTypes; import org.ssssssss.magicapi.utils.ScriptManager; import org.ssssssss.script.functions.StreamExtension; import org.ssssssss.script.parsing.GenericTokenParser; import org.ssssssss.script.parsing.ast.literal.BooleanLiteral; +import java.sql.Types; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -27,6 +35,14 @@ public class TextSqlNode extends SqlNode { private static final GenericTokenParser IF_PARAM_TOKEN_PARSER = new GenericTokenParser("?{", ",", true); + private static final GenericTokenParser OUT_PARAM_TOKEN_PARSER = new GenericTokenParser("@{", ",", true); + + private static final GenericTokenParser OUT_TOKEN_PARSER = new GenericTokenParser("@{", "}", true); + + private static final GenericTokenParser TYPE_TOKEN_PARSER = new GenericTokenParser(",", "}", true); + + private static final GenericTokenParser INOUT_TOKEN_PARSER = new GenericTokenParser("@{", "(", true); + /** * SQL */ @@ -37,6 +53,7 @@ public class TextSqlNode extends SqlNode { } public static String parseSql(String sql, Map varMap, List parameters) { + SQLModule.params = new ArrayList<>(); // 处理?{}参数 sql = IF_TOKEN_PARSER.parse(sql.trim(), text -> { AtomicBoolean ifTrue = new AtomicBoolean(false); @@ -65,6 +82,31 @@ public class TextSqlNode extends SqlNode { return "?"; } }); + sql = OUT_TOKEN_PARSER.parse(sql, text -> { + // 获取类型 + AtomicInteger sqlType = new AtomicInteger(Types.NULL); + TYPE_TOKEN_PARSER.parse(text + "}", type -> { + sqlType.set(SqlTypes.getSqlType(type, true)); + return null; + }); + // 获取名称 + OUT_PARAM_TOKEN_PARSER.parse("@{" + text, param -> { + int index = param.indexOf("("); + if (index > 0) { + // 获取入参值 + String value = param.substring(index + 1, param.lastIndexOf(")")); + INOUT_TOKEN_PARSER.parse("@{" + param, inoutParam -> { + SqlInOutParameter p = new SqlInOutParameter(inoutParam, sqlType.get()); + parameters.add(new SqlParameterValue(p, ScriptManager.executeExpression(value, varMap))); + return null; + }); + } else { + parameters.add(new SqlOutParameter(param, sqlType.get())); + } + return null; + }); + return "?"; + }); return sql; } diff --git a/magic-api/src/main/java/org/ssssssss/magicapi/modules/http/HttpModule.java b/magic-api/src/main/java/org/ssssssss/magicapi/modules/http/HttpModule.java index eebb894c27b9b9c68d87518de949664e62b35728..81b6cba75e444284dfcd3533c698f40b94d6899f 100644 --- a/magic-api/src/main/java/org/ssssssss/magicapi/modules/http/HttpModule.java +++ b/magic-api/src/main/java/org/ssssssss/magicapi/modules/http/HttpModule.java @@ -133,6 +133,12 @@ public class HttpModule { return this; } + @Comment("设置返回值为`String`") + public HttpModule expectString() { + this.responseType = String.class; + return this; + } + @Comment("发送`POST`请求") public ResponseEntity post() { this.method(HttpMethod.POST);