redis插件

This commit is contained in:
mxd 2019-08-27 17:34:02 +08:00
parent 56606342f5
commit 1aa685ff5f
11 changed files with 402 additions and 17 deletions

View File

@ -43,7 +43,7 @@ spider-flow
#### 插件列表
- [x] Selenium插件
- [ ] Redis插件
- [x] Redis插件
- [ ] Mongodb插件
- [ ] Hbase插件
- [x] IP代理池插件

View File

@ -35,5 +35,6 @@
<module>spider-flow-web</module>
<module>spider-flow-selenium</module>
<module>spider-flow-proxypool</module>
<module>spider-flow-redis</module>
</modules>
</project>

31
spider-flow-redis/pom.xml Normal file
View File

@ -0,0 +1,31 @@
<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.spiderflow</groupId>
<artifactId>spider-flow</artifactId>
<version>0.0.1</version>
</parent>
<artifactId>spider-flow-redis</artifactId>
<name>spider-flow-selenium</name>
<url>https://gitee.com/jmxd/spider-flow/tree/master/spider-flow-redis</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.spiderflow</groupId>
<artifactId>spider-flow-api</artifactId>
<version>0.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,95 @@
package org.spiderflow.redis.executor.shape;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spiderflow.ExpressionEngine;
import org.spiderflow.context.SpiderContext;
import org.spiderflow.executor.ShapeExecutor;
import org.spiderflow.model.Shape;
import org.spiderflow.model.SpiderNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
@Component
public class RedisCommandExecutor implements ShapeExecutor{
private static Logger logger = LoggerFactory.getLogger(RedisCommandExecutor.class);
public static final String DATASOURCE_ID = "datasourceId";
public static final String REDIS_COMMAND = "command";
public static final String REDIS_OPERATION_TYPE = "operationType";
public static final String REDIS_COMMAND_VAR = "___redis";
@Autowired
private ExpressionEngine engine;
@Override
public String supportShape() {
return "rediscommand";
}
@Override
public Shape shape() {
Shape shape = new Shape();
shape.setImage("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAA7klEQVRIS93VsQ2CUBDG8f/FwtbGhE5HcATcQCdRJhAneDqRjOAI2lFaamHOQMQQo3IPnhZSw/1e3nH3CV9+5Mv1KYHI6Zgeo6DYlWOeyKECUoWlwD4EojAR2OSJpA8AiPNE4hBA5DQDsj8Ghk4nPcGdlfkpkZPvtTVe0cDpoA8Zgl6UqS/SCBQn7oKYgC6IGWiL+APCDkUuEFv6YQbKPngWv6+e5kF7VfzjzrrvHhPw7uSR0xRh9XIulHWxGkxAOWiwOcPMcufPoLkHvhNcvf+ngAqLoIGjbOt5UARNkLCp9a0InOw3od/2b7F8dwNyJQEoW0HkAAAAAABJRU5ErkJggg==");
shape.setLabel("Redis命令");
shape.setName("rediscommand");
shape.setTitle("Redis命令");
return shape;
}
@Override
public void execute(SpiderNode node, SpiderContext context, Map<String, Object> variables) {
String datasourceId = node.getStringJsonValue(DATASOURCE_ID);
String command = node.getStringJsonValue(REDIS_COMMAND);
String operationType = node.getStringJsonValue(REDIS_OPERATION_TYPE);
if(!StringUtils.isNotBlank(datasourceId)){
context.log("Redis数据源ID为空");
if(logger.isDebugEnabled()){
logger.debug("Redis数据源ID为空");
}
}else if(!StringUtils.isNotBlank(command)){
context.log("redis命令为空");
if(logger.isDebugEnabled()){
logger.debug("redis命令为空");
}
}else if(!StringUtils.isNotBlank(operationType)){
context.log("redis命令类型为空");
if(logger.isDebugEnabled()){
logger.debug("redis命令类型为空");
}
}else{
StringRedisTemplate redisTemplate = (StringRedisTemplate) context.get(RedisExecutor.REDIS_CONTEXT_KEY + datasourceId);
Object operation = getOperations(redisTemplate, operationType);
variables.put(REDIS_COMMAND_VAR, operation);
String expression = String.format("${%s.%s}", REDIS_COMMAND_VAR,command);
Object result = engine.execute(expression, variables);
variables.put("rs", result);
variables.remove(REDIS_COMMAND_VAR);
}
}
private Object getOperations(StringRedisTemplate redisTemplate,String operationType){
switch(operationType){
case "list" : return redisTemplate.opsForList();
case "value" : return redisTemplate.opsForValue();
case "set" : return redisTemplate.opsForSet();
case "zset" : return redisTemplate.opsForZSet();
case "geo" : return redisTemplate.opsForGeo();
case "hash" : return redisTemplate.opsForHash();
case "hyperLogLog" : return redisTemplate.opsForHyperLogLog();
}
return redisTemplate;
}
}

