首页 > 代码库 > QiniuUpload- 一个方便用七牛做图床然后插入markdown的小工具

QiniuUpload- 一个方便用七牛做图床然后插入markdown的小工具

  最近一段时间有用markdown做笔记,其他都好,但是markdown插入图片挺麻烦的,特别是想截图之后直接插入的时候。需要首先把图片保存了,然后还要上传到一个地方生成链接才能插入。如果有个工具可以直接上传图片或者截图生成markdown可以用的链接就好了。所以决定自己下班后写一个,不过自己挺菜的,也就能用,代码完全是渣不能看。。。在这里把自己的思路还有其中遇到的问题记录一下。

  首先需要选一个图床,我选了七牛,主要是一个是有免费的空间,加上提供了SDK,这样就能写程序上传了。语言挑了C#,因为感觉WPF写界面还算方便吧。根据七牛文档写得,首先要用nuget下载官方的SDK,这个可以参考http://developer.qiniu.com/code/v6/sdk/csharp.html。

  所要完成的功能:能够选择图片上传,能够自动上传截图,生成markdown可用的链接

1、界面

主界面:

  技术分享

设置账号界面:

技术分享

技术分享
 1 <Window x:Class="QiniuUpload.MainWindow"
 2         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 3         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 4         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
 5         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 6         xmlns:local="clr-namespace:QiniuUpload"
 7         mc:Ignorable="d"
 8         Title="七牛上传" Height="316.606" Width="419.679" ResizeMode="CanMinimize" WindowStartupLocation="Manual" Name="mainwin">
 9     <Grid>
10         <Grid.RowDefinitions>
11             <RowDefinition Height="50"/>
12             <RowDefinition Height="220*"/>
13             <RowDefinition Height="49*"/>
14         </Grid.RowDefinitions>
15         <StackPanel Grid.Row="0" Orientation="Horizontal">
16             <Button x:Name="AccountButton" Content="账号" HorizontalAlignment="Left"  Width="80" Height="24" Margin="10,0,0,0" Click="AccountButton_Click"/>
17             <Button x:Name="HistoryButton" Content="历史" HorizontalAlignment="Left"  Width="80" Height="24" Margin="10,0,0,0"/>
18             <Button x:Name="CBViewerButton" Content="监控剪切板"  HorizontalAlignment="Left" Width="80" Height="24" Margin="10,0,0,0" Click="CBViewerButton_Click"/>
19             <Button x:Name="InformationButton" Content="关于"  HorizontalAlignment="Left" Width="80" Height="24" Margin="10,0,0,0"/>
20         </StackPanel>
21         <DockPanel x:Name="UpLoadZone"  Grid.Row="1" Width="412" Height="180" MouseLeftButtonDown="UpLoadZone_MouseLeftButtonDown" Background="Transparent" Grid.ColumnSpan="2" Margin="0,0,0,36" Grid.RowSpan="2">
22         </DockPanel>
23         <Grid Grid.Row="2" Margin="10,0,0,10">
24             <Grid.ColumnDefinitions>
25                 <ColumnDefinition Width="3*"/>
26                 <ColumnDefinition Width="1*"/>
27             </Grid.ColumnDefinitions>
28             <TextBlock x:Name="MessageText" Grid.Column="0" HorizontalAlignment="Left" />
29             <Button x:Name="CopyButton" Grid.Column ="1" Content="复制" Width="60" Height="26" VerticalAlignment="Center" HorizontalAlignment="Center" Click="CopyButton_Click"/>
30 
31         </Grid>
32 
33     </Grid>
34 </Window>
View Code
技术分享
 1 <Window x:Class="QiniuUpload.SetAccountWindow"
 2         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 3         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 4         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
 5         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 6         xmlns:local="clr-namespace:QiniuUpload"
 7         mc:Ignorable="d"
 8         Title="SetAccountWindow" Height="197.6" Width="300" ResizeMode="CanMinimize">
 9     <Grid>
