1
0
dq 6 роки тому
батько
коміт
a92d0698a2
25 змінених файлів з 7035 додано та 0 видалено
  1. 35 0
      kmall-admin/src/main/resources/conf/fastdfs.properties
  2. 429 0
      kmall-common/src/main/java/com/kmall/common/fileserver/common/Base64.java
  3. 216 0
      kmall-common/src/main/java/com/kmall/common/fileserver/common/IniFileReader.java
  4. 24 0
      kmall-common/src/main/java/com/kmall/common/fileserver/common/MyException.java
  5. 48 0
      kmall-common/src/main/java/com/kmall/common/fileserver/common/NameValuePair.java
  6. 304 0
      kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/ClientGlobal.java
  7. 27 0
      kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/DownloadCallback.java
  8. 44 0
      kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/DownloadStream.java
  9. 125 0
      kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/FileInfo.java
  10. 503 0
      kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/ProtoCommon.java
  11. 48 0
      kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/ProtoStructDecoder.java
  12. 66 0
      kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/ServerInfo.java
  13. 1786 0
      kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/StorageClient.java
  14. 732 0
      kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/StorageClient1.java
  15. 57 0
      kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/StorageServer.java
  16. 73 0
      kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/StructBase.java
  17. 225 0
      kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/StructGroupStat.java
  18. 982 0
      kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/StructStorageStat.java
  19. 811 0
      kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/TrackerClient.java
  20. 106 0
      kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/TrackerGroup.java
  21. 81 0
      kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/TrackerServer.java
  22. 28 0
      kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/UploadCallback.java
  23. 55 0
      kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/UploadStream.java
  24. 62 0
      kmall-common/src/main/java/com/kmall/common/fileserver/util/FastDFSFile.java
  25. 168 0
      kmall-common/src/main/java/com/kmall/common/fileserver/util/FileManager.java

+ 35 - 0
kmall-admin/src/main/resources/conf/fastdfs.properties

@@ -0,0 +1,35 @@
+##############生产环境################
+
+#连接超时时间
+connect_timeout=30
+
+#网络超时时间
+network_timeout=60
+
+#文件路径
+base_path=/data/files/
+
+#tracker server是FastDFS文件系统的协调者,其主要作用是负载均衡和调度。
+#Tracker server在内存中记录分组和Storage server的状态等信息,不记录文件索引信息
+tracker_server=172.18.68.117:22122
+
+log_level=info
+
+use_connection_pool = false
+
+connection_pool_max_idle_time = 3600
+
+load_fdfs_parameters_from_tracker=false
+
+use_storage_id = false
+
+storage_ids_filename = storage_ids.conf
+
+#HTTP 服务器地址
+http.tracket_nginx_addr=120.76.26.84
+
+#HTTP 服务器端口号
+http.tracker_server_port=80
+
+#文件作者
+file.author=kmall-pt

+ 429 - 0
kmall-common/src/main/java/com/kmall/common/fileserver/common/Base64.java

