diff --git a/.idea/misc.xml b/.idea/misc.xml index 132404b..6f5230a 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..2b63946 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index cad357e..2b1e6af 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,34 @@ ``` +## 使用 +```java + +public class DemoUserSDK { + + /** + * JHApiClient 是一个HTTP连接池,开发者需要复用 + * 其中https://192.168.87.25/appform为景行API服务的地址 + */ + private static final JHApiClient client = JHApiClient.build("https://192.168.87.25/appform"); + + + public static void main(String[] args) { + + // 初始化一个调用调用景行会话服务接口执行器 + JHAppApiExecution jhAppApiExecution = new JHAppApiExecution(client); + + // 调用启动会话的接口 + AppStartedInfo appStartedInfo = jhAppApiExecution.desktopStart("jhadmin", "linux_desktop", new AppStartRequest()); + + // 打印接口的调用结果 + System.out.println(appStartedInfo); + } + +} + +``` + ## 构建 一旦您检出代码,就可以使用Maven构建它。使用以下命令进行构建: ```shell diff --git a/pom.xml b/pom.xml index 858231c..b9603a3 100644 --- a/pom.xml +++ b/pom.xml @@ -40,6 +40,18 @@ org.apache.httpcomponents httpclient 4.5.13 + + + commons-codec + commons-codec + + + + + + commons-codec + commons-codec + 1.15 @@ -48,6 +60,19 @@ 2.0.25 + + cn.hutool + hutool-all + 5.8.25 + + + + org.projectlombok + lombok + 1.18.28 + provided + + junit junit @@ -132,14 +157,7 @@ The Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt - - - - - - - \ No newline at end of file diff --git a/src/main/java/com/jhinno/sdk/openapi/ArgsException.java b/src/main/java/com/jhinno/sdk/openapi/ArgsException.java index 336644d..1b62280 100644 --- a/src/main/java/com/jhinno/sdk/openapi/ArgsException.java +++ b/src/main/java/com/jhinno/sdk/openapi/ArgsException.java @@ -1,5 +1,7 @@ package com.jhinno.sdk.openapi; +import lombok.Getter; + /** *

* 这个异常标识传入的SDK方法的参数错误 @@ -17,13 +19,9 @@ package com.jhinno.sdk.openapi; * @author yanlongqi * @date 2024/1/31 10:36 */ +@Getter public class ArgsException extends RuntimeException { - /** - * 用户名 - */ - private String username; - /** * 错误信息 */ @@ -69,15 +67,6 @@ public class ArgsException extends RuntimeException { @Override public String getMessage() { - return String.format("[ErrorMessage]:%s", errorMessage); - } - - /** - * 获取一个错误信息 - * - * @return 错误信息 - */ - public String getErrorMessage() { return errorMessage; } diff --git a/src/main/java/com/jhinno/sdk/openapi/CommonConstant.java b/src/main/java/com/jhinno/sdk/openapi/CommonConstant.java index 5fb3a9b..e4a7f18 100644 --- a/src/main/java/com/jhinno/sdk/openapi/CommonConstant.java +++ b/src/main/java/com/jhinno/sdk/openapi/CommonConstant.java @@ -21,4 +21,15 @@ public class CommonConstant { * 默认的token有效时间(单位:分钟) */ public static final int DEFAULT_TOKEN_EFFECTIVE_TIME = 30; + + + /** + * token 默认剩余时间 + */ + public static final int DEFAULT_TOKEN_RESIDUE_TIME = 5; + + /** + * 获取token时AES加密的默认key + */ + public static final String DEFAULT_AES_KEY = "jin5no@aqec8gtw6"; } diff --git a/src/main/java/com/jhinno/sdk/openapi/ServiceException.java b/src/main/java/com/jhinno/sdk/openapi/ServiceException.java index 2205f75..83a82ca 100644 --- a/src/main/java/com/jhinno/sdk/openapi/ServiceException.java +++ b/src/main/java/com/jhinno/sdk/openapi/ServiceException.java @@ -28,11 +28,21 @@ package com.jhinno.sdk.openapi; */ public class ServiceException extends RuntimeException { + /** + * 请求错误码 + */ + private int errorCode; + /** * 错误信息 */ private String errorMessage; + /** + * 请求的路径 + */ + private String requestPath; + /** * 创建一个默认的实例 */ @@ -44,21 +54,60 @@ public class ServiceException extends RuntimeException { /** * 创建一个包含错误消息的实例。 * + * @param requestPath 请求路径 + * @param errorCode 错误码 * @param errorMessage 错误信息 */ - public ServiceException(String errorMessage) { - this(errorMessage, null); + public ServiceException(String requestPath, int errorCode, String errorMessage) { + this(requestPath, errorCode, errorMessage, null); } /** * 创建一个包含错误消息和异常的实例 * + * @param requestPath 请求路径 + * @param errorCode 错误码 * @param errorMessage 错误信息 * @param cause 一个异常 */ - public ServiceException(String errorMessage, Throwable cause) { + public ServiceException(String requestPath, int errorCode, String errorMessage, Throwable cause) { super(null, cause); this.errorMessage = errorMessage; + this.requestPath = requestPath; + this.errorCode = errorCode; + } + + + /** + * 获取请求码 + * + * @return 请求码 + */ + public int getErrorCode() { + return errorCode; + } + + /** + * 获取错信息 + * + * @return 错误信息 + */ + public String getErrorMessage() { + return errorMessage; + } + + /** + * 获取请求路径 + * + * @return 请求路径 + */ + public String getRequestPath() { + return requestPath; + } + + @Override + public String getMessage() { + return String.format("%s\n[请求路径]: %s\n[错误码]: %s", errorMessage, requestPath, errorCode); } } diff --git a/src/main/java/com/jhinno/sdk/openapi/api/JHApiExecution.java b/src/main/java/com/jhinno/sdk/openapi/api/JHApiExecution.java index 60685b8..955c5a6 100644 --- a/src/main/java/com/jhinno/sdk/openapi/api/JHApiExecution.java +++ b/src/main/java/com/jhinno/sdk/openapi/api/JHApiExecution.java @@ -1,5 +1,7 @@ package com.jhinno.sdk.openapi.api; +import cn.hutool.crypto.symmetric.AES; +import cn.hutool.crypto.symmetric.SymmetricCrypto; import com.alibaba.fastjson2.TypeReference; import com.jhinno.sdk.openapi.ArgsException; import com.jhinno.sdk.openapi.CommonConstant; @@ -13,6 +15,8 @@ import java.util.HashMap; import java.util.Map; /** + * 定义一个请求的执行器, + * * @author yanlongqi * @date 2024/1/30 19:39 */ @@ -21,8 +25,27 @@ public class JHApiExecution { /** * JHApiClient实例 */ - private static JHApiClient JH_API_CLIENT; + public JHApiClient jhApiClient; + /** + * token的超时时间 + */ + private int tokenTimeout = CommonConstant.DEFAULT_TOKEN_EFFECTIVE_TIME; + + /** + * token提前获取的时间 + */ + private int tokenResidueTime = CommonConstant.DEFAULT_TOKEN_RESIDUE_TIME; + + + /** + * 获取一个执行器的实例 + * + * @param jhApiClient 请求的客户端 + */ + protected JHApiExecution(JHApiClient jhApiClient) { + this.jhApiClient = jhApiClient; + } /** * 用户令牌的缓存 @@ -35,14 +58,33 @@ public class JHApiExecution { * @param jhApiClient 客户端实例 */ public void setJHApiClient(JHApiClient jhApiClient) { - this.JH_API_CLIENT = jhApiClient; + this.jhApiClient = jhApiClient; + } + + + /** + * 设置token超时的时间,单位:分钟 + * + * @param tokenTimeout token的超时时间 + */ + public void setTokenTimeout(int tokenTimeout) { + this.tokenTimeout = tokenTimeout; + } + + /** + * 设置提前获取token的时间,单位:分钟 + * + * @param tokenResidueTime 提前获取token的时间 + */ + public void setTokenResidueTime(int tokenResidueTime) { + this.tokenResidueTime = tokenResidueTime; } /** * 获取用户的Token * * @param username 用户名 - * @param isForce 是否强制获取token + * @param isForce 是否强制获取toke * @return 用户的token */ public String getToken(String username, boolean isForce) { @@ -50,15 +92,22 @@ public class JHApiExecution { throw new ArgsException("用户名称不能为空!"); } TokenInfo tokenInfo = TOKEN_INFO_MAP.get(username); - // 如果是强制获取、用户令牌为空、用户令牌过期等,则获取令牌 - if (isForce || tokenInfo == null || System.currentTimeMillis() - tokenInfo.getCurrentTimestamp() <= CommonConstant.DEFAULT_TOKEN_EFFECTIVE_TIME * 60 * 1000) { - Map params = new HashMap<>(2); - String url = JHApiClient.getUrl(AuthPathConstant.AUTH_TOKEN, params); - ResponseResult result = JH_API_CLIENT.get(url, new TypeReference>() { + // 防止因为服务器时间的问题二导致token不可用,可以通过此配置提前获取token + int tokenEffectiveTime = (tokenTimeout - tokenResidueTime) * 60 * 1000; + // 如果是强制获取、用户令牌为空、用户令牌过期等,则获取令牌 + if (isForce || tokenInfo == null || System.currentTimeMillis() - tokenInfo.getCurrentTimestamp() < tokenEffectiveTime) { + Map params = new HashMap<>(2); + params.put("timeout", tokenTimeout); + + AES aes = new AES(CommonConstant.DEFAULT_AES_KEY.getBytes()); + String base64 = aes.encryptBase64(String.format("%s,%s", username, System.currentTimeMillis())); + params.put("username", base64); + String url = JHApiClient.getUrl(AuthPathConstant.AUTH_TOKEN_PATH, params); + ResponseResult result = jhApiClient.get(url, new TypeReference>() { }); if (StringUtils.equals(result.getResult(), CommonConstant.FAILED)) { - throw new ServiceException("获取token失败"); + throw new ServiceException(AuthPathConstant.AUTH_TOKEN_PATH, result.getCode(), result.getMessage()); } Token token = result.getData(); tokenInfo = new TokenInfo(); @@ -70,5 +119,27 @@ public class JHApiExecution { return tokenInfo.getToken(); } + /** + * 获取用户的Token,获取缓存,不强制获取 + * + * @param username 用户名 + * @return 用户的token + */ + public String getToken(String username) { + return getToken(username, false); + } + + + /** + * 构建一个带token的请求头 + * + * @param username 用户名 + * @return 请求头 + */ + protected Map getHeaders(String username) { + Map headers = new HashMap<>(); + headers.put("token", getToken(username)); + return headers; + } } diff --git a/src/main/java/com/jhinno/sdk/openapi/api/app/AppPathConstant.java b/src/main/java/com/jhinno/sdk/openapi/api/app/AppPathConstant.java new file mode 100644 index 0000000..f910120 --- /dev/null +++ b/src/main/java/com/jhinno/sdk/openapi/api/app/AppPathConstant.java @@ -0,0 +1,13 @@ +package com.jhinno.sdk.openapi.api.app; + +/** + * @author yanlongqi + * @date 2024/2/1 16:27 + */ +public class AppPathConstant { + + /** + * 申请会话 + */ + public static final String APPS_START_PATH = "/ws/api/apps/{appId}/start"; +} diff --git a/src/main/java/com/jhinno/sdk/openapi/api/app/AppStartRequest.java b/src/main/java/com/jhinno/sdk/openapi/api/app/AppStartRequest.java new file mode 100644 index 0000000..c01e72b --- /dev/null +++ b/src/main/java/com/jhinno/sdk/openapi/api/app/AppStartRequest.java @@ -0,0 +1,90 @@ +package com.jhinno.sdk.openapi.api.app; + +import lombok.Data; + +/** + * 启动会话的请求参数 + * + * @author yanlongqi + * @date 2024/2/1 16:31 + */ +@Data +public class AppStartRequest { + + + /** + * 是否启动一个新的会话(非必填:默认:false) + *

+ * 如果是true则会启动一个新的会话,否则复用已经启动好的会话 + */ + private boolean startNew; + + + /** + * 工作路径 + */ + private String cwd; + + /** + * 工作文件 + */ + private String workFile; + + /** + * 指标 + */ + private String metrics; + + + /** + *

+ * 请求获取到的JHClient协议链接的过期目标时间。(必填) + *

+ *

+ * 注意: + *

+ * 1. 该时间戳是获取到的加密串的过期时间,需要传入一个未来的时间 + * (如:当前是2024-02-01 17:53:12,假设加密串5分钟之后过期,则该参数为:20240101175812) + *

+ *

+ * 2. 该时间错由景行的JHClient进行验证,并不是在服务端进行验证, + * 所以为了防止客户端的时间和服务器的时间不一致而导会话不能启动, + * 因此此字段尽量在浏览器的生成 + *

+ */ + private String currentTimestamp; + + /** + * 会话类型 + */ + private String sessionType; + + private String sessionOwner; + + /** + * 启动参数 + *

+ * 例如启动一个demo.exe 该demo.exe需要传入用户名,在命令行的的操作为 demo.exe -u admin + *

+ * + *

+ * 此处传入的参数则为:"-u admin" + *

+ */ + private String param; + + private String loginUser; + + private String sessionId; + + private String remoteAddr; + + private String remotePort; + + private boolean isInMeetingRoom; + + private String currentMeetingId; + + private boolean jhClientInstalled; + +} diff --git a/src/main/java/com/jhinno/sdk/openapi/api/app/AppStartedInfo.java b/src/main/java/com/jhinno/sdk/openapi/api/app/AppStartedInfo.java new file mode 100644 index 0000000..7c3ac8d --- /dev/null +++ b/src/main/java/com/jhinno/sdk/openapi/api/app/AppStartedInfo.java @@ -0,0 +1,38 @@ +package com.jhinno.sdk.openapi.api.app; + +import com.alibaba.fastjson2.JSON; +import lombok.Getter; + +/** + * 会话启动信息 + * + * @author yanlongqi + * @date 2024/2/1 18:39 + */ +@Getter +public class AppStartedInfo { + + /** + * JHClient的地址(用于拉起对应会话的JHClient客户端) + * + * + */ + private String jhappUrl; + + /** + * 会话id + */ + private String desktopId; + +// @Override +// public String toString() { +// return JSON.toJSONString(this); +// } +} diff --git a/src/main/java/com/jhinno/sdk/openapi/api/app/JHAppApiExecution.java b/src/main/java/com/jhinno/sdk/openapi/api/app/JHAppApiExecution.java new file mode 100644 index 0000000..5a3c77d --- /dev/null +++ b/src/main/java/com/jhinno/sdk/openapi/api/app/JHAppApiExecution.java @@ -0,0 +1,51 @@ +package com.jhinno.sdk.openapi.api.app; + +import cn.hutool.core.collection.CollectionUtil; +import com.alibaba.fastjson2.TypeReference; +import com.jhinno.sdk.openapi.CommonConstant; +import com.jhinno.sdk.openapi.ServiceException; +import com.jhinno.sdk.openapi.api.JHApiExecution; +import com.jhinno.sdk.openapi.api.ResponseResult; +import com.jhinno.sdk.openapi.client.JHApiClient; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; + +/** + * @author yanlongqi + * @date 2024/2/1 16:26 + */ +public class JHAppApiExecution extends JHApiExecution { + + /** + * 获取一个执行器的实例 + * + * @param jhApiClient 请求的客户端 + */ + public JHAppApiExecution(JHApiClient jhApiClient) { + super(jhApiClient); + } + + /** + * 启动一个会话 + * + * @param username 用户名 + * @param appId 应用拆 + * @param appStartRequest 启动参数 + * @return JHClient协议链接 + */ + public AppStartedInfo desktopStart(String username, String appId, AppStartRequest appStartRequest) { + String path = AppPathConstant.APPS_START_PATH.replace("{appId}", appId); + ResponseResult> result = jhApiClient.post(path, appStartRequest, getHeaders(username), new TypeReference>>() { + }); + if (StringUtils.equals(result.getResult(), CommonConstant.FAILED)) { + throw new ServiceException(path, result.getCode(), result.getMessage()); + } + List data = result.getData(); + if (CollectionUtil.isEmpty(data)) { + throw new ServiceException(path, result.getCode(), "获取到的会话信息为空"); + } + return data.get(0); + } + +} diff --git a/src/main/java/com/jhinno/sdk/openapi/api/auth/AuthJHApiClientExecution.java b/src/main/java/com/jhinno/sdk/openapi/api/auth/AuthJHApiClientExecution.java deleted file mode 100644 index 45040fb..0000000 --- a/src/main/java/com/jhinno/sdk/openapi/api/auth/AuthJHApiClientExecution.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.jhinno.sdk.openapi.api.auth; - -import com.jhinno.sdk.openapi.api.JHApiExecution; - -/** - * @author yanlongqi - * @date 2024/1/30 19:47 - */ -public class AuthJHApiClientExecution extends JHApiExecution { - - -} diff --git a/src/main/java/com/jhinno/sdk/openapi/api/auth/AuthPathConstant.java b/src/main/java/com/jhinno/sdk/openapi/api/auth/AuthPathConstant.java index 35c3be2..f169d9d 100644 --- a/src/main/java/com/jhinno/sdk/openapi/api/auth/AuthPathConstant.java +++ b/src/main/java/com/jhinno/sdk/openapi/api/auth/AuthPathConstant.java @@ -9,7 +9,7 @@ public class AuthPathConstant { /** * 获取用户token */ - public static final String AUTH_TOKEN = "/ws/api/auth/token"; + public static final String AUTH_TOKEN_PATH = "/ws/api/auth/token"; /** * 注销token diff --git a/src/main/java/com/jhinno/sdk/openapi/client/DefaultHttpClientConfig.java b/src/main/java/com/jhinno/sdk/openapi/client/DefaultHttpClientConfig.java index 0654cb6..4903146 100644 --- a/src/main/java/com/jhinno/sdk/openapi/client/DefaultHttpClientConfig.java +++ b/src/main/java/com/jhinno/sdk/openapi/client/DefaultHttpClientConfig.java @@ -20,17 +20,17 @@ public class DefaultHttpClientConfig { /** - * 默认socket连接超时的时间 + * 默认socket连接超时的时间(单位:秒) */ public static final int SOCKET_TIMEOUT = 5000; /** - * 默认连接超时的时间 + * 默认连接超时的时间(单位:秒) */ public static final int CONNECT_TIMEOUT = 5000; /** - * 默认请求超时的时间 + * 默认请求超时的时间(单位:秒) */ public static final int CONNECTION_REQUEST_TIMEOUT = 5000; } diff --git a/src/main/java/com/jhinno/sdk/openapi/client/JHApiClient.java b/src/main/java/com/jhinno/sdk/openapi/client/JHApiClient.java index 5010f3b..cdd07b5 100644 --- a/src/main/java/com/jhinno/sdk/openapi/client/JHApiClient.java +++ b/src/main/java/com/jhinno/sdk/openapi/client/JHApiClient.java @@ -42,12 +42,14 @@ public class JHApiClient { /** * 基础的请求URL地址 - * 如:https://192.168.3.12/appform + *

+ * 如:https://192.168.3.12/appform + *

*/ - private String baseUrl; + private final String baseUrl; /** - * 初始化一个JHApiClient的实例,值得注意的是该实例只能内部创建 + * 初始化一个JHApiClient的实例,可使用自定义的客户端 * * @param baseUrl 景行接口服务的基础地址 * @param closeableHttpClient 可关闭的HTTP客户端 @@ -66,7 +68,7 @@ public class JHApiClient { /** - * 每次发送请求的配置,如果该配置未进行设置则走{@link DefaultHttpClientConfig}中的默认配置 + * 每次发送请求的配置,如果该配置未进行设置则走 {@link DefaultHttpClientConfig} 中的默认配置 */ private RequestConfig requestConfig; @@ -85,7 +87,7 @@ public class JHApiClient { /** *

- * 通过{@link DefaultHttpClientConfig}默认配置的最大连接数和服务每次能并行接收的请求数量构建一个JHApiClient实例 + * 通过 {@link DefaultHttpClientConfig} 默认配置的最大连接数和服务每次能并行接收的请求数量构建一个JHApiClient实例 *

* * @param baseUrl 景行接口服务的基础地址 @@ -96,7 +98,7 @@ public class JHApiClient { } /** - * 通过外部传入的{@link CloseableHttpClient}构建一个请求客户端 + * 通过外部传入的 {@link CloseableHttpClient} 构建一个请求客户端 *

* * @param httpClient 请求连接池 @@ -151,9 +153,9 @@ public class JHApiClient { *

* *

- * {@link JHApiClient } 默认只配置了 socket连接超时的时间(socketTimeout) 、连接超时的时间(connectTimeout)、 - * 请求超时的时间(connectionRequestTimeout)这三项,其默认配置在{@link DefaultHttpClientConfig}中。 - * 如果你要自定义你自己的配置,则可以通过{@link HttpClients}构建自己的RequestConfig来请求接口 + * {@link JHApiClient} 默认只配置了 socket连接超时的时间(socketTimeout) 、连接超时的时间(connectTimeout)、 + * 请求超时的时间(connectionRequestTimeout)这三项,其默认配置在{@link DefaultHttpClientConfig} 中。 + * 如果你要自定义你自己的配置,则可以通过{@link HttpClients} 构建自己的RequestConfig来请求接口 *

* * @param requestConfig HTTP请求的配置 diff --git a/src/test/java/com/jhinno/sdk/openapi/test/app/AppApiTest.java b/src/test/java/com/jhinno/sdk/openapi/test/app/AppApiTest.java new file mode 100644 index 0000000..656c9a7 --- /dev/null +++ b/src/test/java/com/jhinno/sdk/openapi/test/app/AppApiTest.java @@ -0,0 +1,33 @@ +package com.jhinno.sdk.openapi.test.app; + +import com.jhinno.sdk.openapi.api.app.AppStartRequest; +import com.jhinno.sdk.openapi.api.app.AppStartedInfo; +import com.jhinno.sdk.openapi.api.app.JHAppApiExecution; +import com.jhinno.sdk.openapi.client.JHApiClient; +import org.junit.Test; + +import java.util.List; +import java.util.Map; + +/** + * 会话启动相关单元测试 + * + * @author yanlongqi + * @date 2024/2/1 16:47 + */ + +public class AppApiTest { + + /** + * 测试获取"jhadmin"的Linux桌面会话的JHClient链接 + */ + @Test + public void testStartApp() { + JHApiClient client = JHApiClient.build("https://192.168.87.25/appform"); + JHAppApiExecution jhAppApiExecution = new JHAppApiExecution(client); + AppStartedInfo appStartedInfo = jhAppApiExecution.desktopStart("jhadmin", "linux_desktop", new AppStartRequest()); + System.out.println(appStartedInfo); + } + + +} diff --git a/src/test/java/com/jhinno/sdk/openapi/test/auth/AuthApiTest.java b/src/test/java/com/jhinno/sdk/openapi/test/auth/AuthApiTest.java new file mode 100644 index 0000000..a3554c5 --- /dev/null +++ b/src/test/java/com/jhinno/sdk/openapi/test/auth/AuthApiTest.java @@ -0,0 +1,24 @@ +package com.jhinno.sdk.openapi.test.auth; + +import com.jhinno.sdk.openapi.api.app.JHAppApiExecution; +import com.jhinno.sdk.openapi.client.JHApiClient; +import org.junit.Test; + +/** + * 鉴权相关测试累 + * @author yanlongqi + * @date 2024/2/1 18:06 + */ +public class AuthApiTest { + + /** + * 启动会话 + */ + @Test + public void testStartApp() { + JHApiClient client = JHApiClient.build("https://192.168.87.25/appform"); + JHAppApiExecution jhAppApiExecution = new JHAppApiExecution(client); + String token = jhAppApiExecution.getToken("jhadmin"); + System.out.println(token); + } +}