Windows PowerShell 七牛云批量自动上传文件

本文首发于个人公众号,欢迎关注。


这篇内容设计一些C#的知识点,或者你有Java基础也能看得懂,不然看起来可能有点费力吧,我估计o(╯□╰)o
因为微信公众号自带的编辑页面不支持语法高亮等高级编辑用途,所以我一直在用Md2All在线编辑公众号文章,这是一个用Markdown语法编辑文章的网站,推荐给大家使用。

最近遇到的一个问题是,几乎我每次编辑新的文章都要用到图片,在Md2All需要用到图片外链插入图片,搞得我每个图片都要先上传到微信,然后再用F12去抓取外链,很麻烦事。

1-Get-Wechat-Image

所以就上网找了一个类似云盘的一个网站:七牛云,专门用来存储图片。它有丰富的API可以用来自动化操作,这正是我想要的,现在新人注册后实名制验证会有10GB的空间。

注册完并且创建好存储空间后找到并记下以下5个参数,后面脚本要用到。

  1. Domain (图中外链默认域名)
  2. AccessKey
  3. SecretKey
  4. Bucket
  5. Zone

32-2 七牛云API参数

2 七牛云API参数

我们可以看到主流语言的SDK都有,虽然没有powershell,但是有C#的API我们就可以用powershell来实现。接下来开始分析实现步骤。

32-3 七牛云开发者中心

3 七牛云开发者中心

分析

我们按照C# API的安装步骤下载好zip解压好打开.\tools\net40,里面的Newtonsoft.Json.dll和nunit.framework.dll就是C#语言需要用到的2个DLL文件。

32-4 Dependency DLL

4 Dependency DLL

但是我们现在要用powershell,那么就要把七牛云C#工程本身编译为DLL。这里推荐用VS(Visual Studio)编译,目前社区版都是免费的了。还是打开之前下载下来的解压后的文件夹,按照下面步骤编译,编译好后在.\bin\文件下找到Qiniu.dll。

32-5 编译七牛C#主DLL

5 编译七牛C#主DLL

至此powershell所需的3个DLL完全搞定了,我们把它们3个放在一个相同的文件下(假使c:\temp\qiniu),并和上面的5个参数一起保存到XML配置文件里面(文末我会上传所有文件,你下载下来后参数改成自己对应的就好了)。然后就可以准备写powershell脚本。

32-6 XML配置文件

6 XML配置文件

1. 分析C#代码

32-7 C#上传文件代码

7 C#上传文件代码


在着手写powershell代码之前,我们要先看下C#的源码。我们还在之前七牛云开发者中心页面查看C#上传文件的实例代码:

Mac mac = new Mac(AccessKey, SecretKey);
// 上传文件名
string key = "key";
// 本地文件路径
string filePath = "D:\\tools\\putty.exe";
// 存储空间名
string Bucket = "7qiniu";
// 设置上传策略,详见:https://developer.qiniu.com/kodo/manual/1206/put-policy
PutPolicy putPolicy = new PutPolicy();
putPolicy.Scope = Bucket;
putPolicy.SetExpires(3600);
putPolicy.DeleteAfterDays = 1;
string token = Auth.CreateUploadToken(mac, putPolicy.ToJsonString());
Config config = new Config();
// 设置上传区域
config.Zone = Zone.ZONE_CN_East;
// 设置 http 或者 https 上传
config.UseHttps = true;
config.UseCdnDomains = true;
config.ChunkSize = ChunkUnit.U512K;
// 表单上传
FormUploader target = new FormUploader(config);
HttpResult result = target.UploadFile(filePath, key, token, null);
Console.WriteLine("form upload result: " + result.ToString());

我们以第一行Mac mac = new Mac(AccessKey, SecretKey);为例,看下怎么转化为powershell代码。首先我们要找到Mac这个类的完整命名空间,可在VS里面找到如下结构Qiniu-->Util-->Mac

32-8 完整类目录

8 完整类目录


那么在powershell中实现同样的创建一个新的Mac类的方法如下,其他类的创建同理:

# Mac mac = new Mac(AccessKey, SecretKey);  # C#语法
$msc=[Qiniu.Util.Mac]::new($AccessKey, $SecretKey) # powershell语法
# we run below commnad to get the $msc type and found its name is Mac
# which means we successfully create the Mac class
$msc.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    Mac                                      System.Object

开发powershell脚本

请确保这个时候上文说的3个DLL文件和XML文件你已经修改保存好。

说下我个人思路,大家在用的时候不一定要和我的一致。

  1. 指定一个文件夹,脚本读取里面的文件然后上传
  2. 可以过滤文件,比如只上传.txt或者.jpg等文件
  3. 需要知道每一个文件上传成功与否,文件在七牛云的名字以及对应的外链,这个我用csv存储最终结果
  4. 确保返回的外链是真是有效可访问的
设置参数

注释说明一切,大家自己看吧。