View File

@ -0,0 +1,61 @@
package org.spiderflow.redis.executor.shape;
import java.util.Map;
import org.apache.commons.lang3.math.NumberUtils;
import org.spiderflow.context.SpiderContext;
import org.spiderflow.executor.ShapeExecutor;
import org.spiderflow.model.Shape;
import org.spiderflow.model.SpiderNode;
import org.spiderflow.redis.utils.RedisUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
@Component
public class RedisExecutor implements ShapeExecutor{
public static final String DATABASE_INDEX = "database";
public static final String REDIS_HOST = "host";
public static final String REDIS_PORT = "port";
public static final String REDIS_PASSWORD = "password";
public static final String REDIS_POOL_MAX_ACTIVE = "poolMaxActive";
public static final String REDIS_POOL_MAX_IDLE = "poolMaxIdle";
public static final String REDIS_POOL_MIN_IDLE = "poolMinIdle";
public static final String REDIS_CONTEXT_KEY = "$redis_";
@Override
public String supportShape() {
return "redis";
}
@Override
public Shape shape() {
Shape shape = new Shape();
shape.setImage("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAYCAYAAADpnJ2CAAAEMUlEQVRIS5VWS28bVRT+zp0ZO48676RtoMRIdR6tlBqpRSAWpGFPIrFBQojsaBHQZI2Euq1AciqxY2OrqtQNwvyBPhZQQUGkG5QQFu4ixiFJG+I87bn3oDPxjJ2x3YYrWX7de875HufMJfyP9TA5GmfQFKCmAUwAuA+YbCv2Mq8t5DaPE4qOs+lh8twMA1MATbeNjGB3ack7Fh0cxEE+731mcBpA9q2FP354XsymCQ/RWNcYmCGgyw9y+oMPcebjq9jP57H75xL++vKLUHzOEZAGTObNhcVcOPmRhL8n4137aJ8y4FkCJcObey5Pon1kFD2X30Hb8Ai2fnuEp/fu4u/bt5qA4iwD6VrUXsIfk+ck+DWhTNAIbT0Tk+i8eMkLtLO0iKf372Hr10fedysWw5krnyD31Q3vu2bGlgE2tMHKfglRxTjfGkXMsvz/CxbRHYK+SXfHx35uUep1v0RJduHOdw0rzn19ow7NujZYcw00DskqHBwgd1BGhIA3TrRguCUaxNo1vEmZsREechQiSgV/iE4+fdaJmEedoMvfvgVdLNYVs2cMNjSwZQy23TK6LIWhiINoJWbJGKxpRr5sQDeHh1kidFmEU7ZCzKomro0sjoyeHvQ0lM/yDiIszn3esAg5u2sYq2WNde2l8BbNJxKbRNTp/yBUSOI+W8Giqqc6Ll6Ch3xiMjj8+P33ghapLW7dNVh3NYombGDOUObVl8fLTuu3O6BAR88YYHRbCicdC22qmljQDbw7DbdYPKKnGKfgJTIoVQF5GWMK6LdppddWn9KD8bFtR6n2A08HRqGsAwP49cmBPtvyUIeX6LNSNnimq8apLXjQUYGWK2UN+mY4wa9EbHRbFFC45mpsuKaOEqFbknYogsuMTc1H9JFEsqffVhiwFeyKJIJagOwxqqYRCiWY6Oc7dscY/NOg+kY9Iyz02gr9dtB7DSkWly4AuFAbRBzbZxG6K4cFjSAWjcL6yD5J1FFxdzOKwbxtgM/op+TYbKFkrq9q7gwHa+RY0WrPHLqi16JAn2euxmoTGXyK9xm/0PfnR1mcKGtLG++QaBNegiTsWNkj+ogZGjmzluKiNnhS0ocaCpKXHCswzoscK8NB2kCS+SPNL1AKG3AU2itTJtyTwaTxrRw2TjPH1jIghjvlWIEzn9OTjymVSMjzbr522vjNetJWgXF8x9aOKWFG+sx3ZjPDMPO/BGRNuTzrjZBUPN4Fx5lWRNcBDNVW7/eetIuMOnGszEhZvjNFH9GxbpQBTwCkTak0P5c7vILUPfFTZ89OKKIZEH30IuM0MwyYHxggPbe8LNeOI6vpFSMVj8dVJDIDQF5HUDdqfO835owB5ueWl6W3G65jXaJEZyWJid4OR/H0IZo3pVJ6Lperu8McG2Gj8lKJRFIBsyCaAHOOmLPadSXRsa6IEvM/0FkPMhbAf00AAAAASUVORK5CYII=");
shape.setLabel("Redis");
shape.setName("redis");
shape.setTitle("Redis");
return shape;
}
@Override
public void execute(SpiderNode node, SpiderContext context, Map<String, Object> variables) {
int database = NumberUtils.toInt(node.getStringJsonValue(DATABASE_INDEX),0);
String host = node.getStringJsonValue(REDIS_HOST);
String password = node.getStringJsonValue(REDIS_PASSWORD);
int port = NumberUtils.toInt(node.getStringJsonValue(REDIS_PORT),6379);
int poolMaxActive = NumberUtils.toInt(node.getStringJsonValue(REDIS_POOL_MAX_ACTIVE),8);
int poolMaxIdle = NumberUtils.toInt(node.getStringJsonValue(REDIS_POOL_MAX_IDLE),8);
int poolMinIdle = NumberUtils.toInt(node.getStringJsonValue(REDIS_POOL_MIN_IDLE),0);
StringRedisTemplate redisTemplate = RedisUtils.createRedisTemplate(host, port, password, database, poolMaxActive, poolMaxIdle, poolMinIdle);
context.put(REDIS_CONTEXT_KEY + node.getNodeId(), redisTemplate);
}
}

