/// <summary> /// 修改文件的MIME_TYPE /// </summary> public static void chgm() { Mac mac = new Mac(Settings.AccessKey, Settings.SecretKey); string bucket = "BUCKET"; string key = "KEY"; string mimeType = "MIME_TYPE"; BucketManager bm = new BucketManager(mac); HttpResult result = bm.chgm(bucket, key, mimeType); }
/// <summary> /// 列举所有的bucket /// </summary> public static void buckets() { Mac mac = new Mac(Settings.AccessKey, Settings.SecretKey); BucketManager bm = new BucketManager(mac); BucketsResult result = bm.buckets(); foreach(string bucket in result.Buckets) { System.Console.WriteLine(bucket); } }
/// <summary> /// 复制文件 /// </summary> public static void copy() { Mac mac = new Mac(Settings.AccessKey, Settings.SecretKey); string srcBucket = "SRC_BUCKET"; string srcKey = "SRC_KEY"; string dstBucket = "SRC_BUCKET"; string dstKey = "DST_BUCKET"; BucketManager bm = new BucketManager(mac); HttpResult result = bm.copy(srcBucket, srcKey, dstBucket, dstKey); //支持force参数, bool force = true/false //HttpResult result = bm.copy(srcBucket, srcKey, dstBucket, dstKey, force); }
/// <summary> /// 批量操作 /// </summary> public static void batch() { Mac mac = new Mac(Settings.AccessKey, Settings.SecretKey); // 批量操作类似于 // op=<op1>&op=<op2>&op=<op3>... string batchOps = "BATCH_OPS"; BucketManager bm = new BucketManager(mac); HttpResult result = bm.batch(batchOps); // 或者 //string[] batch_ops={"<op1>","<op2>","<op3>",...}; //bm.batch(batch_ops); System.Console.WriteLine(result.Response); }
public void bktMgrTest() { //Settings.load(); Settings.LoadFromFile(); string testResUrl = "http://test.fengyh.cn/qiniu/files/hello.txt"; Mac mac = new Mac(Settings.AccessKey, Settings.SecretKey); BucketManager target = new BucketManager(mac); target.fetch(testResUrl, Settings.Bucket, "test_BucketManager.txt"); target.stat(Settings.Bucket, "test_BucketManager.txt"); target.copy(Settings.Bucket, "test_BucketManager.txt", Settings.Bucket, "copy_BucketManager.txt", true); target.move(Settings.Bucket, "copy_BucketManager.txt", Settings.Bucket, "move_BucketManager.txt", true); target.delete(Settings.Bucket, "test_BucketManager.txt"); DomainsResult domainsResult = target.domains(Settings.Bucket); BucketsResult bucketsResult = target.buckets(); }
/// <summary> /// sync setting page loaded event handler /// 更新 2016-10-20 15:51 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void SyncSettingPageLoaded_EventHandler(object sender, RoutedEventArgs e) { // 尝试载入account this.account = Account.TryLoadAccount(); // 检查AK&SK if (string.IsNullOrEmpty(this.account.AccessKey) || string.IsNullOrEmpty(this.account.SecretKey)) { this.SettingsErrorTextBlock.Text = "请设置AK&SK"; return; } // 设置AK&SK SystemConfig.ACCESS_KEY = this.account.AccessKey; SystemConfig.SECRET_KEY = this.account.SecretKey; // 初始化bucketManager Mac mac = new Mac(this.account.AccessKey, this.account.SecretKey); this.bucketManager = new BucketManager(mac); if (!ValidateAccount()) { // Account设置不正确-->无法开始任务 ButtonStartSync.IsEnabled = false; return; } ButtonStartSync.IsEnabled = true; // 重建bucket列表 this.SyncTargetBucketsComboBox.ItemsSource = null; new Thread(new ThreadStart(this.reloadBuckets)).Start(); // 其他界面元素初始化 this.initUIDefaults(); }
public void uploadFile(object file) { if (syncProgressPage.checkCancelSignal()) { this.doneEvent.Set(); return; } string fileFullPath = file.ToString(); if (!File.Exists(fileFullPath)) { Log.Error(string.Format("file not found error, {0}", fileFullPath)); this.doneEvent.Set(); return; } //check skipped rules int fileRelativePathIndex = fileFullPath.IndexOf(this.syncSetting.SyncLocalDir); string fileRelativePath = fileFullPath.Substring(fileRelativePathIndex + this.syncSetting.SyncLocalDir.Length); if (fileRelativePath.StartsWith("\\")) { fileRelativePath = fileRelativePath.Substring(1); } string[] skippedPrefixes = this.syncSetting.SkipPrefixes.Split(','); foreach (string prefix in skippedPrefixes) { if (!string.IsNullOrWhiteSpace(prefix)) { if (fileRelativePath.StartsWith(prefix.Trim())) { //skip by prefix this.syncProgressPage.addFileSkippedLog(string.Format("{0}\t{1}", this.syncSetting.SyncTargetBucket, fileFullPath)); this.syncProgressPage.updateUploadLog("按照前缀规则跳过文件不同步 " + fileFullPath); this.syncProgressPage.updateTotalUploadProgress(); this.doneEvent.Set(); return; } } } string[] skippedSuffixes = this.syncSetting.SkipSuffixes.Split(','); foreach (string suffix in skippedSuffixes) { if (!string.IsNullOrWhiteSpace(suffix)) { if (fileRelativePath.EndsWith(suffix.Trim())) { //skip by suffix this.syncProgressPage.addFileSkippedLog(string.Format("{0}\t{1}", this.syncSetting.SyncTargetBucket, fileFullPath)); this.syncProgressPage.updateUploadLog("按照后缀规则跳过文件不同步 " + fileFullPath); this.syncProgressPage.updateTotalUploadProgress(); this.doneEvent.Set(); return; } } } //generate the file key string fileKey = ""; if (this.syncSetting.IgnoreDir) { //check ignore dir fileKey = System.IO.Path.GetFileName(fileFullPath); } else { string newFileFullPath = fileFullPath.Replace('\\', '/'); string newLocalSyncDir = this.syncSetting.SyncLocalDir.Replace('\\', '/'); int fileKeyIndex = newFileFullPath.IndexOf(newLocalSyncDir); fileKey = newFileFullPath.Substring(fileKeyIndex + newLocalSyncDir.Length); if (fileKey.StartsWith("/")) { fileKey = fileKey.Substring(1); } } //add prefix fileKey = this.syncSetting.SyncPrefix + fileKey; //set upload params Qiniu.Common.Config.PUT_THRESHOLD = this.syncSetting.ChunkUploadThreshold; Qiniu.Common.Config.CHUNK_SIZE = this.syncSetting.DefaultChunkSize; string myDocPath = System.Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); string recordPath = System.IO.Path.Combine(myDocPath, "qsunsync", "resume"); if (!Directory.Exists(recordPath)) { Directory.CreateDirectory(recordPath); } bool overwriteUpload = false; Mac mac = new Mac(SystemConfig.ACCESS_KEY, SystemConfig.SECRET_KEY); //current file info FileInfo fileInfo = new FileInfo(fileFullPath); long fileLength = fileInfo.Length; string fileLastModified = fileInfo.LastWriteTimeUtc.ToFileTime().ToString(); //support resume upload string recorderKey = string.Format("{0}:{1}:{2}:{3}:{4}", this.syncSetting.SyncLocalDir, this.syncSetting.SyncTargetBucket, fileKey, fileFullPath, fileLastModified); recorderKey = Tools.md5Hash(recorderKey); if (syncSetting.CheckRemoteDuplicate) { //check remotely BucketManager bucketManager = new BucketManager(mac); StatResult statResult = bucketManager.stat(this.syncSetting.SyncTargetBucket, fileKey); if (!string.IsNullOrEmpty(statResult.Hash)) { //file exists in bucket string localHash = ""; //cached file info try { CachedHash cachedHash = CachedHash.GetCachedHashByLocalPath(fileFullPath, localHashDB); string cachedEtag = cachedHash.Etag; string cachedLmd = cachedHash.LastModified; if (!string.IsNullOrEmpty(cachedEtag) && !string.IsNullOrEmpty(cachedLmd)) { if (cachedLmd.Equals(fileLastModified)) { //file not modified localHash = cachedEtag; } else { //file modified, calc the hash and update db string newEtag = QETag.hash(fileFullPath); localHash = newEtag; try { CachedHash.UpdateCachedHash(fileFullPath, newEtag, fileLastModified, localHashDB); } catch (Exception ex) { Log.Error(string.Format("update local hash failed {0}", ex.Message)); } } } else { //no record, calc hash and insert into db string newEtag = QETag.hash(fileFullPath); localHash = newEtag; try { CachedHash.InsertCachedHash(fileFullPath, newEtag, fileLastModified, localHashDB); } catch (Exception ex) { Log.Error(string.Format("insert local hash failed {0}", ex.Message)); } } } catch (Exception ex) { Log.Error(string.Format("get hash from local db failed {0}", ex.Message)); localHash = QETag.hash(fileFullPath); } if (localHash.Equals(statResult.Hash)) { //same file, no need to upload this.syncProgressPage.addFileExistsLog(string.Format("{0}\t{1}\t{2}", this.syncSetting.SyncTargetBucket, fileFullPath, fileKey)); this.syncProgressPage.updateUploadLog("空间已存在,跳过文件 " + fileFullPath); this.syncProgressPage.updateTotalUploadProgress(); //compatible, insert or update sync log for file try { SyncLog.InsertOrUpdateSyncLog(fileKey, fileFullPath, fileLastModified, this.syncLogDB); } catch (Exception ex) { Log.Error(string.Format("insert ot update sync log error {0}", ex.Message)); } this.doneEvent.Set(); return; } else { if (this.syncSetting.OverwriteFile) { overwriteUpload = true; this.syncProgressPage.updateUploadLog("空间已存在,将覆盖 " + fileFullPath); } else { this.syncProgressPage.updateUploadLog("空间已存在,不覆盖 " + fileFullPath); this.syncProgressPage.addFileNotOverwriteLog(string.Format("{0}\t{1}\t{2}", this.syncSetting.SyncTargetBucket, fileFullPath, fileKey)); this.syncProgressPage.updateTotalUploadProgress(); this.doneEvent.Set(); return; } } } } else { //check locally try { SyncLog syncLog = SyncLog.GetSyncLogByKey(fileKey, this.syncLogDB); if (!string.IsNullOrEmpty(syncLog.Key)) { //has sync log and check whether it changes if (syncLog.LocalPath.Equals(fileFullPath) && syncLog.LastModified.Equals(fileLastModified)) { this.syncProgressPage.addFileExistsLog(string.Format("{0}\t{1}\t{2}", this.syncSetting.SyncTargetBucket, fileFullPath, fileKey)); this.syncProgressPage.updateUploadLog("本地检查已同步,跳过" + fileFullPath); this.syncProgressPage.updateTotalUploadProgress(); this.doneEvent.Set(); return; } } } catch (Exception ex) { Log.Error(string.Format("get sync log failed {0}", ex.Message)); } if (this.syncSetting.OverwriteFile) { overwriteUpload = true; } } //if file not exists or need to overwrite this.syncProgressPage.updateUploadLog("准备上传文件 " + fileFullPath); UploadManager uploadManger = new UploadManager(new Qiniu.Storage.Persistent.ResumeRecorder(recordPath), new Qiniu.Storage.Persistent.KeyGenerator(delegate() { return recorderKey; })); PutPolicy putPolicy = new PutPolicy(); if (overwriteUpload) { putPolicy.Scope = this.syncSetting.SyncTargetBucket + ":" + fileKey; } else { putPolicy.Scope = this.syncSetting.SyncTargetBucket; } putPolicy.SetExpires(24 * 30 * 3600); string uptoken = Auth.createUploadToken(putPolicy, mac); this.syncProgressPage.updateUploadLog("开始上传文件 " + fileFullPath); uploadManger.uploadFile(fileFullPath, fileKey, uptoken, new UploadOptions(null, null, false, new UpProgressHandler(delegate(string key, double percent) { this.syncProgressPage.updateSingleFileProgress(taskId, fileFullPath, fileKey, fileLength, percent); }), new UpCancellationSignal(delegate() { return this.syncProgressPage.checkCancelSignal(); })) , new UpCompletionHandler(delegate(string key, ResponseInfo respInfo, string response) { if (respInfo.StatusCode != 200) { this.syncProgressPage.updateUploadLog("上传失败 " + fileFullPath + "," + respInfo.Error); this.syncProgressPage.addFileUploadErrorLog(string.Format("{0}\t{1}\t{2}\t{3}", this.syncSetting.SyncTargetBucket, fileFullPath, fileKey, respInfo.Error + "" + response)); //file exists error if (respInfo.StatusCode == 614) { this.syncProgressPage.updateUploadLog("空间已存在,未覆盖 " + fileFullPath); this.syncProgressPage.addFileNotOverwriteLog(string.Format("{0}\t{1}\t{2}", this.syncSetting.SyncTargetBucket, fileFullPath, fileKey)); } } else { //insert or update sync log for file try { SyncLog.InsertOrUpdateSyncLog(fileKey, fileFullPath, fileLastModified, this.syncLogDB); } catch (Exception ex) { Log.Error(string.Format("insert ot update sync log error {0}", ex.Message)); } //write new file hash to local db if (!overwriteUpload) { PutRet putRet = JsonConvert.DeserializeObject<PutRet>(response); string fileHash = putRet.Hash; if (this.localHashDB != null) { try { CachedHash.InsertOrUpdateCachedHash(fileFullPath, fileHash, fileLastModified, this.localHashDB); } catch (Exception ex) { Log.Error(string.Format("insert or update cached hash error {0}", ex.Message)); } } Log.Debug(string.Format("insert or update qiniu hash to local: '{0}' => '{1}'", fileFullPath, fileHash)); } //update if (overwriteUpload) { this.syncProgressPage.addFileOverwriteLog(string.Format("{0}\t{1}\t{2}", this.syncSetting.SyncTargetBucket, fileFullPath, fileKey)); } this.syncProgressPage.updateUploadLog("上传成功 " + fileFullPath); this.syncProgressPage.addFileUploadSuccessLog(string.Format("{0}\t{1}\t{2}", this.syncSetting.SyncTargetBucket, fileFullPath, fileKey)); this.syncProgressPage.updateTotalUploadProgress(); } this.doneEvent.Set(); })); }
/// <summary> /// load sync settings, this method is called before the page loaded method /// </summary> /// <param name="syncSetting"></param> public void LoadSyncSetting(SyncSetting syncSetting) { this.syncSetting = syncSetting; this.bucketManager = null; }
/// <summary> /// init the bucket manager /// </summary> private void initBucketManager() { this.account = Account.TryLoadAccount(); if (string.IsNullOrEmpty(account.AccessKey) || string.IsNullOrEmpty(account.SecretKey)) { Log.Info("account info not set"); this.SettingsErrorTextBlock.Text = "请返回设置 AK 和 SK"; return; } Mac mac = new Mac(this.account.AccessKey, this.account.SecretKey); this.bucketManager = new BucketManager(mac); }
/// <summary> /// save account settings to local file and check the validity of the settings /// </summary> private void SaveAccountSetting(object accountObj) { Account account = (Account)accountObj; //write settings to local file string accData = JsonConvert.SerializeObject(account); string myDocPath = System.Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); string appDir = System.IO.Path.Combine(myDocPath, "qsunsync"); try { if (!Directory.Exists(appDir)) { try { Directory.CreateDirectory(appDir); } catch (Exception ex) { Log.Error(string.Format("create app dir {0} failed due to {1}", appDir, ex.Message)); Dispatcher.Invoke(new Action(delegate { this.SettingsErrorTextBlock.Text = "创建本地配置路径失败"; })); } } string accPath = System.IO.Path.Combine(appDir, "account.json"); using (StreamWriter sw = new StreamWriter(accPath, false, Encoding.UTF8)) { sw.Write(accData); } } catch (Exception ex) { Log.Error("save account info to file failed, " + ex.Message); Dispatcher.Invoke(new Action(delegate { this.SettingsErrorTextBlock.Text = "帐号设置写入文件失败"; })); } //check ak & sk validity Mac mac = new Mac(account.AccessKey, account.SecretKey); BucketManager bucketManager = new BucketManager(mac); StatResult statResult = bucketManager.stat("NONE_EXIST_BUCKET", "NONE_EXIST_KEY"); if (statResult.ResponseInfo.isNetworkBroken()) { Log.Error("stat file network error, " + statResult.ResponseInfo.ToString()); Dispatcher.Invoke(new Action(delegate { this.SettingsErrorTextBlock.Text = "网络故障"; })); } else { if (statResult.ResponseInfo.StatusCode == 401) { Log.Error("ak & sk wrong"); Dispatcher.Invoke(new Action(delegate { this.SettingsErrorTextBlock.Text = "AK 或 SK 设置不正确"; })); } else if (statResult.ResponseInfo.StatusCode == 612 || statResult.ResponseInfo.StatusCode == 631) { Log.Info("ak & sk is valid"); Dispatcher.Invoke(new Action(delegate { this.SettingsErrorTextBlock.Text = ""; this.mainWindow.GotoHomePage(); })); } else { Log.Error(string.Format("valid ak&sk unknown error, {0}:{1}:{2}:{3}", statResult.ResponseInfo.StatusCode, statResult.ResponseInfo.Error, statResult.ResponseInfo.ReqId, statResult.Response)); Dispatcher.Invoke(new Action(delegate { this.SettingsErrorTextBlock.Text = "未知错误,请联系七牛"; })); } } }
/// <summary> /// 批量获取文件的hash /// 注意:单次请求的文件数量在1000以下 /// </summary> /// <param name="bktMgr"></param> /// <param name="bucket"></param> /// <param name="keys"></param> /// <returns></returns> public static string[] BatchStat(Mac mac, string bucket, string[] keys) { string[] remoteHash = new string[keys.Length]; BucketManager bktMgr = new BucketManager(mac); StringBuilder opsb = new StringBuilder(); int N = keys.Length; int X = 1000; int G = N / X; int M = N % X; int i; bool first = false; string s1 = "op=/stat/"; string s2 = "&op=/stat/"; string s = ""; HttpResult result = null; StatResponse[] statResults = null; #region LOOP for (int g = 0; g < G; ++g) { opsb.Clear(); first = true; for (i = 0; i < X; ++i) { s = s2; if (first) { s = s1; first = false; } opsb.Append(s + StringUtils.encodedEntry(bucket, keys[g*X+i])); } result = bktMgr.batch(opsb.ToString()); statResults = JsonConvert.DeserializeObject<StatResponse[]>(result.Response); for (i = 0; i < X; ++i) { if (statResults[i].CODE == 200) { // FOUND remoteHash[g * X + i] = statResults[i].DATA.hash; } } } #endregion LOOP #region RESIDUE opsb.Clear(); first = true; for (i = 0; i < M; ++i) { s = s2; if (first) { s = s1; first = false; } opsb.Append(s + StringUtils.encodedEntry(bucket, keys[G*X+i])); } result = bktMgr.batch(opsb.ToString()); statResults = JsonConvert.DeserializeObject<StatResponse[]>(result.Response); for (i = 0; i < M; ++i) { if (statResults[i].CODE == 200) { remoteHash[G * X + i] = statResults[i].DATA.hash; } } #endregion RESIDUE return remoteHash; }
/// <summary> /// 获取指定bucket对应的域名(可能不止一个),类似于abcxx.bkt.clouddn.com这样 /// </summary> public static void domains() { Mac mac = new Mac(Settings.AccessKey, Settings.SecretKey); string bucket = "BUCKET"; BucketManager bm = new BucketManager(mac); DomainsResult result = bm.domains(bucket); foreach(string domain in result.Domains) { System.Console.WriteLine(domain); } }
/// <summary> /// 对于设置了镜像存储的空间,从镜像源站抓取指定名称的资源并存储到该空间中 /// </summary> public static void prefetch() { Mac mac = new Mac(Settings.AccessKey, Settings.SecretKey); string bucket = "BUCKET"; string key = "KEY"; BucketManager bm = new BucketManager(mac); bm.prefetch(bucket, key); }
/// <summary> /// 获取空间文件列表 /// /// BucketManager.listFiles(bucket, prefix, marker, limit, delimiter) /// /// bucket: 目标空间名称 /// /// prefix: 返回指定文件名前缀的文件列表(prefix可设为null) /// /// marker: 考虑到设置limit后返回的文件列表可能不全(需要重复执行listFiles操作) /// 执行listFiles操作时使用marker标记来追加新的结果 /// 特别注意首次执行listFiles操作时marker为null /// /// limit: 每次返回结果所包含的文件总数限制(limit<=1000,建议值100) /// /// delimiter: 分隔符,比如-或者/等等,可以模拟作为目录结构(参考下述示例) /// 假设指定空间中有2个文件 fakepath/1.txt fakepath/2.txt /// 现设置分隔符delimiter = / 得到返回结果items =[],commonPrefixes = [fakepath/] /// 然后调整prefix = fakepath/ delimiter = null 得到所需结果items = [1.txt,2.txt] /// 于是可以在本地先创建一个目录fakepath,然后在该目录下写入items中的文件 /// /// </summary> public static void listFiles() { Mac mac = new Mac(Settings.AccessKey, Settings.SecretKey); string bucket = "BUCKET"; string marker = ""; // 首次请求时marker必须为空 string prefix = null; // 按文件名前缀保留搜索结果 string delimiter = null; // 目录分割字符(比如"/") int limit = 100; // 最大值1000 BucketManager bm = new BucketManager(mac); List<FileDesc> items = new List<FileDesc>(); List<string> commonPrefixes = new List<string>(); do { ListFilesResult result = bm.listFiles(bucket, prefix, marker, limit, delimiter); marker = result.Marker; if (result.Items != null) { items.AddRange(result.Items); } if (result.CommonPrefixes != null) { commonPrefixes.AddRange(result.CommonPrefixes); } } while (!string.IsNullOrEmpty(marker)); foreach (string cp in commonPrefixes) { System.Console.WriteLine(cp); } foreach(var item in items) { System.Console.WriteLine(item.Key); } }
/// <summary> /// 拉取资源到空间 /// </summary> public static void fetch() { Mac mac = new Mac(Settings.AccessKey, Settings.SecretKey); string bucket = "BUCKET"; string saveKey = "SAVE_KEY"; string remoteUrl = "REMOTE_URI"; BucketManager bm = new BucketManager(mac); bm.fetch(remoteUrl, bucket, saveKey); }
/// <summary> /// load sync settings, this method is called before the page loaded method /// </summary> /// <param name="syncSetting"></param> public void LoadSyncSetting(SyncSetting syncSetting) { if(syncSetting==null) { isLoadedFromRecord = false; this.syncSetting = new SyncSetting(); } else { isLoadedFromRecord = true; this.syncSetting = syncSetting; } this.bucketManager = null; }
/// <summary> /// 删除空间中指定文件 /// </summary> public static void delete() { Mac mac = new Mac(Settings.AccessKey, Settings.SecretKey); string bucket = "BUCKET"; string key = "KEY"; BucketManager bm = new BucketManager(mac); HttpResult result = bm.delete(bucket, key); }
/// <summary> /// 使用stat模拟操作来检查Account是否正确 /// </summary> /// <returns></returns> private bool ValidateAccount(Account account) { Dispatcher.Invoke(new Action(delegate { this.SettingsErrorTextBlock.Text = ""; })); //check ak & sk validity Mac mac = new Mac(account.AccessKey, account.SecretKey); BucketManager bucketManager = new BucketManager(mac); StatResult statResult = bucketManager.stat("NONE_EXIST_BUCKET", "NONE_EXIST_KEY"); if (statResult.ResponseInfo.isNetworkBroken()) { Log.Error("stat file network error, " + statResult.ResponseInfo.ToString()); Dispatcher.Invoke(new Action(delegate { this.SettingsErrorTextBlock.Text = "网络故障"; })); } else { if (statResult.ResponseInfo.StatusCode == 401) { Log.Error("ak & sk wrong"); Dispatcher.Invoke(new Action(delegate { this.SettingsErrorTextBlock.Text = "AK 或 SK 设置不正确"; })); } else if (statResult.ResponseInfo.StatusCode == 612 || statResult.ResponseInfo.StatusCode == 631) { Log.Info("ak & sk is valid"); Dispatcher.Invoke(new Action(delegate { this.SettingsErrorTextBlock.Text = ""; this.mainWindow.GotoHomePage(); })); } else { Log.Error(string.Format("valid ak&sk unknown error, {0}:{1}:{2}:{3}", statResult.ResponseInfo.StatusCode, statResult.ResponseInfo.Error, statResult.ResponseInfo.ReqId, statResult.Response)); Dispatcher.Invoke(new Action(delegate { this.SettingsErrorTextBlock.Text = "未知错误,请联系七牛"; })); } } return true; }