[CmdletBinding()]param(
    [ValidateScript({test-path $_})]                           
    [string]$upload_folder=$PSScriptRoot,                       # the folder contains the files we need to upload, default current folder
    [Parameter(Mandatory=$False)]                               
    [string]$file_filter='*',                                   # what kind of files we care to upload, such extension like txt, jpg ...
    [ValidateScript({test-path $_})]                                        
    [string]$qiniu_config="$PSScriptRoot\qiniu_config.xml",     # the qiuniu config we need to load for all the key values
    [string]$log_file="$PSScriptRoot\Upload-FileToQiNiu.log",   # the log file we record process
    [ValidateScript({$_ -imatch "\.csv$"})]                     # must be csv file
    [string]$out_file=("$PSScriptRoot\{0}-Upload-FileToQiNiu.csv" -f (Get-Date).ToString("MMdd-HHmm")) # the csv file to save the result
)
用到的函数
Function Write-Log(){
    ###省略不写了,占文章空间,文末会有码云连接
}
Function Test-URIConnection($uri){ 
    # this is used to test if the uploaded file external link can be access when it's been uploaded to qiniu
    # reference link https://www.petri.com/testing-uris-urls-powershell
    $test_uri_paramter= @{
        UseBasicParsing = $True
        DisableKeepAlive = $True
        Uri = $uri
        Method = 'Head'
        ErrorAction = 4
        TimeoutSec = 30
    }
    Try{
        Switch((Invoke-WebRequest @test_uri_paramter ).statuscode){
            200     {return $true}
            default {return $false}
        }
    }Catch{
        return $false
    }Finally{}
}
Function Exit-Script(){
    ###省略不写了,占文章空间,文末会有码云连接
}

Write-Log(这个之前都有说过的)和Exit-Script都很简单,我就省略不写了,肯定一眼就能看出来作用。Test-URIConnection这个函数是我参考网友的,注释里面有参考链接,我是在他原有脚本基础上改写的,主要目的就是测试返回外链是否有效。这个用到了powershell自带的一个cmdletsInvoke-WebRequest,这个我们以后讲到网页自动化/爬虫的时候会用到,有兴趣的可以先自行了解下。

初始化,自检等
  1. 判断提供要上传的folder里面存在文件
  2. 加载XML文件
  3. 加载DLL文件
  4. 初始化XML文件里的参数

这里稍微说下,这几步关于powershell操作XML格式文件的用法还没有说过,这里大家先了解下或者自己Google先自行了解下,后期会试着专门一篇文章来专门说下。

If(!($to_be_uploaded_files=(ls (join-path (gi $upload_folder).fullname "*.$file_filter")))){
    Exit-Script "Not found any files matched filter '$file_filter' in $upload_folder" $log_file
}
Try{
    $qiniu_config_xml=[xml](cat $qiniu_config -ea 1)
}Catch{
    Exit-Script $_.Exception.Message $log_file
}
$qiniu_config_xml.Qiniu.DLL.ChildNodes.'#text'|%{
    If( test-path ($this_dll="$PSScriptRoot\$_`.dll") ){
        [void][System.Reflection.Assembly]::Load([io.file]::ReadAllBytes($this_dll))
    }else{
        Exit-Script "Not found DLL file: '$this_dll',might the XML value is empty" $log_file
    }
}
$Domain=$qiniu_config_xml.Qiniu.Domain
$AccessKey=$qiniu_config_xml.Qiniu.AccessKey
$SecretKey=$qiniu_config_xml.Qiniu.SecretKey
$Bucket=$qiniu_config_xml.Qiniu.Bucket
$Zone=$qiniu_config_xml.Qiniu.Zone
If( [string]::IsNullOrWhiteSpace($Domain) -or `
    [string]::IsNullOrWhiteSpace($AccessKey) -or `
    [string]::IsNullOrWhiteSpace($SecretKey) -or `
    [string]::IsNullOrWhiteSpace($Bucket) -or `
    [string]::IsNullOrWhiteSpace($Zone)
){
    Exit-Script "One or all of the value of |'Domain','AccessKey','SecretKey','Bucket','Zone'| in XML file is empty" $log_file
}
$Global:upload_result=@() # for the total result record
核心代码

核心代码1,C#代码转powershell代码。就按照上面那种方法一个一个给转过来就行了。

Try{
    $msc=[Qiniu.Util.Mac]::new($AccessKey, $SecretKey)
    $putPolicy=[Qiniu.Storage.PutPolicy]::new()
    $putPolicy.Scope=$Bucket
    $putPolicy.SetExpires(7200)
    $token=[Qiniu.Util.Auth]::CreateUploadToken($msc,$putPolicy.ToJsonString())
    $config =[Qiniu.Storage.Config]::new()
    $config.Zone = [Qiniu.Storage.Zone]::$Zone
    $config.UseHttps = $true;
    $config.UseCdnDomains = $true;
    $config.ChunkSize =[Qiniu.Storage.ChunkUnit]::U512K
    $target = [Qiniu.Storage.FormUploader]::new($config) 
    $result = [Qiniu.Http.HttpResult]::new()
}Catch{
    Exit-Script $_.Exception.Message $log_file
}

