Upload - Java
使用 Java 编程语言编写的请求示例。
HashUtil
package util;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* @author DaoDao
*/
public class HashUtil {
/**
* SHA-256 加密
* 使用 jdk 原生加密算法,转化成十六进制字符串
*
* @param string String
* @return String
*/
public static String sha256(String string) {
if (null == string) {
return null;
}
String hash;
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
digest.reset();
digest.update(string.getBytes());
byte[] bytes = digest.digest();
hash = bytesToHex(bytes);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
return hash;
}
public static String sha256(File file) {
if (null == file || !file.exists() || !file.isFile()) {
return null;
}
String hash;
try {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] buffer = new byte[1024];
int sizeRead;
while ((sizeRead = bis.read(buffer)) != -1) {
digest.update(buffer, 0, sizeRead);
}
bis.close();
byte[] bytes = digest.digest();
hash = bytesToHex(bytes);
} catch (NoSuchAlgorithmException | IOException e) {
throw new RuntimeException(e);
}
return hash;
}
private static String bytesToHex(byte[] bytes) {
StringBuilder hexString = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
}
FireFileTest
文件上传请求示例
package http;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.*;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import util.HashUtil;
import util.RsaUtil;
import java.io.File;
import java.net.URI;
import java.nio.file.Files;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
/**
* 模拟 agency 向 ice-run 发送请求
*
* @author DaoDao
*/
class FireFileTest {
/**
* 服务商(agency)的编号
* 此处使用 agency_no = 100000000000000000 作为模拟
* 真实请求场景,请咨询 ice-run 的技术支持获取正式的 服务商编号 agency_no
*/
private static final String AGENCY_NO = "100000000000000000";
private static final String X_CLIENT = "X-Client";
private static final String X_SECURITY = "X-Security";
private static final String X_HASH = "X-Hash";
private static final String X_SIGN = "X-Sign";
private static final String X_TIME = "X-Time";
private static final String X_TRACE = "X-Trace";
/**
* 安全算法 RSA
*/
private static final String SECURITY = "RSA";
/**
* ice-run 的 公钥
*/
private static final String PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC78QqLKTkVcwvB2Scm4IoImOXmIthVmx4HHr5Im09aol2r2UlMK3jl4lamdxktvjFswWx41hQBD4tyYFHTY+mQJsRK8Zwm7LZmLJDHcgibIuLxEddln+MI+rWwT+SOdOt7lBvd9PcO4nYr/3Nqkyk8L4PrT+GF7dpVk8iE/VpTwQIDAQAB";
/**
* agency 的 私钥
*/
private static final String PRIVATE_KEY = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAOyC76Y+/2NqkkIVUtAHkbZkBYI2XXkuqE9o4oIDkbyAY04jC5MLa3oVc3uGG7T/Elodo9dIbIxNh7/ir7GQLR/hY+ookHAewurJ1z4Jn5TQeRk3+nYQcX6a6orfUOMffwcn0Jtat8D+cNAu3uCi+N7Ez/yNpItNORYqTa8AkgzrAgMBAAECgYAS/rsfl3ysb+E6RHsnsQvvYZ4dpJ8iPfCPnCVg+sdoI8mV+3ORBkBGCFYDjDRKd5fyO+IuRqdNJ2bpLtwcfy9YchcV/x40GIuAlre6A6qt4Raq+0l4FJufaqgdmSiLxd5zPX79gdMvBqTbdYzCSDGP5Pf3pTqIS3iKvSixFTQBzQJBAO6k6rE6JE/EHrbRjVtZNitDZ8rouCL0s9nGsgIDBSfgOCClO+HlZKcHq6W9ry8j1gOFeGH2rF1yMslA/ZymRL0CQQD9tk/gk9Xs1Eg1if7SLwvNxUP+z96oEqQTF26tSudPRwOO88fnfE4ZZ4heBt1QI+s7IhG39qecsFW269nvKfbHAkEArXmEgUBalQFjslGyB+1Zyyk8keuJrx9ifbRKQdwgK1R6eICkfxlZiXGx/NFeP041jGnBkLTXpzYUZOexc+YJoQJBANR3r87vnxAU+l+zr62O7oCk+XtT0y/HZJYEYpBHEQyn+MfnSXqG89R8iovLjd0GJ4E+173KlrU2SqHEQ57w8pMCQAEoDjFUXpYzBl8e0M9XsSpGY531mbOl8ASnVp4OSyavaCsn56eEkL9TI5q69KrYUEA4PN3BazMWfll2s/wu6G0=";
/**
* ice-run 的 test 环境的 API 网关 的域名地址
* 正式生产环境的域名地址请查阅 ice-run 的技术文档
*/
private static final String DOMAIN = "https://test-fire-api.ice.run";
/**
* 文件上传接口地址
*/
private static final String FILE_UPLOAD = DOMAIN + "/file-service/api/upload";
/**
* 模拟请求 文件上传 接口
*/
@SuppressWarnings("unchecked")
@Test
void test() {
/*
* 对方公钥
*/
PublicKey publicKey;
/*
* 己方私钥
*/
PrivateKey privateKey;
try {
publicKey = RsaUtil.publicKey(PUBLIC_KEY);
privateKey = RsaUtil.privateKey(PRIVATE_KEY);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new RuntimeException(e);
}
/*
* 此处使用 ${FILE_UPLOAD} 模拟请求地址
*/
URI uri = URI.create(FILE_UPLOAD);
System.out.println("request uri" + " : " + uri);
/*
* json 序列化和反序列化工具包
* 此处使用 jackson 模拟,在实际场景中,开发者可以自行选择 json 工具包
*/
ObjectMapper objectMapper = new ObjectMapper();
/*
* 文件:限制大小 1M
* 此处临时创建一个文件,模拟请求时,将文件上传
*/
File file = new File("/tmp/0.txt");
if (!file.exists()) {
try {
Files.createFile(file.toPath());
Files.writeString(file.toPath(), "0");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
assert file.exists() && file.isFile();
System.out.println("request multipart file path" + " : " + file.getPath());
String hashPlains = HashUtil.sha256(file);
assert hashPlains != null && !hashPlains.isEmpty();
System.out.println("request multipart file hash plains" + " : " + hashPlains);
String hashCipher;
try {
hashCipher = RsaUtil.encrypt(publicKey, hashPlains);
} catch (Exception e) {
throw new RuntimeException(e);
}
assert hashCipher != null && !hashCipher.isEmpty();
System.out.println("request multipart file hash cipher" + " : " + hashCipher);
String requestSign;
try {
requestSign = RsaUtil.sign(privateKey, hashCipher);
} catch (Exception e) {
throw new RuntimeException(e);
}
assert !requestSign.isEmpty();
System.out.println("request x-sign" + " : " + requestSign);
/*
* 此处使用 spring 的 RestTemplate 发送请求
* 开发者也可以自行选择使用其它 http 客户端工具包
* 注意:必须要构造一个 multipart/form-data 形式的请求
*/
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
headers.set(X_CLIENT, AGENCY_NO);
headers.set(X_SECURITY, SECURITY);
headers.set(X_SIGN, requestSign);
headers.set(X_HASH, hashCipher);
headers.set(X_TIME, ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.RFC_1123_DATE_TIME));
headers.set(X_TRACE, UUID.randomUUID().toString().replaceAll("-", ""));
System.out.println("request headers" + " : " + headers);
FileSystemResource fileResource = new FileSystemResource(file);
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("file", fileResource);
body.add("hash", hashPlains);
HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(body, headers);
RestTemplate restTemplate = new RestTemplate();
ParameterizedTypeReference<String> typeReference = new ParameterizedTypeReference<>() {
};
ResponseEntity<String> responseEntity;
try {
responseEntity = restTemplate.exchange(uri, HttpMethod.POST, httpEntity, typeReference);
} catch (Exception e) {
throw new RuntimeException(e);
}
/*
* 响应 http 状态码
*/
HttpStatusCode httpStatus = responseEntity.getStatusCode();
System.out.println("response http status" + " : " + httpStatus);
assert httpStatus.is2xxSuccessful();
/*
* 响应 header
*/
HttpHeaders responseHeaders = responseEntity.getHeaders();
assert !responseHeaders.isEmpty();
System.out.println("response headers" + " : " + responseHeaders);
/*
* 响应 body ,json 格式的字符串
*/
String responseBody = responseEntity.getBody();
assert null != responseBody && !responseBody.isEmpty();
System.out.println("response body" + " : " + responseBody);
/*
* 响应 header X-Sign
*/
String responseSign = responseHeaders.getFirst(X_SIGN);
assert null != responseSign && !responseSign.isEmpty();
System.out.println("response " + X_SIGN + " : " + responseSign);
/*
* 响应数据 response body,此处使用 LinkedHashMap 模拟,实际场景建议定义数据模型类
* 数据的 键 固定为 data
* 数据的 值 为 json 序列化后的字符串 RSA 加密后的密文
*/
Map<String, Object> responseMap;
try {
responseMap = objectMapper.readValue(responseBody, LinkedHashMap.class);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
assert null != responseMap && !responseMap.isEmpty();
/*
* 响应码,默认 000000 表示请求成功
*/
String code = (String) responseMap.get("code");
assert code != null && !code.isEmpty() && "000000".equals(code);
/*
* 响应 数据密文
*/
String dataCipher = (String) responseMap.get("data");
assert dataCipher != null && !dataCipher.isEmpty();
System.out.println("response data cipher" + " : " + dataCipher);
/*
* 响应验签:ice-run 处理响应时,会使用 ice-run 私钥对响应密文进行签名
* agency 收到响应后,需要使用 ice-run 公钥进行验签
*/
boolean responseSignVerify;
try {
responseSignVerify = RsaUtil.verify(publicKey, dataCipher, responseSign);
} catch (Exception e) {
throw new RuntimeException(e);
}
System.out.println("response sign verify : " + responseSignVerify);
assert responseSignVerify;
/*
* 响应 数据明文
*/
String dataPlains;
try {
dataPlains = RsaUtil.decrypt(privateKey, dataCipher);
} catch (Exception e) {
throw new RuntimeException(e);
}
assert !dataPlains.isEmpty();
System.out.println("response data plains" + " : " + dataPlains);
Map<String, Object> data;
try {
data = objectMapper.readValue(dataPlains, LinkedHashMap.class);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
assert data != null;
}
}