WANG 6 år sedan
förälder
incheckning
8b9bc9f648

+ 14 - 1
examcloud-exchange-inner-service/src/main/java/cn/com/qmth/examcloud/exchange/inner/service/UpyunService.java

@@ -1,6 +1,9 @@
 package cn.com.qmth.examcloud.exchange.inner.service;
 
 import java.io.File;
+import java.io.InputStream;
+
+import cn.com.qmth.examcloud.exchange.inner.service.bean.UpyunPathEnvironmentInfo;
 
 /**
  * 类注释
@@ -11,6 +14,16 @@ import java.io.File;
  */
 public interface UpyunService {
 
-	public String writeFile(Long rootOrgId, String identityNumber, String fileName, File file);
+	String writeFile(Long rootOrgId, String identityNumber, String fileName, File file);
+
+	/**
+	 * 写文件
+	 *
+	 * @author WANGWEI
+	 * @param env
+	 * @param in
+	 * @return
+	 */
+	String writeFile(UpyunPathEnvironmentInfo env, InputStream in);
 
 }

+ 68 - 0
examcloud-exchange-inner-service/src/main/java/cn/com/qmth/examcloud/exchange/inner/service/bean/UpyunPathEnvironmentInfo.java

@@ -0,0 +1,68 @@
+package cn.com.qmth.examcloud.exchange.inner.service.bean;
+
+import cn.com.qmth.examcloud.commons.web.cloud.api.JsonSerializable;
+
+/**
+ * 又拍云路径变量
+ *
+ * @author WANGWEI
+ * @date 2018年11月21日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class UpyunPathEnvironmentInfo implements JsonSerializable {
+
+	private static final long serialVersionUID = -3579995914084929142L;
+
+	/**
+	 * 顶级机构
+	 */
+	private Long rootOrgId;
+
+	/**
+	 * 用户ID
+	 */
+	private Long userId;
+
+	/**
+	 * 时间戳
+	 */
+	private Long currentTimeMillis;
+
+	/**
+	 * 文件后缀(以"."开头,如 ".jpg",".zip")
+	 */
+	private Long fileSuffix;
+
+	public Long getRootOrgId() {
+		return rootOrgId;
+	}
+
+	public void setRootOrgId(Long rootOrgId) {
+		this.rootOrgId = rootOrgId;
+	}
+
+	public Long getUserId() {
+		return userId;
+	}
+
+	public void setUserId(Long userId) {
+		this.userId = userId;
+	}
+
+	public Long getCurrentTimeMillis() {
+		return currentTimeMillis;
+	}
+
+	public void setCurrentTimeMillis(Long currentTimeMillis) {
+		this.currentTimeMillis = currentTimeMillis;
+	}
+
+	public Long getFileSuffix() {
+		return fileSuffix;
+	}
+
+	public void setFileSuffix(Long fileSuffix) {
+		this.fileSuffix = fileSuffix;
+	}
+
+}

+ 16 - 28
examcloud-exchange-inner-service/src/main/java/cn/com/qmth/examcloud/exchange/inner/service/impl/UpyunServiceImpl.java

@@ -2,6 +2,7 @@ package cn.com.qmth.examcloud.exchange.inner.service.impl;
 
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.Locale;
 
 import org.springframework.beans.factory.annotation.Value;
@@ -12,7 +13,9 @@ import cn.com.qmth.examcloud.commons.base.logging.ExamCloudLog;
 import cn.com.qmth.examcloud.commons.base.logging.ExamCloudLogFactory;
 import cn.com.qmth.examcloud.commons.base.util.UrlUtil;
 import cn.com.qmth.examcloud.exchange.inner.service.UpyunService;
+import cn.com.qmth.examcloud.exchange.inner.service.bean.UpyunPathEnvironmentInfo;
 import main.java.com.UpYun;
+import main.java.com.upyun.UpYunUtils;
 
 /**
  * 类注释
@@ -22,14 +25,10 @@ import main.java.com.UpYun;
  * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
  */
 @Service
