UpYunClient.java 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. package cn.com.qmth.examcloud.web.upyun;
  2. import java.io.File;
  3. import java.io.FileInputStream;
  4. import java.io.FileNotFoundException;
  5. import java.io.InputStream;
  6. import java.security.InvalidKeyException;
  7. import java.security.NoSuchAlgorithmException;
  8. import java.security.SignatureException;
  9. import java.text.SimpleDateFormat;
  10. import java.util.Base64;
  11. import java.util.Calendar;
  12. import java.util.Date;
  13. import java.util.Locale;
  14. import java.util.Map;
  15. import java.util.TimeZone;
  16. import java.util.concurrent.TimeUnit;
  17. import javax.crypto.Mac;
  18. import javax.crypto.spec.SecretKeySpec;
  19. import org.apache.commons.compress.utils.IOUtils;
  20. import org.apache.commons.lang3.StringUtils;
  21. import org.apache.commons.lang3.time.DateUtils;
  22. import org.apache.http.HttpStatus;
  23. import org.apache.http.client.config.CookieSpecs;
  24. import org.apache.http.client.config.RequestConfig;
  25. import org.apache.http.client.methods.CloseableHttpResponse;
  26. import org.apache.http.client.methods.HttpDelete;
  27. import org.apache.http.client.methods.HttpPut;
  28. import org.apache.http.entity.InputStreamEntity;
  29. import org.apache.http.impl.client.CloseableHttpClient;
  30. import org.apache.http.impl.client.HttpClients;
  31. import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
  32. import org.apache.http.util.EntityUtils;
  33. import com.google.common.collect.Maps;
  34. import cn.com.qmth.examcloud.commons.exception.ExamCloudRuntimeException;
  35. import cn.com.qmth.examcloud.commons.exception.StatusException;
  36. import cn.com.qmth.examcloud.commons.logging.ExamCloudLog;
  37. import cn.com.qmth.examcloud.commons.logging.ExamCloudLogFactory;
  38. import cn.com.qmth.examcloud.commons.util.JsonUtil;
  39. import cn.com.qmth.examcloud.commons.util.MD5;
  40. /**
  41. * upyun client
  42. *
  43. * @author WANGWEI
  44. * @date 2018年11月21日
  45. * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
  46. */
  47. public class UpYunClient {
  48. protected ExamCloudLog log = ExamCloudLogFactory.getLog(this.getClass());
  49. /**
  50. * 空间名
  51. */
  52. protected String bucketName = null;
  53. /**
  54. * 操作员名
  55. */
  56. protected String userName = null;
  57. /**
  58. * 操作员密码
  59. */
  60. protected String password = null;
  61. protected String md5Password = null;
  62. public static final String API_DOMAIN = "v0.api.upyun.com";
  63. private static final String MKDIR = "mkdir";
  64. private final String METHOD_POST = "POST";
  65. private final String METHOD_PUT = "PUT";
  66. private final String METHOD_DELETE = "DELETE";
  67. private final String DATE = "Date";
  68. private final String AUTHORIZATION = "Authorization";
  69. private final String SEPARATOR = "/";
  70. private static CloseableHttpClient httpclient;
  71. private static RequestConfig requestConfig;
  72. private String domain;
  73. static {
  74. PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(60,
  75. TimeUnit.SECONDS);
  76. cm.setValidateAfterInactivity(1000);
  77. cm.setMaxTotal(8000);
  78. cm.setDefaultMaxPerRoute(800);
  79. requestConfig = RequestConfig.custom().setConnectionRequestTimeout(500)
  80. .setSocketTimeout(10000).setConnectTimeout(10000)
  81. .setCookieSpec(CookieSpecs.IGNORE_COOKIES).build();
  82. httpclient = HttpClients.custom().setConnectionManager(cm).disableAutomaticRetries()
  83. .setDefaultRequestConfig(requestConfig).build();
  84. }
  85. /**
  86. * 构造函数
  87. *
  88. * @param bucketName
  89. * @param userName
  90. * @param password
  91. */
  92. public UpYunClient(String bucketName, String userName, String password, String domain) {
  93. this.bucketName = bucketName;
  94. this.userName = userName;
  95. this.password = password;
  96. this.domain = domain;
  97. this.md5Password = MD5.encrypt32(password);
  98. }
  99. /**
  100. * 创建又拍云请求
  101. *
  102. * @author WANGWEI
  103. * @param filePath
  104. * @return
  105. */
  106. public UpYunHttpRequest buildUpYunHttpRequest(String filePath) {
  107. return buildUpYunHttpRequest(filePath, "");
  108. }
  109. /**
  110. * 创建又拍云请求
  111. *
  112. * @author WANGWEI
  113. * @param filePath
  114. * @param md5
  115. * @return
  116. */
  117. public UpYunHttpRequest buildUpYunHttpRequest(String filePath, String md5) {
  118. String path = filePath.trim();
  119. if (!path.startsWith(SEPARATOR)) {
  120. path = SEPARATOR + path;
  121. }
  122. String formUrl = "https://" + API_DOMAIN + "/" + this.bucketName;
  123. String date = getDate();
  124. Map<String, Object> policyArgs = Maps.newHashMap();
  125. policyArgs.put("bucket", bucketName);
  126. policyArgs.put("save-key", path);
  127. Date expiration = DateUtils.addSeconds(new Date(), 600);
  128. policyArgs.put("expiration", expiration.getTime() / 1000);
  129. policyArgs.put("date", date);
  130. policyArgs.put("content-md5", md5);
  131. String json = JsonUtil.toJson(policyArgs);
  132. String policy = Base64.getEncoder().encodeToString(json.getBytes());
  133. Map<String, String> params = Maps.newHashMap();
  134. String authorization = null;
  135. try {
  136. md5 = null == md5 ? "" : md5;
  137. authorization = sign(userName, md5Password, METHOD_POST, SEPARATOR + this.bucketName,
  138. date, policy, md5);
  139. } catch (Exception e) {
  140. throw new StatusException("100005", "[upyun]. fail to build sign", e);
  141. }
  142. params.put("authorization", authorization);
  143. params.put("policy", policy);
  144. String accessUrl = this.domain + filePath;
  145. UpYunHttpRequest request = new UpYunHttpRequest();
  146. request.setAccessUrl(accessUrl);
  147. request.setFormParams(params);
  148. request.setFormUrl(formUrl);
  149. return request;
  150. }
  151. public UpYunPathInfo writeFile(String filePath, File file) {
  152. return writeFile(filePath, file, false);
  153. }
  154. /**
  155. * 上传文件
  156. *
  157. * @author WANGWEI
  158. * @param filePath
  159. * @param file
  160. * @param withMd5
  161. * @return
  162. */
  163. public UpYunPathInfo writeFile(String filePath, File file, boolean withMd5) {
  164. String md5 = null;
  165. if (withMd5) {
  166. md5 = MD5.md5Hex(file);
  167. }
  168. return writeFile(filePath, file, md5);
  169. }
  170. /**
  171. * 上传文件
  172. *
  173. * @author WANGWEI
  174. * @param filePath
  175. * @param file
  176. * @param md5
  177. * @return
  178. */
  179. public UpYunPathInfo writeFile(String filePath, File file, String md5) {
  180. InputStream in = null;
  181. try {
  182. in = new FileInputStream(file);
  183. return writeFile(filePath, in, md5);
  184. } catch (FileNotFoundException e) {
  185. throw new ExamCloudRuntimeException(e);
  186. } finally {
  187. IOUtils.closeQuietly(in);
  188. }
  189. }
  190. /**
  191. * 上传文件
  192. *
  193. * @author WANGWEI
  194. * @param filePath
  195. * @param in
  196. * @return
  197. */
  198. public UpYunPathInfo writeFile(String filePath, InputStream in) {
  199. return writeFile(filePath, in, "");
  200. }
  201. /**
  202. * 上传文件
  203. *
  204. * @author WANGWEI
  205. * @param filePath
  206. * @param in
  207. * @param md5
  208. * @return
  209. */
  210. public UpYunPathInfo writeFile(String filePath, InputStream in, String md5) {
  211. String path = formatPath(filePath);
  212. String url = "https://" + API_DOMAIN + path;
  213. HttpPut httpPut = new HttpPut(url);
  214. httpPut.setConfig(UpYunClient.requestConfig);
  215. CloseableHttpResponse response = null;
  216. long s = System.currentTimeMillis();
  217. try {
  218. md5 = null == md5 ? "" : md5;
  219. if (md5.equals(MD5.encrypt32(""))) {
  220. throw new StatusException("100009", "empty IO stream");
  221. }
  222. String date = getDate();
  223. String authorization = sign(userName, md5Password, METHOD_PUT, path, date, "", md5);
  224. httpPut.addHeader(AUTHORIZATION, authorization);
  225. httpPut.addHeader(DATE, date);
  226. httpPut.addHeader(MKDIR, "true");
  227. if (StringUtils.isNotBlank(md5)) {
  228. httpPut.addHeader("Content-MD5", md5);
  229. }
  230. httpPut.setEntity(new InputStreamEntity(in));
  231. response = httpclient.execute(httpPut);
  232. int statusCode = response.getStatusLine().getStatusCode();
  233. if (HttpStatus.SC_OK != statusCode) {
  234. log.error("[upyun error] " + EntityUtils.toString(response.getEntity(), "UTF-8"));
  235. throw new StatusException("100001", "[upyun]. fail to write file");
  236. }
  237. } catch (StatusException e) {
  238. throw e;
  239. } catch (Exception e) {
  240. throw new ExamCloudRuntimeException(e);
  241. } finally {
  242. IOUtils.closeQuietly(in);
  243. IOUtils.closeQuietly(response);
  244. // httpPut.releaseConnection();
  245. }
  246. if (log.isDebugEnabled()) {
  247. log.debug("[upyun]. write file. path=" + path + "; cost "
  248. + (System.currentTimeMillis() - s) + " ms.");
  249. }
  250. String fileUrl = this.domain + filePath;
  251. return new UpYunPathInfo(fileUrl, filePath);
  252. }
  253. /**
  254. * 删除文件
  255. *
  256. * @author WANGWEI
  257. * @param filePath
  258. * @return
  259. */
  260. public void deleteFile(String filePath) {
  261. String path = formatPath(filePath);
  262. String url = "https://" + API_DOMAIN + path;
  263. HttpDelete httpDelete = new HttpDelete(url);
  264. httpDelete.setConfig(UpYunClient.requestConfig);
  265. CloseableHttpResponse response = null;
  266. long s = System.currentTimeMillis();
  267. try {
  268. String date = getDate();
  269. String authorization = sign(userName, md5Password, METHOD_DELETE, path, date, "", "");
  270. httpDelete.addHeader(AUTHORIZATION, authorization);
  271. httpDelete.addHeader(DATE, date);
  272. response = httpclient.execute(httpDelete);
  273. int statusCode = response.getStatusLine().getStatusCode();
  274. if (HttpStatus.SC_OK != statusCode) {
  275. log.error("[upyun error] " + EntityUtils.toString(response.getEntity(), "UTF-8"));
  276. throw new StatusException("100002", "[upyun]. fail to delete file");
  277. }
  278. } catch (StatusException e) {
  279. throw e;
  280. } catch (Exception e) {
  281. throw new ExamCloudRuntimeException(e);
  282. } finally {
  283. IOUtils.closeQuietly(response);
  284. // httpDelete.releaseConnection();
  285. }
  286. if (log.isDebugEnabled()) {
  287. log.debug("[upyun]. delete file. path=" + path + "; cost "
  288. + (System.currentTimeMillis() - s) + " ms.");
  289. }
  290. }
  291. /**
  292. * 格式化路径参数
  293. * <p>
  294. * 最终构成的格式:"/空间名/文件路径"
  295. *
  296. * @param path
  297. * 目录路径或文件路径
  298. * @return 格式化后的路径
  299. */
  300. private String formatPath(String path) {
  301. if (StringUtils.isNotBlank(path)) {
  302. // 去除前后的空格
  303. path = path.trim();
  304. // 确保路径以"/"开头
  305. if (!path.startsWith(SEPARATOR)) {
  306. return SEPARATOR + bucketName + SEPARATOR + path;
  307. }
  308. }
  309. return SEPARATOR + bucketName + path;
  310. }
  311. private String getDate() {
  312. Calendar calendar = Calendar.getInstance();
  313. SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z",
  314. Locale.US);
  315. dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
  316. return dateFormat.format(calendar.getTime());
  317. }
  318. private byte[] hashHmac(String data, String key)
  319. throws SignatureException, NoSuchAlgorithmException, InvalidKeyException {
  320. SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), "HmacSHA1");
  321. Mac mac = Mac.getInstance("HmacSHA1");
  322. mac.init(signingKey);
  323. return mac.doFinal(data.getBytes());
  324. }
  325. private String sign(String key, String secret, String method, String uri, String date,
  326. String policy, String md5) throws Exception {
  327. String value = method + "&" + uri + "&" + date;
  328. if (policy != null && policy.length() > 0) {
  329. value = value + "&" + policy;
  330. }
  331. if (md5 != null && md5.length() > 0) {
  332. value = value + "&" + md5;
  333. }
  334. byte[] hmac = hashHmac(value, secret);
  335. String sign = Base64.getEncoder().encodeToString(hmac);
  336. return "UPYUN " + key + ":" + sign;
  337. }
  338. }