Jelajahi Sumber

创建项目

lhs 5 tahun lalu
melakukan
9aa916c9c7

+ 133 - 0
.gitignore

@@ -0,0 +1,133 @@
+# Created by .ignore support plugin (hsz.mobi)
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff:
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/dictionaries
+
+# Sensitive or high-churn files:
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.xml
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+
+# Gradle:
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# CMake
+cmake-build-debug/
+
+# Mongo Explorer plugin:
+.idea/**/mongoSettings.xml
+
+## File-based project format:
+*.iws
+
+## Plugin-specific files:
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+### Java template
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+### Eclipse template
+
+.metadata
+bin/
+tmp/
+*.tmp
+*.bak
+*.swp
+*~.nib
+local.properties
+.settings/
+.loadpath
+.recommenders
+
+# External tool builders
+.externalToolBuilders/
+
+# Locally stored "Eclipse launch configurations"
+*.launch
+
+# PyDev specific (Python IDE for Eclipse)
+*.pydevproject
+
+# CDT-specific (C/C++ Development Tooling)
+.cproject
+
+# Java annotation processor (APT)
+.factorypath
+
+# PDT-specific (PHP Development Tools)
+.buildpath
+
+# sbteclipse plugin
+.target
+
+# Tern plugin
+.tern-project
+
+# TeXlipse plugin
+.texlipse
+
+# STS (Spring Tool Suite)
+.springBeans
+
+# Code Recommenders
+.recommenders/
+
+# Scala IDE specific (Scala & Java development for Eclipse)
+.cache-main
+.scala_dependencies
+.worksheet
+### Example user template template
+### Example user template
+
+# IntelliJ project files
+.idea
+*.iml
+out
+gen

+ 26 - 0
README-DEPLOY.md

@@ -0,0 +1,26 @@
+# 互联网+海关模拟登录查询项目部署
+
+## 环境配置
+* application.yml的修改:正式站点的每个active里面的去掉test
+
+## 互联网+海关表单查询接口地址变更
+* 后期【互联网+海关】相关地址有变化,请修改application.yml的配置
+
+
+
+## 上线打包的流程:
+1. cus_reader_card的打包
+    + 在Maven Projects中找到cus_reader_card
+    + 打开Lifecycle,点击clean运行,之后点击package运行
+
+2. 相应类结构和路径
+    + cus_reader_card的打包路径:cus_reader_card\target\cus_reader_card
+    + 打开WEB-INF文件,将lib文件名改成dependency,并压缩cus_reader_card
+    + cus_reader_card.zip包里面包含的文件夹目录结构:
+    ```
+    -META-INF
+    -WEB-INF
+        -classes
+        -dependency
+    ```
+        

+ 3 - 0
README.md

@@ -0,0 +1,3 @@
+# 互联网+海关模拟登录查询项目
+
+## 跨境电商进口清单查询

+ 150 - 0
build.gradle

@@ -0,0 +1,150 @@
+// Gradle plugin
+plugins {
+    id 'org.springframework.boot' version '2.2.5.RELEASE'
+    id 'io.spring.dependency-management' version '1.0.9.RELEASE'
+}
+
+
+
+apply plugin: 'java'
+apply plugin: 'idea'
+
+apply plugin: 'war'
+///Spring Boot
+apply plugin: "org.springframework.boot"
+//提供类似Maven dependencyManagement依赖管理功能
+apply plugin: 'io.spring.dependency-management'
+
+war {
+    archiveBaseName = 'cus_reader_card'
+}
+
+group = 'com.emato'
+version = '1.0.0'
+
+
+sourceCompatibility = 1.8
+targetCompatibility = 1.8
+
+
+// 指定编码,能防止中文乱码
+tasks.withType(JavaCompile) {
+    options.encoding = "UTF-8"
+}
+
+// tag::repositories[]
+repositories {
+    /*本地仓库,无法下载source jar包*/
+    // mavenLocal()
+    /*JCenter中央仓库,maven搭建,支持https*/
+    jcenter()
+    /*远程中央仓库,依赖maven定义的中央仓库*/
+    mavenCentral()
+    maven {url 'http://central.maven.org/maven2'}
+    maven {url 'https://plugins.gradle.org/m2/'}
+    /* 配置使用nexus 私服 */
+    maven {
+        url 'http://nexus.ds-bay.com/content/repositories/releases'
+        credentials {
+            username 'admin'
+            password 'admin123'
+        }
+    }
+    //maven {url 'others url'}
+}
+
+
+
+sourceSets {
+    main{
+        java{
+            srcDir 'src/main/java'
+        }
+        resources {
+            srcDir 'src/main/resources'
+        }
+    }
+}
+
+
+ext {
+    junit = '4.12'
+    slf4j = '1.7.25'
+
+    spring_boot = '2.2.5.RELEASE'
+    spring = '5.2.4.RELEASE'
+
+    javax_servlet = '4.0.1'
+
+
+    logback_ext_spring = '0.1.4'
+
+    jackson = '2.9.8'
+    google_guava = '23.3-jre'
+    cage = '1.0'
+    okhttp = '3.10.0'
+
+    // Hutool
+    hutool = '4.5.6'
+
+    // fastjson
+    fastjson = '1.2.58'
+
+    commons_beanutils = '1.9.3'
+    commons_io = '2.6'
+    commons_codec = '1.13'
+    commons_lang3 = '3.6'
+
+    commons_pool2 = '2.6.2'
+    apache_httpclient = '4.5.4'
+
+    jsoup = '1.13.1'
+
+    logback_ext_spring = '0.1.4'
+
+    java_websocket = '1.3.9'
+}
+
+
+dependencies {
+
+    testImplementation("junit:junit:${junit}")
+
+    implementation('org.springframework.boot:spring-boot-starter-tomcat')
+
+    implementation('org.springframework.boot:spring-boot-starter-web')
+
+    implementation('org.springframework.boot:spring-boot-starter-data-rest')
+
+    annotationProcessor('org.springframework.boot:spring-boot-configuration-processor')
+
+    implementation("com.alibaba:fastjson:${fastjson}")
+
+    // 数据序列化
+    // 为保证引入 jackson包版本一致性,不使用到其它低版本包, 引入如下三个依赖
+    implementation("com.fasterxml.jackson.core:jackson-databind:${jackson}")
+    implementation("com.fasterxml.jackson.core:jackson-core:${jackson}")
+    implementation("com.fasterxml.jackson.core:jackson-annotations:${jackson}")
+    // jackson 支持格式化LocalDateTime
+    implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:${jackson}")
+    implementation("com.fasterxml.jackson.datatype:jackson-datatype-joda:${jackson}")
+    implementation("com.fasterxml.jackson.module:jackson-module-afterburner:${jackson}")
+    // jackson 支持格式化XML
+    implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:${jackson}")
+
+
+    implementation("com.squareup.okhttp3:okhttp:${okhttp}")
+
+    implementation("org.apache.commons:commons-lang3:${commons_lang3}")
+    implementation("commons-beanutils:commons-beanutils:${commons_beanutils}")
+
+    implementation("org.apache.httpcomponents:httpclient:${apache_httpclient}")
+    implementation("org.apache.httpcomponents:httpmime:${apache_httpclient}")
+
+    implementation("org.jsoup:jsoup:${jsoup}")
+
+    implementation("org.logback-extensions:logback-ext-spring:${logback_ext_spring}")
+
+    implementation("org.java-websocket:Java-WebSocket:${java_websocket}")
+
+}

+ 1 - 0
settings.gradle

@@ -0,0 +1 @@
+rootProject.name = 'cus_reader_card'

+ 24 - 0
src/main/java/com/emato/cus/CusReaderCardApplication.java

@@ -0,0 +1,24 @@
+package com.emato.cus;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+
+@SpringBootApplication(
+	scanBasePackages = {
+		"com.emato.cus"
+	}
+)
+public class CusReaderCardApplication
+		extends SpringBootServletInitializer {
+
+	@Override
+	protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
+		return application.sources(CusReaderCardApplication.class);
+	}
+
+	public static void main(String[] args) {
+		SpringApplication.run(CusReaderCardApplication.class, args);
+	}
+}

+ 52 - 0
src/main/java/com/emato/cus/config/CusCardConfiguration.java

