From 9ee7d3bd9c58a204b1efe38e6be61155bbb15c16 Mon Sep 17 00:00:00 2001
From: chr <chr@chr.com>
Date: 星期三, 21 八月 2024 19:16:22 +0800
Subject: [PATCH] Merge branch 'master' of http://10.10.28.13:8080/r/PDM/PdmSwPlugin2

---
 PdmAlert/Entity/MsgData.cs                    |    8 
 PdmAlert/app.manifest                         |   79 +++
 PdmAlert/MainWindow.xaml.cs                   |  171 +++++++
 PdmAlert/LoginWindow.xaml                     |   26 +
 PdmAlert/Util.cs                              |  452 +++++++++++++++++++
 PdmAlert/Encryptor.cs                         |   63 ++
 PdmAlert/App.xaml.cs                          |   23 
 PdmAlert/PdmAlert.csproj                      |   14 
 PdmAlert/MainWindow.xaml                      |  182 ++++---
 PdmAlert/lib/Newtonsoft.Json.dll              |    0 
 PdmAlert/Icon/iconfont.ttf                    |    0 
 PdmAlert/LoginUser.cs                         |  117 +++++
 PdmAlert/.vs/PdmAlert/v17/DocumentLayout.json |  184 ++++++++
 PdmAlert/LoginWindow.xaml.cs                  |   24 
 PdmAlert/DockApp.cs                           |    1 
 15 files changed, 1,231 insertions(+), 113 deletions(-)

