Java-SDK


Java-SDK

概述

差旅Java-SDK是差旅平台面向IT系统java开发者提供的服务端开发工具包,其中包含签名、验签、加密以及解密相关工具。 通过Java-SDK,开发者可以高效的完成差旅系统对接,无需关心具体的接口鉴权细节,同时提供了接口的定义,提升开发者的对接效率。

本sdk 支持 jdk1.8+,并且对应版本需要支持第三方开源组件Bouncy Castle。

使用说明

步骤一:引入SDK包

下载Java-SDK文件,并将库文件导入到Java项目中,导入方法:

1、在idea上点击file-->Project Structure

2、选择Libraries--->点击+号-->选择Java

3、找到放jar文件的位置,选中jar文件,点击OK,然后下一步一直到最后选择OK就可以了

或者使用maven管理三方依赖,在pom文件中引入(需要将jar包下载后发布到内部仓库中)

<dependency>
    <groupId>com.huawei.itravel.trip.sdk</groupId>
	<artifactId>java-sdk</artifactId>
	<version>2.0.2</version>
</dependency>

步骤二:准备

公钥私钥文件(公钥提供给差旅平台)

生成方法: SDK支持导入openssl生成的密钥文件以及java keytool 生成的密钥文件。 openssl 或 keytool 二选一

openssl:

私钥:(企业可自行修改口令值123456为其它值)

openssl genrsa -out rsaprivatekey_pass.pem -passout pass:123456 -aes256 4096

公钥:

openssl req -new -x509 -key rsaprivatekey_pass.pem -out rsapublic_cert.cert -days 1095 -passin pass:123456

密钥对加载方法:

// 慧通私钥,不对外提供
PrivateKey serverPrivate = CipherUtil.loadPrivateKey(
        Thread.currentThread().getContextClassLoader().getResourceAsStream("cert/server/private.pem"), "pem",
        "123456");

// 该公钥由慧通提供
PublicKey serverPublic = CipherUtil.loadCER(
        Thread.currentThread().getContextClassLoader().getResourceAsStream("cert/server/public.cert")).getPublicKey();        
keytool:

生成密钥文件:(企业可自行修改别名、密码等为其他值)

keytool -genkeypair -alias CLIENT -keyalg RSA -storetype JKS -sigalg SHA256withRSA -storepass 123456 -keystore D://CLIENT.keystore

提取公钥:

keytool -export -alias CLIENT -file D://client_rfc.crt -keystore d://CLIENT.keystore -rfc

密钥对加载方法:

// 私钥由客户自己生成,对应的公钥需要在慧通系统里配置(可以联系企业管理员在前台配置)
PrivateKey clientPrivate = CipherUtil.loadKeyStore(
        Thread.currentThread().getContextClassLoader().getResourceAsStream("cert/client/CLIENT.keystore"),
        "CLIENT", "JKS", "123456", "123456").getPrivate();

// 客户公钥,提供给慧通
PublicKey clientPublic = CipherUtil.loadKeyStore(
        Thread.currentThread().getContextClassLoader().getResourceAsStream("cert/client/CLIENT.keystore"),
        "CLIENT", "JKS", "123456", "123456").getPublic();

企业密码(差旅平台分配)

如:ADADADADADADADADADADADADADADADAD

对接账号Id(差旅平台分配)

如:OpenApiCompanyNum

步骤三:使用示例

下面的实例代码中均提供了两种密钥加载方法,根据步骤二选择的密钥生成工具选择对应的加载方法即可 初始化api client

public class ApiCall {
    // 企业对接账号,可以联系企业管理员获取
    protected static final String AUTH_ID = "OpenApiCompanyNum";

    // 企业密码,可以联系企业管理员在前台配置
    private static final String AES_KEY = "ADADADADADADADADADADADADADADADAD";

    // 对接的环境域名
    private static final String SERVER_ADDRESS = "https://openapi-uat.hwht.com";

    protected static final ApiClient API_CLIENT;

    protected static final ClientAuth clientAuth;

    private static final Logger logger = LoggerFactory.getLogger(ApiTest.class);