-public class UpyunServiceImpl implements UpyunService {
+public class UpyunServiceImpl  implements UpyunService {
 
 	protected ExamCloudLog log = ExamCloudLogFactory.getLog(this.getClass());
 
-	private final Object LOCK = new Object();
-
-	private UpYun upyun;
-
 	@Value("${$UpYun.bucketName}")
 	private String bucketName;
 
@@ -42,23 +41,6 @@ public class UpyunServiceImpl implements UpyunService {
 	@Value("${$UpYun.studentPhotoUrlBasePath}")
 	private String studentPhotoUrlBasePath;
 
-	/**
-	 * 方法注释
-	 *
-	 * @author WANGWEI
-	 * @return
-	 */
-	private UpYun getUpyun() {
-		if (null == upyun) {
-			synchronized (LOCK) {
-				if (null == upyun) {
-					upyun = new UpYun(bucketName, userName, password);
-				}
-			}
-		}
-		return upyun;
-	}
-
 	@Override
 	public String writeFile(Long rootOrgId, String identityNumber, String fileName, File file) {
 		String photoPath = rootOrgId + "/" + UrlUtil.encode(identityNumber).toUpperCase(Locale.US)
@@ -66,16 +48,22 @@ public class UpyunServiceImpl implements UpyunService {
 
 		String filePath = "/student_base_photo" + "/" + photoPath;
 
-		try {
-			getUpyun().writeFile(filePath, file, true);
-		} catch (IOException e) {
-			log.error("[UpYun] fail to write file. filePath=" + filePath, e);
-			throw new StatusException("EX-360001", "上传文件到UpYun失败", e);
-		}
+//		try {
+////			this.writeFile(filePath, file, true);
+//		} catch (IOException e) {
+//			log.error("[UpYun] fail to write file. filePath=" + filePath, e);
+//			throw new StatusException("EX-360001", "上传文件到UpYun失败", e);
+//		}
 
 		String photoUrl = studentPhotoUrlBasePath + filePath;
 
 		return photoUrl;
 	}
 
+	@Override
+	public String writeFile(UpyunPathEnvironmentInfo env, InputStream in) {
+
+		return null;
+	}
+
 }

+ 1326 - 0
examcloud-exchange-inner-service/src/main/java/cn/com/qmth/examcloud/exchange/inner/service/upyun/UpYunClient.java

@@ -0,0 +1,1326 @@
+package cn.com.qmth.examcloud.exchange.inner.service.upyun;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+
+import main.java.com.upyun.UpYunUtils;
+
+public class UpYunClient {
+
+	/**
+	 * 默认的编码格式
+	 */
+	private static final String UTF8 = "UTF-8";
+
+	/**
+	 * SKD版本号
+	 */
+	private final String VERSION = "2.0";
+
+	/**
+	 * 路径的分割符
+	 */
+	private final String SEPARATOR = "/";
+
+	private final String AUTHORIZATION = "Authorization";
+
+	private final String DATE = "Date";
+
+	private final String CONTENT_LENGTH = "Content-Length";
+
+	private final String CONTENT_MD5 = "Content-MD5";
+
+	private final String CONTENT_SECRET = "Content-Secret";
+
+	private final String MKDIR = "mkdir";
+
+	private final String X_UPYUN_WIDTH = "x-upyun-width";
+
+	private final String X_UPYUN_HEIGHT = "x-upyun-height";
+
+	private final String X_UPYUN_FRAMES = "x-upyun-frames";
+
+	private final String X_UPYUN_FILE_TYPE = "x-upyun-file-type";
+
+	private final String X_UPYUN_FILE_SIZE = "x-upyun-file-size";
+
+	private final String X_UPYUN_FILE_DATE = "x-upyun-file-date";
+
+	private final String METHOD_HEAD = "HEAD";
+
+	private final String METHOD_GET = "GET";
+
+	private final String METHOD_PUT = "PUT";
+
+	private final String METHOD_DELETE = "DELETE";
+
+	/**
+	 * 根据网络条件自动选择接入点:v0.api.upyun.com
+	 */
+	public static final String ED_AUTO = "v0.api.upyun.com";
+
+	/**
+	 * 电信接入点:v1.api.upyun.com
+	 */
+	public static final String ED_TELECOM = "v1.api.upyun.com";
+
+	/**
+	 * 联通网通接入点:v2.api.upyun.com
+	 */
+	public static final String ED_CNC = "v2.api.upyun.com";
+
+	/**
+	 * 移动铁通接入点:v3.api.upyun.com
+	 */
+	public static final String ED_CTT = "v3.api.upyun.com";
+
+	// 默认不开启debug模式
+	public boolean debug = false;
+
+	// 默认的超时时间:30秒
+	private int timeout = 30 * 1000;
+
+	// 默认为自动识别接入点
+	private String apiDomain = ED_AUTO;
+
+	// 待上传文件的 Content-MD5 值
+	private String contentMD5 = null;
+
+	// 待上传文件的"访问密钥"
+	private String fileSecret = null;
+
+	// 空间名
+	protected String bucketName = null;
+
+	// 操作员名
+	protected String userName = null;
+
+	// 操作员密码
+	protected String password = null;
+
+	// 图片信息的参数
+	protected String picWidth = null;
+
+	protected String picHeight = null;
+
+	protected String picFrames = null;
+
+	protected String picType = null;
+
+	// 文件信息的参数
+	protected String fileType = null;
+
+	protected String fileSize = null;
+
+	protected String fileDate = null;
+
+	/**
+	 * 初始化 UpYun 存储接口
+	 *
+	 * @param bucketName
+	 *            空间名称
+	 * @param userName
+	 *            操作员名称
+	 * @param password
+	 *            密码,不需要MD5加密
+	 * @return UpYun object
+	 */
+	public UpYunClient(String bucketName, String userName, String password) {
+		this.bucketName = bucketName;
+		this.userName = userName;
+		this.password = md5(password);
+	}
+
+	/**
+	 * 切换 API 接口的域名接入点
+	 * <p>
+	 * 可选参数:<br>
+	 * 1) UpYun.ED_AUTO(v0.api.upyun.com):默认,根据网络条件自动选择接入点 <br>
+	 * 2) UpYun.ED_TELECOM(v1.api.upyun.com):电信接入点<br>
+	 * 3) UpYun.ED_CNC(v2.api.upyun.com):联通网通接入点<br>
+	 * 4) UpYun.ED_CTT(v3.api.upyun.com):移动铁通接入点
+	 *
+	 * @param domain
+	 *            域名接入点
+	 */
+	public void setApiDomain(String domain) {
+		this.apiDomain = domain;
+	}
+
+	/**
+	 * 查看当前的域名接入点
+	 *
+	 * @return
+	 */
+	public String getApiDomain() {
+		return apiDomain;
+	}
+
+	/**
+	 * 设置连接超时时间,默认为30秒
+	 *
+	 * @param second
+	 *            秒数,60即为一分钟超时
+	 */
+	public void setTimeout(int second) {
+		this.timeout = second * 1000;
+	}
+
+	/**
+	 * 查看当前的超时时间
+	 *
+	 * @return
+	 */
+	public int getTimeout() {
+		return timeout;
+	}
+
+	/**
+	 * 查看当前是否是debug模式
+	 *
+	 * @return
+	 */
+	public boolean isDebug() {
+		return debug;
+	}
+
+	/**
+	 * 设置是否开启debug模式
+	 *
+	 * @param debug
+	 */
+	public void setDebug(boolean debug) {
+		this.debug = debug;
+	}
+
+	/**
+	 * 设置待上传文件的 Content-MD5 值
+	 * <p>
+	 * 如果又拍云服务端收到的文件MD5值与用户设置的不一致,将回报 406 Not Acceptable 错误
+	 *
+	 * @param md5Value
+	 *            文件 MD5 校验后的内容
+	 */
+	public void setContentMD5(String md5Value) {
+		this.contentMD5 = md5Value;
+	}
+
+	/**
+	 * 设置待上传文件的"访问密钥"
+	 * <p>
+	 * 注意:<br>
+	 * 仅支持图片空!设置密钥后,无法根据原文件URL直接访问,需带 URL 后面加上 (缩略图间隔标志符+密钥) 进行访问
+	 * <p>
+	 * 举例:<br>
+	 * 如果缩略图间隔标志符为"!",密钥为"bac",上传文件路径为"/folder/test.jpg",<br>
+	 * 那么该图片的对外访问地址为:http://空间域名 /folder/test.jpg!bac
+	 *
+	 * @param secret
+	 *            密钥字符串
+	 */
+	public void setFileSecret(String secret) {
+		this.fileSecret = secret;
+	}
+
+	public String getPicWidth() {
+		return picWidth;
+	}
+
+	public String getPicHeight() {
+		return picHeight;
+	}
+
+	public String getPicFrames() {
+		return picFrames;
+	}
+
+	public String getPicType() {
+		return picType;
+	}
+
+	/**
+	 * 获取当前SDK的版本号
+	 *
+	 * @return SDK版本号
+	 */
+	public String version() {
+		return VERSION;
+	}
+
+	/**
+	 * 获取总体空间的占用量
+	 *
+	 * @param path
+	 *            目标路径
+	 * @return 空间占用量,失败时返回 -1
+	 */
+	public long getBucketUsage() {
+		long usage = -1;
+
+		String result = HttpAction(METHOD_GET, formatPath("/") + "/?usage");
+
+		if (!isEmpty(result)) {
+
+			try {
+				usage = Long.parseLong(result.trim());
+			} catch (NumberFormatException e) {
+			}
+		}
+
+		return usage;
+	}
+
+	/**
+	 * 获取某个子目录的占用量
+	 *
+	 * @param path
+	 *            目标路径
+	 * @return 空间占用量,失败时返回 -1
+	 */
+	@Deprecated
+	public long getFolderUsage(String path) {
+
+		long usage = -1;
+
+		String result = HttpAction(METHOD_GET, formatPath(path) + "/?usage");
+
+		if (!isEmpty(result)) {
+
+			try {
+				usage = Long.parseLong(result.trim());
+			} catch (NumberFormatException e) {
+			}
+		}
+
+		return usage;
+	}
+
+	/**
+	 * 上传文件
+	 *
+	 * @param filePath
+	 *            文件路径(包含文件名)
+	 * @param datas
+	 *            文件内容
+	 * @return true or false
+	 */
+	public boolean writeFile(String filePath, byte[] datas) {
+		return writeFile(filePath, datas, false, null);
+	}
+
+	/**
+	 * 上传文件
+	 *
+	 * @param filePath
+	 *            文件路径(包含文件名)
+	 * @param datas
+	 *            文件内容
+	 * @param auto
+	 *            是否自动创建父级目录(最多10级)
+	 * @return true or false
+	 */
+	public boolean writeFile(String filePath, byte[] datas, boolean auto) {
+		return writeFile(filePath, datas, auto, null);
+	}
+
+	/**
+	 * 上传文件
+	 *
+	 * @param filePath
+	 *            文件路径(包含文件名)
+	 * @param datas
+	 *            文件内容
+	 * @param auto
+	 *            是否自动创建父级目录(最多10级)
+	 * @param params
+	 *            额外参数
+	 * @return true or false
+	 */
+	public boolean writeFile(String filePath, byte[] datas, boolean auto,
+			Map<String, String> params) {
+
+		return HttpAction(METHOD_PUT, formatPath(filePath), datas, null, auto, params) != null;
+	}
+
+	/**
+	 * 上传文件
+	 *
+	 * @param filePath
+	 *            文件路径(包含文件名)
+	 * @param String
+	 *            datas 文件内容
+	 * @return true or false
+	 */
+	public boolean writeFile(String filePath, String datas) {
+		return writeFile(filePath, datas, false, null);
+	}
+
+	/**
+	 * 上传文件
+	 *
+	 * @param filePath
+	 *            文件路径(包含文件名)
+	 * @param String
+	 *            datas 文件内容
+	 * @param auto
+	 *            是否自动创建父级目录(最多10级)
+	 * @return true or false
+	 */
+	public boolean writeFile(String filePath, String datas, boolean auto) {
+		return writeFile(filePath, datas, auto, null);
+	}
+
+	/**
+	 * 上传文件
+	 *
+	 * @param filePath
+	 *            文件路径(包含文件名)
+	 * @param String
+	 *            datas 文件内容
+	 * @param auto
+	 *            是否自动创建父级目录(最多10级)
+	 * @param params
+	 *            额外参数
+	 * @return true or false
+	 */
+	public boolean writeFile(String filePath, String datas, boolean auto,
+			Map<String, String> params) {
+
+		boolean result = false;
+
+		try {
+			result = writeFile(filePath, datas.getBytes(UTF8), auto, params);
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+
+		return result;
+	}
+
+	/**
+	 * 上传文件
+	 *
+	 * @param filePath
+	 *            文件路径(包含文件名)
+	 * @param file
+	 *            待上传的文件
+	 * @return true or false
+	 * @throws IOException
+	 */
+	public boolean writeFile(String filePath, File file) throws IOException {
+		return writeFile(filePath, file, false, null);
+	}
+
+	/**
+	 * 上传文件
+	 *
+	 * @param filePath
+	 *            文件路径(包含文件名)
+	 * @param file
+	 *            待上传的文件
+	 * @param auto
+	 *            是否自动创建父级目录(最多10级)
+	 * @return true or false
+	 * @throws IOException
+	 */
+	public boolean writeFile(String filePath, File file, boolean auto) throws IOException {
+		return writeFile(filePath, file, auto, null);
+	}
+
+	/**
+	 * 上传文件
+	 *
+	 * @param filePath
+	 *            文件路径(包含文件名)
+	 * @param file
+	 *            待上传的文件
+	 * @param auto
+	 *            是否自动创建父级目录(最多10级)
+	 * @param params
+	 *            额外参数
+	 * @return true or false
+	 * @throws IOException
+	 */
+	public boolean writeFile(String filePath, File file, boolean auto, Map<String, String> params)
+			throws IOException {
+
+		filePath = formatPath(filePath);
+
+		InputStream is = null;
+		OutputStream os = null;
+		HttpURLConnection conn = null;
+
+		try {
+			// 读取待上传的文件
+			is = new FileInputStream(file);
+
+			// 获取链接
+			URL url = new URL("http://" + apiDomain + filePath);
+			conn = (HttpURLConnection) url.openConnection();
+
+			// 设置必要参数
+			conn.setConnectTimeout(timeout);
+			conn.setRequestMethod(METHOD_PUT);
+			conn.setUseCaches(false);
+			conn.setDoOutput(true);
+			conn.setChunkedStreamingMode(0);
+
+			String date = getGMTDate();
+
+			// 设置时间
+			conn.setRequestProperty(DATE, date);
+
+			// 设置文件的 MD5 参数
+			if (!isEmpty(this.contentMD5)) {
+				conn.setRequestProperty(CONTENT_MD5, this.contentMD5);
+			}
+
+			// 设置签名
+			// conn.setRequestProperty(AUTHORIZATION,
+			// sign(conn, filePath, file.length()));
+
+			conn.setRequestProperty(AUTHORIZATION, UpYunUtils
+					.sign(METHOD_PUT, date, filePath, userName, password, contentMD5).trim());
+			this.contentMD5 = null;
+
+			// 设置文件的访问密钥
+			if (!isEmpty(this.fileSecret)) {
+				conn.setRequestProperty(CONTENT_SECRET, this.fileSecret);
+				this.fileSecret = null;
+			}
+
+			// 是否自动创建父级目录
+			if (auto) {
+				conn.setRequestProperty(MKDIR, "true");
+			}
+
+			// 设置额外的参数,如图片缩略图等
+			if (params != null && !params.isEmpty()) {
+
+				for (Map.Entry<String, String> param : params.entrySet()) {
+					conn.setRequestProperty(param.getKey(), param.getValue());
+				}
+			}
+
+			// 创建链接
+			conn.connect();
+
+			os = conn.getOutputStream();
+			byte[] data = new byte[4096];
+			int temp = 0;
+
+			// 上传文件内容
+			while ((temp = is.read(data)) != -1) {
+				os.write(data, 0, temp);
+			}
+
+			// 获取返回的信息
+			getText(conn, false);
+
+			// 上传成功
+			return true;
+
+		} catch (Exception e) {
+			if (debug)
+				e.printStackTrace();
+
+			// 上传失败
+			return false;
+		} finally {
+
+			if (os != null) {
+				os.close();
+				os = null;
+			}
+			if (is != null) {
+				is.close();
+				is = null;
+			}
+			if (conn != null) {
+				conn.disconnect();
+				conn = null;
+			}
+		}
+	}
+
+	/**
+	 * 读取文件
+	 *
+	 * @param filePath
+	 *            文件路径(包含文件名)
+	 * @return 文件内容 或 null
+	 */
+	public String readFile(String filePath) {
+		return HttpAction(METHOD_GET, formatPath(filePath));
+	}
+
+	/**
+	 * 读取文件
+	 *
+	 * @param filePath
+	 *            文件路径(包含文件名)
+	 * @param file
+	 *            临时文件
+	 * @return true or false
+	 */
+	public boolean readFile(String filePath, File file) {
+
+		String result = HttpAction(METHOD_GET, formatPath(filePath), null, file, false);
+
+		return "".equals(result);
+	}
+
+	/**
+	 * 获取文件信息
+	 *
+	 * @param filePath
+	 *            文件路径(包含文件名)
+	 * @return 文件信息 或 null
+	 */
+	public Map<String, String> getFileInfo(String filePath) {
+
+		HttpAction(METHOD_HEAD, formatPath(filePath));
+
+		// 判断是否存在文件信息
+		if (isEmpty(fileType) && isEmpty(fileSize) && isEmpty(fileDate)) {
+			return null;
+		}
+
+		Map<String, String> mp = new HashMap<String, String>();
+		mp.put("type", fileType);
+		mp.put("size", fileSize);
+		mp.put("date", fileDate);
+
+		return mp;
+	}
+
+	/**
+	 * 删除文件
+	 *
+	 * @param filePath
+	 *            文件路径(包含文件名)
+	 * @return true or false
+	 */
+	public boolean deleteFile(String filePath) {
+
+		return HttpAction(METHOD_DELETE, formatPath(filePath)) != null;
+	}
+
+	/**
+	 * 创建目录
+	 *
+	 * @param path
+	 *            目录路径
+	 * @return true or false
+	 */
+	public boolean mkDir(String path) {
+		return mkDir(path, false);
+	}
+
+	/**
+	 * 创建目录
+	 *
+	 * @param path
+	 *            目录路径
+	 * @param auto
+	 *            是否自动创建父级目录(最多10级)
+	 * @return true or false
+	 */
+	public boolean mkDir(String path, boolean auto) {
+
+		Map<String, String> params = new HashMap<String, String>(1);
+		params.put(PARAMS.KEY_MAKE_DIR.getValue(), "true");
+
+		String result = HttpAction(METHOD_PUT, formatPath(path), null, null, auto, params);
+
+		return result != null;
+	}
+
+	/**
+	 * 读取目录列表
+	 *
+	 * @param path
+	 *            目录路径
+	 * @return List<FolderItem> 或 null
+	 */
+	public List<FolderItem> readDir(String path) {
+
+		String result = HttpAction(METHOD_GET, formatPath(path) + SEPARATOR);
+
+		if (isEmpty(result))
+			return null;
+
+		List<FolderItem> list = new LinkedList<FolderItem>();
+
+		String[] datas = result.split("\n");
+
+		for (int i = 0; i < datas.length; i++) {
+			if (datas[i].indexOf("\t") > 0) {
+				list.add(new FolderItem(datas[i]));
+			}
+		}
+		return list;
+	}
+
+	/**
+	 * 删除目录
+	 *
+	 * @param path
+	 *            目录路径
+	 * @return true or false
+	 */
+	public boolean rmDir(String path) {
+		return HttpAction(METHOD_DELETE, formatPath(path)) != null;
+	}
+
+	/**
+	 * 获取上传文件后的信息(仅图片空间有返回数据)
+	 *
+	 * @param key
+	 *            信息字段名(x-upyun-width、x-upyun-height、x-upyun-frames、x-upyun-file
+	 *            -type)
+	 * @return value or NULL
+	 * @deprecated
+	 */
+	public String getWritedFileInfo(String key) {
+
+		if (isEmpty(picWidth))
+			return null;
+
+		if (X_UPYUN_WIDTH.equals(key))
+			return picWidth;
+		if (X_UPYUN_HEIGHT.equals(key))
+			return picHeight;
+		if (X_UPYUN_FRAMES.equals(key))
+			return picFrames;
+		if (X_UPYUN_FILE_TYPE.equals(key))
+			return picType;
+
+		return null;
+	}
+
+	/**
+	 * 对字符串进行 MD5 加密
+	 *
+	 * @param str
+	 *            待加密字符串
+	 * @return 加密后字符串
+	 */
+	public static String md5(String str) {
+		char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
+				'e', 'f'};
+		MessageDigest md5 = null;
+		try {
+			md5 = MessageDigest.getInstance("MD5");
+			md5.update(str.getBytes(UTF8));
+		} catch (NoSuchAlgorithmException e) {
+			e.printStackTrace();
+			throw new RuntimeException(e.getMessage());
+		} catch (UnsupportedEncodingException e) {
+			e.printStackTrace();
+			throw new RuntimeException(e.getMessage());
+		}
+		byte[] encodedValue = md5.digest();
+		int j = encodedValue.length;
+		char finalValue[] = new char[j * 2];
+		int k = 0;
+		for (int i = 0; i < j; i++) {
+			byte encoded = encodedValue[i];
+			finalValue[k++] = hexDigits[encoded >> 4 & 0xf];
+			finalValue[k++] = hexDigits[encoded & 0xf];
+		}
+
+		return new String(finalValue);
+	}
+
+	/**
+	 * 对文件进行 MD5 加密
+	 *
+	 * @param file
+	 *            待加密的文件
+	 * @return 文件加密后的 MD5 值
+	 * @throws IOException
+	 */
+	public static String md5(File file) throws IOException {
+		FileInputStream is = new FileInputStream(file);
+		char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
+				'e', 'f'};
+		MessageDigest md5 = null;
+		try {
+			md5 = MessageDigest.getInstance("MD5");
+			int n = 0;
+			byte[] buffer = new byte[1024];
+			do {
+				n = is.read(buffer);
+				if (n > 0) {
+					md5.update(buffer, 0, n);
+				}
+			}
+			while (n != -1);
+			is.skip(0);
+		} catch (NoSuchAlgorithmException e) {
+			e.printStackTrace();
+			throw new RuntimeException(e.getMessage());
+		} finally {
+			is.close();
+		}
+
+		byte[] encodedValue = md5.digest();
+
+		int j = encodedValue.length;
+		char finalValue[] = new char[j * 2];
+		int k = 0;
+		for (int i = 0; i < j; i++) {
+			byte encoded = encodedValue[i];
+			finalValue[k++] = hexDigits[encoded >> 4 & 0xf];
+			finalValue[k++] = hexDigits[encoded & 0xf];
+		}
+
+		return new String(finalValue);
+	}
+
+	/**
+	 * 获取 GMT 格式时间戳
+	 *
+	 * @return GMT 格式时间戳
+	 */
+	private String getGMTDate() {
+		SimpleDateFormat formater = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'",
+				Locale.US);
+		formater.setTimeZone(TimeZone.getTimeZone("GMT"));
+		return formater.format(new Date());
+	}
+
+	/**
+	 * 计算签名
+	 *
+	 * @param conn
+	 *            连接
+	 * @param uri
+	 *            请求地址
+	 * @param length
+	 *            请求所发Body数据长度
+	 * @return 签名字符串
+	 */
+	@SuppressWarnings("unused")
+	private String sign(HttpURLConnection conn, String uri, long length) {
+		String sign = conn.getRequestMethod() + "&" + uri + "&" + conn.getRequestProperty(DATE)
+				+ "&" + length + "&" + password;
+		return "UpYun " + userName + ":" + md5(sign);
+	}
+
+	/**
+	 * 连接处理逻辑
+	 *
+	 * @param method
+	 *            请求方式 {GET, POST, PUT, DELETE}
+	 * @param uri
+	 *            请求地址
+	 * @return 请求结果(字符串)或 null
+	 */
+	private String HttpAction(String method, String uri) {
+		return HttpAction(method, uri, null, null, false);
+	}
+
+	/**
+	 * 连接处理逻辑
+	 *
+	 * @param method
+	 *            请求方式 {GET, POST, PUT, DELETE}
+	 * @param uri
+	 *            请求地址
+	 * @param datas
+	 *            该请求所需发送数据(可为 null)
+	 * @param outFile
+	 *            文件描述符(可为 null)
+	 * @param auto
+	 *            自动创建父级目录(最多10级)
+	 * @return 请求结果(字符串)或 null
+	 */
+	private String HttpAction(String method, String uri, byte[] datas, File outFile, boolean auto) {
+
+		return HttpAction(method, uri, datas, outFile, auto, null);
+	}
+
+	/**
+	 * 连接处理逻辑
+	 *
+	 * @param method
+	 *            请求方式 {GET, POST, PUT, DELETE}
+	 * @param uri
+	 *            请求地址
+	 * @param datas
+	 *            该请求所需发送数据(可为 null)
+	 * @param outFile
+	 *            文件描述符(可为 null)
+	 * @param auto
+	 *            自动创建父级目录(最多10级)
+	 * @param params
+	 *            额外参数
+	 * @return 请求结果(字符串)或 null
+	 */
+	private String HttpAction(String method, String uri, byte[] datas, File outFile, boolean auto,
+			Map<String, String> params) {
+
+		String result = null;
+
+		HttpURLConnection conn = null;
+		OutputStream os = null;
+		InputStream is = null;
+
+		try {
+			// 获取链接
+			URL url = new URL("http://" + apiDomain + uri);
+			conn = (HttpURLConnection) url.openConnection();
+
+			// 设置必要参数
+			conn.setConnectTimeout(timeout);
+			conn.setRequestMethod(method);
+			conn.setUseCaches(false);
+			if (!method.equals(METHOD_DELETE) && !method.equals(METHOD_HEAD)
+					&& !method.equals(METHOD_GET)) {
+				conn.setDoOutput(true);
+			}
+
+			String date = getGMTDate();
+
+			// 设置时间
+			conn.setRequestProperty(DATE, date);
+			conn.setRequestProperty("User-Agent", UpYunUtils.VERSION);
+
+			// 是否自动创建父级目录
+			if (auto) {
+				conn.setRequestProperty(MKDIR, "true");
+			}
+
+			@SuppressWarnings("unused")
+			long contentLength = 0;
+
+			if (datas == null) {
+				conn.setRequestProperty(CONTENT_LENGTH, "0");
+			} else {
+				contentLength = datas.length;
+				conn.setRequestProperty(CONTENT_LENGTH, String.valueOf(datas.length));
+
+				// 设置文件的 MD5 参数
+				if (!isEmpty(this.contentMD5)) {
+					conn.setRequestProperty(CONTENT_MD5, this.contentMD5);
+				}
+				// 设置文件的访问密钥
+				if (!isEmpty(this.fileSecret)) {
+					conn.setRequestProperty(CONTENT_SECRET, this.fileSecret);
+					this.fileSecret = null;
+				}
+			}
+
+			// 设置签名
+			// conn.setRequestProperty(AUTHORIZATION,
+			// sign(conn, uri, contentLength));
+
+			conn.setRequestProperty(AUTHORIZATION,
+					UpYunUtils.sign(method, date, uri, userName, password, contentMD5));
+			this.contentMD5 = null;
+			// 是否是创建文件目录
+			boolean isFolder = false;
+
+			// 设置额外的参数,如图片缩略图等
+			if (params != null && !params.isEmpty()) {
+
+				isFolder = !isEmpty(params.get(PARAMS.KEY_MAKE_DIR.getValue()));
+
+				for (Map.Entry<String, String> param : params.entrySet()) {
+					conn.setRequestProperty(param.getKey(), param.getValue());
+				}
+			}
+
+			// 创建链接
+			conn.connect();
+
+			if (datas != null) {
+				os = conn.getOutputStream();
+				os.write(datas);
+				os.flush();
+			}
+
+			if (isFolder) {
+				os = conn.getOutputStream();
+				os.flush();
+			}
+
+			if (outFile == null) {
+
+				result = getText(conn, METHOD_HEAD.equals(method));
+
+			} else {
+				result = "";
+
+				os = new FileOutputStream(outFile);
+				byte[] data = new byte[4096];
+				int temp = 0;
+
+				is = conn.getInputStream();
+
+				while ((temp = is.read(data)) != -1) {
+					os.write(data, 0, temp);
+				}
+			}
+		} catch (Exception e) {
+			if (debug)
+				e.printStackTrace();
+
+			// 操作失败
+			return null;
+		} finally {
+
+			try {
+				if (os != null) {
+					os.close();
+					os = null;
+				}
+				if (is != null) {
+					is.close();
+					is = null;
+				}
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+
+			if (conn != null) {
+				conn.disconnect();
+				conn = null;
+			}
+		}
+
+		return result;
+	}
+
+	/**
+	 * 获得连接请求的返回数据
+	 *
+	 * @param conn
+	 * @return 字符串
+	 */
+	private String getText(HttpURLConnection conn, boolean isHeadMethod) throws IOException {
+
+		StringBuilder text = new StringBuilder();
+		fileType = null;
+
+		InputStream is = null;
+		InputStreamReader sr = null;
+		BufferedReader br = null;
+
+		int code = conn.getResponseCode();
+
+		try {
+			is = conn.getInputStream();
+
+			if (!isHeadMethod) {
+				sr = new InputStreamReader(is);
+				br = new BufferedReader(sr);
+
+				char[] chars = new char[4096];
+				int length = 0;
+
+				while ((length = br.read(chars)) != -1) {
+					text.append(chars, 0, length);
+				}
+			}
+			if (200 == code && conn.getHeaderField(X_UPYUN_WIDTH) != null) {
+				picWidth = conn.getHeaderField(X_UPYUN_WIDTH);
+				picHeight = conn.getHeaderField(X_UPYUN_HEIGHT);
+				picFrames = conn.getHeaderField(X_UPYUN_FRAMES);
+				picType = conn.getHeaderField(X_UPYUN_FILE_TYPE);
+			} else {
+				picWidth = picHeight = picFrames = picType = null;
+			}
+
+			if (200 == code && conn.getHeaderField(X_UPYUN_FILE_TYPE) != null) {
+				fileType = conn.getHeaderField(X_UPYUN_FILE_TYPE);
+				fileSize = conn.getHeaderField(X_UPYUN_FILE_SIZE);
+				fileDate = conn.getHeaderField(X_UPYUN_FILE_DATE);
+			} else {
+				fileType = fileSize = fileDate = null;
+			}
+		} finally {
+			if (br != null) {
+				br.close();
+				br = null;
+			}
+			if (sr != null) {
+				sr.close();
+				sr = null;
+			}
+			if (is != null) {
+				is.close();
+				is = null;
+			}
+		}
+
+		if (isHeadMethod) {
+			if (code >= 400)
+				return null;
+			return "";
+		}
+
+		if (code >= 400)
+			throw new IOException(text.toString());
+
+		return text.toString();
+	}
+
+	/**
+	 * 判断字符串是否为空
+	 *
+	 * @param str
+	 * @return 是否为空
+	 */
+	private boolean isEmpty(String str) {
+		return str == null || str.length() == 0;
+	}
+
+	/**
+	 * 格式化路径参数,去除前后的空格并确保以"/"开头,最后添加"/空间名"
+	 * <p>
+	 * 最终构成的格式:"/空间名/文件路径"
+	 *
+	 * @param path
+	 *            目录路径或文件路径
+	 * @return 格式化后的路径
+	 */
+	private String formatPath(String path) {
+
+		if (!isEmpty(path)) {
+
+			// 去除前后的空格
+			path = path.trim();
+
+			// 确保路径以"/"开头
+			if (!path.startsWith(SEPARATOR)) {
+				return SEPARATOR + bucketName + SEPARATOR + path;
+			}
+		}
+
+		return SEPARATOR + bucketName + path;
+	}
+
+	public class FolderItem {
+		// 文件名
+		public String name;
+
+		// 文件类型 {file, folder}
+		public String type;
+
+		// 文件大小
+		public long size;
+
+		// 文件日期
+		public Date date;
+
+		public FolderItem(String data) {
+			String[] a = data.split("\t");
+			if (a.length == 4) {
+				this.name = a[0];
+				this.type = ("N".equals(a[1]) ? "File" : "Folder");
+				try {
+					this.size = Long.parseLong(a[2].trim());
+				} catch (NumberFormatException e) {
+					this.size = -1;
+				}
+				long da = 0;
+				try {
+					da = Long.parseLong(a[3].trim());
+				} catch (NumberFormatException e) {
+				}
+				this.date = new Date(da * 1000);
+			}
+		}
+
+		@Override
+		public String toString() {
+			return "time = " + date + "  size = " + size + "  type = " + type + "  name = " + name;
+		}
+	}
+
+	/**
+	 * 其他额外参数的键值和参数值
+	 */
+	public enum PARAMS {
+
+		/**
+		 * 缩略图类型
+		 * <p>
+		 * 使用场景:上传图片时若无需保存原图,而只需某种特定大小的缩略图,比如说用户头像。
+		 * <p>
+		 * 说明:该参数必须搭配 KEY_X_GMKERL_VALUE 使用,否则无效。另外,使用该参数后将不保存原图,切忌。
+		 * <p>
+		 * 可选参数:<br>
+		 * 1)VALUE_FIX_MAX("fix_max"):"限定最长边,短边自适应"<br>
+		 * 2)VALUE_FIX_MIN("fix_min"):"限定最短边,长边自适应"<br>
+		 * 3)VALUE_FIX_WIDTH_OR_HEIGHT("fix_width_or_height"):"限定宽度和高度"<br>
+		 * 4)VALUE_FIX_WIDTH("fix_width"):"限定宽度,高度自适应"<br>
+		 * 5)VALUE_FIX_HEIGHT("fix_height"):"限定高度,宽度自适应"<br>
+		 * 6)VALUE_FIX_BOTH("fix_both"):"固定宽度和高度"<br>
+		 * 7)VALUE_FIX_SCALE("fix_scale"):"等比例缩放"<br>
+		 * 8)VALUE_SQUARE("square"):"方块图,固定高固定宽"<br>
+		 *
+		 * @see 参数举例:http://wiki.upyun.com/index.php?title=缩略图方式差别举例
+		 */
+		KEY_X_GMKERL_TYPE("x-gmkerl-type"),
+
+		/**
+		 * 缩略图参数值
+		 * <p>
+		 * 说明:该参数必须搭配 KEY_X_GMKERL_TYPE 使用,否则无效。具体的值需要根据 KEY_X_GMKERL_TYPE 而定。
+		 */
+		KEY_X_GMKERL_VALUE("x-gmkerl-value"),
+
+		/**
+		 * 缩略图质量:图片压缩质量,默认 95
+		 * <p>
+		 * 使用场景:用户上传高保真图片,但自身业务又无需太高质量的图片时,可以设置该参数减少文件保存的大小,从而减少空间的使用量。
+		 * <p>
+		 * 说明:使用该参数后将不保存原图,切忌。
+		 */
+		KEY_X_GMKERL_QUALITY("x-gmkerl-quality"),
+
+		/**
+		 * 图片锐化:默认锐化(true)
+		 * <p>
+		 * 使用场景:图片处理后质量太差,可以使用该参数模糊边缘,提高图片的清晰度或者焦距程度,使图片特定区域的色彩更加鲜明。
+		 * <p>
+		 * 说明:锐化不是万能的,很容易使图片不真实;另外,也无法通过锐化达到原图的效果。
+		 */
+		KEY_X_GMKERL_UNSHARP("x-gmkerl-unsharp"),
+
+		/**
+		 * 缩略图版本
+		 * <p>
+		 * 使用场景:快速处理原图,生成自定义的缩略图。
+		 * <p>
+		 * 说明:使用该参数前需要创建好缩略图版本号;另外,使用该参数后将不保存原图,切忌。
+		 *
+		 * @see http://wiki.upyun.com/index.php?title=如何创建自定义缩略图
+		 */
+		KEY_X_GMKERL_THUMBNAIL("x-gmkerl-thumbnail"),
+
+		/**
+		 * 图片旋转
+		 * <p>
+		 * 使用场景:待上传的图片若是倾斜的,使用该参数可以直接进行强制的或自动的扶正。
+		 * <p>
+		 * 说明:只接受"auto","90","180","270"四种参数,其中"auto"参数根据图片 EXIF
+		 * 中的信息进行自动扶正,若图片没有 EXIF 信息,则该参数无效。另外,使用该参数后将不保存原图,切忌。
+		 *
+		 * @see http://wiki.upyun.com/index.php?title=图片旋转
+		 */
+		KEY_X_GMKERL_ROTATE("x-gmkerl-rotate"),
+
+		/**
+		 * 图片裁剪
+		 * <p>
+		 * 使用场景:只需要保存待上传图片的某一个部分,比如用户上传头像图片进行裁剪。
+		 * <p>
+		 * 说明:参数格式为x,y,width,height,且需要满足 x >= 0 && y >=0 && width > 0 && height
+		 * > 0
+		 *
+		 * @see http://wiki.upyun.com/index.php?title=图片裁剪
+		 */
+		KEY_X_GMKERL_CROP("x-gmkerl-crop"),
+
+		/**
+		 * 是否保留exif信息
+		 * <p>
+		 * 使用场景:对于原图包含EXIF信息,在上传图片时又进行了“破坏性处理”(比如裁剪、缩略、自定义版本等),
+		 * upyun默认会删除原图的EXIF信息。 此时搭配该参数可以保留原图的EXIF信息。比如旅游应用从缩略图中获取具体的地理信息。
+		 * <p>
+		 * 说明:仅搭配"破坏性处理"的参数使用时有效,其他处理均无效;另外key对应的值仅设置为"true"时有效;
+		 */
+		KEY_X_GMKERL_EXIF_SWITCH("x-gmkerl-exif-switch"),
+
+		/**
+		 * 创建目录
+		 * <p>
+		 * 说明:SDK内部使用
+		 */
+		KEY_MAKE_DIR("folder"),
+
+		/**
+		 * 缩略图类型之 "限定最长边,短边自适应",参数为像素值,如: 150
+		 */
+		VALUE_FIX_MAX("fix_max"),
+		/**
+		 * 缩略图类型之 "限定最短边,长边自适应",参数为像素值,如: 150
+		 */
+		VALUE_FIX_MIN("fix_min"),
+		/**
+		 * 缩略图类型之 "限定宽度和高度",参数为像素值,如: 150x130
+		 */
+		VALUE_FIX_WIDTH_OR_HEIGHT("fix_width_or_height"),
+		/**
+		 * 缩略图类型之 "限定宽度,高度自适应",参数为像素值,如: 150
+		 */
+		VALUE_FIX_WIDTH("fix_width"),
+		/**
+		 * 缩略图类型之 "限定高度,宽度自适应",参数为像素值,如: 150
+		 */
+		VALUE_FIX_HEIGHT("fix_height"),
+		/**
+		 * 缩略图类型之 "方块图,固定高固定宽",参数为像素值,如: 150
+		 */
+		VALUE_SQUARE("square"),
+		/**
+		 * 缩略图类型之 "固定宽度和高度",参数为像素值,如: 150x130
+		 */
+		VALUE_FIX_BOTH("fix_both"),
+		/**
+		 * 缩略图类型之 "等比例缩放",参数为比例值(1-99),如: 50
+		 */
+		VALUE_FIX_SCALE("fix_scale"),
+
+		/**
+		 * 图片旋转之 "自动扶正"
+		 */
+		VALUE_ROTATE_AUTO("auto"),
+		/**
+		 * 图片旋转之 "旋转90度"
+		 */
+		VALUE_ROTATE_90("90"),
+		/**
+		 * 图片旋转之 "旋转180度"
+		 */
+		VALUE_ROTATE_180("180"),
+		/**
+		 * 图片旋转之 "旋转270度"
+		 */
+		VALUE_ROTATE_270("270");
+
+		private final String value;
+
+		private PARAMS(String val) {
+			value = val;
+		}
+
+		public String getValue() {
+			return value;
+		}
+	}
+}