View File

@ -0,0 +1,28 @@
package org.spiderflow.redis.utils;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import redis.clients.jedis.JedisPoolConfig;
public class RedisUtils {
public static StringRedisTemplate createRedisTemplate(String host,int port,String password,int database,int poolMaxActive,int poolMaxIdle,int poolMinIdle){
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(poolMaxActive);
poolConfig.setMaxIdle(poolMaxIdle);
poolConfig.setMinIdle(poolMinIdle);
JedisClientConfiguration jedisConfigConfiguration = JedisClientConfiguration.builder().usePooling().poolConfig(poolConfig).build();
RedisStandaloneConfiguration standaloneConfiguration = new RedisStandaloneConfiguration();
standaloneConfiguration.setDatabase(database);
standaloneConfiguration.setHostName(host);
standaloneConfiguration.setPassword(RedisPassword.of(password));
standaloneConfiguration.setPort(port);
StringRedisTemplate redisTemplate = new StringRedisTemplate(new JedisConnectionFactory(standaloneConfiguration,jedisConfigConfiguration));
return redisTemplate;
}
}

View File

@ -0,0 +1,32 @@
package org.spiderflow.redis.web;
import org.spiderflow.model.JsonBean;
import org.spiderflow.redis.utils.RedisUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/redis")
public class RedisController {
@RequestMapping("/test")
public JsonBean<String> test(String host,Integer port,String password,Integer database){
try {
StringRedisTemplate redisTemplate = RedisUtils.createRedisTemplate(host, port, password, database, 1, 1, 0);
ValueOperations<String, String> operation = redisTemplate.opsForValue();
String testKey = "____spider_flow_redis_test";
operation.set(testKey, "1");
if (redisTemplate.hasKey(testKey)) {
redisTemplate.delete(testKey);
return new JsonBean<String>(1,"测试成功");
}
return new JsonBean<String>(0,"测试失败");
} catch (Exception e) {
e.printStackTrace();
return new JsonBean<String>(-1,e.getMessage());
}
}
}