核心代码2,针对每一个文件,创建哈希表存储上传结果信息。关于外链,七牛云的API是不直接返回的。我上网大概搜了下,好像还要用到另一个算法的API,感觉太麻烦,就直接本地转了,用的是.NET自带的[System.Web.HttpUtility]类。

# for [System.Web.HttpUtility] use
[void][System.Reflection.Assembly]::Load([io.file]::ReadAllBytes('C:\WINDOWS\Microsoft.Net\assembly\GAC_64\System.Web\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Web.dll'))
Foreach( $file in $to_be_uploaded_files){
    $this_upload_result=[pscustomobject]@{ # for csv export use
        LocalFile=$file.fullname
        Zone=$Zone
        Bucket=$Bucket
        QiNiuFile=$file.name
        UploadSuccess=$false
        ExternalLink = "http://$Domain/" + [System.Web.HttpUtility]::UrlPathEncode($file.name).replace("#","%23")
    }
    write-host -fore yellow "Uploading $($file.fullname)"
    $result=$target.UploadFile($this_upload_result.LocalFile, $this_upload_result.QiNiuFile, $token, $null) # [void]
    If( 200 -eq $result.Code ){
        If(Test-URIConnection $this_upload_result.ExternalLink ){
            $this_upload_result.UploadSuccess=$true
        }
    }
    $Global:upload_result+=$this_upload_result
}

需要说明一下的是[System.Web.HttpUtility]的2个转码的方法都不能完全和七牛云手动上传产生的外链一致。但是如果文件名完全为中文的话,2个方法和七牛云自己转码的外链都是一样的。

$file_name='(t-=e s+t te_stt#est).jpg'
write-host ("UrlEncode    : "+[System.Web.HttpUtility]::UrlEncode($file_name))
write-host ("UrlPathEncode: "+[System.Web.HttpUtility]::UrlPathEncode($file_name))
write-host "QiniuEncode  : %28t-=e%20s+t%20te_stt%23est%29.jpg"
#<---------------------below are the output 
UrlEncode    : (t-%3de+s%2bt+te_stt%23est).jpg
UrlPathEncode: (t-=e%20s+t%20te_stt#est).jpg
QiniuEncode  : %28t-=e%20s+t%20te_stt%23est%29.jpg

比如上面例子中的这个文件:

  • UrlEncode把“=”转为“%3d”,空格转为“+”,“#”转为“%23”
  • UrlPathEncode把空格转为“%20”,“=”和“#”都没有转
  • QiniuEncode(七牛云转码的外链)把“#”转为“%23”,空格转为“%20”,“(”转为“%28”,“)”转为“%29”,“=”没有转

我实际测试的时候发现“(”和“)”不转,链接也有效,即下面2个都能work,所以我最终用了UrlPathEncode方法,并加了一个replace("#","%23")方法。这个其实就要求我们给文件起名字的时候规范点,不要起一些乱七八糟的名字。
http://pakt6u66x.bkt.clouddn.com/(test).jpg
http://pakt6u66x.bkt.clouddn.com/%28test%29.jpg

打印输出,保存结果

最后就是把结果输出到powershell控制台,然后保存到csv中。

Try{
    If(!(test-path $out_file) ){
        New-Item -Path $out_file -ItemType file -Force -ea 1|out-null
    }
    # add timestamp to the csv file
    $Global:upload_result|ft QiNiuFile,ExternalLink,UploadSuccess -Wrap # -AutoSize
    $Global:upload_result|Export-Csv -Path $out_file -UseCulture -NoTypeInformation -Encoding UTF8 -Force -ea 1
    write-log Success "The full result has been saved in '$out_file'" $log_file
    write-log Normal "<--------------------------End Of Script Upload-FileToQiNiu.ps1(UFTQ)-------------------------->`r`n" $log_file
}Catch{
    Exit-Script $_.Exception.Message $log_file
}

这里遇到一个小问题,不知道是不是powershell自己的BUG,就是同样的代码在powershell.exe和powershell_ise.exe显式的格式居然不一样,可以看到powershell_ise.exe最后一行永远对不齐。刚开始还以为我代码没写好,后来改了好几次测试都这样就放弃了,将就看吧,最终结果反正保存在CSV中了。

38-9 显式对比

9 显示对比


完全跑一遍就是昨天发的那张图片:
38-10 最终结果

10 最终结果

38-11 csv result

11 csv result

总结

完整代码和相关文件已上传码云。七牛云的API文档很长,我还没有来得及全部仔细看完,如果你在使用脚本过程中出现什么问题,欢迎留言或者加我个人微信Tivoli91,备注PowerShell。我虽不能第一时间回复,但是会在下一篇文章中回复。

参考链接
七牛云C#开发中心
测试网址有效性
System.Web.HttpUtility

如果你觉得本文有所帮助,欢迎推荐分享给你的朋友。

SPPS

转载于:https://my.oschina.net/chaoyuew/blog/1838318

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值