@@ -0,0 +1,429 @@
+package com.kmall.common.fileserver.common;
+
+import java.io.IOException;
+
+/**
+ * Freeware from:
+ * Roedy Green
+ * Canadian Mind Products
+ * #327 - 964 Heywood Avenue
+ * Victoria, BC Canada V8V 2Y5
+ * tel:(250) 361-9093
+ * mailto:roedy@mindprod.com
+ */
+
+/**
+ * Encode arbitrary binary into printable ASCII using BASE64 encoding.
+ * very loosely based on the Base64 Reader by
+ * Dr. Mark Thornton
+ * Optrak Distribution Software Ltd.
+ * http://www.optrak.co.uk
+ * and Kevin Kelley's  http://www.ruralnet.net/~kelley/java/Base64.java
+ * <p>
+ * Base64 is a way of encoding 8-bit characters using only ASCII printable
+ * characters similar to UUENCODE.  UUENCODE includes a filename where BASE64 does not.
+ * The spec is described in RFC 2045.  Base64 is a scheme where
+ * 3 bytes are concatenated, then split to form 4 groups of 6-bits each; and
+ * each 6-bits gets translated to an encoded printable ASCII character, via a
+ * table lookup.  An encoded string is therefore longer than the original by
+ * about 1/3.  The "=" character is used to pad the end.  Base64 is used,
+ * among other things, to encode the user:password string in an
+ * Authorization: header for HTTP.  Don't confuse Base64 with
+ * x-www-form-urlencoded which is handled by
+ * Java.net.URLEncoder.encode/decode
+ * If you don't like this code, there is another implementation at http://www.ruffboy.com/download.htm
+ * Sun has an undocumented method called sun.misc.Base64Encoder.encode.
+ * You could use hex, simpler to code, but not as compact.
+ * <p>
+ * If you wanted to encode a giant file, you could do it in large chunks that
+ * are even multiples of 3 bytes, except for the last chunk, and append the outputs.
+ * <p>
+ * To encode a string, rather than binary data java.net.URLEncoder may be better. See
+ * printable characters in the Java glossary for a discussion of the differences.
+ * <p>
+ * version 1.4 2002 February 15  -- correct bugs with uneven line lengths,
+ * allow you to configure line separator.
+ * now need Base64 object and instance methods.
+ * new mailing address.
+ * version 1.3 2000 September 12 -- fix problems with estimating output length in encode
+ * version 1.2 2000 September 09 -- now handles decode as well.
+ * version 1.1 1999 December 04 -- more symmetrical encoding algorithm.
+ * more accurate StringBuffer allocation size.
+ * version 1.0 1999 December 03 -- posted in comp.lang.java.programmer.
+ * Futures Streams or files.
+ */
+
+public class Base64 {
+
+  /**
+   * Marker value for chars we just ignore, e.g. \n \r high ascii
+   */
+  static final int IGNORE = -1;
+  /**
+   * Marker for = trailing pad
+   */
+  static final int PAD = -2;
+  /**
+   * used to disable test driver
+   */
+  private static final boolean debug = true;
+  /**
+   * how we separate lines, e.g. \n, \r\n, \r etc.
+   */
+  private String lineSeparator = System.getProperty("line.separator");
+  /**
+   * max chars per line, excluding lineSeparator.  A multiple of 4.
+   */
+  private int lineLength = 72;
+  private char[] valueToChar = new char[64];
+  /**
+   * binary value encoded by a given letter of the alphabet 0..63
+   */
+  private int[] charToValue = new int[256];
+  private int[] charToPad = new int[4];
+
+  /* constructor */
+  public Base64() {
+    this.init('+', '/', '=');
+  }
+
+  /* constructor */
+  public Base64(char chPlus, char chSplash, char chPad, int lineLength) {
+    this.init(chPlus, chSplash, chPad);
+    this.lineLength = lineLength;
+  }
+
+  public Base64(int lineLength) {
+    this.lineLength = lineLength;
+  }
+
+  /**
+   * debug display array
+   */
+  public static void show(byte[] b) {
+    int count = 0;
+    int rows = 0;
+
+
+    for (int i = 0; i < b.length; i++) {
+      if (count == 8) {
+        System.out.print("  ");
+      } else if (count == 16) {
+        System.out.println("");
+        count = 0;
+        continue;
+      }
+      System.out.print(Integer.toHexString(b[i] & 0xFF).toUpperCase() + " ");
+      count++;
+
+    }
+    System.out.println();
+  }
+
+  /**
+   * debug display array
+   */
+  public static void display(byte[] b) {
+    for (int i = 0; i < b.length; i++) {
+      System.out.print((char) b[i]);
+    }
+    System.out.println();
+  }
+
+  public static void test() {
+    try {
+      Base64 b64 = new Base64();
+
+      //encode
+      //str64 = b64.encode(str.getBytes());
+      //System.out.println(str64);
+
+      String str64 = "CwUEFYoAAAADjQMC7ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267EI=";
+      //decode
+      byte[] theBytes = b64.decode(str64);
+      show(theBytes);
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+  /* initialise defaultValueToChar and defaultCharToValue tables */
+  private void init(char chPlus, char chSplash, char chPad) {
+    int index = 0;
+    // build translate this.valueToChar table only once.
+    // 0..25 -> 'A'..'Z'
+    for (int i = 'A'; i <= 'Z'; i++) {
+      this.valueToChar[index++] = (char) i;
+    }
+
+    // 26..51 -> 'a'..'z'
+    for (int i = 'a'; i <= 'z'; i++) {
+      this.valueToChar[index++] = (char) i;
+    }
+
+    // 52..61 -> '0'..'9'
+    for (int i = '0'; i <= '9'; i++) {
+      this.valueToChar[index++] = (char) i;
+    }
+
+    this.valueToChar[index++] = chPlus;
+    this.valueToChar[index++] = chSplash;
+
+    // build translate defaultCharToValue table only once.
+    for (int i = 0; i < 256; i++) {
+      this.charToValue[i] = IGNORE;  // default is to ignore
+    }
+
+    for (int i = 0; i < 64; i++) {
+      this.charToValue[this.valueToChar[i]] = i;
+    }
+
+    this.charToValue[chPad] = PAD;
+    java.util.Arrays.fill(this.charToPad, chPad);
+  }
+
+  /**
+   * Encode an arbitrary array of bytes as Base64 printable ASCII.
+   * It will be broken into lines of 72 chars each.  The last line is not
+   * terminated with a line separator.
+   * The output will always have an even multiple of data characters,
+   * exclusive of \n.  It is padded out with =.
+   */
+  public String encode(byte[] b) throws IOException {
+    // Each group or partial group of 3 bytes becomes four chars
+    // covered quotient
+    int outputLength = ((b.length + 2) / 3) * 4;
+
+    // account for trailing newlines, on all but the very last line
+    if (lineLength != 0) {
+      int lines = (outputLength + lineLength - 1) / lineLength - 1;
+      if (lines > 0) {
+        outputLength += lines * lineSeparator.length();
+      }
+    }
+
+    // must be local for recursion to work.
+    StringBuffer sb = new StringBuffer(outputLength);
+
+    // must be local for recursion to work.
+    int linePos = 0;
+
+    // first deal with even multiples of 3 bytes.
+    int len = (b.length / 3) * 3;
+    int leftover = b.length - len;
+    for (int i = 0; i < len; i += 3) {
+      // Start a new line if next 4 chars won't fit on the current line
+      // We can't encapsulete the following code since the variable need to
+      // be local to this incarnation of encode.
+      linePos += 4;
+      if (linePos > lineLength) {
+        if (lineLength != 0) {
+          sb.append(lineSeparator);
+        }
+        linePos = 4;
+      }
+
+      // get next three bytes in unsigned form lined up,
+      // in big-endian order
+      int combined = b[i + 0] & 0xff;
+      combined <<= 8;
+      combined |= b[i + 1] & 0xff;
+      combined <<= 8;
+      combined |= b[i + 2] & 0xff;
+
+      // break those 24 bits into a 4 groups of 6 bits,
+      // working LSB to MSB.
+      int c3 = combined & 0x3f;
+      combined >>>= 6;
+      int c2 = combined & 0x3f;
+      combined >>>= 6;
+      int c1 = combined & 0x3f;
+      combined >>>= 6;
+      int c0 = combined & 0x3f;
+
+      // Translate into the equivalent alpha character
+      // emitting them in big-endian order.
+      sb.append(valueToChar[c0]);
+      sb.append(valueToChar[c1]);
+      sb.append(valueToChar[c2]);
+      sb.append(valueToChar[c3]);
+    }
+
+    // deal with leftover bytes
+    switch (leftover) {
+      case 0:
+      default:
+        // nothing to do
+        break;
+
+      case 1:
+        // One leftover byte generates xx==
+        // Start a new line if next 4 chars won't fit on the current line
+        linePos += 4;
+        if (linePos > lineLength) {
+
+          if (lineLength != 0) {
+            sb.append(lineSeparator);
+          }
+          linePos = 4;
+        }
+
+        // Handle this recursively with a faked complete triple.
+        // Throw away last two chars and replace with ==
+        sb.append(encode(new byte[]{b[len], 0, 0}
+        ).substring(0, 2));
+        sb.append("==");
+        break;
+
+      case 2:
+        // Two leftover bytes generates xxx=
+        // Start a new line if next 4 chars won't fit on the current line
+        linePos += 4;
+        if (linePos > lineLength) {
+          if (lineLength != 0) {
+            sb.append(lineSeparator);
+          }
+          linePos = 4;
+        }
+        // Handle this recursively with a faked complete triple.
+        // Throw away last char and replace with =
+        sb.append(encode(new byte[]{b[len], b[len + 1], 0}
+        ).substring(0, 3));
+        sb.append("=");
+        break;
+
+    } // end switch;
+
+    if (outputLength != sb.length()) {
+      System.out.println("oops: minor program flaw: output length mis-estimated");
+      System.out.println("estimate:" + outputLength);
+      System.out.println("actual:" + sb.length());
+    }
+    return sb.toString();
+  }// end encode
+
+  /**
+   * decode a well-formed complete Base64 string back into an array of bytes.
+   * It must have an even multiple of 4 data characters (not counting \n),
+   * padded out with = as needed.
+   */
+  public byte[] decodeAuto(String s) {
+    int nRemain = s.length() % 4;
+    if (nRemain == 0) {
+      return this.decode(s);
+    } else {
+      return this.decode(s + new String(this.charToPad, 0, 4 - nRemain));
+    }
+  }
+
+  /**
+   * decode a well-formed complete Base64 string back into an array of bytes.
+   * It must have an even multiple of 4 data characters (not counting \n),
+   * padded out with = as needed.
+   */
+  public byte[] decode(String s) {
+
+    // estimate worst case size of output array, no embedded newlines.
+    byte[] b = new byte[(s.length() / 4) * 3];
+
+    // tracks where we are in a cycle of 4 input chars.
+    int cycle = 0;
+
+    // where we combine 4 groups of 6 bits and take apart as 3 groups of 8.
+    int combined = 0;
+
+    // how many bytes we have prepared.
+    int j = 0;
+    // will be an even multiple of 4 chars, plus some embedded \n
+    int len = s.length();
+    int dummies = 0;
+    for (int i = 0; i < len; i++) {
+
+      int c = s.charAt(i);
+      int value = (c <= 255) ? charToValue[c] : IGNORE;
+      // there are two magic values PAD (=) and IGNORE.
+      switch (value) {
+        case IGNORE:
+          // e.g. \n, just ignore it.
+          break;
+
+        case PAD:
+          value = 0;
+          dummies++;
+          // fallthrough
+        default:
+               /* regular value character */
+          switch (cycle) {
+            case 0:
+              combined = value;
+              cycle = 1;
+              break;
+
+            case 1:
+              combined <<= 6;
+              combined |= value;
+              cycle = 2;
+              break;
+
+            case 2:
+              combined <<= 6;
+              combined |= value;
+              cycle = 3;
+              break;
+
+            case 3:
+              combined <<= 6;
+              combined |= value;
+              // we have just completed a cycle of 4 chars.
+              // the four 6-bit values are in combined in big-endian order
+              // peel them off 8 bits at a time working lsb to msb
+              // to get our original 3 8-bit bytes back
+
+              b[j + 2] = (byte) combined;
+              combined >>>= 8;
+              b[j + 1] = (byte) combined;
+              combined >>>= 8;
+              b[j] = (byte) combined;
+              j += 3;
+              cycle = 0;
+              break;
+          }
+          break;
+      }
+    } // end for
+    if (cycle != 0) {
+      throw new ArrayIndexOutOfBoundsException("Input to decode not an even multiple of 4 characters; pad with =.");
+    }
+    j -= dummies;
+    if (b.length != j) {
+      byte[] b2 = new byte[j];
+      System.arraycopy(b, 0, b2, 0, j);
+      b = b2;
+    }
+    return b;
+
+  }// end decode
+
+  /**
+   * determines how long the lines are that are generated by encode.
+   * Ignored by decode.
+   *
+   * @param length 0 means no newlines inserted. Must be a multiple of 4.
+   */
+  public void setLineLength(int length) {
+    this.lineLength = (length / 4) * 4;
+  }
+
+  /**
+   * How lines are separated.
+   * Ignored by decode.
+   *
+   * @param lineSeparator may be "" but not null.
+   *                      Usually contains only a combination of chars \n and \r.
+   *                      Could be any chars not in set A-Z a-z 0-9 + /.
+   */
+  public void setLineSeparator(String lineSeparator) {
+    this.lineSeparator = lineSeparator;
+  }
+} // end Base64
+

+ 216 - 0
kmall-common/src/main/java/com/kmall/common/fileserver/common/IniFileReader.java

@@ -0,0 +1,216 @@
+/**
+ * Copyright (C) 2008 Happy Fish / YuQing
+ * <p>
+ * FastDFS Java Client may be copied only under the terms of the GNU Lesser
+ * General Public License (LGPL).
+ * Please visit the FastDFS Home Page http://www.csource.org/ for more detail.
+ **/
+
+package com.kmall.common.fileserver.common;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.Hashtable;
+
+/**
+ * ini file reader / parser
+ *
+ * @author Happy Fish / YuQing
+ * @version Version 1.0
+ */
+public class IniFileReader {
+  private Hashtable paramTable;
+  private String conf_filename;
+
+  /**
+   * @param conf_filename config filename
+   */
+  public IniFileReader(String conf_filename) throws IOException {
+    this.conf_filename = conf_filename;
+    loadFromFile(conf_filename);
+  }
+
+  public static ClassLoader classLoader() {
+    ClassLoader loader = Thread.currentThread().getContextClassLoader();
+    if (loader == null) {
+      loader = ClassLoader.getSystemClassLoader();
+    }
+    return loader;
+  }
+
+  public static InputStream loadFromOsFileSystemOrClasspathAsStream(String filePath) {
+    InputStream in = null;
+    try {
+      // 优先从文件系统路径加载
+      if (new File(filePath).exists()) {
+        in = new FileInputStream(filePath);
+        //System.out.println("loadFrom...file path done");
+      }
+      // 从类路径加载
+      else {
+        in = classLoader().getResourceAsStream(filePath);
+        //System.out.println("loadFrom...class path done");
+      }
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return in;
+  }
+
+  /**
+   * get the config filename
+   *
+   * @return config filename
+   */
+  public String getConfFilename() {
+    return this.conf_filename;
+  }
+
+  /**
+   * get string value from config file
+   *
+   * @param name item name in config file
+   * @return string value
+   */
+  public String getStrValue(String name) {
+    Object obj;
+    obj = this.paramTable.get(name);
+    if (obj == null) {
+      return null;
+    }
+
+    if (obj instanceof String) {
+      return (String) obj;
+    }
+
+    return (String) ((ArrayList) obj).get(0);
+  }
+
+  /**
+   * get int value from config file
+   *
+   * @param name          item name in config file
+   * @param default_value the default value
+   * @return int value
+   */
+  public int getIntValue(String name, int default_value) {
+    String szValue = this.getStrValue(name);
+    if (szValue == null) {
+      return default_value;
+    }
+
+    return Integer.parseInt(szValue);
+  }
+
+  /**
+   * get boolean value from config file
+   *
+   * @param name          item name in config file
+   * @param default_value the default value
+   * @return boolean value
+   */
+  public boolean getBoolValue(String name, boolean default_value) {
+    String szValue = this.getStrValue(name);
+    if (szValue == null) {
+      return default_value;
+    }
+
+    return szValue.equalsIgnoreCase("yes") || szValue.equalsIgnoreCase("on") ||
+      szValue.equalsIgnoreCase("true") || szValue.equals("1");
+  }
+
+  /**
+   * get all values from config file
+   *
+   * @param name item name in config file
+   * @return string values (array)
+   */
+  public String[] getValues(String name) {
+    Object obj;
+    String[] values;
+
+    obj = this.paramTable.get(name);
+    if (obj == null) {
+      return null;
+    }
+
+    if (obj instanceof String) {
+      values = new String[1];
+      values[0] = (String) obj;
+      return values;
+    }
+
+    Object[] objs = ((ArrayList) obj).toArray();
+    values = new String[objs.length];
+    System.arraycopy(objs, 0, values, 0, objs.length);
+    return values;
+  }
+
+  private void loadFromFile(String confFilePath) throws IOException {
+    InputStream in = loadFromOsFileSystemOrClasspathAsStream(confFilePath);
+    try {
+      readToParamTable(in);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    } finally {
+      try {
+        if (in != null) in.close();
+        //System.out.println("loadFrom...finally...in.close(); done");
+      } catch (Exception ex) {
+        ex.printStackTrace();
+      }
+    }
+  }
+
+  private void readToParamTable(InputStream in) throws IOException {
+    this.paramTable = new Hashtable();
+    if (in == null) return;
+    String line;
+    String[] parts;
+    String name;
+    String value;
+    Object obj;
+    ArrayList valueList;
+    InputStreamReader inReader = null;
+    BufferedReader bufferedReader = null;
+    try {
+      inReader = new InputStreamReader(in);
+      bufferedReader = new BufferedReader(inReader);
+      while ((line = bufferedReader.readLine()) != null) {
+        line = line.trim();
+        if (line.length() == 0 || line.charAt(0) == '#') {
+          continue;
+        }
+        parts = line.split("=", 2);
+        if (parts.length != 2) {
+          continue;
+        }
+        name = parts[0].trim();
+        value = parts[1].trim();
+        obj = this.paramTable.get(name);
+        if (obj == null) {
+          this.paramTable.put(name, value);
+        } else if (obj instanceof String) {
+          valueList = new ArrayList();
+          valueList.add(obj);
+          valueList.add(value);
+          this.paramTable.put(name, valueList);
+        } else {
+          valueList = (ArrayList) obj;
+          valueList.add(value);
+        }
+      }
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    } finally {
+      try {
+        if (bufferedReader != null) bufferedReader.close();
+        if (inReader != null) inReader.close();
+        //System.out.println("readToParamTable...finally...bufferedReader.close();inReader.close(); done");
+      } catch (Exception ex) {
+        ex.printStackTrace();
+      }
+    }
+  }
+
+}

+ 24 - 0
kmall-common/src/main/java/com/kmall/common/fileserver/common/MyException.java

@@ -0,0 +1,24 @@
+/*
+* Copyright (C) 2008 Happy Fish / YuQing
+*
+* FastDFS Java Client may be copied only under the terms of the GNU Lesser
+* General Public License (LGPL).
+* Please visit the FastDFS Home Page http://www.csource.org/ for more detail.
+*/
+
+package com.kmall.common.fileserver.common;
+
+/**
+ * My Exception
+ *
+ * @author Happy Fish / YuQing
+ * @version Version 1.0
+ */
+public class MyException extends Exception {
+  public MyException() {
+  }
+
+  public MyException(String message) {
+    super(message);
+  }
+}

+ 48 - 0
kmall-common/src/main/java/com/kmall/common/fileserver/common/NameValuePair.java

@@ -0,0 +1,48 @@
+/*
+* Copyright (C) 2008 Happy Fish / YuQing
+*
+* FastDFS Java Client may be copied only under the terms of the GNU Lesser
+* General Public License (LGPL).
+* Please visit the FastDFS Home Page http://www.csource.org/ for more detail.
+*/
+
+package com.kmall.common.fileserver.common;
+
+/**
+ * name(key) and value pair model
+ *
+ * @author Happy Fish / YuQing
+ * @version Version 1.0
+ */
+public class NameValuePair {
+  protected String name;
+  protected String value;
+
+  public NameValuePair() {
+  }
+
+  public NameValuePair(String name) {
+    this.name = name;
+  }
+
+  public NameValuePair(String name, String value) {
+    this.name = name;
+    this.value = value;
+  }
+
+  public String getName() {
+    return this.name;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  public String getValue() {
+    return this.value;
+  }
+
+  public void setValue(String value) {
+    this.value = value;
+  }
+}

+ 304 - 0
kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/ClientGlobal.java

@@ -0,0 +1,304 @@
+/**
+ * Copyright (C) 2008 Happy Fish / YuQing
+ * <p>
+ * FastDFS Java Client may be copied only under the terms of the GNU Lesser
+ * General Public License (LGPL).
+ * Please visit the FastDFS Home Page http://www.csource.org/ for more detail.
+ **/
+
+package com.kmall.common.fileserver.fastdfs;
+
+import com.kmall.common.fileserver.common.IniFileReader;
+import com.kmall.common.fileserver.common.MyException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+/**
+ * Global variables
+ *
+ * @author Happy Fish / YuQing
+ * @version Version 1.11
+ */
+public class ClientGlobal {
+
+	public static final String CONF_KEY_CONNECT_TIMEOUT = "connect_timeout";
+	public static final String CONF_KEY_NETWORK_TIMEOUT = "network_timeout";
+	public static final String CONF_KEY_CHARSET = "charset";
+	public static final String CONF_KEY_HTTP_ANTI_STEAL_TOKEN = "http.anti_steal_token";
+	public static final String CONF_KEY_HTTP_SECRET_KEY = "http.secret_key";
+	public static final String CONF_KEY_HTTP_TRACKER_HTTP_PORT = "http.tracker_http_port";
+	public static final String CONF_KEY_TRACKER_SERVER = "tracker_server";
+
+	public static final String PROP_KEY_CONNECT_TIMEOUT_IN_SECONDS = "fastdfs.connect_timeout_in_seconds";
+	public static final String PROP_KEY_NETWORK_TIMEOUT_IN_SECONDS = "fastdfs.network_timeout_in_seconds";
+	public static final String PROP_KEY_CHARSET = "fastdfs.charset";
+	public static final String PROP_KEY_HTTP_ANTI_STEAL_TOKEN = "fastdfs.http_anti_steal_token";
+	public static final String PROP_KEY_HTTP_SECRET_KEY = "fastdfs.http_secret_key";
+	public static final String PROP_KEY_HTTP_TRACKER_HTTP_PORT = "fastdfs.http_tracker_http_port";
+	public static final String PROP_KEY_TRACKER_SERVERS = "fastdfs.tracker_servers";
+
+	public static final int DEFAULT_CONNECT_TIMEOUT = 5; // second
+	public static final int DEFAULT_NETWORK_TIMEOUT = 30; // second
+	public static final String DEFAULT_CHARSET = "UTF-8";
+	public static final boolean DEFAULT_HTTP_ANTI_STEAL_TOKEN = false;
+	public static final String DEFAULT_HTTP_SECRET_KEY = "FastDFS1234567890";
+	public static final int DEFAULT_HTTP_TRACKER_HTTP_PORT = 80;
+
+	public static int g_connect_timeout = DEFAULT_CONNECT_TIMEOUT * 1000; // millisecond
+	public static int g_network_timeout = DEFAULT_NETWORK_TIMEOUT * 1000; // millisecond
+	public static String g_charset = DEFAULT_CHARSET;
+	public static boolean g_anti_steal_token = DEFAULT_HTTP_ANTI_STEAL_TOKEN; // if anti-steal token
+	public static String g_secret_key = DEFAULT_HTTP_SECRET_KEY; // generage token secret key
+	public static int g_tracker_http_port = DEFAULT_HTTP_TRACKER_HTTP_PORT;
+
+	public static String http_tracket_nginx_addr = null;
+	public static String http_tracket_server_port = null;
+	public static String file_author = null;
+
+	public static TrackerGroup g_tracker_group;
+
+	private ClientGlobal() {
+	}
+
+	/**
+	 * load global variables
+	 *
+	 * @param conf_filename
+	 *            config filename
+	 */
+	public static void init(String conf_filename) throws IOException, MyException {
+		IniFileReader iniReader;
+		String[] szTrackerServers;
+		String[] parts;
+
+		iniReader = new IniFileReader(conf_filename);
+
+		g_connect_timeout = iniReader.getIntValue("connect_timeout", DEFAULT_CONNECT_TIMEOUT);
+		if (g_connect_timeout < 0) {
+			g_connect_timeout = DEFAULT_CONNECT_TIMEOUT;
+		}
+		g_connect_timeout *= 1000; // millisecond
+
+		g_network_timeout = iniReader.getIntValue("network_timeout", DEFAULT_NETWORK_TIMEOUT);
+		if (g_network_timeout < 0) {
+			g_network_timeout = DEFAULT_NETWORK_TIMEOUT;
+		}
+		g_network_timeout *= 1000; // millisecond
+
+		g_charset = iniReader.getStrValue("charset");
+		if (g_charset == null || g_charset.length() == 0) {
+			g_charset = "ISO8859-1";
+		}
+
+		szTrackerServers = iniReader.getValues("tracker_server");
+		if (szTrackerServers == null) {
+			throw new MyException("item \"tracker_server\" in " + conf_filename + " not found");
+		}
+
+		InetSocketAddress[] tracker_servers = new InetSocketAddress[szTrackerServers.length];
+		for (int i = 0; i < szTrackerServers.length; i++) {
+			parts = szTrackerServers[i].split("\\:", 2);
+			if (parts.length != 2) {
+				throw new MyException("the value of item \"tracker_server\" is invalid, the correct format is host:port");
+			}
+
+			tracker_servers[i] = new InetSocketAddress(parts[0].trim(), Integer.parseInt(parts[1].trim()));
+		}
+		g_tracker_group = new TrackerGroup(tracker_servers);
+
+		g_tracker_http_port = iniReader.getIntValue("http.tracker_http_port", 80);
+		g_anti_steal_token = iniReader.getBoolValue("http.anti_steal_token", false);
+		if (g_anti_steal_token) {
+			g_secret_key = iniReader.getStrValue("http.secret_key");
+		}
+
+		http_tracket_nginx_addr = iniReader.getStrValue("http.tracket_nginx_addr");
+		http_tracket_server_port = iniReader.getStrValue("http.tracker_server_port");
+		file_author = iniReader.getStrValue("file.author");
+
+	}
+
+	/**
+	 * load from properties file
+	 *
+	 * @param propsFilePath
+	 */
+	public static void initByProperties(String propsFilePath) throws IOException, MyException {
+		Properties props = new Properties();
+		InputStream in = IniFileReader.loadFromOsFileSystemOrClasspathAsStream(propsFilePath);
+		if (in != null) {
+			props.load(in);
+		}
+		initByProperties(props);
+	}
+
+	public static void initByProperties(Properties props) throws IOException, MyException {
+		String trackerServersConf = props.getProperty(PROP_KEY_TRACKER_SERVERS);
+		if (trackerServersConf == null || trackerServersConf.trim().length() == 0) {
+			throw new MyException(String.format("configure item %s is required", PROP_KEY_TRACKER_SERVERS));
+		}
+		initByTrackers(trackerServersConf.trim());
+
+		String connectTimeoutInSecondsConf = props.getProperty(PROP_KEY_CONNECT_TIMEOUT_IN_SECONDS);
+		String networkTimeoutInSecondsConf = props.getProperty(PROP_KEY_NETWORK_TIMEOUT_IN_SECONDS);
+		String charsetConf = props.getProperty(PROP_KEY_CHARSET);
+		String httpAntiStealTokenConf = props.getProperty(PROP_KEY_HTTP_ANTI_STEAL_TOKEN);
+		String httpSecretKeyConf = props.getProperty(PROP_KEY_HTTP_SECRET_KEY);
+		String httpTrackerHttpPortConf = props.getProperty(PROP_KEY_HTTP_TRACKER_HTTP_PORT);
+		if (connectTimeoutInSecondsConf != null && connectTimeoutInSecondsConf.trim().length() != 0) {
+			g_connect_timeout = Integer.parseInt(connectTimeoutInSecondsConf.trim()) * 1000;
+		}
+		if (networkTimeoutInSecondsConf != null && networkTimeoutInSecondsConf.trim().length() != 0) {
+			g_network_timeout = Integer.parseInt(networkTimeoutInSecondsConf.trim()) * 1000;
+		}
+		if (charsetConf != null && charsetConf.trim().length() != 0) {
+			g_charset = charsetConf.trim();
+		}
+		if (httpAntiStealTokenConf != null && httpAntiStealTokenConf.trim().length() != 0) {
+			g_anti_steal_token = Boolean.parseBoolean(httpAntiStealTokenConf);
+		}
+		if (httpSecretKeyConf != null && httpSecretKeyConf.trim().length() != 0) {
+			g_secret_key = httpSecretKeyConf.trim();
+		}
+		if (httpTrackerHttpPortConf != null && httpTrackerHttpPortConf.trim().length() != 0) {
+			g_tracker_http_port = Integer.parseInt(httpTrackerHttpPortConf);
+		}
+	}
+
+	/**
+	 * load from properties file
+	 *
+	 * @param trackerServers
+	 *            例如:"10.0.11.245:22122,10.0.11.246:22122" server的IP和端口用冒号':'分隔 server之间用逗号','分隔
+	 */
+	public static void initByTrackers(String trackerServers) throws IOException, MyException {
+		List<InetSocketAddress> list = new ArrayList();
+		String spr1 = ",";
+		String spr2 = ":";
+		String[] arr1 = trackerServers.trim().split(spr1);
+		for (String addrStr : arr1) {
+			String[] arr2 = addrStr.trim().split(spr2);
+			String host = arr2[0].trim();
+			int port = Integer.parseInt(arr2[1].trim());
+			list.add(new InetSocketAddress(host, port));
+		}
+		InetSocketAddress[] trackerAddresses = list.toArray(new InetSocketAddress[list.size()]);
+		initByTrackers(trackerAddresses);
+	}
+
+	public static void initByTrackers(InetSocketAddress[] trackerAddresses) throws IOException, MyException {
+		g_tracker_group = new TrackerGroup(trackerAddresses);
+	}
+
+	/**
+	 * construct Socket object
+	 *
+	 * @param ip_addr
+	 *            ip address or hostname
+	 * @param port
+	 *            port number
+	 * @return connected Socket object
+	 */
+	public static Socket getSocket(String ip_addr, int port) throws IOException {
+		Socket sock = new Socket();
+		sock.setSoTimeout(ClientGlobal.g_network_timeout);
+		sock.connect(new InetSocketAddress(ip_addr, port), ClientGlobal.g_connect_timeout);
+		return sock;
+	}
+
+	/**
+	 * construct Socket object
+	 *
+	 * @param addr
+	 *            InetSocketAddress object, including ip address and port
+	 * @return connected Socket object
+	 */
+	public static Socket getSocket(InetSocketAddress addr) throws IOException {
+		Socket sock = new Socket();
+		sock.setSoTimeout(ClientGlobal.g_network_timeout);
+		sock.connect(addr, ClientGlobal.g_connect_timeout);
+		return sock;
+	}
+
+	public static int getG_connect_timeout() {
+		return g_connect_timeout;
+	}
+
+	public static void setG_connect_timeout(int connect_timeout) {
+		ClientGlobal.g_connect_timeout = connect_timeout;
+	}
+
+	public static int getG_network_timeout() {
+		return g_network_timeout;
+	}
+
+	public static void setG_network_timeout(int network_timeout) {
+		ClientGlobal.g_network_timeout = network_timeout;
+	}
+
+	public static String getG_charset() {
+		return g_charset;
+	}
+
+	public static void setG_charset(String charset) {
+		ClientGlobal.g_charset = charset;
+	}
+
+	public static int getG_tracker_http_port() {
+		return g_tracker_http_port;
+	}
+
+	public static void setG_tracker_http_port(int tracker_http_port) {
+		ClientGlobal.g_tracker_http_port = tracker_http_port;
+	}
+
+	public static boolean getG_anti_steal_token() {
+		return g_anti_steal_token;
+	}
+
+	public static boolean isG_anti_steal_token() {
+		return g_anti_steal_token;
+	}
+
+	public static void setG_anti_steal_token(boolean anti_steal_token) {
+		ClientGlobal.g_anti_steal_token = anti_steal_token;
+	}
+
+	public static String getG_secret_key() {
+		return g_secret_key;
+	}
+
+	public static void setG_secret_key(String secret_key) {
+		ClientGlobal.g_secret_key = secret_key;
+	}
+
+	public static TrackerGroup getG_tracker_group() {
+		return g_tracker_group;
+	}
+
+	public static void setG_tracker_group(TrackerGroup tracker_group) {
+		ClientGlobal.g_tracker_group = tracker_group;
+	}
+
+	public static String configInfo() {
+		String trackerServers = "";
+		if (g_tracker_group != null) {
+			InetSocketAddress[] trackerAddresses = g_tracker_group.tracker_servers;
+			for (InetSocketAddress inetSocketAddress : trackerAddresses) {
+				if (trackerServers.length() > 0)
+					trackerServers += ",";
+				trackerServers += inetSocketAddress.toString().substring(1);
+			}
+		}
+		return "{" + "\n  g_connect_timeout(ms) = " + g_connect_timeout + "\n  g_network_timeout(ms) = " + g_network_timeout + "\n  g_charset = "
+				+ g_charset + "\n  g_anti_steal_token = " + g_anti_steal_token + "\n  g_secret_key = " + g_secret_key + "\n  g_tracker_http_port = "
+				+ g_tracker_http_port + "\n  trackerServers = " + trackerServers + "\n}";
+	}
+
+}

+ 27 - 0
kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/DownloadCallback.java

@@ -0,0 +1,27 @@
+/**
+ * Copyright (C) 2008 Happy Fish / YuQing
+ * <p>
+ * FastDFS Java Client may be copied only under the terms of the GNU Lesser
+ * General Public License (LGPL).
+ * Please visit the FastDFS Home Page http://www.csource.org/ for more detail.
+ */
+
+package com.kmall.common.fileserver.fastdfs;
+
+/**
+ * Download file callback interface
+ *
+ * @author Happy Fish / YuQing
+ * @version Version 1.4
+ */
+public interface DownloadCallback {
+  /**
+   * recv file content callback function, may be called more than once when the file downloaded
+   *
+   * @param file_size file size
+   * @param data      data buff
+   * @param bytes     data bytes
+   * @return 0 success, return none zero(errno) if fail
+   */
+  public int recv(long file_size, byte[] data, int bytes);
+}

+ 44 - 0
kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/DownloadStream.java

@@ -0,0 +1,44 @@
+package com.kmall.common.fileserver.fastdfs;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Download file by stream (download callback class)
+ *
+ * @author zhouzezhong & Happy Fish / YuQing
+ * @version Version 1.11
+ */
+public class DownloadStream implements DownloadCallback {
+  private OutputStream out;
+  private long currentBytes = 0;
+
+  public DownloadStream(OutputStream out) {
+    super();
+    this.out = out;
+  }
+
+  /**
+   * recv file content callback function, may be called more than once when the file downloaded
+   *
+   * @param fileSize file size
+   * @param data     data buff
+   * @param bytes    data bytes
+   * @return 0 success, return none zero(errno) if fail
+   */
+  public int recv(long fileSize, byte[] data, int bytes) {
+    try {
+      out.write(data, 0, bytes);
+    } catch (IOException ex) {
+      ex.printStackTrace();
+      return -1;
+    }
+
+    currentBytes += bytes;
+    if (this.currentBytes == fileSize) {
+      this.currentBytes = 0;
+    }
+
+    return 0;
+  }
+}

+ 125 - 0
kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/FileInfo.java

@@ -0,0 +1,125 @@
+/**
+ * Copyright (C) 2008 Happy Fish / YuQing
+ * <p>
+ * FastDFS Java Client may be copied only under the terms of the GNU Lesser
+ * General Public License (LGPL).
+ * Please visit the FastDFS Home Page http://www.csource.org/ for more detail.
+ */
+
+package com.kmall.common.fileserver.fastdfs;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * Server Info
+ *
+ * @author Happy Fish / YuQing
+ * @version Version 1.23
+ */
+public class FileInfo {
+  protected String source_ip_addr;
+  protected long file_size;
+  protected Date create_timestamp;
+  protected int crc32;
+
+  /**
+   * Constructor
+   *
+   * @param file_size        the file size
+   * @param create_timestamp create timestamp in seconds
+   * @param crc32            the crc32 signature
+   * @param source_ip_addr   the source storage ip address
+   */
+  public FileInfo(long file_size, int create_timestamp, int crc32, String source_ip_addr) {
+    this.file_size = file_size;
+    this.create_timestamp = new Date(create_timestamp * 1000L);
+    this.crc32 = crc32;
+    this.source_ip_addr = source_ip_addr;
+  }
+
+  /**
+   * get the source ip address of the file uploaded to
+   *
+   * @return the source ip address of the file uploaded to
+   */
+  public String getSourceIpAddr() {
+    return this.source_ip_addr;
+  }
+
+  /**
+   * set the source ip address of the file uploaded to
+   *
+   * @param source_ip_addr the source ip address
+   */
+  public void setSourceIpAddr(String source_ip_addr) {
+    this.source_ip_addr = source_ip_addr;
+  }
+
+  /**
+   * get the file size
+   *
+   * @return the file size
+   */
+  public long getFileSize() {
+    return this.file_size;
+  }
+
+  /**
+   * set the file size
+   *
+   * @param file_size the file size
+   */
+  public void setFileSize(long file_size) {
+    this.file_size = file_size;
+  }
+
+  /**
+   * get the create timestamp of the file
+   *
+   * @return the create timestamp of the file
+   */
+  public Date getCreateTimestamp() {
+    return this.create_timestamp;
+  }
+
+  /**
+   * set the create timestamp of the file
+   *
+   * @param create_timestamp create timestamp in seconds
+   */
+  public void setCreateTimestamp(int create_timestamp) {
+    this.create_timestamp = new Date(create_timestamp * 1000L);
+  }
+
+  /**
+   * get the file CRC32 signature
+   *
+   * @return the file CRC32 signature
+   */
+  public long getCrc32() {
+    return this.crc32;
+  }
+
+  /**
+   * set the create timestamp of the file
+   *
+   * @param crc32 the crc32 signature
+   */
+  public void setCrc32(int crc32) {
+    this.crc32 = crc32;
+  }
+
+  /**
+   * to string
+   *
+   * @return string
+   */
+  public String toString() {
+    SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+    return "source_ip_addr = " + this.source_ip_addr + ", " +
+      "file_size = " + this.file_size + ", " +
+      "create_timestamp = " + df.format(this.create_timestamp) + ", " +
+      "crc32 = " + this.crc32;
+  }
+}

+ 503 - 0
kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/ProtoCommon.java

@@ -0,0 +1,503 @@
+/**
+ * Copyright (C) 2008 Happy Fish / YuQing
+ * <p>
+ * FastDFS Java Client may be copied only under the terms of the GNU Lesser
+ * General Public License (LGPL).
+ * Please visit the FastDFS Home Page http://www.csource.org/ for more detail.
+ **/
+
+package com.kmall.common.fileserver.fastdfs;
+
+import com.kmall.common.fileserver.common.MyException;
+import com.kmall.common.fileserver.common.NameValuePair;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.Socket;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+
+/**
+ * protocol common functions
+ *
+ * @author Happy Fish / YuQing
+ * @version Version 1.18
+ */
+public class ProtoCommon {
+  public static final byte FDFS_PROTO_CMD_QUIT = 82;
+  public static final byte TRACKER_PROTO_CMD_SERVER_LIST_GROUP = 91;
+  public static final byte TRACKER_PROTO_CMD_SERVER_LIST_STORAGE = 92;
+  public static final byte TRACKER_PROTO_CMD_SERVER_DELETE_STORAGE = 93;
+  public static final byte TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITHOUT_GROUP_ONE = 101;
+  public static final byte TRACKER_PROTO_CMD_SERVICE_QUERY_FETCH_ONE = 102;
+  public static final byte TRACKER_PROTO_CMD_SERVICE_QUERY_UPDATE = 103;
+  public static final byte TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITH_GROUP_ONE = 104;
+  public static final byte TRACKER_PROTO_CMD_SERVICE_QUERY_FETCH_ALL = 105;
+  public static final byte TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITHOUT_GROUP_ALL = 106;
+  public static final byte TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITH_GROUP_ALL = 107;
+  public static final byte TRACKER_PROTO_CMD_RESP = 100;
+  public static final byte FDFS_PROTO_CMD_ACTIVE_TEST = 111;
+  public static final byte STORAGE_PROTO_CMD_UPLOAD_FILE = 11;
+  public static final byte STORAGE_PROTO_CMD_DELETE_FILE = 12;
+  public static final byte STORAGE_PROTO_CMD_SET_METADATA = 13;
+  public static final byte STORAGE_PROTO_CMD_DOWNLOAD_FILE = 14;
+  public static final byte STORAGE_PROTO_CMD_GET_METADATA = 15;
+  public static final byte STORAGE_PROTO_CMD_UPLOAD_SLAVE_FILE = 21;
+  public static final byte STORAGE_PROTO_CMD_QUERY_FILE_INFO = 22;
+  public static final byte STORAGE_PROTO_CMD_UPLOAD_APPENDER_FILE = 23;  //create appender file
+  public static final byte STORAGE_PROTO_CMD_APPEND_FILE = 24;  //append file
+  public static final byte STORAGE_PROTO_CMD_MODIFY_FILE = 34;  //modify appender file
+  public static final byte STORAGE_PROTO_CMD_TRUNCATE_FILE = 36;  //truncate appender file
+  public static final byte STORAGE_PROTO_CMD_RESP = TRACKER_PROTO_CMD_RESP;
+  public static final byte FDFS_STORAGE_STATUS_INIT = 0;
+  public static final byte FDFS_STORAGE_STATUS_WAIT_SYNC = 1;
+  public static final byte FDFS_STORAGE_STATUS_SYNCING = 2;
+  public static final byte FDFS_STORAGE_STATUS_IP_CHANGED = 3;
+  public static final byte FDFS_STORAGE_STATUS_DELETED = 4;
+  public static final byte FDFS_STORAGE_STATUS_OFFLINE = 5;
+  public static final byte FDFS_STORAGE_STATUS_ONLINE = 6;
+  public static final byte FDFS_STORAGE_STATUS_ACTIVE = 7;
+  public static final byte FDFS_STORAGE_STATUS_NONE = 99;
+  /**
+   * for overwrite all old metadata
+   */
+  public static final byte STORAGE_SET_METADATA_FLAG_OVERWRITE = 'O';
+  /**
+   * for replace, insert when the meta item not exist, otherwise update it
+   */
+  public static final byte STORAGE_SET_METADATA_FLAG_MERGE = 'M';
+  public static final int FDFS_PROTO_PKG_LEN_SIZE = 8;
+  public static final int FDFS_PROTO_CMD_SIZE = 1;
+  public static final int FDFS_GROUP_NAME_MAX_LEN = 16;
+  public static final int FDFS_IPADDR_SIZE = 16;
+  public static final int FDFS_DOMAIN_NAME_MAX_SIZE = 128;
+  public static final int FDFS_VERSION_SIZE = 6;
+  public static final int FDFS_STORAGE_ID_MAX_SIZE = 16;
+  public static final String FDFS_RECORD_SEPERATOR = "\u0001";
+  public static final String FDFS_FIELD_SEPERATOR = "\u0002";
+  public static final int TRACKER_QUERY_STORAGE_FETCH_BODY_LEN = FDFS_GROUP_NAME_MAX_LEN
+    + FDFS_IPADDR_SIZE - 1 + FDFS_PROTO_PKG_LEN_SIZE;
+  public static final int TRACKER_QUERY_STORAGE_STORE_BODY_LEN = FDFS_GROUP_NAME_MAX_LEN
+    + FDFS_IPADDR_SIZE + FDFS_PROTO_PKG_LEN_SIZE;
+  public static final byte FDFS_FILE_EXT_NAME_MAX_LEN = 6;
+  public static final byte FDFS_FILE_PREFIX_MAX_LEN = 16;
+  public static final byte FDFS_FILE_PATH_LEN = 10;
+  public static final byte FDFS_FILENAME_BASE64_LENGTH = 27;
+  public static final byte FDFS_TRUNK_FILE_INFO_LEN = 16;
+  public static final byte ERR_NO_ENOENT = 2;
+  public static final byte ERR_NO_EIO = 5;
+  public static final byte ERR_NO_EBUSY = 16;
+  public static final byte ERR_NO_EINVAL = 22;
+  public static final byte ERR_NO_ENOSPC = 28;
+  public static final byte ECONNREFUSED = 61;
+  public static final byte ERR_NO_EALREADY = 114;
+  public static final long INFINITE_FILE_SIZE = 256 * 1024L * 1024 * 1024 * 1024 * 1024L;
+  public static final long APPENDER_FILE_SIZE = INFINITE_FILE_SIZE;
+  public static final long TRUNK_FILE_MARK_SIZE = 512 * 1024L * 1024 * 1024 * 1024 * 1024L;
+  public static final long NORMAL_LOGIC_FILENAME_LENGTH = FDFS_FILE_PATH_LEN + FDFS_FILENAME_BASE64_LENGTH + FDFS_FILE_EXT_NAME_MAX_LEN + 1;
+  public static final long TRUNK_LOGIC_FILENAME_LENGTH = NORMAL_LOGIC_FILENAME_LENGTH + FDFS_TRUNK_FILE_INFO_LEN;
+  protected static final int PROTO_HEADER_CMD_INDEX = FDFS_PROTO_PKG_LEN_SIZE;
+  protected static final int PROTO_HEADER_STATUS_INDEX = FDFS_PROTO_PKG_LEN_SIZE + 1;
+
+  private ProtoCommon() {
+  }
+
+  public static String getStorageStatusCaption(byte status) {
+    switch (status) {
+      case FDFS_STORAGE_STATUS_INIT:
+        return "INIT";
+      case FDFS_STORAGE_STATUS_WAIT_SYNC:
+        return "WAIT_SYNC";
+      case FDFS_STORAGE_STATUS_SYNCING:
+        return "SYNCING";
+      case FDFS_STORAGE_STATUS_IP_CHANGED:
+        return "IP_CHANGED";
+      case FDFS_STORAGE_STATUS_DELETED:
+        return "DELETED";
+      case FDFS_STORAGE_STATUS_OFFLINE:
+        return "OFFLINE";
+      case FDFS_STORAGE_STATUS_ONLINE:
+        return "ONLINE";
+      case FDFS_STORAGE_STATUS_ACTIVE:
+        return "ACTIVE";
+      case FDFS_STORAGE_STATUS_NONE:
+        return "NONE";
+      default:
+        return "UNKOWN";
+    }
+  }
+
+  /**
+   * pack header by FastDFS transfer protocol
+   *
+   * @param cmd     which command to send
+   * @param pkg_len package body length
+   * @param errno   status code, should be (byte)0
+   * @return packed byte buffer
+   */
+  public static byte[] packHeader(byte cmd, long pkg_len, byte errno) throws UnsupportedEncodingException {
+    byte[] header;
+    byte[] hex_len;
+
+    header = new byte[FDFS_PROTO_PKG_LEN_SIZE + 2];
+    Arrays.fill(header, (byte) 0);
+
+    hex_len = ProtoCommon.long2buff(pkg_len);
+    System.arraycopy(hex_len, 0, header, 0, hex_len.length);
+    header[PROTO_HEADER_CMD_INDEX] = cmd;
+    header[PROTO_HEADER_STATUS_INDEX] = errno;
+    return header;
+  }
+
+  /**
+   * receive pack header
+   *
+   * @param in              input stream
+   * @param expect_cmd      expect response command
+   * @param expect_body_len expect response package body length
+   * @return RecvHeaderInfo: errno and pkg body length
+   */
+  public static RecvHeaderInfo recvHeader(InputStream in, byte expect_cmd, long expect_body_len) throws IOException {
+    byte[] header;
+    int bytes;
+    long pkg_len;
+
+    header = new byte[FDFS_PROTO_PKG_LEN_SIZE + 2];
+
+    if ((bytes = in.read(header)) != header.length) {
+      throw new IOException("recv package size " + bytes + " != " + header.length);
+    }
+
+    if (header[PROTO_HEADER_CMD_INDEX] != expect_cmd) {
+      throw new IOException("recv cmd: " + header[PROTO_HEADER_CMD_INDEX] + " is not correct, expect cmd: " + expect_cmd);
+    }
+
+    if (header[PROTO_HEADER_STATUS_INDEX] != 0) {
+      return new RecvHeaderInfo(header[PROTO_HEADER_STATUS_INDEX], 0);
+    }
+
+    pkg_len = ProtoCommon.buff2long(header, 0);
+    if (pkg_len < 0) {
+      throw new IOException("recv body length: " + pkg_len + " < 0!");
+    }
+
+    if (expect_body_len >= 0 && pkg_len != expect_body_len) {
+      throw new IOException("recv body length: " + pkg_len + " is not correct, expect length: " + expect_body_len);
+    }
+
+    return new RecvHeaderInfo((byte) 0, pkg_len);
+  }
+
+  /**
+   * receive whole pack
+   *
+   * @param in              input stream
+   * @param expect_cmd      expect response command
+   * @param expect_body_len expect response package body length
+   * @return RecvPackageInfo: errno and reponse body(byte buff)
+   */
+  public static RecvPackageInfo recvPackage(InputStream in, byte expect_cmd, long expect_body_len) throws IOException {
+    RecvHeaderInfo header = recvHeader(in, expect_cmd, expect_body_len);
+    if (header.errno != 0) {
+      return new RecvPackageInfo(header.errno, null);
+    }
+
+    byte[] body = new byte[(int) header.body_len];
+    int totalBytes = 0;
+    int remainBytes = (int) header.body_len;
+    int bytes;
+
+    while (totalBytes < header.body_len) {
+      if ((bytes = in.read(body, totalBytes, remainBytes)) < 0) {
+        break;
+      }
+
+      totalBytes += bytes;
+      remainBytes -= bytes;
+    }
+
+    if (totalBytes != header.body_len) {
+      throw new IOException("recv package size " + totalBytes + " != " + header.body_len);
+    }
+
+    return new RecvPackageInfo((byte) 0, body);
+  }
+
+  /**
+   * split metadata to name value pair array
+   *
+   * @param meta_buff metadata
+   * @return name value pair array
+   */
+  public static NameValuePair[] split_metadata(String meta_buff) {
+    return split_metadata(meta_buff, FDFS_RECORD_SEPERATOR, FDFS_FIELD_SEPERATOR);
+  }
+
+  /**
+   * split metadata to name value pair array
+   *
+   * @param meta_buff       metadata
+   * @param recordSeperator record/row seperator
+   * @param filedSeperator  field/column seperator
+   * @return name value pair array
+   */
+  public static NameValuePair[] split_metadata(String meta_buff,
+                                               String recordSeperator, String filedSeperator) {
+    String[] rows;
+    String[] cols;
+    NameValuePair[] meta_list;
+
+    rows = meta_buff.split(recordSeperator);
+    meta_list = new NameValuePair[rows.length];
+    for (int i = 0; i < rows.length; i++) {
+      cols = rows[i].split(filedSeperator, 2);
+      meta_list[i] = new NameValuePair(cols[0]);
+      if (cols.length == 2) {
+        meta_list[i].setValue(cols[1]);
+      }
+    }
+
+    return meta_list;
+  }
+
+  /**
+   * pack metadata array to string
+   *
+   * @param meta_list metadata array
+   * @return packed metadata
+   */
+  public static String pack_metadata(NameValuePair[] meta_list) {
+    if (meta_list.length == 0) {
+      return "";
+    }
+
+    StringBuffer sb = new StringBuffer(32 * meta_list.length);
+    sb.append(meta_list[0].getName()).append(FDFS_FIELD_SEPERATOR).append(meta_list[0].getValue());
+    for (int i = 1; i < meta_list.length; i++) {
+      sb.append(FDFS_RECORD_SEPERATOR);
+      sb.append(meta_list[i].getName()).append(FDFS_FIELD_SEPERATOR).append(meta_list[i].getValue());
+    }
+
+    return sb.toString();
+  }
+
+  /**
+   * send quit command to server and close socket
+   *
+   * @param sock the Socket object
+   */
+  public static void closeSocket(Socket sock) throws IOException {
+    byte[] header;
+    header = packHeader(FDFS_PROTO_CMD_QUIT, 0, (byte) 0);
+    sock.getOutputStream().write(header);
+    sock.close();
+  }
+
+  /**
+   * send ACTIVE_TEST command to server, test if network is ok and the server is alive
+   *
+   * @param sock the Socket object
+   */
+  public static boolean activeTest(Socket sock) throws IOException {
+    byte[] header;
+    header = packHeader(FDFS_PROTO_CMD_ACTIVE_TEST, 0, (byte) 0);
+    sock.getOutputStream().write(header);
+
+    RecvHeaderInfo headerInfo = recvHeader(sock.getInputStream(), TRACKER_PROTO_CMD_RESP, 0);
+    return headerInfo.errno == 0 ? true : false;
+  }
+
+  /**
+   * long convert to buff (big-endian)
+   *
+   * @param n long number
+   * @return 8 bytes buff
+   */
+  public static byte[] long2buff(long n) {
+    byte[] bs;
+
+    bs = new byte[8];
+    bs[0] = (byte) ((n >> 56) & 0xFF);
+    bs[1] = (byte) ((n >> 48) & 0xFF);
+    bs[2] = (byte) ((n >> 40) & 0xFF);
+    bs[3] = (byte) ((n >> 32) & 0xFF);
+    bs[4] = (byte) ((n >> 24) & 0xFF);
+    bs[5] = (byte) ((n >> 16) & 0xFF);
+    bs[6] = (byte) ((n >> 8) & 0xFF);
+    bs[7] = (byte) (n & 0xFF);
+
+    return bs;
+  }
+
+  /**
+   * buff convert to long
+   *
+   * @param bs     the buffer (big-endian)
+   * @param offset the start position based 0
+   * @return long number
+   */
+  public static long buff2long(byte[] bs, int offset) {
+    return (((long) (bs[offset] >= 0 ? bs[offset] : 256 + bs[offset])) << 56) |
+      (((long) (bs[offset + 1] >= 0 ? bs[offset + 1] : 256 + bs[offset + 1])) << 48) |
+      (((long) (bs[offset + 2] >= 0 ? bs[offset + 2] : 256 + bs[offset + 2])) << 40) |
+      (((long) (bs[offset + 3] >= 0 ? bs[offset + 3] : 256 + bs[offset + 3])) << 32) |
+      (((long) (bs[offset + 4] >= 0 ? bs[offset + 4] : 256 + bs[offset + 4])) << 24) |
+      (((long) (bs[offset + 5] >= 0 ? bs[offset + 5] : 256 + bs[offset + 5])) << 16) |
+      (((long) (bs[offset + 6] >= 0 ? bs[offset + 6] : 256 + bs[offset + 6])) << 8) |
+      ((long) (bs[offset + 7] >= 0 ? bs[offset + 7] : 256 + bs[offset + 7]));
+  }
+
+  /**
+   * buff convert to int
+   *
+   * @param bs     the buffer (big-endian)
+   * @param offset the start position based 0
+   * @return int number
+   */
+  public static int buff2int(byte[] bs, int offset) {
+    return (((int) (bs[offset] >= 0 ? bs[offset] : 256 + bs[offset])) << 24) |
+      (((int) (bs[offset + 1] >= 0 ? bs[offset + 1] : 256 + bs[offset + 1])) << 16) |
+      (((int) (bs[offset + 2] >= 0 ? bs[offset + 2] : 256 + bs[offset + 2])) << 8) |
+      ((int) (bs[offset + 3] >= 0 ? bs[offset + 3] : 256 + bs[offset + 3]));
+  }
+
+  /**
+   * buff convert to ip address
+   *
+   * @param bs     the buffer (big-endian)
+   * @param offset the start position based 0
+   * @return ip address
+   */
+  public static String getIpAddress(byte[] bs, int offset) {
+    if (bs[0] == 0 || bs[3] == 0) //storage server ID
+    {
+      return "";
+    }
+
+    int n;
+    StringBuilder sbResult = new StringBuilder(16);
+    for (int i = offset; i < offset + 4; i++) {
+      n = (bs[i] >= 0) ? bs[i] : 256 + bs[i];
+      if (sbResult.length() > 0) {
+        sbResult.append(".");
+      }
+      sbResult.append(String.valueOf(n));
+    }
+
+    return sbResult.toString();
+  }
+
+  /**
+   * md5 function
+   *
+   * @param source the input buffer
+   * @return md5 string
+   */
+  public static String md5(byte[] source) throws NoSuchAlgorithmException {
+    char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+    java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
+    md.update(source);
+    byte tmp[] = md.digest();
+    char str[] = new char[32];
+    int k = 0;
+    for (int i = 0; i < 16; i++) {
+      str[k++] = hexDigits[tmp[i] >>> 4 & 0xf];
+      str[k++] = hexDigits[tmp[i] & 0xf];
+    }
+
+    return new String(str);
+  }
+
+  /**
+   * get token for file URL
+   *
+   * @param remote_filename the filename return by FastDFS server
+   * @param ts              unix timestamp, unit: second
+   * @param secret_key      the secret key
+   * @return token string
+   */
+  public static String getToken(String remote_filename, int ts, String secret_key) throws UnsupportedEncodingException, NoSuchAlgorithmException, MyException {
+    byte[] bsFilename = remote_filename.getBytes(ClientGlobal.g_charset);
+    byte[] bsKey = secret_key.getBytes(ClientGlobal.g_charset);
+    byte[] bsTimestamp = (new Integer(ts)).toString().getBytes(ClientGlobal.g_charset);
+
+    byte[] buff = new byte[bsFilename.length + bsKey.length + bsTimestamp.length];
+    System.arraycopy(bsFilename, 0, buff, 0, bsFilename.length);
+    System.arraycopy(bsKey, 0, buff, bsFilename.length, bsKey.length);
+    System.arraycopy(bsTimestamp, 0, buff, bsFilename.length + bsKey.length, bsTimestamp.length);
+
+    return md5(buff);
+  }
+
+  /**
+   * generate slave filename
+   *
+   * @param master_filename the master filename to generate the slave filename
+   * @param prefix_name     the prefix name to generate the slave filename
+   * @param ext_name        the extension name of slave filename, null for same as the master extension name
+   * @return slave filename string
+   */
+  public static String genSlaveFilename(String master_filename,
+                                        String prefix_name, String ext_name) throws MyException {
+    String true_ext_name;
+    int dotIndex;
+
+    if (master_filename.length() < 28 + FDFS_FILE_EXT_NAME_MAX_LEN) {
+      throw new MyException("master filename \"" + master_filename + "\" is invalid");
+    }
+
+    dotIndex = master_filename.indexOf('.', master_filename.length() - (FDFS_FILE_EXT_NAME_MAX_LEN + 1));
+    if (ext_name != null) {
+      if (ext_name.length() == 0) {
+        true_ext_name = "";
+      } else if (ext_name.charAt(0) == '.') {
+        true_ext_name = ext_name;
+      } else {
+        true_ext_name = "." + ext_name;
+      }
+    } else {
+      if (dotIndex < 0) {
+        true_ext_name = "";
+      } else {
+        true_ext_name = master_filename.substring(dotIndex);
+      }
+    }
+
+    if (true_ext_name.length() == 0 && prefix_name.equals("-m")) {
+      throw new MyException("prefix_name \"" + prefix_name + "\" is invalid");
+    }
+
+    if (dotIndex < 0) {
+      return master_filename + prefix_name + true_ext_name;
+    } else {
+      return master_filename.substring(0, dotIndex) + prefix_name + true_ext_name;
+    }
+  }
+
+  /**
+   * receive package info
+   */
+  public static class RecvPackageInfo {
+    public byte errno;
+    public byte[] body;
+
+    public RecvPackageInfo(byte errno, byte[] body) {
+      this.errno = errno;
+      this.body = body;
+    }
+  }
+
+  /**
+   * receive header info
+   */
+  public static class RecvHeaderInfo {
+    public byte errno;
+    public long body_len;
+
+    public RecvHeaderInfo(byte errno, long body_len) {
+      this.errno = errno;
+      this.body_len = body_len;
+    }
+  }
+}

+ 48 - 0
kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/ProtoStructDecoder.java

@@ -0,0 +1,48 @@
+/**
+ * Copyright (C) 2008 Happy Fish / YuQing
+ * <p>
+ * FastDFS Java Client may be copied only under the terms of the GNU Lesser
+ * General Public License (LGPL).
+ * Please visit the FastDFS Home Page http://www.csource.org/ for more detail.
+ */
+
+package com.kmall.common.fileserver.fastdfs;
+
+import java.io.IOException;
+import java.lang.reflect.Array;
+
+/**
+ * C struct body decoder
+ *
+ * @author Happy Fish / YuQing
+ * @version Version 1.17
+ */
+public class ProtoStructDecoder<T extends StructBase> {
+  /**
+   * Constructor
+   */
+  public ProtoStructDecoder() {
+  }
+
+  /**
+   * decode byte buffer
+   */
+  public T[] decode(byte[] bs, Class<T> clazz, int fieldsTotalSize) throws Exception {
+    if (bs.length % fieldsTotalSize != 0) {
+      throw new IOException("byte array length: " + bs.length + " is invalid!");
+    }
+
+    int count = bs.length / fieldsTotalSize;
+    int offset;
+    T[] results = (T[]) Array.newInstance(clazz, count);
+
+    offset = 0;
+    for (int i = 0; i < results.length; i++) {
+      results[i] = clazz.newInstance();
+      results[i].setFields(bs, offset);
+      offset += fieldsTotalSize;
+    }
+
+    return results;
+  }
+}

+ 66 - 0
kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/ServerInfo.java

@@ -0,0 +1,66 @@
+/**
+ * Copyright (C) 2008 Happy Fish / YuQing
+ * <p>
+ * FastDFS Java Client may be copied only under the terms of the GNU Lesser
+ * General Public License (LGPL).
+ * Please visit the FastDFS Home Page http://www.csource.org/ for more detail.
+ */
+
+package com.kmall.common.fileserver.fastdfs;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+
+/**
+ * Server Info
+ *
+ * @author Happy Fish / YuQing
+ * @version Version 1.7
+ */
+public class ServerInfo {
+  protected String ip_addr;
+  protected int port;
+
+  /**
+   * Constructor
+   *
+   * @param ip_addr address of the server
+   * @param port    the port of the server
+   */
+  public ServerInfo(String ip_addr, int port) {
+    this.ip_addr = ip_addr;
+    this.port = port;
+  }
+
+  /**
+   * return the ip address
+   *
+   * @return the ip address
+   */
+  public String getIpAddr() {
+    return this.ip_addr;
+  }
+
+  /**
+   * return the port of the server
+   *
+   * @return the port of the server
+   */
+  public int getPort() {
+    return this.port;
+  }
+
+  /**
+   * connect to server
+   *
+   * @return connected Socket object
+   */
+  public Socket connect() throws IOException {
+    Socket sock = new Socket();
+    sock.setReuseAddress(true);
+    sock.setSoTimeout(ClientGlobal.g_network_timeout);
+    sock.connect(new InetSocketAddress(this.ip_addr, this.port), ClientGlobal.g_connect_timeout);
+    return sock;
+  }
+}

+ 1786 - 0
kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/StorageClient.java

@@ -0,0 +1,1786 @@
+/**
+ * Copyright (C) 2008 Happy Fish / YuQing
+ * <p>
+ * FastDFS Java Client may be copied only under the terms of the GNU Lesser
+ * General Public License (LGPL).
+ * Please visit the FastDFS Home Page http://www.csource.org/ for more detail.
+ */
+
+package com.kmall.common.fileserver.fastdfs;
+
+import com.kmall.common.fileserver.common.Base64;
+import com.kmall.common.fileserver.common.MyException;
+import com.kmall.common.fileserver.common.NameValuePair;
+
+import java.io.*;
+import java.net.Socket;
+import java.util.Arrays;
+
+/**
+ * Storage client for 2 fields file id: group name and filename
+ *
+ * @author Happy Fish / YuQing
+ * @version Version 1.24
+ */
+public class StorageClient {
+  public final static Base64 base64 = new Base64('-', '_', '.', 0);
+  protected TrackerServer trackerServer;
+  protected StorageServer storageServer;
+  protected byte errno;
+
+  /**
+   * constructor using global settings in class ClientGlobal
+   */
+  public StorageClient() {
+    this.trackerServer = null;
+    this.storageServer = null;
+  }
+
+  /**
+   * constructor with tracker server and storage server
+   *
+   * @param trackerServer the tracker server, can be null
+   * @param storageServer the storage server, can be null
+   */
+  public StorageClient(TrackerServer trackerServer, StorageServer storageServer) {
+    this.trackerServer = trackerServer;
+    this.storageServer = storageServer;
+  }
+
+  /**
+   * get the error code of last call
+   *
+   * @return the error code of last call
+   */
+  public byte getErrorCode() {
+    return this.errno;
+  }
+
+  /**
+   * upload file to storage server (by file name)
+   *
+   * @param local_filename local filename to upload
+   * @param file_ext_name  file ext name, do not include dot(.), null to extract ext name from the local filename
+   * @param meta_list      meta info array
+   * @return 2 elements string array if success:<br>
+   * <ul><li>results[0]: the group name to store the file </li></ul>
+   * <ul><li>results[1]: the new created filename</li></ul>
+   * return null if fail
+   */
+  public String[] upload_file(String local_filename, String file_ext_name,
+                              NameValuePair[] meta_list) throws IOException, MyException {
+    final String group_name = null;
+    return this.upload_file(group_name, local_filename, file_ext_name, meta_list);
+  }
+
+  /**
+   * upload file to storage server (by file name)
+   *
+   * @param group_name     the group name to upload file to, can be empty
+   * @param local_filename local filename to upload
+   * @param file_ext_name  file ext name, do not include dot(.), null to extract ext name from the local filename
+   * @param meta_list      meta info array
+   * @return 2 elements string array if success:<br>
+   * <ul><li>results[0]: the group name to store the file </li></ul>
+   * <ul><li>results[1]: the new created filename</li></ul>
+   * return null if fail
+   */
+  protected String[] upload_file(String group_name, String local_filename, String file_ext_name,
+                                 NameValuePair[] meta_list) throws IOException, MyException {
+    final byte cmd = ProtoCommon.STORAGE_PROTO_CMD_UPLOAD_FILE;
+    return this.upload_file(cmd, group_name, local_filename, file_ext_name, meta_list);
+  }
+
+  /**
+   * upload file to storage server (by file name)
+   *
+   * @param cmd            the command
+   * @param group_name     the group name to upload file to, can be empty
+   * @param local_filename local filename to upload
+   * @param file_ext_name  file ext name, do not include dot(.), null to extract ext name from the local filename
+   * @param meta_list      meta info array
+   * @return 2 elements string array if success:<br>
+   * <ul><li>results[0]: the group name to store the file </li></ul>
+   * <ul><li>results[1]: the new created filename</li></ul>
+   * return null if fail
+   */
+  protected String[] upload_file(byte cmd, String group_name, String local_filename, String file_ext_name,
+                                 NameValuePair[] meta_list) throws IOException, MyException {
+    File f = new File(local_filename);
+    FileInputStream fis = new FileInputStream(f);
+
+    if (file_ext_name == null) {
+      int nPos = local_filename.lastIndexOf('.');
+      if (nPos > 0 && local_filename.length() - nPos <= ProtoCommon.FDFS_FILE_EXT_NAME_MAX_LEN + 1) {
+        file_ext_name = local_filename.substring(nPos + 1);
+      }
+    }
+
+    try {
+      return this.do_upload_file(cmd, group_name, null, null, file_ext_name,
+        f.length(), new UploadStream(fis, f.length()), meta_list);
+    } finally {
+      fis.close();
+    }
+  }
+
+  /**
+   * upload file to storage server (by file buff)
+   *
+   * @param file_buff     file content/buff
+   * @param offset        start offset of the buff
+   * @param length        the length of buff to upload
+   * @param file_ext_name file ext name, do not include dot(.)
+   * @param meta_list     meta info array
+   * @return 2 elements string array if success:<br>
+   * <ul><li>results[0]: the group name to store the file</li></ul>
+   * <ul><li>results[1]: the new created filename</li></ul>
+   * return null if fail
+   */
+  public String[] upload_file(byte[] file_buff, int offset, int length, String file_ext_name,
+                              NameValuePair[] meta_list) throws IOException, MyException {
+    final String group_name = null;
+    return this.upload_file(group_name, file_buff, offset, length, file_ext_name, meta_list);
+  }
+
+  /**
+   * upload file to storage server (by file buff)
+   *
+   * @param group_name    the group name to upload file to, can be empty
+   * @param file_buff     file content/buff
+   * @param offset        start offset of the buff
+   * @param length        the length of buff to upload
+   * @param file_ext_name file ext name, do not include dot(.)
+   * @param meta_list     meta info array
+   * @return 2 elements string array if success:<br>
+   * <ul><li>results[0]: the group name to store the file</li></ul>
+   * <ul><li>results[1]: the new created filename</li></ul>
+   * return null if fail
+   */
+  public String[] upload_file(String group_name, byte[] file_buff, int offset, int length,
+                              String file_ext_name, NameValuePair[] meta_list) throws IOException, MyException {
+    return this.do_upload_file(ProtoCommon.STORAGE_PROTO_CMD_UPLOAD_FILE, group_name, null, null, file_ext_name,
+      length, new UploadBuff(file_buff, offset, length), meta_list);
+  }
+
+  /**
+   * upload file to storage server (by file buff)
+   *
+   * @param file_buff     file content/buff
+   * @param file_ext_name file ext name, do not include dot(.)
+   * @param meta_list     meta info array
+   * @return 2 elements string array if success:<br>
+   * <ul><li>results[0]: the group name to store the file</li></ul>
+   * <ul><li>results[1]: the new created filename</li></ul>
+   * return null if fail
+   */
+  public String[] upload_file(byte[] file_buff, String file_ext_name,
+                              NameValuePair[] meta_list) throws IOException, MyException {
+    final String group_name = null;
+    return this.upload_file(group_name, file_buff, 0, file_buff.length, file_ext_name, meta_list);
+  }
+
+  /**
+   * upload file to storage server (by file buff)
+   *
+   * @param group_name    the group name to upload file to, can be empty
+   * @param file_buff     file content/buff
+   * @param file_ext_name file ext name, do not include dot(.)
+   * @param meta_list     meta info array
+   * @return 2 elements string array if success:<br>
+   * <ul><li>results[0]: the group name to store the file</li></ul>
+   * <ul><li>results[1]: the new created filename</li></ul>
+   * return null if fail
+   */
+  public String[] upload_file(String group_name, byte[] file_buff,
+                              String file_ext_name, NameValuePair[] meta_list) throws IOException, MyException {
+    return this.do_upload_file(ProtoCommon.STORAGE_PROTO_CMD_UPLOAD_FILE, group_name, null, null, file_ext_name,
+      file_buff.length, new UploadBuff(file_buff, 0, file_buff.length), meta_list);
+  }
+
+  /**
+   * upload file to storage server (by callback)
+   *
+   * @param group_name    the group name to upload file to, can be empty
+   * @param file_size     the file size
+   * @param callback      the write data callback object
+   * @param file_ext_name file ext name, do not include dot(.)
+   * @param meta_list     meta info array
+   * @return 2 elements string array if success:<br>
+   * <ul><li>results[0]: the group name to store the file</li></ul>
+   * <ul><li>results[1]: the new created filename</li></ul>
+   * return null if fail
+   */
+  public String[] upload_file(String group_name, long file_size, UploadCallback callback,
+                              String file_ext_name, NameValuePair[] meta_list) throws IOException, MyException {
+    final String master_filename = null;
+    final String prefix_name = null;
+
+    return this.do_upload_file(ProtoCommon.STORAGE_PROTO_CMD_UPLOAD_FILE, group_name, master_filename, prefix_name,
+      file_ext_name, file_size, callback, meta_list);
+  }
+
+  /**
+   * upload file to storage server (by file name, slave file mode)
+   *
+   * @param group_name      the group name of master file
+   * @param master_filename the master file name to generate the slave file
+   * @param prefix_name     the prefix name to generate the slave file
+   * @param local_filename  local filename to upload
+   * @param file_ext_name   file ext name, do not include dot(.), null to extract ext name from the local filename
+   * @param meta_list       meta info array
+   * @return 2 elements string array if success:<br>
+   * <ul><li>results[0]: the group name to store the file </li></ul>
+   * <ul><li>results[1]: the new created filename</li></ul>
+   * return null if fail
+   */
+  public String[] upload_file(String group_name, String master_filename, String prefix_name,
+                              String local_filename, String file_ext_name, NameValuePair[] meta_list) throws IOException, MyException {
+    if ((group_name == null || group_name.length() == 0) ||
+      (master_filename == null || master_filename.length() == 0) ||
+      (prefix_name == null)) {
+      throw new MyException("invalid arguement");
+    }
+
+    File f = new File(local_filename);
+    FileInputStream fis = new FileInputStream(f);
+
+    if (file_ext_name == null) {
+      int nPos = local_filename.lastIndexOf('.');
+      if (nPos > 0 && local_filename.length() - nPos <= ProtoCommon.FDFS_FILE_EXT_NAME_MAX_LEN + 1) {
+        file_ext_name = local_filename.substring(nPos + 1);
+      }
+    }
+
+    try {
+      return this.do_upload_file(ProtoCommon.STORAGE_PROTO_CMD_UPLOAD_SLAVE_FILE, group_name, master_filename, prefix_name,
+        file_ext_name, f.length(), new UploadStream(fis, f.length()), meta_list);
+    } finally {
+      fis.close();
+    }
+  }
+
+  /**
+   * upload file to storage server (by file buff, slave file mode)
+   *
+   * @param group_name      the group name of master file
+   * @param master_filename the master file name to generate the slave file
+   * @param prefix_name     the prefix name to generate the slave file
+   * @param file_buff       file content/buff
+   * @param file_ext_name   file ext name, do not include dot(.)
+   * @param meta_list       meta info array
+   * @return 2 elements string array if success:<br>
+   * <ul><li>results[0]: the group name to store the file</li></ul>
+   * <ul><li>results[1]: the new created filename</li></ul>
+   * return null if fail
+   */
+  public String[] upload_file(String group_name, String master_filename, String prefix_name,
+                              byte[] file_buff, String file_ext_name, NameValuePair[] meta_list) throws IOException, MyException {
+    if ((group_name == null || group_name.length() == 0) ||
+      (master_filename == null || master_filename.length() == 0) ||
+      (prefix_name == null)) {
+      throw new MyException("invalid arguement");
+    }
+
+    return this.do_upload_file(ProtoCommon.STORAGE_PROTO_CMD_UPLOAD_SLAVE_FILE, group_name, master_filename, prefix_name,
+      file_ext_name, file_buff.length, new UploadBuff(file_buff, 0, file_buff.length), meta_list);
+  }
+
+  /**
+   * upload file to storage server (by file buff, slave file mode)
+   *
+   * @param group_name      the group name of master file
+   * @param master_filename the master file name to generate the slave file
+   * @param prefix_name     the prefix name to generate the slave file
+   * @param file_buff       file content/buff
+   * @param offset          start offset of the buff
+   * @param length          the length of buff to upload
+   * @param file_ext_name   file ext name, do not include dot(.)
+   * @param meta_list       meta info array
+   * @return 2 elements string array if success:<br>
+   * <ul><li>results[0]: the group name to store the file</li></ul>
+   * <ul><li>results[1]: the new created filename</li></ul>
+   * return null if fail
+   */
+  public String[] upload_file(String group_name, String master_filename, String prefix_name,
+                              byte[] file_buff, int offset, int length, String file_ext_name,
+                              NameValuePair[] meta_list) throws IOException, MyException {
+    if ((group_name == null || group_name.length() == 0) ||
+      (master_filename == null || master_filename.length() == 0) ||
+      (prefix_name == null)) {
+      throw new MyException("invalid arguement");
+    }
+
+    return this.do_upload_file(ProtoCommon.STORAGE_PROTO_CMD_UPLOAD_SLAVE_FILE, group_name, master_filename, prefix_name,
+      file_ext_name, length, new UploadBuff(file_buff, offset, length), meta_list);
+  }
+
+  /**
+   * upload file to storage server (by callback, slave file mode)
+   *
+   * @param group_name      the group name to upload file to, can be empty
+   * @param master_filename the master file name to generate the slave file
+   * @param prefix_name     the prefix name to generate the slave file
+   * @param file_size       the file size
+   * @param callback        the write data callback object
+   * @param file_ext_name   file ext name, do not include dot(.)
+   * @param meta_list       meta info array
+   * @return 2 elements string array if success:<br>
+   * <ul><li>results[0]: the group name to store the file</li></ul>
+   * <ul><li>results[1]: the new created filename</li></ul>
+   * return null if fail
+   */
+  public String[] upload_file(String group_name, String master_filename,
+                              String prefix_name, long file_size, UploadCallback callback,
+                              String file_ext_name, NameValuePair[] meta_list) throws IOException, MyException {
+    return this.do_upload_file(ProtoCommon.STORAGE_PROTO_CMD_UPLOAD_SLAVE_FILE, group_name, master_filename, prefix_name,
+      file_ext_name, file_size, callback, meta_list);
+  }
+
+  /**
+   * upload appender file to storage server (by file name)
+   *
+   * @param local_filename local filename to upload
+   * @param file_ext_name  file ext name, do not include dot(.), null to extract ext name from the local filename
+   * @param meta_list      meta info array
+   * @return 2 elements string array if success:<br>
+   * <ul><li>results[0]: the group name to store the file </li></ul>
+   * <ul><li>results[1]: the new created filename</li></ul>
+   * return null if fail
+   */
+  public String[] upload_appender_file(String local_filename, String file_ext_name,
+                                       NameValuePair[] meta_list) throws IOException, MyException {
+    final String group_name = null;
+    return this.upload_appender_file(group_name, local_filename, file_ext_name, meta_list);
+  }
+
+  /**
+   * upload appender file to storage server (by file name)
+   *
+   * @param group_name     the group name to upload file to, can be empty
+   * @param local_filename local filename to upload
+   * @param file_ext_name  file ext name, do not include dot(.), null to extract ext name from the local filename
+   * @param meta_list      meta info array
+   * @return 2 elements string array if success:<br>
+   * <ul><li>results[0]: the group name to store the file </li></ul>
+   * <ul><li>results[1]: the new created filename</li></ul>
+   * return null if fail
+   */
+  protected String[] upload_appender_file(String group_name, String local_filename, String file_ext_name,
+                                          NameValuePair[] meta_list) throws IOException, MyException {
+    final byte cmd = ProtoCommon.STORAGE_PROTO_CMD_UPLOAD_APPENDER_FILE;
+    return this.upload_file(cmd, group_name, local_filename, file_ext_name, meta_list);
+  }
+
+  /**
+   * upload appender file to storage server (by file buff)
+   *
+   * @param file_buff     file content/buff
+   * @param offset        start offset of the buff
+   * @param length        the length of buff to upload
+   * @param file_ext_name file ext name, do not include dot(.)
+   * @param meta_list     meta info array
+   * @return 2 elements string array if success:<br>
+   * <ul><li>results[0]: the group name to store the file</li></ul>
+   * <ul><li>results[1]: the new created filename</li></ul>
+   * return null if fail
+   */
+  public String[] upload_appender_file(byte[] file_buff, int offset, int length, String file_ext_name,
+                                       NameValuePair[] meta_list) throws IOException, MyException {
+    final String group_name = null;
+    return this.upload_appender_file(group_name, file_buff, offset, length, file_ext_name, meta_list);
+  }
+
+  /**
+   * upload appender file to storage server (by file buff)
+   *
+   * @param group_name    the group name to upload file to, can be empty
+   * @param file_buff     file content/buff
+   * @param offset        start offset of the buff
+   * @param length        the length of buff to upload
+   * @param file_ext_name file ext name, do not include dot(.)
+   * @param meta_list     meta info array
+   * @return 2 elements string array if success:<br>
+   * <ul><li>results[0]: the group name to store the file</li></ul>
+   * <ul><li>results[1]: the new created filename</li></ul>
+   * return null if fail
+   */
+  public String[] upload_appender_file(String group_name, byte[] file_buff, int offset, int length,
+                                       String file_ext_name, NameValuePair[] meta_list) throws IOException, MyException {
+    return this.do_upload_file(ProtoCommon.STORAGE_PROTO_CMD_UPLOAD_APPENDER_FILE, group_name, null, null, file_ext_name,
+      length, new UploadBuff(file_buff, offset, length), meta_list);
+  }
+
+  /**
+   * upload appender file to storage server (by file buff)
+   *
+   * @param file_buff     file content/buff
+   * @param file_ext_name file ext name, do not include dot(.)
+   * @param meta_list     meta info array
+   * @return 2 elements string array if success:<br>
+   * <ul><li>results[0]: the group name to store the file</li></ul>
+   * <ul><li>results[1]: the new created filename</li></ul>
+   * return null if fail
+   */
+  public String[] upload_appender_file(byte[] file_buff, String file_ext_name,
+                                       NameValuePair[] meta_list) throws IOException, MyException {
+    final String group_name = null;
+    return this.upload_appender_file(group_name, file_buff, 0, file_buff.length, file_ext_name, meta_list);
+  }
+
+  /**
+   * upload appender file to storage server (by file buff)
+   *
+   * @param group_name    the group name to upload file to, can be empty
+   * @param file_buff     file content/buff
+   * @param file_ext_name file ext name, do not include dot(.)
+   * @param meta_list     meta info array
+   * @return 2 elements string array if success:<br>
+   * <ul><li>results[0]: the group name to store the file</li></ul>
+   * <ul><li>results[1]: the new created filename</li></ul>
+   * return null if fail
+   */
+  public String[] upload_appender_file(String group_name, byte[] file_buff,
+                                       String file_ext_name, NameValuePair[] meta_list) throws IOException, MyException {
+    return this.do_upload_file(ProtoCommon.STORAGE_PROTO_CMD_UPLOAD_APPENDER_FILE, group_name, null, null, file_ext_name,
+      file_buff.length, new UploadBuff(file_buff, 0, file_buff.length), meta_list);
+  }
+
+  /**
+   * upload appender file to storage server (by callback)
+   *
+   * @param group_name    the group name to upload file to, can be empty
+   * @param file_size     the file size
+   * @param callback      the write data callback object
+   * @param file_ext_name file ext name, do not include dot(.)
+   * @param meta_list     meta info array
+   * @return 2 elements string array if success:<br>
+   * <ul><li>results[0]: the group name to store the file</li></ul>
+   * <ul><li>results[1]: the new created filename</li></ul>
+   * return null if fail
+   */
+  public String[] upload_appender_file(String group_name, long file_size, UploadCallback callback,
+                                       String file_ext_name, NameValuePair[] meta_list) throws IOException, MyException {
+    final String master_filename = null;
+    final String prefix_name = null;
+
+    return this.do_upload_file(ProtoCommon.STORAGE_PROTO_CMD_UPLOAD_APPENDER_FILE, group_name, master_filename, prefix_name,
+      file_ext_name, file_size, callback, meta_list);
+  }
+
+  /**
+   * append file to storage server (by file name)
+   *
+   * @param group_name        the group name of appender file
+   * @param appender_filename the appender filename
+   * @param local_filename    local filename to append
+   * @return 0 for success, != 0 for error (error no)
+   */
+  public int append_file(String group_name, String appender_filename, String local_filename) throws IOException, MyException {
+    File f = new File(local_filename);
+    FileInputStream fis = new FileInputStream(f);
+
+    try {
+      return this.do_append_file(group_name, appender_filename, f.length(), new UploadStream(fis, f.length()));
+    } finally {
+      fis.close();
+    }
+  }
+
+  /**
+   * append file to storage server (by file buff)
+   *
+   * @param group_name        the group name of appender file
+   * @param appender_filename the appender filename
+   * @param file_buff         file content/buff
+   * @return 0 for success, != 0 for error (error no)
+   */
+  public int append_file(String group_name, String appender_filename, byte[] file_buff) throws IOException, MyException {
+    return this.do_append_file(group_name, appender_filename, file_buff.length, new UploadBuff(file_buff, 0, file_buff.length));
+  }
+
+  /**
+   * append file to storage server (by file buff)
+   *
+   * @param group_name        the group name of appender file
+   * @param appender_filename the appender filename
+   * @param file_buff         file content/buff
+   * @param offset            start offset of the buff
+   * @param length            the length of buff to append
+   * @return 0 for success, != 0 for error (error no)
+   */
+  public int append_file(String group_name, String appender_filename,
+                         byte[] file_buff, int offset, int length) throws IOException, MyException {
+    return this.do_append_file(group_name, appender_filename, length, new UploadBuff(file_buff, offset, length));
+  }
+
+  /**
+   * append file to storage server (by callback)
+   *
+   * @param group_name        the group name to append file to
+   * @param appender_filename the appender filename
+   * @param file_size         the file size
+   * @param callback          the write data callback object
+   * @return 0 for success, != 0 for error (error no)
+   */
+  public int append_file(String group_name, String appender_filename,
+                         long file_size, UploadCallback callback) throws IOException, MyException {
+    return this.do_append_file(group_name, appender_filename, file_size, callback);
+  }
+
+  /**
+   * modify appender file to storage server (by file name)
+   *
+   * @param group_name        the group name of appender file
+   * @param appender_filename the appender filename
+   * @param file_offset       the offset of appender file
+   * @param local_filename    local filename to append
+   * @return 0 for success, != 0 for error (error no)
+   */
+  public int modify_file(String group_name, String appender_filename,
+                         long file_offset, String local_filename) throws IOException, MyException {
+    File f = new File(local_filename);
+    FileInputStream fis = new FileInputStream(f);
+
+    try {
+      return this.do_modify_file(group_name, appender_filename, file_offset,
+        f.length(), new UploadStream(fis, f.length()));
+    } finally {
+      fis.close();
+    }
+  }
+
+  /**
+   * modify appender file to storage server (by file buff)
+   *
+   * @param group_name        the group name of appender file
+   * @param appender_filename the appender filename
+   * @param file_offset       the offset of appender file
+   * @param file_buff         file content/buff
+   * @return 0 for success, != 0 for error (error no)
+   */
+  public int modify_file(String group_name, String appender_filename,
+                         long file_offset, byte[] file_buff) throws IOException, MyException {
+    return this.do_modify_file(group_name, appender_filename, file_offset,
+      file_buff.length, new UploadBuff(file_buff, 0, file_buff.length));
+  }
+
+  /**
+   * modify appender file to storage server (by file buff)
+   *
+   * @param group_name        the group name of appender file
+   * @param appender_filename the appender filename
+   * @param file_offset       the offset of appender file
+   * @param file_buff         file content/buff
+   * @param buffer_offset     start offset of the buff
+   * @param buffer_length     the length of buff to modify
+   * @return 0 for success, != 0 for error (error no)
+   */
+  public int modify_file(String group_name, String appender_filename,
+                         long file_offset, byte[] file_buff, int buffer_offset, int buffer_length) throws IOException, MyException {
+    return this.do_modify_file(group_name, appender_filename, file_offset,
+      buffer_length, new UploadBuff(file_buff, buffer_offset, buffer_length));
+  }
+
+  /**
+   * modify appender file to storage server (by callback)
+   *
+   * @param group_name        the group name to modify file to
+   * @param appender_filename the appender filename
+   * @param file_offset       the offset of appender file
+   * @param modify_size       the modify size
+   * @param callback          the write data callback object
+   * @return 0 for success, != 0 for error (error no)
+   */
+  public int modify_file(String group_name, String appender_filename,
+                         long file_offset, long modify_size, UploadCallback callback) throws IOException, MyException {
+    return this.do_modify_file(group_name, appender_filename, file_offset,
+      modify_size, callback);
+  }
+
+  /**
+   * upload file to storage server
+   *
+   * @param cmd             the command code
+   * @param group_name      the group name to upload file to, can be empty
+   * @param master_filename the master file name to generate the slave file
+   * @param prefix_name     the prefix name to generate the slave file
+   * @param file_ext_name   file ext name, do not include dot(.)
+   * @param file_size       the file size
+   * @param callback        the write data callback object
+   * @param meta_list       meta info array
+   * @return 2 elements string array if success:<br>
+   * <ul><li> results[0]: the group name to store the file</li></ul>
+   * <ul><li> results[1]: the new created filename</li></ul>
+   * return null if fail
+   */
+  protected String[] do_upload_file(byte cmd, String group_name, String master_filename,
+                                    String prefix_name, String file_ext_name, long file_size, UploadCallback callback,
+                                    NameValuePair[] meta_list) throws IOException, MyException {
+    byte[] header;
+    byte[] ext_name_bs;
+    String new_group_name;
+    String remote_filename;
+    boolean bNewConnection;
+    Socket storageSocket;
+    byte[] sizeBytes;
+    byte[] hexLenBytes;
+    byte[] masterFilenameBytes;
+    boolean bUploadSlave;
+    int offset;
+    long body_len;
+
+    bUploadSlave = ((group_name != null && group_name.length() > 0) &&
+      (master_filename != null && master_filename.length() > 0) &&
+      (prefix_name != null));
+    if (bUploadSlave) {
+      bNewConnection = this.newUpdatableStorageConnection(group_name, master_filename);
+    } else {
+      bNewConnection = this.newWritableStorageConnection(group_name);
+    }
+
+    try {
+      storageSocket = this.storageServer.getSocket();
+
+      ext_name_bs = new byte[ProtoCommon.FDFS_FILE_EXT_NAME_MAX_LEN];
+      Arrays.fill(ext_name_bs, (byte) 0);
+      if (file_ext_name != null && file_ext_name.length() > 0) {
+        byte[] bs = file_ext_name.getBytes(ClientGlobal.g_charset);
+        int ext_name_len = bs.length;
+        if (ext_name_len > ProtoCommon.FDFS_FILE_EXT_NAME_MAX_LEN) {
+          ext_name_len = ProtoCommon.FDFS_FILE_EXT_NAME_MAX_LEN;
+        }
+        System.arraycopy(bs, 0, ext_name_bs, 0, ext_name_len);
+      }
+
+      if (bUploadSlave) {
+        masterFilenameBytes = master_filename.getBytes(ClientGlobal.g_charset);
+
+        sizeBytes = new byte[2 * ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE];
+        body_len = sizeBytes.length + ProtoCommon.FDFS_FILE_PREFIX_MAX_LEN + ProtoCommon.FDFS_FILE_EXT_NAME_MAX_LEN
+          + masterFilenameBytes.length + file_size;
+
+        hexLenBytes = ProtoCommon.long2buff(master_filename.length());
+        System.arraycopy(hexLenBytes, 0, sizeBytes, 0, hexLenBytes.length);
+        offset = hexLenBytes.length;
+      } else {
+        masterFilenameBytes = null;
+        sizeBytes = new byte[1 + 1 * ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE];
+        body_len = sizeBytes.length + ProtoCommon.FDFS_FILE_EXT_NAME_MAX_LEN + file_size;
+
+        sizeBytes[0] = (byte) this.storageServer.getStorePathIndex();
+        offset = 1;
+      }
+
+      hexLenBytes = ProtoCommon.long2buff(file_size);
+      System.arraycopy(hexLenBytes, 0, sizeBytes, offset, hexLenBytes.length);
+
+      OutputStream out = storageSocket.getOutputStream();
+      header = ProtoCommon.packHeader(cmd, body_len, (byte) 0);
+      byte[] wholePkg = new byte[(int) (header.length + body_len - file_size)];
+      System.arraycopy(header, 0, wholePkg, 0, header.length);
+      System.arraycopy(sizeBytes, 0, wholePkg, header.length, sizeBytes.length);
+      offset = header.length + sizeBytes.length;
+      if (bUploadSlave) {
+        byte[] prefix_name_bs = new byte[ProtoCommon.FDFS_FILE_PREFIX_MAX_LEN];
+        byte[] bs = prefix_name.getBytes(ClientGlobal.g_charset);
+        int prefix_name_len = bs.length;
+        Arrays.fill(prefix_name_bs, (byte) 0);
+        if (prefix_name_len > ProtoCommon.FDFS_FILE_PREFIX_MAX_LEN) {
+          prefix_name_len = ProtoCommon.FDFS_FILE_PREFIX_MAX_LEN;
+        }
+        if (prefix_name_len > 0) {
+          System.arraycopy(bs, 0, prefix_name_bs, 0, prefix_name_len);
+        }
+
+        System.arraycopy(prefix_name_bs, 0, wholePkg, offset, prefix_name_bs.length);
+        offset += prefix_name_bs.length;
+      }
+
+      System.arraycopy(ext_name_bs, 0, wholePkg, offset, ext_name_bs.length);
+      offset += ext_name_bs.length;
+
+      if (bUploadSlave) {
+        System.arraycopy(masterFilenameBytes, 0, wholePkg, offset, masterFilenameBytes.length);
+        offset += masterFilenameBytes.length;
+      }
+
+      out.write(wholePkg);
+
+      if ((this.errno = (byte) callback.send(out)) != 0) {
+        return null;
+      }
+
+      ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(storageSocket.getInputStream(),
+        ProtoCommon.STORAGE_PROTO_CMD_RESP, -1);
+      this.errno = pkgInfo.errno;
+      if (pkgInfo.errno != 0) {
+        return null;
+      }
+
+      if (pkgInfo.body.length <= ProtoCommon.FDFS_GROUP_NAME_MAX_LEN) {
+        throw new MyException("body length: " + pkgInfo.body.length + " <= " + ProtoCommon.FDFS_GROUP_NAME_MAX_LEN);
+      }
+
+      new_group_name = new String(pkgInfo.body, 0, ProtoCommon.FDFS_GROUP_NAME_MAX_LEN).trim();
+      remote_filename = new String(pkgInfo.body, ProtoCommon.FDFS_GROUP_NAME_MAX_LEN, pkgInfo.body.length - ProtoCommon.FDFS_GROUP_NAME_MAX_LEN);
+      String[] results = new String[2];
+      results[0] = new_group_name;
+      results[1] = remote_filename;
+
+      if (meta_list == null || meta_list.length == 0) {
+        return results;
+      }
+
+      int result = 0;
+      try {
+        result = this.set_metadata(new_group_name, remote_filename,
+          meta_list, ProtoCommon.STORAGE_SET_METADATA_FLAG_OVERWRITE);
+      } catch (IOException ex) {
+        result = 5;
+        throw ex;
+      } finally {
+        if (result != 0) {
+          this.errno = (byte) result;
+          this.delete_file(new_group_name, remote_filename);
+          return null;
+        }
+      }
+
+      return results;
+    } catch (IOException ex) {
+      if (!bNewConnection) {
+        try {
+          this.storageServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        } finally {
+          this.storageServer = null;
+        }
+      }
+
+      throw ex;
+    } finally {
+      if (bNewConnection) {
+        try {
+          this.storageServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        } finally {
+          this.storageServer = null;
+        }
+      }
+    }
+  }
+
+  /**
+   * append file to storage server
+   *
+   * @param group_name        the group name of appender file
+   * @param appender_filename the appender filename
+   * @param file_size         the file size
+   * @param callback          the write data callback object
+   * @return return true for success, false for fail
+   */
+  protected int do_append_file(String group_name, String appender_filename,
+                               long file_size, UploadCallback callback) throws IOException, MyException {
+    byte[] header;
+    boolean bNewConnection;
+    Socket storageSocket;
+    byte[] hexLenBytes;
+    byte[] appenderFilenameBytes;
+    int offset;
+    long body_len;
+
+    if ((group_name == null || group_name.length() == 0) ||
+      (appender_filename == null || appender_filename.length() == 0)) {
+      this.errno = ProtoCommon.ERR_NO_EINVAL;
+      return this.errno;
+    }
+
+    bNewConnection = this.newUpdatableStorageConnection(group_name, appender_filename);
+
+    try {
+      storageSocket = this.storageServer.getSocket();
+
+      appenderFilenameBytes = appender_filename.getBytes(ClientGlobal.g_charset);
+      body_len = 2 * ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE + appenderFilenameBytes.length + file_size;
+
+      header = ProtoCommon.packHeader(ProtoCommon.STORAGE_PROTO_CMD_APPEND_FILE, body_len, (byte) 0);
+      byte[] wholePkg = new byte[(int) (header.length + body_len - file_size)];
+      System.arraycopy(header, 0, wholePkg, 0, header.length);
+      offset = header.length;
+
+      hexLenBytes = ProtoCommon.long2buff(appender_filename.length());
+      System.arraycopy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.length);
+      offset += hexLenBytes.length;
+
+      hexLenBytes = ProtoCommon.long2buff(file_size);
+      System.arraycopy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.length);
+      offset += hexLenBytes.length;
+
+      OutputStream out = storageSocket.getOutputStream();
+
+      System.arraycopy(appenderFilenameBytes, 0, wholePkg, offset, appenderFilenameBytes.length);
+      offset += appenderFilenameBytes.length;
+
+      out.write(wholePkg);
+      if ((this.errno = (byte) callback.send(out)) != 0) {
+        return this.errno;
+      }
+
+      ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(storageSocket.getInputStream(),
+        ProtoCommon.STORAGE_PROTO_CMD_RESP, 0);
+      this.errno = pkgInfo.errno;
+      if (pkgInfo.errno != 0) {
+        return this.errno;
+      }
+
+      return 0;
+    } catch (IOException ex) {
+      if (!bNewConnection) {
+        try {
+          this.storageServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        } finally {
+          this.storageServer = null;
+        }
+      }
+
+      throw ex;
+    } finally {
+      if (bNewConnection) {
+        try {
+          this.storageServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        } finally {
+          this.storageServer = null;
+        }
+      }
+    }
+  }
+
+  /**
+   * modify appender file to storage server
+   *
+   * @param group_name        the group name of appender file
+   * @param appender_filename the appender filename
+   * @param file_offset       the offset of appender file
+   * @param modify_size       the modify size
+   * @param callback          the write data callback object
+   * @return return true for success, false for fail
+   */
+  protected int do_modify_file(String group_name, String appender_filename,
+                               long file_offset, long modify_size, UploadCallback callback) throws IOException, MyException {
+    byte[] header;
+    boolean bNewConnection;
+    Socket storageSocket;
+    byte[] hexLenBytes;
+    byte[] appenderFilenameBytes;
+    int offset;
+    long body_len;
+
+    if ((group_name == null || group_name.length() == 0) ||
+      (appender_filename == null || appender_filename.length() == 0)) {
+      this.errno = ProtoCommon.ERR_NO_EINVAL;
+      return this.errno;
+    }
+
+    bNewConnection = this.newUpdatableStorageConnection(group_name, appender_filename);
+
+    try {
+      storageSocket = this.storageServer.getSocket();
+
+      appenderFilenameBytes = appender_filename.getBytes(ClientGlobal.g_charset);
+      body_len = 3 * ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE + appenderFilenameBytes.length + modify_size;
+
+      header = ProtoCommon.packHeader(ProtoCommon.STORAGE_PROTO_CMD_MODIFY_FILE, body_len, (byte) 0);
+      byte[] wholePkg = new byte[(int) (header.length + body_len - modify_size)];
+      System.arraycopy(header, 0, wholePkg, 0, header.length);
+      offset = header.length;
+
+      hexLenBytes = ProtoCommon.long2buff(appender_filename.length());
+      System.arraycopy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.length);
+      offset += hexLenBytes.length;
+
+      hexLenBytes = ProtoCommon.long2buff(file_offset);
+      System.arraycopy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.length);
+      offset += hexLenBytes.length;
+
+      hexLenBytes = ProtoCommon.long2buff(modify_size);
+      System.arraycopy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.length);
+      offset += hexLenBytes.length;
+
+      OutputStream out = storageSocket.getOutputStream();
+
+      System.arraycopy(appenderFilenameBytes, 0, wholePkg, offset, appenderFilenameBytes.length);
+      offset += appenderFilenameBytes.length;
+
+      out.write(wholePkg);
+      if ((this.errno = (byte) callback.send(out)) != 0) {
+        return this.errno;
+      }
+
+      ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(storageSocket.getInputStream(),
+        ProtoCommon.STORAGE_PROTO_CMD_RESP, 0);
+      this.errno = pkgInfo.errno;
+      if (pkgInfo.errno != 0) {
+        return this.errno;
+      }
+
+      return 0;
+    } catch (IOException ex) {
+      if (!bNewConnection) {
+        try {
+          this.storageServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        } finally {
+          this.storageServer = null;
+        }
+      }
+
+      throw ex;
+    } finally {
+      if (bNewConnection) {
+        try {
+          this.storageServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        } finally {
+          this.storageServer = null;
+        }
+      }
+    }
+  }
+
+  /**
+   * delete file from storage server
+   *
+   * @param group_name      the group name of storage server
+   * @param remote_filename filename on storage server
+   * @return 0 for success, none zero for fail (error code)
+   */
+  public int delete_file(String group_name, String remote_filename) throws IOException, MyException {
+    boolean bNewConnection = this.newUpdatableStorageConnection(group_name, remote_filename);
+    Socket storageSocket = this.storageServer.getSocket();
+
+    try {
+      this.send_package(ProtoCommon.STORAGE_PROTO_CMD_DELETE_FILE, group_name, remote_filename);
+      ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(storageSocket.getInputStream(),
+        ProtoCommon.STORAGE_PROTO_CMD_RESP, 0);
+
+      this.errno = pkgInfo.errno;
+      return pkgInfo.errno;
+    } catch (IOException ex) {
+      if (!bNewConnection) {
+        try {
+          this.storageServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        } finally {
+          this.storageServer = null;
+        }
+      }
+
+      throw ex;
+    } finally {
+      if (bNewConnection) {
+        try {
+          this.storageServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        } finally {
+          this.storageServer = null;
+        }
+      }
+    }
+  }
+
+  /**
+   * truncate appender file to size 0 from storage server
+   *
+   * @param group_name        the group name of storage server
+   * @param appender_filename the appender filename
+   * @return 0 for success, none zero for fail (error code)
+   */
+  public int truncate_file(String group_name, String appender_filename) throws IOException, MyException {
+    final long truncated_file_size = 0;
+    return this.truncate_file(group_name, appender_filename, truncated_file_size);
+  }
+
+  /**
+   * truncate appender file from storage server
+   *
+   * @param group_name          the group name of storage server
+   * @param appender_filename   the appender filename
+   * @param truncated_file_size truncated file size
+   * @return 0 for success, none zero for fail (error code)
+   */
+  public int truncate_file(String group_name, String appender_filename,
+                           long truncated_file_size) throws IOException, MyException {
+    byte[] header;
+    boolean bNewConnection;
+    Socket storageSocket;
+    byte[] hexLenBytes;
+    byte[] appenderFilenameBytes;
+    int offset;
+    int body_len;
+
+    if ((group_name == null || group_name.length() == 0) ||
+      (appender_filename == null || appender_filename.length() == 0)) {
+      this.errno = ProtoCommon.ERR_NO_EINVAL;
+      return this.errno;
+    }
+
+    bNewConnection = this.newUpdatableStorageConnection(group_name, appender_filename);
+
+    try {
+      storageSocket = this.storageServer.getSocket();
+
+      appenderFilenameBytes = appender_filename.getBytes(ClientGlobal.g_charset);
+      body_len = 2 * ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE + appenderFilenameBytes.length;
+
+      header = ProtoCommon.packHeader(ProtoCommon.STORAGE_PROTO_CMD_TRUNCATE_FILE, body_len, (byte) 0);
+      byte[] wholePkg = new byte[header.length + body_len];
+      System.arraycopy(header, 0, wholePkg, 0, header.length);
+      offset = header.length;
+
+      hexLenBytes = ProtoCommon.long2buff(appender_filename.length());
+      System.arraycopy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.length);
+      offset += hexLenBytes.length;
+
+      hexLenBytes = ProtoCommon.long2buff(truncated_file_size);
+      System.arraycopy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.length);
+      offset += hexLenBytes.length;
+
+      OutputStream out = storageSocket.getOutputStream();
+
+      System.arraycopy(appenderFilenameBytes, 0, wholePkg, offset, appenderFilenameBytes.length);
+      offset += appenderFilenameBytes.length;
+
+      out.write(wholePkg);
+      ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(storageSocket.getInputStream(),
+        ProtoCommon.STORAGE_PROTO_CMD_RESP, 0);
+      this.errno = pkgInfo.errno;
+      return pkgInfo.errno;
+    } catch (IOException ex) {
+      if (!bNewConnection) {
+        try {
+          this.storageServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        } finally {
+          this.storageServer = null;
+        }
+      }
+
+      throw ex;
+    } finally {
+      if (bNewConnection) {
+        try {
+          this.storageServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        } finally {
+          this.storageServer = null;
+        }
+      }
+    }
+  }
+
+  /**
+   * download file from storage server
+   *
+   * @param group_name      the group name of storage server
+   * @param remote_filename filename on storage server
+   * @return file content/buff, return null if fail
+   */
+  public byte[] download_file(String group_name, String remote_filename) throws IOException, MyException {
+    final long file_offset = 0;
+    final long download_bytes = 0;
+
+    return this.download_file(group_name, remote_filename, file_offset, download_bytes);
+  }
+
+  /**
+   * download file from storage server
+   *
+   * @param group_name      the group name of storage server
+   * @param remote_filename filename on storage server
+   * @param file_offset     the start offset of the file
+   * @param download_bytes  download bytes, 0 for remain bytes from offset
+   * @return file content/buff, return null if fail
+   */
+  public byte[] download_file(String group_name, String remote_filename, long file_offset, long download_bytes) throws IOException, MyException {
+    boolean bNewConnection = this.newReadableStorageConnection(group_name, remote_filename);
+    Socket storageSocket = this.storageServer.getSocket();
+
+    try {
+      ProtoCommon.RecvPackageInfo pkgInfo;
+
+      this.send_download_package(group_name, remote_filename, file_offset, download_bytes);
+      pkgInfo = ProtoCommon.recvPackage(storageSocket.getInputStream(),
+        ProtoCommon.STORAGE_PROTO_CMD_RESP, -1);
+
+      this.errno = pkgInfo.errno;
+      if (pkgInfo.errno != 0) {
+        return null;
+      }
+
+      return pkgInfo.body;
+    } catch (IOException ex) {
+      if (!bNewConnection) {
+        try {
+          this.storageServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        } finally {
+          this.storageServer = null;
+        }
+      }
+
+      throw ex;
+    } finally {
+      if (bNewConnection) {
+        try {
+          this.storageServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        } finally {
+          this.storageServer = null;
+        }
+      }
+    }
+  }
+
+  /**
+   * download file from storage server
+   *
+   * @param group_name      the group name of storage server
+   * @param remote_filename filename on storage server
+   * @param local_filename  filename on local
+   * @return 0 success, return none zero errno if fail
+   */
+  public int download_file(String group_name, String remote_filename,
+                           String local_filename) throws IOException, MyException {
+    final long file_offset = 0;
+    final long download_bytes = 0;
+    return this.download_file(group_name, remote_filename,
+      file_offset, download_bytes, local_filename);
+  }
+
+  /**
+   * download file from storage server
+   *
+   * @param group_name      the group name of storage server
+   * @param remote_filename filename on storage server
+   * @param file_offset     the start offset of the file
+   * @param download_bytes  download bytes, 0 for remain bytes from offset
+   * @param local_filename  filename on local
+   * @return 0 success, return none zero errno if fail
+   */
+  public int download_file(String group_name, String remote_filename,
+                           long file_offset, long download_bytes,
+                           String local_filename) throws IOException, MyException {
+    boolean bNewConnection = this.newReadableStorageConnection(group_name, remote_filename);
+    Socket storageSocket = this.storageServer.getSocket();
+    try {
+      ProtoCommon.RecvHeaderInfo header;
+      FileOutputStream out = new FileOutputStream(local_filename);
+      try {
+        this.errno = 0;
+        this.send_download_package(group_name, remote_filename, file_offset, download_bytes);
+
+        InputStream in = storageSocket.getInputStream();
+        header = ProtoCommon.recvHeader(in, ProtoCommon.STORAGE_PROTO_CMD_RESP, -1);
+        this.errno = header.errno;
+        if (header.errno != 0) {
+          return header.errno;
+        }
+
+        byte[] buff = new byte[256 * 1024];
+        long remainBytes = header.body_len;
+        int bytes;
+
+        //System.out.println("expect_body_len=" + header.body_len);
+
+        while (remainBytes > 0) {
+          if ((bytes = in.read(buff, 0, remainBytes > buff.length ? buff.length : (int) remainBytes)) < 0) {
+            throw new IOException("recv package size " + (header.body_len - remainBytes) + " != " + header.body_len);
+          }
+
+          out.write(buff, 0, bytes);
+          remainBytes -= bytes;
+
+          //System.out.println("totalBytes=" + (header.body_len - remainBytes));
+        }
+
+        return 0;
+      } catch (IOException ex) {
+        if (this.errno == 0) {
+          this.errno = ProtoCommon.ERR_NO_EIO;
+        }
+
+        throw ex;
+      } finally {
+        out.close();
+        if (this.errno != 0) {
+          (new File(local_filename)).delete();
+        }
+      }
+    } catch (IOException ex) {
+      if (!bNewConnection) {
+        try {
+          this.storageServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        } finally {
+          this.storageServer = null;
+        }
+      }
+
+      throw ex;
+    } finally {
+      if (bNewConnection) {
+        try {
+          this.storageServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        } finally {
+          this.storageServer = null;
+        }
+      }
+    }
+  }
+
+  /**
+   * download file from storage server
+   *
+   * @param group_name      the group name of storage server
+   * @param remote_filename filename on storage server
+   * @param callback        call callback.recv() when data arrive
+   * @return 0 success, return none zero errno if fail
+   */
+  public int download_file(String group_name, String remote_filename,
+                           DownloadCallback callback) throws IOException, MyException {
+    final long file_offset = 0;
+    final long download_bytes = 0;
+    return this.download_file(group_name, remote_filename,
+      file_offset, download_bytes, callback);
+  }
+
+  /**
+   * download file from storage server
+   *
+   * @param group_name      the group name of storage server
+   * @param remote_filename filename on storage server
+   * @param file_offset     the start offset of the file
+   * @param download_bytes  download bytes, 0 for remain bytes from offset
+   * @param callback        call callback.recv() when data arrive
+   * @return 0 success, return none zero errno if fail
+   */
+  public int download_file(String group_name, String remote_filename,
+                           long file_offset, long download_bytes,
+                           DownloadCallback callback) throws IOException, MyException {
+    int result;
+    boolean bNewConnection = this.newReadableStorageConnection(group_name, remote_filename);
+    Socket storageSocket = this.storageServer.getSocket();
+
+    try {
+      ProtoCommon.RecvHeaderInfo header;
+      this.send_download_package(group_name, remote_filename, file_offset, download_bytes);
+
+      InputStream in = storageSocket.getInputStream();
+      header = ProtoCommon.recvHeader(in, ProtoCommon.STORAGE_PROTO_CMD_RESP, -1);
+      this.errno = header.errno;
+      if (header.errno != 0) {
+        return header.errno;
+      }
+
+      byte[] buff = new byte[2 * 1024];
+      long remainBytes = header.body_len;
+      int bytes;
+
+      //System.out.println("expect_body_len=" + header.body_len);
+
+      while (remainBytes > 0) {
+        if ((bytes = in.read(buff, 0, remainBytes > buff.length ? buff.length : (int) remainBytes)) < 0) {
+          throw new IOException("recv package size " + (header.body_len - remainBytes) + " != " + header.body_len);
+        }
+
+        if ((result = callback.recv(header.body_len, buff, bytes)) != 0) {
+          this.errno = (byte) result;
+          return result;
+        }
+
+        remainBytes -= bytes;
+        //System.out.println("totalBytes=" + (header.body_len - remainBytes));
+      }
+
+      return 0;
+    } catch (IOException ex) {
+      if (!bNewConnection) {
+        try {
+          this.storageServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        } finally {
+          this.storageServer = null;
+        }
+      }
+
+      throw ex;
+    } finally {
+      if (bNewConnection) {
+        try {
+          this.storageServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        } finally {
+          this.storageServer = null;
+        }
+      }
+    }
+  }
+
+  /**
+   * get all metadata items from storage server
+   *
+   * @param group_name      the group name of storage server
+   * @param remote_filename filename on storage server
+   * @return meta info array, return null if fail
+   */
+  public NameValuePair[] get_metadata(String group_name, String remote_filename) throws IOException, MyException {
+    boolean bNewConnection = this.newUpdatableStorageConnection(group_name, remote_filename);
+    Socket storageSocket = this.storageServer.getSocket();
+
+    try {
+      ProtoCommon.RecvPackageInfo pkgInfo;
+
+      this.send_package(ProtoCommon.STORAGE_PROTO_CMD_GET_METADATA, group_name, remote_filename);
+      pkgInfo = ProtoCommon.recvPackage(storageSocket.getInputStream(),
+        ProtoCommon.STORAGE_PROTO_CMD_RESP, -1);
+
+      this.errno = pkgInfo.errno;
+      if (pkgInfo.errno != 0) {
+        return null;
+      }
+
+      return ProtoCommon.split_metadata(new String(pkgInfo.body, ClientGlobal.g_charset));
+    } catch (IOException ex) {
+      if (!bNewConnection) {
+        try {
+          this.storageServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        } finally {
+          this.storageServer = null;
+        }
+      }
+
+      throw ex;
+    } finally {
+      if (bNewConnection) {
+        try {
+          this.storageServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        } finally {
+          this.storageServer = null;
+        }
+      }
+    }
+  }
+
+  /**
+   * set metadata items to storage server
+   *
+   * @param group_name      the group name of storage server
+   * @param remote_filename filename on storage server
+   * @param meta_list       meta item array
+   * @param op_flag         flag, can be one of following values: <br>
+   *                        <ul><li> ProtoCommon.STORAGE_SET_METADATA_FLAG_OVERWRITE: overwrite all old
+   *                        metadata items</li></ul>
+   *                        <ul><li> ProtoCommon.STORAGE_SET_METADATA_FLAG_MERGE: merge, insert when
+   *                        the metadata item not exist, otherwise update it</li></ul>
+   * @return 0 for success, !=0 fail (error code)
+   */
+  public int set_metadata(String group_name, String remote_filename,
+                          NameValuePair[] meta_list, byte op_flag) throws IOException, MyException {
+    boolean bNewConnection = this.newUpdatableStorageConnection(group_name, remote_filename);
+    Socket storageSocket = this.storageServer.getSocket();
+
+    try {
+      byte[] header;
+      byte[] groupBytes;
+      byte[] filenameBytes;
+      byte[] meta_buff;
+      byte[] bs;
+      int groupLen;
+      byte[] sizeBytes;
+      ProtoCommon.RecvPackageInfo pkgInfo;
+
+      if (meta_list == null) {
+        meta_buff = new byte[0];
+      } else {
+        meta_buff = ProtoCommon.pack_metadata(meta_list).getBytes(ClientGlobal.g_charset);
+      }
+
+      filenameBytes = remote_filename.getBytes(ClientGlobal.g_charset);
+      sizeBytes = new byte[2 * ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE];
+      Arrays.fill(sizeBytes, (byte) 0);
+
+      bs = ProtoCommon.long2buff(filenameBytes.length);
+      System.arraycopy(bs, 0, sizeBytes, 0, bs.length);
+      bs = ProtoCommon.long2buff(meta_buff.length);
+      System.arraycopy(bs, 0, sizeBytes, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE, bs.length);
+
+      groupBytes = new byte[ProtoCommon.FDFS_GROUP_NAME_MAX_LEN];
+      bs = group_name.getBytes(ClientGlobal.g_charset);
+
+      Arrays.fill(groupBytes, (byte) 0);
+      if (bs.length <= groupBytes.length) {
+        groupLen = bs.length;
+      } else {
+        groupLen = groupBytes.length;
+      }
+      System.arraycopy(bs, 0, groupBytes, 0, groupLen);
+
+      header = ProtoCommon.packHeader(ProtoCommon.STORAGE_PROTO_CMD_SET_METADATA,
+        2 * ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE + 1 + groupBytes.length
+          + filenameBytes.length + meta_buff.length, (byte) 0);
+      OutputStream out = storageSocket.getOutputStream();
+      byte[] wholePkg = new byte[header.length + sizeBytes.length + 1 + groupBytes.length + filenameBytes.length];
+      System.arraycopy(header, 0, wholePkg, 0, header.length);
+      System.arraycopy(sizeBytes, 0, wholePkg, header.length, sizeBytes.length);
+      wholePkg[header.length + sizeBytes.length] = op_flag;
+      System.arraycopy(groupBytes, 0, wholePkg, header.length + sizeBytes.length + 1, groupBytes.length);
+      System.arraycopy(filenameBytes, 0, wholePkg, header.length + sizeBytes.length + 1 + groupBytes.length, filenameBytes.length);
+      out.write(wholePkg);
+      if (meta_buff.length > 0) {
+        out.write(meta_buff);
+      }
+
+      pkgInfo = ProtoCommon.recvPackage(storageSocket.getInputStream(),
+        ProtoCommon.STORAGE_PROTO_CMD_RESP, 0);
+
+      this.errno = pkgInfo.errno;
+      return pkgInfo.errno;
+    } catch (IOException ex) {
+      if (!bNewConnection) {
+        try {
+          this.storageServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        } finally {
+          this.storageServer = null;
+        }
+      }
+
+      throw ex;
+    } finally {
+      if (bNewConnection) {
+        try {
+          this.storageServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        } finally {
+          this.storageServer = null;
+        }
+      }
+    }
+  }
+
+  /**
+   * get file info decoded from the filename, fetch from the storage if necessary
+   *
+   * @param group_name      the group name
+   * @param remote_filename the filename
+   * @return FileInfo object for success, return null for fail
+   */
+  public FileInfo get_file_info(String group_name, String remote_filename) throws IOException, MyException {
+    if (remote_filename.length() < ProtoCommon.FDFS_FILE_PATH_LEN + ProtoCommon.FDFS_FILENAME_BASE64_LENGTH
+      + ProtoCommon.FDFS_FILE_EXT_NAME_MAX_LEN + 1) {
+      this.errno = ProtoCommon.ERR_NO_EINVAL;
+      return null;
+    }
+
+    byte[] buff = base64.decodeAuto(remote_filename.substring(ProtoCommon.FDFS_FILE_PATH_LEN,
+      ProtoCommon.FDFS_FILE_PATH_LEN + ProtoCommon.FDFS_FILENAME_BASE64_LENGTH));
+
+    long file_size = ProtoCommon.buff2long(buff, 4 * 2);
+    if (((remote_filename.length() > ProtoCommon.TRUNK_LOGIC_FILENAME_LENGTH) ||
+      ((remote_filename.length() > ProtoCommon.NORMAL_LOGIC_FILENAME_LENGTH) && ((file_size & ProtoCommon.TRUNK_FILE_MARK_SIZE) == 0))) ||
+      ((file_size & ProtoCommon.APPENDER_FILE_SIZE) != 0)) { //slave file or appender file
+      FileInfo fi = this.query_file_info(group_name, remote_filename);
+      if (fi == null) {
+        return null;
+      }
+      return fi;
+    }
+
+    FileInfo fileInfo = new FileInfo(file_size, 0, 0, ProtoCommon.getIpAddress(buff, 0));
+    fileInfo.setCreateTimestamp(ProtoCommon.buff2int(buff, 4));
+    if ((file_size >> 63) != 0) {
+      file_size &= 0xFFFFFFFFL;  //low 32 bits is file size
+      fileInfo.setFileSize(file_size);
+    }
+    fileInfo.setCrc32(ProtoCommon.buff2int(buff, 4 * 4));
+
+    return fileInfo;
+  }
+
+  /**
+   * get file info from storage server
+   *
+   * @param group_name      the group name of storage server
+   * @param remote_filename filename on storage server
+   * @return FileInfo object for success, return null for fail
+   */
+  public FileInfo query_file_info(String group_name, String remote_filename) throws IOException, MyException {
+    boolean bNewConnection = this.newUpdatableStorageConnection(group_name, remote_filename);
+    Socket storageSocket = this.storageServer.getSocket();
+
+    try {
+      byte[] header;
+      byte[] groupBytes;
+      byte[] filenameBytes;
+      byte[] bs;
+      int groupLen;
+      ProtoCommon.RecvPackageInfo pkgInfo;
+
+      filenameBytes = remote_filename.getBytes(ClientGlobal.g_charset);
+      groupBytes = new byte[ProtoCommon.FDFS_GROUP_NAME_MAX_LEN];
+      bs = group_name.getBytes(ClientGlobal.g_charset);
+
+      Arrays.fill(groupBytes, (byte) 0);
+      if (bs.length <= groupBytes.length) {
+        groupLen = bs.length;
+      } else {
+        groupLen = groupBytes.length;
+      }
+      System.arraycopy(bs, 0, groupBytes, 0, groupLen);
+
+      header = ProtoCommon.packHeader(ProtoCommon.STORAGE_PROTO_CMD_QUERY_FILE_INFO,
+        +groupBytes.length + filenameBytes.length, (byte) 0);
+      OutputStream out = storageSocket.getOutputStream();
+      byte[] wholePkg = new byte[header.length + groupBytes.length + filenameBytes.length];
+      System.arraycopy(header, 0, wholePkg, 0, header.length);
+      System.arraycopy(groupBytes, 0, wholePkg, header.length, groupBytes.length);
+      System.arraycopy(filenameBytes, 0, wholePkg, header.length + groupBytes.length, filenameBytes.length);
+      out.write(wholePkg);
+
+      pkgInfo = ProtoCommon.recvPackage(storageSocket.getInputStream(),
+        ProtoCommon.STORAGE_PROTO_CMD_RESP,
+        3 * ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE +
+          ProtoCommon.FDFS_IPADDR_SIZE);
+
+      this.errno = pkgInfo.errno;
+      if (pkgInfo.errno != 0) {
+        return null;
+      }
+
+      long file_size = ProtoCommon.buff2long(pkgInfo.body, 0);
+      int create_timestamp = (int) ProtoCommon.buff2long(pkgInfo.body, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+      int crc32 = (int) ProtoCommon.buff2long(pkgInfo.body, 2 * ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+      String source_ip_addr = (new String(pkgInfo.body, 3 * ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE, ProtoCommon.FDFS_IPADDR_SIZE)).trim();
+      return new FileInfo(file_size, create_timestamp, crc32, source_ip_addr);
+    } catch (IOException ex) {
+      if (!bNewConnection) {
+        try {
+          this.storageServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        } finally {
+          this.storageServer = null;
+        }
+      }
+
+      throw ex;
+    } finally {
+      if (bNewConnection) {
+        try {
+          this.storageServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        } finally {
+          this.storageServer = null;
+        }
+      }
+    }
+  }
+
+  /**
+   * check storage socket, if null create a new connection
+   *
+   * @param group_name the group name to upload file to, can be empty
+   * @return true if create a new connection
+   */
+  protected boolean newWritableStorageConnection(String group_name) throws IOException, MyException {
+    if (this.storageServer != null) {
+      return false;
+    } else {
+      TrackerClient tracker = new TrackerClient();
+      this.storageServer = tracker.getStoreStorage(this.trackerServer, group_name);
+      if (this.storageServer == null) {
+        throw new MyException("getStoreStorage fail, errno code: " + tracker.getErrorCode());
+      }
+      return true;
+    }
+  }
+
+  /**
+   * check storage socket, if null create a new connection
+   *
+   * @param group_name      the group name of storage server
+   * @param remote_filename filename on storage server
+   * @return true if create a new connection
+   */
+  protected boolean newReadableStorageConnection(String group_name, String remote_filename) throws IOException, MyException {
+    if (this.storageServer != null) {
+      return false;
+    } else {
+      TrackerClient tracker = new TrackerClient();
+      this.storageServer = tracker.getFetchStorage(this.trackerServer, group_name, remote_filename);
+      if (this.storageServer == null) {
+        throw new MyException("getStoreStorage fail, errno code: " + tracker.getErrorCode());
+      }
+      return true;
+    }
+  }
+
+  /**
+   * check storage socket, if null create a new connection
+   *
+   * @param group_name      the group name of storage server
+   * @param remote_filename filename on storage server
+   * @return true if create a new connection
+   */
+  protected boolean newUpdatableStorageConnection(String group_name, String remote_filename) throws IOException, MyException {
+    if (this.storageServer != null) {
+      return false;
+    } else {
+      TrackerClient tracker = new TrackerClient();
+      this.storageServer = tracker.getUpdateStorage(this.trackerServer, group_name, remote_filename);
+      if (this.storageServer == null) {
+        throw new MyException("getStoreStorage fail, errno code: " + tracker.getErrorCode());
+      }
+      return true;
+    }
+  }
+
+  /**
+   * send package to storage server
+   *
+   * @param cmd             which command to send
+   * @param group_name      the group name of storage server
+   * @param remote_filename filename on storage server
+   */
+  protected void send_package(byte cmd, String group_name, String remote_filename) throws IOException {
+    byte[] header;
+    byte[] groupBytes;
+    byte[] filenameBytes;
+    byte[] bs;
+    int groupLen;
+
+    groupBytes = new byte[ProtoCommon.FDFS_GROUP_NAME_MAX_LEN];
+    bs = group_name.getBytes(ClientGlobal.g_charset);
+    filenameBytes = remote_filename.getBytes(ClientGlobal.g_charset);
+
+    Arrays.fill(groupBytes, (byte) 0);
+    if (bs.length <= groupBytes.length) {
+      groupLen = bs.length;
+    } else {
+      groupLen = groupBytes.length;
+    }
+    System.arraycopy(bs, 0, groupBytes, 0, groupLen);
+
+    header = ProtoCommon.packHeader(cmd, groupBytes.length + filenameBytes.length, (byte) 0);
+    byte[] wholePkg = new byte[header.length + groupBytes.length + filenameBytes.length];
+    System.arraycopy(header, 0, wholePkg, 0, header.length);
+    System.arraycopy(groupBytes, 0, wholePkg, header.length, groupBytes.length);
+    System.arraycopy(filenameBytes, 0, wholePkg, header.length + groupBytes.length, filenameBytes.length);
+    this.storageServer.getSocket().getOutputStream().write(wholePkg);
+  }
+
+  /**
+   * send package to storage server
+   *
+   * @param group_name      the group name of storage server
+   * @param remote_filename filename on storage server
+   * @param file_offset     the start offset of the file
+   * @param download_bytes  download bytes
+   */
+  protected void send_download_package(String group_name, String remote_filename, long file_offset, long download_bytes) throws IOException {
+    byte[] header;
+    byte[] bsOffset;
+    byte[] bsDownBytes;
+    byte[] groupBytes;
+    byte[] filenameBytes;
+    byte[] bs;
+    int groupLen;
+
+    bsOffset = ProtoCommon.long2buff(file_offset);
+    bsDownBytes = ProtoCommon.long2buff(download_bytes);
+    groupBytes = new byte[ProtoCommon.FDFS_GROUP_NAME_MAX_LEN];
+    bs = group_name.getBytes(ClientGlobal.g_charset);
+    filenameBytes = remote_filename.getBytes(ClientGlobal.g_charset);
+
+    Arrays.fill(groupBytes, (byte) 0);
+    if (bs.length <= groupBytes.length) {
+      groupLen = bs.length;
+    } else {
+      groupLen = groupBytes.length;
+    }
+    System.arraycopy(bs, 0, groupBytes, 0, groupLen);
+
+    header = ProtoCommon.packHeader(ProtoCommon.STORAGE_PROTO_CMD_DOWNLOAD_FILE,
+      bsOffset.length + bsDownBytes.length + groupBytes.length + filenameBytes.length, (byte) 0);
+    byte[] wholePkg = new byte[header.length + bsOffset.length + bsDownBytes.length + groupBytes.length + filenameBytes.length];
+    System.arraycopy(header, 0, wholePkg, 0, header.length);
+    System.arraycopy(bsOffset, 0, wholePkg, header.length, bsOffset.length);
+    System.arraycopy(bsDownBytes, 0, wholePkg, header.length + bsOffset.length, bsDownBytes.length);
+    System.arraycopy(groupBytes, 0, wholePkg, header.length + bsOffset.length + bsDownBytes.length, groupBytes.length);
+    System.arraycopy(filenameBytes, 0, wholePkg, header.length + bsOffset.length + bsDownBytes.length + groupBytes.length, filenameBytes.length);
+    this.storageServer.getSocket().getOutputStream().write(wholePkg);
+  }
+
+  /**
+   * Upload file by file buff
+   *
+   * @author Happy Fish / YuQing
+   * @version Version 1.12
+   */
+  public static class UploadBuff implements UploadCallback {
+    private byte[] fileBuff;
+    private int offset;
+    private int length;
+
+    /**
+     * constructor
+     *
+     * @param fileBuff the file buff for uploading
+     */
+    public UploadBuff(byte[] fileBuff, int offset, int length) {
+      super();
+      this.fileBuff = fileBuff;
+      this.offset = offset;
+      this.length = length;
+    }
+
+    /**
+     * send file content callback function, be called only once when the file uploaded
+     *
+     * @param out output stream for writing file content
+     * @return 0 success, return none zero(errno) if fail
+     */
+    public int send(OutputStream out) throws IOException {
+      out.write(this.fileBuff, this.offset, this.length);
+
+      return 0;
+    }
+  }
+}

+ 732 - 0
kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/StorageClient1.java

@@ -0,0 +1,732 @@
+/**
+ * Copyright (C) 2008 Happy Fish / YuQing
+ * <p>
+ * FastDFS Java Client may be copied only under the terms of the GNU Lesser
+ * General Public License (LGPL).
+ * Please visit the FastDFS Home Page http://www.csource.org/ for more detail.
+ */
+
+package com.kmall.common.fileserver.fastdfs;
+
+import com.kmall.common.fileserver.common.MyException;
+import com.kmall.common.fileserver.common.NameValuePair;
+
+import java.io.IOException;
+
+/**
+ * Storage client for 1 field file id: combined group name and filename
+ *
+ * @author Happy Fish / YuQing
+ * @version Version 1.21
+ */
+public class StorageClient1 extends StorageClient {
+  public static final String SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR = "/";
+
+  /**
+   * constructor
+   */
+  public StorageClient1() {
+    super();
+  }
+
+  /**
+   * constructor
+   *
+   * @param trackerServer the tracker server, can be null
+   * @param storageServer the storage server, can be null
+   */
+  public StorageClient1(TrackerServer trackerServer, StorageServer storageServer) {
+    super(trackerServer, storageServer);
+  }
+
+  public static byte split_file_id(String file_id, String[] results) {
+    int pos = file_id.indexOf(SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR);
+    if ((pos <= 0) || (pos == file_id.length() - 1)) {
+      return ProtoCommon.ERR_NO_EINVAL;
+    }
+
+    results[0] = file_id.substring(0, pos); //group name
+    results[1] = file_id.substring(pos + 1); //file name
+    return 0;
+  }
+
+  /**
+   * upload file to storage server (by file name)
+   *
+   * @param local_filename local filename to upload
+   * @param file_ext_name  file ext name, do not include dot(.), null to extract ext name from the local filename
+   * @param meta_list      meta info array
+   * @return file id(including group name and filename) if success, <br>
+   * return null if fail
+   */
+  public String upload_file1(String local_filename, String file_ext_name,
+                             NameValuePair[] meta_list) throws IOException, MyException {
+    String parts[] = this.upload_file(local_filename, file_ext_name, meta_list);
+    if (parts != null) {
+      return parts[0] + SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR + parts[1];
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * upload file to storage server (by file name)
+   *
+   * @param group_name     the group name to upload file to, can be empty
+   * @param local_filename local filename to upload
+   * @param file_ext_name  file ext name, do not include dot(.), null to extract ext name from the local filename
+   * @param meta_list      meta info array
+   * @return file id(including group name and filename) if success, <br>
+   * return null if fail
+   */
+  public String upload_file1(String group_name, String local_filename, String file_ext_name,
+                             NameValuePair[] meta_list) throws IOException, MyException {
+    String parts[] = this.upload_file(group_name, local_filename, file_ext_name, meta_list);
+    if (parts != null) {
+      return parts[0] + SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR + parts[1];
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * upload file to storage server (by file buff)
+   *
+   * @param file_buff     file content/buff
+   * @param file_ext_name file ext name, do not include dot(.)
+   * @param meta_list     meta info array
+   * @return file id(including group name and filename) if success, <br>
+   * return null if fail
+   */
+  public String upload_file1(byte[] file_buff, String file_ext_name,
+                             NameValuePair[] meta_list) throws IOException, MyException {
+    String parts[] = this.upload_file(file_buff, file_ext_name, meta_list);
+    if (parts != null) {
+      return parts[0] + SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR + parts[1];
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * upload file to storage server (by file buff)
+   *
+   * @param group_name    the group name to upload file to, can be empty
+   * @param file_buff     file content/buff
+   * @param file_ext_name file ext name, do not include dot(.)
+   * @param meta_list     meta info array
+   * @return file id(including group name and filename) if success, <br>
+   * return null if fail
+   */
+  public String upload_file1(String group_name, byte[] file_buff, String file_ext_name,
+                             NameValuePair[] meta_list) throws IOException, MyException {
+    String parts[] = this.upload_file(group_name, file_buff, file_ext_name, meta_list);
+    if (parts != null) {
+      return parts[0] + SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR + parts[1];
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * upload file to storage server (by callback)
+   *
+   * @param group_name    the group name to upload file to, can be empty
+   * @param file_size     the file size
+   * @param callback      the write data callback object
+   * @param file_ext_name file ext name, do not include dot(.)
+   * @param meta_list     meta info array
+   * @return file id(including group name and filename) if success, <br>
+   * return null if fail
+   */
+  public String upload_file1(String group_name, long file_size,
+                             UploadCallback callback, String file_ext_name,
+                             NameValuePair[] meta_list) throws IOException, MyException {
+    String parts[] = this.upload_file(group_name, file_size, callback, file_ext_name, meta_list);
+    if (parts != null) {
+      return parts[0] + SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR + parts[1];
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * upload appender file to storage server (by file name)
+   *
+   * @param local_filename local filename to upload
+   * @param file_ext_name  file ext name, do not include dot(.), null to extract ext name from the local filename
+   * @param meta_list      meta info array
+   * @return file id(including group name and filename) if success, <br>
+   * return null if fail
+   */
+  public String upload_appender_file1(String local_filename, String file_ext_name,
+                                      NameValuePair[] meta_list) throws IOException, MyException {
+    String parts[] = this.upload_appender_file(local_filename, file_ext_name, meta_list);
+    if (parts != null) {
+      return parts[0] + SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR + parts[1];
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * upload appender file to storage server (by file name)
+   *
+   * @param group_name     the group name to upload file to, can be empty
+   * @param local_filename local filename to upload
+   * @param file_ext_name  file ext name, do not include dot(.), null to extract ext name from the local filename
+   * @param meta_list      meta info array
+   * @return file id(including group name and filename) if success, <br>
+   * return null if fail
+   */
+  public String upload_appender_file1(String group_name, String local_filename, String file_ext_name,
+                                      NameValuePair[] meta_list) throws IOException, MyException {
+    String parts[] = this.upload_appender_file(group_name, local_filename, file_ext_name, meta_list);
+    if (parts != null) {
+      return parts[0] + SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR + parts[1];
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * upload appender file to storage server (by file buff)
+   *
+   * @param file_buff     file content/buff
+   * @param file_ext_name file ext name, do not include dot(.)
+   * @param meta_list     meta info array
+   * @return file id(including group name and filename) if success, <br>
+   * return null if fail
+   */
+  public String upload_appender_file1(byte[] file_buff, String file_ext_name,
+                                      NameValuePair[] meta_list) throws IOException, MyException {
+    String parts[] = this.upload_appender_file(file_buff, file_ext_name, meta_list);
+    if (parts != null) {
+      return parts[0] + SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR + parts[1];
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * upload appender file to storage server (by file buff)
+   *
+   * @param group_name    the group name to upload file to, can be empty
+   * @param file_buff     file content/buff
+   * @param file_ext_name file ext name, do not include dot(.)
+   * @param meta_list     meta info array
+   * @return file id(including group name and filename) if success, <br>
+   * return null if fail
+   */
+  public String upload_appender_file1(String group_name, byte[] file_buff, String file_ext_name,
+                                      NameValuePair[] meta_list) throws IOException, MyException {
+    String parts[] = this.upload_appender_file(group_name, file_buff, file_ext_name, meta_list);
+    if (parts != null) {
+      return parts[0] + SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR + parts[1];
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * upload appender file to storage server (by callback)
+   *
+   * @param group_name    the group name to upload file to, can be empty
+   * @param file_size     the file size
+   * @param callback      the write data callback object
+   * @param file_ext_name file ext name, do not include dot(.)
+   * @param meta_list     meta info array
+   * @return file id(including group name and filename) if success, <br>
+   * return null if fail
+   */
+  public String upload_appender_file1(String group_name, long file_size,
+                                      UploadCallback callback, String file_ext_name,
+                                      NameValuePair[] meta_list) throws IOException, MyException {
+    String parts[] = this.upload_appender_file(group_name, file_size, callback, file_ext_name, meta_list);
+    if (parts != null) {
+      return parts[0] + SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR + parts[1];
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * upload file to storage server (by file name, slave file mode)
+   *
+   * @param master_file_id the master file id to generate the slave file
+   * @param prefix_name    the prefix name to generate the slave file
+   * @param local_filename local filename to upload
+   * @param file_ext_name  file ext name, do not include dot(.), null to extract ext name from the local filename
+   * @param meta_list      meta info array
+   * @return file id(including group name and filename) if success, <br>
+   * return null if fail
+   */
+  public String upload_file1(String master_file_id, String prefix_name,
+                             String local_filename, String file_ext_name, NameValuePair[] meta_list) throws IOException, MyException {
+    String[] parts = new String[2];
+    this.errno = this.split_file_id(master_file_id, parts);
+    if (this.errno != 0) {
+      return null;
+    }
+
+    parts = this.upload_file(parts[0], parts[1], prefix_name,
+      local_filename, file_ext_name, meta_list);
+    if (parts != null) {
+      return parts[0] + SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR + parts[1];
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * upload file to storage server (by file buff, slave file mode)
+   *
+   * @param master_file_id the master file id to generate the slave file
+   * @param prefix_name    the prefix name to generate the slave file
+   * @param file_buff      file content/buff
+   * @param file_ext_name  file ext name, do not include dot(.)
+   * @param meta_list      meta info array
+   * @return file id(including group name and filename) if success, <br>
+   * return null if fail
+   */
+  public String upload_file1(String master_file_id, String prefix_name,
+                             byte[] file_buff, String file_ext_name, NameValuePair[] meta_list) throws IOException, MyException {
+    String[] parts = new String[2];
+    this.errno = this.split_file_id(master_file_id, parts);
+    if (this.errno != 0) {
+      return null;
+    }
+
+    parts = this.upload_file(parts[0], parts[1], prefix_name, file_buff, file_ext_name, meta_list);
+    if (parts != null) {
+      return parts[0] + SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR + parts[1];
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * upload file to storage server (by file buff, slave file mode)
+   *
+   * @param master_file_id the master file id to generate the slave file
+   * @param prefix_name    the prefix name to generate the slave file
+   * @param file_buff      file content/buff
+   * @param file_ext_name  file ext name, do not include dot(.)
+   * @param meta_list      meta info array
+   * @return file id(including group name and filename) if success, <br>
+   * return null if fail
+   */
+  public String upload_file1(String master_file_id, String prefix_name,
+                             byte[] file_buff, int offset, int length, String file_ext_name,
+                             NameValuePair[] meta_list) throws IOException, MyException {
+    String[] parts = new String[2];
+    this.errno = this.split_file_id(master_file_id, parts);
+    if (this.errno != 0) {
+      return null;
+    }
+
+    parts = this.upload_file(parts[0], parts[1], prefix_name, file_buff,
+      offset, length, file_ext_name, meta_list);
+    if (parts != null) {
+      return parts[0] + SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR + parts[1];
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * upload file to storage server (by callback)
+   *
+   * @param master_file_id the master file id to generate the slave file
+   * @param prefix_name    the prefix name to generate the slave file
+   * @param file_size      the file size
+   * @param callback       the write data callback object
+   * @param file_ext_name  file ext name, do not include dot(.)
+   * @param meta_list      meta info array
+   * @return file id(including group name and filename) if success, <br>
+   * return null if fail
+   */
+  public String upload_file1(String master_file_id, String prefix_name, long file_size,
+                             UploadCallback callback, String file_ext_name,
+                             NameValuePair[] meta_list) throws IOException, MyException {
+    String[] parts = new String[2];
+    this.errno = this.split_file_id(master_file_id, parts);
+    if (this.errno != 0) {
+      return null;
+    }
+
+    parts = this.upload_file(parts[0], parts[1], prefix_name, file_size, callback, file_ext_name, meta_list);
+    if (parts != null) {
+      return parts[0] + SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR + parts[1];
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * append file to storage server (by file name)
+   *
+   * @param appender_file_id the appender file id
+   * @param local_filename   local filename to append
+   * @return 0 for success, != 0 for error (error no)
+   */
+  public int append_file1(String appender_file_id, String local_filename) throws IOException, MyException {
+    String[] parts = new String[2];
+    this.errno = this.split_file_id(appender_file_id, parts);
+    if (this.errno != 0) {
+      return this.errno;
+    }
+
+    return this.append_file(parts[0], parts[1], local_filename);
+  }
+
+  /**
+   * append file to storage server (by file buff)
+   *
+   * @param appender_file_id the appender file id
+   * @param file_buff        file content/buff
+   * @return 0 for success, != 0 for error (error no)
+   */
+  public int append_file1(String appender_file_id, byte[] file_buff) throws IOException, MyException {
+    String[] parts = new String[2];
+    this.errno = this.split_file_id(appender_file_id, parts);
+    if (this.errno != 0) {
+      return this.errno;
+    }
+
+    return this.append_file(parts[0], parts[1], file_buff);
+  }
+
+  /**
+   * append file to storage server (by file buff)
+   *
+   * @param appender_file_id the appender file id
+   * @param file_buff        file content/buffer
+   * @param offset           start offset of the buffer
+   * @param length           the length of the buffer to append
+   * @return 0 for success, != 0 for error (error no)
+   */
+  public int append_file1(String appender_file_id, byte[] file_buff, int offset, int length) throws IOException, MyException {
+    String[] parts = new String[2];
+    this.errno = this.split_file_id(appender_file_id, parts);
+    if (this.errno != 0) {
+      return this.errno;
+    }
+
+    return this.append_file(parts[0], parts[1], file_buff, offset, length);
+  }
+
+  /**
+   * append file to storage server (by callback)
+   *
+   * @param appender_file_id the appender file id
+   * @param file_size        the file size
+   * @param callback         the write data callback object
+   * @return 0 for success, != 0 for error (error no)
+   */
+  public int append_file1(String appender_file_id, long file_size, UploadCallback callback) throws IOException, MyException {
+    String[] parts = new String[2];
+    this.errno = this.split_file_id(appender_file_id, parts);
+    if (this.errno != 0) {
+      return this.errno;
+    }
+
+    return this.append_file(parts[0], parts[1], file_size, callback);
+  }
+
+  /**
+   * modify appender file to storage server (by file name)
+   *
+   * @param appender_file_id the appender file id
+   * @param file_offset      the offset of appender file
+   * @param local_filename   local filename to append
+   * @return 0 for success, != 0 for error (error no)
+   */
+  public int modify_file1(String appender_file_id,
+                          long file_offset, String local_filename) throws IOException, MyException {
+    String[] parts = new String[2];
+    this.errno = this.split_file_id(appender_file_id, parts);
+    if (this.errno != 0) {
+      return this.errno;
+    }
+
+    return this.modify_file(parts[0], parts[1], file_offset, local_filename);
+  }
+
+  /**
+   * modify appender file to storage server (by file buff)
+   *
+   * @param appender_file_id the appender file id
+   * @param file_offset      the offset of appender file
+   * @param file_buff        file content/buff
+   * @return 0 for success, != 0 for error (error no)
+   */
+  public int modify_file1(String appender_file_id,
+                          long file_offset, byte[] file_buff) throws IOException, MyException {
+    String[] parts = new String[2];
+    this.errno = this.split_file_id(appender_file_id, parts);
+    if (this.errno != 0) {
+      return this.errno;
+    }
+
+    return this.modify_file(parts[0], parts[1], file_offset, file_buff);
+  }
+
+  /**
+   * modify appender file to storage server (by file buff)
+   *
+   * @param appender_file_id the appender file id
+   * @param file_offset      the offset of appender file
+   * @param file_buff        file content/buff
+   * @param buffer_offset    start offset of the buff
+   * @param buffer_length    the length of buff to modify
+   * @return 0 for success, != 0 for error (error no)
+   */
+  public int modify_file1(String appender_file_id,
+                          long file_offset, byte[] file_buff, int buffer_offset, int buffer_length) throws IOException, MyException {
+    String[] parts = new String[2];
+    this.errno = this.split_file_id(appender_file_id, parts);
+    if (this.errno != 0) {
+      return this.errno;
+    }
+
+    return this.modify_file(parts[0], parts[1], file_offset,
+      file_buff, buffer_offset, buffer_length);
+  }
+
+  /**
+   * modify appender file to storage server (by callback)
+   *
+   * @param appender_file_id the appender file id
+   * @param file_offset      the offset of appender file
+   * @param modify_size      the modify size
+   * @param callback         the write data callback object
+   * @return 0 for success, != 0 for error (error no)
+   */
+  public int modify_file1(String appender_file_id,
+                          long file_offset, long modify_size, UploadCallback callback) throws IOException, MyException {
+    String[] parts = new String[2];
+    this.errno = this.split_file_id(appender_file_id, parts);
+    if (this.errno != 0) {
+      return this.errno;
+    }
+
+    return this.modify_file(parts[0], parts[1], file_offset, modify_size, callback);
+  }
+
+  /**
+   * delete file from storage server
+   *
+   * @param file_id the file id(including group name and filename)
+   * @return 0 for success, none zero for fail (error code)
+   */
+  public int delete_file1(String file_id) throws IOException, MyException {
+    String[] parts = new String[2];
+    this.errno = this.split_file_id(file_id, parts);
+    if (this.errno != 0) {
+      return this.errno;
+    }
+
+    return this.delete_file(parts[0], parts[1]);
+  }
+
+  /**
+   * truncate appender file to size 0 from storage server
+   *
+   * @param appender_file_id the appender file id
+   * @return 0 for success, none zero for fail (error code)
+   */
+  public int truncate_file1(String appender_file_id) throws IOException, MyException {
+    String[] parts = new String[2];
+    this.errno = this.split_file_id(appender_file_id, parts);
+    if (this.errno != 0) {
+      return this.errno;
+    }
+
+    return this.truncate_file(parts[0], parts[1]);
+  }
+
+  /**
+   * truncate appender file from storage server
+   *
+   * @param appender_file_id    the appender file id
+   * @param truncated_file_size truncated file size
+   * @return 0 for success, none zero for fail (error code)
+   */
+  public int truncate_file1(String appender_file_id, long truncated_file_size) throws IOException, MyException {
+    String[] parts = new String[2];
+    this.errno = this.split_file_id(appender_file_id, parts);
+    if (this.errno != 0) {
+      return this.errno;
+    }
+
+    return this.truncate_file(parts[0], parts[1], truncated_file_size);
+  }
+
+  /**
+   * download file from storage server
+   *
+   * @param file_id the file id(including group name and filename)
+   * @return file content/buffer, return null if fail
+   */
+  public byte[] download_file1(String file_id) throws IOException, MyException {
+    final long file_offset = 0;
+    final long download_bytes = 0;
+
+    return this.download_file1(file_id, file_offset, download_bytes);
+  }
+
+  /**
+   * download file from storage server
+   *
+   * @param file_id        the file id(including group name and filename)
+   * @param file_offset    the start offset of the file
+   * @param download_bytes download bytes, 0 for remain bytes from offset
+   * @return file content/buff, return null if fail
+   */
+  public byte[] download_file1(String file_id, long file_offset, long download_bytes) throws IOException, MyException {
+    String[] parts = new String[2];
+    this.errno = this.split_file_id(file_id, parts);
+    if (this.errno != 0) {
+      return null;
+    }
+
+    return this.download_file(parts[0], parts[1], file_offset, download_bytes);
+  }
+
+  /**
+   * download file from storage server
+   *
+   * @param file_id        the file id(including group name and filename)
+   * @param local_filename the filename on local
+   * @return 0 success, return none zero errno if fail
+   */
+  public int download_file1(String file_id, String local_filename) throws IOException, MyException {
+    final long file_offset = 0;
+    final long download_bytes = 0;
+
+    return this.download_file1(file_id, file_offset, download_bytes, local_filename);
+  }
+
+  /**
+   * download file from storage server
+   *
+   * @param file_id        the file id(including group name and filename)
+   * @param file_offset    the start offset of the file
+   * @param download_bytes download bytes, 0 for remain bytes from offset
+   * @param local_filename the filename on local
+   * @return 0 success, return none zero errno if fail
+   */
+  public int download_file1(String file_id, long file_offset, long download_bytes, String local_filename) throws IOException, MyException {
+    String[] parts = new String[2];
+    this.errno = this.split_file_id(file_id, parts);
+    if (this.errno != 0) {
+      return this.errno;
+    }
+
+    return this.download_file(parts[0], parts[1], file_offset, download_bytes, local_filename);
+  }
+
+  /**
+   * download file from storage server
+   *
+   * @param file_id  the file id(including group name and filename)
+   * @param callback the callback object, will call callback.recv() when data arrive
+   * @return 0 success, return none zero errno if fail
+   */
+  public int download_file1(String file_id, DownloadCallback callback) throws IOException, MyException {
+    final long file_offset = 0;
+    final long download_bytes = 0;
+
+    return this.download_file1(file_id, file_offset, download_bytes, callback);
+  }
+
+  /**
+   * download file from storage server
+   *
+   * @param file_id        the file id(including group name and filename)
+   * @param file_offset    the start offset of the file
+   * @param download_bytes download bytes, 0 for remain bytes from offset
+   * @param callback       the callback object, will call callback.recv() when data arrive
+   * @return 0 success, return none zero errno if fail
+   */
+  public int download_file1(String file_id, long file_offset, long download_bytes, DownloadCallback callback) throws IOException, MyException {
+    String[] parts = new String[2];
+    this.errno = this.split_file_id(file_id, parts);
+    if (this.errno != 0) {
+      return this.errno;
+    }
+
+    return this.download_file(parts[0], parts[1], file_offset, download_bytes, callback);
+  }
+
+  /**
+   * get all metadata items from storage server
+   *
+   * @param file_id the file id(including group name and filename)
+   * @return meta info array, return null if fail
+   */
+  public NameValuePair[] get_metadata1(String file_id) throws IOException, MyException {
+    String[] parts = new String[2];
+    this.errno = this.split_file_id(file_id, parts);
+    if (this.errno != 0) {
+      return null;
+    }
+
+    return this.get_metadata(parts[0], parts[1]);
+  }
+
+  /**
+   * set metadata items to storage server
+   *
+   * @param file_id   the file id(including group name and filename)
+   * @param meta_list meta item array
+   * @param op_flag   flag, can be one of following values: <br>
+   *                  <ul><li> ProtoCommon.STORAGE_SET_METADATA_FLAG_OVERWRITE: overwrite all old
+   *                  metadata items</li></ul>
+   *                  <ul><li> ProtoCommon.STORAGE_SET_METADATA_FLAG_MERGE: merge, insert when
+   *                  the metadata item not exist, otherwise update it</li></ul>
+   * @return 0 for success, !=0 fail (error code)
+   */
+  public int set_metadata1(String file_id, NameValuePair[] meta_list, byte op_flag) throws IOException, MyException {
+    String[] parts = new String[2];
+    this.errno = this.split_file_id(file_id, parts);
+    if (this.errno != 0) {
+      return this.errno;
+    }
+
+    return this.set_metadata(parts[0], parts[1], meta_list, op_flag);
+  }
+
+  /**
+   * get file info from storage server
+   *
+   * @param file_id the file id(including group name and filename)
+   * @return FileInfo object for success, return null for fail
+   */
+  public FileInfo query_file_info1(String file_id) throws IOException, MyException {
+    String[] parts = new String[2];
+    this.errno = this.split_file_id(file_id, parts);
+    if (this.errno != 0) {
+      return null;
+    }
+
+    return this.query_file_info(parts[0], parts[1]);
+  }
+
+  /**
+   * get file info decoded from filename
+   *
+   * @param file_id the file id(including group name and filename)
+   * @return FileInfo object for success, return null for fail
+   */
+  public FileInfo get_file_info1(String file_id) throws IOException, MyException {
+    String[] parts = new String[2];
+    this.errno = this.split_file_id(file_id, parts);
+    if (this.errno != 0) {
+      return null;
+    }
+
+    return this.get_file_info(parts[0], parts[1]);
+  }
+}

+ 57 - 0
kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/StorageServer.java

@@ -0,0 +1,57 @@
+/**
+ * Copyright (C) 2008 Happy Fish / YuQing
+ * <p>
+ * FastDFS Java Client may be copied only under the terms of the GNU Lesser
+ * General Public License (LGPL).
+ * Please visit the FastDFS Home Page http://www.csource.org/ for more detail.
+ */
+
+package com.kmall.common.fileserver.fastdfs;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+
+/**
+ * Storage Server Info
+ *
+ * @author Happy Fish / YuQing
+ * @version Version 1.11
+ */
+public class StorageServer extends TrackerServer {
+  protected int store_path_index = 0;
+
+  /**
+   * Constructor
+   *
+   * @param ip_addr    the ip address of storage server
+   * @param port       the port of storage server
+   * @param store_path the store path index on the storage server
+   */
+  public StorageServer(String ip_addr, int port, int store_path) throws IOException {
+    super(ClientGlobal.getSocket(ip_addr, port), new InetSocketAddress(ip_addr, port));
+    this.store_path_index = store_path;
+  }
+
+  /**
+   * Constructor
+   *
+   * @param ip_addr    the ip address of storage server
+   * @param port       the port of storage server
+   * @param store_path the store path index on the storage server
+   */
+  public StorageServer(String ip_addr, int port, byte store_path) throws IOException {
+    super(ClientGlobal.getSocket(ip_addr, port), new InetSocketAddress(ip_addr, port));
+    if (store_path < 0) {
+      this.store_path_index = 256 + store_path;
+    } else {
+      this.store_path_index = store_path;
+    }
+  }
+
+  /**
+   * @return the store path index on the storage server
+   */
+  public int getStorePathIndex() {
+    return this.store_path_index;
+  }
+}

+ 73 - 0
kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/StructBase.java

@@ -0,0 +1,73 @@
+/**
+ * Copyright (C) 2008 Happy Fish / YuQing
+ * <p>
+ * FastDFS Java Client may be copied only under the terms of the GNU Lesser
+ * General Public License (LGPL).
+ * Please visit the FastDFS Home Page http://www.csource.org/ for more detail.
+ */
+
+package com.kmall.common.fileserver.fastdfs;
+
+import java.io.UnsupportedEncodingException;
+import java.util.Date;
+
+/**
+ * C struct body decoder
+ *
+ * @author Happy Fish / YuQing
+ * @version Version 1.17
+ */
+public abstract class StructBase {
+  /**
+   * set fields
+   *
+   * @param bs     byte array
+   * @param offset start offset
+   */
+  public abstract void setFields(byte[] bs, int offset);
+
+  protected String stringValue(byte[] bs, int offset, FieldInfo filedInfo) {
+    try {
+      return (new String(bs, offset + filedInfo.offset, filedInfo.size, ClientGlobal.g_charset)).trim();
+    } catch (UnsupportedEncodingException ex) {
+      ex.printStackTrace();
+      return null;
+    }
+  }
+
+  protected long longValue(byte[] bs, int offset, FieldInfo filedInfo) {
+    return ProtoCommon.buff2long(bs, offset + filedInfo.offset);
+  }
+
+  protected int intValue(byte[] bs, int offset, FieldInfo filedInfo) {
+    return (int) ProtoCommon.buff2long(bs, offset + filedInfo.offset);
+  }
+
+  protected int int32Value(byte[] bs, int offset, FieldInfo filedInfo) {
+    return ProtoCommon.buff2int(bs, offset + filedInfo.offset);
+  }
+
+  protected byte byteValue(byte[] bs, int offset, FieldInfo filedInfo) {
+    return bs[offset + filedInfo.offset];
+  }
+
+  protected boolean booleanValue(byte[] bs, int offset, FieldInfo filedInfo) {
+    return bs[offset + filedInfo.offset] != 0;
+  }
+
+  protected Date dateValue(byte[] bs, int offset, FieldInfo filedInfo) {
+    return new Date(ProtoCommon.buff2long(bs, offset + filedInfo.offset) * 1000);
+  }
+
+  protected static class FieldInfo {
+    protected String name;
+    protected int offset;
+    protected int size;
+
+    public FieldInfo(String name, int offset, int size) {
+      this.name = name;
+      this.offset = offset;
+      this.size = size;
+    }
+  }
+}

+ 225 - 0
kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/StructGroupStat.java

@@ -0,0 +1,225 @@
+/**
+ * Copyright (C) 2008 Happy Fish / YuQing
+ * <p>
+ * FastDFS Java Client may be copied only under the terms of the GNU Lesser
+ * General Public License (LGPL).
+ * Please visit the FastDFS Home Page http://www.csource.org/ for more detail.
+ */
+
+package com.kmall.common.fileserver.fastdfs;
+
+/**
+ * C struct body decoder
+ *
+ * @author Happy Fish / YuQing
+ * @version Version 1.18
+ */
+public class StructGroupStat extends StructBase {
+  protected static final int FIELD_INDEX_GROUP_NAME = 0;
+  protected static final int FIELD_INDEX_TOTAL_MB = 1;
+  protected static final int FIELD_INDEX_FREE_MB = 2;
+  protected static final int FIELD_INDEX_TRUNK_FREE_MB = 3;
+  protected static final int FIELD_INDEX_STORAGE_COUNT = 4;
+  protected static final int FIELD_INDEX_STORAGE_PORT = 5;
+  protected static final int FIELD_INDEX_STORAGE_HTTP_PORT = 6;
+  protected static final int FIELD_INDEX_ACTIVE_COUNT = 7;
+  protected static final int FIELD_INDEX_CURRENT_WRITE_SERVER = 8;
+  protected static final int FIELD_INDEX_STORE_PATH_COUNT = 9;
+  protected static final int FIELD_INDEX_SUBDIR_COUNT_PER_PATH = 10;
+  protected static final int FIELD_INDEX_CURRENT_TRUNK_FILE_ID = 11;
+
+  protected static int fieldsTotalSize;
+  protected static StructBase.FieldInfo[] fieldsArray = new StructBase.FieldInfo[12];
+
+  static {
+    int offset = 0;
+    fieldsArray[FIELD_INDEX_GROUP_NAME] = new StructBase.FieldInfo("groupName", offset, ProtoCommon.FDFS_GROUP_NAME_MAX_LEN + 1);
+    offset += ProtoCommon.FDFS_GROUP_NAME_MAX_LEN + 1;
+
+    fieldsArray[FIELD_INDEX_TOTAL_MB] = new StructBase.FieldInfo("totalMB", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_FREE_MB] = new StructBase.FieldInfo("freeMB", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_TRUNK_FREE_MB] = new StructBase.FieldInfo("trunkFreeMB", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_STORAGE_COUNT] = new StructBase.FieldInfo("storageCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_STORAGE_PORT] = new StructBase.FieldInfo("storagePort", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_STORAGE_HTTP_PORT] = new StructBase.FieldInfo("storageHttpPort", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_ACTIVE_COUNT] = new StructBase.FieldInfo("activeCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_CURRENT_WRITE_SERVER] = new StructBase.FieldInfo("currentWriteServer", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_STORE_PATH_COUNT] = new StructBase.FieldInfo("storePathCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_SUBDIR_COUNT_PER_PATH] = new StructBase.FieldInfo("subdirCountPerPath", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_CURRENT_TRUNK_FILE_ID] = new StructBase.FieldInfo("currentTrunkFileId", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsTotalSize = offset;
+  }
+
+  protected String groupName;  //name of this group
+  protected long totalMB;      //total disk storage in MB
+  protected long freeMB;       //free disk space in MB
+  protected long trunkFreeMB;  //trunk free space in MB
+  protected int storageCount;  //storage server count
+  protected int storagePort;   //storage server port
+  protected int storageHttpPort; //storage server HTTP port
+  protected int activeCount;     //active storage server count
+  protected int currentWriteServer; //current storage server index to upload file
+  protected int storePathCount;     //store base path count of each storage server
+  protected int subdirCountPerPath; //sub dir count per store path
+  protected int currentTrunkFileId; //current trunk file id
+
+  /**
+   * get fields total size
+   *
+   * @return fields total size
+   */
+  public static int getFieldsTotalSize() {
+    return fieldsTotalSize;
+  }
+
+  /**
+   * get group name
+   *
+   * @return group name
+   */
+  public String getGroupName() {
+    return this.groupName;
+  }
+
+  /**
+   * get total disk space in MB
+   *
+   * @return total disk space in MB
+   */
+  public long getTotalMB() {
+    return this.totalMB;
+  }
+
+  /**
+   * get free disk space in MB
+   *
+   * @return free disk space in MB
+   */
+  public long getFreeMB() {
+    return this.freeMB;
+  }
+
+  /**
+   * get trunk free space in MB
+   *
+   * @return trunk free space in MB
+   */
+  public long getTrunkFreeMB() {
+    return this.trunkFreeMB;
+  }
+
+  /**
+   * get storage server count in this group
+   *
+   * @return storage server count in this group
+   */
+  public int getStorageCount() {
+    return this.storageCount;
+  }
+
+  /**
+   * get active storage server count in this group
+   *
+   * @return active storage server count in this group
+   */
+  public int getActiveCount() {
+    return this.activeCount;
+  }
+
+  /**
+   * get storage server port
+   *
+   * @return storage server port
+   */
+  public int getStoragePort() {
+    return this.storagePort;
+  }
+
+  /**
+   * get storage server HTTP port
+   *
+   * @return storage server HTTP port
+   */
+  public int getStorageHttpPort() {
+    return this.storageHttpPort;
+  }
+
+  /**
+   * get current storage server index to upload file
+   *
+   * @return current storage server index to upload file
+   */
+  public int getCurrentWriteServer() {
+    return this.currentWriteServer;
+  }
+
+  /**
+   * get store base path count of each storage server
+   *
+   * @return store base path count of each storage server
+   */
+  public int getStorePathCount() {
+    return this.storePathCount;
+  }
+
+  /**
+   * get sub dir count per store path
+   *
+   * @return sub dir count per store path
+   */
+  public int getSubdirCountPerPath() {
+    return this.subdirCountPerPath;
+  }
+
+  /**
+   * get current trunk file id
+   *
+   * @return current trunk file id
+   */
+  public int getCurrentTrunkFileId() {
+    return this.currentTrunkFileId;
+  }
+
+  /**
+   * set fields
+   *
+   * @param bs     byte array
+   * @param offset start offset
+   */
+  public void setFields(byte[] bs, int offset) {
+    this.groupName = stringValue(bs, offset, fieldsArray[FIELD_INDEX_GROUP_NAME]);
+    this.totalMB = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_MB]);
+    this.freeMB = longValue(bs, offset, fieldsArray[FIELD_INDEX_FREE_MB]);
+    this.trunkFreeMB = longValue(bs, offset, fieldsArray[FIELD_INDEX_TRUNK_FREE_MB]);
+    this.storageCount = intValue(bs, offset, fieldsArray[FIELD_INDEX_STORAGE_COUNT]);
+    this.storagePort = intValue(bs, offset, fieldsArray[FIELD_INDEX_STORAGE_PORT]);
+    this.storageHttpPort = intValue(bs, offset, fieldsArray[FIELD_INDEX_STORAGE_HTTP_PORT]);
+    this.activeCount = intValue(bs, offset, fieldsArray[FIELD_INDEX_ACTIVE_COUNT]);
+    this.currentWriteServer = intValue(bs, offset, fieldsArray[FIELD_INDEX_CURRENT_WRITE_SERVER]);
+    this.storePathCount = intValue(bs, offset, fieldsArray[FIELD_INDEX_STORE_PATH_COUNT]);
+    this.subdirCountPerPath = intValue(bs, offset, fieldsArray[FIELD_INDEX_SUBDIR_COUNT_PER_PATH]);
+    this.currentTrunkFileId = intValue(bs, offset, fieldsArray[FIELD_INDEX_CURRENT_TRUNK_FILE_ID]);
+  }
+}

+ 982 - 0
kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/StructStorageStat.java

@@ -0,0 +1,982 @@
+/**
+ * Copyright (C) 2008 Happy Fish / YuQing
+ * <p>
+ * FastDFS Java Client may be copied only under the terms of the GNU Lesser
+ * General Public License (LGPL).
+ * Please visit the FastDFS Home Page http://www.csource.org/ for more detail.
+ */
+
+package com.kmall.common.fileserver.fastdfs;
+
+import java.util.Date;
+
+/**
+ * C struct body decoder
+ *
+ * @author Happy Fish / YuQing
+ * @version Version 1.25
+ */
+public class StructStorageStat extends StructBase {
+  protected static final int FIELD_INDEX_STATUS = 0;
+  protected static final int FIELD_INDEX_ID = 1;
+  protected static final int FIELD_INDEX_IP_ADDR = 2;
+  protected static final int FIELD_INDEX_DOMAIN_NAME = 3;
+  protected static final int FIELD_INDEX_SRC_IP_ADDR = 4;
+  protected static final int FIELD_INDEX_VERSION = 5;
+  protected static final int FIELD_INDEX_JOIN_TIME = 6;
+  protected static final int FIELD_INDEX_UP_TIME = 7;
+  protected static final int FIELD_INDEX_TOTAL_MB = 8;
+  protected static final int FIELD_INDEX_FREE_MB = 9;
+  protected static final int FIELD_INDEX_UPLOAD_PRIORITY = 10;
+  protected static final int FIELD_INDEX_STORE_PATH_COUNT = 11;
+  protected static final int FIELD_INDEX_SUBDIR_COUNT_PER_PATH = 12;
+  protected static final int FIELD_INDEX_CURRENT_WRITE_PATH = 13;
+  protected static final int FIELD_INDEX_STORAGE_PORT = 14;
+  protected static final int FIELD_INDEX_STORAGE_HTTP_PORT = 15;
+
+  protected static final int FIELD_INDEX_CONNECTION_ALLOC_COUNT = 16;
+  protected static final int FIELD_INDEX_CONNECTION_CURRENT_COUNT = 17;
+  protected static final int FIELD_INDEX_CONNECTION_MAX_COUNT = 18;
+
+  protected static final int FIELD_INDEX_TOTAL_UPLOAD_COUNT = 19;
+  protected static final int FIELD_INDEX_SUCCESS_UPLOAD_COUNT = 20;
+  protected static final int FIELD_INDEX_TOTAL_APPEND_COUNT = 21;
+  protected static final int FIELD_INDEX_SUCCESS_APPEND_COUNT = 22;
+  protected static final int FIELD_INDEX_TOTAL_MODIFY_COUNT = 23;
+  protected static final int FIELD_INDEX_SUCCESS_MODIFY_COUNT = 24;
+  protected static final int FIELD_INDEX_TOTAL_TRUNCATE_COUNT = 25;
+  protected static final int FIELD_INDEX_SUCCESS_TRUNCATE_COUNT = 26;
+  protected static final int FIELD_INDEX_TOTAL_SET_META_COUNT = 27;
+  protected static final int FIELD_INDEX_SUCCESS_SET_META_COUNT = 28;
+  protected static final int FIELD_INDEX_TOTAL_DELETE_COUNT = 29;
+  protected static final int FIELD_INDEX_SUCCESS_DELETE_COUNT = 30;
+  protected static final int FIELD_INDEX_TOTAL_DOWNLOAD_COUNT = 31;
+  protected static final int FIELD_INDEX_SUCCESS_DOWNLOAD_COUNT = 32;
+  protected static final int FIELD_INDEX_TOTAL_GET_META_COUNT = 33;
+  protected static final int FIELD_INDEX_SUCCESS_GET_META_COUNT = 34;
+  protected static final int FIELD_INDEX_TOTAL_CREATE_LINK_COUNT = 35;
+  protected static final int FIELD_INDEX_SUCCESS_CREATE_LINK_COUNT = 36;
+  protected static final int FIELD_INDEX_TOTAL_DELETE_LINK_COUNT = 37;
+  protected static final int FIELD_INDEX_SUCCESS_DELETE_LINK_COUNT = 38;
+  protected static final int FIELD_INDEX_TOTAL_UPLOAD_BYTES = 39;
+  protected static final int FIELD_INDEX_SUCCESS_UPLOAD_BYTES = 40;
+  protected static final int FIELD_INDEX_TOTAL_APPEND_BYTES = 41;
+  protected static final int FIELD_INDEX_SUCCESS_APPEND_BYTES = 42;
+  protected static final int FIELD_INDEX_TOTAL_MODIFY_BYTES = 43;
+  protected static final int FIELD_INDEX_SUCCESS_MODIFY_BYTES = 44;
+  protected static final int FIELD_INDEX_TOTAL_DOWNLOAD_BYTES = 45;
+  protected static final int FIELD_INDEX_SUCCESS_DOWNLOAD_BYTES = 46;
+  protected static final int FIELD_INDEX_TOTAL_SYNC_IN_BYTES = 47;
+  protected static final int FIELD_INDEX_SUCCESS_SYNC_IN_BYTES = 48;
+  protected static final int FIELD_INDEX_TOTAL_SYNC_OUT_BYTES = 49;
+  protected static final int FIELD_INDEX_SUCCESS_SYNC_OUT_BYTES = 50;
+  protected static final int FIELD_INDEX_TOTAL_FILE_OPEN_COUNT = 51;
+  protected static final int FIELD_INDEX_SUCCESS_FILE_OPEN_COUNT = 52;
+  protected static final int FIELD_INDEX_TOTAL_FILE_READ_COUNT = 53;
+  protected static final int FIELD_INDEX_SUCCESS_FILE_READ_COUNT = 54;
+  protected static final int FIELD_INDEX_TOTAL_FILE_WRITE_COUNT = 55;
+  protected static final int FIELD_INDEX_SUCCESS_FILE_WRITE_COUNT = 56;
+  protected static final int FIELD_INDEX_LAST_SOURCE_UPDATE = 57;
+  protected static final int FIELD_INDEX_LAST_SYNC_UPDATE = 58;
+  protected static final int FIELD_INDEX_LAST_SYNCED_TIMESTAMP = 59;
+  protected static final int FIELD_INDEX_LAST_HEART_BEAT_TIME = 60;
+  protected static final int FIELD_INDEX_IF_TRUNK_FILE = 61;
+
+  protected static int fieldsTotalSize;
+  protected static StructBase.FieldInfo[] fieldsArray = new StructBase.FieldInfo[62];
+
+  static {
+    int offset = 0;
+
+    fieldsArray[FIELD_INDEX_STATUS] = new StructBase.FieldInfo("status", offset, 1);
+    offset += 1;
+
+    fieldsArray[FIELD_INDEX_ID] = new StructBase.FieldInfo("id", offset, ProtoCommon.FDFS_STORAGE_ID_MAX_SIZE);
+    offset += ProtoCommon.FDFS_STORAGE_ID_MAX_SIZE;
+
+    fieldsArray[FIELD_INDEX_IP_ADDR] = new StructBase.FieldInfo("ipAddr", offset, ProtoCommon.FDFS_IPADDR_SIZE);
+    offset += ProtoCommon.FDFS_IPADDR_SIZE;
+
+    fieldsArray[FIELD_INDEX_DOMAIN_NAME] = new StructBase.FieldInfo("domainName", offset, ProtoCommon.FDFS_DOMAIN_NAME_MAX_SIZE);
+    offset += ProtoCommon.FDFS_DOMAIN_NAME_MAX_SIZE;
+
+    fieldsArray[FIELD_INDEX_SRC_IP_ADDR] = new StructBase.FieldInfo("srcIpAddr", offset, ProtoCommon.FDFS_IPADDR_SIZE);
+    offset += ProtoCommon.FDFS_IPADDR_SIZE;
+
+    fieldsArray[FIELD_INDEX_VERSION] = new StructBase.FieldInfo("version", offset, ProtoCommon.FDFS_VERSION_SIZE);
+    offset += ProtoCommon.FDFS_VERSION_SIZE;
+
+    fieldsArray[FIELD_INDEX_JOIN_TIME] = new StructBase.FieldInfo("joinTime", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_UP_TIME] = new StructBase.FieldInfo("upTime", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_TOTAL_MB] = new StructBase.FieldInfo("totalMB", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_FREE_MB] = new StructBase.FieldInfo("freeMB", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_UPLOAD_PRIORITY] = new StructBase.FieldInfo("uploadPriority", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_STORE_PATH_COUNT] = new StructBase.FieldInfo("storePathCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_SUBDIR_COUNT_PER_PATH] = new StructBase.FieldInfo("subdirCountPerPath", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_CURRENT_WRITE_PATH] = new StructBase.FieldInfo("currentWritePath", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_STORAGE_PORT] = new StructBase.FieldInfo("storagePort", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_STORAGE_HTTP_PORT] = new StructBase.FieldInfo("storageHttpPort", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_CONNECTION_ALLOC_COUNT] = new StructBase.FieldInfo("connectionAllocCount", offset, 4);
+    offset += 4;
+
+    fieldsArray[FIELD_INDEX_CONNECTION_CURRENT_COUNT] = new StructBase.FieldInfo("connectionCurrentCount", offset, 4);
+    offset += 4;
+
+    fieldsArray[FIELD_INDEX_CONNECTION_MAX_COUNT] = new StructBase.FieldInfo("connectionMaxCount", offset, 4);
+    offset += 4;
+
+    fieldsArray[FIELD_INDEX_TOTAL_UPLOAD_COUNT] = new StructBase.FieldInfo("totalUploadCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_SUCCESS_UPLOAD_COUNT] = new StructBase.FieldInfo("successUploadCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_TOTAL_APPEND_COUNT] = new StructBase.FieldInfo("totalAppendCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_SUCCESS_APPEND_COUNT] = new StructBase.FieldInfo("successAppendCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_TOTAL_MODIFY_COUNT] = new StructBase.FieldInfo("totalModifyCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_SUCCESS_MODIFY_COUNT] = new StructBase.FieldInfo("successModifyCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_TOTAL_TRUNCATE_COUNT] = new StructBase.FieldInfo("totalTruncateCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_SUCCESS_TRUNCATE_COUNT] = new StructBase.FieldInfo("successTruncateCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_TOTAL_SET_META_COUNT] = new StructBase.FieldInfo("totalSetMetaCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_SUCCESS_SET_META_COUNT] = new StructBase.FieldInfo("successSetMetaCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_TOTAL_DELETE_COUNT] = new StructBase.FieldInfo("totalDeleteCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_SUCCESS_DELETE_COUNT] = new StructBase.FieldInfo("successDeleteCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_TOTAL_DOWNLOAD_COUNT] = new StructBase.FieldInfo("totalDownloadCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_SUCCESS_DOWNLOAD_COUNT] = new StructBase.FieldInfo("successDownloadCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_TOTAL_GET_META_COUNT] = new StructBase.FieldInfo("totalGetMetaCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_SUCCESS_GET_META_COUNT] = new StructBase.FieldInfo("successGetMetaCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_TOTAL_CREATE_LINK_COUNT] = new StructBase.FieldInfo("totalCreateLinkCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_SUCCESS_CREATE_LINK_COUNT] = new StructBase.FieldInfo("successCreateLinkCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_TOTAL_DELETE_LINK_COUNT] = new StructBase.FieldInfo("totalDeleteLinkCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_SUCCESS_DELETE_LINK_COUNT] = new StructBase.FieldInfo("successDeleteLinkCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_TOTAL_UPLOAD_BYTES] = new StructBase.FieldInfo("totalUploadBytes", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_SUCCESS_UPLOAD_BYTES] = new StructBase.FieldInfo("successUploadBytes", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_TOTAL_APPEND_BYTES] = new StructBase.FieldInfo("totalAppendBytes", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_SUCCESS_APPEND_BYTES] = new StructBase.FieldInfo("successAppendBytes", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_TOTAL_MODIFY_BYTES] = new StructBase.FieldInfo("totalModifyBytes", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_SUCCESS_MODIFY_BYTES] = new StructBase.FieldInfo("successModifyBytes", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_TOTAL_DOWNLOAD_BYTES] = new StructBase.FieldInfo("totalDownloadloadBytes", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_SUCCESS_DOWNLOAD_BYTES] = new StructBase.FieldInfo("successDownloadloadBytes", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_TOTAL_SYNC_IN_BYTES] = new StructBase.FieldInfo("totalSyncInBytes", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_SUCCESS_SYNC_IN_BYTES] = new StructBase.FieldInfo("successSyncInBytes", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_TOTAL_SYNC_OUT_BYTES] = new StructBase.FieldInfo("totalSyncOutBytes", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_SUCCESS_SYNC_OUT_BYTES] = new StructBase.FieldInfo("successSyncOutBytes", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_TOTAL_FILE_OPEN_COUNT] = new StructBase.FieldInfo("totalFileOpenCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_SUCCESS_FILE_OPEN_COUNT] = new StructBase.FieldInfo("successFileOpenCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_TOTAL_FILE_READ_COUNT] = new StructBase.FieldInfo("totalFileReadCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_SUCCESS_FILE_READ_COUNT] = new StructBase.FieldInfo("successFileReadCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_TOTAL_FILE_WRITE_COUNT] = new StructBase.FieldInfo("totalFileWriteCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_SUCCESS_FILE_WRITE_COUNT] = new StructBase.FieldInfo("successFileWriteCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_LAST_SOURCE_UPDATE] = new StructBase.FieldInfo("lastSourceUpdate", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_LAST_SYNC_UPDATE] = new StructBase.FieldInfo("lastSyncUpdate", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_LAST_SYNCED_TIMESTAMP] = new StructBase.FieldInfo("lastSyncedTimestamp", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_LAST_HEART_BEAT_TIME] = new StructBase.FieldInfo("lastHeartBeatTime", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+    offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+    fieldsArray[FIELD_INDEX_IF_TRUNK_FILE] = new StructBase.FieldInfo("ifTrunkServer", offset, 1);
+    offset += 1;
+
+    fieldsTotalSize = offset;
+  }
+
+  protected byte status;
+  protected String id;
+  protected String ipAddr;
+  protected String srcIpAddr;
+  protected String domainName; //http domain name
+  protected String version;
+  protected long totalMB; //total disk storage in MB
+  protected long freeMB;  //free disk storage in MB
+  protected int uploadPriority;  //upload priority
+  protected Date joinTime; //storage join timestamp (create timestamp)
+  protected Date upTime;   //storage service started timestamp
+  protected int storePathCount;  //store base path count of each storage server
+  protected int subdirCountPerPath;
+  protected int storagePort;
+  protected int storageHttpPort; //storage http server port
+  protected int currentWritePath; //current write path index
+  protected int connectionAllocCount;
+  protected int connectionCurrentCount;
+  protected int connectionMaxCount;
+  protected long totalUploadCount;
+  protected long successUploadCount;
+  protected long totalAppendCount;
+  protected long successAppendCount;
+  protected long totalModifyCount;
+  protected long successModifyCount;
+  protected long totalTruncateCount;
+  protected long successTruncateCount;
+  protected long totalSetMetaCount;
+  protected long successSetMetaCount;
+  protected long totalDeleteCount;
+  protected long successDeleteCount;
+  protected long totalDownloadCount;
+  protected long successDownloadCount;
+  protected long totalGetMetaCount;
+  protected long successGetMetaCount;
+  protected long totalCreateLinkCount;
+  protected long successCreateLinkCount;
+  protected long totalDeleteLinkCount;
+  protected long successDeleteLinkCount;
+  protected long totalUploadBytes;
+  protected long successUploadBytes;
+  protected long totalAppendBytes;
+  protected long successAppendBytes;
+  protected long totalModifyBytes;
+  protected long successModifyBytes;
+  protected long totalDownloadloadBytes;
+  protected long successDownloadloadBytes;
+  protected long totalSyncInBytes;
+  protected long successSyncInBytes;
+  protected long totalSyncOutBytes;
+  protected long successSyncOutBytes;
+  protected long totalFileOpenCount;
+  protected long successFileOpenCount;
+  protected long totalFileReadCount;
+  protected long successFileReadCount;
+  protected long totalFileWriteCount;
+  protected long successFileWriteCount;
+  protected Date lastSourceUpdate;
+  protected Date lastSyncUpdate;
+  protected Date lastSyncedTimestamp;
+  protected Date lastHeartBeatTime;
+  protected boolean ifTrunkServer;
+
+  /**
+   * get fields total size
+   *
+   * @return fields total size
+   */
+  public static int getFieldsTotalSize() {
+    return fieldsTotalSize;
+  }
+
+  /**
+   * get storage status
+   *
+   * @return storage status
+   */
+  public byte getStatus() {
+    return this.status;
+  }
+
+  /**
+   * get storage server id
+   *
+   * @return storage server id
+   */
+  public String getId() {
+    return this.id;
+  }
+
+  /**
+   * get storage server ip address
+   *
+   * @return storage server ip address
+   */
+  public String getIpAddr() {
+    return this.ipAddr;
+  }
+
+  /**
+   * get source storage ip address
+   *
+   * @return source storage ip address
+   */
+  public String getSrcIpAddr() {
+    return this.srcIpAddr;
+  }
+
+  /**
+   * get the domain name of the storage server
+   *
+   * @return the domain name of the storage server
+   */
+  public String getDomainName() {
+    return this.domainName;
+  }
+
+  /**
+   * get storage version
+   *
+   * @return storage version
+   */
+  public String getVersion() {
+    return this.version;
+  }
+
+  /**
+   * get total disk space in MB
+   *
+   * @return total disk space in MB
+   */
+  public long getTotalMB() {
+    return this.totalMB;
+  }
+
+  /**
+   * get free disk space in MB
+   *
+   * @return free disk space in MB
+   */
+  public long getFreeMB() {
+    return this.freeMB;
+  }
+
+  /**
+   * get storage server upload priority
+   *
+   * @return storage server upload priority
+   */
+  public int getUploadPriority() {
+    return this.uploadPriority;
+  }
+
+  /**
+   * get storage server join time
+   *
+   * @return storage server join time
+   */
+  public Date getJoinTime() {
+    return this.joinTime;
+  }
+
+  /**
+   * get storage server up time
+   *
+   * @return storage server up time
+   */
+  public Date getUpTime() {
+    return this.upTime;
+  }
+
+  /**
+   * get store base path count of each storage server
+   *
+   * @return store base path count of each storage server
+   */
+  public int getStorePathCount() {
+    return this.storePathCount;
+  }
+
+  /**
+   * get sub dir count per store path
+   *
+   * @return sub dir count per store path
+   */
+  public int getSubdirCountPerPath() {
+    return this.subdirCountPerPath;
+  }
+
+  /**
+   * get storage server port
+   *
+   * @return storage server port
+   */
+  public int getStoragePort() {
+    return this.storagePort;
+  }
+
+  /**
+   * get storage server HTTP port
+   *
+   * @return storage server HTTP port
+   */
+  public int getStorageHttpPort() {
+    return this.storageHttpPort;
+  }
+
+  /**
+   * get current write path index
+   *
+   * @return current write path index
+   */
+  public int getCurrentWritePath() {
+    return this.currentWritePath;
+  }
+
+  /**
+   * get total upload file count
+   *
+   * @return total upload file count
+   */
+  public long getTotalUploadCount() {
+    return this.totalUploadCount;
+  }
+
+  /**
+   * get success upload file count
+   *
+   * @return success upload file count
+   */
+  public long getSuccessUploadCount() {
+    return this.successUploadCount;
+  }
+
+  /**
+   * get total append count
+   *
+   * @return total append count
+   */
+  public long getTotalAppendCount() {
+    return this.totalAppendCount;
+  }
+
+  /**
+   * get success append count
+   *
+   * @return success append count
+   */
+  public long getSuccessAppendCount() {
+    return this.successAppendCount;
+  }
+
+  /**
+   * get total modify count
+   *
+   * @return total modify count
+   */
+  public long getTotalModifyCount() {
+    return this.totalModifyCount;
+  }
+
+  /**
+   * get success modify count
+   *
+   * @return success modify count
+   */
+  public long getSuccessModifyCount() {
+    return this.successModifyCount;
+  }
+
+  /**
+   * get total truncate count
+   *
+   * @return total truncate count
+   */
+  public long getTotalTruncateCount() {
+    return this.totalTruncateCount;
+  }
+
+  /**
+   * get success truncate count
+   *
+   * @return success truncate count
+   */
+  public long getSuccessTruncateCount() {
+    return this.successTruncateCount;
+  }
+
+  /**
+   * get total set meta data count
+   *
+   * @return total set meta data count
+   */
+  public long getTotalSetMetaCount() {
+    return this.totalSetMetaCount;
+  }
+
+  /**
+   * get success set meta data count
+   *
+   * @return success set meta data count
+   */
+  public long getSuccessSetMetaCount() {
+    return this.successSetMetaCount;
+  }
+
+  /**
+   * get total delete file count
+   *
+   * @return total delete file count
+   */
+  public long getTotalDeleteCount() {
+    return this.totalDeleteCount;
+  }
+
+  /**
+   * get success delete file count
+   *
+   * @return success delete file count
+   */
+  public long getSuccessDeleteCount() {
+    return this.successDeleteCount;
+  }
+
+  /**
+   * get total download file count
+   *
+   * @return total download file count
+   */
+  public long getTotalDownloadCount() {
+    return this.totalDownloadCount;
+  }
+
+  /**
+   * get success download file count
+   *
+   * @return success download file count
+   */
+  public long getSuccessDownloadCount() {
+    return this.successDownloadCount;
+  }
+
+  /**
+   * get total get metadata count
+   *
+   * @return total get metadata count
+   */
+  public long getTotalGetMetaCount() {
+    return this.totalGetMetaCount;
+  }
+
+  /**
+   * get success get metadata count
+   *
+   * @return success get metadata count
+   */
+  public long getSuccessGetMetaCount() {
+    return this.successGetMetaCount;
+  }
+
+  /**
+   * get total create linke count
+   *
+   * @return total create linke count
+   */
+  public long getTotalCreateLinkCount() {
+    return this.totalCreateLinkCount;
+  }
+
+  /**
+   * get success create linke count
+   *
+   * @return success create linke count
+   */
+  public long getSuccessCreateLinkCount() {
+    return this.successCreateLinkCount;
+  }
+
+  /**
+   * get total delete link count
+   *
+   * @return total delete link count
+   */
+  public long getTotalDeleteLinkCount() {
+    return this.totalDeleteLinkCount;
+  }
+
+  /**
+   * get success delete link count
+   *
+   * @return success delete link count
+   */
+  public long getSuccessDeleteLinkCount() {
+    return this.successDeleteLinkCount;
+  }
+
+  /**
+   * get total upload file bytes
+   *
+   * @return total upload file bytes
+   */
+  public long getTotalUploadBytes() {
+    return this.totalUploadBytes;
+  }
+
+  /**
+   * get success upload file bytes
+   *
+   * @return success upload file bytes
+   */
+  public long getSuccessUploadBytes() {
+    return this.successUploadBytes;
+  }
+
+  /**
+   * get total append bytes
+   *
+   * @return total append bytes
+   */
+  public long getTotalAppendBytes() {
+    return this.totalAppendBytes;
+  }
+
+  /**
+   * get success append bytes
+   *
+   * @return success append bytes
+   */
+  public long getSuccessAppendBytes() {
+    return this.successAppendBytes;
+  }
+
+  /**
+   * get total modify bytes
+   *
+   * @return total modify bytes
+   */
+  public long getTotalModifyBytes() {
+    return this.totalModifyBytes;
+  }
+
+  /**
+   * get success modify bytes
+   *
+   * @return success modify bytes
+   */
+  public long getSuccessModifyBytes() {
+    return this.successModifyBytes;
+  }
+
+  /**
+   * get total download file bytes
+   *
+   * @return total download file bytes
+   */
+  public long getTotalDownloadloadBytes() {
+    return this.totalDownloadloadBytes;
+  }
+
+  /**
+   * get success download file bytes
+   *
+   * @return success download file bytes
+   */
+  public long getSuccessDownloadloadBytes() {
+    return this.successDownloadloadBytes;
+  }
+
+  /**
+   * get total sync in bytes
+   *
+   * @return total sync in bytes
+   */
+  public long getTotalSyncInBytes() {
+    return this.totalSyncInBytes;
+  }
+
+  /**
+   * get success sync in bytes
+   *
+   * @return success sync in bytes
+   */
+  public long getSuccessSyncInBytes() {
+    return this.successSyncInBytes;
+  }
+
+  /**
+   * get total sync out bytes
+   *
+   * @return total sync out bytes
+   */
+  public long getTotalSyncOutBytes() {
+    return this.totalSyncOutBytes;
+  }
+
+  /**
+   * get success sync out bytes
+   *
+   * @return success sync out bytes
+   */
+  public long getSuccessSyncOutBytes() {
+    return this.successSyncOutBytes;
+  }
+
+  /**
+   * get total file opened count
+   *
+   * @return total file opened bytes
+   */
+  public long getTotalFileOpenCount() {
+    return this.totalFileOpenCount;
+  }
+
+  /**
+   * get success file opened count
+   *
+   * @return success file opened count
+   */
+  public long getSuccessFileOpenCount() {
+    return this.successFileOpenCount;
+  }
+
+  /**
+   * get total file read count
+   *
+   * @return total file read bytes
+   */
+  public long getTotalFileReadCount() {
+    return this.totalFileReadCount;
+  }
+
+  /**
+   * get success file read count
+   *
+   * @return success file read count
+   */
+  public long getSuccessFileReadCount() {
+    return this.successFileReadCount;
+  }
+
+  /**
+   * get total file write count
+   *
+   * @return total file write bytes
+   */
+  public long getTotalFileWriteCount() {
+    return this.totalFileWriteCount;
+  }
+
+  /**
+   * get success file write count
+   *
+   * @return success file write count
+   */
+  public long getSuccessFileWriteCount() {
+    return this.successFileWriteCount;
+  }
+
+  /**
+   * get last source update timestamp
+   *
+   * @return last source update timestamp
+   */
+  public Date getLastSourceUpdate() {
+    return this.lastSourceUpdate;
+  }
+
+  /**
+   * get last synced update timestamp
+   *
+   * @return last synced update timestamp
+   */
+  public Date getLastSyncUpdate() {
+    return this.lastSyncUpdate;
+  }
+
+  /**
+   * get last synced timestamp
+   *
+   * @return last synced timestamp
+   */
+  public Date getLastSyncedTimestamp() {
+    return this.lastSyncedTimestamp;
+  }
+
+  /**
+   * get last heart beat timestamp
+   *
+   * @return last heart beat timestamp
+   */
+  public Date getLastHeartBeatTime() {
+    return this.lastHeartBeatTime;
+  }
+
+  /**
+   * if the trunk server
+   *
+   * @return true for the trunk server, otherwise false
+   */
+  public boolean isTrunkServer() {
+    return this.ifTrunkServer;
+  }
+
+  /**
+   * get connection alloc count
+   *
+   * @return connection alloc count
+   */
+  public int getConnectionAllocCount() {
+    return this.connectionAllocCount;
+  }
+
+  /**
+   * get connection current count
+   *
+   * @return connection current count
+   */
+  public int getConnectionCurrentCount() {
+    return this.connectionCurrentCount;
+  }
+
+  /**
+   * get connection max count
+   *
+   * @return connection max count
+   */
+  public int getConnectionMaxCount() {
+    return this.connectionMaxCount;
+  }
+
+  /**
+   * set fields
+   *
+   * @param bs     byte array
+   * @param offset start offset
+   */
+  public void setFields(byte[] bs, int offset) {
+    this.status = byteValue(bs, offset, fieldsArray[FIELD_INDEX_STATUS]);
+    this.id = stringValue(bs, offset, fieldsArray[FIELD_INDEX_ID]);
+    this.ipAddr = stringValue(bs, offset, fieldsArray[FIELD_INDEX_IP_ADDR]);
+    this.srcIpAddr = stringValue(bs, offset, fieldsArray[FIELD_INDEX_SRC_IP_ADDR]);
+    this.domainName = stringValue(bs, offset, fieldsArray[FIELD_INDEX_DOMAIN_NAME]);
+    this.version = stringValue(bs, offset, fieldsArray[FIELD_INDEX_VERSION]);
+    this.totalMB = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_MB]);
+    this.freeMB = longValue(bs, offset, fieldsArray[FIELD_INDEX_FREE_MB]);
+    this.uploadPriority = intValue(bs, offset, fieldsArray[FIELD_INDEX_UPLOAD_PRIORITY]);
+    this.joinTime = dateValue(bs, offset, fieldsArray[FIELD_INDEX_JOIN_TIME]);
+    this.upTime = dateValue(bs, offset, fieldsArray[FIELD_INDEX_UP_TIME]);
+    this.storePathCount = intValue(bs, offset, fieldsArray[FIELD_INDEX_STORE_PATH_COUNT]);
+    this.subdirCountPerPath = intValue(bs, offset, fieldsArray[FIELD_INDEX_SUBDIR_COUNT_PER_PATH]);
+    this.storagePort = intValue(bs, offset, fieldsArray[FIELD_INDEX_STORAGE_PORT]);
+    this.storageHttpPort = intValue(bs, offset, fieldsArray[FIELD_INDEX_STORAGE_HTTP_PORT]);
+    this.currentWritePath = intValue(bs, offset, fieldsArray[FIELD_INDEX_CURRENT_WRITE_PATH]);
+
+    this.connectionAllocCount = int32Value(bs, offset, fieldsArray[FIELD_INDEX_CONNECTION_ALLOC_COUNT]);
+    this.connectionCurrentCount = int32Value(bs, offset, fieldsArray[FIELD_INDEX_CONNECTION_CURRENT_COUNT]);
+    this.connectionMaxCount = int32Value(bs, offset, fieldsArray[FIELD_INDEX_CONNECTION_MAX_COUNT]);
+
+    this.totalUploadCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_UPLOAD_COUNT]);
+    this.successUploadCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_UPLOAD_COUNT]);
+    this.totalAppendCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_APPEND_COUNT]);
+    this.successAppendCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_APPEND_COUNT]);
+    this.totalModifyCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_MODIFY_COUNT]);
+    this.successModifyCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_MODIFY_COUNT]);
+    this.totalTruncateCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_TRUNCATE_COUNT]);
+    this.successTruncateCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_TRUNCATE_COUNT]);
+    this.totalSetMetaCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_SET_META_COUNT]);
+    this.successSetMetaCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_SET_META_COUNT]);
+    this.totalDeleteCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_DELETE_COUNT]);
+    this.successDeleteCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_DELETE_COUNT]);
+    this.totalDownloadCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_DOWNLOAD_COUNT]);
+    this.successDownloadCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_DOWNLOAD_COUNT]);
+    this.totalGetMetaCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_GET_META_COUNT]);
+    this.successGetMetaCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_GET_META_COUNT]);
+    this.totalCreateLinkCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_CREATE_LINK_COUNT]);
+    this.successCreateLinkCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_CREATE_LINK_COUNT]);
+    this.totalDeleteLinkCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_DELETE_LINK_COUNT]);
+    this.successDeleteLinkCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_DELETE_LINK_COUNT]);
+    this.totalUploadBytes = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_UPLOAD_BYTES]);
+    this.successUploadBytes = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_UPLOAD_BYTES]);
+    this.totalAppendBytes = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_APPEND_BYTES]);
+    this.successAppendBytes = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_APPEND_BYTES]);
+    this.totalModifyBytes = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_MODIFY_BYTES]);
+    this.successModifyBytes = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_MODIFY_BYTES]);
+    this.totalDownloadloadBytes = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_DOWNLOAD_BYTES]);
+    this.successDownloadloadBytes = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_DOWNLOAD_BYTES]);
+    this.totalSyncInBytes = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_SYNC_IN_BYTES]);
+    this.successSyncInBytes = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_SYNC_IN_BYTES]);
+    this.totalSyncOutBytes = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_SYNC_OUT_BYTES]);
+    this.successSyncOutBytes = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_SYNC_OUT_BYTES]);
+    this.totalFileOpenCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_FILE_OPEN_COUNT]);
+    this.successFileOpenCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_FILE_OPEN_COUNT]);
+    this.totalFileReadCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_FILE_READ_COUNT]);
+    this.successFileReadCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_FILE_READ_COUNT]);
+    this.totalFileWriteCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_FILE_WRITE_COUNT]);
+    this.successFileWriteCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_FILE_WRITE_COUNT]);
+    this.lastSourceUpdate = dateValue(bs, offset, fieldsArray[FIELD_INDEX_LAST_SOURCE_UPDATE]);
+    this.lastSyncUpdate = dateValue(bs, offset, fieldsArray[FIELD_INDEX_LAST_SYNC_UPDATE]);
+    this.lastSyncedTimestamp = dateValue(bs, offset, fieldsArray[FIELD_INDEX_LAST_SYNCED_TIMESTAMP]);
+    this.lastHeartBeatTime = dateValue(bs, offset, fieldsArray[FIELD_INDEX_LAST_HEART_BEAT_TIME]);
+    this.ifTrunkServer = booleanValue(bs, offset, fieldsArray[FIELD_INDEX_IF_TRUNK_FILE]);
+  }
+}