View File

@ -0,0 +1,96 @@
<div class="layui-tab layui-tab-fixed layui-tab-brief">
<ul class="layui-tab-title">
<li class="layui-this">基本配置</li>
</ul>
<div class="layui-tab-content editor-form-node">
<div class="layui-tab-item layui-show">
<form class="layui-form">
<div class="layui-form-item">
<label class="layui-form-label">节点名称</label>
<div class="layui-input-block">
<input type="text" name="value" placeholder="请输入节点名称" value="{{=d.value}}" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">host</label>
<div class="layui-input-block">
<input type="text" name="host" placeholder="请输入redis host" autocomplete="off" class="layui-input input-default" value="{{=d.data.object.host}}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">port</label>
<div class="layui-input-block">
<input type="text" name="port" placeholder="请输入redis port" autocomplete="off" class="layui-input input-default" value="{{=d.data.object.port}}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">密码</label>
<div class="layui-input-block">
<input type="password" name="password" placeholder="请输入redis password,没有则留空" autocomplete="off" class="layui-input input-default" value="{{=d.data.object.password}}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">数据库索引</label>
<div class="layui-input-block">
<input type="text" name="database" placeholder="请输入redis 数据库索引,默认为0" autocomplete="off" class="layui-input input-default" value="{{=d.data.object.database}}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">最大连接数</label>
<div class="layui-input-block">
<input type="text" name="poolMaxActive" placeholder="请输入最大连接数,默认为8" autocomplete="off" class="layui-input input-default" value="{{=d.data.object.poolMaxActive}}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">最大空闲连接</label>
<div class="layui-input-block">
<input type="text" name="poolMaxIdle" placeholder="请输入最大空闲连接,默认为8" autocomplete="off" class="layui-input input-default" value="{{=d.data.object.poolMaxIdle}}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">最小空闲连接</label>
<div class="layui-input-block">
<input type="text" name="poolMinIdle" placeholder="请输入最小空闲连接,默认为0" autocomplete="off" class="layui-input input-default" value="{{=d.data.object.poolMinIdle}}">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn btn-redis-datasource-test" type="button">测试连接</button>
</div>
</div>
</form>
</div>
</div>
</div>
<script>
$('.layui-form').on('click','.btn-redis-datasource-test',function(){
var $form = $('.layui-form');
var host = $form.find('input[name=host]').val();
var port = $form.find('input[name=port]').val();
var password = $form.find('input[name=password]').val();
var database = $form.find('input[name=database]').val();
$.ajax({
url : 'redis/test',
data : {
host : host || '127.0.0.1',
port : port || 6379,
password : password,
database : database || 0
},
type : 'post',
dataType : 'json',
success : function(json){
if(json.code == 1){
layui.layer.msg('测试成功');
}else{
layui.layer.alert('测试失败,' + json.message || '',{
icon : 2
})
}
},
error : function(){
layui.layer.msg('测试失败');
}
})
});
</script>