    static {
        PrivateKey clientPrivate;
        PublicKey serverPublic;
        try {
            // 私钥由客户自己生成,对应的公钥需要在慧通系统里配置(可以联系企业管理员在前台配置)
            clientPrivate = CipherUtil.loadPrivateKey(
                Thread.currentThread().getContextClassLoader().getResourceAsStream("cert/private.pem"), "pem",
                "123456");

            // 该公钥由慧通提供
            serverPublic = CipherUtil.loadCER(
                Thread.currentThread().getContextClassLoader().getResourceAsStream("cert/public.cert")).getPublicKey();
        } catch (Exception ex) {
            SdkUtil.logError("init cert fail {}", ex);
            throw new RuntimeException(ex);
        }
        clientAuth = new ClientAuth(AUTH_ID, AES_KEY, clientPrivate, serverPublic);
        // 创建一个调用客户端,可以通过实现ApiHttpClient接口来封装自定义的client,该Demo采用默认的client
        API_CLIENT = SdkFactory.createClient(clientAuth, SERVER_ADDRESS, new DefaultHttpClient());

        // 根据需求决定是否放开日志打印
        SdkFactory.funcLogInfo = logger::info;
        SdkFactory.funcLogDebug = logger::debug;
        SdkFactory.funcLogError = logger::error;
    }
}

接口调用

public class App extends ApiCall {
    public static void main(String[] args) {
        // 测试单点登录
        testSSO() ;
        // 查询审批单列表
        testTrListQuery() ;
    }

    public static void testSSO() {
        OpenApiUrlLoginReq req = new OpenApiUrlLoginReq();
        req.setCorpCode(AUTH_ID);
        req.setLoginName("GL_99999");
        try {
            String queryString = clientAuth.getQueryStringByRequest("GET", "/v3/urllogin", SdkUtil.toJson(req));
            SdkUtil.logInfo("https://openapi-uat.hwht.com/v3/urllogin?" + queryString);
            //SdkUtil.logInfo("https://m-itravel-uat.hwht.com/openapi/v3/urllogin?" + queryString);
        } catch (Exception e) {
             SdkUtil.logInfo(e.toString());
        }
    }

    public static void testTrListQuery() {
        OpenApiQueryApproveListReq req = new OpenApiQueryApproveListReq();
        req.setVersion("3.0");
        req.setTimestamp(SdkUtil.toYYYYMMddHHmmss(LocalDateTime.now()));
        req.setMsgID(AUTH_ID+SdkUtil.toYYYYMMddHHmmssSSS(LocalDateTime.now()));
        req.setCorpCode(AUTH_ID);
        req.setLoginName("sysadmin");
        req.setStartCreateTime("20230701");
        req.setEndCreateTime("20230731");
        req.setPageIndex("1");
        req.setSize("10");
    
        // 客户端调用同步接口
        OpenApiQueryApproveListRsp resp = API_CLIENT.createApi(TrApi.class).queryTrList(req);
    
        // 打印返回结果
        SdkUtil.logInfo("respose is {}", resp);
        SdkUtil.logInfo("respose is {}", resp.isSuccess());
    }
}

纯JSON格式调用OpenApi接口

SDK中提供的工具大多都是public的,可以自行组合使用 如下面这个例子,可以直接将json消息,签名、加密后通过httpclint调用OpenApi接口

OpenApiTrSyncReq syncRequest = getSyncRequest();
String url = "https://openapi-uat.hwht.com/v3/syntr";
String body = SdkUtil.toJson(syncRequest);
String auth = clientAuth.signClientRequest("POST", "/v3/syntr", body);
String encrypted = clientAuth.encryptClientRequestWithJson(body);
Map<String, String> headers = new HashMap<>();
headers.put(com.google.common.net.HttpHeaders.AUTHORIZATION, auth);
headers.put(com.google.common.net.HttpHeaders.CONTENT_TYPE, "application/json");
headers.put(com.google.common.net.HttpHeaders.ACCEPT, "application/json");
ApiHttpClient.HttpResult result = new DefaultHttpClient().post(url, headers, encrypted, 10000, 60000);
String respString = clientAuth.verifyServerResponse("POST", "/v3/syntr", result.getAuthorization(), result.getBody());
OpenApiTrSyncResp resp = SdkUtil.fromJson(respString, OpenApiTrSyncResp.class);

其中clientAuth初始化方法参考上面的例子。

接口分组与API对应关系

具体分组里面的方法名基本符合url的小驼峰格式,具体可以点击接口查看url详情 如:url为adduserinfo,则方法名为addUserInfo

接口分组API
成员管理MemberApi
部门管理DeptApi
成本中心CostCenterApi
企业自有审批单管理TrApi
审批管理TrApi
城市编码CityApi
附加功能OtherApi
国内机票AirOrderApi
国际机票AirOrderApi
国内火车票TrainApi
国内打车CarApi
酒店订单HotelApi
结算单AccountApi
其他功能OtherApi

