From cb8c71fc15ab1bd4590dd3ef475056dd9e4f60cd Mon Sep 17 00:00:00 2001
From: houzhongjian <houzhongyi@126.com>
Date: 星期五, 28 二月 2025 15:21:05 +0800
Subject: [PATCH] 平台授权SDK

---
 sdk/src/main/java/com/iailab/sdk/auth/client/dto/TokenDTO.java         |   11 
 sdk/src/main/java/com/iailab/sdk/auth/config/SdkConfiguration.java     |   48 ++++
 sdk/src/main/resources/application.yaml                                |   13 +
 sdk/src/main/java/com/iailab/sdk/util/http/HttpClientFactory.java      |  162 +++++++++++++
 sdk/src/test/java/com/iailab/sdk/IailabClientTest.java                 |   39 +++
 sdk/src/main/resources/application-dev.yaml                            |    6 
 sdk/src/test/resources/application-unit-test.yaml                      |   57 ++++
 sdk/src/main/java/com/iailab/sdk/auth/interceptor/AuthInterceptor.java |   26 ++
 sdk/src/main/java/com/iailab/sdk/util/package-info.java                |    4 
 sdk/pom.xml                                                            |   86 +++++++
 sdk/src/main/java/com/iailab/sdk/auth/client/IailabAuthClient.java     |   94 +++++++
 sdk/src/main/java/com/iailab/sdk/auth/client/vo/AuthLoginReqVO.java    |   31 ++
 sdk/src/main/java/com/iailab/sdk/util/http/IailabHttpUtils.java        |  106 ++++++++
 sdk/src/main/java/com/iailab/sdk/SdkMain.java                          |   14 +
 14 files changed, 697 insertions(+), 0 deletions(-)

