Browse Source

初始化

chenanlian 5 months ago
commit
2485467eaa

+ 133 - 0
.gitignore

@@ -0,0 +1,133 @@
+## .gitignore for Grails 1.2 and 1.3
+
+# .gitignore for maven 
+/target
+*.releaseBackup
+
+# web application files
+#/web-app/WEB-INF
+ 
+# IDE support files
+/.classpath
+/.launch
+/.project
+/.settings
+/*.launch
+/*.tmproj
+/ivy*
+/eclipse
+ 
+# default HSQL database files for production mode
+/prodDb.*
+ 
+# general HSQL database files
+*Db.properties
+*Db.script
+ 
+# logs
+/stacktrace.log
+/test/reports
+/logs
+*.log
+*.log.*
+ 
+# project release file
+/*.war
+ 
+# plugin release file
+/*.zip
+/*.zip.sha1
+ 
+# older plugin install locations
+/plugins
+/web-app/plugins
+/web-app/WEB-INF/classes
+ 
+# "temporary" build files
+out/
+build/
+ 
+# other
+*.iws
+ 
+#.gitignore for java
+*.class
+ 
+# Package Files #
+*.jar
+*.war
+*.ear
+ 
+## .gitignore for eclipse
+ 
+*.pydevproject
+.project
+.metadata
+bin/**
+tmp/**
+tmp/**/*
+*.tmp
+*.bak
+*.swp
+*~.nib
+local.properties
+.classpath
+.settings/
+.loadpath
+ 
+# External tool builders
+.externalToolBuilders/
+ 
+# Locally stored "Eclipse launch configurations"
+*.launch
+ 
+# CDT-specific
+.cproject
+ 
+# PDT-specific
+.buildpath
+ 
+## .gitignore for intellij
+ 
+*.iml
+*.ipr
+*.iws
+.idea/
+ 
+## .gitignore for linux
+.*
+!.gitignore
+*~
+ 
+## .gitignore for windows
+ 
+# Windows image file caches
+Thumbs.db
+ehthumbs.db
+ 
+# Folder config file
+Desktop.ini
+ 
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+ 
+## .gitignore for mac os x
+ 
+.DS_Store
+.AppleDouble
+.LSOverride
+Icon
+ 
+ 
+# Thumbnails
+._*
+ 
+# Files that might appear on external disk
+.Spotlight-V100
+.Trashes
+
+## hack for graddle wrapper
+!wrapper/*.jar
+!**/wrapper/*.jar
+
+/nodeId

+ 202 - 0
pom.xml

@@ -0,0 +1,202 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+
+    <modelVersion>4.0.0</modelVersion>
+    <packaging>jar</packaging>
+    <groupId>com.outmind</groupId>
+    <artifactId>winbot</artifactId>
+    <version>1.0.0</version>
+    <name>winbot</name>
+    <description>win机器人</description>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <java.version>1.8</java.version>
+        <jcuda.version>12.0.0</jcuda.version>
+    </properties>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-core</artifactId>
+            <version>1.1.7</version>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+            <version>1.1.7</version>
+        </dependency>
+
+        <!-- JSON解析 -->
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+            <version>2.9.5</version>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <version>2.9.5</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.8.1</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>commons-codec</groupId>
+                    <artifactId>commons-codec</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+            <version>1.13</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+            <version>2.6</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.7</version>
+        </dependency>
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>3.8.1</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter</artifactId>
+            <version>5.8.0</version>
+            <scope>test</scope>
+        </dependency>
+
+    </dependencies>
+    <build>
+        <plugins>
+            <!-- 清理构建目录外的文件-->
+            <plugin>
+                <artifactId>maven-clean-plugin</artifactId>
+                <version>2.5</version>
+                <configuration>
+                    <filesets>
+                        <fileset>
+                            <directory>.</directory>
+                            <includes>
+                                <include>**/*~</include>
+                            </includes>
+                        </fileset>
+                        <fileset>
+                            <directory>..</directory>
+                            <includes>
+                                <include>**/*.class</include>
+                            </includes>
+                        </fileset>
+                        <fileset>
+                            <directory>../logs</directory>
+                            <includes>
+                                <include>**/*.log</include>
+                            </includes>
+                        </fileset>
+                        <fileset>
+                            <directory>../tmp</directory>
+                            <includes>
+                                <include>**/*</include>
+                            </includes>
+                        </fileset>
+                        <fileset>
+                            <directory>../upload</directory>
+                            <includes>
+                                <include>**/*</include>
+                            </includes>
+                        </fileset>
+                    </filesets>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                    <encoding>UTF-8</encoding>
+                    <compilerArguments>
+                        <extdirs>libs</extdirs>
+                        <!--  rt包没有打到项目中去 -->
+                        <verbose/>
+                        <bootclasspath>${java.home}/lib/rt.jar;${java.home}/lib/jce.jar;${java.home}/lib/jsse.jar
+                        </bootclasspath>
+                    </compilerArguments>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-shade-plugin</artifactId>
+                <version>1.4</version>
+                <configuration>
+                    <createDependencyReducedPom>false</createDependencyReducedPom>
+                </configuration>
+                <executions>
+                    <execution>
+                        <!-- 执行package的phase -->
+                        <phase>package</phase>
+                        <!-- 为这个phase绑定goal -->
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+                        <configuration>
+                            <!-- 过滤掉以下文件,不打包 :解决包重复引用导致的打包错误-->
+                            <filters>
+                                <filter>
+                                    <artifact>*:*</artifact>
+                                    <excludes>
+                                        <exclude>META-INF/*.SF</exclude>
+                                        <exclude>META-INF/*.DSA</exclude>
+                                        <exclude>META-INF/*.RSA</exclude>
+                                    </excludes>
+                                </filter>
+                            </filters>
+                            <transformers>
+                                <transformer
+                                        implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
+                                    <resource>META-INF/spring.handlers</resource>
+                                </transformer>
+                                <!-- 打成可执行的jar包 的主方法入口-->
+                                <transformer
+                                        implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+                                    <mainClass>com.wefanbot.winbot.Main</mainClass>
+                                </transformer>
+
+                                <transformer
+                                        implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
+                                    <resource>META-INF/spring.schemas</resource>
+                                </transformer>
+                            </transformers>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+
+        </plugins>
+        <defaultGoal>compile</defaultGoal>
+    </build>
+</project>

+ 148 - 0
src/main/java/com/wefanbot/winbot/Frame.java

@@ -0,0 +1,148 @@
+package com.wefanbot.winbot;
+
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.swing.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
+import java.io.File;
+
+public class Frame extends JFrame {
+
+    private static final Logger log = LoggerFactory.getLogger(Frame.class);
+
+
+    /**
+     * 窗口宽度
+     */
+    int WIDTH;
+
+    /**
+     * 窗口高度
+     */
+    int HEIGHT;
+
+
+    /**
+     * 菜单栏
+     */
+    private JMenuBar menuBar;
+
+    /**
+     * 菜单栏的高度
+     */
+    int menuBarHeight = 60;
+
+
+    public static File excelSource;
+    public static File imgDir;
+
+    public Frame(int width, int height, int panelOperatorWidth) {
+
+        this.WIDTH = width;
+        this.HEIGHT = height;
+
+        this.setTitle("EXCEL");
+        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        this.setSize(width, height);
+        this.setResizable(false);
+        this.setLayout(null);
+        setLocationRelativeTo(null);
+        this.initMenuBar();
+    }
+
+    /**
+     * 初始化菜单
+     */
+    private void initMenuBar() {
+
+        menuBar = new JMenuBar();
+        JMenu menuFile = new JMenu("文件(F)");
+        menuFile.setMnemonic('F');  //设置菜单的键盘操作方式是Alt + F键
+
+        JMenuItem itemOpen, itemSave, itemSaveOther;
+        itemOpen = new JMenuItem("选择Excel");
+        itemSave = new JMenuItem("选择图片文件夹");
+        itemSaveOther = new JMenuItem("另存为");
+        menuFile.add(itemOpen);
+        menuFile.addSeparator();
+        menuFile.add(itemSave);
+//        menuFile.add(itemSaveOther);
+        menuBar.add(menuFile);
+
+        //设置菜单项的键盘操作方式是Ctrl+O和Ctrl+S键
+        KeyStroke Ctrl_cutKey = KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_MASK);
+        itemOpen.setAccelerator(Ctrl_cutKey);
+
+        Ctrl_cutKey = KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_MASK);
+        itemSave.setAccelerator(Ctrl_cutKey);
+
+        setJMenuBar(menuBar);
+
+        itemOpen.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+
+                JFileChooser jfc = new JFileChooser();
+                jfc.setFileSelectionMode(JFileChooser.FILES_ONLY);
+                jfc.showDialog(new JLabel(), "选择Excel");
+                File file = jfc.getSelectedFile();
+
+                if (file == null) {
+                    return;
+                }
+
+                if (!file.exists()) {
+                    JOptionPane.showMessageDialog(null, "文件不存在", "错误", JOptionPane.YES_NO_OPTION);
+                    return;
+                }
+
+                String path = file.getAbsolutePath();
+                if (!(path.endsWith(".csv") || path.endsWith(".xlsx") || path.endsWith(".xsx"))) {
+                    JOptionPane.showMessageDialog(null, "不是xsx文件", "错误", JOptionPane.YES_NO_OPTION);
+                    return;
+                }
+
+                excelSource = file;
+                log.info("选择excel文件来源,{}", excelSource.getAbsolutePath());
+
+            }
+        });
+
+        itemSave.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+
+                JFileChooser jfc = new JFileChooser();
+                jfc.setFileSelectionMode(JFileChooser.FILES_ONLY);
+                jfc.showDialog(new JLabel(), "选择图片文件夹");
+                File file = jfc.getSelectedFile();
+
+                if (file == null) {
+                    return;
+                }
+
+                if (!file.exists()) {
+                    JOptionPane.showMessageDialog(null, "文件不存在", "错误", JOptionPane.YES_NO_OPTION);
+                    return;
+                }
+
+                if (!file.isDirectory()) {
+                    JOptionPane.showMessageDialog(null, "不是文件夹", "错误", JOptionPane.YES_NO_OPTION);
+                    return;
+                }
+
+                imgDir = file;
+                log.info("选择excel文件来源,{}", imgDir.getAbsolutePath());
+
+            }
+        });
+
+    }
+
+
+}