附springboot工程示例

新建配置类

package com.hwht.trip.sdk;

import com.huawei.trip.sdk.auth.CipherUtil;
import com.huawei.trip.sdk.auth.ClientAuth;
import com.huawei.trip.sdk.auth.ServerAuth;
import com.huawei.trip.sdk.spring.TripApiFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

import java.security.PrivateKey;
import java.security.PublicKey;
import java.time.Duration;

@Configuration
public class OpenApiConfiguration {
    // 企业对接账号,可以联系企业管理员获取
    protected static final String CLIENT_AUTH_ID = "OpenApiCompanyNum";

    protected static final String SERVER_AUTH_ID = "HWHT";

    // 企业密码,可以联系企业管理员在前台配置
    private static final String AES_KEY = "ADADADADADADADADADADADADADADADAD";

    @Bean
    public ClientAuth clientAuthRegistration() {
        PrivateKey clientPrivate;
        PublicKey serverPublic;
        try {
            // 私钥由客户自己生成,对应的公钥需要在慧通系统里配置(可以联系企业管理员在前台配置)
            clientPrivate = CipherUtil.loadKeyStore(
                    Thread.currentThread().getContextClassLoader().getResourceAsStream("cert/client/CLIENT.keystore"),
                    "CLIENT", "JKS", "123456", "123456").getPrivate();

            // 该公钥由慧通提供
            serverPublic = CipherUtil.loadCER(
                    Thread.currentThread().getContextClassLoader().getResourceAsStream("cert/server/public.cert")).getPublicKey();
        } catch (Exception ex) {
            SdkUtil.logError("init cert fail {}", ex);
            throw new RuntimeException(ex);
        }

        return new ClientAuth(CLIENT_AUTH_ID, AES_KEY, clientPrivate, serverPublic);
    }

    @Bean
    public TripApiFilter tripApiFilterRegistration(@Autowired ClientAuth clientAuth) {
        TripApiFilter tripApiFilter = new TripApiFilter(clientAuth);
        return tripApiFilter;
    }

    @Bean
    @Qualifier("tripApiClient")
    public RestTemplate openApiClient(@Autowired RestTemplateBuilder builder) {
        // 专门用于调用Open API的客户端,可能还需要自定义ClientHttpRequestFactory等来精细控制并发策略、SSL等
        return builder.setConnectTimeout(Duration.ofSeconds(5))
                .setReadTimeout(Duration.ofSeconds(60))
                .build();
    }

    @Bean
    public FilterRegistrationBean<TripApiFilter> authFilterRegistration(@Autowired TripApiFilter tripApiFilter) {
        // 拦截Open API请求,根据自己的开放接口的url进行配置
        FilterRegistrationBean<TripApiFilter> reg = new FilterRegistrationBean<>();
        reg.setFilter(tripApiFilter);
        reg.addUrlPatterns("/openapi/*");
        reg.setName("TripApiAuthFilter");
        return reg;
    }
}

其中 authFilterRegistration 方法为可选,可以实现回调接口(提供给慧通的接口,例如订单状态通知)请求消息解密、鉴权,响应消息签名、加密,客户只需要处理业务逻辑即可

authFilterRegistration 方法中用到的TripApiFilter实现

import com.huawei.trip.sdk.SdkUtil;
import com.huawei.trip.sdk.auth.ClientAuth;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;

import javax.security.auth.message.AuthException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * 通过拦截Servlet的请求,对Open API进行验签并解密数据,spring项目按需使用
 */
public class TripApiFilter implements Filter {
    private final ClientAuth clientAuth;