View File

@ -0,0 +1,50 @@
<div class="layui-tab layui-tab-fixed layui-tab-brief">
<ul class="layui-tab-title">
<li class="layui-this">基本配置</li>
</ul>
<div class="layui-tab-content editor-form-node">
<div class="layui-tab-item layui-show">
<form class="layui-form">
<div class="layui-form-item">
<label class="layui-form-label">节点名称</label>
<div class="layui-input-block">
<input type="text" name="value" placeholder="请输入节点名称" value="{{=d.value}}" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">数据源</label>
<div class="layui-input-block">
<select name="datasourceId">
{{# for(var datasourceIndex in d.model.cells){ }}
{{# var cell = d.model.cells[datasourceIndex] }}
{{# if(cell.data&&cell.data.get('shape') == 'redis'){ }}
<option value="{{=datasourceIndex}}" {{datasourceIndex == d.data.object.datasourceId ? 'selected': ''}}>{{cell.value}}</option>
{{# } }}
{{# } }}
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">命令类型</label>
<div class="layui-input-block">
<select name="operationType">
<option value="value" {{d.data.object.operationType == 'value' ? 'selected': ''}}>value</option>
<option value="keys" {{d.data.object.operationType == 'keys' ? 'selected': ''}}>keys</option>
<option value="list" {{d.data.object.operationType == 'list' ? 'selected': ''}}>list</option>
<option value="hash" {{d.data.object.operationType == 'hash' ? 'selected': ''}}>hash</option>
<option value="set" {{d.data.object.operationType == 'set' ? 'selected': ''}}>set</option>
<option value="zset" {{d.data.object.operationType == 'zset' ? 'selected': ''}}>zset</option>
<option value="hyperLogLog" {{d.data.object.operationType == 'hyperLogLog' ? 'selected': ''}}>hyperLogLog</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">Redis命令</label>
<div class="layui-input-block">
<textarea class="layui-input" style="height:200px" name="command">{{=d.data.object.command}}</textarea>
</div>
</div>
</form>
</div>
</div>
</div>

View File

@ -32,5 +32,10 @@
<artifactId>spider-flow-proxypool</artifactId>
<version>0.0.1</version>
</dependency>
<dependency>
<groupId>org.spiderflow</groupId>
<artifactId>spider-flow-redis</artifactId>
<version>0.0.1</version>
</dependency>
</dependencies>
</project>

View File

@ -98,7 +98,8 @@ $(function(){
data : cell.data,
value : cell.value,
datasources : datasources,
flows : flows || []
flows : flows || [],
model : model
},function(html){
$(".properties-container").html(html);
layui.form.render();
@ -291,21 +292,6 @@ $(function(){
name : 'process',
image : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAACQElEQVRIS7WVwXEaQRBF3+di3YwPNtwEEZgMJEUgMhCOQLsRSIpgIAKjCIwjEGQgR2B0W+wLvsEB2tXDLrVg5MWuYqqoLXan+0///7tHnHjpmPzNYC2rcS2oF/tXa77+TPVcFV8J8CFYtya+HExkfMpSDf8GUgnQDDY26Ai6e4mGCMsStZvBeoib/PsU4zFLNfb/lQCNYCPEBaInMfcgM+oy+mb8mqXqRADwn2e8iM+8ukqA98E6NRhLvC1X4MkNej9Sjcrvo14wkviIcVUJUAQ3g12WExUUHOLfQRDfnaqjAarcsv/dtTtKg39NXOxvBHsWzI+qIIpY43xHA5jPbjUo3tWD1c+gnqWaRjrFE8ZDJUAjWCIRDlVixmCWKvHkb8STjBRome83tITWFsAbSuJO0Mlt9pClus+5bC2gM08VberLKUCczxK9i3S4a4plvKyg650eAfJG+YzxArg4U3+6U7wPJK4N+kUf5L2QyPiWpbosxT+uYVS2rkqWmiygWz6lJ8q53TTbrgiTFSTFPMqbbbpvXzWC+cluMdou0P+65rU4r2Dsp8tcyhMsB/ChdXNKAJ+ELnB0TSF6eQw3g93/oYEYL9YM9jXbJ6FwUaQJGFJjaGtGa+PKBcyT3x1kbzOWN1P0lRUBcqf0SzMdg/nSaJ/ByKDuY7mco7BvFbU7wrpl19CJN1g+z7cu8+oU+yPeB3H+G5NZqv2LaKeWg85xTxcabKsDny9xHvld4I24hN5RGpzAnduUJ/F++cC/AZxmDfR5GHMZAAAAAElFTkSuQmCC',
title : '子流程'
},{
name : 'redis',
image : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAYCAYAAADpnJ2CAAAEMUlEQVRIS5VWS28bVRT+zp0ZO48676RtoMRIdR6tlBqpRSAWpGFPIrFBQojsaBHQZI2Euq1AciqxY2OrqtQNwvyBPhZQQUGkG5QQFu4ixiFJG+I87bn3oDPxjJ2x3YYrWX7de875HufMJfyP9TA5GmfQFKCmAUwAuA+YbCv2Mq8t5DaPE4qOs+lh8twMA1MATbeNjGB3ack7Fh0cxEE+731mcBpA9q2FP354XsymCQ/RWNcYmCGgyw9y+oMPcebjq9jP57H75xL++vKLUHzOEZAGTObNhcVcOPmRhL8n4137aJ8y4FkCJcObey5Pon1kFD2X30Hb8Ai2fnuEp/fu4u/bt5qA4iwD6VrUXsIfk+ck+DWhTNAIbT0Tk+i8eMkLtLO0iKf372Hr10fedysWw5krnyD31Q3vu2bGlgE2tMHKfglRxTjfGkXMsvz/CxbRHYK+SXfHx35uUep1v0RJduHOdw0rzn19ow7NujZYcw00DskqHBwgd1BGhIA3TrRguCUaxNo1vEmZsREechQiSgV/iE4+fdaJmEedoMvfvgVdLNYVs2cMNjSwZQy23TK6LIWhiINoJWbJGKxpRr5sQDeHh1kidFmEU7ZCzKomro0sjoyeHvQ0lM/yDiIszn3esAg5u2sYq2WNde2l8BbNJxKbRNTp/yBUSOI+W8Giqqc6Ll6Ch3xiMjj8+P33ghapLW7dNVh3NYombGDOUObVl8fLTuu3O6BAR88YYHRbCicdC22qmljQDbw7DbdYPKKnGKfgJTIoVQF5GWMK6LdppddWn9KD8bFtR6n2A08HRqGsAwP49cmBPtvyUIeX6LNSNnimq8apLXjQUYGWK2UN+mY4wa9EbHRbFFC45mpsuKaOEqFbknYogsuMTc1H9JFEsqffVhiwFeyKJIJagOwxqqYRCiWY6Oc7dscY/NOg+kY9Iyz02gr9dtB7DSkWly4AuFAbRBzbZxG6K4cFjSAWjcL6yD5J1FFxdzOKwbxtgM/op+TYbKFkrq9q7gwHa+RY0WrPHLqi16JAn2euxmoTGXyK9xm/0PfnR1mcKGtLG++QaBNegiTsWNkj+ogZGjmzluKiNnhS0ocaCpKXHCswzoscK8NB2kCS+SPNL1AKG3AU2itTJtyTwaTxrRw2TjPH1jIghjvlWIEzn9OTjymVSMjzbr522vjNetJWgXF8x9aOKWFG+sx3ZjPDMPO/BGRNuTzrjZBUPN4Fx5lWRNcBDNVW7/eetIuMOnGszEhZvjNFH9GxbpQBTwCkTak0P5c7vILUPfFTZ89OKKIZEH30IuM0MwyYHxggPbe8LNeOI6vpFSMVj8dVJDIDQF5HUDdqfO835owB5ueWl6W3G65jXaJEZyWJid4OR/H0IZo3pVJ6Lperu8McG2Gj8lKJRFIBsyCaAHOOmLPadSXRsa6IEvM/0FkPMhbAf00AAAAASUVORK5CYII=',
title : 'redis',
disabled : true
},{
name : 'mongodb',
image : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAB/klEQVRIS2NkIAHMPjO9nYmBbWuySfIRYrUxEqtw4YV54Refn5vP9JfzY49PtySx+oiyYOm1pfJ7bm69//33N8aPn38xGEob3mnzaFUlxhKiLCjanv392eenHCADQRaAgKua29wi24IUQpYQtGDyyf71Rx8eDIAZBLOAi43rv5empx2h+MBrAXLQwCwQ45BluP36LpirLa71vMenRwqfL/BaMOP05DkH7u9NRjYA2QKQeIh+sC0+X+C1oHJ38Yf77+/y47PAUMrwbptnqwouX+C1IGJVwH90jeg+EOYW/rMkYjEryRbMPz+7YOftrf2ELADJ++sGRmSYpa7EZglOH5Biga+mT2GWVdaEEWYBqOzZfmvTCmLigKwgAhmcuD7q//ff31DsUOLTZDj/9CKK2PbkbTjjEm8ybT3QcOfyqwvKyKapC+oynHp0Fi6kLa79Al/pitcCbMHExyLG8Pj9E7gFhAo9goVd/b7K5zffXJdAL+xAfBl+mR+zQ2Zxkl0WgTRCC7wHsLiAlaaEMhjMUoI+ACkEBdWB+3tWcLFwMbD95Wa48fY29eoDmEvWnF+Uff3trSmSPwUYmPh4bybZpmsQqmxA8kT5AGbQ8WNb5nz/9snBySUKZ+mJbilJFpw9vaPg2/dvAbZ2QQ7EuJ5kH4A0HDu8Yb2VbUAgsRYAADh/9hka9HGsAAAAAElFTkSuQmCC',
title : 'mongodb',
disabled : true
},{
name : 'oss',
image : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAABrklEQVRIS+WVzVEbQRBG3yfKJW7IB5u9oQwQERgi8BIBZGAmAkQEQwZABIgIrAzAEVi6LeaAuMkH1NSshGqFVjsLVVRRxRx3pvv1z9e94p2P3tk/nwyQeDs08VPQAdoGN8BAopf90kVZuWuV6Ju3zpq4DE4rejZ4NPbvnAJ0fqKA795SiTNBKyYIg9HE2CtCKgGJt7aJ6zrOn+EBImMncxqEbzHAOeIgFvnSvXGROR1WAlreWuvi/tXOZwZj4+vIabSQQXDabHAgIzXolJXGjAeJjSjY2Muc+nNA7lz8nkmw1N6MP/9htwl9ie1KiHGSOXXngMRbH/FjlVEeOXRC88I8IM5qA2Y6v44Y5CmHN3m20wFbXapiiTa9HUn4iujdrdNp8T7xVqmwhSYn3rqI45UA6EncMMnlF0q0O4FWYzrdy+elTGuVCAhRfYH2mlhZzmKvFuagRpOvbp3SWLYTY/+fU+85raKKwiILStoqmczheLpBaYq/pavDGD5CWrnsZuoI2k1zkDEEzsdwGqYyLL4GHBXlbMZVCOylCJYyiE7mGx9E1/Ub/db/H3x4wBPCkrQZHDvXGwAAAABJRU5ErkJggg==',
title : 'oss',
disabled : true
}]
var addShape = function(shape){
var image = new Image();