184 lines
4.0 KiB
Go
184 lines
4.0 KiB
Go
|
package ufsdk
|
|||
|
|
|||
|
import (
|
|||
|
"bytes"
|
|||
|
"crypto/md5"
|
|||
|
"crypto/sha1"
|
|||
|
"encoding/base64"
|
|||
|
"encoding/binary"
|
|||
|
"encoding/json"
|
|||
|
"fmt"
|
|||
|
"io"
|
|||
|
"io/ioutil"
|
|||
|
"net/http"
|
|||
|
"os"
|
|||
|
"time"
|
|||
|
)
|
|||
|
|
|||
|
const (
|
|||
|
blkSIZE = 2 << 21
|
|||
|
)
|
|||
|
|
|||
|
//Config 配置文件序列化所需的全部字段
|
|||
|
type Config struct {
|
|||
|
PublicKey string `json:"public_key"`
|
|||
|
PrivateKey string `json:"private_key"`
|
|||
|
BucketName string `json:"bucket_name"`
|
|||
|
FileHost string `json:"file_host"`
|
|||
|
BucketHost string `json:"bucket_host"`
|
|||
|
VerifyUploadMD5 bool `json:"verfiy_upload_md5"`
|
|||
|
}
|
|||
|
|
|||
|
//LoadConfig 从配置文件加载一个配置。
|
|||
|
func LoadConfig(jsonPath string) (*Config, error) {
|
|||
|
file, err := openFile(jsonPath)
|
|||
|
if err != nil {
|
|||
|
return nil, err
|
|||
|
}
|
|||
|
defer file.Close()
|
|||
|
configBytes, err := ioutil.ReadAll(file)
|
|||
|
if err != nil {
|
|||
|
return nil, err
|
|||
|
}
|
|||
|
c := new(Config)
|
|||
|
err = json.Unmarshal(configBytes, c)
|
|||
|
if err != nil {
|
|||
|
return nil, err
|
|||
|
}
|
|||
|
return c, nil
|
|||
|
}
|
|||
|
|
|||
|
//VerifyHTTPCode 检查 HTTP 的返回值是否为 2XX,如果不是就返回 false。
|
|||
|
func VerifyHTTPCode(code int) bool {
|
|||
|
if code < http.StatusOK || code > http.StatusIMUsed {
|
|||
|
return false
|
|||
|
}
|
|||
|
return true
|
|||
|
}
|
|||
|
|
|||
|
//GetFileMimeType 获取文件的 mime type 值,接收文件路径作为参数。如果检测不到,则返回空。
|
|||
|
func GetFileMimeType(path string) string {
|
|||
|
f, err := openFile(path)
|
|||
|
if err != nil {
|
|||
|
return ""
|
|||
|
}
|
|||
|
defer f.Close()
|
|||
|
return getMimeType(f)
|
|||
|
}
|
|||
|
|
|||
|
func getMimeType(f *os.File) string {
|
|||
|
buffer := make([]byte, 512)
|
|||
|
|
|||
|
_, err := f.Read(buffer)
|
|||
|
defer func() { f.Seek(0, 0) }() //revert file's seek
|
|||
|
if err != nil {
|
|||
|
return "plain/text"
|
|||
|
}
|
|||
|
|
|||
|
return http.DetectContentType(buffer)
|
|||
|
}
|
|||
|
|
|||
|
func openFile(path string) (*os.File, error) {
|
|||
|
return os.Open(path)
|
|||
|
}
|
|||
|
|
|||
|
//getFileSize get opened file size
|
|||
|
func getFileSize(f *os.File) int64 {
|
|||
|
fi, err := f.Stat()
|
|||
|
if err != nil {
|
|||
|
panic(err.Error())
|
|||
|
}
|
|||
|
return fi.Size()
|
|||
|
}
|
|||
|
|
|||
|
//GetFileEtag 获取文件的 etag 值
|
|||
|
func GetFileEtag(path string) string {
|
|||
|
f, err := openFile(path)
|
|||
|
if err != nil {
|
|||
|
return ""
|
|||
|
}
|
|||
|
defer f.Close()
|
|||
|
return calculateEtag(f)
|
|||
|
}
|
|||
|
|
|||
|
//Calculatek 计算文件的 etag 值。
|
|||
|
func calculateEtag(f *os.File) string {
|
|||
|
fsize := getFileSize(f)
|
|||
|
blkcnt := uint32(fsize / blkSIZE)
|
|||
|
if fsize%blkSIZE != 0 {
|
|||
|
blkcnt++
|
|||
|
}
|
|||
|
|
|||
|
bs := make([]byte, 4)
|
|||
|
binary.LittleEndian.PutUint32(bs, blkcnt)
|
|||
|
|
|||
|
h := sha1.New()
|
|||
|
buf := make([]byte, 0, 24)
|
|||
|
buf = append(buf, bs...)
|
|||
|
if fsize <= blkSIZE {
|
|||
|
io.Copy(h, f)
|
|||
|
} else {
|
|||
|
var i uint32
|
|||
|
for i = 0; i < blkcnt; i++ {
|
|||
|
shaBlk := sha1.New()
|
|||
|
io.Copy(shaBlk, io.LimitReader(f, blkSIZE))
|
|||
|
io.Copy(h, bytes.NewReader(shaBlk.Sum(nil)))
|
|||
|
}
|
|||
|
}
|
|||
|
buf = h.Sum(buf)
|
|||
|
etag := base64.URLEncoding.EncodeToString(buf)
|
|||
|
return etag
|
|||
|
}
|
|||
|
|
|||
|
func makeBoundry() string {
|
|||
|
h := md5.New()
|
|||
|
t := time.Now()
|
|||
|
io.WriteString(h, t.String())
|
|||
|
return fmt.Sprintf("%x", h.Sum(nil))
|
|||
|
}
|
|||
|
|
|||
|
func makeFormBody(authorization, boundry, keyName, mimeType string, verifyMD5 bool, file *os.File) *bytes.Buffer {
|
|||
|
boundry = "--" + boundry + "\r\n"
|
|||
|
boundryBytes := []byte(boundry)
|
|||
|
body := new(bytes.Buffer)
|
|||
|
|
|||
|
body.Write(boundryBytes)
|
|||
|
body.Write(makeFormField("Authorization", authorization))
|
|||
|
body.Write(boundryBytes)
|
|||
|
body.Write(makeFormField("Content-Type", mimeType))
|
|||
|
body.Write(boundryBytes)
|
|||
|
body.Write(makeFormField("FileName", keyName))
|
|||
|
body.Write(boundryBytes)
|
|||
|
|
|||
|
if verifyMD5 {
|
|||
|
h := md5.New()
|
|||
|
io.Copy(h, file)
|
|||
|
md5Str := fmt.Sprintf("%x", h.Sum(nil))
|
|||
|
body.Write(makeFormField("Content-MD5", md5Str))
|
|||
|
body.Write(boundryBytes)
|
|||
|
}
|
|||
|
|
|||
|
addtionalStr := fmt.Sprintf("Content-Disposition: form-data; name=\"file\"; filename=\"%s\"\r\n", keyName)
|
|||
|
addtionalStr += fmt.Sprintf("Content-Type: %s\r\n\r\n", mimeType)
|
|||
|
body.Write([]byte(addtionalStr))
|
|||
|
body.ReadFrom(file)
|
|||
|
body.Write([]byte("\r\n"))
|
|||
|
body.Write(boundryBytes)
|
|||
|
|
|||
|
return body
|
|||
|
}
|
|||
|
|
|||
|
func makeFormField(key, value string) []byte {
|
|||
|
keyStr := fmt.Sprintf("Content-Disposition: form-data; name=\"%s\"\r\n\r\n", key)
|
|||
|
valueStr := fmt.Sprintf("%s\r\n", value)
|
|||
|
return []byte(keyStr + valueStr)
|
|||
|
}
|
|||
|
|
|||
|
func structPrettyStr(data interface{}) string {
|
|||
|
bytes, err := json.MarshalIndent(data, "", " ")
|
|||
|
if err == nil {
|
|||
|
return fmt.Sprintf("%s\n", bytes)
|
|||
|
}
|
|||
|
return ""
|
|||
|
}
|