    public TripApiFilter(ClientAuth clientAuth) {
        this.clientAuth = clientAuth;
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
        throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        HttpServletResponse rsp = (HttpServletResponse) servletResponse;
        HttpRequestWrapper requestWrapper = new HttpRequestWrapper(req);
        HttpResponseWrapper responseWrapper = new HttpResponseWrapper(rsp);
        SdkUtil.logInfo("do open api auth filter for {}", req.getRequestURI());

        try {
            String authorization;
            String encryptedBody;
            String httpMethod = req.getMethod();
            if ("GET".equals(httpMethod)) {
                authorization = req.getQueryString();
                encryptedBody = req.getParameter("encrypt");
                encryptedBody = "{\"encrypt\":\"" + encryptedBody + "\"}";
            } else {
                authorization = req.getHeader(HttpHeaders.AUTHORIZATION);
                encryptedBody = requestWrapper.getBody();
            }
            SdkUtil.logDebug("receive authorization: {}", authorization);
            SdkUtil.logDebug("receive encrypted: {}", encryptedBody);
            String body = clientAuth.verifyClientRequest(req.getMethod(), req.getRequestURI(), authorization,
                    encryptedBody);
            requestWrapper.setBody(body);
            if ("GET".equals(httpMethod)) {
                Map<String, String[]> paramsMap = new HashMap<>();
                ((Map<String, String>) SdkUtil.fromJson(body, Map.class))
                        .forEach((key, value) -> paramsMap.put(key, new String[]{value}));
                requestWrapper.setParamsMap(paramsMap);
            }

            // 原处理链
            filterChain.doFilter(requestWrapper, responseWrapper);

            // 响应消息签名和转换
            body = responseWrapper.getCaptureBody();
            authorization = clientAuth.signServerResponse(req.getMethod(), req.getRequestURI(), body);
            SdkUtil.logDebug("response authorization: {}", authorization);
            String encrypt = clientAuth.encryptServerResponse(body);
            SdkUtil.logDebug("response body: {}", encrypt);
            rsp.addHeader(HttpHeaders.AUTHORIZATION, authorization);
            rsp.getWriter().write(encrypt);
        } catch (AuthException ex) {
            SdkUtil.logError(ex.getMessage(), ex);
            rsp.sendError(HttpStatus.UNAUTHORIZED.value(), ex.getMessage());
        } catch (Exception ex) {
            SdkUtil.logError(ex.getMessage(), ex);
            rsp.sendError(HttpStatus.UNAUTHORIZED.value(), ex.getMessage());
        }
    }
}

其中 openApiClient 方法为可选,可以实现自定义http调用客户端,自定义后通过下面的示例配置和使用 自定义http调用客户端(可选)

import com.huawei.trip.sdk.client.ApiHttpClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.util.Map;

/**
 * Spring配置示例代码
 */
@Slf4j
@Component
public class RestTemplateClient implements ApiHttpClient {

    @Autowired
    @Qualifier("tripApiClient")
    private RestTemplate tripApiClient;

    @Override
    public HttpResult post(String url, Map<String, String> headers, String body, int connTimeout, int readTimeout) {
        HttpHeaders httpHeaders = new HttpHeaders();
        headers.forEach((key, value) -> httpHeaders.add(key, value));
        HttpEntity<String> httpEntity = new HttpEntity(body, httpHeaders);
        log.info("send header: {}", headers);
        log.info("send body: {}", body);
        ResponseEntity<String> rsp = tripApiClient.exchange(url, HttpMethod.POST, httpEntity, String.class);
        return new HttpResult(rsp.getHeaders().getFirst(HttpHeaders.AUTHORIZATION), rsp.getBody());
    }

    @Override
    public HttpResult get(String url, Map<String, String> headers, Map<String, String> params, int connTimeout,
                          int readTimeout) {
        HttpHeaders httpHeaders = new HttpHeaders();
        headers.forEach((key, value) -> httpHeaders.add(key, value));
        HttpEntity<String> httpEntity = new HttpEntity(httpHeaders);
        log.info("send header: {}", headers);
        log.info("send body: {}", params);
        ResponseEntity<String> rsp = tripApiClient.exchange(url, HttpMethod.GET, httpEntity, String.class, params);
        return new HttpResult(rsp.getHeaders().getFirst(HttpHeaders.AUTHORIZATION), rsp.getBody());
    }
}

接口调用

// 对接的环境域名
private static final String SERVER_ADDRESS = "https://openapi-uat.hwht.com";

@Autowired
private ClientAuth clientAuth;

@Autowired
private RestTemplateClient restTemplateClient;

public void testSynctr() {

    // 获取审批单同步的请求
    OpenApiTrSyncReq syncRequest = getSyncRequest();

    // 客户端调用同步接口
    ApiClient apiClient = SdkFactory.createClient(clientAuth, SERVER_ADDRESS, restTemplateClient);
    OpenApiTrSyncResp resp = apiClient.createApi(TrApi.class).syntr(syncRequest);
    Assertions.assertTrue(resp.isSuccess());
}

public static OpenApiTrSyncReq getSyncRequest() {
    OpenApiTrSyncReq req = new OpenApiTrSyncReq();
    req.setCorpCode("OpenApiCompanyNum");
    // 参数设置细节省略
    // ...
    return req;
}