+ 79 - 0
src/main/java/com/wefanbot/winbot/Main.java

@@ -0,0 +1,79 @@
+package com.wefanbot.winbot;
+
+
+import com.wefanbot.winbot.util.ProUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * @author chenanlian
+ */
+public class Main {
+
+    private static final Logger log = LoggerFactory.getLogger(Main.class);
+
+    /**
+     * 控制主线程是否结束
+     */
+    public static volatile CountDownLatch countDownLatchMain = new CountDownLatch(1);
+
+    public static long lastTime = System.currentTimeMillis();
+
+    public static Frame f = null;
+
+
+    public static void main(String[] args) throws Exception {
+        try {
+            long startTime = System.currentTimeMillis();
+            log.info("args:{}", args);
+            String javaHome = System.getProperty("java.home");
+            log.info("java home is {}", javaHome);
+            //加载配置
+            initEnv(args);
+
+            CountDownLatch countDownLatchFrame = new CountDownLatch(1);
+            javax.swing.SwingUtilities.invokeLater(new Runnable() {
+                public void run() {
+                    f = new Frame(300, 100, 0);
+                    countDownLatchFrame.countDown();
+                }
+            });
+
+            try {
+                countDownLatchFrame.await();
+            } catch (InterruptedException e) {
+                log.info("初始化完毕");
+            }
+            f.setVisible(true);
+
+
+            log.info("【程序启动】注册已经完成。耗时={}。", System.currentTimeMillis() - startTime);
+
+            countDownLatchMain.await();
+            log.info("停止程序......");
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+
+
+    public static void initEnv(String[] args) {
+        log.info("Environment properties init start......");
+        Long envInitStart = System.currentTimeMillis();
+        for (String arg : args) {
+            if (arg.startsWith("server.profiles.active=")) {
+                ProUtil.innerEnv = false;
+                ProUtil.env = arg.substring(23);
+                log.info("启动参数配置环境:{}", ProUtil.env);
+                break;
+            }
+        }
+        ProUtil.init();
+        log.info("配置参数:{}", ProUtil.string());
+
+        log.info("Environment properties init end, cost time:{}ms.", System.currentTimeMillis() - envInitStart);
+    }
+
+}

+ 488 - 0
src/main/java/com/wefanbot/winbot/util/JsonUtil.java

@@ -0,0 +1,488 @@
+package com.wefanbot.winbot.util;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * @author chenanlian
+ */
+public class JsonUtil {
+
+    private static Logger log = LoggerFactory.getLogger(JsonUtil.class);
+
+    private static volatile ObjectMapper objectMapper;
+
+
+    /**
+     * 获取单例ObjectMapper
+     *
+     * @return
+     */
+    public static ObjectMapper getObjectMapper() {
+        if (objectMapper == null) {
+            synchronized (JsonUtil.class) {
+                if (objectMapper == null) {
+                    objectMapper = new ObjectMapper();
+                    objectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
+                    objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
+                    objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+                    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+                    objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
+
+                }
+            }
+        }
+        return objectMapper;
+    }
+
+
+    public static ObjectNode createObjectNode() {
+        return getObjectMapper().createObjectNode();
+    }
+
+    /**
+     * 对象转json字符串
+     *
+     * @param object
+     * @return
+     */
+    public static String entity2Json(Object object) {
+        try {
+            return getObjectMapper().writeValueAsString(object);
+        } catch (JsonProcessingException e) {
+            log.error(e.getMessage(), e);
+            return null;
+        }
+    }
+
+    /**
+     * 将json形式字符串转换为java实体类
+     */
+    public static <T> T str2Entity(String jsonStr, Class<T> clazz) {
+        T readValue = null;
+        try {
+            readValue = getObjectMapper().readValue(jsonStr, clazz);
+        } catch (JsonParseException e) {
+            log.error(e.getMessage(), e);
+        } catch (JsonMappingException e) {
+            log.error(e.getMessage(), e);
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+        }
+        return readValue;
+    }
+
+    public static <T, T1> T str2Entity(String jsonStr, Class<T> clazz, Class<T1> clazz2) {
+        T readValue = null;
+        try {
+            JavaType javaType1 = JsonUtil.getObjectMapper().getTypeFactory().constructParametricType(clazz, clazz2);
+            readValue = JsonUtil.getObjectMapper().readValue(jsonStr, javaType1);
+        } catch (JsonParseException e) {
+            log.error(e.getMessage(), e);
+        } catch (JsonMappingException e) {
+            log.error(e.getMessage(), e);
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+        }
+        return readValue;
+    }
+
+    /**
+     * jsonNode 转实体
+     *
+     * @param jsonNode
+     * @param clazz
+     * @param <T>
+     * @return
+     */
+    public static <T> T jsonNode2Entity(JsonNode jsonNode, Class<T> clazz) {
+        T readValue = null;
+        try {
+            readValue = getObjectMapper().convertValue(jsonNode, clazz);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        return readValue;
+    }
+
+    public static <T, T1> T jsonNode2Entity(JsonNode jsonStr, Class<T> clazz, Class<T1> clazz2) {
+        T readValue = null;
+        try {
+            JavaType javaType1 = JsonUtil.getObjectMapper().getTypeFactory().constructParametricType(clazz, clazz2);
+            readValue = getObjectMapper().convertValue(jsonStr, javaType1);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        return readValue;
+    }
+
+    /**
+     * 获取泛型的Collection Type
+     *
+     * @param collectionClass 泛型的Collection
+     * @param elementClasses  元素类
+     * @return JavaType Java类型
+     * @since 1.0
+     */
+    public static JavaType getCollectionType(Class<?> collectionClass, Class<?>... elementClasses) {
+        return getObjectMapper().getTypeFactory().constructParametricType(collectionClass, elementClasses);
+    }
+
+    /**
+     * json字符串转集合类
+     *
+     * @param jsonString
+     * @param collectionClass
+     * @param elementClasses
+     * @return
+     */
+    public static Object str2Collection(String jsonString, Class<?> collectionClass, Class<?>... elementClasses) {
+        JavaType javaType = getCollectionType(collectionClass, elementClasses);
+        try {
+            return getObjectMapper().readValue(jsonString, javaType);
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+            return null;
+        }
+    }
+
+    public static Object jsonNode2Collection(String node, Class<?> collectionClass, Class<?> elementClasses) {
+        JavaType javaType = getCollectionType(collectionClass, elementClasses);
+        try {
+            return getObjectMapper().convertValue(node, javaType);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return null;
+        }
+    }
+
+
+    /**
+     * jsonNode 转字符串并按字母排序
+     *
+     * @param node
+     * @return
+     */
+    public static String jsonNode2SortStr(final JsonNode node) {
+        Object obj;
+        String json = null;
+        try {
+            obj = getObjectMapper().treeToValue(node, Object.class);
+            json = getObjectMapper().writeValueAsString(obj);
+        } catch (JsonProcessingException e) {
+            log.error(e.getMessage(), e);
+        }
+        return json;
+    }
+
+    /**
+     * 字符串转JsonNode
+     *
+     * @param str
+     * @return
+     */
+    public static JsonNode str2JsonNode(String str) {
+        try {
+            return getObjectMapper().readTree(str);
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+            return null;
+        }
+
+    }
+
+    public static JsonNode entity2JsonNode(Object entity) {
+        return getObjectMapper().convertValue(entity, JsonNode.class);
+    }
+
+
+    public static JsonNode map2JsonNode(Map<String, String> map) {
+
+        ObjectNode jsonNodes = getObjectMapper().createObjectNode();
+        for (Map.Entry<String, String> entry : map.entrySet()) {
+            jsonNodes.put(entry.getKey(), entry.getValue());
+        }
+        return jsonNodes;
+    }
+
+    /**
+     * JsonNode转Map,只取第一层,值转为字符串
+     *
+     * @param jsonNode
+     * @return
+     */
+    public static Map<String, String> jsonNode2MapStr(JsonNode jsonNode) {
+        Map<String, String> map = new HashMap<>();
+        if (jsonNode.isObject()) {
+            Iterator<Map.Entry<String, JsonNode>> it = jsonNode.fields();
+            while (it.hasNext()) {
+                Map.Entry<String, JsonNode> entry = it.next();
+                map.put(entry.getKey(), entry.getValue().asText(null));
+            }
+        }
+        return map;
+    }
+
+    /**
+     * JsonNode转Map,只取第一层
+     *
+     * @param jsonNode
+     * @return
+     */
+    public static Map<String, Object> jsonNode2Map(JsonNode jsonNode) {
+        Map<String, Object> map = new HashMap<>();
+        if (jsonNode.isObject()) {
+            Iterator<Map.Entry<String, JsonNode>> it = jsonNode.fields();
+            while (it.hasNext()) {
+                Map.Entry<String, JsonNode> entry = it.next();
+                map.put(entry.getKey(), entry.getValue());
+            }
+        }
+        return map;
+    }
+
+    public static void jsonLeaf(JsonNode node) {
+        if (node.isValueNode()) {
+            System.out.println(node.toString());
+            return;
+        }
+
+        if (node.isObject()) {
+            Iterator<Map.Entry<String, JsonNode>> it = node.fields();
+            while (it.hasNext()) {
+                Map.Entry<String, JsonNode> entry = it.next();
+                jsonLeaf(entry.getValue());
+            }
+        }
+
+        if (node.isArray()) {
+            Iterator<JsonNode> it = node.iterator();
+            while (it.hasNext()) {
+                jsonLeaf(it.next());
+            }
+        }
+    }
+
+
+    public static List<String> getStrList(JsonNode jsonNode) {
+        List<String> listll = new ArrayList<>();
+        if (jsonNode.isArray()) {
+            for (JsonNode objNode : jsonNode) {
+                String value = objNode.asText();
+                listll.add(value);
+            }
+        }
+        return listll;
+    }
+
+    /**
+     * 将数据源jsonNode 按模板jsonNode输出sql语句列表
+     *
+     * @param data     数据来源
+     * @param template json 模板
+     * @return sql 语句列表
+     */
+    public static List<String> transform(JsonNode data, JsonNode template) {
+        List<String> sqlList = new ArrayList<>();
+        Iterator<String> tableIterator = template.fieldNames();
+        while (tableIterator.hasNext()) {
+            /*表*/
+            String tableName = tableIterator.next();
+            JsonNode tableNode = template.get(tableName);
+            Iterator<String> fieldIterator = tableNode.fieldNames();
+
+            StringBuilder sb = new StringBuilder();
+            StringBuilder sbv = new StringBuilder();
+            sb.append("INSERT INTO ");
+            sb.append(tableName);
+            sb.append(" (");
+            sbv.append(" VALUES (");
+
+            while (fieldIterator.hasNext()) {
+                /*字段*/
+                String fieldName = fieldIterator.next();
+                String fieldPath = tableNode.get(fieldName).asText();
+                JsonNode fileValue = findValueByPath(data, fieldPath);
+                /*去除没有值或取值错误的节点*/
+                if (null != fileValue && !fileValue.equals("")) {
+                    sb.append(fieldName);
+                    sb.append(",");
+                    sbv.append(fileValue);
+                    sbv.append(",");
+                }
+            }
+            sb.deleteCharAt(sb.length() - 1);
+            sbv.deleteCharAt(sbv.length() - 1);
+            sb.append(")");
+            sbv.append(")");
+            sb.append(sbv);
+
+            sqlList.add(sb.toString());
+        }
+        return sqlList;
+    }
+
+
+    /**
+     * 根据路径获取值
+     *
+     * @param jsonNode
+     * @param valuePath 节点路径
+     * @return
+     */
+    public static JsonNode findValueByPath(JsonNode jsonNode, String valuePath) {
+        String[] pathMutil = valuePath.split("\\.");
+        JsonNode jsonTem = jsonNode;
+        for (int i = 0; i < pathMutil.length; i++) {
+            jsonTem = jsonTem.path(pathMutil[i]);
+        }
+        return jsonTem;
+    }
+
+
+    /**
+     * 重组json
+     *
+     * @param jsonNode
+     * @param nodePath
+     * @return
+     */
+    public static JsonNode jsonNodeTransform(JsonNode jsonNode, List<String> nodePath) {
+        JsonNodeFactory factory = new JsonNodeFactory(false);
+        ObjectNode objectNode = factory.objectNode();
+        for (String path : nodePath) {
+            String[] pathMutil = path.split("\\.");
+            if (pathMutil.length > 1) {
+                JsonNode jsonTem = jsonNode;
+                for (int i = 0; i < pathMutil.length; i++) {
+                    jsonTem = jsonTem.path(pathMutil[i]);
+                }
+                objectNode.put(path, jsonTem);
+            } else {
+                JsonNode jsonNodeChild = jsonNode.path(path);
+                objectNode.put(path, jsonNodeChild);
+            }
+        }
+        return objectNode;
+    }
+
+    /**
+     * 获取字符串值
+     *
+     * @param jsonNode
+     * @param key
+     * @return
+     */
+    public static String getString(JsonNode jsonNode, String key) {
+        JsonNode value = jsonNode.get(key);
+        if (value == null) {
+            return null;
+        } else {
+            return value.asText().trim();
+        }
+    }
+
+    public static Integer getInteger(JsonNode jsonNode, String key) {
+        JsonNode value = jsonNode.get(key);
+        if (value == null) {
+            return null;
+        } else {
+            return value.asInt();
+        }
+    }
+
+    public static Integer getInteger(JsonNode jsonNode, String key, Integer defaultValue) {
+        JsonNode value = jsonNode.get(key);
+        if (value == null) {
+            return defaultValue;
+        } else {
+            return value.asInt();
+        }
+    }
+
+    public static Boolean getBoolean(JsonNode jsonNode, String key, Boolean defaultValue) {
+        JsonNode value = jsonNode.get(key);
+        if (value == null) {
+            return defaultValue;
+        } else {
+            return value.asBoolean();
+        }
+    }
+
+    /**
+     * 实体对象转成Map
+     *
+     * @param obj 实体对象
+     * @return
+     */
+    public static Map<String, Object> object2Map(Object obj) {
+        Map<String, Object> map = new HashMap<String, Object>();
+        if (obj == null) {
+            return map;
+        }
+        Class clazz = obj.getClass();
+        Field[] fields = clazz.getDeclaredFields();
+        try {
+            for (Field field : fields) {
+                field.setAccessible(true);
+                map.put(field.getName(), field.get(obj));
+            }
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        return map;
+    }
+
+    /**
+     * Map转成实体对象
+     *
+     * @param map   map实体对象包含属性
+     * @param clazz 实体对象类型
+     * @return
+     */
+    public static <T> T map2Object(Map<String, Object> map, Class<T> clazz) {
+        if (map == null) {
+            return null;
+        }
+        T obj = null;
+        try {
+            obj = clazz.newInstance();
+
+            Field[] fields = obj.getClass().getDeclaredFields();
+            for (Field field : fields) {
+                int mod = field.getModifiers();
+                if (Modifier.isStatic(mod) || Modifier.isFinal(mod)) {
+                    continue;
+                }
+                field.setAccessible(true);
+                String filedTypeName = field.getType().getName();
+                if (filedTypeName.equalsIgnoreCase("java.util.date")) {
+                    String datetimestamp = String.valueOf(map.get(field.getName()));
+                    if (datetimestamp.equalsIgnoreCase("null")) {
+                        field.set(obj, null);
+                    } else {
+                        field.set(obj, new Date(Long.parseLong(datetimestamp)));
+                    }
+                } else {
+                    field.set(obj, map.get(field.getName()));
+                }
+            }
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        return obj;
+    }
+}
+

+ 257 - 0
src/main/java/com/wefanbot/winbot/util/ProUtil.java

@@ -0,0 +1,257 @@
+package com.wefanbot.winbot.util;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author chenanlian
+ * <p>
+ * 配置文件加载类
+ */
+public class ProUtil {
+
+    private final static Logger log = LoggerFactory.getLogger(ProUtil.class);
+
+    /**
+     * 是否使用的是application.properties配置的环境,如果是由java -jar 启动,并且传了server.profiles.active=dev 参数
+     * 则为false;dev可以换成其他环境后缀{env},配置文件名为application_{env}.properties
+     */
+    public static boolean innerEnv = true;
+
+    /**
+     * 环境标识
+     */
+    public static String env = "dev";
+
+    /**
+     * 项目的上一层目录
+     */
+    public final static String userDir = System.getProperty("user.dir") + File.separator;
+
+    /**
+     * 配置文件加载进来的key-value
+     */
+    public static Map<String, String> map = new ConcurrentHashMap<>();
+
+    /**
+     * 配置文件的加载顺序为:
+     * 1.resources目录下的共同配置文件application.properties。
+     * 2.放在与jar包同一个目录下的共同配置文件,application.properties。
+     * 3.如果启动参数传入了指定的环境。如java -jar server.profiles.active=prod,则环境指定为了prod。
+     * 否则,则读取共同配置文件application.properties中配置的环境,如配置:server.profiles.active=prod。
+     * 如果都没有配置,则环境默认为dev,将默认使用application_dev.properties配置文件。
+     * 4.后面配置的值如果key与前面已经加载的相同,则覆盖之。即四个配置文件的优先顺序为:
+     * a.jar包外的指定环境配置文件,如application_dev.properties
+     * b.jar包内的指定环境配置文件,如application_dev.properties
+     * c.jar包外的共同配置文件,application.properties
+     * d.jar包内的共同配置文件,application.properties
+     */
+    public static void init() {
+        /**
+         * jar包内的共同配置文件,application.properties
+         */
+        Properties innerPro = loadInnerProperties("/application.properties");
+        if (null != innerPro) {
+            iteratorProperties(innerPro);
+            log.info("jar包内application.properties加载完毕.");
+        }
+
+        /**
+         * jar包外的共同配置文件,application.properties
+         */
+
+        Properties OutterPro = loadOutterProperties(userDir + "application.properties");
+        if (null != OutterPro) {
+            iteratorProperties(OutterPro);
+            log.info("jar包外application.properties加载完毕.");
+        }
+
+        /**
+         * 如果启动参数没有传入指定的环境,则取配置文件中配置的环境,如果同样没有,则取默认值:dev
+         */
+        if (innerEnv) {
+            ProUtil.map.put("env", ProUtil.getString("server.profiles.active", "dev"));
+            env = map.get("server.profiles.active");
+        }
+
+        /**
+         * jar包内的指定环境配置文件,如application_dev.properties
+         */
+        String envProperties = "/application_" + ProUtil.env + ".properties";
+        Properties innerProEnv = loadInnerProperties(envProperties);
+        if (null != innerProEnv) {
+            iteratorProperties(innerProEnv);
+            log.info("jar包内{}加载完毕.", envProperties);
+        }
+
+
+        /**
+         * jar包外的指定环境配置文件,如application_dev.properties
+         */
+        Properties outterProEnv = loadOutterProperties(userDir + envProperties);
+        if (null != outterProEnv) {
+            iteratorProperties(outterProEnv);
+            log.info("jar包外{}加载完毕.", envProperties);
+        }
+
+    }
+
+    /**
+     * 根据key获得字符串值
+     *
+     * @param key
+     * @return
+     */
+    public static String getString(String key) {
+        return map.get(key);
+    }
+
+    /**
+     * 根据key获得整型值
+     *
+     * @param key
+     * @return
+     */
+    public static Integer getInteger(String key) {
+        String result = getString(key).trim();
+        if (StringUtils.isNotBlank(result)) {
+            return Integer.valueOf(result);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * 根据key获得整型值
+     *
+     * @param key
+     * @return
+     */
+    public static Long getLong(String key) {
+        String result = getString(key).trim();
+        if (StringUtils.isNotBlank(result)) {
+            return Long.valueOf(result);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * 根据key获得字符串值
+     *
+     * @param key
+     * @param defaultValue 当缺失时返回的的默认值
+     * @return
+     */
+    public static String getString(String key, String defaultValue) {
+        String result = map.get(key);
+        if (result == null) {
+            return defaultValue;
+        } else {
+            return result;
+        }
+    }
+
+    /**
+     * 根据key获得整型值
+     *
+     * @param key
+     * @param defaultValue 当缺失时返回的的默认值
+     * @return
+     */
+    public static Integer getInteger(String key, Integer defaultValue) {
+        String result = getString(key, String.valueOf(defaultValue)).trim();
+        if (StringUtils.isNotBlank(result)) {
+            return Integer.valueOf(result);
+        } else {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * 加载放在项目内resources目录下的配置文件
+     *
+     * @param filePath
+     * @return
+     */
+    private static Properties loadInnerProperties(String filePath) {
+        InputStream is = null;
+        try {
+            Properties properties = new Properties();
+            is = ProUtil.class.getClass().getResourceAsStream(filePath);
+            properties.load(is);
+            return properties;
+        } catch (Exception e) {
+            return null;
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                    log.error(e.getMessage(), e);
+                }
+            }
+        }
+    }
+
+    /**
+     * 加载与jar包同一目录下的配置文件
+     *
+     * @param filePath
+     * @return
+     */
+    private static Properties loadOutterProperties(String filePath) {
+        FileInputStream fis = null;
+        InputStreamReader isr = null;
+        try {
+            Properties properties = new Properties();
+            fis = new FileInputStream(filePath);
+            isr = new InputStreamReader(fis, "UTF-8");
+            properties.load(isr);
+            return properties;
+        } catch (Exception e) {
+            return null;
+        } finally {
+            if (fis != null) {
+                try {
+                    fis.close();
+                } catch (IOException e) {
+                    log.error(e.getMessage(), e);
+                }
+            }
+            if (isr != null) {
+                try {
+                    isr.close();
+                } catch (IOException e) {
+                    log.error(e.getMessage(), e);
+                }
+            }
+        }
+    }
+
+    /**
+     * 遍历Properties并复制进map变量中
+     *
+     * @param properties
+     */
+    private static void iteratorProperties(Properties properties) {
+        Iterator<Map.Entry<Object, Object>> it = properties.entrySet().iterator();
+        while (it.hasNext()) {
+            Map.Entry<Object, Object> entry = it.next();
+            Object key = entry.getKey();
+            Object value = entry.getValue();
+            map.put(key.toString(), value != null ? value.toString() : null);
+        }
+    }
+
+    public static String string() {
+        return JsonUtil.entity2Json(map);
+    }
+}

+ 1 - 0
src/main/resources/application.properties

@@ -0,0 +1 @@
+server.profiles.active=local

+ 0 - 0
src/main/resources/application_local.properties


+ 0 - 0
src/main/resources/application_prod.properties


+ 0 - 0
src/main/resources/application_test.properties


+ 173 - 0
src/main/resources/logback.xml

@@ -0,0 +1,173 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
+<!-- scan:当此属性设置为true时,配置文档如果发生改变,将会被重新加载,默认值为true -->
+<!-- scanPeriod:设置监测配置文档是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。
+                 当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
+<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
+<!-- appender定义文件输出的级别; logger 指定包或类打印级别;root指定全局打印级别,优先级低于logger。-->
+<!-- logger和root的配置级别不应该低于appender否则,也不会输出。logger的级别不应该高于root,否则没意义,因为一定会输出-->
+
+<configuration scan="true" scanPeriod="60 second" debug="false">
+
+    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义后,可以使“${}”来使用变量。 -->
+    <property name="app-group" value="wefanbot" />
+    <property name="app-name" value="winbot" />
+    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义后,可以使“${}”来使用变量。 -->
+    <property name="LOG_HOME" value="./logs" />
+    <property name="PATTERN" value="[%date{ISO8601}][%level][${app-group}][${app-name}][%X{traceid}][%X{userId}][%thread][%logger{80}][%line] - %.-3024msg%n" />
+
+    <!--1. 输出到控制台-->
+    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
+        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+            <level>DEBUG</level>
+        </filter>
+        <encoder>
+            <Pattern>${PATTERN}</Pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+
+    <!--2. 输出到文档-->
+    <!-- 2.1 level为 DEBUG 日志,时间滚动输出  -->
+    <appender name="DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!-- 正在记录的日志文档的路径及文档名 -->
+        <file>${LOG_HOME}/debug.log</file>
+        <!--日志文档输出格式-->
+        <encoder>
+            <Pattern>${PATTERN}</Pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志归档 -->
+            <fileNamePattern>${LOG_HOME}/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <maxFileSize>512MB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+            <!--日志文档保留天数-->
+            <maxHistory>7</maxHistory>
+        </rollingPolicy>
+        <!-- append: true,日志被追加到文件结尾; false,清空现存文件;默认是true -->
+        <append>true</append>
+        <!-- 此日志文档只记录debug级别的 -->
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>debug</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <!-- 2.2 level为 INFO 日志,时间滚动输出  -->
+    <appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <File>${LOG_HOME}/info.log</File>
+        <encoder>
+            <Pattern>${PATTERN}</Pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 每天日志归档路径以及格式 -->
+            <fileNamePattern>${LOG_HOME}/info.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <maxFileSize>512MB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+            <!--日志文档保留天数-->
+            <maxHistory>15</maxHistory>
+        </rollingPolicy>
+        <!-- ThresholdFilter:临界值过滤器,过滤掉 TRACE 和 DEBUG 级别的日志 -->
+        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+            <level>INFO</level>
+        </filter>
+    </appender>
+    <!-- 2.3 level为 WARN 日志,时间滚动输出  -->
+    <appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${LOG_HOME}/warn.log</file>
+        <encoder>
+            <Pattern>${PATTERN}</Pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${LOG_HOME}/warn.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <maxFileSize>512MB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+            <!--日志文档保留天数-->
+            <maxHistory>20</maxHistory>
+        </rollingPolicy>
+        <!-- 此日志文档只记录warn级别的 -->
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>warn</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${LOG_HOME}/error.log</file>
+        <encoder>
+            <Pattern>${PATTERN}</Pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${LOG_HOME}/error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <maxFileSize>512MB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+            <!--日志文档保留天数-->
+            <maxHistory>30</maxHistory>
+        </rollingPolicy>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>ERROR</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <!-- 异步输出 -->
+    <!--	<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">-->
+    <!--		&lt;!&ndash; 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 &ndash;&gt;-->
+    <!--		<discardingThreshold>0</discardingThreshold>-->
+    <!--		&lt;!&ndash; 更改默认的队列的深度,该值会影响性能.默认值为256 &ndash;&gt;-->
+    <!--		<queueSize>512</queueSize>-->
+    <!--		&lt;!&ndash; 添加附加的appender,最多只能添加一个 &ndash;&gt;-->
+    <!--		<appender-ref ref="ERROR" />-->
+    <!--	</appender>-->
+
+    <!--
+        <logger>用来设置某一个包或者具体的某一个类的日志打印级别、
+        以及指定<appender>。<logger>仅有一个name属性,
+        一个可选的level和一个可选的addtivity属性。
+        name:用来指定受此logger约束的某一个包或者具体的某一个类。
+        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
+              还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
+              如果未设置此属性,那么当前logger将会继承上级的级别。
+        addtivity:是否向上级logger传递打印信息。默认是true。
+        <logger name="org.springframework.web" level="info"/>
+        <logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>
+    -->
+    <!-- hibernate logger -->
+
+
+
+    <!--
+            使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
+            第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
+            第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
+            【logging.level.org.mybatis=debug logging.level.dao=debug】
+         -->
+    <!--
+            root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
+            level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
+            不能设置为INHERITED或者同义词NULL。默认是DEBUG
+            可以包含零个或多个元素,标识这个appender将会添加到这个logger。
+        -->
+
+    <root level="DEBUG" additivity="false">
+        <appender-ref ref="CONSOLE" />
+        <appender-ref ref="DEBUG" />
+        <appender-ref ref="INFO" />
+        <appender-ref ref="WARN" />
+        <appender-ref ref="ERROR"/>
+    </root>
+</configuration>

+ 6 - 0
src/main/resources/rpa-node-start-local.bat

@@ -0,0 +1,6 @@
+@echo off
+title excel
+echo start...
+chcp 65001
+java -Dfile.encoding=utf-8 -Xms512m -Xmx1g -Xmn512m -jar winbot-1.0.0.jar server.profiles.active=local
+echo exit...