@@ -0,0 +1,52 @@
+package com.emato.cus.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+@Component
+public class CusCardConfiguration {
+
+    @Value("${cus.card.url}")
+    private String cusCardUrl;
+
+    @Value("${cus.card.password}")
+    private String cusCardPassword;
+
+    @Value("${cus.login.url}")
+    private String cusLoginUrl;
+
+    @Value("${cus.inveQueryUrl}")
+    private String inveQueryUrl;
+
+    public String getCusCardUrl() {
+        return cusCardUrl;
+    }
+
+    public void setCusCardUrl(String cusCardUrl) {
+        this.cusCardUrl = cusCardUrl;
+    }
+
+    public String getCusCardPassword() {
+        return cusCardPassword;
+    }
+
+    public void setCusCardPassword(String cusCardPassword) {
+        this.cusCardPassword = cusCardPassword;
+    }
+
+    public String getCusLoginUrl() {
+        return cusLoginUrl;
+    }
+
+    public void setCusLoginUrl(String cusLoginUrl) {
+        this.cusLoginUrl = cusLoginUrl;
+    }
+
+    public String getInveQueryUrl() {
+        return inveQueryUrl;
+    }
+
+    public void setInveQueryUrl(String inveQueryUrl) {
+        this.inveQueryUrl = inveQueryUrl;
+    }
+}

+ 77 - 0
src/main/java/com/emato/cus/config/HttpMsgConverter.java

@@ -0,0 +1,77 @@
+package com.emato.cus.config;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.StringHttpMessageConverter;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * HTTP 请求响应消息转换
+ */
+@Configuration
+public class HttpMsgConverter {
+
+    @Autowired
+    ObjectMapper objectMapper;
+
+    /**
+     * 扩展现有的消息转换器链表
+     * 也可以继承并重写{@link org.springframework.web.servlet.config.annotation.WebMvcConfigurer#configureMessageConverters}方法
+     * @return
+     */
+    @Bean
+    public HttpMessageConverters httpMessageConverters() {
+        List<HttpMessageConverter<?>> list = new ArrayList<>();
+        list.add(httpMessageConverter());
+        list.add(mappingJackson2HttpMessageConverter());
+        return new HttpMessageConverters(list);
+    }
+
+    /**
+     * jackson进行消息转换时,处理中文乱码
+     * @return
+     */
+    @Bean
+    public HttpMessageConverter httpMessageConverter() {
+        StringHttpMessageConverter messageConverter = new StringHttpMessageConverter();
+        messageConverter.setSupportedMediaTypes(mediaTypes());
+        messageConverter.setDefaultCharset(Charset.forName("UTF-8"));
+        return messageConverter;
+    }
+
+    /**
+     * 处理中文乱码
+     * @return
+     */
+    public List<MediaType> mediaTypes() {
+        //处理中文乱码
+        List<MediaType> list = new LinkedList();
+        list.add(MediaType.TEXT_HTML);
+        list.add(MediaType.APPLICATION_JSON_UTF8);
+        return list;
+    }
+
+    /**
+     * HTTP 请求消息转换器
+     * 处理Date日期类型,处理LocalDateTime日期类型
+     * 使用<code>MappingJackson2HttpMessageConverter</code>类
+     * @return
+     */
+    @Bean
+    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
+        MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
+        messageConverter.setObjectMapper(objectMapper);
+        messageConverter.setSupportedMediaTypes(mediaTypes());
+        return messageConverter;
+    }
+}

+ 38 - 0
src/main/java/com/emato/cus/config/WebMvcConfiguration.java

@@ -0,0 +1,38 @@
+package com.emato.cus.config;
+
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.filter.CharacterEncodingFilter;
+import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler;
+
+@Configuration
+public class WebMvcConfiguration {
+
+    /**
+     * 编码过滤器
+     * @return
+     */
+    @Bean
+    public FilterRegistrationBean myCharacterEncodingFilter() {
+        CharacterEncodingFilter filter = new CharacterEncodingFilter();
+        filter.setEncoding("UTF-8");
+        filter.setForceEncoding(true);
+
+        FilterRegistrationBean filterBean = new FilterRegistrationBean();
+        filterBean.setFilter(filter);
+        filterBean.addUrlPatterns("/*");
+        return filterBean;
+    }
+
+    /**
+     * 对进入DispatcherServlet的URL进行筛查
+     * 如果发现是静态资源的请求,就将该请求转由Web应用服务器默认的Servlet处理
+     * 如果不是静态资源的请求,才由DispatcherServlet继续处理
+     * @return
+     */
+    @Bean
+    public DefaultServletHttpRequestHandler defaultServletHttpRequestHandler() {
+        return new DefaultServletHttpRequestHandler();
+    }
+}

+ 254 - 0
src/main/java/com/emato/cus/controller/ReaderCardController.java