10         <Grid.RowDefinitions>
11             <RowDefinition Height="1*"/>
12             <RowDefinition Height="1*"/>
13             <RowDefinition Height="1*"/>
14             <RowDefinition Height="1*"/>
15             <RowDefinition Height="1*"/>
16         </Grid.RowDefinitions>
17         <Grid.ColumnDefinitions>
18             <ColumnDefinition Width="85*"/>
19             <ColumnDefinition Width="209*"/>
20         </Grid.ColumnDefinitions>
21         <Label Content="七牛空间名" Grid.Column="0" Grid.Row="0" Width="70" Height="26" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="8,4,7,4"/>
22         <Label Content="AccessKey" Grid.Column="0" Grid.Row="1" Width="70" Height="26" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="8,4,7,4"/>
23         <Label Content="SecretKey" Grid.Column="0" Grid.Row="2" Width="70" Height="26" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="8,4,7,4"/>
24         <Label Content="域名https:\\" Grid.Column="0" Grid.Row="3" Width="86" Height="26" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="8,4,200,4" Grid.ColumnSpan="2"/>
25         <Button x:Name="OK_Button" Content="确定" Grid.Column="1" HorizontalAlignment="Left" Grid.Row="4" Width="53" Margin="20,6,0,7" Click="OK_Button_Click"/>
26         <Button x:Name="Cancel_Button" Content="取消" Grid.Column="1" HorizontalAlignment="Left" Grid.Row="4" Width="53" Margin="90,6,0,7" Click="Cancel_Button_Click"/>
27         <TextBox x:Name="SpaceName" Grid.Column="1" Grid.Row="0" TextWrapping="Wrap" Margin="8,7"/>
28         <TextBox x:Name="AccessKey" Grid.Column="1" Grid.Row="1" TextWrapping="Wrap" Margin="8,7" FontSize="8"/>
29         <TextBox x:Name="SecretKey" Grid.Column="1" Grid.Row="2" TextWrapping="Wrap" Margin="8,7" FontSize="8"/>
30         <TextBox x:Name="HostName" Grid.Column="1" Grid.Row="3" TextWrapping="Wrap" Margin="8,7"/>
31 
32     </Grid>
33 </Window>
View Code

2. 选择图片上传功能

点击中间区域弹出文件浏览框,选择图片后上传。这个通过绑定控件的鼠标事件来完成,上传图片用到了七牛提供的API。主体函数如下,首先是通过比对文件的hash值来判断是否本地以及远程已经有过上传,如果没有上传过,就通过七牛提供的上传API进行上传,并在预览区显示图片。

技术分享
 1 private bool UpLoadFile(string filepath)
 2         {
 3             string filename = System.IO.Path.GetFileName(filepath);
 4             Qiniu.Util.Mac lMac = new Qiniu.Util.Mac(UserAccount.AccessKey, UserAccount.SecretKey);
 5             string lRemoteHash = RemoteFileInfo.RemoteFileStat(lMac, UserAccount.SpaceName, filename);
 6             bool lSkip = false;
 7             bool lUpLoadSuccess = false;
 8             //check local
 9             string lLocalHash = String.Empty;
10             if (!string.IsNullOrEmpty(lRemoteHash))
11             {
12                 lLocalHash = QETag.hash(filepath);
13 
14                 if (historyLog.ContainsKey(lLocalHash))
15                 {
16                     if(historyLog[lLocalHash].Contains(filename))
17                     {
18                         lSkip = true;
19                         URL = CreateURL(filename);
20                         lUpLoadSuccess = true;
21                     }                   
22                 }
23                 else if (string.Equals(lLocalHash, lRemoteHash))
24                 {
25                     lSkip = true;
26                     URL = CreateURL(filename);
27                     lUpLoadSuccess = true;
28                 }
29             }
30             if (!lSkip)
31             {
32                 putPolicy.Scope = UserAccount.SpaceName;
33                 putPolicy.SetExpires(3600 * 24 * 30);
34                 string lUploadToken = Auth.createUploadToken(putPolicy, lMac);
35                 UploadManager lUploadMgr = new UploadManager();
36                 lUploadMgr.uploadFile(filepath, filename, lUploadToken, null, new UpCompletionHandler(delegate (string key, ResponseInfo responseinfo, string response)
37                 {
38                     if (responseinfo.StatusCode != 200)
39                     {
40                         MessageStr = Properties.Resources.ErrorMessage;
41                     }
42                     else
43                     {
44                         MessageStr = Properties.Resources.SuccessMessage;
45                         if (historyLog.ContainsKey(lLocalHash))
46                         {
47                             historyLog[lLocalHash].Add(filename);
48                         }
49                         URL = CreateURL(filename);
50                         lUpLoadSuccess = true;
51                     }
52                 }));                
53             }
54 
55             if (lUpLoadSuccess)
56             {
57                 DisplayImage(filepath);
58                 MessageStr = URL;
59             }
60 
61             return lUpLoadSuccess;
62         }
View Code

3. 截图上传