diff --git a/PdmAlert/.vs/PdmAlert/v17/DocumentLayout.json b/PdmAlert/.vs/PdmAlert/v17/DocumentLayout.json
new file mode 100644
index 0000000..fb64986
--- /dev/null
+++ b/PdmAlert/.vs/PdmAlert/v17/DocumentLayout.json
@@ -0,0 +1,184 @@
+{
+  "Version": 1,
+  "WorkspaceRootPath": "C:\\Main\\Workspace\\VisualStudio\\PdmSwPlugin2\\PdmAlert\\",
+  "Documents": [
+    {
+      "AbsoluteMoniker": "D:0:0:{FC7FBDE7-D0D2-4E79-A586-501ABC73FE46}|PdmAlert.csproj|c:\\main\\workspace\\visualstudio\\pdmswplugin2\\pdmalert\\loginuser.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+      "RelativeMoniker": "D:0:0:{FC7FBDE7-D0D2-4E79-A586-501ABC73FE46}|PdmAlert.csproj|solutionrelative:loginuser.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+    },
+    {
+      "AbsoluteMoniker": "D:0:0:{FC7FBDE7-D0D2-4E79-A586-501ABC73FE46}|PdmAlert.csproj|C:\\Main\\Workspace\\VisualStudio\\PdmSwPlugin2\\PdmAlert\\loginwindow.xaml.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+      "RelativeMoniker": "D:0:0:{FC7FBDE7-D0D2-4E79-A586-501ABC73FE46}|PdmAlert.csproj|solutionrelative:loginwindow.xaml.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+    },
+    {
+      "AbsoluteMoniker": "D:0:0:{FC7FBDE7-D0D2-4E79-A586-501ABC73FE46}|PdmAlert.csproj|C:\\Main\\Workspace\\VisualStudio\\PdmSwPlugin2\\PdmAlert\\mainwindow.xaml.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+      "RelativeMoniker": "D:0:0:{FC7FBDE7-D0D2-4E79-A586-501ABC73FE46}|PdmAlert.csproj|solutionrelative:mainwindow.xaml.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+    },
+    {
+      "AbsoluteMoniker": "D:0:0:{FC7FBDE7-D0D2-4E79-A586-501ABC73FE46}|PdmAlert.csproj|C:\\Main\\Workspace\\VisualStudio\\PdmSwPlugin2\\PdmAlert\\app.xaml.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+      "RelativeMoniker": "D:0:0:{FC7FBDE7-D0D2-4E79-A586-501ABC73FE46}|PdmAlert.csproj|solutionrelative:app.xaml.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+    },
+    {
+      "AbsoluteMoniker": "D:0:0:{FC7FBDE7-D0D2-4E79-A586-501ABC73FE46}|PdmAlert.csproj|c:\\main\\workspace\\visualstudio\\pdmswplugin2\\pdmalert\\loginwindow.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}",
+      "RelativeMoniker": "D:0:0:{FC7FBDE7-D0D2-4E79-A586-501ABC73FE46}|PdmAlert.csproj|solutionrelative:loginwindow.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}"
+    },
+    {
+      "AbsoluteMoniker": "D:0:0:{FC7FBDE7-D0D2-4E79-A586-501ABC73FE46}|PdmAlert.csproj|C:\\Main\\Workspace\\VisualStudio\\PdmSwPlugin2\\PdmAlert\\util.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+      "RelativeMoniker": "D:0:0:{FC7FBDE7-D0D2-4E79-A586-501ABC73FE46}|PdmAlert.csproj|solutionrelative:util.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+    },
+    {
+      "AbsoluteMoniker": "D:0:0:{FC7FBDE7-D0D2-4E79-A586-501ABC73FE46}|PdmAlert.csproj|C:\\Main\\Workspace\\VisualStudio\\PdmSwPlugin2\\PdmAlert\\mainwindow.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}",
+      "RelativeMoniker": "D:0:0:{FC7FBDE7-D0D2-4E79-A586-501ABC73FE46}|PdmAlert.csproj|solutionrelative:mainwindow.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}"
+    },
+    {
+      "AbsoluteMoniker": "D:0:0:{FC7FBDE7-D0D2-4E79-A586-501ABC73FE46}|PdmAlert.csproj|C:\\Main\\Workspace\\VisualStudio\\PdmSwPlugin2\\PdmAlert\\entity\\msgdata.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+      "RelativeMoniker": "D:0:0:{FC7FBDE7-D0D2-4E79-A586-501ABC73FE46}|PdmAlert.csproj|solutionrelative:entity\\msgdata.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+    },
+    {
+      "AbsoluteMoniker": "D:0:0:{FC7FBDE7-D0D2-4E79-A586-501ABC73FE46}|PdmAlert.csproj|C:\\Main\\Workspace\\VisualStudio\\PdmSwPlugin2\\PdmAlert\\dockapp.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+      "RelativeMoniker": "D:0:0:{FC7FBDE7-D0D2-4E79-A586-501ABC73FE46}|PdmAlert.csproj|solutionrelative:dockapp.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+    },
+    {
+      "AbsoluteMoniker": "D:0:0:{FC7FBDE7-D0D2-4E79-A586-501ABC73FE46}|PdmAlert.csproj|C:\\Main\\Workspace\\VisualStudio\\PdmSwPlugin2\\PdmAlert\\encryptor.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+      "RelativeMoniker": "D:0:0:{FC7FBDE7-D0D2-4E79-A586-501ABC73FE46}|PdmAlert.csproj|solutionrelative:encryptor.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+    }
+  ],
+  "DocumentGroupContainers": [
+    {
+      "Orientation": 0,
+      "VerticalTabListWidth": 256,
+      "DocumentGroups": [
+        {
+          "DockedWidth": 200,
+          "SelectedChildIndex": 7,
+          "Children": [
+            {
+              "$type": "Document",
+              "DocumentIndex": 4,
+              "Title": "LoginWindow.xaml",
+              "DocumentMoniker": "C:\\Main\\Workspace\\VisualStudio\\PdmSwPlugin2\\PdmAlert\\LoginWindow.xaml",
+              "RelativeDocumentMoniker": "LoginWindow.xaml",
+              "ToolTip": "C:\\Main\\Workspace\\VisualStudio\\PdmSwPlugin2\\PdmAlert\\LoginWindow.xaml",
+              "RelativeToolTip": "LoginWindow.xaml",
+              "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003549|",
+              "WhenOpened": "2024-08-20T10:32:06.895Z",
+              "EditorCaption": ""
+            },
+            {
+              "$type": "Document",
+              "DocumentIndex": 5,
+              "Title": "Util.cs",
+              "DocumentMoniker": "C:\\Main\\Workspace\\VisualStudio\\PdmSwPlugin2\\PdmAlert\\Util.cs",
+              "RelativeDocumentMoniker": "Util.cs",
+              "ToolTip": "C:\\Main\\Workspace\\VisualStudio\\PdmSwPlugin2\\PdmAlert\\Util.cs",
+              "RelativeToolTip": "Util.cs",
+              "ViewState": "AQIAAKgBAAAAAAAAAAAowLMBAAARAAAA",
+              "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+              "WhenOpened": "2024-08-20T10:17:23.879Z"
+            },
+            {
+              "$type": "Document",
+              "DocumentIndex": 7,
+              "Title": "MsgData.cs",
+              "DocumentMoniker": "C:\\Main\\Workspace\\VisualStudio\\PdmSwPlugin2\\PdmAlert\\Entity\\MsgData.cs",
+              "RelativeDocumentMoniker": "Entity\\MsgData.cs",
+              "ToolTip": "C:\\Main\\Workspace\\VisualStudio\\PdmSwPlugin2\\PdmAlert\\Entity\\MsgData.cs",
+              "RelativeToolTip": "Entity\\MsgData.cs",
+              "ViewState": "AQIAAAAAAAAAAAAAAAAAAAgAAAAAAAAA",
+              "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+              "WhenOpened": "2024-08-20T09:14:07.015Z"
+            },
+            {
+              "$type": "Document",
+              "DocumentIndex": 6,
+              "Title": "MainWindow.xaml",
+              "DocumentMoniker": "C:\\Main\\Workspace\\VisualStudio\\PdmSwPlugin2\\PdmAlert\\MainWindow.xaml",
+              "RelativeDocumentMoniker": "MainWindow.xaml",
+              "ToolTip": "C:\\Main\\Workspace\\VisualStudio\\PdmSwPlugin2\\PdmAlert\\MainWindow.xaml",
+              "RelativeToolTip": "MainWindow.xaml",
+              "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003549|",
+              "WhenOpened": "2024-08-20T09:08:54.87Z",
+              "EditorCaption": ""
+            },
+            {
+              "$type": "Document",
+              "DocumentIndex": 2,
+              "Title": "MainWindow.xaml.cs",
+              "DocumentMoniker": "C:\\Main\\Workspace\\VisualStudio\\PdmSwPlugin2\\PdmAlert\\MainWindow.xaml.cs",
+              "RelativeDocumentMoniker": "MainWindow.xaml.cs",
+              "ToolTip": "C:\\Main\\Workspace\\VisualStudio\\PdmSwPlugin2\\PdmAlert\\MainWindow.xaml.cs",
+              "RelativeToolTip": "MainWindow.xaml.cs",
+              "ViewState": "AQIAAGIAAAAAAAAAAAAnwJEAAAAsAAAA",
+              "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+              "WhenOpened": "2024-08-20T09:03:17.694Z",
+              "EditorCaption": ""
+            },
+            {
+              "$type": "Document",
+              "DocumentIndex": 8,
+              "Title": "DockApp.cs",
+              "DocumentMoniker": "C:\\Main\\Workspace\\VisualStudio\\PdmSwPlugin2\\PdmAlert\\DockApp.cs",
+              "RelativeDocumentMoniker": "DockApp.cs",
+              "ToolTip": "C:\\Main\\Workspace\\VisualStudio\\PdmSwPlugin2\\PdmAlert\\DockApp.cs",
+              "RelativeToolTip": "DockApp.cs",
+              "ViewState": "AQIAABkAAAAAAAAAAAAkwA4AAAArAAAA",
+              "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+              "WhenOpened": "2024-08-20T06:58:33.822Z"
+            },
+            {
+              "$type": "Document",
+              "DocumentIndex": 9,
+              "Title": "Encryptor.cs",
+              "DocumentMoniker": "C:\\Main\\Workspace\\VisualStudio\\PdmSwPlugin2\\PdmAlert\\Encryptor.cs",
+              "RelativeDocumentMoniker": "Encryptor.cs",
+              "ToolTip": "C:\\Main\\Workspace\\VisualStudio\\PdmSwPlugin2\\PdmAlert\\Encryptor.cs",
+              "RelativeToolTip": "Encryptor.cs",
+              "ViewState": "AQIAACIAAAAAAAAAAAAawBIAAAAuAAAA",
+              "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+              "WhenOpened": "2024-08-20T06:23:54.361Z"
+            },
+            {
+              "$type": "Document",
+              "DocumentIndex": 0,
+              "Title": "LoginUser.cs",
+              "DocumentMoniker": "C:\\Main\\Workspace\\VisualStudio\\PdmSwPlugin2\\PdmAlert\\LoginUser.cs",
+              "RelativeDocumentMoniker": "LoginUser.cs",
+              "ToolTip": "C:\\Main\\Workspace\\VisualStudio\\PdmSwPlugin2\\PdmAlert\\LoginUser.cs",
+              "RelativeToolTip": "LoginUser.cs",
+              "ViewState": "AQIAAEYAAAAAAAAAAAAywFgAAAANAAAA",
+              "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+              "WhenOpened": "2024-08-20T03:50:43.52Z",
+              "EditorCaption": ""
+            },
+            {
+              "$type": "Document",
+              "DocumentIndex": 1,
+              "Title": "LoginWindow.xaml.cs",
+              "DocumentMoniker": "C:\\Main\\Workspace\\VisualStudio\\PdmSwPlugin2\\PdmAlert\\LoginWindow.xaml.cs",
+              "RelativeDocumentMoniker": "LoginWindow.xaml.cs",
+              "ToolTip": "C:\\Main\\Workspace\\VisualStudio\\PdmSwPlugin2\\PdmAlert\\LoginWindow.xaml.cs",
+              "RelativeToolTip": "LoginWindow.xaml.cs",
+              "ViewState": "AQIAAAYAAAAAAAAAAAAAABAAAAAfAAAA",
+              "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+              "WhenOpened": "2024-08-20T03:46:25.163Z",
+              "EditorCaption": ""
+            },
+            {
+              "$type": "Document",
+              "DocumentIndex": 3,
+              "Title": "App.xaml.cs",
+              "DocumentMoniker": "C:\\Main\\Workspace\\VisualStudio\\PdmSwPlugin2\\PdmAlert\\App.xaml.cs",
+              "RelativeDocumentMoniker": "App.xaml.cs",
+              "ToolTip": "C:\\Main\\Workspace\\VisualStudio\\PdmSwPlugin2\\PdmAlert\\App.xaml.cs",
+              "RelativeToolTip": "App.xaml.cs",
+              "ViewState": "AQIAAAgAAAAAAAAAAAAIwBgAAAAjAAAA",
+              "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+              "WhenOpened": "2024-08-20T03:42:24.667Z",
+              "EditorCaption": ""
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/PdmAlert/App.xaml.cs b/PdmAlert/App.xaml.cs
index 67e9db4..9740dfb 100644
--- a/PdmAlert/App.xaml.cs
+++ b/PdmAlert/App.xaml.cs
@@ -1,10 +1,4 @@
-锘縰sing System;
-using System.Collections.Generic;
-using System.Configuration;
-using System.Data;
-using System.Linq;
-using System.Threading.Tasks;
-using System.Windows;
+锘縰sing System.Windows;
 
 namespace PdmAlert
 {
@@ -14,16 +8,27 @@
     public partial class App : Application
     {
         DockApp dockApp;
+        LoginWindow loginWindow;
         protected override void OnStartup(StartupEventArgs e)
         {
             base.OnStartup(e);
             this.ShutdownMode = ShutdownMode.OnExplicitShutdown;
-            dockApp.Run();
+            if (LoginUser.TryAutoLogin())
+            {
+
+                dockApp = new DockApp();
+                dockApp.Run();
+            }
+            else
+            {
+                loginWindow = new LoginWindow();
+                loginWindow.Show();
+            }
         }
 
         private void Application_Startup(object sender, StartupEventArgs e)
         {
-            dockApp = new DockApp();
+
         }
     }
 }
diff --git a/PdmAlert/DockApp.cs b/PdmAlert/DockApp.cs
index b68feff..4e78dbc 100644
--- a/PdmAlert/DockApp.cs
+++ b/PdmAlert/DockApp.cs
@@ -38,6 +38,7 @@
             if (showWindow != null)
             {
                 showWindow.Show();
+                showWindow.Activate();
             }
             else
             {
diff --git a/PdmAlert/Encryptor.cs b/PdmAlert/Encryptor.cs
new file mode 100644
index 0000000..88768ab
--- /dev/null
+++ b/PdmAlert/Encryptor.cs
@@ -0,0 +1,63 @@
+锘縰sing System;
+using System.IO;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace PdmAlert
+{
+    public class Encryptor
+    {
+        private readonly byte[] key;
+        private readonly byte[] iv;
+
+        public Encryptor(string keyString, string ivString)
+        {
+            key = Encoding.UTF8.GetBytes(keyString);
+            iv = Encoding.UTF8.GetBytes(ivString);
+        }
+
+        public string Encrypt(string plainText)
+        {
+            using (var aes = Aes.Create())
+            {
+                aes.Key = key;
+                aes.IV = iv;
+                ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
+
+                using (MemoryStream msEncrypt = new MemoryStream())
+                {
+                    using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
+                    {
+                        using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
+                        {
+                            swEncrypt.Write(plainText);
+                        }
+                        return Convert.ToBase64String(msEncrypt.ToArray());
+                    }
+                }
+            }
+        }
+
+
+        public string Decrypt(string cipherText)
+        {
+            using (var aes = Aes.Create())
+            {
+                aes.Key = key;
+                aes.IV = iv;
+                ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
+                byte[] arr = Convert.FromBase64String(cipherText);
+                using (MemoryStream msDecrypt = new MemoryStream(arr))
+                {
+                    using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
+                    {
+                        using (StreamReader srDecrypt = new StreamReader(csDecrypt))
+                        {
+                            return srDecrypt.ReadToEnd();
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/PdmAlert/Entity/MsgData.cs b/PdmAlert/Entity/MsgData.cs
new file mode 100644
index 0000000..c428e94
--- /dev/null
+++ b/PdmAlert/Entity/MsgData.cs
@@ -0,0 +1,8 @@
+锘縩amespace PdmAlert.Entity
+{
+    public class MsgData
+    {
+        public string title { get; set; }
+        public string content { get; set; }
+    }
+}
diff --git a/PdmAlert/Icon/iconfont.ttf b/PdmAlert/Icon/iconfont.ttf
new file mode 100644
index 0000000..66fd530
--- /dev/null
+++ b/PdmAlert/Icon/iconfont.ttf
Binary files differ
diff --git a/PdmAlert/LoginUser.cs b/PdmAlert/LoginUser.cs
new file mode 100644
index 0000000..f397804
--- /dev/null
+++ b/PdmAlert/LoginUser.cs
@@ -0,0 +1,117 @@
+锘縰sing PdmAlert.Util;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net.Http;
+using System.Reflection;
+
+namespace PdmAlert
+{
+    public class LoginUser
+    {
+        public static LoginUser CurrentUser { get; private set; }
+
+        private static readonly Encryptor encryptor = new Encryptor("12345678123456781234567812345678",
+            "1234567812345678");
+
+        public string id { get; set; }
+        public string realName { get; set; }
+        public string username { get; set; }
+        public string password { get; set; }
+        public string appId { get; set; }
+        public string pluginVersion { get; set; }
+        public bool rememberMe { get; set; }
+
+        public static bool TryAutoLogin()
+        {
+            string fileContent = null;
+            if (LoadStrFromBin(ref fileContent))
+            {
+                try
+                {
+                    string jsonStr = encryptor.Decrypt(fileContent);
+                    Dictionary<string, string> user = JsonUtil.Deserialize<Dictionary<string, string>>(jsonStr);
+                    return Login(user["username"], user["password"], true);
+                }
+                catch (Exception ex)
+                {
+                    RemoveCacheBin();
+                    return false;
+                }
+
+            }
+            return false;
+        }
+
+        public static bool Login(string username, string password, bool rememberMe)
+        {
+
+            using (HttpClient client = new HttpClient
+            {
+                BaseAddress = new Uri("http://localhost:8888/pdm-web/"),
+            })
+            {
+                try
+                {
+                    Result<LoginUser> res = client.PostSyncAction<LoginUser>(new LoginUser
+                    {
+                        username = username,
+                        password = password,
+                        appId = "sockettool",
+                        pluginVersion = "0.0.0.1"
+
+                    }, "openApi/wpf/login");
+                    CurrentUser = res.HandleResult();
+                    CurrentUser.password = password;
+                    CurrentUser.rememberMe = rememberMe;
+                    if (rememberMe)
+                    {
+                        string userInfo = JsonUtil.Stringfiy(CurrentUser);
+                        SaveStrToBin(encryptor.Encrypt(userInfo));
+                    }
+                    return true;
+                }
+                catch (Exception ex)
+                {
+                    RemoveCacheBin();
+                }
+            }
+            return false;
+        }
+
+
+        public static void SaveStrToBin(string content)
+        {
+            string filePath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().FullName), "bin");
+            byte[] bytes = Convert.FromBase64String(content);
+            using (Stream stream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite))
+            using (BinaryWriter writer = new BinaryWriter(stream))
+            {
+                writer.Write(bytes);
+            }
+        }
+
+        public static bool LoadStrFromBin(ref string content)
+        {
+            string filePath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().FullName), "bin");
+            if (!File.Exists(filePath))
+            {
+                return false;
+            }
+            using (FileStream stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
+            using (BinaryReader reader = new BinaryReader(stream))
+            {
+                byte[] bytes = new byte[stream.Length];
+                reader.Read(bytes, 0, bytes.Length);
+                content = Convert.ToBase64String(bytes);
+                return true;
+            }
+        }
+
+        public static void RemoveCacheBin()
+        {
+            string filePath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().FullName), "bin");
+            File.Delete(filePath);
+        }
+    }
+}
diff --git a/PdmAlert/LoginWindow.xaml b/PdmAlert/LoginWindow.xaml
index 73fe38e..d6f4081 100644
--- a/PdmAlert/LoginWindow.xaml
+++ b/PdmAlert/LoginWindow.xaml
@@ -6,7 +6,27 @@
         xmlns:local="clr-namespace:PdmAlert"
         mc:Ignorable="d"
         Title="LoginWindow" Height="450" Width="800">
-    <Grid>
-        
-    </Grid>
+    <AdornerDecorator>
+        <Border x:Name="mainBorder">
+            <Grid Height="200" VerticalAlignment="Top" Margin="20">
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="30"/>
+                    <RowDefinition Height="30"/>
+                    <RowDefinition Height="30"/>
+                    <RowDefinition Height="50"/>
+                </Grid.RowDefinitions>
+
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="50"/>
+                    <ColumnDefinition/>
+                </Grid.ColumnDefinitions>
+                <Label Content="鐢ㄦ埛鍚�" VerticalContentAlignment="Center" Grid.Row="0" Grid.Column="0"/>
+                <TextBox x:Name="usernameTextBlock" VerticalAlignment="Stretch" VerticalContentAlignment="Center" Grid.Row="0" Grid.Column="1"/>
+                <Label Content="瀵嗙爜" VerticalContentAlignment="Center" Grid.Row="1" Grid.Column="0"/>
+                <PasswordBox x:Name="passwordTextBlock" VerticalAlignment="Stretch" VerticalContentAlignment="Center" Grid.Row="1" Grid.Column="1"/>
+                <CheckBox Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" x:Name="rememberMeInput">鑷姩鐧诲綍</CheckBox>
+                <Button Margin="0,10,0,0"  Click="Button_Click" Padding="5" Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2">鐧诲綍</Button>
+            </Grid>
+        </Border>
+    </AdornerDecorator>
 </Window>
diff --git a/PdmAlert/LoginWindow.xaml.cs b/PdmAlert/LoginWindow.xaml.cs
index 99cd421..6db9861 100644
--- a/PdmAlert/LoginWindow.xaml.cs
+++ b/PdmAlert/LoginWindow.xaml.cs
@@ -1,16 +1,4 @@
-锘縰sing System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Data;
-using System.Windows.Documents;
-using System.Windows.Input;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using System.Windows.Shapes;
+锘縰sing System.Windows;
 
 namespace PdmAlert
 {
@@ -23,5 +11,15 @@
         {
             InitializeComponent();
         }
+
+        public void Button_Click(object sender, RoutedEventArgs e)
+        {
+            if (LoginUser.Login(usernameTextBlock.Text, passwordTextBlock.Password, rememberMeInput.IsChecked == true))
+            {
+
+
+            }
+
+        }
     }
 }
diff --git a/PdmAlert/MainWindow.xaml b/PdmAlert/MainWindow.xaml
index 1f51b53..3defeb9 100644
--- a/PdmAlert/MainWindow.xaml
+++ b/PdmAlert/MainWindow.xaml
@@ -6,115 +6,135 @@
         xmlns:local="clr-namespace:PdmAlert"
         mc:Ignorable="d"
         WindowStyle="None"
+        Topmost="True"
         ResizeMode="NoResize"
         WindowStartupLocation="Manual"
         Loaded="Window_Loaded"
+        Deactivated="Window_Deactivated"
+        ShowInTaskbar="False"
         Title="MainWindow" Height="600" Width="400">
     <Window.Resources>
-        <Brush x:Key="MainBorder">#0d4d84</Brush>
-        <Brush x:Key="MainMenu">#1d6eb2</Brush>
-        <Brush x:Key="ChildBorder">#3692e1</Brush>
-        <Brush x:Key="MainStatusBar">#1d6eb2</Brush>
+        <ResourceDictionary>
+            <FontFamily x:Key="iconfont">
+                /PdmAlert;component/Icon/#iconfont
+            </FontFamily>
+            <Brush x:Key="MainBorder">#0d4d84</Brush>
+            <Brush x:Key="MainMenu">#1d6eb2</Brush>
+            <Brush x:Key="ChildBorder">#3692e1</Brush>
+            <Brush x:Key="MainStatusBar">#1d6eb2</Brush>
 
-        <Brush x:Key="SecondaryImportant">Orange</Brush>
-        <Brush x:Key="Important">#e17933</Brush>
-        <Brush x:Key="ImportantFont">#FFF</Brush>
+            <Brush x:Key="SecondaryImportant">Orange</Brush>
+            <Brush x:Key="Important">#e17933</Brush>
+            <Brush x:Key="ImportantFont">#FFF</Brush>
 
 
-        <Brush x:Key="ButtonColor">#103156</Brush>
-        <Brush x:Key="ButtonSelected">#02579E</Brush>
-        <Brush x:Key="ButtonFont">White</Brush>
-        <Brush x:Key="ButtonFontSelected">White</Brush>
-        
-        <Style x:Key="WindowButton" TargetType="Button">
-            <Setter Property="Background" Value="Transparent"/>
-            <Setter Property="FontWeight" Value="SemiBold"/>
-            <Setter Property="Foreground" Value="White"/>
-            <Setter Property="Width" Value="50"/>
-            <Setter Property="FontSize" Value="20"/>
-            <Setter Property="BorderThickness" Value="0"/>
-            <Setter Property="BorderBrush" Value="Transparent"/>
+            <Brush x:Key="ButtonColor">#103156</Brush>
+            <Brush x:Key="ButtonSelected">#02579E</Brush>
+            <Brush x:Key="ButtonFont">White</Brush>
+            <Brush x:Key="ButtonFontSelected">White</Brush>
+
+            <Style x:Key="WindowButton" TargetType="Button">
+                <Setter Property="FontFamily" Value="{StaticResource iconfont}"/>
+                <Setter Property="Background" Value="Transparent"/>
+                <Setter Property="FontWeight" Value="SemiBold"/>
+                <Setter Property="Foreground" Value="Black"/>
+                <Setter Property="Width" Value="50"/>
+                <Setter Property="FontSize" Value="20"/>
+                <Setter Property="BorderThickness" Value="1"/>
+                <Setter Property="BorderBrush" Value="Transparent"/>
 
 
-            <Setter Property="Template">
-                <Setter.Value>
-                    <ControlTemplate TargetType="Button">
-                        <Border x:Name="border" Width="{TemplateBinding Width}" Background="{TemplateBinding Background}" 
+                <Setter Property="Template">
+                    <Setter.Value>
+                        <ControlTemplate TargetType="Button">
+                            <Border x:Name="border" Width="{TemplateBinding Width}" Background="{TemplateBinding Background}" 
                         BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="0">
-                            <Label x:Name="label" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" 
+                                <Label x:Name="label" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" 
                            HorizontalContentAlignment="Center" VerticalContentAlignment="Center" 
                            Foreground="{TemplateBinding Foreground}"
                                    Content="{TemplateBinding Content}"/>
-                        </Border>
+                            </Border>
 
-                        <ControlTemplate.Triggers>
-                            <Trigger Property="IsMouseOver" Value="True">
-                                <Setter TargetName="border" Property="Background" Value="Gray"/>
-                            </Trigger>
-                        </ControlTemplate.Triggers>
-                    </ControlTemplate>
-                </Setter.Value>
-            </Setter>
-        </Style>
+                            <ControlTemplate.Triggers>
+                                <Trigger Property="IsMouseOver" Value="True">
+                                    <Setter TargetName="border" Property="Background" Value="Gray"/>
+                                </Trigger>
+                            </ControlTemplate.Triggers>
+                        </ControlTemplate>
+                    </Setter.Value>
+                </Setter>
+            </Style>
 
-        <Style x:Key="WindowCloseButton" BasedOn="{StaticResource WindowButton}" TargetType="Button">
-            <Setter Property="Template">
-                <Setter.Value>
-                    <ControlTemplate TargetType="Button">
-                        <Border x:Name="border" Width="{TemplateBinding Width}" Background="{TemplateBinding Background}" 
+            <Style x:Key="WindowCloseButton" BasedOn="{StaticResource WindowButton}" TargetType="Button">
+                <Setter Property="Template">
+                    <Setter.Value>
+                        <ControlTemplate TargetType="Button">
+                            <Border x:Name="border" Width="{TemplateBinding Width}" Background="{TemplateBinding Background}" 
             BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="0">
-                            <Label x:Name="label" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" 
+                                <Label x:Name="label" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" 
                HorizontalContentAlignment="Center" VerticalContentAlignment="Center" 
                Foreground="{TemplateBinding Foreground}"
                        Content="{TemplateBinding Content}"/>
-                        </Border>
+                            </Border>
 
-                        <ControlTemplate.Triggers>
-                            <Trigger Property="IsMouseOver" Value="True">
-                                <Setter TargetName="border" Property="Background" Value="Red"/>
-                            </Trigger>
-                        </ControlTemplate.Triggers>
-                    </ControlTemplate>
-                </Setter.Value>
-            </Setter>
-        </Style>
+                            <ControlTemplate.Triggers>
+                                <Trigger Property="IsMouseOver" Value="True">
+                                    <Setter TargetName="border" Property="Background" Value="Red"/>
+                                </Trigger>
+                            </ControlTemplate.Triggers>
+                        </ControlTemplate>
+                    </Setter.Value>
+                </Setter>
+            </Style>
 
-        <Style x:Key="ChildWindowButton" BasedOn="{StaticResource WindowButton}" TargetType="Button">
-            <Setter Property="FontSize" Value="15"/>
-            <Setter Property="Width" Value="30"/>
-            <Setter Property="Background" Value="{StaticResource ChildBorder}"/>
-        </Style>
+            <Style x:Key="ChildWindowButton" BasedOn="{StaticResource WindowButton}" TargetType="Button">
+                <Setter Property="FontSize" Value="15"/>
+                <Setter Property="Width" Value="30"/>
+                <Setter Property="Background" Value="{StaticResource ChildBorder}"/>
+            </Style>
 
-        <Style x:Key="ChildWindowCloseButton" BasedOn="{StaticResource ChildWindowButton}" TargetType="Button">
-            
-        </Style>
+            <Style x:Key="ChildWindowCloseButton" BasedOn="{StaticResource ChildWindowButton}" TargetType="Button">
 
-        <Style x:Key="LinkButton" TargetType="Button">
-            <Setter Property="FontFamily" Value="{StaticResource iconfont}"/>
-            <Setter Property="Foreground" Value="{StaticResource ChildBorder}"/>
+            </Style>
 
-            <Setter Property="Template">
-                <Setter.Value>
-                    <ControlTemplate TargetType="Button">
-                        <TextBlock Style="{x:Null}" x:Name="label" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
+            <Style x:Key="LinkButton" TargetType="Button">
+                <Setter Property="FontFamily" Value="{StaticResource iconfont}"/>
+                <Setter Property="Foreground" Value="{StaticResource ChildBorder}"/>
+
+                <Setter Property="Template">
+                    <Setter.Value>
+                        <ControlTemplate TargetType="Button">
+                            <TextBlock Style="{x:Null}" x:Name="label" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                            Foreground="{TemplateBinding Foreground}"
                                    Text="{TemplateBinding Content}"/>
-                    </ControlTemplate>
-                </Setter.Value>
-            </Setter>
-        </Style>
+                        </ControlTemplate>
+                    </Setter.Value>
+                </Setter>
+            </Style>
+        </ResourceDictionary>
     </Window.Resources>
-    
-    
+
+
     <StackPanel>
-        <StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right">
-            <Button Style="{StaticResource WindowButton}" Click="Button_Click">鍒锋柊</Button>
-            <Button Style="{StaticResource WindowButton}" Click="Button_Click">鍒囨崲鐢ㄦ埛</Button>
-            <Button Style="{StaticResource WindowCloseButton}" Click="Hide_Click">鏈�灏忓寲</Button>
-        </StackPanel>
-        <Grid>
-            <Button Click="Button_Click">娴嬭瘯</Button>
-        </Grid>
+        <Border BorderThickness="0" Background="LightGray">
+            <StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right">
+                <Button Style="{StaticResource WindowButton}" Click="Refresh_Click" BorderBrush="Black" ToolTip="鍒锋柊">&#xe600;</Button>
+                <Button Style="{StaticResource WindowButton}" Click="SwitchUser_Click" ToolTip="鍒囨崲鐢ㄦ埛">&#xe6ed;</Button>
+                <Button Style="{StaticResource WindowCloseButton}" Click="Hide_Click" ToolTip="鏈�灏忓寲">&#xe67a;</Button>
+            </StackPanel>
+        </Border>
+
+
+        <Label Content="{Binding messageTitle}"/>
+
+        <ListView ItemsSource="{Binding messages}">
+            <ListView.ItemTemplate>
+                <DataTemplate>
+                    <Label Content="999"/>
+                </DataTemplate>
+            </ListView.ItemTemplate>
+        </ListView>
+
     </StackPanel>
-    
+
 </Window>
diff --git a/PdmAlert/MainWindow.xaml.cs b/PdmAlert/MainWindow.xaml.cs
index 1f2fcde..8d0f0e9 100644
--- a/PdmAlert/MainWindow.xaml.cs
+++ b/PdmAlert/MainWindow.xaml.cs
@@ -2,27 +2,80 @@
 using DevComponents.DotNetBar;
 using System.Windows;
 using System.Drawing;
+using System.Net.WebSockets;
+using System;
+using System.Threading.Tasks;
+using System.Text;
+using System.Diagnostics;
+using System.Timers;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using PdmAlert.Entity;
 
 namespace PdmAlert
 {
     /// <summary>
     /// MainWindow.xaml 鐨勪氦浜掗�昏緫
     /// </summary>
-    public partial class MainWindow : Window
+    public partial class MainWindow : Window, INotifyPropertyChanged
     {
+        #region INotifyPropertyChanged
+        public virtual event PropertyChangedEventHandler PropertyChanged;
+
+        protected virtual void RaisePropertyChanged(string name)
+        {
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+        }
+
+        protected void RaiseAndSetIfChanged<T>(ref T old, T @new, [CallerMemberName] string propertyName = null)
+        {
+            old = @new;
+            if (propertyName != null)
+            {
+                RaisePropertyChanged(propertyName);
+            }
+        }
+        #endregion
+
+        private ClientWebSocket socket;
+        private Task readTask;
+        private Task writeTask;
+        private System.Timers.Timer heartbeatTimer;
+
+        private string _messageTitle = "鍏辨湁 0 鏉℃湭璇绘秷鎭�";
+
+        public string messageTitle
+        {
+            get => _messageTitle;
+            set => RaiseAndSetIfChanged(ref _messageTitle, value);
+        }
+
+        private ObservableCollection<MsgData> _messages;
+
+        public ObservableCollection<MsgData> messages
+        {
+            get => _messages;
+            set => RaiseAndSetIfChanged(ref _messages, value);
+        }
+
         public MainWindow()
         {
             InitializeComponent();
         }
 
-        private long _RunningAlertId = 0;
-        private void Button_Click(object sender, RoutedEventArgs e)
+        private void Refresh_Click(object sender, RoutedEventArgs e)
         {
-            eDesktopAlertColor color = eDesktopAlertColor.Default;
-            eAlertPosition position = eAlertPosition.BottomRight;
-            DesktopAlert.Show("123", "\uf005", eSymbolSet.Awesome, Color.Empty, color, position, 10, ++_RunningAlertId, AlertClicked);
+
 
         }
+
+        private void SwitchUser_Click(object sender, RoutedEventArgs e)
+        {
+
+
+        }
+
         private void AlertClicked(long alertId)
         {
 
@@ -35,6 +88,108 @@
 
             this.Left = screeWidth - this.Width;
             this.Top = sHeight - this.Height;
+            ConnectWebSocket();
+        }
+
+        private async Task ConnectWebSocket()
+        {
+            try
+            {
+                ClientWebSocket ws = new ClientWebSocket();
+                await ws.ConnectAsync(new Uri($"ws://localhost:8888/pdm-web/daws/{LoginUser.CurrentUser?.id}"), default);
+                socket = ws;
+                ReadMsg();
+                StartHeartbeat();
+            }
+            catch (Exception ex)
+            {
+                await ConnectWebSocket();
+            }
+        }
+
+        private void ReadMsg()
+        {
+            readTask = Task.Run(() =>
+            {
+                while (socket.State == WebSocketState.Open)
+                {
+                    byte[] buffer = new byte[1024];
+                    var task = socket.ReceiveAsync(new ArraySegment<byte>(buffer), default);
+                    task.Wait();
+                    var res = task.Result;
+                    var msg = Encoding.UTF8.GetString(buffer, 0, res.Count);
+                    HandleMsg(msg);
+                }
+            });
+        }
+
+        private void HandleMsg(string msg)
+        {
+            if (string.IsNullOrEmpty(msg) || msg == "heartcheck")
+            {
+                return;
+            }
+            Debug.WriteLine(msg);
+            Dispatcher.Invoke(() =>
+            {
+                eDesktopAlertColor color = eDesktopAlertColor.Default;
+                eAlertPosition position = eAlertPosition.BottomRight;
+                DesktopAlert.Show(msg, "\uf005", eSymbolSet.Awesome, Color.Empty, color, position, 5,
+                    DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), AlertClicked);
+            });
+        }
+
+        private void StartHeartbeat()
+        {
+            heartbeatTimer = new System.Timers.Timer(30 * 1000);
+            heartbeatTimer.Elapsed += HeartbeatTimer_Elapsed;
+            heartbeatTimer.AutoReset = true;
+            heartbeatTimer.Start();
+        }
+
+        private void HeartbeatTimer_Elapsed(object sender, ElapsedEventArgs e)
+        {
+            if (socket == null || socket.State != WebSocketState.Open)
+            {
+                DoDispose();
+                ConnectWebSocket().Wait();
+            }
+            else
+            {
+                byte[] buffer = Encoding.UTF8.GetBytes("test");
+                socket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, default);
+            }
+        }
+
+        public void DoDispose()
+        {
+            if (heartbeatTimer != null)
+            {
+                heartbeatTimer.Stop();
+                heartbeatTimer.Dispose();
+                heartbeatTimer = null;
+            }
+            if (readTask != null)
+            {
+                readTask.Dispose();
+                readTask = null;
+            }
+            if (socket != null)
+            {
+                try
+                {
+                    socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "DoDispose", default).Wait();
+                    socket.Dispose();
+                }
+                catch (Exception ex)
+                {
+
+                }
+                finally
+                {
+                    socket = null;
+                }
+            }
         }
 
         private void Hide_Click(object sender, RoutedEventArgs e)
@@ -42,5 +197,9 @@
             this.Hide();
         }
 
+        private void Window_Deactivated(object sender, EventArgs e)
+        {
+            this.Hide();
+        }
     }
 }
diff --git a/PdmAlert/PdmAlert.csproj b/PdmAlert/PdmAlert.csproj
index 74f7194..9a18d0d 100644
--- a/PdmAlert/PdmAlert.csproj
+++ b/PdmAlert/PdmAlert.csproj
@@ -54,13 +54,20 @@
     <ErrorReport>prompt</ErrorReport>
     <Prefer32Bit>true</Prefer32Bit>
   </PropertyGroup>
+  <PropertyGroup>
+    <ApplicationManifest>app.manifest</ApplicationManifest>
+  </PropertyGroup>
   <ItemGroup>
     <Reference Include="DevComponents.DotNetBar2">
       <HintPath>..\lib\DevComponents.DotNetBar2.dll</HintPath>
     </Reference>
+    <Reference Include="Newtonsoft.Json">
+      <HintPath>lib\Newtonsoft.Json.dll</HintPath>
+    </Reference>
     <Reference Include="System" />
     <Reference Include="System.Data" />
     <Reference Include="System.Drawing" />
+    <Reference Include="System.Web" />
     <Reference Include="System.Windows.Forms" />
     <Reference Include="System.Xml" />
     <Reference Include="Microsoft.CSharp" />
@@ -80,6 +87,7 @@
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
     </ApplicationDefinition>
+    <Compile Include="Util.cs" />
     <Page Include="LoginWindow.xaml">
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
@@ -93,6 +101,9 @@
       <SubType>Code</SubType>
     </Compile>
     <Compile Include="DockApp.cs" />
+    <Compile Include="Encryptor.cs" />
+    <Compile Include="Entity\MsgData.cs" />
+    <Compile Include="LoginUser.cs" />
     <Compile Include="LoginWindow.xaml.cs">
       <DependentUpon>LoginWindow.xaml</DependentUpon>
     </Compile>
@@ -119,6 +130,8 @@
       <Generator>ResXFileCodeGenerator</Generator>
       <LastGenOutput>Resources.Designer.cs</LastGenOutput>
     </EmbeddedResource>
+    <None Include="app.manifest" />
+    <Resource Include="Icon\iconfont.ttf" />
     <None Include="Properties\Settings.settings">
       <Generator>SettingsSingleFileGenerator</Generator>
       <LastGenOutput>Settings.Designer.cs</LastGenOutput>
@@ -127,7 +140,6 @@
   <ItemGroup>
     <None Include="App.config" />
   </ItemGroup>
-  <ItemGroup />
   <ItemGroup>
     <Content Include="Icon\Icon.ico">
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
diff --git a/PdmAlert/Util.cs b/PdmAlert/Util.cs
new file mode 100644
index 0000000..b5ed241
--- /dev/null
+++ b/PdmAlert/Util.cs
@@ -0,0 +1,452 @@
+锘縰sing System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using System.Web;
+using Newtonsoft.Json;
+
+namespace PdmAlert.Util
+{
+    [Serializable]
+    public class Result<T>
+    {
+        [JsonProperty("success")]
+        public bool success
+        {
+            get; set;
+        }
+
+        [JsonProperty("message")]
+        public string message
+        {
+            get; set;
+        }
+
+        [JsonProperty("code")]
+        public int code
+        {
+            get; set;
+        }
+
+        [JsonProperty("result")]
+        public T result
+        {
+            get; set;
+        }
+    }
+
+    public static class ResultHandler
+    {
+        public static T HandleResult<T>(this Result<T> result)
+        {
+            if (result.success)
+            {
+                return result.result;
+            }
+            throw new CustomWebException(result.message);
+        }
+    }
+
+    /// <summary>
+    /// 鑷畾涔変粠Web杩斿洖鐨勫紓甯�
+    /// </summary>
+    public class CustomWebException : Exception
+    {
+        public CustomWebException()
+        {
+
+        }
+
+        public CustomWebException(string message) : base(message)
+        {
+
+        }
+
+        public CustomWebException(string message, Exception inner) : base(message, inner)
+        {
+
+        }
+    }
+
+    public class JsonUtil
+    {
+        static readonly JsonSerializerSettings JsSetting = new JsonSerializerSettings()
+        {
+            NullValueHandling = NullValueHandling.Ignore
+        };
+
+        public static T Deserialize<T>(string jsonStr)
+        {
+            return JsonConvert.DeserializeObject<T>(jsonStr);
+        }
+
+        public static string Serialize(object obj)
+        {
+            return JsonConvert.SerializeObject(obj);
+        }
+
+        public static string Stringfiy(object obj)
+        {
+            return JsonConvert.SerializeObject(obj, Formatting.Indented, JsSetting);
+        }
+
+    }
+
+    public static class HttpUtil
+    {
+
+        /// <summary>
+        /// Http GET 鍚屾鑾峰彇瀵硅薄
+        /// </summary>
+        /// <typeparam name="T">瀵硅薄绫诲瀷</typeparam>
+        /// <param name="client">瀹㈡埛绔�</param>
+        /// <param name="url">url</param>
+        /// <param name="models">鍙傛暟</param>
+        /// <returns>Http杩斿洖缁撴灉</returns>
+        public static Result<T> GetSyncAction<T>(this HttpClient client, string url, params object[] models)
+        {
+            return JsonUtil.Deserialize<Result<T>>(GetSyncAction(client, url, models));
+        }
+
+        /// <summary>
+        /// Http GET 鍚屾鑾峰彇瀛楃
+        /// </summary>
+        /// <param name="client">瀹㈡埛绔�</param>
+        /// <param name="url">url</param>
+        /// <param name="models">鍙傛暟</param>
+        /// <returns>Http杩斿洖缁撴灉</returns>
+        public static string GetSyncAction(this HttpClient client, string url, params object[] models)
+        {
+            try
+            {
+                foreach (object model in models)
+                {
+                    url = url.ModelToUriParam(model);
+                }
+
+                Task<string> task = client.GetStringAsync(url);
+                task.Wait();
+                return task.Result;
+            }
+            catch (Exception e)
+            {
+                throw new CustomWebException("璇锋眰寮傚父", e);
+            }
+        }
+
+        /// <summary>
+        /// Http GET 寮傛鑾峰彇瀵硅薄
+        /// </summary>
+        /// <typeparam name="T">瀵硅薄绫诲瀷</typeparam>
+        /// <param name="client">瀹㈡埛绔�</param>
+        /// <param name="url">url</param>
+        /// <param name="models">鍙傛暟</param>
+        /// <returns>寮傛Http杩斿洖缁撴灉</returns>
+        public static async Task<Result<T>> GetAsyncAction<T>(this HttpClient client, string url, params object[] models)
+        {
+            try
+            {
+                foreach (object model in models)
+                {
+                    url = url.ModelToUriParam(model);
+                }
+                return JsonUtil.Deserialize<Result<T>>(await client.GetStringAsync(url));
+            }
+            catch (Exception e)
+            {
+                throw new CustomWebException("璇锋眰寮傚父", e);
+            }
+        }
+
+        /// <summary>
+        /// Http POST 鍚屾鑾峰彇瀵硅薄
+        /// </summary>
+        /// <typeparam name="T">瀵硅薄绫诲瀷</typeparam>
+        /// <param name="client">瀹㈡埛绔�</param>
+        /// <param name="url">url</param>
+        /// <param name="data">鍙傛暟json瀛楃</param>
+        /// <returns>Http杩斿洖缁撴灉</returns>
+        public static Result<T> PostSyncAction<T>(this HttpClient client, string url, string data)
+        {
+            StringContent content = new StringContent(data, Encoding.UTF8, "application/json");
+            Task<HttpResponseMessage> task = client.PostAsync(url, content);
+            task.Wait();
+            HttpResponseMessage response = task.Result;
+            response.EnsureSuccessStatusCode();
+            Task<string> task2 = response.Content.ReadAsStringAsync();
+            task2.Wait();
+            string responseBody = task2.Result;
+            return JsonUtil.Deserialize<Result<T>>(responseBody);
+        }
+
+        /// <summary>
+        /// Http POST 鍚屾鑾峰彇瀵硅薄
+        /// </summary>
+        /// <typeparam name="T">瀵硅薄绫诲瀷</typeparam>
+        /// <param name="client">瀹㈡埛绔�</param>
+        /// <param name="url">url</param>
+        /// <param name="content">Http璐熻浇</param>
+        /// <returns>Http杩斿洖缁撴灉</returns>
+        public static Result<T> PostSyncAction<T>(this HttpClient client, string url, HttpContent content)
+        {
+            Task<HttpResponseMessage> task = client.PostAsync(url, content);
+            task.Wait();
+            HttpResponseMessage response = task.Result;
+            response.EnsureSuccessStatusCode();
+            Task<string> task2 = response.Content.ReadAsStringAsync();
+            task2.Wait();
+            return JsonUtil.Deserialize<Result<T>>(task2.Result);
+        }
+
+        /// <summary>
+        /// Http POST 鍚屾鑾峰彇瀵硅薄
+        /// </summary>
+        /// <typeparam name="T">瀵硅薄绫诲瀷</typeparam>
+        /// <param name="client">瀹㈡埛绔�</param>
+        /// <param name="data">鍙傛暟瀵硅薄</param>
+        /// <param name="url">url</param>
+        /// <returns>Http杩斿洖缁撴灉</returns>
+        public static Result<T> PostSyncAction<T>(this HttpClient client, object data, string url)
+        {
+            string dataStr = JsonUtil.Serialize(data);
+            StringContent content = new StringContent(dataStr, Encoding.UTF8, "application/json");
+            Task<HttpResponseMessage> task = client.PostAsync(url, content);
+            task.Wait();
+            HttpResponseMessage response = task.Result;
+            response.EnsureSuccessStatusCode();
+            Task<string> task2 = response.Content.ReadAsStringAsync();
+            task2.Wait();
+            string responseBody = task2.Result;
+            return JsonUtil.Deserialize<Result<T>>(responseBody);
+        }
+
+        public static string PostSyncAction(this HttpClient client, string url, string data)
+        {
+            StringContent content = new StringContent(data, Encoding.UTF8, "application/json");
+            Task<HttpResponseMessage> task = client.PostAsync(url, content);
+            task.Wait();
+            HttpResponseMessage response = task.Result;
+            response.EnsureSuccessStatusCode();
+            Task<string> task2 = response.Content.ReadAsStringAsync();
+            task2.Wait();
+            return task2.Result;
+        }
+
+        public static async Task<T> PostAsyncAction<T>(this HttpClient client, string url, string data)
+        {
+            StringContent content = new StringContent(data, Encoding.UTF8, "application/json");
+            HttpResponseMessage response = await client.PostAsync(url, content);
+            _ = response.EnsureSuccessStatusCode();
+            return JsonUtil.Deserialize<T>(await response.Content.ReadAsStringAsync());
+        }
+
+        public static async Task<Result<T>> PostAsyncAction<T>(this HttpClient client, string url, object data)
+        {
+            string dataStr = JsonUtil.Serialize(data);
+            StringContent content = new StringContent(dataStr, Encoding.UTF8, "application/json");
+            HttpResponseMessage response = await client.PostAsync(url, content);
+            _ = response.EnsureSuccessStatusCode();
+            return JsonUtil.Deserialize<Result<T>>(await response.Content.ReadAsStringAsync());
+        }
+
+        public static async Task<Result<T>> PostAsyncAction<T>(this HttpClient client, string url, HttpContent content)
+        {
+            HttpResponseMessage response = await client.PostAsync(url, content);
+            _ = response.EnsureSuccessStatusCode();
+            return JsonUtil.Deserialize<Result<T>>(await response.Content.ReadAsStringAsync());
+        }
+
+        /// <summary>
+        /// 涓嬭浇鏂囦欢淇濆瓨鍒扮粷瀵硅矾寰�
+        /// </summary>
+        /// <param name="client"></param>
+        /// <param name="url"></param>
+        /// <param name="absolutePath"></param>
+        /// <param name="models"></param>
+        public static async Task GetDownload(this HttpClient client, string url, string absolutePath, params object[] models)
+        {
+            foreach (object model in models)
+            {
+                url = url.ModelToUriParam(model);
+            }
+            HttpResponseMessage response = await client.GetAsync(url);
+            int code = Convert.ToInt32(response.StatusCode);
+            if (code == 200)
+            {
+                using (Stream stream = await response.Content.ReadAsStreamAsync())
+                {
+                    string suffix = Path.GetExtension(response.RequestMessage.RequestUri.ToString());
+                    using (FileStream fs = new FileStream(absolutePath, FileMode.OpenOrCreate))
+                    {
+                        byte[] buffer = new byte[1024];
+                        int readLength = 0;
+                        int length;
+                        while ((length = await stream.ReadAsync(buffer, 0, buffer.Length)) != 0)
+                        {
+                            readLength += length;
+                            fs.Write(buffer, 0, length);
+                        }
+                    }
+                }
+            }
+            else
+            {
+                string error = "鍥剧焊涓嬭浇澶辫触";
+                if (response.Headers.TryGetValues("FileName", out IEnumerable<string> values))
+                {
+                    error = values.First();
+                }
+                throw new CustomWebException(error);
+            }
+        }
+
+        /// <summary>
+        /// 涓嬭浇鏂囦欢淇濆瓨鍒扮粷瀵硅矾寰�
+        /// </summary>
+        /// <param name="client"></param>
+        /// <param name="url"></param>
+        /// <param name="absolutePath"></param>
+        /// <param name="models"></param>
+        public static void GetDownloadSync(this HttpClient client, string url, string absolutePath, params object[] models)
+        {
+            foreach (object model in models)
+            {
+                url = url.ModelToUriParam(model);
+            }
+            Task<HttpResponseMessage> task = client.GetAsync(url);
+            task.Wait();
+            HttpResponseMessage response = task.Result;
+            int code = Convert.ToInt32(response.StatusCode);
+            if (code == 200)
+            {
+                Task<Stream> task2 = response.Content.ReadAsStreamAsync();
+                task2.Wait();
+                using (Stream stream = task2.Result)
+                {
+                    string suffix = Path.GetExtension(response.RequestMessage.RequestUri.ToString());
+                    using (FileStream fs = new FileStream(absolutePath, FileMode.OpenOrCreate))
+                    {
+                        byte[] buffer = new byte[1024];
+                        int readLength = 0;
+                        int length;
+                        while ((length = stream.Read(buffer, 0, buffer.Length)) != 0)
+                        {
+                            readLength += length;
+                            fs.Write(buffer, 0, length);
+                        }
+                    }
+                }
+            }
+            else
+            {
+                string error = "鍥剧焊涓嬭浇澶辫触";
+                if (response.Headers.TryGetValues("FileName", out IEnumerable<string> values))
+                {
+                    error = HttpUtility.UrlDecode(values.First());
+                }
+                throw new CustomWebException(error);
+            }
+        }
+
+        /// <summary>
+        /// 涓嬭浇鏂囦欢鍒版寚瀹氭枃浠跺す
+        /// </summary>
+        /// <param name="client"></param>
+        /// <param name="url"></param>
+        /// <param name="dir"></param>
+        /// <param name="fileName"></param>
+        /// <param name="models"></param>
+        public static async Task GetDownload(this HttpClient client, string url, string dir, string fileName, params object[] models)
+        {
+            foreach (object model in models)
+            {
+                url = url.ModelToUriParam(model);
+            }
+            HttpResponseMessage response = await client.GetAsync(url);
+            using (Stream stream = await response.Content.ReadAsStreamAsync())
+            {
+                string suffix = Path.GetExtension(response.RequestMessage.RequestUri.ToString());
+                using (FileStream fs = new FileStream($"{dir}/{fileName}", FileMode.CreateNew))
+                {
+                    byte[] buffer = new byte[1024];
+                    int readLength = 0;
+                    int length;
+                    while ((length = await stream.ReadAsync(buffer, 0, buffer.Length)) != 0)
+                    {
+                        readLength += length;
+                        fs.Write(buffer, 0, length);
+                    }
+                }
+            }
+        }
+
+        public static async void GetDownloadWithName(this HttpClient client, string url, string dir, params object[] models)
+        {
+            foreach (object model in models)
+            {
+                url = url.ModelToUriParam(model);
+            }
+
+            HttpResponseMessage response = await client.GetAsync(url);
+            string fileName = HttpUtility.UrlDecode(response.Headers.GetValues("FileName").FirstOrDefault());
+            using (Stream stream = await response.Content.ReadAsStreamAsync())
+            {
+                string suffix = Path.GetExtension(response.RequestMessage.RequestUri.ToString());
+                using (FileStream fs = new FileStream($"{dir}/{fileName}", FileMode.CreateNew))
+                {
+                    byte[] buffer = new byte[1024];
+                    int readLength = 0;
+                    int length;
+                    while ((length = await stream.ReadAsync(buffer, 0, buffer.Length)) != 0)
+                    {
+                        readLength += length;
+                        fs.Write(buffer, 0, length);
+                    }
+                }
+            }
+        }
+
+        public static string ModelToUriParam(this string url, object obj)
+        {
+            PropertyInfo[] properties = obj.GetType().GetProperties();
+            StringBuilder sb = new StringBuilder()
+                .Append(url).Append("?");
+            if (obj is Dictionary<string, string>)
+            {
+                Dictionary<string, string> dict = (Dictionary<string, string>)obj;
+                foreach (var item in dict)
+                {
+                    if (item.Value == null || string.IsNullOrWhiteSpace(item.Value))
+                    {
+                        continue;
+                    }
+                    sb = sb.Append(item.Key)
+                       .Append("=")
+                       .Append(HttpUtility.UrlEncode(item.Value))
+                       .Append("&");
+                }
+            }
+            else
+            {
+                foreach (PropertyInfo p in properties)
+                {
+                    object v = p.GetValue(obj, null);
+                    if (v == null || string.IsNullOrWhiteSpace(v.ToString()))
+                    {
+                        continue;
+                    }
+                    sb = sb.Append(p.Name)
+                        .Append("=")
+                        .Append(HttpUtility.UrlEncode(v.ToString()))
+                        .Append("&");
+                }
+            }
+            sb = sb.Remove(sb.Length - 1, 1);
+            return sb.ToString();
+        }
+    }
+}
diff --git a/PdmAlert/app.manifest b/PdmAlert/app.manifest
new file mode 100644
index 0000000..4fddf7e
--- /dev/null
+++ b/PdmAlert/app.manifest
@@ -0,0 +1,79 @@
+锘�<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+	<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
+	<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+		<security>
+			<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
+				<!-- UAC 娓呭崟閫夐」
+             濡傛灉鎯宠鏇存敼 Windows 鐢ㄦ埛甯愭埛鎺у埗绾у埆锛岃浣跨敤
+             浠ヤ笅鑺傜偣涔嬩竴鏇挎崲 requestedExecutionLevel 鑺傜偣銆�
+
+        <requestedExecutionLevel  level="asInvoker" uiAccess="false" />
+        <requestedExecutionLevel  level="requireAdministrator" uiAccess="false" />
+        <requestedExecutionLevel  level="highestAvailable" uiAccess="false" />
+
+            鎸囧畾 requestedExecutionLevel 鍏冪礌灏嗙鐢ㄦ枃浠跺拰娉ㄥ唽琛ㄨ櫄鎷熷寲銆�
+            濡傛灉浣犵殑搴旂敤绋嬪簭闇�瑕佹铏氭嫙鍖栨潵瀹炵幇鍚戝悗鍏煎鎬э紝鍒欑Щ闄ゆ
+            鍏冪礌銆�
+        -->
+				<requestedExecutionLevel  level="requireAdministrator" uiAccess="false" />
+			</requestedPrivileges>
+		</security>
+	</trustInfo>
+
+	<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+		<application>
+			<!-- 璁捐姝ゅ簲鐢ㄧ▼搴忎笌鍏朵竴璧峰伐浣滀笖宸查拡瀵规搴旂敤绋嬪簭杩涜娴嬭瘯鐨�
+           Windows 鐗堟湰鐨勫垪琛ㄣ�傚彇娑堣瘎璁洪�傚綋鐨勫厓绱狅紝
+           Windows 灏嗚嚜鍔ㄩ�夋嫨鏈�鍏煎鐨勭幆澧冦�� -->
+
+			<!-- Windows Vista -->
+			<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
+
+			<!-- Windows 7 -->
+			<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
+
+			<!-- Windows 8 -->
+			<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
+
+			<!-- Windows 8.1 -->
+			<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
+
+			<!-- Windows 10 -->
+			<!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />-->
+
+		</application>
+	</compatibility>
+
+	<!-- 鎸囩ず璇ュ簲鐢ㄧ▼搴忓彲鎰熺煡 DPI 涓� Windows 鍦� DPI 杈冮珮鏃跺皢涓嶄細瀵瑰叾杩涜
+       鑷姩缂╂斁銆俉indows Presentation Foundation (WPF)搴旂敤绋嬪簭鑷姩鎰熺煡 DPI锛屾棤闇�
+       閫夋嫨鍔犲叆銆傞�夋嫨鍔犲叆姝よ缃殑 Windows 绐椾綋搴旂敤绋嬪簭(闈㈠悜 .NET Framework 4.6)杩樺簲
+       鍦ㄥ叾 app.config 涓皢 "EnableWindowsFormsHighDpiAutoResizing" 璁剧疆璁剧疆涓� "true"銆�
+       
+       灏嗗簲鐢ㄧ▼搴忚涓烘劅鐭ラ暱璺緞銆傝鍙傞槄 https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation -->
+	<!--
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">
+    <windowsSettings>
+      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
+      <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
+    </windowsSettings>
+  </application>
+  -->
+
+	<!-- 鍚敤 Windows 鍏叡鎺т欢鍜屽璇濇鐨勪富棰�(Windows XP 鍜屾洿楂樼増鏈�) -->
+	<!--
+  <dependency>
+    <dependentAssembly>
+      <assemblyIdentity
+          type="win32"
+          name="Microsoft.Windows.Common-Controls"
+          version="6.0.0.0"
+          processorArchitecture="*"
+          publicKeyToken="6595b64144ccf1df"
+          language="*"
+        />
+    </dependentAssembly>
+  </dependency>
+  -->
+
+</assembly>
diff --git a/PdmAlert/lib/Newtonsoft.Json.dll b/PdmAlert/lib/Newtonsoft.Json.dll
new file mode 100644
index 0000000..341d08f
--- /dev/null
+++ b/PdmAlert/lib/Newtonsoft.Json.dll
Binary files differ

--
Gitblit v1.9.1