@@ -0,0 +1,254 @@
+package com.emato.cus.controller;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.emato.cus.config.CusCardConfiguration;
+import com.emato.cus.entity.ResponseMsg;
+import com.emato.cus.utils.HttpUtil;
+import com.emato.cus.utils.JacksonUtils;
+import com.emato.cus.utils.websocket.Callback;
+import com.emato.cus.utils.websocket.WebSocketClientHandle;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.Header;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.message.BasicNameValuePair;
+import org.jsoup.Connection;
+import org.jsoup.Jsoup;
+import org.jsoup.Connection.Response;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 读取操作员卡数据,获取互联网+海关登录信息
+ */
+@RestController
+public class ReaderCardController {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(ReaderCardController.class);
+
+    @Autowired
+    private CusCardConfiguration cusCardConfiguration;
+
+    private static CloseableHttpClient httpClient;
+
+    private static Object object;
+
+    // 记录历史登录信息
+    private static StringBuilder cookieStr = new StringBuilder();
+
+    @RequestMapping("/readCusCard")
+    public ResponseMsg readCusCard() {
+        // 读取互联网+海关的配置
+        String url = cusCardConfiguration.getCusLoginUrl();
+        String wssUrl = cusCardConfiguration.getCusCardUrl();
+        String password = cusCardConfiguration.getCusCardPassword();
+
+        // 判断登录信息是否失效
+        if(checkCookie(cookieStr.toString())) {
+            try {
+                httpClient = HttpClients.createDefault();
+                cookieStr.delete(0, cookieStr.length());
+
+                //-------------------- 第一次请求登陆页面,获取页面信息(表单信息和cookie) --------------------
+                Connection con = Jsoup.connect(url); // 获取连接
+                con.header("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:29.0) Gecko/20100101 Firefox/29.0"); // 配置模拟浏览器
+                Response rs = con.execute(); // 获取响应
+
+                Document d1 = Jsoup.parse(rs.body()); // 转换为Dom树
+                List<Element> et = d1.select("#fm1"); // 获取登录form表单
+                String random = ""; // 获取表单随机码
+
+                // 获取页面已生成的随机码
+                for (Element e : et.get(0).getAllElements()) {
+                    if (e.attr("name").equals("random")) {
+                        random = e.attr("value");
+                    }
+                }
+
+                //-------------------- 处理客户端交互业务 --------------------
+                // 获取签名信息方法
+                String content = "{\"_method\":\"security_SpcSignRandom\",\"_id\":1,\"args\":{\"passwd\":\"" + password + "\",\"random\":\"" + random + "\"}}";
+                object = null;
+
+                // 处理客户端wss交互
+                WebSocketClientHandle.send(
+                    wssUrl,
+                    content,
+                    new Callback() {
+                        @Override
+                        public Object run(Object params) {
+                            LOGGER.info("连接客户端响应信息:" + params);
+                            object = params;
+                            return null;
+                        }
+                    },
+                    new Callback() {
+                        @Override
+                        public Object run(Object params) {
+                            Exception e = (Exception) params;
+                            e.printStackTrace();
+                            return null;
+                        }
+                    }
+                );
+
+                // 等待5秒后执行以下操作
+                Thread.sleep(5000);
+
+                // 读取操作员卡响应信息
+                JSONObject json = JSON.parseObject(object.toString());
+                json = JSON.parseObject(json.getString("_args"));
+
+                // 判断是否读取成功
+                if (!json.getBoolean("Result")) {
+                    String errMsg = json.getString("Error").split(",")[0];
+                    return new ResponseMsg(ResponseMsg.ERROR_CODE, errMsg.substring(2, errMsg.length() - 1));
+                }
+
+                // 获取操作员卡信息
+                StringBuilder data = new StringBuilder();
+                data.append(json.getString("Data"));
+                String[] dataArr = data.substring(1, data.indexOf("]")).split(",");
+
+                // 获取操作员卡数据
+                String info = dataArr[1].replace("\"", "");
+                String[] infoArr = info.split("\\|\\|");
+
+
+                //-------------------- 第二次请求,提交表单数据业务逻辑 --------------------
+                // 设置登录请求参数
+                List<NameValuePair> params = new ArrayList<>();
+                for (Element e : et.get(0).getAllElements()) {
+                    if (e.attr("name").equals("icCard")) {
+                        e.attr("value", infoArr[5]); // 设置操作员卡编号
+                    }
+                    if (e.attr("name").equals("certNo")) {
+                        e.attr("value", infoArr[0]); // 设置操作员卡认证编号
+                    }
+                    if (e.attr("name").equals("signData")) {
+                        e.attr("value", dataArr[0].replace("\"", "")); // 设置签名
+                    }
+                    if (e.attr("name").equals("userPin")) {
+                        e.attr("value", password); // 设置操作员卡密码
+                    }
+
+                    // 排除空值表单属性
+                    if (e.attr("name").length() > 0) {
+                        params.add(new BasicNameValuePair(e.attr("name"), e.attr("value")));
+                    }
+                }
+
+                // 获取页面cookie信息
+                String cookie = rs.cookies().toString();
+                cookie = cookie.substring(1, cookie.indexOf("}"));
+
+                CloseableHttpResponse response = null;
+                try {
+                    // HttpClient的Post请求类
+                    HttpPost post = new HttpPost(url);
+                    post.setHeader("Cookie", cookie);
+
+                    // params是包装请求参数的List
+                    if (params != null) {
+                        post.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
+                    }
+                    // 执行请求用execute方法
+                    response = httpClient.execute(post);
+
+                    // 打印响应的状态码
+                    if (response.getStatusLine().getStatusCode() == 302) {
+                        Header firstHeader = response.getFirstHeader("Location");
+                        LOGGER.info("执行登录请求,响应状态码:" + response.getStatusLine().getStatusCode() + ",重定向地址:" + firstHeader.getValue());
+
+                        // 判断是否重定向
+                        if (firstHeader.getValue() != null) {
+                            // 重定向请求
+                            post = new HttpPost(firstHeader.getValue());
+                            response = httpClient.execute(post);
+
+                            // 解析重定向地址响应,获取请求头Cookie信息
+                            Header[] headerArr = response.getAllHeaders();
+                            for (Header header : headerArr) {
+                                if (header.getName().equals("Set-Cookie")) {
+                                    cookieStr.append(header.getValue().split(";")[0] + ";");
+                                }
+                            }
+                        }
+                        LOGGER.info("获取互联网+海关登录信息:" + cookieStr);
+                    }
+                } catch (IOException e) {
+                    e.printStackTrace();
+                    return new ResponseMsg(ResponseMsg.ERROR_CODE, "调用互联网+海关接口异常!");
+                } finally {
+                    if (response != null) {
+                        try {
+                            response.close();
+                        } catch (IOException e) {
+                            e.printStackTrace();
+                        }
+                    }
+                }
+            } catch (Exception e) {
+                LOGGER.info("读取操作员卡异常!");
+                e.printStackTrace();
+                return new ResponseMsg(ResponseMsg.ERROR_CODE, "读取操作员卡异常!");
+            }
+        } else {
+            LOGGER.info("获取上一次登录信息:" + cookieStr);
+        }
+
+        if(StringUtils.isNotBlank(cookieStr.toString())) {
+            return new ResponseMsg(ResponseMsg.SUCCESS_CODE, cookieStr.toString());
+        }
+        return new ResponseMsg(ResponseMsg.ERROR_CODE, "互联网+海关登录失败,请重试!");
+    }
+
+    /**
+     * 校验登录cookie是否有效
+     *
+     * @param cookie
+     * @return
+     */
+    public boolean checkCookie(String cookie) {
+        //-------------------- 模拟清单回执表单查询操作 --------------------
+        // 查询参数
+        Map<String, Object> map = new HashMap<>();
+        map.put("formCondition", "{\"invtNo\":\"53492020I121054368\",\"sysDateFrom\":\"2020-02-01 00:00:00\",\"sysDateTo\":\"2020-03-05 23:59:59\"}");
+        map.put("limit", 10);
+        map.put("offset", 0);
+        map.put("order", "asc");
+        map.put("queryArea", "QUERY");
+        String requestParams = JSONObject.toJSONString(map);
+
+        boolean ckFlag = true;
+        try {
+            // 调用清单回执查询接口
+            String result = HttpUtil.postRequet(cusCardConfiguration.getInveQueryUrl(), requestParams, cookie);
+            LOGGER.info("调用【互联网+海关】查询清单接口响应:" + result);
+
+            if(StringUtils.isNotBlank(result) && JacksonUtils.isJson(result)) {
+                ckFlag = false;
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return ckFlag;
+    }
+}

+ 35 - 0
src/main/java/com/emato/cus/controller/WebSocketClientTest.java

@@ -0,0 +1,35 @@
+package com.emato.cus.controller;
+
+import com.emato.cus.utils.websocket.Callback;
+import com.emato.cus.utils.websocket.WebSocketClientHandle;
+import org.java_websocket.client.WebSocketClient;
+
+import java.nio.channels.NotYetConnectedException;
+
+public class WebSocketClientTest {
+
+    public static WebSocketClient client;
+
+    public static void main(String[] args) throws NotYetConnectedException {
+        String content = "{\"_method\":\"security_SpcSignRandom\",\"_id\":1,\"args\":{\"passwd\":\"88888888\",\"random\":\"123\"}}";
+
+        WebSocketClientHandle.send(
+            "wss://wss.singlewindow.cn:61231",
+            content,
+            new Callback() {
+                @Override
+                public Object run(Object params) {
+                    System.out.println(params);
+                    return null;
+                }
+            }, new Callback() {
+                @Override
+                public Object run(Object params) {
+                    Exception e = (Exception) params;
+                    e.printStackTrace();
+                    return null;
+                }
+            }
+        );
+    }
+}

+ 109 - 0
src/main/java/com/emato/cus/entity/ResponseMsg.java

@@ -0,0 +1,109 @@
+package com.emato.cus.entity;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.Serializable;
+
+public class ResponseMsg implements Serializable {
+
+    private static final long serialVersionUID = 6556920017380881713L;
+
+    public static final String SUCCESS_CODE = "0";
+
+    public static final String ERROR_CODE = "-1";
+
+    private String code;
+    private String message;
+    private Object data;
+
+    public ResponseMsg(){
+        this.code = SUCCESS_CODE;
+        this.message = "成功";
+        this.data = null;
+    }
+
+    public ResponseMsg(String code, String message) {
+        this.code = code;
+        this.message = message;
+        this.data = null;
+    }
+
+    public ResponseMsg(String code, String message, Object data){
+        this.code = code;
+        this.message = message;
+        this.data = data;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    public Object getData() {
+        return data;
+    }
+
+    public void setData(Object data) {
+        this.data = data;
+    }
+
+    /**
+     * 消息构建器类
+     */
+    public static class Builder {
+
+        private String code;
+        private String message;
+        private Object data;
+
+        public Builder() {
+            this.code = SUCCESS_CODE;
+            this.message = "";
+            this.data = null;
+        }
+
+        public Builder(String code, String message){
+            this.code = StringUtils.isBlank(code) ? SUCCESS_CODE : code;
+            this.message = code.equals(SUCCESS_CODE) ? "成功" : message;
+        }
+
+        public Builder(String code, String message, Object data) {
+            this.code = StringUtils.isBlank(code) ? SUCCESS_CODE : code;
+            this.message = code.equals(SUCCESS_CODE) ? "成功" : message;
+            this.data = data;
+        }
+
+        public Builder setCode(String code) {
+            this.code = code;
+            return this;
+        }
+
+        public Builder setMessage(String message) {
+            this.message = message;
+            return this;
+        }
+
+        public ResponseMsg build() {
+            ResponseMsg message = new ResponseMsg();
+            message.code = this.code;
+            message.message = this.message;
+            message.data = this.data;
+            return message;
+        }
+    }
+}

+ 28 - 0
src/main/java/com/emato/cus/entity/WsData.java

@@ -0,0 +1,28 @@
+package com.emato.cus.entity;
+
+import java.io.Serializable;
+
+public class WsData implements Serializable {
+
+    private static final long serialVersionUID = 6247154791350441380L;
+
+    private String passwd;
+
+    private String random;
+
+    public String getPasswd() {
+        return passwd;
+    }
+
+    public void setPasswd(String passwd) {
+        this.passwd = passwd;
+    }
+
+    public String getRandom() {
+        return random;
+    }
+
+    public void setRandom(String random) {
+        this.random = random;
+    }
+}

+ 38 - 0
src/main/java/com/emato/cus/entity/WsInfo.java

@@ -0,0 +1,38 @@
+package com.emato.cus.entity;
+
+import java.io.Serializable;
+
+public class WsInfo implements Serializable {
+
+    private static final long serialVersionUID = -1649902156826290331L;
+
+    private String _method;
+
+    private int _id;
+
+    private WsData args;
+
+    public String get_method() {
+        return _method;
+    }
+
+    public void set_method(String _method) {
+        this._method = _method;
+    }
+
+    public int get_id() {
+        return _id;
+    }
+
+    public void set_id(int _id) {
+        this._id = _id;
+    }
+
+    public WsData getArgs() {
+        return args;
+    }
+
+    public void setArgs(WsData args) {
+        this.args = args;
+    }
+}

+ 205 - 0
src/main/java/com/emato/cus/utils/HttpUtil.java

@@ -0,0 +1,205 @@
+package com.emato.cus.utils;
+
+import org.apache.commons.codec.Charsets;
+import org.apache.http.*;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.protocol.HTTP;
+import org.apache.http.util.EntityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+public class HttpUtil {
+
+    private static final Logger logger = LoggerFactory.getLogger(HttpUtil.class);
+
+    private static ResponseHandler<String> getResponseHandler() {
+        ResponseHandler<String> handler = new ResponseHandler<String>() {
+            public String handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
+                HttpEntity httpEntity = response.getEntity();
+
+                String strReslut = null;
+                int statuscode = response.getStatusLine().getStatusCode();
+                if ((statuscode == HttpStatus.SC_MOVED_TEMPORARILY) || (statuscode == HttpStatus.SC_MOVED_PERMANENTLY)
+                    || (statuscode == HttpStatus.SC_SEE_OTHER) || (statuscode == HttpStatus.SC_TEMPORARY_REDIRECT)) {
+
+                    Header locationHeader = response.getFirstHeader("Location");
+                    if (locationHeader != null) {
+                        strReslut = locationHeader.getValue();
+                    }
+                } else if (httpEntity != null) {
+                    strReslut = EntityUtils.toString(httpEntity, Charsets.UTF_8);
+                }
+                return strReslut;
+
+            }
+        };
+
+        return handler;
+    }
+
+    /**
+     * get请求
+     *
+     * @return
+     */
+    public static String doGet(String url) {
+        try {
+            HttpClient client = new DefaultHttpClient();
+            //发送get请求
+            HttpGet request = new HttpGet(url);
+            HttpResponse response = client.execute(request);
+
+            /**请求发送成功,并得到响应**/
+            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
+                /**读取服务器返回过来的json字符串数据**/
+                String strResult = EntityUtils.toString(response.getEntity());
+
+                return strResult;
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+
+    /**
+     * post请求(用于key-value格式的参数)
+     *
+     * @param url
+     * @param params
+     * @return
+     */
+    public static String doPost(String url, Map params) {
+        BufferedReader in = null;
+        try {
+            // 定义HttpClient
+            HttpClient client = new DefaultHttpClient();
+            // 实例化HTTP方法
+            HttpPost request = new HttpPost();
+            request.setURI(new URI(url));
+
+            //设置参数
+            List<NameValuePair> nvps = new ArrayList<NameValuePair>();
+            for (Iterator iter = params.keySet().iterator(); iter.hasNext(); ) {
+                String name = (String) iter.next();
+                String value = String.valueOf(params.get(name));
+                nvps.add(new BasicNameValuePair(name, value));
+            }
+            request.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
+
+            HttpResponse response = client.execute(request);
+            int code = response.getStatusLine().getStatusCode();
+            if (code == 200) {    //请求成功
+                in = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "utf-8"));
+                StringBuffer sb = new StringBuffer("");
+                String line = "";
+                String NL = System.getProperty("line.separator");
+                while ((line = in.readLine()) != null) {
+                    sb.append(line + NL);
+                }
+
+                in.close();
+
+                return sb.toString();
+            } else {    //
+                System.out.println("状态码:" + code);
+                return null;
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+
+            return null;
+        }
+    }
+
+    /**
+     * post请求(用于请求json格式的参数)
+     *
+     * @param url
+     * @param params
+     * @return
+     */
+    public static String doPost(String url, String params) throws Exception {
+
+        CloseableHttpClient httpclient = HttpClients.createDefault();
+        HttpPost httpPost = new HttpPost(url);// 创建httpPost
+        httpPost.setHeader("Accept", "application/json");
+        httpPost.setHeader("Content-Type", "application/json");
+        String charSet = "UTF-8";
+        StringEntity entity = new StringEntity(params, charSet);
+        httpPost.setEntity(entity);
+        CloseableHttpResponse response = null;
+
+        try {
+
+            response = httpclient.execute(httpPost);
+            StatusLine status = response.getStatusLine();
+            int state = status.getStatusCode();
+            if (state == HttpStatus.SC_OK) {
+                HttpEntity responseEntity = response.getEntity();
+                String jsonString = EntityUtils.toString(responseEntity);
+                return jsonString;
+            } else {
+                logger.error("请求返回:" + state + "(" + url + ")");
+            }
+        } finally {
+            if (response != null) {
+                try {
+                    response.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            try {
+                httpclient.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * post请求(用于请求json格式的参数,以及设置cookie)
+     *
+     * @param reqUrl    请求地址
+     * @param reqParams 请求参数
+     * @param cookie
+     * @return
+     * @throws Exception
+     */
+    public static String postRequet(final String reqUrl, String reqParams, String cookie) throws Exception {
+        StringEntity entity = new StringEntity(reqParams);
+
+        HttpClient httpClient = HttpClients.createDefault();
+        HttpPost httpPost = new HttpPost(reqUrl);
+        httpPost.setHeader("Content-type", "application/json;charset=utf-8");
+        httpPost.setHeader("Cookie", cookie);
+        httpPost.setEntity(entity);
+
+        ResponseHandler<String> handler = getResponseHandler();
+        String strResult = httpClient.execute(httpPost, handler);
+        return strResult;
+    }
+}

+ 70 - 0
src/main/java/com/emato/cus/utils/JacksonStringUnicodeSerializer.java

@@ -0,0 +1,70 @@
+package com.emato.cus.utils;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.io.CharTypes;
+import com.fasterxml.jackson.core.json.JsonWriteContext;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+import java.io.IOException;
+
+/**
+ * jackson处理以unicode方式编码中文
+ */
+public class JacksonStringUnicodeSerializer extends JsonSerializer<String> {
+
+	private final char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
+	private final int[] ESCAPE_CODES = CharTypes.get7BitOutputEscapes();
+
+	private void writeUnicodeEscape(JsonGenerator gen, char c) throws IOException {
+		gen.writeRaw('\\');
+		gen.writeRaw('u');
+		gen.writeRaw(HEX_CHARS[(c >> 12) & 0xF]);
+		gen.writeRaw(HEX_CHARS[(c >> 8) & 0xF]);
+		gen.writeRaw(HEX_CHARS[(c >> 4) & 0xF]);
+		gen.writeRaw(HEX_CHARS[c & 0xF]);
+	}
+
+	private void writeShortEscape(JsonGenerator gen, char c) throws IOException {
+		gen.writeRaw('\\');
+		gen.writeRaw(c);
+	}
+
+	@Override
+	public void serialize(String str, JsonGenerator gen, SerializerProvider provider) throws IOException {
+		int status = ((JsonWriteContext) gen.getOutputContext()).writeValue();
+		switch (status) {
+			case JsonWriteContext.STATUS_OK_AFTER_COLON:
+				gen.writeRaw(':');
+				break;
+			case JsonWriteContext.STATUS_OK_AFTER_COMMA:
+				gen.writeRaw(',');
+				break;
+			case JsonWriteContext.STATUS_EXPECT_NAME:
+				throw new JsonParseException(null, "Can not write string value here");
+		}
+
+		// 写入JSON中字符串的开头引号
+		gen.writeRaw('"');
+		for (char c : str.toCharArray()) {
+			if (c >= 0x80) {
+				// 为所有非ASCII字符生成转义的unicode字符
+				writeUnicodeEscape(gen, c);
+			} else {
+				// 为ASCII字符中前128个字符使用转义的unicode字符
+				int code = (c < ESCAPE_CODES.length ? ESCAPE_CODES[c] : 0);
+				if (code == 0) {
+					gen.writeRaw(c); // 此处不用转义
+				} else if (code < 0) {
+					writeUnicodeEscape(gen, (char) (-code - 1)); // 通用转义字符
+				} else {
+					writeShortEscape(gen, (char) code); // 短转义字符 (\n \t ...)
+				}
+			}
+		}
+		// 写入JSON中字符串的结束引号
+		gen.writeRaw('"');
+	}
+}

+ 355 - 0
src/main/java/com/emato/cus/utils/JacksonUtils.java

@@ -0,0 +1,355 @@
+package com.emato.cus.utils;
+
+import com.alibaba.fastjson.JSONObject;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonGenerationException;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.util.Locale;
+
+/**
+ * JacksonUtils 工具类
+ */
+public class JacksonUtils {
+
+    private static final Logger logger = LoggerFactory.getLogger(JacksonUtils.class);
+
+    private static ObjectMapper objectMapper;
+
+    private static final ThreadLocal<JacksonUtils> jacksonUtilThreadLocal = new ThreadLocal<>();
+
+    private static final ThreadLocal<ObjectMapper> objectMapperThreadLocal = new ThreadLocal<>();
+
+    /**
+     * 禁止调用无参构造
+     */
+    private JacksonUtils() {}
+
+    /**
+     * JacksonUtils 工具类实例
+     * @return
+     */
+    public static JacksonUtils instance() {
+        JacksonUtils jacksonUtils = jacksonUtilThreadLocal.get();
+        if (jacksonUtils == null) {
+            jacksonUtils = new JacksonUtils();
+            jacksonUtilThreadLocal.set(jacksonUtils);
+        }
+        return jacksonUtils;
+    }
+
+    /**
+     * ObjectMapper实例
+     * @return
+     */
+    public static ObjectMapper objectMapper() {
+        objectMapper = objectMapperThreadLocal.get();
+        if (objectMapper== null){
+            objectMapper= new ObjectMapper();
+
+            // 格式化国家环境指定
+            objectMapper.setLocale(Locale.SIMPLIFIED_CHINESE);
+
+            //序列化时,如果没有为类型找到访问者,则会抛出异常以将其指定为非可序列化类型; 如果禁用,它们将被序列化为空对象
+            objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
+            //反序列化时,遇到未知属性是否抛JsonMappingException异常.默认JsonMappingException
+            objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+            //反序列化时,避免空值转换抛出异常
+            objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true) ;
+            //序列化和反序列化时,避免转义字符抛出异常情况
+            objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true) ;
+
+            //设置null值不参与序列化(字段不被显示)
+            objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+
+            objectMapperThreadLocal.set(objectMapper);
+        }
+        return objectMapper;
+    }
+
+    /**
+     * 序列化Unicode编码非ASCII字符
+     */
+    private static SimpleModule unicodeSerModule() {
+        SimpleModule unicodeSerModule = new SimpleModule();
+        unicodeSerModule.addSerializer(String.class, new JacksonStringUnicodeSerializer());
+        return unicodeSerModule;
+    }
+
+    /**
+     * 创建Json处理器的静态方法
+     * 用于序列化
+     * @param content Json字符串
+     * @return
+     */
+    private static JsonParser getParser(String content){
+        try {
+            return objectMapper().getFactory().createParser(content);
+        } catch (IOException ioe) {
+            ioe.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 创建Byte Json处理器的静态方法
+     * 用于序列化
+     * @param data byte array
+     * @return
+     */
+    private static JsonParser getByteParser(byte[] data){
+        try {
+            return objectMapper().getFactory().createParser(data);
+        } catch (IOException ioe) {
+            ioe.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 创建Reader Json处理器的静态方法
+     * 用于序列化
+     * @param r Reader
+     * @return
+     */
+    private static JsonParser getReaderParser(Reader r){
+        try {
+            return objectMapper().getFactory().createParser(r);
+        } catch (IOException ioe) {
+            ioe.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 创建InputStream Json处理器的静态方法
+     * 用于序列化
+     * @param in InputStream
+     * @return
+     */
+    private static JsonParser getReaderParser(InputStream in){
+        try {
+            return objectMapper().getFactory().createParser(in);
+        } catch (IOException ioe) {
+            ioe.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 创建Json生成器的静态方法, 使用标准输出
+     * 用于反序列化
+     * @return
+     */
+    private static JsonGenerator getGenerator(StringWriter sw){
+        try {
+            return objectMapper().getFactory().createGenerator(sw);
+        } catch (IOException ioe) {
+            ioe.printStackTrace();
+            return null;
+        }
+    }
+
+
+    // -------------------- to json --------------------
+
+    /**
+     * Object对象序列化为Json字符串
+     * @param obj
+     * @param <T>
+     * @return
+     */
+    public static <T> String toJson(T obj){
+        if (obj == null){
+            return null;
+        }
+
+        if (obj instanceof String) {
+            return (String) obj;
+        }
+
+        StringWriter sw= new StringWriter();
+        JsonGenerator jsonGen= getGenerator(sw);
+        if (jsonGen == null){
+            try {
+                sw.close();
+            } catch (IOException e) {
+            }
+            return null;
+        }
+
+        try {
+            // 由于在getGenerator方法中指定了OutputStream为sw
+            // 因此调用writeObject会将数据输出到sw
+            jsonGen.writeObject(obj);
+            // 由于采用流式输出 在输出完毕后务必清空缓冲区并关闭输出流
+            jsonGen.flush();
+            jsonGen.close();
+
+            return sw.toString();
+        } catch (JsonGenerationException jge) {
+            logger.error("toJSON序列化失败, 异常类型【JsonGenerationException】,错误原因:{}", jge.getMessage());
+        } catch (IOException ioe) {
+            logger.error("toJSON序列化失败, 异常类型【IOException】, 错误原因:{}", ioe.getMessage());
+            ioe.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * Object 转 json 字符串并格式化美化
+     * @param obj
+     * @param <T>
+     * @return
+     */
+    public static <T> String toJsonWithPretty(T obj){
+        if (obj == null){
+            return null;
+        }
+
+        if (obj instanceof String) {
+            return (String) obj;
+        }
+
+        StringWriter sw= new StringWriter();
+        JsonGenerator jsonGen= getGenerator(sw);
+        if (jsonGen == null){
+            try {
+                sw.close();
+            } catch (IOException e) {
+            }
+            return null;
+        }
+
+        try {
+            jsonGen.useDefaultPrettyPrinter().writeObject(obj);
+            jsonGen.flush();
+            jsonGen.close();
+
+            return sw.toString();
+        } catch (Exception e) {
+            System.out.println("Parse object to String error");
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+
+    // -------------------- from json --------------------
+
+    /**
+     * 将Json Byte反序列化成对象
+     *
+     * @param data
+     * @param clazz
+     * @return
+     */
+    public static <T> T fromByteJson(byte[] data, Class<T> clazz) {
+        try {
+            JsonParser jp= getByteParser(data);
+            return jp.readValueAs(clazz);
+        } catch (JsonParseException e){
+            logger.error(String.format("fromByteJson反序列化失败, 异常类型【JsonParseException】, 错误原因:{}", e.getMessage()));
+        } catch (JsonMappingException e){
+            logger.error(String.format("fromByteJson反序列化失败, 异常类型【JsonMappingException】, 错误原因:{}", e.getMessage()));
+        } catch (IOException e){
+            logger.error(String.format("fromByteJson反序列化失败, 异常类型【IOException】, 错误原因:{}", e.getMessage()));
+        }
+        return null;
+    }
+
+    /**
+     * 将Json String反序列化成对象
+     *
+     * @param json
+     * @param clazz
+     * @return
+     */
+    public static <T> T fromStringJson(String json, Class<T> clazz) {
+        try {
+            return clazz.equals(String.class) ? (T) json : getParser(json).readValueAs(clazz);
+        } catch (JsonParseException e) {
+            logger.error(String.format("fromStringJson反序列化失败, 异常类型【JsonParseException】, 错误原因:{}", e.getMessage()));
+            logger.error("decode(String, Class<T>)", e);
+        } catch (JsonMappingException e) {
+            logger.error(String.format("fromStringJson反序列化失败, 异常类型【JsonMappingException】, 错误原因:{}", e.getMessage()));
+            logger.error("decode(String, Class<T>)", e);
+        } catch (IOException e) {
+            logger.error(String.format("fromStringJson反序列化失败, 异常类型【IOException】, 错误原因:{}", e.getMessage()));
+            logger.error("decode(String, Class<T>)", e);
+        }
+        return null;
+    }
+
+    /**
+     * 将Json Array或List反序列化为对象
+     *
+     * @param json
+     * @param typeReference 被转对象引用类型
+     * @param <T>
+     * @return
+     */
+    public static <T> T fromStringJson(String json, TypeReference<T> typeReference) {
+        try {
+            //写成List.class是不行的
+            JsonParser jp= getParser(json);
+            return (T) (typeReference.getType().equals(String.class) ? json : jp.readValueAs(typeReference));
+        } catch (JsonParseException e) {
+            logger.error(String.format("fromListJson反序列化失败, 异常类型【JsonParseException】, 错误原因:{}", e.getMessage()));
+            logger.error("decode(String, Class<T>)", e);
+        } catch (JsonMappingException e) {
+            logger.error(String.format("fromListJson反序列化失败, 异常类型【JsonMappingException】, 错误原因:{}", e.getMessage()));
+            logger.error("decode(String, Class<T>)", e);
+        } catch (IOException e) {
+            logger.error(String.format("fromListJson反序列化失败, 异常类型【IOException】, 错误原因:{}", e.getMessage()));
+            logger.error("decode(String, Class<T>)", e);
+        }
+        return null;
+    }
+
+    /**
+     * string转object 用于转为集合对象
+     * @param json json字符串
+     * @param collectionClass 被转集合class
+     * @param elementClasses 被转集合中对象类型class
+     * @param <T>
+     * @return
+     */
+    public static <T> T fromStringJson(String json, Class<?> collectionClass, Class<?>... elementClasses){
+        JavaType javaType = objectMapper()
+                .getTypeFactory()
+                .constructParametricType(collectionClass, elementClasses);
+        try {
+            return objectMapper().readValue(json, javaType);
+        } catch (IOException e) {
+            logger.error("Parse String to Object error");
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 校验是否JSON格式
+     * @param text
+     * @return
+     */
+    public static boolean isJson(String text) {
+        try {
+            new JSONObject().parse(text);
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+}

+ 411 - 0
src/main/java/com/emato/cus/utils/OkHttpUtils.java

@@ -0,0 +1,411 @@
+package com.emato.cus.utils;
+
+import okhttp3.*;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.net.ssl.*;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+public class OkHttpUtils {
+
+    private static final Logger logger = LoggerFactory.getLogger(OkHttpUtils.class);
+
+    public static final MediaType MEDIA_TYPE_JSON = MediaType.parse("application/json;charset=UTF-8");
+    public static final MediaType MEDIA_TYPE_XML = MediaType.parse("application/xml;charset=UTF-8");
+    public static final MediaType MEDIA_TYPE_TEXT_HTML = MediaType.parse("text/html;charset=UTF-8");
+    private static final MediaType MEDIA_TYPE_FORM= MediaType.parse("application/x-www-form-urlencoded");
+
+    public static final int CONNECT_TIME_OUT = 20;
+    public static final int READ_TIME = 30;
+    public static final int WRITE_TIME = 30;
+
+    private static final ThreadLocal<OkHttpClient> okHttpClientThreadLocal = new ThreadLocal<>();
+    private static final ThreadLocal<OkHttpClient> okHttpClientSslThreadLocal = new ThreadLocal<>();
+
+    /**
+     * 禁止调用无参构造
+     */
+    private OkHttpUtils() {}
+
+    /**
+     * 默认http
+     * @return
+     */
+    private static OkHttpClient okHttpClient(){
+        return okHttpClient(null);
+    }
+
+    /**
+     * https 和 http
+     * @param ssl
+     * @return
+     */
+    private static OkHttpClient okHttpClient(String ssl){
+        return StringUtils.isNotBlank(ssl) && "ssl".equalsIgnoreCase(ssl) ? okHttpInstanceSsl() : okHttpInstance();
+    }
+
+    /**
+     * OkHttpClient 实例
+     * @return
+     */
+    private static OkHttpClient okHttpInstance() {
+        OkHttpClient client = okHttpClientThreadLocal.get();
+        if (client == null) {
+            OkHttpClient.Builder builder = new OkHttpClient.Builder();
+            builder.connectTimeout(CONNECT_TIME_OUT, TimeUnit.SECONDS)
+                    .readTimeout(READ_TIME, TimeUnit.SECONDS)
+                    .writeTimeout(WRITE_TIME, TimeUnit.SECONDS);
+
+            client = builder.build();
+            okHttpClientThreadLocal.set(client);
+        }
+        return client;
+    }
+
+    private static OkHttpClient okHttpInstanceSsl() {
+        return okHttpInstanceSsl(null);
+    }
+
+    /**
+     * OkHttpClient ssl 实例
+     * @return
+     */
+    private static OkHttpClient okHttpInstanceSsl(InputStream... certificates) {
+        OkHttpClient client = okHttpClientSslThreadLocal.get();
+        if (client == null) {
+            X509TrustManager trustManager = buildX509TrustManager(certificates);
+            SSLSocketFactory sslSocketFactory = buildSSLSocketFactory(trustManager);
+
+            OkHttpClient.Builder builder = new OkHttpClient.Builder();
+            builder.connectTimeout(CONNECT_TIME_OUT, TimeUnit.SECONDS)
+                    .readTimeout(READ_TIME, TimeUnit.SECONDS)
+                    .writeTimeout(WRITE_TIME, TimeUnit.SECONDS)
+                    .hostnameVerifier(new HostnameVerifier() {
+                        @Override
+                        public boolean verify(String hostname, SSLSession session) {
+                            return true;
+                        }
+                    }).sslSocketFactory(sslSocketFactory, trustManager);
+
+            client = builder.build();
+            okHttpClientSslThreadLocal.set(client);
+        }
+        return client;
+    }
+
+
+    //------------------------------ build RequestBody ------------------------------
+
+    public static RequestBody buildRequestBody(MediaType mediaType, String json) {
+        return RequestBody.create(mediaType, json);
+    }
+
+    public static RequestBody buildRequestBody(String key, Object value) {
+        if (StringUtils.isBlank(key)) {
+            return null;
+        }
+        Map<String, Object> param = new HashMap<>();
+        param.put(key, value);
+        return buildRequestBody(param);
+    }
+
+    public static RequestBody buildMapStringRequestBody(Map<String, String> params) {
+        FormBody.Builder builder = new FormBody.Builder();
+        Iterator<Map.Entry<String, String>> iterator = params.entrySet().iterator();
+        while (iterator.hasNext()) {
+            Map.Entry<String, String> entry = iterator.next();
+            builder.add(entry.getKey(), entry.getValue());
+        }
+        return builder.build();
+    }
+
+    /**
+     * 构造RequestBody
+     *
+     * @param params
+     * @return
+     */
+    public static RequestBody buildRequestBody(Map<String, Object> params) {
+        FormBody.Builder builder = new FormBody.Builder();
+        Iterator<Map.Entry<String, Object>> iterator = params.entrySet().iterator();
+        while (iterator.hasNext()) {
+            Map.Entry<String, Object> entry = iterator.next();
+            builder.add(entry.getKey(), JacksonUtils.toJson(entry.getValue()));
+        }
+        return builder.build();
+    }
+
+
+    //------------------------------ build Request ------------------------------
+
+    /**
+     * 以字符串数据构建
+     * @param json
+     * @param url
+     * @param type
+     * @return
+     */
+    public static Request buildRequest(String json, String url, String type) {
+        //默认JSON
+        MediaType mediaType = MEDIA_TYPE_JSON;
+
+        if (type.equalsIgnoreCase("text_html")) {
+            mediaType = MEDIA_TYPE_TEXT_HTML;
+        }else if (type.equalsIgnoreCase("form")) {
+            mediaType = MEDIA_TYPE_FORM;
+        }else if (type.equalsIgnoreCase("xml")) {
+            mediaType = MEDIA_TYPE_XML;
+        }
+
+        RequestBody body = buildRequestBody(mediaType, json);
+        return buildRequest(body, url);
+    }
+
+
+    public static Request buildStringRequest(Map<String, String> param, String url) {
+        return buildRequest(buildMapStringRequestBody(param), url);
+    }
+
+    /**
+     * 构建Request
+     * @param param
+     * @param url
+     * @return
+     */
+    public static Request buildRequest(Map<String, Object> param, String url) {
+        return buildRequest(buildRequestBody(param), url);
+    }
+
+    /**
+     * 构建Request
+     * @param key
+     * @param value
+     * @param url
+     * @return
+     */
+    public static Request buildRequest(String key, Object value, String url) {
+        return buildRequest(buildRequestBody(key, value), url);
+    }
+
+    /**
+     * 构建Request
+     * @param body
+     * @param url
+     * @return
+     */
+    public static Request buildRequest(RequestBody body, String url) {
+        return new Request.Builder()
+                .url(url)
+                .post(body)
+                .build();
+    }
+
+
+    //------------------------------ post ------------------------------
+
+    public static String post(String json, String url, String type, String ssl) {
+        return post(buildRequest(json, url, type), ssl);
+    }
+
+    public static String post(String key, Object value, String url, String ssl) {
+        return post(buildRequest(key, value, url), ssl);
+    }
+
+    public static String postString(Map<String, String> param, String url, String ssl) {
+        return post(buildStringRequest(param, url), ssl);
+    }
+
+    public static String post(Map<String, Object> param, String url, String ssl) {
+        return post(buildRequest(param, url), ssl);
+    }
+
+    /**
+     * 同步访问,返回结果字符串
+     * 可能超时
+     *
+     * @param request
+     * @param ssl
+     * @return
+     * @throws IOException
+     */
+    public static String post(Request request, String ssl) {
+        String result = "";
+        try {
+            Response response = okHttpClient(ssl).newCall(request).execute();
+
+            if (response.isSuccessful()) {
+                result = response.body().string();
+            } else {
+                logger.error("okhttp3 post failed.");
+            }
+        } catch (IOException e) {
+            logger.error("okhttp3 post throw IOException, {}", e.getMessage());
+        }
+        return result;
+    }
+
+    /**
+     * 同步访问,返回Response
+     * 可能超时
+     *
+     * @param request
+     * @param ssl
+     * @return
+     * @throws IOException
+     */
+    public static String postReturnResponse(Request request, String ssl) {
+        try {
+            return okHttpClient(ssl).newCall(request).execute().body().string();
+        } catch (IOException e) {
+                logger.error("okhttp3 post throw IOException, {}", e.getMessage());
+        }
+        return null;
+    }
+
+    /**
+     * 异步访问,回调结果
+     * @param request
+     * @param ssl
+     * @param responseCallback
+     */
+    public static void asyncPostCallback(Request request, String ssl,  Callback responseCallback) {
+        okHttpClient(ssl).newCall(request).enqueue(responseCallback);
+    }
+
+    /**
+     * 异步访问,无结果返回
+     * @param request
+     * @param ssl
+     */
+    public static void asyncPost(Request request, String ssl) {
+        okHttpClient(ssl).newCall(request).enqueue(new Callback() {
+            @Override
+            public void onFailure(Call call, IOException e) {
+
+            }
+
+            @Override
+            public void onResponse(Call call, Response response) throws IOException {
+
+            }
+        });
+    }
+
+
+    //------------------------------ ssl ------------------------------
+
+    /**
+     * 自定义证书
+     * @param certificates
+     * @return
+     */
+    private static KeyStore generateKeyStore(InputStream... certificates) {
+        try {
+            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
+
+            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+            keyStore.load(null);
+
+            int index = 0;
+            if (certificates !=null && certificates.length > 0) {
+                for (InputStream certificate : certificates) {
+                    String certificateAlias = Integer.toString(index++);
+                    keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
+
+                    try {
+                        if (certificate != null) {
+                            certificate.close();
+                        }
+                    } catch (IOException e) {
+                    }
+                }
+            }
+
+            return keyStore;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+
+    private static X509TrustManager buildX509TrustManager(){
+        return buildX509TrustManager(null);
+    }
+
+    private static X509TrustManager buildX509TrustManager(InputStream... certificates) {
+        try {
+            KeyStore keyStore = generateKeyStore(certificates);
+
+            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
+                    TrustManagerFactory.getDefaultAlgorithm());
+
+            trustManagerFactory.init(keyStore);
+
+            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
+            if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
+                throw new IllegalStateException("Unexpected default trust managers:"
+                        + Arrays.toString(trustManagers));
+            }
+
+            X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
+            return trustManager;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+
+    private static SSLSocketFactory buildSSLSocketFactory() {
+        return buildSSLSocketFactory((InputStream)null);
+    }
+
+    private static SSLSocketFactory buildSSLSocketFactory(InputStream... certificates) {
+        try {
+            TrustManager trustManager = buildX509TrustManager(certificates);
+            return buildSSLSocketFactory(trustManager);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    private static SSLSocketFactory buildSSLSocketFactory(TrustManager trustManager) {
+        try {
+            if (trustManager == null) {
+                throw new IllegalStateException("TrustManager is null");
+            }
+
+            SSLContext sslContext = SSLContext.getInstance("SSL");
+            sslContext.init(null, new TrustManager[]{new X509TrustManager() {
+                @Override
+                public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {}
+                @Override
+                public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {}
+                @Override
+                public X509Certificate[] getAcceptedIssuers() {
+                    return new X509Certificate[0];
+                }
+            }}, new SecureRandom());
+
+            return sslContext.getSocketFactory();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+}

+ 31 - 0
src/main/java/com/emato/cus/utils/SSLUtils.java

@@ -0,0 +1,31 @@
+package com.emato.cus.utils;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+public class SSLUtils {
+
+	public static SSLContext createSslContext() throws NoSuchAlgorithmException, KeyManagementException {
+        SSLContext sc = SSLContext.getInstance("SSL");
+
+        sc.init(null, new TrustManager[]{new X509TrustManager() {
+            @Override
+            public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {}
+
+            @Override
+            public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {}
+
+            @Override
+            public X509Certificate[] getAcceptedIssuers() {
+                return new X509Certificate[0];
+            }
+        }}, new java.security.SecureRandom());
+
+        return sc;
+    }
+}

+ 6 - 0
src/main/java/com/emato/cus/utils/websocket/Callback.java

@@ -0,0 +1,6 @@
+package com.emato.cus.utils.websocket;
+
+public interface Callback {
+
+    Object run(Object params);
+}

+ 91 - 0
src/main/java/com/emato/cus/utils/websocket/WebSocketClientHandle.java

@@ -0,0 +1,91 @@
+package com.emato.cus.utils.websocket;
+
+import org.java_websocket.WebSocket;
+import org.java_websocket.client.WebSocketClient;
+import org.java_websocket.handshake.ServerHandshake;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+
+public class WebSocketClientHandle {
+
+    static Map<String,WebSocketClient> clientMap=new HashMap<>();
+
+    public static void send(String url, String message, Callback onSuccess, Callback onError){
+        WebSocketClient client = clientMap.get(url);
+
+        if(client!=null&&client.isConnecting()&&client.isOpen()){
+            client.send(message);
+
+        } else {
+            try {
+                client = new WebSocketClient(new URI(url)) {
+
+                    @Override
+                    public void onOpen(ServerHandshake arg0) {
+                       // System.out.println("打开链接");
+                    }
+
+                    @Override
+                    public void onMessage(String arg0) {
+                        if(onSuccess!=null){
+                            onSuccess.run(arg0);
+                        }
+                    }
+
+                    @Override
+                    public void onError(Exception arg0) {
+                        if(onError!=null){
+                            onError(arg0);
+                        }
+                    }
+
+                    @Override
+                    public void onClose(int arg0, String arg1, boolean arg2) {
+                        //System.out.println("链接已关闭");
+                    }
+
+                    @Override
+                    public void onMessage(ByteBuffer bytes) {
+                        try {
+                            if(onSuccess!=null){
+                                onSuccess.run(new String(bytes.array(),"utf-8"));
+                            }
+                        } catch (UnsupportedEncodingException e) {
+                            e.printStackTrace();
+                        }
+                    }
+                };
+
+                client.connect();
+                Integer timeOut=3*60*1000;
+                Integer step=10;
+                Integer count=0;
+
+                //阻塞以等待OPEN
+                while(!client.getReadyState().equals(WebSocket.READYSTATE.OPEN)){
+                    try {
+                        count+=step;
+                        Thread.sleep(step);
+                        if(count>=timeOut){
+                            onError.run(new Exception("WS服务器连接超时!或服务器已经关闭"));
+                            break;
+                        }
+                    } catch (InterruptedException e) {
+                        e.printStackTrace();
+                    }
+                }
+
+                client.send(message);
+                clientMap.put(url, client);
+
+            } catch (URISyntaxException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+}

+ 25 - 0
src/main/resources/application-dev.yml

@@ -0,0 +1,25 @@
+#---------- Spring Boot 项目 ----------#
+### 服务配置
+server:
+    address: 192.168.1.118
+    port: 50080
+    servlet:
+        context-path: /api
+
+### 使用环境
+spring:
+    profiles:
+        active: dev
+
+
+### 互联网+海关配置
+cus:
+    ## 操作员卡配置
+    card:
+        url: 'wss://wss.singlewindow.cn:61231'
+        password: '88888888'
+    ## 登陆地址
+    login:
+        url: https://app.singlewindow.cn/cas/login?service=https://swapp.singlewindow.cn/deskserver/j_spring_cas_security_check&logoutFlag=1&_swCardF=1
+    ## 查询地址
+    inveQueryUrl: https://swapp.singlewindow.cn/swceb2web/inventory/inventoryQuery

+ 25 - 0
src/main/resources/application-test.yml

@@ -0,0 +1,25 @@
+#---------- Spring Boot 项目 ----------#
+### 服务配置
+server:
+    address: 127.0.0.1
+    port: 50080
+    servlet:
+        context-path: /api
+
+### 使用环境
+spring:
+    profiles:
+        active: test
+
+
+### 互联网+海关配置
+cus:
+    ## 操作员卡配置
+    card:
+        url: 'wss://wss.singlewindow.cn:61231'
+        password: '88888888'
+    ## 登陆地址
+    login:
+        url: https://app.singlewindow.cn/cas/login?service=https://swapp.singlewindow.cn/deskserver/j_spring_cas_security_check&logoutFlag=1&_swCardF=1
+    ## 查询地址
+    inveQueryUrl: https://swapp.singlewindow.cn/swceb2web/inventory/inventoryQuery

+ 30 - 0
src/main/resources/application.yml

@@ -0,0 +1,30 @@
+#---------- Spring Boot 项目 ----------#
+### 项目配置
+app:
+    name: Spring Boot
+    description: ${app.name} is a Spring Boot application
+
+### 服务配置
+server:
+    address: 192.168.1.118
+    port: 50080
+    servlet:
+        context-path: /api
+
+### 使用环境
+spring:
+    profiles:
+        active: test
+
+
+### 互联网+海关配置
+cus:
+    ## 操作员卡配置
+    card:
+        url: 'wss://wss.singlewindow.cn:61231'
+        password: '88888888'
+    ## 登陆地址
+    login:
+        url: https://app.singlewindow.cn/cas/login?service=https://swapp.singlewindow.cn/deskserver/j_spring_cas_security_check&logoutFlag=1&_swCardF=1
+    ## 查询地址
+    inveQueryUrl: https://swapp.singlewindow.cn/swceb2web/inventory/inventoryQuery

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

@@ -0,0 +1,229 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
+scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒当scan为true时,此属性生效。默认的时间间隔为1分钟。
+debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
+-->
+<configuration scan="false" scanPeriod="60 seconds" debug="false">
+
+    <property name="LOG_HOME" value="/data/logs/cus_reader_card/"/>
+    <!-- 定义日志的根目录 -->
+    <property name="TRACE_DIR" value="trace" />
+    <property name="DEBUG_DIR" value="debug" />
+    <property name="INFO_DIR" value="info" />
+    <property name="WARN_DIR" value="warn" />
+    <property name="ERROR_DIR" value="error" />
+    <!-- 定义日志文件名称 -->
+    <property name="TRACE_FILE_NAME" value="reader_card-trace"></property>
+    <property name="DEBUG_FILE_NAME" value="reader_card-debug"></property>
+    <property name="INFO_FILE_NAME" value="reader_card-info"></property>
+    <property name="WARN_FILE_NAME" value="reader_card-warn"></property>
+    <property name="ERROR_FILE_NAME" value="reader_card-error"></property>
+
+    <!-- 定义日志级别颜色 -->
+    <!-- 控制台显示 -->
+    <property name="STD_CONSOLE_LOG_PATTERN"
+              value="%d{yyyy-MM-dd HH:mm:ss.SSS}[%yellow(%thread)]-[%highlight(%-5level)][%green(%logger{70}):%cyan(%line)] - %msg%n"/>
+
+    <!-- 日志文件打印 -->
+    <property name="CONSOLE_LOG_PATTERN"
+              value="%d{yyyy-MM-dd HH:mm:ss.SSS}[%thread]-[%-5level][%logger{70}:%line] - %msg%n"/>
+
+    <!-- ConsoleAppender 控制台输出 appender -->
+    <appender name="stdoutAppender" class="ch.qos.logback.core.ConsoleAppender">
+        <!--
+        日志输出格式:%d表示日期时间,%thread表示线程名,%-5level:级别从左显示5个字符宽度
+        %logger{70} 表示logger名字最长50个字符,否则按照句点分割。 %msg:日志消息,%n是换行符
+        -->
+        <encoder>
+            <pattern>${STD_CONSOLE_LOG_PATTERN}</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+
+
+    <!-- TRACE 日志 appender  -->
+    <appender name="traceAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!-- 指定日志文件的名称 -->
+        <file>${LOG_HOME}/${TRACE_DIR}/${TRACE_FILE_NAME}.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${LOG_HOME}/${TRACE_DIR}/${TRACE_FILE_NAME}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
+            <MaxHistory>365</MaxHistory>
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <maxFileSize>10MB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>TRACE</level>
+            <!--<onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>-->
+        </filter>
+    </appender>
+
+
+    <!-- DEBUG 日志 appender  -->
+    <appender name="debugAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!-- 指定日志文件的名称 -->
+        <file>${LOG_HOME}/${DEBUG_DIR}/${DEBUG_FILE_NAME}.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${LOG_HOME}/${DEBUG_DIR}/${DEBUG_FILE_NAME}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
+            <MaxHistory>365</MaxHistory>
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <maxFileSize>10MB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>DEBUG</level>
+            <!--<onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>-->
+        </filter>
+    </appender>
+
+
+    <!-- phrase 日志 appender  -->
+    <appender name="infoAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!-- 指定日志文件的名称 -->
+        <file>${LOG_HOME}/${INFO_DIR}/${INFO_FILE_NAME}.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${LOG_HOME}/${INFO_DIR}/${INFO_FILE_NAME}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
+            <MaxHistory>365</MaxHistory>
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <maxFileSize>10MB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>INFO</level>
+            <!--<onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>-->
+        </filter>
+    </appender>
+
+
+    <!-- WARN 日志 appender  -->
+    <appender name="warnAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!-- 指定日志文件的名称 -->
+        <file>${LOG_HOME}/${WARN_DIR}/${WARN_FILE_NAME}.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${LOG_HOME}/${WARN_DIR}/${WARN_FILE_NAME}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
+            <MaxHistory>365</MaxHistory>
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <maxFileSize>10MB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>WARN</level>
+            <!--<onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>-->
+        </filter>
+    </appender>
+
+
+    <!-- ERROR 日志 appender  -->
+    <appender name="errorAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!-- 指定日志文件的名称 -->
+        <file>${LOG_HOME}/${ERROR_DIR}/${ERROR_FILE_NAME}.log</file>
+        <!--
+        当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名
+        TimeBasedRollingPolicy: 最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动。
+        -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!--
+            滚动时产生的文件的存放位置及文件名称 %d{yyyy-MM-dd}:按天进行日志滚动
+            %i:当文件大小超过maxFileSize时,按照i进行文件滚动
+            -->
+            <fileNamePattern>${LOG_HOME}/${ERROR_DIR}/${ERROR_FILE_NAME}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
+            <!--
+            可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件。假设设置每天滚动,
+            且maxHistory是365,则只保存最近365天的文件,删除之前的旧文件。注意,删除旧文件是,
+            那些为了归档而创建的目录也会被删除。
+            -->
+            <MaxHistory>365</MaxHistory>
+            <!--
+            当日志文件超过maxFileSize指定的大小是,根据上面提到的%i进行日志文件滚动 注意此处配置SizeBasedTriggeringPolicy是无法实现按文件大小进行滚动的,必须配置timeBasedFileNamingAndTriggeringPolicy
+            -->
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <maxFileSize>10MB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+        </rollingPolicy>
+        <!--
+        日志输出格式:%d表示日期时间,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %logger{70} 表示logger名字最长50个字符,否则按照句点分割。 %msg:日志消息,%n是换行符
+        -->
+        <encoder>
+            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+        <!--
+        过滤器返回枚举:DENY,NEUTRAL,ACCEPT。
+        返回DENY,日志将立即被抛弃不再经过其他过滤器;返回NEUTRAL,有序列表里的下个过滤器过接着处理日志;返回ACCEPT,日志会被立即处理不再经过剩余过滤器。
+        LevelFilter: 级别过滤器,根据日志级别进行过滤。如果日志级别等于配置级别,过滤器会根据onMath 和 onMismatch接收或拒绝日志。节点:level,onMatch,onMismatch
+        -->
+        <filter class="ch.qos.logback.classic.filter.LevelFilter"><!-- 只打印错误日志 -->
+            <!-- 设置过滤级别 -->
+            <level>ERROR</level>
+            <!-- 配置符合过滤条件的操作
+            <onMatch>ACCEPT</onMatch>
+            &lt;!&ndash; 配置不符合过滤条件的操作 &ndash;&gt;
+            <onMismatch>DENY</onMismatch>-->
+        </filter>
+    </appender>
+
+
+    <!--
+    logger主要用于存放日志对象,也可以定义日志类型、级别
+    name:表示匹配的logger类型前缀,也就是包的前半部分
+    level:要记录的日志级别,大小写无关,包括 TRACE,DEBUG,INFO,WARN,ERROR,ALL 和 OFF。
+    additivity:是否向上级loger传递打印信息。默认是true。 作用在于children-logger是否向上级root-logger配置的appender传递打印信息,false:不传递,true:传递
+    -->
+    <!--
+    没设置level,继承他的上级<root>的日志级别;
+    没有设置additivity,默认为true,将此loger的打印信息向上级<root>传递;
+    没有设置appender,此loger本身不打印任何信息;
+    子<logger>向<root>传递信息后,日志level 完全由子级别的level 决定;
+    -->
+
+    <logger name="org.apache" level="ERROR"/>
+    <logger name="org.apache.shiro" level="DEBUG"/>
+    <logger name="org.mybatis" level="DEBUG"/>
+    <logger name="org.hibernate" level="WARN"/>
+    <logger name="org.springframework" level="INFO"/>
+    <logger name="io.lettuce" level="INFO"/>
+    <logger name="org.thymeleaf" level="INFO"/>
+    <logger name="com.zaxxer.hikari" level="ERROR" additivity="false">
+        <appender-ref ref="stdoutAppender" />
+        <appender-ref ref="errorAppender" />
+    </logger>
+    <logger name="org.apache.shiro" level="DEBUG" additivity="false">
+        <appender-ref ref="stdoutAppender" />
+        <appender-ref ref="traceAppender" />
+    </logger>
+    <logger name="com.songmao" level="DEBUG"/>
+
+    <!--
+    root与logger是父子关系,没有特别定义则默认为root,任何一个类只会和一个logger对应,
+    要么是定义的logger,要么是root,判断的关键在于找到这个logger,然后判断这个logger的level和appender。
+    -->
+    <root level="DEBUG" >
+        <appender-ref ref="stdoutAppender" />
+        <!--<appender-ref ref="traceAppender" />-->
+        <appender-ref ref="debugAppender" />
+        <!--<appender-ref ref="infoAppender" />
+        <appender-ref ref="warnAppender" />
+        <appender-ref ref="errorAppender" />-->
+    </root>
+</configuration>