+ 811 - 0
kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/TrackerClient.java

@@ -0,0 +1,811 @@
+/**
+ * Copyright (C) 2008 Happy Fish / YuQing
+ * <p>
+ * FastDFS Java Client may be copied only under the terms of the GNU Lesser
+ * General Public License (LGPL).
+ * Please visit the FastDFS Home Page http://www.csource.org/ for more detail.
+ */
+
+package com.kmall.common.fileserver.fastdfs;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.util.Arrays;
+
+/**
+ * Tracker client
+ *
+ * @author Happy Fish / YuQing
+ * @version Version 1.19
+ */
+public class TrackerClient {
+  protected TrackerGroup tracker_group;
+  protected byte errno;
+
+  /**
+   * constructor with global tracker group
+   */
+  public TrackerClient() {
+    this.tracker_group = ClientGlobal.g_tracker_group;
+  }
+
+  /**
+   * constructor with specified tracker group
+   *
+   * @param tracker_group the tracker group object
+   */
+  public TrackerClient(TrackerGroup tracker_group) {
+    this.tracker_group = tracker_group;
+  }
+
+  /**
+   * get the error code of last call
+   *
+   * @return the error code of last call
+   */
+  public byte getErrorCode() {
+    return this.errno;
+  }
+
+  /**
+   * get a connection to tracker server
+   *
+   * @return tracker server Socket object, return null if fail
+   */
+  public TrackerServer getConnection() throws IOException {
+    return this.tracker_group.getConnection();
+  }
+
+  /**
+   * query storage server to upload file
+   *
+   * @param trackerServer the tracker server
+   * @return storage server Socket object, return null if fail
+   */
+  public StorageServer getStoreStorage(TrackerServer trackerServer) throws IOException {
+    final String groupName = null;
+    return this.getStoreStorage(trackerServer, groupName);
+  }
+
+  /**
+   * query storage server to upload file
+   *
+   * @param trackerServer the tracker server
+   * @param groupName     the group name to upload file to, can be empty
+   * @return storage server object, return null if fail
+   */
+  public StorageServer getStoreStorage(TrackerServer trackerServer, String groupName) throws IOException {
+    byte[] header;
+    String ip_addr;
+    int port;
+    byte cmd;
+    int out_len;
+    boolean bNewConnection;
+    byte store_path;
+    Socket trackerSocket;
+
+    if (trackerServer == null) {
+      trackerServer = getConnection();
+      if (trackerServer == null) {
+        return null;
+      }
+      bNewConnection = true;
+    } else {
+      bNewConnection = false;
+    }
+
+    trackerSocket = trackerServer.getSocket();
+    OutputStream out = trackerSocket.getOutputStream();
+
+    try {
+      if (groupName == null || groupName.length() == 0) {
+        cmd = ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITHOUT_GROUP_ONE;
+        out_len = 0;
+      } else {
+        cmd = ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITH_GROUP_ONE;
+        out_len = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN;
+      }
+      header = ProtoCommon.packHeader(cmd, out_len, (byte) 0);
+      out.write(header);
+
+      if (groupName != null && groupName.length() > 0) {
+        byte[] bGroupName;
+        byte[] bs;
+        int group_len;
+
+        bs = groupName.getBytes(ClientGlobal.g_charset);
+        bGroupName = new byte[ProtoCommon.FDFS_GROUP_NAME_MAX_LEN];
+
+        if (bs.length <= ProtoCommon.FDFS_GROUP_NAME_MAX_LEN) {
+          group_len = bs.length;
+        } else {
+          group_len = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN;
+        }
+        Arrays.fill(bGroupName, (byte) 0);
+        System.arraycopy(bs, 0, bGroupName, 0, group_len);
+        out.write(bGroupName);
+      }
+
+      ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(trackerSocket.getInputStream(),
+        ProtoCommon.TRACKER_PROTO_CMD_RESP,
+        ProtoCommon.TRACKER_QUERY_STORAGE_STORE_BODY_LEN);
+      this.errno = pkgInfo.errno;
+      if (pkgInfo.errno != 0) {
+        return null;
+      }
+
+      ip_addr = new String(pkgInfo.body, ProtoCommon.FDFS_GROUP_NAME_MAX_LEN, ProtoCommon.FDFS_IPADDR_SIZE - 1).trim();
+
+      port = (int) ProtoCommon.buff2long(pkgInfo.body, ProtoCommon.FDFS_GROUP_NAME_MAX_LEN
+        + ProtoCommon.FDFS_IPADDR_SIZE - 1);
+      store_path = pkgInfo.body[ProtoCommon.TRACKER_QUERY_STORAGE_STORE_BODY_LEN - 1];
+
+      return new StorageServer(ip_addr, port, store_path);
+    } catch (IOException ex) {
+      if (!bNewConnection) {
+        try {
+          trackerServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        }
+      }
+
+      throw ex;
+    } finally {
+      if (bNewConnection) {
+        try {
+          trackerServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        }
+      }
+    }
+  }
+
+  /**
+   * query storage servers to upload file
+   *
+   * @param trackerServer the tracker server
+   * @param groupName     the group name to upload file to, can be empty
+   * @return storage servers, return null if fail
+   */
+  public StorageServer[] getStoreStorages(TrackerServer trackerServer, String groupName) throws IOException {
+    byte[] header;
+    String ip_addr;
+    int port;
+    byte cmd;
+    int out_len;
+    boolean bNewConnection;
+    Socket trackerSocket;
+
+    if (trackerServer == null) {
+      trackerServer = getConnection();
+      if (trackerServer == null) {
+        return null;
+      }
+      bNewConnection = true;
+    } else {
+      bNewConnection = false;
+    }
+
+    trackerSocket = trackerServer.getSocket();
+    OutputStream out = trackerSocket.getOutputStream();
+
+    try {
+      if (groupName == null || groupName.length() == 0) {
+        cmd = ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITHOUT_GROUP_ALL;
+        out_len = 0;
+      } else {
+        cmd = ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITH_GROUP_ALL;
+        out_len = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN;
+      }
+      header = ProtoCommon.packHeader(cmd, out_len, (byte) 0);
+      out.write(header);
+
+      if (groupName != null && groupName.length() > 0) {
+        byte[] bGroupName;
+        byte[] bs;
+        int group_len;
+
+        bs = groupName.getBytes(ClientGlobal.g_charset);
+        bGroupName = new byte[ProtoCommon.FDFS_GROUP_NAME_MAX_LEN];
+
+        if (bs.length <= ProtoCommon.FDFS_GROUP_NAME_MAX_LEN) {
+          group_len = bs.length;
+        } else {
+          group_len = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN;
+        }
+        Arrays.fill(bGroupName, (byte) 0);
+        System.arraycopy(bs, 0, bGroupName, 0, group_len);
+        out.write(bGroupName);
+      }
+
+      ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(trackerSocket.getInputStream(),
+        ProtoCommon.TRACKER_PROTO_CMD_RESP, -1);
+      this.errno = pkgInfo.errno;
+      if (pkgInfo.errno != 0) {
+        return null;
+      }
+
+      if (pkgInfo.body.length < ProtoCommon.TRACKER_QUERY_STORAGE_STORE_BODY_LEN) {
+        this.errno = ProtoCommon.ERR_NO_EINVAL;
+        return null;
+      }
+
+      int ipPortLen = pkgInfo.body.length - (ProtoCommon.FDFS_GROUP_NAME_MAX_LEN + 1);
+      final int recordLength = ProtoCommon.FDFS_IPADDR_SIZE - 1 + ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+      if (ipPortLen % recordLength != 0) {
+        this.errno = ProtoCommon.ERR_NO_EINVAL;
+        return null;
+      }
+
+      int serverCount = ipPortLen / recordLength;
+      if (serverCount > 16) {
+        this.errno = ProtoCommon.ERR_NO_ENOSPC;
+        return null;
+      }
+
+      StorageServer[] results = new StorageServer[serverCount];
+      byte store_path = pkgInfo.body[pkgInfo.body.length - 1];
+      int offset = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN;
+
+      for (int i = 0; i < serverCount; i++) {
+        ip_addr = new String(pkgInfo.body, offset, ProtoCommon.FDFS_IPADDR_SIZE - 1).trim();
+        offset += ProtoCommon.FDFS_IPADDR_SIZE - 1;
+
+        port = (int) ProtoCommon.buff2long(pkgInfo.body, offset);
+        offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+        results[i] = new StorageServer(ip_addr, port, store_path);
+      }
+
+      return results;
+    } catch (IOException ex) {
+      if (!bNewConnection) {
+        try {
+          trackerServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        }
+      }
+
+      throw ex;
+    } finally {
+      if (bNewConnection) {
+        try {
+          trackerServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        }
+      }
+    }
+  }
+
+  /**
+   * query storage server to download file
+   *
+   * @param trackerServer the tracker server
+   * @param groupName     the group name of storage server
+   * @param filename      filename on storage server
+   * @return storage server Socket object, return null if fail
+   */
+  public StorageServer getFetchStorage(TrackerServer trackerServer,
+                                       String groupName, String filename) throws IOException {
+    ServerInfo[] servers = this.getStorages(trackerServer, ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_FETCH_ONE,
+      groupName, filename);
+    if (servers == null) {
+      return null;
+    } else {
+      return new StorageServer(servers[0].getIpAddr(), servers[0].getPort(), 0);
+    }
+  }
+
+  /**
+   * query storage server to update file (delete file or set meta data)
+   *
+   * @param trackerServer the tracker server
+   * @param groupName     the group name of storage server
+   * @param filename      filename on storage server
+   * @return storage server Socket object, return null if fail
+   */
+  public StorageServer getUpdateStorage(TrackerServer trackerServer,
+                                        String groupName, String filename) throws IOException {
+    ServerInfo[] servers = this.getStorages(trackerServer, ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_UPDATE,
+      groupName, filename);
+    if (servers == null) {
+      return null;
+    } else {
+      return new StorageServer(servers[0].getIpAddr(), servers[0].getPort(), 0);
+    }
+  }
+
+  /**
+   * get storage servers to download file
+   *
+   * @param trackerServer the tracker server
+   * @param groupName     the group name of storage server
+   * @param filename      filename on storage server
+   * @return storage servers, return null if fail
+   */
+  public ServerInfo[] getFetchStorages(TrackerServer trackerServer,
+                                       String groupName, String filename) throws IOException {
+    return this.getStorages(trackerServer, ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_FETCH_ALL,
+      groupName, filename);
+  }
+
+  /**
+   * query storage server to download file
+   *
+   * @param trackerServer the tracker server
+   * @param cmd           command code, ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_FETCH_ONE or
+   *                      ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_UPDATE
+   * @param groupName     the group name of storage server
+   * @param filename      filename on storage server
+   * @return storage server Socket object, return null if fail
+   */
+  protected ServerInfo[] getStorages(TrackerServer trackerServer,
+                                     byte cmd, String groupName, String filename) throws IOException {
+    byte[] header;
+    byte[] bFileName;
+    byte[] bGroupName;
+    byte[] bs;
+    int len;
+    String ip_addr;
+    int port;
+    boolean bNewConnection;
+    Socket trackerSocket;
+
+    if (trackerServer == null) {
+      trackerServer = getConnection();
+      if (trackerServer == null) {
+        return null;
+      }
+      bNewConnection = true;
+    } else {
+      bNewConnection = false;
+    }
+    trackerSocket = trackerServer.getSocket();
+    OutputStream out = trackerSocket.getOutputStream();
+
+    try {
+      bs = groupName.getBytes(ClientGlobal.g_charset);
+      bGroupName = new byte[ProtoCommon.FDFS_GROUP_NAME_MAX_LEN];
+      bFileName = filename.getBytes(ClientGlobal.g_charset);
+
+      if (bs.length <= ProtoCommon.FDFS_GROUP_NAME_MAX_LEN) {
+        len = bs.length;
+      } else {
+        len = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN;
+      }
+      Arrays.fill(bGroupName, (byte) 0);
+      System.arraycopy(bs, 0, bGroupName, 0, len);
+
+      header = ProtoCommon.packHeader(cmd, ProtoCommon.FDFS_GROUP_NAME_MAX_LEN + bFileName.length, (byte) 0);
+      byte[] wholePkg = new byte[header.length + bGroupName.length + bFileName.length];
+      System.arraycopy(header, 0, wholePkg, 0, header.length);
+      System.arraycopy(bGroupName, 0, wholePkg, header.length, bGroupName.length);
+      System.arraycopy(bFileName, 0, wholePkg, header.length + bGroupName.length, bFileName.length);
+      out.write(wholePkg);
+
+      ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(trackerSocket.getInputStream(),
+        ProtoCommon.TRACKER_PROTO_CMD_RESP, -1);
+      this.errno = pkgInfo.errno;
+      if (pkgInfo.errno != 0) {
+        return null;
+      }
+
+      if (pkgInfo.body.length < ProtoCommon.TRACKER_QUERY_STORAGE_FETCH_BODY_LEN) {
+        throw new IOException("Invalid body length: " + pkgInfo.body.length);
+      }
+
+      if ((pkgInfo.body.length - ProtoCommon.TRACKER_QUERY_STORAGE_FETCH_BODY_LEN) % (ProtoCommon.FDFS_IPADDR_SIZE - 1) != 0) {
+        throw new IOException("Invalid body length: " + pkgInfo.body.length);
+      }
+
+      int server_count = 1 + (pkgInfo.body.length - ProtoCommon.TRACKER_QUERY_STORAGE_FETCH_BODY_LEN) / (ProtoCommon.FDFS_IPADDR_SIZE - 1);
+
+      ip_addr = new String(pkgInfo.body, ProtoCommon.FDFS_GROUP_NAME_MAX_LEN, ProtoCommon.FDFS_IPADDR_SIZE - 1).trim();
+      int offset = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN + ProtoCommon.FDFS_IPADDR_SIZE - 1;
+
+      port = (int) ProtoCommon.buff2long(pkgInfo.body, offset);
+      offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+      ServerInfo[] servers = new ServerInfo[server_count];
+      servers[0] = new ServerInfo(ip_addr, port);
+      for (int i = 1; i < server_count; i++) {
+        servers[i] = new ServerInfo(new String(pkgInfo.body, offset, ProtoCommon.FDFS_IPADDR_SIZE - 1).trim(), port);
+        offset += ProtoCommon.FDFS_IPADDR_SIZE - 1;
+      }
+
+      return servers;
+    } catch (IOException ex) {
+      if (!bNewConnection) {
+        try {
+          trackerServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        }
+      }
+
+      throw ex;
+    } finally {
+      if (bNewConnection) {
+        try {
+          trackerServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        }
+      }
+    }
+  }
+
+  /**
+   * query storage server to download file
+   *
+   * @param trackerServer the tracker server
+   * @param file_id       the file id(including group name and filename)
+   * @return storage server Socket object, return null if fail
+   */
+  public StorageServer getFetchStorage1(TrackerServer trackerServer, String file_id) throws IOException {
+    String[] parts = new String[2];
+    this.errno = StorageClient1.split_file_id(file_id, parts);
+    if (this.errno != 0) {
+      return null;
+    }
+
+    return this.getFetchStorage(trackerServer, parts[0], parts[1]);
+  }
+
+  /**
+   * get storage servers to download file
+   *
+   * @param trackerServer the tracker server
+   * @param file_id       the file id(including group name and filename)
+   * @return storage servers, return null if fail
+   */
+  public ServerInfo[] getFetchStorages1(TrackerServer trackerServer, String file_id) throws IOException {
+    String[] parts = new String[2];
+    this.errno = StorageClient1.split_file_id(file_id, parts);
+    if (this.errno != 0) {
+      return null;
+    }
+
+    return this.getFetchStorages(trackerServer, parts[0], parts[1]);
+  }
+
+  /**
+   * list groups
+   *
+   * @param trackerServer the tracker server
+   * @return group stat array, return null if fail
+   */
+  public StructGroupStat[] listGroups(TrackerServer trackerServer) throws IOException {
+    byte[] header;
+    String ip_addr;
+    int port;
+    byte cmd;
+    int out_len;
+    boolean bNewConnection;
+    byte store_path;
+    Socket trackerSocket;
+
+    if (trackerServer == null) {
+      trackerServer = getConnection();
+      if (trackerServer == null) {
+        return null;
+      }
+      bNewConnection = true;
+    } else {
+      bNewConnection = false;
+    }
+
+    trackerSocket = trackerServer.getSocket();
+    OutputStream out = trackerSocket.getOutputStream();
+
+    try {
+      header = ProtoCommon.packHeader(ProtoCommon.TRACKER_PROTO_CMD_SERVER_LIST_GROUP, 0, (byte) 0);
+      out.write(header);
+
+      ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(trackerSocket.getInputStream(),
+        ProtoCommon.TRACKER_PROTO_CMD_RESP, -1);
+      this.errno = pkgInfo.errno;
+      if (pkgInfo.errno != 0) {
+        return null;
+      }
+
+      ProtoStructDecoder<StructGroupStat> decoder = new ProtoStructDecoder<StructGroupStat>();
+      return decoder.decode(pkgInfo.body, StructGroupStat.class, StructGroupStat.getFieldsTotalSize());
+    } catch (IOException ex) {
+      if (!bNewConnection) {
+        try {
+          trackerServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        }
+      }
+
+      throw ex;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      this.errno = ProtoCommon.ERR_NO_EINVAL;
+      return null;
+    } finally {
+      if (bNewConnection) {
+        try {
+          trackerServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        }
+      }
+    }
+  }
+
+  /**
+   * query storage server stat info of the group
+   *
+   * @param trackerServer the tracker server
+   * @param groupName     the group name of storage server
+   * @return storage server stat array, return null if fail
+   */
+  public StructStorageStat[] listStorages(TrackerServer trackerServer, String groupName) throws IOException {
+    final String storageIpAddr = null;
+    return this.listStorages(trackerServer, groupName, storageIpAddr);
+  }
+
+  /**
+   * query storage server stat info of the group
+   *
+   * @param trackerServer the tracker server
+   * @param groupName     the group name of storage server
+   * @param storageIpAddr the storage server ip address, can be null or empty
+   * @return storage server stat array, return null if fail
+   */
+  public StructStorageStat[] listStorages(TrackerServer trackerServer,
+                                          String groupName, String storageIpAddr) throws IOException {
+    byte[] header;
+    byte[] bGroupName;
+    byte[] bs;
+    int len;
+    boolean bNewConnection;
+    Socket trackerSocket;
+
+    if (trackerServer == null) {
+      trackerServer = getConnection();
+      if (trackerServer == null) {
+        return null;
+      }
+      bNewConnection = true;
+    } else {
+      bNewConnection = false;
+    }
+    trackerSocket = trackerServer.getSocket();
+    OutputStream out = trackerSocket.getOutputStream();
+
+    try {
+      bs = groupName.getBytes(ClientGlobal.g_charset);
+      bGroupName = new byte[ProtoCommon.FDFS_GROUP_NAME_MAX_LEN];
+
+      if (bs.length <= ProtoCommon.FDFS_GROUP_NAME_MAX_LEN) {
+        len = bs.length;
+      } else {
+        len = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN;
+      }
+      Arrays.fill(bGroupName, (byte) 0);
+      System.arraycopy(bs, 0, bGroupName, 0, len);
+
+      int ipAddrLen;
+      byte[] bIpAddr;
+      if (storageIpAddr != null && storageIpAddr.length() > 0) {
+        bIpAddr = storageIpAddr.getBytes(ClientGlobal.g_charset);
+        if (bIpAddr.length < ProtoCommon.FDFS_IPADDR_SIZE) {
+          ipAddrLen = bIpAddr.length;
+        } else {
+          ipAddrLen = ProtoCommon.FDFS_IPADDR_SIZE - 1;
+        }
+      } else {
+        bIpAddr = null;
+        ipAddrLen = 0;
+      }
+
+      header = ProtoCommon.packHeader(ProtoCommon.TRACKER_PROTO_CMD_SERVER_LIST_STORAGE, ProtoCommon.FDFS_GROUP_NAME_MAX_LEN + ipAddrLen, (byte) 0);
+      byte[] wholePkg = new byte[header.length + bGroupName.length + ipAddrLen];
+      System.arraycopy(header, 0, wholePkg, 0, header.length);
+      System.arraycopy(bGroupName, 0, wholePkg, header.length, bGroupName.length);
+      if (ipAddrLen > 0) {
+        System.arraycopy(bIpAddr, 0, wholePkg, header.length + bGroupName.length, ipAddrLen);
+      }
+      out.write(wholePkg);
+
+      ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(trackerSocket.getInputStream(),
+        ProtoCommon.TRACKER_PROTO_CMD_RESP, -1);
+      this.errno = pkgInfo.errno;
+      if (pkgInfo.errno != 0) {
+        return null;
+      }
+
+      ProtoStructDecoder<StructStorageStat> decoder = new ProtoStructDecoder<StructStorageStat>();
+      return decoder.decode(pkgInfo.body, StructStorageStat.class, StructStorageStat.getFieldsTotalSize());
+    } catch (IOException ex) {
+      if (!bNewConnection) {
+        try {
+          trackerServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        }
+      }
+
+      throw ex;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      this.errno = ProtoCommon.ERR_NO_EINVAL;
+      return null;
+    } finally {
+      if (bNewConnection) {
+        try {
+          trackerServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        }
+      }
+    }
+  }
+
+  /**
+   * delete a storage server from the tracker server
+   *
+   * @param trackerServer the connected tracker server
+   * @param groupName     the group name of storage server
+   * @param storageIpAddr the storage server ip address
+   * @return true for success, false for fail
+   */
+  private boolean deleteStorage(TrackerServer trackerServer,
+                                String groupName, String storageIpAddr) throws IOException {
+    byte[] header;
+    byte[] bGroupName;
+    byte[] bs;
+    int len;
+    Socket trackerSocket;
+
+    trackerSocket = trackerServer.getSocket();
+    OutputStream out = trackerSocket.getOutputStream();
+
+    bs = groupName.getBytes(ClientGlobal.g_charset);
+    bGroupName = new byte[ProtoCommon.FDFS_GROUP_NAME_MAX_LEN];
+
+    if (bs.length <= ProtoCommon.FDFS_GROUP_NAME_MAX_LEN) {
+      len = bs.length;
+    } else {
+      len = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN;
+    }
+    Arrays.fill(bGroupName, (byte) 0);
+    System.arraycopy(bs, 0, bGroupName, 0, len);
+
+    int ipAddrLen;
+    byte[] bIpAddr = storageIpAddr.getBytes(ClientGlobal.g_charset);
+    if (bIpAddr.length < ProtoCommon.FDFS_IPADDR_SIZE) {
+      ipAddrLen = bIpAddr.length;
+    } else {
+      ipAddrLen = ProtoCommon.FDFS_IPADDR_SIZE - 1;
+    }
+
+    header = ProtoCommon.packHeader(ProtoCommon.TRACKER_PROTO_CMD_SERVER_DELETE_STORAGE, ProtoCommon.FDFS_GROUP_NAME_MAX_LEN + ipAddrLen, (byte) 0);
+    byte[] wholePkg = new byte[header.length + bGroupName.length + ipAddrLen];
+    System.arraycopy(header, 0, wholePkg, 0, header.length);
+    System.arraycopy(bGroupName, 0, wholePkg, header.length, bGroupName.length);
+    System.arraycopy(bIpAddr, 0, wholePkg, header.length + bGroupName.length, ipAddrLen);
+    out.write(wholePkg);
+
+    ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(trackerSocket.getInputStream(),
+      ProtoCommon.TRACKER_PROTO_CMD_RESP, 0);
+    this.errno = pkgInfo.errno;
+    return pkgInfo.errno == 0;
+  }
+
+  /**
+   * delete a storage server from the global FastDFS cluster
+   *
+   * @param groupName     the group name of storage server
+   * @param storageIpAddr the storage server ip address
+   * @return true for success, false for fail
+   */
+  public boolean deleteStorage(String groupName, String storageIpAddr) throws IOException {
+    return this.deleteStorage(ClientGlobal.g_tracker_group, groupName, storageIpAddr);
+  }
+
+  /**
+   * delete a storage server from the FastDFS cluster
+   *
+   * @param trackerGroup  the tracker server group
+   * @param groupName     the group name of storage server
+   * @param storageIpAddr the storage server ip address
+   * @return true for success, false for fail
+   */
+  public boolean deleteStorage(TrackerGroup trackerGroup,
+                               String groupName, String storageIpAddr) throws IOException {
+    int serverIndex;
+    int notFoundCount;
+    TrackerServer trackerServer;
+
+    notFoundCount = 0;
+    for (serverIndex = 0; serverIndex < trackerGroup.tracker_servers.length; serverIndex++) {
+      try {
+        trackerServer = trackerGroup.getConnection(serverIndex);
+      } catch (IOException ex) {
+        ex.printStackTrace(System.err);
+        this.errno = ProtoCommon.ECONNREFUSED;
+        return false;
+      }
+
+      try {
+        StructStorageStat[] storageStats = listStorages(trackerServer, groupName, storageIpAddr);
+        if (storageStats == null) {
+          if (this.errno == ProtoCommon.ERR_NO_ENOENT) {
+            notFoundCount++;
+          } else {
+            return false;
+          }
+        } else if (storageStats.length == 0) {
+          notFoundCount++;
+        } else if (storageStats[0].getStatus() == ProtoCommon.FDFS_STORAGE_STATUS_ONLINE ||
+          storageStats[0].getStatus() == ProtoCommon.FDFS_STORAGE_STATUS_ACTIVE) {
+          this.errno = ProtoCommon.ERR_NO_EBUSY;
+          return false;
+        }
+      } finally {
+        try {
+          trackerServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        }
+      }
+    }
+
+    if (notFoundCount == trackerGroup.tracker_servers.length) {
+      this.errno = ProtoCommon.ERR_NO_ENOENT;
+      return false;
+    }
+
+    notFoundCount = 0;
+    for (serverIndex = 0; serverIndex < trackerGroup.tracker_servers.length; serverIndex++) {
+      try {
+        trackerServer = trackerGroup.getConnection(serverIndex);
+      } catch (IOException ex) {
+        System.err.println("connect to server " + trackerGroup.tracker_servers[serverIndex].getAddress().getHostAddress() + ":" + trackerGroup.tracker_servers[serverIndex].getPort() + " fail");
+        ex.printStackTrace(System.err);
+        this.errno = ProtoCommon.ECONNREFUSED;
+        return false;
+      }
+
+      try {
+        if (!this.deleteStorage(trackerServer, groupName, storageIpAddr)) {
+          if (this.errno != 0) {
+            if (this.errno == ProtoCommon.ERR_NO_ENOENT) {
+              notFoundCount++;
+            } else if (this.errno != ProtoCommon.ERR_NO_EALREADY) {
+              return false;
+            }
+          }
+        }
+      } finally {
+        try {
+          trackerServer.close();
+        } catch (IOException ex1) {
+          ex1.printStackTrace();
+        }
+      }
+    }
+
+    if (notFoundCount == trackerGroup.tracker_servers.length) {
+      this.errno = ProtoCommon.ERR_NO_ENOENT;
+      return false;
+    }
+
+    if (this.errno == ProtoCommon.ERR_NO_ENOENT) {
+      this.errno = 0;
+    }
+
+    return this.errno == 0;
+  }
+}

