UpYunClient.java 10 KB

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