import java.net.URI; import java.net.URLDecoder; import java.net.URLEncoder; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.time.Instant; import java.util.ArrayList; import java.util.Base64; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.UUID; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; public final class DggOpenPlatformClient { private final HttpClient httpClient = HttpClient.newHttpClient(); private final String baseUrl; private final String appId; private final String appSecret; public DggOpenPlatformClient(String baseUrl, String appId, String appSecret) { this.baseUrl = baseUrl.replaceAll("/+$", ""); this.appId = appId; this.appSecret = appSecret; } public String get(String pathWithQuery) throws Exception { return request("GET", pathWithQuery, null, null); } public String get(String pathWithQuery, String bearerToken) throws Exception { return request("GET", pathWithQuery, null, bearerToken); } public String post(String pathWithQuery, String jsonBody) throws Exception { return request("POST", pathWithQuery, jsonBody, null); } public String post(String pathWithQuery, String jsonBody, String bearerToken) throws Exception { return request("POST", pathWithQuery, jsonBody, bearerToken); } public String request(String method, String pathWithQuery, String jsonBody, String bearerToken) throws Exception { String body = jsonBody == null ? "" : jsonBody; String timestamp = String.valueOf(Instant.now().toEpochMilli()); String nonce = UUID.randomUUID().toString().replace("-", ""); String methodUpper = method.toUpperCase(); String signature = sign(methodUpper, pathWithQuery, body, timestamp, nonce); HttpRequest.Builder builder = HttpRequest.newBuilder() .uri(URI.create(baseUrl + pathWithQuery)) .header("Content-Type", "application/json") .header("X-DGG-App-Id", appId) .header("X-DGG-Timestamp", timestamp) .header("X-DGG-Nonce", nonce) .header("X-DGG-Signature", signature); if (bearerToken != null && !bearerToken.isBlank()) { builder.header("Authorization", "Bearer " + bearerToken); } HttpRequest request = builder .method(methodUpper, body.isEmpty() ? HttpRequest.BodyPublishers.noBody() : HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8)) .build(); return httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)).body(); } public String login(String account, String password, String clientType) throws Exception { return post("/openapi/v1/auth/login", "{" + "\"account\":" + jsonString(account) + "," + "\"password\":" + jsonString(password) + "," + "\"clientType\":" + jsonString(clientType == null ? "web" : clientType) + "}"); } public String registerUser( String username, String password, String displayName, String externalUserId, String mobile, String email) throws Exception { return post("/openapi/v1/auth/register", "{" + "\"username\":" + jsonString(username) + "," + "\"password\":" + jsonString(password) + "," + "\"displayName\":" + jsonString(displayName == null ? username : displayName) + "," + "\"externalUserId\":" + jsonString(externalUserId) + "," + "\"mobile\":" + jsonString(mobile) + "," + "\"email\":" + jsonString(email) + "}"); } public String listAuthUsers( int pageNo, int pageSize, String keyword, String status, String globalStatus, Boolean realNameVerified, String mobile, String email) throws Exception { Map params = new LinkedHashMap<>(); params.put("pageNo", String.valueOf(pageNo)); params.put("pageSize", String.valueOf(pageSize)); params.put("keyword", keyword); params.put("status", status); params.put("globalStatus", globalStatus); params.put("realNameVerified", realNameVerified == null ? null : String.valueOf(realNameVerified)); params.put("mobile", mobile); params.put("email", email); return get(withQuery("/openapi/v1/auth/users", params)); } public String getAuthUser(String userId) throws Exception { return get("/openapi/v1/auth/users/" + urlEncode(userId)); } public String getAuthUserByExternalId(String externalUserId) throws Exception { return get("/openapi/v1/auth/users/by-external/" + urlEncode(externalUserId)); } public String updateAuthUser(String userId, String jsonBody) throws Exception { return request("PUT", "/openapi/v1/auth/users/" + urlEncode(userId), jsonBody, null); } public String disableAuthUser(String userId) throws Exception { return post("/openapi/v1/auth/users/" + urlEncode(userId) + "/disable", "{}"); } public String enableAuthUser(String userId) throws Exception { return post("/openapi/v1/auth/users/" + urlEncode(userId) + "/enable", "{}"); } public String resetAuthUserPassword(String userId) throws Exception { return post("/openapi/v1/auth/users/" + urlEncode(userId) + "/password/reset", "{}"); } public String resetAuthUserPassword(String userId, String newPassword) throws Exception { if (newPassword == null) { return resetAuthUserPassword(userId); } return post("/openapi/v1/auth/users/" + urlEncode(userId) + "/password/reset", "{" + "\"newPassword\":" + jsonString(newPassword) + "}"); } public String listAuthUserSessions( String userId, int pageNo, int pageSize, String status, String clientType, String deviceId) throws Exception { Map params = new LinkedHashMap<>(); params.put("pageNo", String.valueOf(pageNo)); params.put("pageSize", String.valueOf(pageSize)); params.put("status", status); params.put("clientType", clientType); params.put("deviceId", deviceId); return get(withQuery("/openapi/v1/auth/users/" + urlEncode(userId) + "/sessions", params)); } public String revokeAuthUserSession(String userId, String sessionId) throws Exception { return post("/openapi/v1/auth/users/" + urlEncode(userId) + "/sessions/" + urlEncode(sessionId) + "/revoke", "{}"); } public String createAuthzRole(String jsonBody) throws Exception { return post("/openapi/v1/authz/roles", jsonBody); } public String listAuthzRoles(int pageNo, int pageSize, String keyword, String status) throws Exception { Map params = new LinkedHashMap<>(); params.put("pageNo", String.valueOf(pageNo)); params.put("pageSize", String.valueOf(pageSize)); params.put("keyword", keyword); params.put("status", status); return get(withQuery("/openapi/v1/authz/roles", params)); } public String getAuthzRole(String roleId) throws Exception { return get("/openapi/v1/authz/roles/" + urlEncode(roleId)); } public String updateAuthzRole(String roleId, String jsonBody) throws Exception { return request("PUT", "/openapi/v1/authz/roles/" + urlEncode(roleId), jsonBody, null); } public String enableAuthzRole(String roleId) throws Exception { return post("/openapi/v1/authz/roles/" + urlEncode(roleId) + "/enable", "{}"); } public String disableAuthzRole(String roleId) throws Exception { return post("/openapi/v1/authz/roles/" + urlEncode(roleId) + "/disable", "{}"); } public String setUserAuthzRoles(String userId, String jsonBody) throws Exception { return request("PUT", "/openapi/v1/authz/users/" + urlEncode(userId) + "/roles", jsonBody, null); } public String listUserAuthzRoles(String userId) throws Exception { return get("/openapi/v1/authz/users/" + urlEncode(userId) + "/roles"); } public String getUserAuthorizationSnapshot(String userId) throws Exception { return get("/openapi/v1/authz/users/" + urlEncode(userId) + "/permissions"); } public String getCurrentUserAuthorizationSnapshot(String accessToken) throws Exception { return get("/openapi/v1/authz/me/permissions", accessToken); } public String mobileLogin(String mobile, String verifyId, String code, String clientType) throws Exception { return mobileLogin(mobile, "login", verifyId, code, clientType); } public String mobileLogin(String mobile, String scene, String verifyId, String code, String clientType) throws Exception { return post("/openapi/v1/auth/mobile-login", "{" + "\"mobile\":" + jsonString(mobile) + "," + "\"scene\":" + jsonString(scene) + "," + "\"verifyId\":" + jsonString(verifyId) + "," + "\"code\":" + jsonString(code) + "," + "\"clientType\":" + jsonString(clientType == null ? "web" : clientType) + "}"); } public String getSsoLoginUrl(String jsonBody) throws Exception { return post("/openapi/v1/auth/sso/login-url", jsonBody); } public String exchangeSsoToken(String jsonBody) throws Exception { return post("/openapi/v1/auth/sso/token", jsonBody); } public String refreshToken(String refreshToken) throws Exception { return post("/openapi/v1/auth/token/refresh", "{\"refreshToken\":" + jsonString(refreshToken) + "}"); } public String introspectToken(String token) throws Exception { return post("/openapi/v1/auth/token/introspect", "{\"token\":" + jsonString(token) + "}"); } public String userinfo(String accessToken) throws Exception { return get("/openapi/v1/auth/userinfo", accessToken); } public String changePassword(String accessToken, String oldPassword, String newPassword) throws Exception { return post("/openapi/v1/auth/password/change", "{" + "\"oldPassword\":" + jsonString(oldPassword) + "," + "\"newPassword\":" + jsonString(newPassword) + "}", accessToken); } public String logout(String refreshToken, String accessToken) throws Exception { return post("/openapi/v1/auth/logout", "{\"refreshToken\":" + jsonString(refreshToken) + "}", accessToken); } public String createSmsTemplate(String jsonBody) throws Exception { return post("/openapi/v1/sms/templates", jsonBody); } public String getSmsTemplate(String templateId) throws Exception { return get("/openapi/v1/sms/templates/" + urlEncode(templateId)); } public String listSmsTemplates(int pageNo, int pageSize, String scene, String auditStatus, String keyword) throws Exception { return get(withQuery("/openapi/v1/sms/templates", pageNo, pageSize, scene, auditStatus, keyword)); } public String sendSmsMessage(String jsonBody) throws Exception { return post("/openapi/v1/sms/messages", jsonBody); } public String sendBatchSmsMessages(String jsonBody) throws Exception { return post("/openapi/v1/sms/batch-messages", jsonBody); } public String listSmsMessages( int pageNo, int pageSize, String status, String scene, String templateId, String provider, String bizNo, String keyword, String createdFrom, String createdTo) throws Exception { Map params = new LinkedHashMap<>(); params.put("pageNo", String.valueOf(pageNo)); params.put("pageSize", String.valueOf(pageSize)); params.put("status", status); params.put("scene", scene); params.put("templateId", templateId); params.put("provider", provider); params.put("bizNo", bizNo); params.put("keyword", keyword); params.put("createdFrom", createdFrom); params.put("createdTo", createdTo); return get(withQuery("/openapi/v1/sms/messages", params)); } public String getSmsMessage(String messageId) throws Exception { return get("/openapi/v1/sms/messages/" + urlEncode(messageId)); } public String sendSmsCode(String mobile, String scene, String templateId) throws Exception { return post("/openapi/v1/verification-codes/sms/send", "{" + "\"mobile\":" + jsonString(mobile) + "," + "\"scene\":" + jsonString(scene) + "," + "\"templateId\":" + jsonString(templateId) + "}"); } public String verifySmsCode(String mobile, String scene, String verifyId, String code) throws Exception { return post("/openapi/v1/verification-codes/sms/verify", "{" + "\"mobile\":" + jsonString(mobile) + "," + "\"scene\":" + jsonString(scene) + "," + "\"verifyId\":" + jsonString(verifyId) + "," + "\"code\":" + jsonString(code) + "}"); } public String createEmailTemplate(String jsonBody) throws Exception { return post("/openapi/v1/emails/templates", jsonBody); } public String getEmailTemplate(String templateId) throws Exception { return get("/openapi/v1/emails/templates/" + urlEncode(templateId)); } public String listEmailTemplates(int pageNo, int pageSize, String scene, String auditStatus, String keyword) throws Exception { return get(withQuery("/openapi/v1/emails/templates", pageNo, pageSize, scene, auditStatus, keyword)); } public String sendEmailMessage(String jsonBody) throws Exception { return post("/openapi/v1/emails/messages", jsonBody); } public String sendBatchEmailMessages(String jsonBody) throws Exception { return post("/openapi/v1/emails/batch-messages", jsonBody); } public String listEmailMessages( int pageNo, int pageSize, String status, String scene, String templateId, String provider, String bizNo, String keyword, String createdFrom, String createdTo) throws Exception { Map params = new LinkedHashMap<>(); params.put("pageNo", String.valueOf(pageNo)); params.put("pageSize", String.valueOf(pageSize)); params.put("status", status); params.put("scene", scene); params.put("templateId", templateId); params.put("provider", provider); params.put("bizNo", bizNo); params.put("keyword", keyword); params.put("createdFrom", createdFrom); params.put("createdTo", createdTo); return get(withQuery("/openapi/v1/emails/messages", params)); } public String getEmailMessage(String messageId) throws Exception { return get("/openapi/v1/emails/messages/" + urlEncode(messageId)); } public String sendEmailCode(String email, String scene, String templateId) throws Exception { return post("/openapi/v1/verification-codes/email/send", "{" + "\"email\":" + jsonString(email) + "," + "\"scene\":" + jsonString(scene) + "," + "\"templateId\":" + jsonString(templateId) + "}"); } public String verifyEmailCode(String email, String scene, String verifyId, String code) throws Exception { return post("/openapi/v1/verification-codes/email/verify", "{" + "\"email\":" + jsonString(email) + "," + "\"scene\":" + jsonString(scene) + "," + "\"verifyId\":" + jsonString(verifyId) + "," + "\"code\":" + jsonString(code) + "}"); } public String validateInvoiceTitle(String jsonBody) throws Exception { return post("/openapi/v1/invoice-titles/validate", jsonBody); } public String createInvoiceApplication(String jsonBody) throws Exception { return post("/openapi/v1/invoices/applications", jsonBody); } public String listInvoiceApplications( int pageNo, int pageSize, String status, String invoiceType, String provider, String externalOrderNo, String invoiceNo, String redStatus, String keyword, String createdFrom, String createdTo) throws Exception { Map params = new LinkedHashMap<>(); params.put("pageNo", String.valueOf(pageNo)); params.put("pageSize", String.valueOf(pageSize)); params.put("status", status); params.put("invoiceType", invoiceType); params.put("provider", provider); params.put("externalOrderNo", externalOrderNo); params.put("invoiceNo", invoiceNo); params.put("redStatus", redStatus); params.put("keyword", keyword); params.put("createdFrom", createdFrom); params.put("createdTo", createdTo); return get(withQuery("/openapi/v1/invoices/applications", params)); } public String getInvoiceApplication(String applicationId) throws Exception { return get("/openapi/v1/invoices/applications/" + urlEncode(applicationId)); } public String getInvoiceApplicationByExternalOrder(String externalOrderNo) throws Exception { return get("/openapi/v1/invoices/by-external-order?externalOrderNo=" + urlEncode(externalOrderNo)); } public String syncInvoiceApplication(String applicationId) throws Exception { return post("/openapi/v1/invoices/applications/" + urlEncode(applicationId) + "/sync", "{}"); } public String getInvoiceDownload(String applicationId) throws Exception { return get("/openapi/v1/invoices/applications/" + urlEncode(applicationId) + "/download"); } public String cancelInvoiceApplication(String applicationId, String jsonBody) throws Exception { return post("/openapi/v1/invoices/applications/" + urlEncode(applicationId) + "/cancel", jsonBody == null ? "{}" : jsonBody); } public String createInvoiceRedApplication(String jsonBody) throws Exception { return post("/openapi/v1/invoices/red-applications", jsonBody); } public String listInvoiceRedApplications( int pageNo, int pageSize, String status, String invoiceApplicationId, String provider, String externalRedNo, String providerRedNo, String keyword, String createdFrom, String createdTo) throws Exception { Map params = new LinkedHashMap<>(); params.put("pageNo", String.valueOf(pageNo)); params.put("pageSize", String.valueOf(pageSize)); params.put("status", status); params.put("invoiceApplicationId", invoiceApplicationId); params.put("provider", provider); params.put("externalRedNo", externalRedNo); params.put("providerRedNo", providerRedNo); params.put("keyword", keyword); params.put("createdFrom", createdFrom); params.put("createdTo", createdTo); return get(withQuery("/openapi/v1/invoices/red-applications", params)); } public String getInvoiceRedApplication(String redApplicationId) throws Exception { return get("/openapi/v1/invoices/red-applications/" + urlEncode(redApplicationId)); } public String getInvoiceRedApplicationByExternalRedNo(String externalRedNo) throws Exception { Map params = new LinkedHashMap<>(); params.put("externalRedNo", externalRedNo); return get(withQuery("/openapi/v1/invoices/red-applications/by-external-red", params)); } public String syncInvoiceRedApplication(String redApplicationId) throws Exception { return post("/openapi/v1/invoices/red-applications/" + urlEncode(redApplicationId) + "/sync", "{}"); } public String createPaymentOrder(String jsonBody) throws Exception { return post("/openapi/v1/payments/orders", jsonBody); } public String listPaymentOrders( int pageNo, int pageSize, String status, String payChannel, String provider, String merchantOrderNo, String keyword, String createdFrom, String createdTo) throws Exception { Map params = new LinkedHashMap<>(); params.put("pageNo", String.valueOf(pageNo)); params.put("pageSize", String.valueOf(pageSize)); params.put("status", status); params.put("payChannel", payChannel); params.put("provider", provider); params.put("merchantOrderNo", merchantOrderNo); params.put("keyword", keyword); params.put("createdFrom", createdFrom); params.put("createdTo", createdTo); return get(withQuery("/openapi/v1/payments/orders", params)); } public String getPaymentOrder(String payOrderId) throws Exception { return get("/openapi/v1/payments/orders/" + urlEncode(payOrderId)); } public String getPaymentOrderByMerchantOrderNo(String merchantOrderNo) throws Exception { Map params = new LinkedHashMap<>(); params.put("merchantOrderNo", merchantOrderNo); return get(withQuery("/openapi/v1/payments/orders/by-merchant-order", params)); } public String syncPaymentOrder(String payOrderId) throws Exception { return post("/openapi/v1/payments/orders/" + urlEncode(payOrderId) + "/sync", "{}"); } public String closePaymentOrder(String payOrderId) throws Exception { return post("/openapi/v1/payments/orders/" + urlEncode(payOrderId) + "/close", "{}"); } public String createPaymentRefund(String jsonBody) throws Exception { return post("/openapi/v1/payments/refunds", jsonBody); } public String listPaymentRefunds( int pageNo, int pageSize, String status, String payOrderId, String provider, String merchantRefundNo, String keyword, String createdFrom, String createdTo) throws Exception { Map params = new LinkedHashMap<>(); params.put("pageNo", String.valueOf(pageNo)); params.put("pageSize", String.valueOf(pageSize)); params.put("status", status); params.put("payOrderId", payOrderId); params.put("provider", provider); params.put("merchantRefundNo", merchantRefundNo); params.put("keyword", keyword); params.put("createdFrom", createdFrom); params.put("createdTo", createdTo); return get(withQuery("/openapi/v1/payments/refunds", params)); } public String getPaymentRefund(String refundId) throws Exception { return get("/openapi/v1/payments/refunds/" + urlEncode(refundId)); } public String getPaymentRefundByMerchantRefundNo(String merchantRefundNo) throws Exception { Map params = new LinkedHashMap<>(); params.put("merchantRefundNo", merchantRefundNo); return get(withQuery("/openapi/v1/payments/refunds/by-merchant-refund", params)); } public String syncPaymentRefund(String refundId) throws Exception { return post("/openapi/v1/payments/refunds/" + urlEncode(refundId) + "/sync", "{}"); } public String verifyIdentityIndividual(String jsonBody) throws Exception { return post("/openapi/v1/identity/individuals/verify", jsonBody); } public String verifyIdentityEnterprise(String jsonBody) throws Exception { return post("/openapi/v1/identity/enterprises/verify", jsonBody); } public String listIdentityVerificationRecords( int pageNo, int pageSize, String verificationType, String verifyMode, String status, String scene, String subjectId, String externalBizNo, String provider, String idNo, String unifiedSocialCreditCode, String mobile, String keyword, String createdFrom, String createdTo) throws Exception { Map params = new LinkedHashMap<>(); params.put("pageNo", String.valueOf(pageNo)); params.put("pageSize", String.valueOf(pageSize)); params.put("verificationType", verificationType); params.put("verifyMode", verifyMode); params.put("status", status); params.put("scene", scene); params.put("subjectId", subjectId); params.put("externalBizNo", externalBizNo); params.put("provider", provider); params.put("idNo", idNo); params.put("unifiedSocialCreditCode", unifiedSocialCreditCode); params.put("mobile", mobile); params.put("keyword", keyword); params.put("createdFrom", createdFrom); params.put("createdTo", createdTo); return get(withQuery("/openapi/v1/identity/records", params)); } public String getIdentityVerificationRecord(String recordId) throws Exception { return get("/openapi/v1/identity/records/" + urlEncode(recordId)); } public String createFaceVerificationSession(String jsonBody) throws Exception { return post("/openapi/v1/face/verification-sessions", jsonBody); } public String listFaceVerificationSessions( int pageNo, int pageSize, String status, String scene, String subjectId, String provider, String keyword, String createdFrom, String createdTo) throws Exception { Map params = new LinkedHashMap<>(); params.put("pageNo", String.valueOf(pageNo)); params.put("pageSize", String.valueOf(pageSize)); params.put("status", status); params.put("scene", scene); params.put("subjectId", subjectId); params.put("provider", provider); params.put("keyword", keyword); params.put("createdFrom", createdFrom); params.put("createdTo", createdTo); return get(withQuery("/openapi/v1/face/verification-sessions", params)); } public String getFaceVerificationSession(String sessionId) throws Exception { return get("/openapi/v1/face/verification-sessions/" + urlEncode(sessionId)); } public String syncFaceVerificationSession(String sessionId) throws Exception { return post("/openapi/v1/face/verification-sessions/" + urlEncode(sessionId) + "/sync", "{}"); } public String createExternalMailbox(String email, String provider, String accountLabel) throws Exception { return post("/openapi/v1/mailboxes/external", "{" + "\"email\":" + jsonString(email) + "," + "\"provider\":" + jsonString(provider == null ? "mock_mailbox" : provider) + "," + "\"accountLabel\":" + jsonString(accountLabel) + "}"); } public String listExternalMailboxes( int pageNo, int pageSize, String status, String verificationStatus, String provider, String keyword, String createdFrom, String createdTo) throws Exception { Map params = new LinkedHashMap<>(); params.put("pageNo", String.valueOf(pageNo)); params.put("pageSize", String.valueOf(pageSize)); params.put("status", status); params.put("verificationStatus", verificationStatus); params.put("provider", provider); params.put("keyword", keyword); params.put("createdFrom", createdFrom); params.put("createdTo", createdTo); return get(withQuery("/openapi/v1/mailboxes/external", params)); } public String getExternalMailbox(String mailboxId) throws Exception { return get("/openapi/v1/mailboxes/external/" + urlEncode(mailboxId)); } public String verifyExternalMailbox(String mailboxId) throws Exception { return post("/openapi/v1/mailboxes/external/" + urlEncode(mailboxId) + "/verify", "{}"); } public String disableExternalMailbox(String mailboxId) throws Exception { return post("/openapi/v1/mailboxes/external/" + urlEncode(mailboxId) + "/disable", "{}"); } public String enableExternalMailbox(String mailboxId) throws Exception { return post("/openapi/v1/mailboxes/external/" + urlEncode(mailboxId) + "/enable", "{}"); } public String sendExternalMailboxMessage(String mailboxId, String jsonBody) throws Exception { return post("/openapi/v1/mailboxes/external/" + urlEncode(mailboxId) + "/messages", jsonBody); } public String listExternalMailboxMessages(String mailboxId, int pageNo, int pageSize) throws Exception { return get("/openapi/v1/mailboxes/external/" + urlEncode(mailboxId) + "/messages?pageNo=" + pageNo + "&pageSize=" + pageSize); } public String getExternalMailboxMessage(String mailboxId, String messageId) throws Exception { return get("/openapi/v1/mailboxes/external/" + urlEncode(mailboxId) + "/messages/" + urlEncode(messageId)); } public String createFileRecord(String jsonBody) throws Exception { return post("/openapi/v1/files", jsonBody); } public String listFileRecords( int pageNo, int pageSize, String status, String scene, String storageProvider, String bizNo, String keyword, String createdFrom, String createdTo) throws Exception { Map params = new LinkedHashMap<>(); params.put("pageNo", String.valueOf(pageNo)); params.put("pageSize", String.valueOf(pageSize)); params.put("status", status); params.put("scene", scene); params.put("storageProvider", storageProvider); params.put("bizNo", bizNo); params.put("keyword", keyword); params.put("createdFrom", createdFrom); params.put("createdTo", createdTo); return get(withQuery("/openapi/v1/files", params)); } public String getFileRecord(String fileId) throws Exception { return get("/openapi/v1/files/" + urlEncode(fileId)); } public String createFileDownloadUrl(String fileId, String jsonBody) throws Exception { return post("/openapi/v1/files/" + urlEncode(fileId) + "/download-url", jsonBody); } public String disableFileRecord(String fileId) throws Exception { return post("/openapi/v1/files/" + urlEncode(fileId) + "/disable", "{}"); } public String createBlockchainEvidence(String jsonBody) throws Exception { return post("/openapi/v1/blockchain/evidence-records", jsonBody); } public String listBlockchainEvidence( int pageNo, int pageSize, String status, String hashAlgorithm, String evidenceHash, String bizNo, String provider, String txHash, String chainId, String keyword, String createdFrom, String createdTo) throws Exception { Map params = new LinkedHashMap<>(); params.put("pageNo", String.valueOf(pageNo)); params.put("pageSize", String.valueOf(pageSize)); params.put("status", status); params.put("hashAlgorithm", hashAlgorithm); params.put("evidenceHash", evidenceHash); params.put("bizNo", bizNo); params.put("provider", provider); params.put("txHash", txHash); params.put("chainId", chainId); params.put("keyword", keyword); params.put("createdFrom", createdFrom); params.put("createdTo", createdTo); return get(withQuery("/openapi/v1/blockchain/evidence-records", params)); } public String getBlockchainEvidence(String evidenceId) throws Exception { return get("/openapi/v1/blockchain/evidence-records/" + urlEncode(evidenceId)); } public String getBlockchainEvidenceByBizNo(String bizNo) throws Exception { Map params = new LinkedHashMap<>(); params.put("bizNo", bizNo); return get(withQuery("/openapi/v1/blockchain/evidence-records/by-biz-no", params)); } public String verifyBlockchainEvidence(String evidenceId, String evidenceHash) throws Exception { String body = evidenceHash == null || evidenceHash.trim().isEmpty() ? "{}" : "{\"evidenceHash\":" + jsonString(evidenceHash) + "}"; return post("/openapi/v1/blockchain/evidence-records/" + urlEncode(evidenceId) + "/verify", body); } public String sign(String method, String pathWithQuery, String body, String timestamp, String nonce) throws Exception { String canonical = String.join("\n", method.toUpperCase(), canonicalPath(pathWithQuery), canonicalQuery(pathWithQuery), "X-DGG-App-Id:" + appId, "X-DGG-Timestamp:" + timestamp, "X-DGG-Nonce:" + nonce, sha256Hex(body == null ? "" : body)); return hmacSha256Base64(appSecret, canonical); } public String callbackSignature(String callbackUrl, String body, String timestamp, String nonce) throws Exception { return callbackSignature(callbackUrl, body, timestamp, nonce, appId); } public String callbackSignature(String callbackUrl, String body, String timestamp, String nonce, String callbackAppId) throws Exception { URI uri = URI.create(callbackUrl); String canonical = String.join("\n", "POST", callbackPath(uri), canonicalQueryFromRaw(uri.getRawQuery()), "X-DGG-App-Id:" + callbackAppId, "X-DGG-Timestamp:" + timestamp, "X-DGG-Nonce:" + nonce, sha256Hex(body == null ? "" : body)); return hmacSha256Base64(appSecret, canonical); } public boolean verifyCallbackSignature( String callbackUrl, String body, String timestamp, String nonce, String signature) throws Exception { return verifyCallbackSignature(callbackUrl, body, timestamp, nonce, signature, 5 * 60 * 1000L); } public boolean verifyCallbackSignature( String callbackUrl, String body, String timestamp, String nonce, String signature, long maxSkewMs) throws Exception { if (callbackUrl == null || callbackUrl.isBlank() || timestamp == null || timestamp.isBlank() || nonce == null || nonce.isBlank() || signature == null || signature.isBlank()) { return false; } if (maxSkewMs >= 0) { long timestampMs; try { timestampMs = Long.parseLong(timestamp); } catch (NumberFormatException error) { return false; } if (Math.abs(Instant.now().toEpochMilli() - timestampMs) > maxSkewMs) return false; } String expected = callbackSignature(callbackUrl, body, timestamp, nonce); return MessageDigest.isEqual( expected.getBytes(StandardCharsets.UTF_8), signature.getBytes(StandardCharsets.UTF_8)); } private static String hmacSha256Base64(String secret, String value) throws Exception { Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); return Base64.getEncoder().encodeToString(mac.doFinal(value.getBytes(StandardCharsets.UTF_8))); } private static String callbackPath(URI uri) { String path = uri.getRawPath(); return path == null || path.isBlank() ? "/" : path; } private static String canonicalPath(String pathWithQuery) { int index = pathWithQuery.indexOf('?'); return index >= 0 ? pathWithQuery.substring(0, index) : pathWithQuery; } private static String canonicalQuery(String pathWithQuery) { int index = pathWithQuery.indexOf('?'); if (index < 0 || index == pathWithQuery.length() - 1) return ""; String[] pairs = pathWithQuery.substring(index + 1).split("&"); List params = new ArrayList<>(); for (String pair : pairs) { int eq = pair.indexOf('='); String key = eq >= 0 ? pair.substring(0, eq) : pair; String value = eq >= 0 ? pair.substring(eq + 1) : ""; params.add(new String[] { key, value }); } params.sort(Comparator.comparing(item -> item[0]).thenComparing(item -> item[1])); List encoded = new ArrayList<>(); for (String[] item : params) { encoded.add(urlEncode(item[0]) + "=" + urlEncode(item[1])); } return String.join("&", encoded); } private static String canonicalQueryFromRaw(String rawQuery) throws Exception { if (rawQuery == null || rawQuery.isBlank()) return ""; String[] pairs = rawQuery.split("&"); List params = new ArrayList<>(); for (String pair : pairs) { int eq = pair.indexOf('='); String key = eq >= 0 ? pair.substring(0, eq) : pair; String value = eq >= 0 ? pair.substring(eq + 1) : ""; params.add(new String[] { urlDecode(key), urlDecode(value) }); } params.sort(Comparator.comparing(item -> item[0]).thenComparing(item -> item[1])); List encoded = new ArrayList<>(); for (String[] item : params) { encoded.add(urlEncode(item[0]) + "=" + urlEncode(item[1])); } return String.join("&", encoded); } private static String urlDecode(String value) { return URLDecoder.decode(value, StandardCharsets.UTF_8); } private static String urlEncode(String value) { return URLEncoder.encode(value, StandardCharsets.UTF_8).replace("+", "%20"); } private static String withQuery(String path, int pageNo, int pageSize, String scene, String auditStatus, String keyword) { List params = new ArrayList<>(); params.add("pageNo=" + pageNo); params.add("pageSize=" + pageSize); if (scene != null && !scene.isBlank()) params.add("scene=" + urlEncode(scene)); if (auditStatus != null && !auditStatus.isBlank()) params.add("auditStatus=" + urlEncode(auditStatus)); if (keyword != null && !keyword.isBlank()) params.add("keyword=" + urlEncode(keyword)); return path + "?" + String.join("&", params); } private static String withQuery(String path, Map params) { List query = new ArrayList<>(); for (Map.Entry entry : params.entrySet()) { String value = entry.getValue(); if (value != null && !value.isBlank()) { query.add(urlEncode(entry.getKey()) + "=" + urlEncode(value)); } } return query.isEmpty() ? path : path + "?" + String.join("&", query); } private static String jsonString(String value) { if (value == null) return "null"; StringBuilder output = new StringBuilder("\""); for (int i = 0; i < value.length(); i++) { char c = value.charAt(i); switch (c) { case '"': output.append("\\\""); break; case '\\': output.append("\\\\"); break; case '\b': output.append("\\b"); break; case '\f': output.append("\\f"); break; case '\n': output.append("\\n"); break; case '\r': output.append("\\r"); break; case '\t': output.append("\\t"); break; default: if (c < 0x20) { output.append(String.format("\\u%04x", (int) c)); } else { output.append(c); } } } output.append("\""); return output.toString(); } private static String sha256Hex(String value) throws Exception { byte[] digest = MessageDigest.getInstance("SHA-256").digest(value.getBytes(StandardCharsets.UTF_8)); StringBuilder output = new StringBuilder(); for (byte b : digest) output.append(String.format("%02x", b)); return output.toString(); } }