+ 106 - 0
kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/TrackerGroup.java

@@ -0,0 +1,106 @@
+/**
+ * Copyright (C) 2008 Happy Fish / YuQing
+ * <p>
+ * FastDFS Java Client may be copied only under the terms of the GNU Lesser
+ * General Public License (LGPL).
+ * Please visit the FastDFS Home Page http://www.csource.org/ for more detail.
+ */
+
+package com.kmall.common.fileserver.fastdfs;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+
+/**
+ * Tracker server group
+ *
+ * @author Happy Fish / YuQing
+ * @version Version 1.17
+ */
+public class TrackerGroup {
+  public int tracker_server_index;
+  public InetSocketAddress[] tracker_servers;
+  protected Integer lock;
+
+  /**
+   * Constructor
+   *
+   * @param tracker_servers tracker servers
+   */
+  public TrackerGroup(InetSocketAddress[] tracker_servers) {
+    this.tracker_servers = tracker_servers;
+    this.lock = new Integer(0);
+    this.tracker_server_index = 0;
+  }
+
+  /**
+   * return connected tracker server
+   *
+   * @return connected tracker server, null for fail
+   */
+  public TrackerServer getConnection(int serverIndex) throws IOException {
+    Socket sock = new Socket();
+    sock.setReuseAddress(true);
+    sock.setSoTimeout(ClientGlobal.g_network_timeout);
+    sock.connect(this.tracker_servers[serverIndex], ClientGlobal.g_connect_timeout);
+    return new TrackerServer(sock, this.tracker_servers[serverIndex]);
+  }
+
+  /**
+   * return connected tracker server
+   *
+   * @return connected tracker server, null for fail
+   */
+  public TrackerServer getConnection() throws IOException {
+    int current_index;
+
+    synchronized (this.lock) {
+      this.tracker_server_index++;
+      if (this.tracker_server_index >= this.tracker_servers.length) {
+        this.tracker_server_index = 0;
+      }
+
+      current_index = this.tracker_server_index;
+    }
+
+    try {
+      return this.getConnection(current_index);
+    } catch (IOException ex) {
+      System.err.println("connect to server " + this.tracker_servers[current_index].getAddress().getHostAddress() + ":" + this.tracker_servers[current_index].getPort() + " fail");
+      ex.printStackTrace(System.err);
+    }
+
+    for (int i = 0; i < this.tracker_servers.length; i++) {
+      if (i == current_index) {
+        continue;
+      }
+
+      try {
+        TrackerServer trackerServer = this.getConnection(i);
+
+        synchronized (this.lock) {
+          if (this.tracker_server_index == current_index) {
+            this.tracker_server_index = i;
+          }
+        }
+
+        return trackerServer;
+      } catch (IOException ex) {
+        System.err.println("connect to server " + this.tracker_servers[i].getAddress().getHostAddress() + ":" + this.tracker_servers[i].getPort() + " fail");
+        ex.printStackTrace(System.err);
+      }
+    }
+
+    return null;
+  }
+
+  public Object clone() {
+    InetSocketAddress[] trackerServers = new InetSocketAddress[this.tracker_servers.length];
+    for (int i = 0; i < trackerServers.length; i++) {
+      trackerServers[i] = new InetSocketAddress(this.tracker_servers[i].getAddress().getHostAddress(), this.tracker_servers[i].getPort());
+    }
+
+    return new TrackerGroup(trackerServers);
+  }
+}

