下载逻辑
This commit is contained in:
@@ -275,6 +275,9 @@ public class ContractLaunchDto implements Serializable {
|
||||
/** 签署完成后的合同文件大小(字节) */
|
||||
private Long signedFileSize;
|
||||
|
||||
/** 在线签章平台原始下载URL(保留原始地址,signedFilePath 存本地路径) */
|
||||
private String signedFileUrl;
|
||||
|
||||
/** 未在线签署的原因(线下签署时填写) */
|
||||
private String signedOfflineReason;
|
||||
|
||||
|
||||
@@ -36,5 +36,8 @@ public class ContractLaunchSealDto implements Serializable {
|
||||
private Long signedFileSize;
|
||||
|
||||
/** 未在线签署的原因(线下签署时填写) */
|
||||
/** 在线签章平台原始下载URL(云平台签署完成时返回的下载地址) */
|
||||
private String signedFileUrl;
|
||||
|
||||
private String signedOfflineReason;
|
||||
}
|
||||
|
||||
+68
-1
@@ -21,6 +21,12 @@ import com.nbport.zgwl.contractlaunch.utils.JsonUtils;
|
||||
import com.nbport.zgwl.contractpush.dto.PushPayloadDTO;
|
||||
import com.nbport.zgwl.contractpush.service.DataCollectionPushService;
|
||||
import com.nbport.zgwl.contractpush.service.SettlementPushService;
|
||||
import com.nbport.zgwl.seal.service.SealTokenService;
|
||||
import com.nbport.zgwl.seal.config.SealConfig;
|
||||
import com.nbport.zgwl.seal.utils.SealHttpClient;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.nbport.zgwl.service.LocalFileService;
|
||||
import com.nbport.zgwl.entity.SysUserEntity;
|
||||
import com.nbport.zgwl.exception.ServiceException;
|
||||
import com.nbport.zgwl.utils.AdminSecurityManage;
|
||||
@@ -54,6 +60,9 @@ public class ContractLaunchServiceImpl implements ContractLaunchService {
|
||||
private final SettlementPushService settlementPushService;
|
||||
private final DataCollectionPushService dataCollectionPushService;
|
||||
private final ContractLaunchLegalFileBridgeService legalFileBridgeService;
|
||||
private final LocalFileService localFileService;
|
||||
private final SealTokenService sealTokenService;
|
||||
private final SealConfig sealConfig;
|
||||
|
||||
/** 分页查询,附带合同方/文件/履行计划(列表页不查日志) */
|
||||
@Override
|
||||
@@ -390,7 +399,10 @@ public class ContractLaunchServiceImpl implements ContractLaunchService {
|
||||
contract.setSealContractId(sealDto.getSealContractId() == null || sealDto.getSealContractId().isBlank() ? contract.getSealContractId() : sealDto.getSealContractId());
|
||||
contract.setSealContractStatus(sealDto.getSealContractStatus());
|
||||
contract.setSignedFileName(sealDto.getSignedFileName());
|
||||
contract.setSignedFilePath(sealDto.getSignedFilePath());
|
||||
contract.setSignedFilePath(resolveSignedFilePath(sealDto));
|
||||
contract.setSignedFileUrl(sealDto.getSignedFileUrl() != null && !sealDto.getSignedFileUrl().isBlank()
|
||||
? sealDto.getSignedFileUrl()
|
||||
: (isExternalUrl(sealDto.getSignedFilePath()) ? sealDto.getSignedFilePath() : null));
|
||||
contract.setSignedFileType(sealDto.getSignedFileType());
|
||||
contract.setSignedFileSize(sealDto.getSignedFileSize());
|
||||
contract.setSignedOfflineReason(sealDto.getSignedOfflineReason());
|
||||
@@ -1261,6 +1273,61 @@ public class ContractLaunchServiceImpl implements ContractLaunchService {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果 signedFilePath 是外部URL,则下载到本地并返回本地路径;
|
||||
* 如果已是本地路径则直接返回。
|
||||
*/
|
||||
private String resolveSignedFilePath(ContractLaunchSealDto sealDto) {
|
||||
String filePath = sealDto.getSignedFilePath();
|
||||
if (filePath == null || filePath.isBlank()) {
|
||||
return filePath;
|
||||
}
|
||||
if (isExternalUrl(filePath)) {
|
||||
log.info("签章文件为外部URL,先通过签章API获取新下载地址: {}", filePath);
|
||||
String token = sealTokenService.getAccessToken();
|
||||
// 通过签章API获取服务端可用的下载URL
|
||||
String freshUrl = fetchSealDownloadUrl(sealDto.getSealContractId(), token, filePath);
|
||||
log.info("获取到签章下载地址: {}", freshUrl);
|
||||
String localPath = localFileService.downloadFromUrl(freshUrl, token);
|
||||
log.info("签章文件已下载到本地: {}", localPath);
|
||||
return localPath;
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过签章平台API获取下载URL(服务端IP绑定的新URL)
|
||||
*/
|
||||
private String fetchSealDownloadUrl(String sealContractId, String token, String fallbackUrl) {
|
||||
try {
|
||||
String apiUrl = sealConfig.getBaseUrl() + "/saas/api/contract/download";
|
||||
JSONObject body = new JSONObject();
|
||||
body.put("contractId", sealContractId);
|
||||
String response = SealHttpClient.post(apiUrl, body.toJSONString(), token, "application/json");
|
||||
log.info("签章平台下载API响应: {}", response);
|
||||
JSONObject result = JSON.parseObject(response);
|
||||
if (result != null && result.getBooleanValue("isSuccess")) {
|
||||
Object dataObj = result.get("data");
|
||||
log.info("签章平台data类型: {}, 值: {}", dataObj != null ? dataObj.getClass().getSimpleName() : "null", dataObj);
|
||||
if (dataObj instanceof JSONObject) {
|
||||
String url = ((JSONObject) dataObj).getString("downloadUrl");
|
||||
if (url != null && !url.isBlank()) return url;
|
||||
} else if (dataObj instanceof String && !((String) dataObj).isBlank()) {
|
||||
return (String) dataObj;
|
||||
}
|
||||
} else {
|
||||
log.warn("签章平台下载API返回失败: {}", response);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("通过签章API获取下载URL失败,使用原始URL: {}", e.getMessage());
|
||||
}
|
||||
return fallbackUrl;
|
||||
}
|
||||
|
||||
private boolean isExternalUrl(String path) {
|
||||
return path != null && (path.startsWith("http://") || path.startsWith("https://"));
|
||||
}
|
||||
|
||||
private String trim(String value) {
|
||||
return value == null ? null : value.trim();
|
||||
}
|
||||
|
||||
@@ -17,6 +17,11 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@@ -79,6 +84,128 @@ public class LocalFileService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从外部URL下载文件并保存到本地,返回本地相对路径(如 20260603/uuid.pdf)。
|
||||
* 文件名优先从 Content-Disposition 或 URL 路径提取,否则使用兜底名。
|
||||
*/
|
||||
public String downloadFromUrl(String fileUrl) {
|
||||
return downloadFromUrl(fileUrl, null);
|
||||
}
|
||||
|
||||
public String downloadFromUrl(String fileUrl, String accessToken) {
|
||||
if (fileUrl == null || fileUrl.isBlank()) {
|
||||
throw new ServiceException("下载URL不能为空");
|
||||
}
|
||||
// 先尝试不带 token(签章平台下载 URL 自带 JWT 认证)
|
||||
try {
|
||||
return doDownload(fileUrl, null);
|
||||
} catch (ServiceException e) {
|
||||
// 如果 401/403,再尝试带 token
|
||||
if (accessToken != null && !accessToken.isBlank() && e.getMessage().contains("40")) {
|
||||
log.info("无token下载失败,尝试带token重试: {}", fileUrl);
|
||||
return doDownload(fileUrl, accessToken);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private String doDownload(String fileUrl, String accessToken) {
|
||||
HttpURLConnection conn = null;
|
||||
try {
|
||||
String cleanUrl = fileUrl.contains("?") ? fileUrl.substring(0, fileUrl.indexOf("?")) : fileUrl;
|
||||
URL url = new URL(cleanUrl);
|
||||
conn = openConnection(url);
|
||||
conn.setConnectTimeout(30000);
|
||||
conn.setReadTimeout(60000);
|
||||
conn.setRequestProperty("User-Agent", "Mozilla/5.0");
|
||||
if (accessToken != null && !accessToken.isBlank()) {
|
||||
conn.setRequestProperty("access_token", accessToken);
|
||||
}
|
||||
|
||||
int responseCode = conn.getResponseCode();
|
||||
if (responseCode < 200 || responseCode >= 300) {
|
||||
// 读取错误响应体用于排查
|
||||
String errorBody = "";
|
||||
try (InputStream es = conn.getErrorStream()) {
|
||||
if (es != null) {
|
||||
errorBody = new String(es.readAllBytes(), StandardCharsets.UTF_8);
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
log.error("下载签章文件失败, URL={}, code={}, body={}", fileUrl, responseCode, errorBody);
|
||||
throw new ServiceException("下载签章文件失败,HTTP状态码: " + responseCode + ", 详情: " + errorBody);
|
||||
}
|
||||
|
||||
String fileName = extractFileName(fileUrl, conn);
|
||||
String suffix = FileStorageUtils.getFileSuffix(fileName);
|
||||
|
||||
Path rootPath = Paths.get(localPath).toAbsolutePath().normalize();
|
||||
Files.createDirectories(rootPath);
|
||||
String dateFolder = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
|
||||
String storedName = IdUtil.simpleUUID() + suffix;
|
||||
String relativePath = dateFolder + "/" + storedName;
|
||||
Path targetPath = FileStorageUtils.resolveFilePath(rootPath, relativePath);
|
||||
Files.createDirectories(targetPath.getParent());
|
||||
|
||||
try (InputStream is = conn.getInputStream()) {
|
||||
Files.copy(is, targetPath);
|
||||
}
|
||||
|
||||
log.info("从URL下载文件成功: {} -> {}", fileUrl, targetPath);
|
||||
return relativePath;
|
||||
} catch (IOException e) {
|
||||
log.error("从URL下载文件失败: {}", fileUrl, e);
|
||||
throw new ServiceException("下载签章文件失败: " + e.getMessage());
|
||||
} finally {
|
||||
if (conn != null) {
|
||||
conn.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private HttpURLConnection openConnection(URL url) throws IOException {
|
||||
try {
|
||||
if ("https".equalsIgnoreCase(url.getProtocol())) {
|
||||
javax.net.ssl.HttpsURLConnection httpsConn = (javax.net.ssl.HttpsURLConnection) url.openConnection();
|
||||
javax.net.ssl.SSLContext sslContext = javax.net.ssl.SSLContext.getInstance("TLS");
|
||||
sslContext.init(null, new javax.net.ssl.TrustManager[]{
|
||||
new javax.net.ssl.X509TrustManager() {
|
||||
public void checkClientTrusted(java.security.cert.X509Certificate[] c, String a) {}
|
||||
public void checkServerTrusted(java.security.cert.X509Certificate[] c, String a) {}
|
||||
public java.security.cert.X509Certificate[] getAcceptedIssuers() { return new java.security.cert.X509Certificate[0]; }
|
||||
}
|
||||
}, new java.security.SecureRandom());
|
||||
httpsConn.setSSLSocketFactory(sslContext.getSocketFactory());
|
||||
httpsConn.setHostnameVerifier((hostname, session) -> true);
|
||||
return httpsConn;
|
||||
}
|
||||
return (HttpURLConnection) url.openConnection();
|
||||
} catch (Exception e) {
|
||||
log.warn("SSL初始化失败,使用默认连接: {}", e.getMessage());
|
||||
return (HttpURLConnection) url.openConnection();
|
||||
}
|
||||
}
|
||||
|
||||
private String extractFileName(String urlStr, HttpURLConnection conn) {
|
||||
String disposition = conn.getHeaderField("Content-Disposition");
|
||||
if (disposition != null) {
|
||||
for (String part : disposition.split(";")) {
|
||||
part = part.trim();
|
||||
if (part.toLowerCase().startsWith("filename=")) {
|
||||
String name = part.substring(part.indexOf('=') + 1).replaceAll("^[\"\']|[\"\']$", "").trim();
|
||||
if (!name.isBlank()) return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
String path = urlStr.contains("?") ? urlStr.substring(0, urlStr.indexOf('?')) : urlStr;
|
||||
String name = path.substring(path.lastIndexOf('/') + 1);
|
||||
String suffix = FileStorageUtils.getFileSuffix(name);
|
||||
// 后缀过长(>10)说明是JWT/乱码而非真实文件名, 默认pdf
|
||||
if (!name.isBlank() && suffix.length() > 0 && suffix.length() <= 10) {
|
||||
return URLDecoder.decode(name, StandardCharsets.UTF_8);
|
||||
}
|
||||
return "signed_contract.pdf";
|
||||
}
|
||||
|
||||
public byte[] readFileBytes(String fileName) {
|
||||
try {
|
||||
Path rootPath = Paths.get(localPath).toAbsolutePath().normalize();
|
||||
@@ -92,4 +219,5 @@ public class LocalFileService {
|
||||
throw new ServiceException("文件读取失败");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
<result property="signedFilePath" column="SIGNED_FILE_PATH"/>
|
||||
<result property="signedFileType" column="SIGNED_FILE_TYPE"/>
|
||||
<result property="signedFileSize" column="SIGNED_FILE_SIZE"/>
|
||||
<result property="signedFileUrl" column="SIGNED_FILE_URL"/>
|
||||
<result property="signedOfflineReason" column="SIGNED_OFFLINE_REASON"/>
|
||||
<result property="signDate" column="SIGN_DATE"/>
|
||||
<result property="activeDate" column="ACTIVE_DATE"/>
|
||||
@@ -181,7 +182,7 @@
|
||||
BIDDING_NO, OPEN_BIDDING_REMARK, CONTRACT_GIST_REMARK,
|
||||
EW_AMOUNT_TYPE, DJ_AMOUNT, YJ_AMOUNT, YFK_AMOUNT, BZJ_AMOUNT, BODY_SUMMARY,
|
||||
CONTRACT_TEXT_FILE_NAME, CONTRACT_TEXT_FILE_PATH, CONTRACT_TEXT_FILE_TYPE, CONTRACT_TEXT_FILE_SIZE,
|
||||
SIGNED_FILE_NAME, SIGNED_FILE_PATH, SIGNED_FILE_TYPE, SIGNED_FILE_SIZE, SIGNED_OFFLINE_REASON,
|
||||
SIGNED_FILE_NAME, SIGNED_FILE_PATH, SIGNED_FILE_TYPE, SIGNED_FILE_SIZE, SIGNED_FILE_URL, SIGNED_OFFLINE_REASON,
|
||||
SIGN_DATE, ACTIVE_DATE, ARCHIVE_DATE, ARCHIVE_KEEPER, ARCHIVE_KEEPER_UNIT,
|
||||
ARCHIVE_KEEPER_UNIT_CODE, INVOICE_DATE, RECEIVE_DATE, REMIND_DAYS,
|
||||
INITIATOR, INITIATOR_DEPT, COMPANY_NAME, ORG_ID, QUERY_SCOPE,
|
||||
@@ -259,7 +260,7 @@
|
||||
EW_AMOUNT_TYPE, DJ_AMOUNT, YJ_AMOUNT, YFK_AMOUNT, BZJ_AMOUNT, BODY_SUMMARY,
|
||||
TEMPLATE_FIELD_VALUES_JSON,
|
||||
CONTRACT_TEXT_FILE_NAME, CONTRACT_TEXT_FILE_PATH, CONTRACT_TEXT_FILE_TYPE, CONTRACT_TEXT_FILE_SIZE,
|
||||
SIGNED_FILE_NAME, SIGNED_FILE_PATH, SIGNED_FILE_TYPE, SIGNED_FILE_SIZE, SIGNED_OFFLINE_REASON,
|
||||
SIGNED_FILE_NAME, SIGNED_FILE_PATH, SIGNED_FILE_TYPE, SIGNED_FILE_SIZE, SIGNED_FILE_URL, SIGNED_OFFLINE_REASON,
|
||||
SIGN_DATE, ACTIVE_DATE, ARCHIVE_DATE, ARCHIVE_KEEPER, ARCHIVE_KEEPER_UNIT,
|
||||
ARCHIVE_KEEPER_UNIT_CODE, INVOICE_DATE, RECEIVE_DATE, REMIND_DAYS,
|
||||
INITIATOR, INITIATOR_DEPT, COMPANY_NAME, ORG_ID, QUERY_SCOPE,
|
||||
@@ -279,7 +280,7 @@
|
||||
#{ewAmountType}, #{djAmount}, #{yjAmount}, #{yfkAmount}, #{bzjAmount}, #{bodySummary},
|
||||
#{templateFieldValuesJson},
|
||||
#{contractTextFileName}, #{contractTextFilePath}, #{contractTextFileType}, #{contractTextFileSize},
|
||||
#{signedFileName}, #{signedFilePath}, #{signedFileType}, #{signedFileSize}, #{signedOfflineReason},
|
||||
#{signedFileName}, #{signedFilePath}, #{signedFileType}, #{signedFileSize}, #{signedFileUrl}, #{signedOfflineReason},
|
||||
#{signDate}, #{activeDate}, #{archiveDate}, #{archiveKeeper}, #{archiveKeeperUnit},
|
||||
#{archiveKeeperUnitCode}, #{invoiceDate}, #{receiveDate}, #{remindDays},
|
||||
#{initiator}, #{initiatorDept}, #{companyName}, #{orgId}, #{queryScope},
|
||||
@@ -359,6 +360,7 @@
|
||||
SIGNED_FILE_PATH = #{signedFilePath},
|
||||
SIGNED_FILE_TYPE = #{signedFileType},
|
||||
SIGNED_FILE_SIZE = #{signedFileSize},
|
||||
SIGNED_FILE_URL = #{signedFileUrl},
|
||||
SIGNED_OFFLINE_REASON = #{signedOfflineReason},
|
||||
SIGN_DATE = #{signDate},
|
||||
ACTIVE_DATE = #{activeDate},
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
package com.nbport.zgwl.test;
|
||||
|
||||
import com.nbport.zgwl.seal.utils.SealHttpClient;
|
||||
import com.nbport.zgwl.seal.utils.HMacSHA256Util;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.io.*;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.UUID;
|
||||
|
||||
public class SealDownloadManualTest {
|
||||
|
||||
private static final String BASE_URL = "https://saasapi.certca.cn";
|
||||
private static final String APP_KEY = "A7C4FD32BE454B5";
|
||||
private static final String APP_SECRET = "6c74358c28c149f18242bd614a101910";
|
||||
private static final String SAVE_DIR = "D:/temp-seal-download";
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
String contractId = args.length > 0 ? args[0] : "63b5552bfebe44a9de0d16d39162b678";
|
||||
System.out.println("===== Seal Download Test =====");
|
||||
|
||||
String token = getAccessToken();
|
||||
System.out.println("[1] token OK");
|
||||
|
||||
JSONObject body = new JSONObject();
|
||||
body.put("contractId", contractId);
|
||||
String response = SealHttpClient.post(
|
||||
BASE_URL + "/saas/api/contract/download",
|
||||
body.toJSONString(), token, MediaType.APPLICATION_JSON_VALUE);
|
||||
JSONObject result = JSON.parseObject(response);
|
||||
if (result == null || !result.getBooleanValue("isSuccess")) {
|
||||
System.err.println("[FAIL] " + response);
|
||||
return;
|
||||
}
|
||||
Object dataObj = result.get("data");
|
||||
String rawUrl = null;
|
||||
if (dataObj instanceof JSONObject) {
|
||||
rawUrl = ((JSONObject) dataObj).getString("downloadUrl");
|
||||
} else if (dataObj instanceof String) {
|
||||
rawUrl = (String) dataObj;
|
||||
}
|
||||
System.out.println("[2] rawUrl: " + rawUrl);
|
||||
|
||||
// 尝试多种方式下载
|
||||
System.out.println("\n[3] Trying download methods...");
|
||||
|
||||
// 方法1: 直接GET
|
||||
System.out.println(" [3a] Direct GET...");
|
||||
tryDownload(rawUrl, null, false);
|
||||
|
||||
// 方法2: 带 access_token header
|
||||
System.out.println(" [3b] GET + access_token header...");
|
||||
tryDownload(rawUrl, token, false);
|
||||
|
||||
// 方法3: URL编码中文参数后再GET
|
||||
String encodedUrl = encodeChineseInUrl(rawUrl);
|
||||
if (!encodedUrl.equals(rawUrl)) {
|
||||
System.out.println(" [3c] GET with URL-encoded params...");
|
||||
tryDownload(encodedUrl, null, false);
|
||||
System.out.println(" [3d] GET URL-encoded + access_token...");
|
||||
tryDownload(encodedUrl, token, false);
|
||||
}
|
||||
|
||||
// 方法4: POST方式
|
||||
System.out.println(" [3e] POST download (as API)...");
|
||||
tryPostDownload(rawUrl, token);
|
||||
|
||||
// 方法5: 去掉query string
|
||||
String noQuery = rawUrl.contains("?") ? rawUrl.substring(0, rawUrl.indexOf('?')) : rawUrl;
|
||||
System.out.println(" [3f] GET without query string...");
|
||||
tryDownload(noQuery, token, false);
|
||||
|
||||
System.out.println("\n===== Done =====");
|
||||
}
|
||||
|
||||
private static String encodeChineseInUrl(String urlStr) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < urlStr.length(); i++) {
|
||||
char c = urlStr.charAt(i);
|
||||
if (c > 127) {
|
||||
sb.append(URLEncoder.encode(String.valueOf(c), StandardCharsets.UTF_8));
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void tryDownload(String fileUrl, String token, boolean isPost) {
|
||||
try {
|
||||
URL url = new URL(fileUrl);
|
||||
HttpURLConnection conn = openConnection(url);
|
||||
conn.setConnectTimeout(15000);
|
||||
conn.setReadTimeout(30000);
|
||||
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
|
||||
if (token != null && !token.isBlank()) {
|
||||
conn.setRequestProperty("access_token", token);
|
||||
}
|
||||
if (isPost) {
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setDoOutput(true);
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
try (OutputStream os = conn.getOutputStream()) {
|
||||
os.write("{}".getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
|
||||
int code = conn.getResponseCode();
|
||||
if (code >= 200 && code < 300) {
|
||||
long size = conn.getContentLengthLong();
|
||||
System.out.println(" [OK] HTTP " + code + ", size=" + size);
|
||||
} else {
|
||||
String err = readStream(conn.getErrorStream());
|
||||
System.out.println(" [FAIL] HTTP " + code + " body=" + err);
|
||||
}
|
||||
conn.disconnect();
|
||||
} catch (Exception e) {
|
||||
System.out.println(" [ERR] " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void tryPostDownload(String fileUrl, String token) {
|
||||
try {
|
||||
URL url = new URL(fileUrl);
|
||||
HttpURLConnection conn = openConnection(url);
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setDoOutput(true);
|
||||
conn.setConnectTimeout(15000);
|
||||
conn.setReadTimeout(30000);
|
||||
conn.setRequestProperty("User-Agent", "Mozilla/5.0");
|
||||
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
|
||||
if (token != null && !token.isBlank()) {
|
||||
conn.setRequestProperty("access_token", token);
|
||||
}
|
||||
try (OutputStream os = conn.getOutputStream()) {
|
||||
os.write(new byte[0]);
|
||||
}
|
||||
int code = conn.getResponseCode();
|
||||
if (code >= 200 && code < 300) {
|
||||
System.out.println(" [OK] HTTP " + code);
|
||||
} else {
|
||||
String err = readStream(conn.getErrorStream());
|
||||
System.out.println(" [FAIL] HTTP " + code + " body=" + err);
|
||||
}
|
||||
conn.disconnect();
|
||||
} catch (Exception e) {
|
||||
System.out.println(" [ERR] " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static String getAccessToken() throws Exception {
|
||||
long ts = System.currentTimeMillis();
|
||||
JSONObject signData = new JSONObject();
|
||||
signData.put("appKey", APP_KEY);
|
||||
signData.put("timestamp", ts);
|
||||
String sig = HMacSHA256Util.encrypt(JSON.toJSONString(signData), APP_SECRET);
|
||||
JSONObject req = new JSONObject();
|
||||
req.put("appKey", APP_KEY);
|
||||
req.put("timestamp", ts);
|
||||
req.put("signature", sig);
|
||||
String resp = SealHttpClient.post(
|
||||
BASE_URL + "/saas/api/token/getAccessToken",
|
||||
req.toJSONString(), null, MediaType.APPLICATION_JSON_VALUE);
|
||||
return JSON.parseObject(resp).getJSONObject("data").getString("access_token");
|
||||
}
|
||||
|
||||
private static HttpURLConnection openConnection(URL url) throws Exception {
|
||||
if ("https".equalsIgnoreCase(url.getProtocol())) {
|
||||
HttpsURLConnection h = (HttpsURLConnection) url.openConnection();
|
||||
SSLContext ctx = SSLContext.getInstance("TLS");
|
||||
ctx.init(null, new TrustManager[]{new X509TrustManager() {
|
||||
public void checkClientTrusted(X509Certificate[] c, String a) {}
|
||||
public void checkServerTrusted(X509Certificate[] c, String a) {}
|
||||
public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
|
||||
}}, new java.security.SecureRandom());
|
||||
h.setSSLSocketFactory(ctx.getSocketFactory());
|
||||
h.setHostnameVerifier((host, sess) -> true);
|
||||
return h;
|
||||
}
|
||||
return (HttpURLConnection) url.openConnection();
|
||||
}
|
||||
|
||||
private static String readStream(InputStream is) throws IOException {
|
||||
if (is == null) return "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
try (BufferedReader r = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
|
||||
String line; while ((line = r.readLine()) != null) sb.append(line);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
-- 补充 SIGNED_FILE_URL 字段(在线签章原始下载地址)
|
||||
-- 幂等执行:如已存在则跳过
|
||||
DECLARE
|
||||
v_count NUMBER;
|
||||
BEGIN
|
||||
SELECT COUNT(*) INTO v_count FROM ALL_TAB_COLUMNS
|
||||
WHERE OWNER = 'DEVELOPER' AND TABLE_NAME = 'SEAL_CONTRACT_LAUNCH_MAIN' AND COLUMN_NAME = 'SIGNED_FILE_URL';
|
||||
IF v_count = 0 THEN
|
||||
EXECUTE IMMEDIATE 'ALTER TABLE SEAL_CONTRACT_LAUNCH_MAIN ADD SIGNED_FILE_URL VARCHAR2(500)';
|
||||
EXECUTE IMMEDIATE 'COMMENT ON COLUMN SEAL_CONTRACT_LAUNCH_MAIN.SIGNED_FILE_URL IS ''在线签章平台原始下载地址''';
|
||||
DBMS_OUTPUT.PUT_LINE('SIGNED_FILE_URL column added.');
|
||||
ELSE
|
||||
DBMS_OUTPUT.PUT_LINE('SIGNED_FILE_URL already exists, skipped.');
|
||||
END IF;
|
||||
END;
|
||||
/
|
||||
Reference in New Issue
Block a user