package cn.com.qmth.examcloud.web.upyun; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; import java.text.SimpleDateFormat; import java.util.Base64; import java.util.Calendar; import java.util.Locale; import java.util.Map; import java.util.TimeZone; import java.util.concurrent.TimeUnit; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.compress.utils.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpStatus; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpPut; import org.apache.http.entity.InputStreamEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.util.EntityUtils; import com.google.common.collect.Maps; import cn.com.qmth.examcloud.commons.exception.ExamCloudRuntimeException; import cn.com.qmth.examcloud.commons.exception.StatusException; import cn.com.qmth.examcloud.commons.logging.ExamCloudLog; import cn.com.qmth.examcloud.commons.logging.ExamCloudLogFactory; import cn.com.qmth.examcloud.commons.util.MD5; /** * upyun client * * @author WANGWEI * @date 2018年11月21日 * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved. */ public class UpYunClient { protected ExamCloudLog log = ExamCloudLogFactory.getLog(this.getClass()); /** * 空间名 */ protected String bucketName = null; /** * 操作员名 */ protected String userName = null; /** * 操作员密码 */ protected String password = null; protected String md5Password = null; public static final String API_DOMAIN = "v0.api.upyun.com"; private static final String MKDIR = "mkdir"; private final String METHOD_PUT = "PUT"; private final String METHOD_DELETE = "DELETE"; private final String DATE = "Date"; private final String AUTHORIZATION = "Authorization"; private final String SEPARATOR = "/"; private static CloseableHttpClient httpclient; private static RequestConfig requestConfig; private String domain; static { PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(10, TimeUnit.SECONDS); cm.setValidateAfterInactivity(2000); cm.setMaxTotal(1000); cm.setDefaultMaxPerRoute(1000); httpclient = HttpClients.custom().setConnectionManager(cm).disableAutomaticRetries() .build(); requestConfig = RequestConfig.custom().setConnectionRequestTimeout(10000) .setSocketTimeout(10000).setConnectTimeout(10000).build(); } /** * 构造函数 * * @param bucketName * @param userName * @param password */ public UpYunClient(String bucketName, String userName, String password, String domain) { this.bucketName = bucketName; this.userName = userName; this.password = password; this.domain = domain; this.md5Password = MD5.encrypt32(password); } /** * 创建又拍云签名 * * @author WANGWEI * @param filePath * @return */ public UpYunSign buildUpYunSign(String filePath) { String path = formatPath(filePath); String url = "https://" + API_DOMAIN + path; Map headers = Maps.newHashMap(); String date = getDate(); String authorization = null; try { authorization = sign(userName, md5Password, METHOD_PUT, path, date, "", ""); } catch (Exception e) { throw new StatusException("100005", "[upyun]. fail to build sign", e); } headers.put(AUTHORIZATION, authorization); headers.put(DATE, date); headers.put(MKDIR, "true"); UpYunSign sign = new UpYunSign(); sign.setUrl(url); sign.setHeaders(headers); return sign; } /** * 上传文件 * * @author WANGWEI * @param filePath * @param file * @return */ public UpYunPathInfo writeFile(String filePath, File file) { InputStream in = null; try { in = new FileInputStream(file); return writeFile(filePath, in); } catch (FileNotFoundException e) { throw new ExamCloudRuntimeException(e); } finally { IOUtils.closeQuietly(in); } } /** * 上传文件 * * @author WANGWEI * @param filePath * @param in * @return */ public UpYunPathInfo writeFile(String filePath, InputStream in) { String path = formatPath(filePath); String url = "https://" + API_DOMAIN + path; HttpPut httpPut = new HttpPut(url); httpPut.setConfig(UpYunClient.requestConfig); CloseableHttpResponse response = null; long s = System.currentTimeMillis(); try { String date = getDate(); String authorization = sign(userName, md5Password, METHOD_PUT, path, date, "", ""); httpPut.addHeader(AUTHORIZATION, authorization); httpPut.addHeader(DATE, date); httpPut.addHeader(MKDIR, "true"); httpPut.setEntity(new InputStreamEntity(in)); response = httpclient.execute(httpPut); int statusCode = response.getStatusLine().getStatusCode(); if (HttpStatus.SC_OK != statusCode) { log.error("[upyun error] " + EntityUtils.toString(response.getEntity(), "UTF-8")); throw new StatusException("100001", "[upyun]. fail to write file"); } } catch (StatusException e) { throw e; } catch (Exception e) { throw new ExamCloudRuntimeException(e); } finally { IOUtils.closeQuietly(in); IOUtils.closeQuietly(response); httpPut.releaseConnection(); } if (log.isDebugEnabled()) { log.debug("[upyun]. write file. path=" + path + "; cost " + (System.currentTimeMillis() - s) + " ms."); } String fileUrl = this.domain + filePath; return new UpYunPathInfo(fileUrl, filePath); } /** * 删除文件 * * @author WANGWEI * @param filePath * @return */ public void deleteFile(String filePath) { String path = formatPath(filePath); String url = "https://" + API_DOMAIN + path; HttpDelete httpDelete = new HttpDelete(url); httpDelete.setConfig(UpYunClient.requestConfig); CloseableHttpResponse response = null; long s = System.currentTimeMillis(); try { String date = getDate(); String authorization = sign(userName, md5Password, METHOD_DELETE, path, date, "", ""); httpDelete.addHeader(AUTHORIZATION, authorization); httpDelete.addHeader(DATE, date); response = httpclient.execute(httpDelete); int statusCode = response.getStatusLine().getStatusCode(); if (HttpStatus.SC_OK != statusCode) { log.error("[upyun error] " + EntityUtils.toString(response.getEntity(), "UTF-8")); throw new StatusException("100002", "[upyun]. fail to delete file"); } } catch (StatusException e) { throw e; } catch (Exception e) { throw new ExamCloudRuntimeException(e); } finally { IOUtils.closeQuietly(response); httpDelete.releaseConnection(); } if (log.isDebugEnabled()) { log.debug("[upyun]. delete file. path=" + path + "; cost " + (System.currentTimeMillis() - s) + " ms."); } } /** * 格式化路径参数 *

* 最终构成的格式:"/空间名/文件路径" * * @param path * 目录路径或文件路径 * @return 格式化后的路径 */ private String formatPath(String path) { if (StringUtils.isNotBlank(path)) { // 去除前后的空格 path = path.trim(); // 确保路径以"/"开头 if (!path.startsWith(SEPARATOR)) { return SEPARATOR + bucketName + SEPARATOR + path; } } return SEPARATOR + bucketName + path; } private String getDate() { Calendar calendar = Calendar.getInstance(); SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); return dateFormat.format(calendar.getTime()); } private byte[] hashHmac(String data, String key) throws SignatureException, NoSuchAlgorithmException, InvalidKeyException { SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), "HmacSHA1"); Mac mac = Mac.getInstance("HmacSHA1"); mac.init(signingKey); return mac.doFinal(data.getBytes()); } private String sign(String key, String secret, String method, String uri, String date, String policy, String md5) throws Exception { String value = method + "&" + uri + "&" + date; if (policy != null && policy.length() > 0) { value = value + "&" + policy; } if (md5 != null && md5.length() > 0) { value = value + "&" + md5; } byte[] hmac = hashHmac(value, secret); String sign = Base64.getEncoder().encodeToString(hmac); return "UPYUN " + key + ":" + sign; } }