+ 81 - 0
kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/TrackerServer.java

@@ -0,0 +1,81 @@
+/**
+ * Copyright (C) 2008 Happy Fish / YuQing
+ * <p>
+ * FastDFS Java Client may be copied only under the terms of the GNU Lesser
+ * General Public License (LGPL).
+ * Please visit the FastDFS Home Page http://www.csource.org/ for more detail.
+ */
+
+package com.kmall.common.fileserver.fastdfs;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+
+/**
+ * Tracker Server Info
+ *
+ * @author Happy Fish / YuQing
+ * @version Version 1.11
+ */
+public class TrackerServer {
+  protected Socket sock;
+  protected InetSocketAddress inetSockAddr;
+
+  /**
+   * Constructor
+   *
+   * @param sock         Socket of server
+   * @param inetSockAddr the server info
+   */
+  public TrackerServer(Socket sock, InetSocketAddress inetSockAddr) {
+    this.sock = sock;
+    this.inetSockAddr = inetSockAddr;
+  }
+
+  /**
+   * get the connected socket
+   *
+   * @return the socket
+   */
+  public Socket getSocket() throws IOException {
+    if (this.sock == null) {
+      this.sock = ClientGlobal.getSocket(this.inetSockAddr);
+    }
+
+    return this.sock;
+  }
+
+  /**
+   * get the server info
+   *
+   * @return the server info
+   */
+  public InetSocketAddress getInetSocketAddress() {
+    return this.inetSockAddr;
+  }
+
+  public OutputStream getOutputStream() throws IOException {
+    return this.sock.getOutputStream();
+  }
+
+  public InputStream getInputStream() throws IOException {
+    return this.sock.getInputStream();
+  }
+
+  public void close() throws IOException {
+    if (this.sock != null) {
+      try {
+        ProtoCommon.closeSocket(this.sock);
+      } finally {
+        this.sock = null;
+      }
+    }
+  }
+
+  protected void finalize() throws Throwable {
+    this.close();
+  }
+}