diff --git a/sdk/pom.xml b/sdk/pom.xml
new file mode 100644
index 0000000..7d6080e
--- /dev/null
+++ b/sdk/pom.xml
@@ -0,0 +1,86 @@
+<?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">
+    <parent>
+        <groupId>com.iailab</groupId>
+        <artifactId>iailab-plat</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>sdk</artifactId>
+    <packaging>jar</packaging>
+
+    <name>${project.artifactId}</name>
+    <description>
+        sdk
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.5.13</version>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>2.0.55</version>
+        </dependency>
+        <dependency>
+            <groupId>com.iailab</groupId>
+            <artifactId>iailab-common</artifactId>
+            <version>${revision}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.hibernate.validator</groupId>
+            <artifactId>hibernate-validator</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.iailab</groupId>
+            <artifactId>iailab-common-test</artifactId>
+        </dependency>
+    </dependencies>
+
+<!--    <build>-->
+<!--        &lt;!&ndash; 设置构建的 jar 包名 &ndash;&gt;-->
+<!--        <finalName>${project.artifactId}</finalName>-->
+<!--        <plugins>-->
+<!--            &lt;!&ndash; 打包 &ndash;&gt;-->
+<!--            <plugin>-->
+<!--                <groupId>org.springframework.boot</groupId>-->
+<!--                <artifactId>spring-boot-maven-plugin</artifactId>-->
+<!--                <version>${spring.boot.version}</version>-->
+<!--                <executions>-->
+<!--                    <execution>-->
+<!--                        <goals>-->
+<!--                            <goal>repackage</goal> &lt;!&ndash; 将引入的 jar 打入其中 &ndash;&gt;-->
+<!--                        </goals>-->
+<!--                    </execution>-->
+<!--                </executions>-->
+<!--            </plugin>-->
+<!--        </plugins>-->
+<!--    </build>-->
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <version>3.3.0</version>
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <addClasspath>true</addClasspath>
+                        </manifest>
+                    </archive>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/sdk/src/main/java/com/iailab/sdk/SdkMain.java b/sdk/src/main/java/com/iailab/sdk/SdkMain.java
new file mode 100644
index 0000000..56c8abc
--- /dev/null
+++ b/sdk/src/main/java/com/iailab/sdk/SdkMain.java
@@ -0,0 +1,14 @@
+package com.iailab.sdk;
+
+/**
+ * 项目的主类
+ *
+ * @author iailab
+ */
+public class SdkMain {
+
+    public static void main(String[] args) {
+        System.out.println("I am the main class");
+    }
+
+}
diff --git a/sdk/src/main/java/com/iailab/sdk/auth/client/IailabAuthClient.java b/sdk/src/main/java/com/iailab/sdk/auth/client/IailabAuthClient.java
new file mode 100644
index 0000000..dbfbdef
--- /dev/null
+++ b/sdk/src/main/java/com/iailab/sdk/auth/client/IailabAuthClient.java
@@ -0,0 +1,94 @@
+package com.iailab.sdk.auth.client;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.iailab.sdk.auth.client.dto.TokenDTO;
+import com.iailab.sdk.auth.client.vo.AuthLoginReqVO;
+import com.iailab.sdk.auth.config.SdkConfiguration;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.*;
+import org.springframework.stereotype.Component;
+import org.springframework.stereotype.Service;
+import org.springframework.util.Assert;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.*;
+
+/**
+ * @author Houzhongjian
+ * @Description
+ * @createTime 2025年02月18日
+ */
+@Component
+@Service
+public class IailabAuthClient {
+
+    /**
+     * 平台地址
+     */
+    public static String BASE_URL = "http://172.16.8.100:48080/admin-api";
+
+    /**
+     * 租户编号
+     */
+    public static String TENANT_ID = "1";
+
+    private static final RestTemplate restTemplate = new RestTemplate();
+
+    /**
+     * 用户名密码方式获取平台token
+     */
+    public static synchronized TokenDTO login(AuthLoginReqVO loginReqVO) throws Exception {
+        System.out.println("登录获取平台token");
+        ObjectMapper objectMapper = new ObjectMapper();
+        String paramString = objectMapper.writeValueAsString(loginReqVO);
+        // 1.1 构建请求头
+        HttpHeaders headers = new HttpHeaders();
+        addClientHeader(headers);
+        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
+        // 1.2 构建authenticate请求URL
+        String authenticateUrl = BASE_URL + "/system/auth/login";
+        // 2. 执行请求
+        ResponseEntity<Map<String, Object>> exchange = restTemplate.exchange(
+                authenticateUrl,
+                HttpMethod.POST,
+                new HttpEntity<>(paramString, headers),
+                new ParameterizedTypeReference<Map<String, Object>>() {
+                });
+        return handleResponse(exchange);
+    }
+
+    public static synchronized TokenDTO refreshToken(String refreshToken) {
+        System.out.println("刷新token");
+        // 1.1 构建请求头
+        HttpHeaders headers = new HttpHeaders();
+        addClientHeader(headers);
+        // 1.2 构建authenticate请求URL
+        String authenticateUrl = BASE_URL + "/system/auth/refresh-token?refreshToken=" + refreshToken;
+        // 2. 执行请求
+        ResponseEntity<Map<String, Object>> exchange = restTemplate.exchange(
+                authenticateUrl,
+                HttpMethod.POST,
+                new HttpEntity<>(headers),
+                new ParameterizedTypeReference<Map<String, Object>>() {
+                });
+        return handleResponse(exchange);
+    }
+
+    private static void addClientHeader(HttpHeaders headers) {
+        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+        headers.set("tenant-id", TENANT_ID);
+    }
+
+    // 统一处理响应
+    private static <T> TokenDTO handleResponse(ResponseEntity<T> response) {
+        Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
+        System.out.println(response);
+        TokenDTO authTokenDTO = new TokenDTO();
+        Map<String, Object> authMap = (Map<String, Object>)response.getBody();
+        Map<String, Object> tokenData = (Map<String, Object>)authMap.get("data");
+        authTokenDTO.setAccessToken(tokenData.get("accessToken").toString());
+        authTokenDTO.setRefreshToken(tokenData.get("refreshToken").toString());
+        authTokenDTO.setExpiresTime(Long.valueOf(tokenData.get("expiresTime").toString()));
+        return authTokenDTO;
+    }
+}
\ No newline at end of file
diff --git a/sdk/src/main/java/com/iailab/sdk/auth/client/dto/TokenDTO.java b/sdk/src/main/java/com/iailab/sdk/auth/client/dto/TokenDTO.java
new file mode 100644
index 0000000..b710033
--- /dev/null
+++ b/sdk/src/main/java/com/iailab/sdk/auth/client/dto/TokenDTO.java
@@ -0,0 +1,11 @@
+package com.iailab.sdk.auth.client.dto;
+
+import lombok.Data;
+
+@Data
+public class TokenDTO {
+    private String accessToken;
+    private String refreshToken;
+    private Long expiresTime;
+    private String tokenType;
+}
\ No newline at end of file
diff --git a/sdk/src/main/java/com/iailab/sdk/auth/client/vo/AuthLoginReqVO.java b/sdk/src/main/java/com/iailab/sdk/auth/client/vo/AuthLoginReqVO.java
new file mode 100644
index 0000000..d60d47a
--- /dev/null
+++ b/sdk/src/main/java/com/iailab/sdk/auth/client/vo/AuthLoginReqVO.java
@@ -0,0 +1,31 @@
+package com.iailab.sdk.auth.client.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.Pattern;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class AuthLoginReqVO {
+
+    @NotEmpty(message = "登录账号不能为空")
+    @Length(min = 4, max = 16, message = "账号长度为 4-16 位")
+    @Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母")
+    private String username;
+
+    @NotEmpty(message = "密码不能为空")
+    @Length(min = 4, max = 16, message = "密码长度为 4-16 位")
+    private String password;
+
+    // ========== 图片验证码相关 ==========
+    private String captchaVerification;
+
+
+}
diff --git a/sdk/src/main/java/com/iailab/sdk/auth/config/SdkConfiguration.java b/sdk/src/main/java/com/iailab/sdk/auth/config/SdkConfiguration.java
new file mode 100644
index 0000000..6b7d958
--- /dev/null
+++ b/sdk/src/main/java/com/iailab/sdk/auth/config/SdkConfiguration.java
@@ -0,0 +1,48 @@
+package com.iailab.sdk.auth.config;
+
+import org.springframework.context.annotation.Configuration;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+/**
+ * SDK配置文件
+ */
+@Configuration
+public class SdkConfiguration {
+
+    private String baseUrl;
+
+    public String getTenantId() {
+        return tenantId;
+    }
+
+    private String tenantId;
+
+    public static SdkConfiguration load() {
+        SdkConfiguration config = new SdkConfiguration();
+
+        try(InputStream is = SdkConfiguration.class.getResourceAsStream("/application.yaml")) {
+            Properties props = new Properties();
+            props.load(is);
+            config.baseUrl = props.getProperty("base-url");
+            config.tenantId = props.getProperty("tenant-id");
+        } catch (IOException e) {
+            // 处理异常或使用默认值
+        }
+
+        if(config.baseUrl == null) {
+            throw new IllegalStateException("BaseUrl must be configured");
+        }
+        if(config.tenantId  == null) {
+            throw new IllegalStateException("TenantId must be configured");
+        }
+
+        return config;
+    }
+
+    public String getBaseUrl() {
+        return baseUrl;
+    }
+}
diff --git a/sdk/src/main/java/com/iailab/sdk/auth/interceptor/AuthInterceptor.java b/sdk/src/main/java/com/iailab/sdk/auth/interceptor/AuthInterceptor.java
new file mode 100644
index 0000000..790b75f
--- /dev/null
+++ b/sdk/src/main/java/com/iailab/sdk/auth/interceptor/AuthInterceptor.java
@@ -0,0 +1,26 @@
+//package com.iailab.module.sdk.auth.interceptor;
+//
+//import org.springframework.http.HttpRequest;
+//import org.springframework.http.client.ClientHttpRequestExecution;
+//import org.springframework.http.client.ClientHttpRequestInterceptor;
+//import org.springframework.http.client.ClientHttpResponse;
+//
+//import java.io.IOException;
+//
+//public class AuthInterceptor implements ClientHttpRequestInterceptor {
+//
+//    private final AuthTokenHolder tokenHolder;
+//
+//    public AuthInterceptor(AuthTokenHolder tokenHolder) {
+//        this.tokenHolder = tokenHolder;
+//    }
+//
+//    @Override
+//    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) {
+//        if (tokenHolder.getToken() != null) {
+//            request.getHeaders().setBearerAuth(tokenHolder.getToken());
+//        }
+//        return execution.execute(request, body);
+//    }
+//
+//}
\ No newline at end of file
diff --git a/sdk/src/main/java/com/iailab/sdk/util/http/HttpClientFactory.java b/sdk/src/main/java/com/iailab/sdk/util/http/HttpClientFactory.java
new file mode 100644
index 0000000..d775d70
--- /dev/null
+++ b/sdk/src/main/java/com/iailab/sdk/util/http/HttpClientFactory.java
@@ -0,0 +1,162 @@
+package com.iailab.sdk.util.http;
+
+import com.alibaba.fastjson.JSON;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.config.CookieSpecs;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.config.Registry;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.ssl.SSLContexts;
+import org.apache.http.util.EntityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.net.ssl.SSLContext;
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.Map;
+
+public  class HttpClientFactory {
+    private static final Logger logger = LoggerFactory.getLogger(HttpClientFactory.class);
+    private static PoolingHttpClientConnectionManager clientConnectionManager=null;
+    // private static CloseableHttpClient httpClient=null;
+    private static RequestConfig config = RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD_STRICT).build();
+    // private final static Object syncLock = new Object();
+    private static boolean isInited=false;
+    /**
+     * 创建httpclient连接池并初始化
+     */
+    private static void init(){
+    if(isInited)return;
+        try {
+            //添加对https的支持,该sslContext没有加载客户端证书
+            // 如果需要加载客户端证书,请使用如下sslContext,其中KEYSTORE_FILE和KEYSTORE_PASSWORD分别是你的证书路径和证书密码
+            //KeyStore keyStore  =  KeyStore.getInstance(KeyStore.getDefaultType()
+            //FileInputStream instream =   new FileInputStream(new File(KEYSTORE_FILE));
+            //keyStore.load(instream, KEYSTORE_PASSWORD.toCharArray());
+            //SSLContext sslContext = SSLContexts.custom().loadKeyMaterial(keyStore,KEYSTORE_PASSWORD.toCharArray())
+            // .loadTrustMaterial(null, new TrustSelfSignedStrategy())
+            //.build();
+            SSLContext sslContext = SSLContexts.custom()
+                    .loadTrustMaterial(null, new TrustSelfSignedStrategy())
+                    .build();
+            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext,SSLConnectionSocketFactory.getDefaultHostnameVerifier());
+            Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
+                    .register("https", sslsf)
+                    .register("http", PlainConnectionSocketFactory.getSocketFactory())
+                    .build();
+            clientConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
+            clientConnectionManager.setMaxTotal(50);
+            clientConnectionManager.setDefaultMaxPerRoute(25);
+            isInited=true;
+        }catch (Exception e){
+            logger.warn("httpUtils init get exception:",e);
+        }
+    }
+
+
+    public static CloseableHttpClient getHttpClient(){
+        init();
+//        if(httpClient == null){
+//            synchronized (syncLock){
+//                if(httpClient == null){
+////                    CookieStore cookieStore = new BasicCookieStore();
+////                    BasicClientCookie cookie = new BasicClientCookie("sessionID", "######");
+////                    cookie.setDomain("#####");
+////                    cookie.setPath("/");
+////                    cookieStore.addCookie(cookie);
+//                    httpClient =HttpClients.custom().setConnectionManager(clientConnectionManager)
+//                            //.setDefaultCookieStore(cookieStore)
+//                            .setDefaultRequestConfig(config).build();
+//                }
+//            }
+//        }
+ //       return httpClient;
+        return HttpClients.custom().setConnectionManager(clientConnectionManager)
+                            //.setDefaultCookieStore(cookieStore)
+                            .setDefaultRequestConfig(config).build();
+    }
+
+
+    /**
+     * 设置请求头信息
+     * @param headers
+     * @param request
+     * @return
+     */
+    private static HttpRequest setHeaders(Map<String,Object> headers, HttpRequest request) {
+        for (Map.Entry entry : headers.entrySet()) {
+            if (!entry.getKey().equals("Cookie")) {
+                request.addHeader((String) entry.getKey(), (String) entry.getValue());
+            } else {
+                Map<String, Object> Cookies = (Map<String, Object>) entry.getValue();
+                for (Map.Entry entry1 : Cookies.entrySet()) {
+                    request.addHeader(new BasicHeader("Cookie", (String) entry1.getValue()));
+                }
+            }
+        }
+        return request;
+    }
+
+
+    /**
+     * post请求,使用json格式传参
+     * @param url
+     * @param data
+     * @param headers
+     * @return
+     */
+    public static <T> T httpPost(String url, String data, Map<String,Object> headers,Class<T> clazz){
+        CloseableHttpClient httpClient = getHttpClient();
+        HttpRequest request = new HttpPost(url);
+        if(headers!=null&&!headers.isEmpty()){
+            request = setHeaders(headers,request);
+        }
+        CloseableHttpResponse response = null;
+
+        try {
+            HttpPost httpPost = (HttpPost) request;
+            httpPost.setEntity(new StringEntity(data, ContentType.create("application/json", "UTF-8")));
+            response=httpClient.execute(httpPost);
+            HttpEntity entity = response.getEntity();
+            if (HttpStatus.SC_OK == response.getStatusLine().getStatusCode() && null != entity) {
+                String respBody = EntityUtils.toString(entity);
+                logger.info("validate st response:"+respBody);
+                T respObj = (T) JSON.parseObject(respBody, clazz);
+                return respObj;
+            }
+        } catch (IOException e) {
+            logger.error("http post exec error,msg"+e.getMessage(),e);
+        }
+        finally {
+            safeClose(response,null);
+           // safeClose(httpClient,null);
+        }
+        return null;
+    }
+
+    private static   void safeClose(Closeable closeable,String errMsg){
+        try{
+            if(closeable!=null)
+            closeable.close();
+        }catch (Exception ex){
+            if(errMsg!=null && "".equals(errMsg)){
+                logger.error(errMsg+",error:"+ex.getMessage(),ex);
+            }
+        }
+    }
+}
diff --git a/sdk/src/main/java/com/iailab/sdk/util/http/IailabHttpUtils.java b/sdk/src/main/java/com/iailab/sdk/util/http/IailabHttpUtils.java
new file mode 100644
index 0000000..b5cf6c4
--- /dev/null
+++ b/sdk/src/main/java/com/iailab/sdk/util/http/IailabHttpUtils.java
@@ -0,0 +1,106 @@
+package com.iailab.sdk.util.http;
+
+import com.iailab.sdk.auth.client.IailabAuthClient;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.util.EntityUtils;
+import org.springframework.util.CollectionUtils;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.Map;
+
+
+public class IailabHttpUtils {
+
+    /**
+     *
+     * @param url
+     * @param map
+     * @param charset
+     * @return
+     */
+    public static String doGet(String url, Map<String, String> map, String charset, String accessToken) {
+        System.out.println("start doGet url: " + url);
+        org.apache.http.client.HttpClient httpClient = null;
+        HttpGet httpGet = null;
+        String result = null;
+        try {
+            StringBuilder sb = new StringBuilder();
+            sb.append(url);
+            if (!CollectionUtils.isEmpty(map)) {
+                if ((url.indexOf("?") == -1)) {
+                    sb.append("?");
+                } else {
+                    sb.append("&");
+                }
+                map.forEach((k, v) -> {
+                    try {
+                        sb.append(k + "=" + URLEncoder.encode(v, charset) + "&");
+                    } catch (UnsupportedEncodingException e) {
+                        e.printStackTrace();
+                    }
+                });
+                sb.append("t=" + System.currentTimeMillis());
+            }
+            httpClient = HttpClientFactory.getHttpClient();
+            httpGet = new HttpGet(sb.toString());
+            //设置参数
+            httpGet.addHeader("Accept", "application/json");
+            httpGet.addHeader("Content-Type", "application/json;charset=UTF-8");
+            httpGet.addHeader("Authorization", "Bearer " + accessToken);
+            httpGet.addHeader("Tenant-Id", String.valueOf(IailabAuthClient.TENANT_ID));
+            HttpResponse response = httpClient.execute(httpGet);
+            if (response != null) {
+                HttpEntity resEntity = response.getEntity();
+                if (resEntity != null) {
+                    result = EntityUtils.toString(resEntity, charset);
+                }
+            }
+        } catch (Exception ex) {
+            ex.printStackTrace();
+        }
+        return result;
+    }
+
+    /**
+     *
+     * @param url
+     * @param json
+     * @param charset
+     * @return
+     */
+    public static String doPost(String url, String json, String charset, String accessToken) {
+        System.out.println("start doPost url: " + url);
+        org.apache.http.client.HttpClient httpClient = null;
+        HttpPost httpPost = null;
+        String result = null;
+        try {
+            httpClient = HttpClientFactory.getHttpClient();
+            httpPost = new HttpPost(url);
+            //设置参数
+            httpPost.addHeader("Accept", "application/json");
+            httpPost.addHeader("Content-Type", "application/json;charset=UTF-8");
+            httpPost.addHeader("Authorization", "Bearer " + accessToken);
+            httpPost.addHeader("Tenant-Id", String.valueOf(IailabAuthClient.TENANT_ID));
+            StringEntity stringEntity = new StringEntity(json);
+            stringEntity.setContentEncoding("UTF-8");
+            stringEntity.setContentType("application/json");
+            httpPost.setEntity(stringEntity);
+            HttpResponse response = httpClient.execute(httpPost);
+            if (response != null) {
+                HttpEntity resEntity = response.getEntity();
+                if (resEntity != null) {
+                    result = EntityUtils.toString(resEntity, charset);
+                }
+            }
+        } catch (Exception ex) {
+            ex.printStackTrace();
+        }
+        return result;
+    }
+
+}
diff --git a/sdk/src/main/java/com/iailab/sdk/util/package-info.java b/sdk/src/main/java/com/iailab/sdk/util/package-info.java
new file mode 100644
index 0000000..230affa
--- /dev/null
+++ b/sdk/src/main/java/com/iailab/sdk/util/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * 每个模块的 util 包,放专属当前模块的 Utils 工具类
+ */
+package com.iailab.sdk.util;
diff --git a/sdk/src/main/resources/application-dev.yaml b/sdk/src/main/resources/application-dev.yaml
new file mode 100644
index 0000000..0a38e15
--- /dev/null
+++ b/sdk/src/main/resources/application-dev.yaml
@@ -0,0 +1,6 @@
+--- #################### SDK相关配置 ####################
+
+iailab:
+  auth:
+    base-url: http://172.16.8.100:48080/admin-api
+    tenant-id: 1
\ No newline at end of file
diff --git a/sdk/src/main/resources/application.yaml b/sdk/src/main/resources/application.yaml
new file mode 100644
index 0000000..946abfd
--- /dev/null
+++ b/sdk/src/main/resources/application.yaml
@@ -0,0 +1,13 @@
+spring:
+  application:
+    name: sdk
+
+  profiles:
+    active: dev
+
+iailab:
+  auth:
+    base-url: http://172.16.8.100:48080/admin-api
+    tenant-id: 1
+
+
diff --git a/sdk/src/test/java/com/iailab/sdk/IailabClientTest.java b/sdk/src/test/java/com/iailab/sdk/IailabClientTest.java
new file mode 100644
index 0000000..7f15846
--- /dev/null
+++ b/sdk/src/test/java/com/iailab/sdk/IailabClientTest.java
@@ -0,0 +1,39 @@
+package com.iailab.sdk;
+
+import com.iailab.framework.test.core.ut.BaseMockitoUnitTest;
+import com.iailab.sdk.auth.client.IailabAuthClient;
+import com.iailab.sdk.auth.client.dto.TokenDTO;
+import com.iailab.sdk.auth.client.vo.AuthLoginReqVO;
+import org.junit.jupiter.api.Test;
+
+import javax.annotation.Resource;
+
+
+/**
+ * {@link IailabClientTest} 的单元测试
+ *
+ * @author iailab
+ */
+public class IailabClientTest extends BaseMockitoUnitTest {
+
+    @Test
+    public void testLogin() throws Exception {
+        // 准备参数
+        AuthLoginReqVO authLoginReqVO = new AuthLoginReqVO();
+        authLoginReqVO.setUsername("sysadmin");
+        authLoginReqVO.setPassword("iailab2019");
+        TokenDTO login = IailabAuthClient.login(authLoginReqVO);
+        System.out.println(login.getAccessToken());
+        System.out.println(login.getRefreshToken());
+    }
+
+    @Test
+    public void testRefreshToken() {
+        // 准备参数
+        String refreshToken = "1d62031562364ed29d6d414fea97e2dd";
+        TokenDTO tokenDTO = IailabAuthClient.refreshToken(refreshToken);
+        System.out.println(tokenDTO);
+    }
+
+
+}
diff --git a/sdk/src/test/resources/application-unit-test.yaml b/sdk/src/test/resources/application-unit-test.yaml
new file mode 100644
index 0000000..fef9365
--- /dev/null
+++ b/sdk/src/test/resources/application-unit-test.yaml
@@ -0,0 +1,57 @@
+spring:
+  main:
+    lazy-initialization: true # 开启懒加载,加快速度
+    banner-mode: off # 单元测试,禁用 Banner
+
+--- #################### 数据库相关配置 ####################
+
+spring:
+  # 数据源配置项
+  datasource:
+    name: ruoyi-vue-pro
+    url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写
+    driver-class-name: org.h2.Driver
+    username: sa
+    password:
+    druid:
+      async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度
+      initial-size: 1 # 单元测试,配置为 1,提升启动速度
+  sql:
+    init:
+      schema-locations: classpath:/sql/create_tables.sql
+
+  # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
+  redis:
+    host: 127.0.0.1 # 地址
+    port: 16379 # 端口(单元测试,使用 16379 端口)
+    database: 0 # 数据库索引
+
+mybatis:
+  lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试
+
+mybatis-plus:
+  global-config:
+    db-config:
+      id-type: AUTO # H2 主键递增
+
+--- #################### 定时任务相关配置 ####################
+
+--- #################### 配置中心相关配置 ####################
+
+--- #################### 服务保障相关配置 ####################
+
+# Lock4j 配置项(单元测试,禁用 Lock4j)
+
+--- #################### 监控相关配置 ####################
+
+--- #################### 平台相关配置 ####################
+
+# 平台配置项,设置当前项目所有自定义的配置
+iailab:
+  info:
+    base-package: com.iailab.module
+  captcha:
+    timeout: 5m
+    width: 160
+    height: 60
+    enable: true

--
Gitblit v1.9.3