为了完成这个,搜了下资料,需要用到win32的两个函数,分别是AddClipboardFormatListener以及RemoveClipboardFormatListener,然后检查系统消息是否是WM_CLIPBOARDUPDATE。为了给用户提供选择是否开启这个,在主界面上添加了一个button来控制是否开启。因为七牛SDK的上传api的参数是一个文件路径,所以这里我首先会将截图存储到本地再上传。

技术分享
 1     private void CBViewerButton_Click(object sender, RoutedEventArgs e)
 2         {
 3             if(!IsViewing)
 4             {
 5                 InitCBViewer();
 6                 this.CBViewerButton.Content = "停止监控";
 7             }
 8             else
 9             {
10                 StopCBViewer();
11                 this.CBViewerButton.Content = "监控剪切板";
12             }
13         }
14         private void InitCBViewer()
15         {
16             WindowInteropHelper lwindowih = new WindowInteropHelper(this);
17             hWndSource = HwndSource.FromHwnd(lwindowih.Handle);
18 
19             hWndSource.AddHook(this.WndProc);
20             Win32.AddClipboardFormatListener(hWndSource.Handle);
21             IsViewing = true;
22         }
23 
24         private void StopCBViewer()
25         {
26             Win32.RemoveClipboardFormatListener(hWndSource.Handle);
27             hWndSource.RemoveHook(this.WndProc);
28             IsViewing = false;
29         }
30 
31 private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParm, ref bool handled)
32         {
33             if(msg == Win32.WM_CLIPBOARDUPDATE)
34             {
35                 ProcessClipBoard();
36             }
37             return IntPtr.Zero;
38         }
39 
40  private void ProcessClipBoard()
41         {
42             if(System.Windows.Clipboard.ContainsImage())
43             {
44                 MessageStr = Properties.Resources.UpLoading;
45 
46                 BmpBitmapEncoder enc = new BmpBitmapEncoder();
47                 enc.Frames.Add(BitmapFrame.Create(System.Windows.Clipboard.GetImage()));
48 
49                 string lFileName = CreateFileName();
50                 string lSaveFilePath = System.IO.Path.Combine(Properties.Resources.ImageSavePathDir, lFileName);
51                 using (FileStream fs = new FileStream(lSaveFilePath, FileMode.OpenOrCreate, FileAccess.Write))
52                 {
53                     enc.Save(fs);
54                     fs.Close();
55                 }
56 
57                 //because unkown reason, when use wechat snapshot hotkey, the message will process twice, to avoid this, check whether we save the same file
58                 string lLocalHash = QETag.hash(lSaveFilePath);
59                 if(historyLog.ContainsKey(lLocalHash))
60                 {
61                     File.Delete(lSaveFilePath);
62                     URL = CreateURL(historyLog[lLocalHash][0]);
63                     lSaveFilePath = System.IO.Path.Combine(Properties.Resources.ImageSavePathDir, historyLog[lLocalHash][0]);                   
64                 }
65                 else
66                 {                   
67                     if(!UpLoadFile(lSaveFilePath))
68                     {
69                         File.Delete(lSaveFilePath);
70                     }
71                 }                
72             }
73         }
View Code

PS:在做这个的时候我碰到一个问题就是当我用微信的截图快捷键的时候,这个Clipboard事件会被触发两次,为了解决这个,我的做法是判断本地的上传记录,比对文件的hash值。如果已经上传过,就把后来又存储的文件给删除掉。目前我也想不到其他方式,暂且这样处理吧。

4. 账号设置

用七牛做图床需要设置四个参数,分别是目标空间名,Accesskey, secrectkey以及域名。为了方便存储和读取,用了C#里的xml序列化和反序列化

技术分享
1  if (File.Exists(Properties.Resources.ConfigFilePath))
2             {
3                 XmlSerializer xs = new XmlSerializer(typeof(AccountInfo));
4                 using (Stream s = File.OpenRead(Properties.Resources.ConfigFilePath))
5                 {
6                     UserAccount = (AccountInfo)(xs.Deserialize(s));
7                 }
8             }
View Code
技术分享
1  using (Stream s = File.OpenWrite(Properties.Resources.ConfigFilePath))
2             {
3                 XmlSerializer xs = new XmlSerializer(typeof(AccountInfo));
4                 xs.Serialize(s, MainWindow.UserAccount);
5             }
View Code

 

5. 未完成功能:历史记录查看等

完整代码:

https://github.com/HaoLiuHust/QiniuUpload

 

PS: 代码很烂,还需要多练

QiniuUpload- 一个方便用七牛做图床然后插入markdown的小工具