+ 28 - 0
kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/UploadCallback.java

@@ -0,0 +1,28 @@
+/**
+ * Copyright (C) 2008 Happy Fish / YuQing
+ * <p>
+ * FastDFS Java Client may be copied only under the terms of the GNU Lesser
+ * General Public License (LGPL).
+ * Please visit the FastDFS Home Page http://www.csource.org/ for more detail.
+ */
+
+package com.kmall.common.fileserver.fastdfs;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * upload file callback interface
+ *
+ * @author Happy Fish / YuQing
+ * @version Version 1.0
+ */
+public interface UploadCallback {
+  /**
+   * send file content callback function, be called only once when the file uploaded
+   *
+   * @param out output stream for writing file content
+   * @return 0 success, return none zero(errno) if fail
+   */
+  public int send(OutputStream out) throws IOException;
+}

+ 55 - 0
kmall-common/src/main/java/com/kmall/common/fileserver/fastdfs/UploadStream.java

@@ -0,0 +1,55 @@
+package com.kmall.common.fileserver.fastdfs;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Upload file by stream
+ *
+ * @author zhouzezhong & Happy Fish / YuQing
+ * @version Version 1.11
+ */
+public class UploadStream implements UploadCallback {
+  private InputStream inputStream; //input stream for reading
+  private long fileSize = 0;  //size of the uploaded file
+
+  /**
+   * constructor
+   *
+   * @param inputStream input stream for uploading
+   * @param fileSize    size of uploaded file
+   */
+  public UploadStream(InputStream inputStream, long fileSize) {
+    super();
+    this.inputStream = inputStream;
+    this.fileSize = fileSize;
+  }
+
+  /**
+   * send file content callback function, be called only once when the file uploaded
+   *
+   * @param out output stream for writing file content
+   * @return 0 success, return none zero(errno) if fail
+   */
+  public int send(OutputStream out) throws IOException {
+    long remainBytes = fileSize;
+    byte[] buff = new byte[256 * 1024];
+    int bytes;
+    while (remainBytes > 0) {
+      try {
+        if ((bytes = inputStream.read(buff, 0, remainBytes > buff.length ? buff.length : (int) remainBytes)) < 0) {
+          return -1;
+        }
+      } catch (IOException ex) {
+        ex.printStackTrace();
+        return -1;
+      }
+
+      out.write(buff, 0, bytes);
+      remainBytes -= bytes;
+    }
+
+    return 0;
+  }
+}

+ 62 - 0
kmall-common/src/main/java/com/kmall/common/fileserver/util/FastDFSFile.java

@@ -0,0 +1,62 @@
+package com.kmall.common.fileserver.util;
+
+public class FastDFSFile {
+
+	private static final long serialVersionUID = 8042057955846275561L;
+	private byte[] content;
+	private String name;
+	private String ext;
+	private String length;
+	// private String author = FILE_DEFAULT_AUTHOR;
+
+	public FastDFSFile(byte[] content, String ext) {
+		this.content = content;
+		this.ext = ext;
+	}
+
+	public FastDFSFile(byte[] content, String name, String ext) {
+		this.content = content;
+		this.name = name;
+		this.ext = ext;
+	}
+
+	public FastDFSFile(byte[] content, String name, String ext, String length, String author) {
+		this.content = content;
+		this.name = name;
+		this.ext = ext;
+		this.length = length;
+	}
+
+	public byte[] getContent() {
+		return content;
+	}
+
+	public void setContent(byte[] content) {
+		this.content = content;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public String getExt() {
+		return ext;
+	}
+
+	public void setExt(String ext) {
+		this.ext = ext;
+	}
+
+	public String getLength() {
+		return length;
+	}
+
+	public void setLength(String length) {
+		this.length = length;
+	}
+
+}

+ 168 - 0
kmall-common/src/main/java/com/kmall/common/fileserver/util/FileManager.java

@@ -0,0 +1,168 @@
+package com.kmall.common.fileserver.util;
+
+import com.kmall.common.fileserver.common.NameValuePair;
+import com.kmall.common.fileserver.fastdfs.*;
+import org.apache.log4j.Logger;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.UUID;
+
+public class FileManager implements Serializable {
+
+    private static final long serialVersionUID = -9042217554595446459L;
+
+    public static final String PROTOCOL = "http://";
+
+    public static final String SEPARATOR = "/";
+    public static final String COLON = ":";
+
+
+    /**
+     * 配置文件名字
+     */
+    public static final String CLIENT_CONFIG_FILE = "conf/fastdfs.properties";
+
+    private static Logger logger = Logger.getLogger(FileManager.class);
+
+    private static TrackerClient trackerClient;
+    private static TrackerServer trackerServer;
+    private static StorageServer storageServer;
+    private static StorageClient storageClient;
+
+    static {
+        System.out.println("FileManager");
+        try {
+            String fdfsClientConfigFilePath = CLIENT_CONFIG_FILE;
+            ClientGlobal.init(fdfsClientConfigFilePath);
+
+            trackerClient = new TrackerClient();
+            trackerServer = trackerClient.getConnection();
+
+            storageClient = new StorageClient(trackerServer, storageServer);
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * <strong>方法概要: 文件上传</strong> <br>
+     * <strong>创建时间: 2016-9-26 上午10:26:11</strong> <br>
+     *
+     * @param FastDFSFile file
+     * @return fileAbsolutePath
+     * @author Wang Liang
+     */
+    public static String upload(MultipartFile attach) {
+        String[] uploadResults = null;
+        String ext = attach.getOriginalFilename().substring(attach.getOriginalFilename().lastIndexOf(".") + 1);
+        try {
+            FastDFSFile file = new FastDFSFile(attach.getBytes(), ext);
+            NameValuePair[] meta_list = new NameValuePair[4];
+            meta_list[0] = new NameValuePair("fileName", attach.getOriginalFilename());
+            meta_list[1] = new NameValuePair("fileLength", String.valueOf(attach.getSize()));
+            meta_list[2] = new NameValuePair("fileExt", ext);
+            meta_list[3] = new NameValuePair("fileAuthor", ClientGlobal.file_author);
+            synchronized (storageClient) {
+                uploadResults = storageClient.upload_file(file.getContent(), file.getExt(), meta_list);
+            }
+        } catch (Exception e1) {
+            e1.printStackTrace();
+        }
+        String groupName = uploadResults[0];
+        String remoteFileName = uploadResults[1];
+
+        String httpAddr = ClientGlobal.http_tracket_nginx_addr;
+
+        String httpPort = ClientGlobal.http_tracket_server_port;
+
+        String fileAbsolutePath = PROTOCOL + httpAddr + COLON + httpPort + SEPARATOR + groupName + SEPARATOR + remoteFileName;
+        return fileAbsolutePath;
+    }
+
+
+    /**
+     * <strong>方法概要: 文件上传</strong> <br>
+     * <strong>创建时间: 2016-9-26 上午10:26:11</strong> <br>
+     *
+     * @return fileAbsolutePath
+     * @author Wang Liang
+     */
+    public static String upload(String originalFileName, byte[] content, String fileLength) {
+        String[] uploadResults = null;
+        String ext = originalFileName.substring(originalFileName.lastIndexOf(".") + 1);
+        try {
+            FastDFSFile file = new FastDFSFile(content, ext);
+            NameValuePair[] meta_list = new NameValuePair[4];
+            meta_list[0] = new NameValuePair("fileName", originalFileName);
+            meta_list[1] = new NameValuePair("fileLength", fileLength);
+            meta_list[2] = new NameValuePair("fileExt", ext);
+            meta_list[3] = new NameValuePair("fileAuthor", ClientGlobal.file_author);
+            synchronized (storageClient) {
+                uploadResults = storageClient.upload_file(file.getContent(), file.getExt(), meta_list);
+            }
+        } catch (Exception e1) {
+            e1.printStackTrace();
+        }
+        String groupName = uploadResults[0];
+        String remoteFileName = uploadResults[1];
+
+        String httpAddr = ClientGlobal.http_tracket_nginx_addr;
+
+        String httpPort = ClientGlobal.http_tracket_server_port;
+
+        String fileAbsolutePath = PROTOCOL + httpAddr + COLON + httpPort + SEPARATOR + groupName + SEPARATOR + remoteFileName;
+        return fileAbsolutePath;
+    }
+
+    /**
+     * <strong>方法概要: 文件下载</strong> <br>
+     * <strong>创建时间: 2016-9-26 上午10:28:21</strong> <br>
+     *
+     * @param String groupName
+     * @param String remoteFileName
+     * @return returned value comment here
+     * @author Wang Liang
+     */
+    public static ResponseEntity<byte[]> download(String filePath, String fileName) {
+        byte[] content = null;
+        HttpHeaders headers = new HttpHeaders();
+        String substr = filePath.substring(filePath.indexOf("group"));
+        String groupName = substr.split("/")[0];
+        String remoteFileName = substr.substring(substr.indexOf("/") + 1);
+        String specFileName = substr.substring(substr.indexOf("."));
+        try {
+            if (fileName == null || fileName.trim().equals("")) {
+                fileName = UUID.randomUUID() + specFileName;
+            } else {
+                fileName = fileName + specFileName;
+            }
+            content = storageClient.download_file(groupName, remoteFileName);
+            headers.setContentDispositionFormData("attachment", new String(fileName.getBytes("UTF-8"), "iso-8859-1"));
+            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return new ResponseEntity<byte[]>(content, headers, HttpStatus.CREATED);
+    }
+
+    public static FileInfo getFile(String filePath) {
+        String substr = filePath.substring(filePath.indexOf("group"));
+        String groupName = substr.split("/")[0];
+        String remoteFileName = substr.substring(substr.indexOf("/") + 1);
+        try {
+            return storageClient.get_file_info(groupName, remoteFileName);
+        } catch (IOException e) {
+            logger.error("IO Exception: Get File from Fast DFS failed", e);
+        } catch (Exception e) {
+            logger.error("Non IO Exception: Get File from Fast DFS failed", e);
+        }
+        return null;
+    }
+}