C#调用SmtpClient发送Hotmail邮件

以下是C#通过引用System.Net.Mail的SmtpClient,调用微软的Hotmail的Smtp服务器,去发送一封邮件

public static void SendEmail(string Email)
{
    var Emails = Email.Split(',').ToList(); //收件人,多个收件人用“,”来分割
    MailMessage mail = new MailMessage();
    mail.From = new MailAddress("xxxxx@hotmail.com");  //发送人,用自己的hotmail邮箱
    Emails.ForEach(i =>
    {
        mail.To.Add(i);
    });
    mail.Subject = "您好,这是一封测试邮件"; //邮件标题
    mail.Body =  "您好,这是一封测试邮件的具体内容"; //邮件详情 

    // 设置SMTP客户端
    using (SmtpClient client = new SmtpClient("smtp.office365.com", 587))
    {
        client.Credentials = new NetworkCredential("xxxxx@hotmail.com", "密码密码");  //发送人,自己的Hotmail用户名和密码
        client.EnableSsl = true; // 如果SMTP服务器需要安全连接,则启用

        // 发送邮件
        client.Send(mail);
    }
}

Emails:收件人,多个收件人用“,”来分割

mail.From:发送人,用自己的hotmail邮箱

SMTP服务端:用smtp.office365.com,端口587

Credentials:用的是自己的Hotmail用户名和密码

C#调用WindowsAPI模拟键盘鼠标事件

今天接到一个需求,要求开发一个定制的小工具,运行后,能自动化测试一个桌面应用,不能用按键精灵,于是,我想到了可以利用C#开发一个调用WindowsAPI的接口,来实现该需求,以下是具体步骤

首先我们先申明一个鼠标的事件类,用来标识鼠标的各个事件

    public static class Mouse
    {
        //移动鼠标 
        public const int MOUSEEVENTF_MOVE = 0x0001;
        //模拟鼠标左键按下 
        public const int MOUSEEVENTF_LEFTDOWN = 0x0002;
        //模拟鼠标左键抬起 
        public const int MOUSEEVENTF_LEFTUP = 0x0004;
        //模拟鼠标右键按下 
        public const int MOUSEEVENTF_RIGHTDOWN = 0x0008;
        //模拟鼠标右键抬起 
        public const int MOUSEEVENTF_RIGHTUP = 0x0010;
        //模拟鼠标中键按下 
        public const int MOUSEEVENTF_MIDDLEDOWN = 0x0020;
        //模拟鼠标中键抬起 
        public const int MOUSEEVENTF_MIDDLEUP = 0x0040;
        //标示是否采用绝对坐标 
        public const int MOUSEEVENTF_ABSOLUTE = 0x8000;
    }

其次,我们再申明一个键盘类,用来映射键盘的各个物理按键

    public static class Keyboard
    {
        #region bVk参数 常量定义

        public const int vbKeyLButton = 0x1;    // 鼠标左键
        public const int vbKeyRButton = 0x2;    // 鼠标右键
        public const int vbKeyCancel = 0x3;     // CANCEL 键
        public const int vbKeyMButton = 0x4;    // 鼠标中键
        public const int vbKeyBack = 0x8;       // BACKSPACE 键
        public const int vbKeyTab = 0x9;        // TAB 键
        public const int vbKeyClear = 0xC;      // CLEAR 键
        public const int vbKeyReturn = 0xD;     // ENTER 键
        public const int vbKeyShift = 0x10;     // SHIFT 键
        public const int vbKeyControl = 0x11;   // CTRL 键
        public const int vbKeyAlt = 18;         // Alt 键  (键码18)
        public const int vbKeyMenu = 0x12;      // MENU 键
        public const int vbKeyPause = 0x13;     // PAUSE 键
        public const int vbKeyCapital = 0x14;   // CAPS LOCK 键
        public const int vbKeyEscape = 0x1B;    // ESC 键
        public const int vbKeySpace = 0x20;     // SPACEBAR 键
        public const int vbKeyPageUp = 0x21;    // PAGE UP 键
        public const int vbKeyEnd = 0x23;       // End 键
        public const int vbKeyHome = 0x24;      // HOME 键
        public const int vbKeyLeft = 0x25;      // LEFT ARROW 键
        public const int vbKeyUp = 0x26;        // UP ARROW 键
        public const int vbKeyRight = 0x27;     // RIGHT ARROW 键
        public const int vbKeyDown = 0x28;      // DOWN ARROW 键
        public const int vbKeySelect = 0x29;    // Select 键
        public const int vbKeyPrint = 0x2A;     // PRINT SCREEN 键
        public const int vbKeyExecute = 0x2B;   // EXECUTE 键
        public const int vbKeySnapshot = 0x2C;  // SNAPSHOT 键
        public const int vbKeyDelete = 0x2E;    // Delete 键
        public const int vbKeyHelp = 0x2F;      // HELP 键
        public const int vbKeyNumlock = 0x90;   // NUM LOCK 键

        //常用键 字母键A到Z
        public const int vbKeyA = 65;
        public const int vbKeyB = 66;
        public const int vbKeyC = 67;
        public const int vbKeyD = 68;
        public const int vbKeyE = 69;
        public const int vbKeyF = 70;
        public const int vbKeyG = 71;
        public const int vbKeyH = 72;
        public const int vbKeyI = 73;
        public const int vbKeyJ = 74;
        public const int vbKeyK = 75;
        public const int vbKeyL = 76;
        public const int vbKeyM = 77;
        public const int vbKeyN = 78;
        public const int vbKeyO = 79;
        public const int vbKeyP = 80;
        public const int vbKeyQ = 81;
        public const int vbKeyR = 82;
        public const int vbKeyS = 83;
        public const int vbKeyT = 84;
        public const int vbKeyU = 85;
        public const int vbKeyV = 86;
        public const int vbKeyW = 87;
        public const int vbKeyX = 88;
        public const int vbKeyY = 89;
        public const int vbKeyZ = 90;

        //数字键盘0到9
        public const int vbKey0 = 48;    // 0 键
        public const int vbKey1 = 49;    // 1 键
        public const int vbKey2 = 50;    // 2 键
        public const int vbKey3 = 51;    // 3 键
        public const int vbKey4 = 52;    // 4 键
        public const int vbKey5 = 53;    // 5 键
        public const int vbKey6 = 54;    // 6 键
        public const int vbKey7 = 55;    // 7 键
        public const int vbKey8 = 56;    // 8 键
        public const int vbKey9 = 57;    // 9 键


        public const int vbKeyNumpad0 = 0x60;    //0 键
        public const int vbKeyNumpad1 = 0x61;    //1 键
        public const int vbKeyNumpad2 = 0x62;    //2 键
        public const int vbKeyNumpad3 = 0x63;    //3 键
        public const int vbKeyNumpad4 = 0x64;    //4 键
        public const int vbKeyNumpad5 = 0x65;    //5 键
        public const int vbKeyNumpad6 = 0x66;    //6 键
        public const int vbKeyNumpad7 = 0x67;    //7 键
        public const int vbKeyNumpad8 = 0x68;    //8 键
        public const int vbKeyNumpad9 = 0x69;    //9 键
        public const int vbKeyMultiply = 0x6A;   // MULTIPLICATIONSIGN(*)键
        public const int vbKeyAdd = 0x6B;        // PLUS SIGN(+) 键
        public const int vbKeySeparator = 0x6C;  // ENTER 键
        public const int vbKeySubtract = 0x6D;   // MINUS SIGN(-) 键
        public const int vbKeyDecimal = 0x6E;    // DECIMAL POINT(.) 键
        public const int vbKeyDivide = 0x6F;     // DIVISION SIGN(/) 键


        //F1到F12按键
        public const int vbKeyF1 = 0x70;   //F1 键
        public const int vbKeyF2 = 0x71;   //F2 键
        public const int vbKeyF3 = 0x72;   //F3 键
        public const int vbKeyF4 = 0x73;   //F4 键
        public const int vbKeyF5 = 0x74;   //F5 键
        public const int vbKeyF6 = 0x75;   //F6 键
        public const int vbKeyF7 = 0x76;   //F7 键
        public const int vbKeyF8 = 0x77;   //F8 键
        public const int vbKeyF9 = 0x78;   //F9 键
        public const int vbKeyF10 = 0x79;  //F10 键
        public const int vbKeyF11 = 0x7A;  //F11 键
        public const int vbKeyF12 = 0x7B;  //F12 键

        #endregion
    }

申明窗体坐标位置类

    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public int Left; //最左坐标
        public int Top; //最上坐标
        public int Right; //最右坐标
        public int Bottom; //最下坐标
    }

然后,申明Action类,用来引入WINDOWSAPI的接口

    public static class Action
    {
        public static int WM_CHAR = 0x102;   //键入事件
        public static int WM_CLICK = 0x00F5; //点击事件
        public static int WM_CLOSE = 0x0010; //窗体关闭事件

        public static int CB_SETCURSEL = 0x014E; //下拉框搜索
        public static int CB_FINDSTRINGEXACT = 0x0158; //下拉框选择

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);

        [DllImport("User32.dll", EntryPoint = "SendMessage")]
        public static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, string lParam);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll")]
        public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter,
            string lpszClass, string lpszWindow);

        [DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

        [DllImport("user32.dll")]
        public static extern int AnyPopup();

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);

        [DllImport("user32.dll")]
        public static extern int EnumThreadWindows(IntPtr dwThreadId, CallBack lpfn, int lParam);

        [DllImport("user32.dll")]
        public static extern int EnumChildWindows(IntPtr hWndParent, CallBack lpfn, int lParam);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr PostMessage(IntPtr hwnd, int wMsg, int wParam, int lParam);

        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        public static extern IntPtr SendMessageA(IntPtr hwnd, int wMsg, int wParam, int lParam);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);

        [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern int GetWindowTextLength(IntPtr hWnd);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
        public static extern IntPtr GetParent(IntPtr hWnd);

        [DllImport("user32.dll")]
        public static extern bool SetForegroundWindow(IntPtr hWnd);

        [DllImport("user32.dll")]
        public static extern int mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo);
        [DllImport("user32.dll")]
        public static extern bool SetCursorPos(int X, int Y);
        [DllImport("user32.dll")]
        public static extern void keybd_event(int bVk, int bScan, int dwFlags, int dwExtraInfo);

        public delegate bool CallBack(IntPtr hwnd, int lParam);
    }

接下来,我们就可以封装方法使用这些接口,在此,我封装了一个MessageHelp方法,里面有一些常用的事件,比如可按照如下思想设计“按照窗体名称定位句柄”“定位到句柄后给句柄设置文字”;“把鼠标移动指定坐标位置并点击”“找到指定文字的按钮并点击”

   public static class MessageHelp
    {
        /// <summary>
        /// windows像句柄设置文字
        /// </summary>
        /// <param name="hand"></param>
        /// <param name="ch"></param>
        /// <param name="SleepTime"></param>
        public static void SendChar(IntPtr hand, char ch, int SleepTime = 10)
        {
            BaseHelp.Action.PostMessage(hand, BaseHelp.Action.WM_CHAR, ch, 0);
            Thread.Sleep(SleepTime);
        }

        /// <summary>
        /// windows关闭句柄窗体
        /// </summary>
        /// <param name="hwnd"></param>
        /// <param name="SleepTime"></param>
        public static void CloseWindow(IntPtr hwnd, int SleepTime = 100)
        {
            BaseHelp.Action.PostMessage(hwnd, BaseHelp.Action.WM_CLOSE, 0, 0);
            Thread.Sleep(SleepTime);
        }

        /// <summary>
        /// 移动鼠标位置(相对于句柄窗体位置)
        /// </summary>
        /// <param name="X"></param>
        /// <param name="Y"></param>
        /// <param name="WindowRect"></param>
        /// <param name="SleepTime"></param>
        public static void SetCursorPos(int X,int Y, BaseHelp.RECT WindowRect, int SleepTime = 10)
        {
            BaseHelp.Action.SetCursorPos(X + (WindowRect.Left), Y + (WindowRect.Top));
            Thread.Sleep(SleepTime);
        }

        /// <summary>
        /// 鼠标左键单击
        /// </summary>
        /// <param name="SleepTime"></param>
        public static void MouseLEFTDown(int SleepTime = 10)
        {
            //模拟鼠标按下操作
            BaseHelp.Action.mouse_event((int)(BaseHelp.Mouse.MOUSEEVENTF_LEFTDOWN | BaseHelp.Mouse.MOUSEEVENTF_LEFTDOWN), 0, 0, 0, 0);
            Thread.Sleep(SleepTime);
        }

        /// <summary>
        /// 查找窗体里句柄
        /// </summary>
        /// <param name="lpClassName"></param>
        /// <param name="lpWindowName"></param>
        /// <returns></returns>
        public static IntPtr FindWindow(string lpClassName, string lpWindowName)
        {
            return BaseHelp.Action.FindWindow(lpClassName, lpWindowName);
        }

        /// <summary>
        /// 查找窗体里的控件句柄
        /// </summary>
        /// <param name="hwndParent"></param>
        /// <param name="hwndChildAfter"></param>
        /// <param name="lpszClass"></param>
        /// <param name="lpszWindow"></param>
        /// <returns></returns>
        public static IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter,string lpszClass, string lpszWindow)
        {
            return BaseHelp.Action.FindWindowEx(hwndParent, hwndChildAfter, lpszClass, lpszWindow);
        }

        /// <summary>
        /// 遍历获取Windows的控件子句柄
        /// </summary>
        /// <param name="hWndParent"></param>
        /// <param name="lpfn"></param>
        /// <param name="lParam"></param>
        /// <returns></returns>
        public static int EnumChildWindows(IntPtr hWndParent, CallBack lpfn, int lParam)
        {
            return BaseHelp.Action.EnumChildWindows(hWndParent, lpfn, lParam);
        }

        /// <summary>
        /// 获取Windows窗体坐标
        /// </summary>
        /// <param name="hWnd"></param>
        /// <param name="lpRect"></param>
        /// <returns></returns>
        public static bool GetWindowRect(IntPtr hWnd, ref RECT lpRect)
        {
            return BaseHelp.Action.GetWindowRect(hWnd, ref lpRect);
        }

        /// <summary>
        /// 发送异步Windows消息
        /// </summary>
        /// <param name="hwnd"></param>
        /// <param name="wMsg"></param>
        /// <param name="wParam"></param>
        /// <param name="lParam"></param>
        /// <param name="SleepTime"></param>
        /// <returns></returns>
        public static IntPtr PostMessage(IntPtr hwnd, int wMsg, int wParam, int lParam)
        {
            return BaseHelp.Action.PostMessage(hwnd, wMsg, wParam, lParam);
        }

        /// <summary>
        /// 发送同步Windows消息
        /// </summary>
        /// <param name="hWnd"></param>
        /// <param name="Msg"></param>
        /// <param name="wParam"></param>
        /// <param name="lParam"></param>
        /// <returns></returns>
        public static IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam)
        {
            return BaseHelp.Action.SendMessage(hWnd, Msg, wParam, lParam);
        }

        /// <summary>
        /// 调用键盘事件
        /// </summary>
        /// <param name="bVk"></param>
        /// <param name="bScan"></param>
        /// <param name="dwFlags"></param>
        /// <param name="dwExtraInfo"></param>
        /// <param name="SleepTime"></param>
        public static void keybd_event(int bVk, int bScan, int dwFlags, int dwExtraInfo, int SleepTime = 10)
        {
            BaseHelp.Action.keybd_event(bVk, bScan, dwFlags, dwExtraInfo);
            Thread.Sleep(SleepTime);
        }

        /// <summary>
        /// 切换前台窗体
        /// </summary>
        /// <param name="hWnd"></param>
        /// <returns></returns>
        public static bool SetForegroundWindow(IntPtr hWnd)
        {
            return BaseHelp.Action.SetForegroundWindow(hWnd);
        }

        /// <summary>
        /// 查找定位窗体句柄
        /// </summary>
        /// <param name="WindowsIntPtr"></param>
        /// <param name="Action"></param>
        /// <returns></returns>
        public static List<MessageHelp.MatchIntPtr> FindControl(IntPtr WindowsIntPtr, MessageHelp.ActionDetail Action)
        {
            List<MessageHelp.MatchIntPtr> result = new List<MessageHelp.MatchIntPtr>();
            if(!string.IsNullOrEmpty(Action.SpyID))
            {
                return FindControlBySpyID(WindowsIntPtr, Action);
            }
            else if(Action.Points!=null && Action.Points.Count>0)
            {
                return FindControlByPoint(WindowsIntPtr, Action);
            }
            else
            {
                return FindControlByName(WindowsIntPtr, Action);
            }
        }

        /// <summary>
        /// 根据SPYID,查找窗体
        /// </summary>
        /// <param name="WindowsIntPtr"></param>
        /// <param name="SpyID"></param>
        /// <returns></returns>
        private static List<MessageHelp.MatchIntPtr> FindControlBySpyID(IntPtr WindowsIntPtr, MessageHelp.ActionDetail Action)
        {
            List<MessageHelp.MatchIntPtr> result = new List<MessageHelp.MatchIntPtr>();
            MessageHelp.EnumChildWindows(WindowsIntPtr, new BaseHelp.Action.CallBack(delegate (IntPtr hwnd, int lParam)
            {
                StringBuilder title = new StringBuilder(256);
                GetClassName(hwnd, title, title.Capacity);
                if (title.ToString() == Action.SpyID)
                {
                    result.Add(new MessageHelp.MatchIntPtr()
                    {
                        intPtr = hwnd,
                    });
                }
                return true;
            }), 0);
            return result;
        }

        /// <summary>
        /// 根据坐标点,查找窗体的WINFORM控件句柄
        /// </summary>
        /// <param name="WindowsIntPtr"></param>
        /// <param name="Action"></param>
        /// <returns></returns>
        private static List<MessageHelp.MatchIntPtr> FindControlByPoint(IntPtr WindowsIntPtr, MessageHelp.ActionDetail Action)
        {
            List<MessageHelp.MatchIntPtr> result = new List<MessageHelp.MatchIntPtr>();
            RECT WindowsRect = new RECT();
            #region 获取窗体的绝对坐标
            MessageHelp.GetWindowRect(WindowsIntPtr, ref WindowsRect);
            #endregion
            MessageHelp.EnumChildWindows(WindowsIntPtr, new BaseHelp.Action.CallBack(delegate (IntPtr hwnd, int lParam)
            {
                RECT clientRect = new RECT();

                #region 获取控件的绝对坐标
                MessageHelp.GetWindowRect(hwnd, ref clientRect);
                #endregion

                #region 计算相对坐标
                int Left = clientRect.Left - WindowsRect.Left;
                int Right = Left + (clientRect.Right - clientRect.Left);
                int Top = clientRect.Top - WindowsRect.Top;
                int Bottom = Top + (clientRect.Bottom - clientRect.Top);
                #endregion
                foreach (var Point in Action.Points)
                {
                    if (Top <= Point.PointY && Bottom >= Point.PointY && Left <= Point.PointX && Right >= Point.PointX)
                    {
                        result.Add(new MessageHelp.MatchIntPtr()
                        {
                            intPtr = hwnd,
                        });
                    }
                }
                return true;
            }), 0);
            return result;
        }

        /// <summary>
        /// 根据标题名称,查找窗体的WINFORM控件句柄
        /// </summary>
        /// <param name="WindowsIntPtr"></param>
        /// <param name="Action"></param>
        /// <returns></returns>
        private static List<MessageHelp.MatchIntPtr> FindControlByName(IntPtr WindowsIntPtr, MessageHelp.ActionDetail Action)
        {
            List<MessageHelp.MatchIntPtr> result = new List<MessageHelp.MatchIntPtr>();
            MessageHelp.EnumChildWindows(WindowsIntPtr, new BaseHelp.Action.CallBack(delegate (IntPtr hwnd, int lParam)
            {
                StringBuilder title = new StringBuilder();
                GetWindowText(hwnd, title, 200);
                if (title.ToString() == Action.CharMsg)
                {
                    result.Add(new MessageHelp.MatchIntPtr()
                    {
                        intPtr = hwnd,
                    });
                }
                return true;
            }), 0);
            return result;
        }

        /// <summary>
        /// 切图保存
        /// </summary>
        /// <param name="folderPath"></param>
        /// <param name="fileName"></param>
        public static void CaptureScreenAndSave(string folderPath, string fileName)
        {
            // 确保文件夹存在
            Directory.CreateDirectory(folderPath);

            // 创建完整的文件路径
            string filePath = Path.Combine(folderPath, fileName);

            // 获取整个屏幕的尺寸
            Rectangle bounds = Screen.PrimaryScreen.Bounds;

            // 创建一个bitmap
            using (Bitmap bmp = new Bitmap(bounds.Width, bounds.Height))
            {
                // 创建一个画布
                using (Graphics g = Graphics.FromImage(bmp))
                {
                    // 截取屏幕
                    g.CopyFromScreen(bounds.Location, Point.Empty, bounds.Size);
                }

                // 保存截图到文件
                bmp.Save(filePath, ImageFormat.Png);
            }
        }

        public class ActionDetail
        {
            public string Form { get; set; }

            public List<ActionPoint> Points { get; set; }

            public string CharMsg { get; set; }

            public string SpyID { get; set; }
        }

        public class ActionPoint
        {
            public int PointX { get; set; }

            public int PointY { get; set; }
        }

        public class MatchIntPtr
        {
            public IntPtr intPtr { get; set; }
        }
    }

其中SpyID的获取方法,可以使用VS自带的Spy++工具来进行选点

注意:以下部分是伪代码,旨意再说明上述方法的使用,其中Actions对象是一系列准备待操作鼠标和键盘事件的集合,将依次执行,Action.Job_Type是定义的枚举事件的方法

foreach (var Action in Actions)
{
    IntPtr hwnd_Form = MessageHelp.FindWindow(null, Action.Action.Form);
    if (hwnd_Form == IntPtr.Zero && Action.Job_Type != "009" && Action.Job_Type != "007" && Action.Job_Type != "006")
    {
        continue;
    }
    if (Action.Job_Type == "001") //文字键入
    {
        List<MessageHelp.MatchIntPtr> MatchsCos = new List<MessageHelp.MatchIntPtr>();
        MatchsCos = MessageHelp.FindControl(hwnd_Form, Action.Action);
        foreach (var MatchsCo in MatchsCos)
        {
            char[] UserChar = Action.Action.CharMsg.ToCharArray();
            foreach (char ch in UserChar)
            {
                MessageHelp.SendChar(MatchsCo.intPtr, ch);
            }
        }
    }
    else if (Action.Job_Type == "002") //鼠标移动
    {
        RECT rect = new RECT();
        MessageHelp.GetWindowRect(hwnd_Form, ref rect);
        MessageHelp.SetCursorPos(Action.Action.Points[0].PointX, Action.Action.Points[0].PointY, rect);
    }
    else if (Action.Job_Type == "003")//鼠标左击
    {
        MessageHelp.MouseLEFTDown();
    }
    else if (Action.Job_Type == "004")//按钮点击
    {
        var MatchsCos = MessageHelp.FindControl(hwnd_Form, Action.Action);
        foreach (var MatchsCo in MatchsCos)
        {
            MessageHelp.PostMessage(MatchsCo.intPtr, BaseHelp.Action.WM_CLICK, (int)hwnd_Form, 0);
        }
    }
    else if (Action.Job_Type == "005")//下拉选择
    {
        var MatchsCos = MessageHelp.FindControl(hwnd_Form, Action.Action);
        foreach (var MatchsCo in MatchsCos)
        {
            int index = (int)MessageHelp.SendMessage(MatchsCo.intPtr, BaseHelp.Action.CB_FINDSTRINGEXACT, IntPtr.Zero, Marshal.StringToHGlobalAuto(Action.Action.CharMsg));
            MessageHelp.SendMessage(MatchsCo.intPtr, BaseHelp.Action.CB_SETCURSEL, (IntPtr)index, (IntPtr)0);
        }
    }
    else if (Action.Job_Type == "006")//按下回车
    {
        MessageHelp.keybd_event(BaseHelp.Keyboard.vbKeyReturn, 0, 0, 0);
    }
    else if (Action.Job_Type == "007")//按下ESC
    {
        MessageHelp.keybd_event(BaseHelp.Keyboard.vbKeyEscape, 0, 0, 0);
    }
    else if(Action.Job_Type== "011")//按下TAB
    {
        MessageHelp.keybd_event(BaseHelp.Keyboard.vbKeyTab, 0, 0, 0);
    }
    else if (Action.Job_Type == "008")//关闭窗体
    {
        MessageHelp.SendMessage(hwnd_Form, BaseHelp.Action.WM_CLOSE, (IntPtr)0, (IntPtr)0);
    }
    else if (Action.Job_Type == "009")//截图保存
    {
        var Now = DateTime.Now;
        string currentDirectory = Application.StartupPath;
        MessageHelp.CaptureScreenAndSave($@"{currentDirectory}/{DataSets.Rows[0]["Job_Name"].ToString()}/{Now.ToString("yyyyMMdd")}", $@"{DataSets.Rows[0]["Job_Title"].ToString()}{Now.ToString("yyyyMMddHHmmssfff")}.png");
    }
    else if (Action.Job_Type == "010")//等待时间
    {
        Thread.Sleep(Convert.ToInt32(Action.Action.CharMsg));
    }
    Thread.Sleep(2000);
}

使用 Docker 快速部署 shadowsocks

Step1:拉取 shadowsocks-libev

docker pull appso/shadowsocks-libev

Step2:创建 shadowssocks配置文件

mkdir -p /etc/shadowsocks-libev/
vi /etc/shadowsocks-libev/config.json

将下面的内容修改后,粘贴进去。其中server_port就是可以随便改,比如,我这里是8188。密码修改成自己的

{
  "server": "0.0.0.0",
  "server_port": 8188,
  "password": "密码",
  "timeout": 600,
  "method": "aes-256-gcm",
  "fast_open": false,
  "mode": "tcp_and_udp"
}

Step3:保存退出后,启动Docker容器

docker run -d -p 8188:8188 -p 8188:8188/udp \
       --name ss-libev \
       --restart=always \
       -v /etc/shadowsocks-libev:/etc/shadowsocks-libev \
       appso/shadowsocks-libev

这样服务端就算是配置完成了

接下来,就可以下载对应客户端文件,去各个终端连接服务器的8188端口进行使用了

Windows客户端:点击下载

MAC客户端:点击下载

Android客户端:点击下载

Docker部署.Net Core 3.1程序(超详细)

首先,我们把编译好的.Net Core 3.1程序上传到宿主机的特地目录,如下图,确保你的程序路径在netcoreapp3.1/publish/文件夹中,以下是以我的程序Web.dll为例

然后,在netcoreapp3.1文件同级,新建Dockerfile文件,并且为了防止DOCK镜像中可能不存在字体,我们在同级别目录放一个字体文件 simsun.ttc

Dockerfile的内容如下,以下我以运行我的Web.dll程序为例子,为了调试方便,看到错误信息,ASPNETCORE_ENVIRONMENT暂时设置为Development,容器内端口设置为80,容器内的工作目录设置为/app

# 使用官方.NET Core运行时镜像作为基础镜像
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
 
# 设置工作目录
WORKDIR /app
 
# 复制项目发布文件到容器中的工作目录
COPY ./netcoreapp3.1/publish/ .

COPY simsun.ttc /usr/share/fonts/
 
# 设置环境变量,端口
ENV ASPNETCORE_ENVIRONMENT=Development
ENV ASPNETCORE_URLS=http://+:80
 
# 当容器启动时,运行应用程序
ENTRYPOINT ["dotnet", "Web.dll"]

接下来,在控制台,定位到指定目录,用以下命令生成镜像,然后就会新增一个mathapp的Image镜像

sudo docker build -t mathapp .

此时,我们可以试着用这个镜像部署一个容器mathapp,并且把宿主机的8080端口,映射到容器80

sudo docker run --name mathapp -d -p 8080:80 mathapp

这时,我们就可以在游览器地址栏输入:http://服务器IP:8080 来访问自己的.Net Core程序了

接下来,我们为了后续更新方便,也可以试着在宿主机做个文件映射,首先,把容器内刚刚的/app工作目录的文件,复制到宿主机

sudo docker cp mathapp:/app /home/ec2-user/www

此时,我们看到,宿主机的对应目录中多了一个app文件,里面就是我们容器的工作目录文件

此时,我们可以删除之前创建的测试容器,创建全新容器,然后把这个目录挂载到新容器上

sudo docker stop mathapp
sudo docker rm mathapp
sudo docker run -d -p 8080:80 --name mathapp --restart=always -v /home/ec2-user/www/app:/app mathapp

接下来,后续更新只要更新宿主机中的文件,然后重启对应的docker容器即可

Docker部署wordpress

一、拉取wordpress镜像

docker pull wordpress

二、部署启动Docker容器,把宿主机当前用户的本地路径挂载到容器上,宿主机的端口可以设置成8081,这样,容器内的 WordPress 网站就会使用 /data 目录作为持久存储,你可以将数据保存在该目录下,即使容器被删除或重新创建,数据也不会丢失。请确保在运行此命令之前,已经在主机上创建了目标挂载点。

docker run -it --name mywordpress2 -p 8081:80 -v ~/www/blog:/var/www/html -d wordpress

此时在浏览器访问http://localhost:8081/wp-admin/setup-config.php进行安装。

此时需要配置数据库信息,DOCKER配置MYSQL数据库的方法,参考如下:Centos下的Docker环境RabbitMQ以及SqlServer和MySql搭建

如果您已经在 Docker 容器中分别安装了 WordPress 和 MySQL,并且想要让它们链接起来,可以按照以下步骤进行操作,首先,需要查找正在运行的 MySQL 容器的 IP 地址。可以使用以下命令:

docker inspect 容器id或容器名 | grep IPAddress

此时,会返回DOCKER容器中MYSQL对应的IP地址,回到刚刚的WordPress配置页面将数据库主机填上你获取的数据库ip地址即可

配置完毕后,如果是小内存服务器,可以再在WordPress的根目录的wp-config.php文件上,添加一个内存限制,如下,就是限制到128兆内存

define('WP_MEMORY_LIMIT', '64M');
define('WP_MAX_MEMORY_LIMIT', '128M');

同时,也可以对Docker的容器内存大小进行限制,(例:不能超过148M)

docker update --restart=always --memory="148m" --memory-swap="148m" 容器ID

Docker环境部署Nginx

1、运行容器

docker run --name nginx -d nginx

2、在宿主机的当前用户文件夹下创建nginx的目录结构

mkdir -p ~/nginx/{html,logs,conf,conf.d,cert}

3、把容器中的Nginx目录拷贝到宿主机的当前用户文件夹下

docker cp nginx:/etc/nginx/conf.d/default.conf ~/nginx/conf.d/default.conf
docker cp nginx:/etc/nginx/nginx.conf ~/nginx/conf/nginx.conf
docker cp nginx:/usr/share/nginx/html ~/nginx/html

4、此时,宿主机中有了nginx容器的配置,可以停止容器,并删除

docker stop nginx
docker rm nginx

5、现在,我们可以重新创建一个新的nginx容器,并且把宿主机刚刚复制出的文件挂载到容器上

docker run --name=nginx \
           -p 80:80 \
           -p 443:443 \
           -v ~/nginx/conf.d:/etc/nginx/conf.d \
           -v ~/nginx/conf/nginx.conf:/etc/nginx/nginx.conf \
           -v ~/nginx/html:/usr/share/nginx/html \
           -v ~/nginx/logs:/var/log/nginx \
           -v ~/nginx/cert:/etc/nginx/cert \
           --privileged=true \
           --restart=always \
           -d \
           nginx

6、平滑重启Nginx

docker exec nginx nginx -s reload

以下为Nginx的一个简单反向代理配置

server {
    listen 80;
    server_name www.域名A.com bbs.域名B.com;
    access_log  /var/log/nginx/www.域名A.com.access.log;
    error_log /var/log/nginx/www.域名A.com.error.log;
    location / {
        proxy_pass http://IP地址:端口;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

MySQL8 新特性和常用查询优化

1:默认数据引擎改变为InnoDB

Mysql8之前的版本,默认引擎为MyISAM(主要的非事务处理存储引擎),从Mysql8开始,数据库默认引擎为InnoDB(支持事务、分布式、事务部分回滚、行级锁、外键)

2:默认字符集改变为utf8mb4

Mysql8之前的版本,默认字符集为lation1,从Mysql8开始,数据库默认编码改为utf8mb4

3:新增设置系统变量参数(Persist),全局变量的持久化,如下:

show [global|session] variables like '%time_zone%'; 
 set [global|session|persist] time_zone='+8:00';  
 flush privileges; # 立即生效

4:自增变量的持久化

 Create table test1(
 id int auto_increment primary key,
 Val int
 );
 Insert into test1(val) values (1);
 Delete from table where id=1;
 -- 重启数据库
 Insert into test1(val) values (1);
 Select * from test1
 -- 发现id变成了2,但如果是MySQL5的版本,则id会是1
 

5:新增窗口函数

窗口函数类似sum(),count()那样的聚合函数,但是它不会将多行合并,而是将结果多行返回,也就是说,窗口函数是不需要Group By的,以下是几个简单的例子

 select *,sum(count) over() as totalcount from Orders;
 
 select *,count/(sum(count) over()) as totalcount from Orders;

6:新增通用表达式

通用表达试简称CTE,CTE是命名的临时结果集,作用范围是当前语句,CTE可以理解成一个可以复用的子查询,CTE可以引用其他CTE,但是子查询不能引用其他子查询 例如我们在8.0之前的sql,只能使用子查询获取类别名称

 Select g.*,(select name from category where id=g.cat_id)  cat_name from goods g;

但是在8.0之后,我们可以用CTE的方式

 With cte as ( select * from category)    
 Select g.*,(select cte.name from cte where cte.id=g.cat_id)  cat_name from goods g;

相比子查询,cte的效率会更高,因为非递归的cte只会查询一次并可以重复使用。
我们也可以使用cte引用其它cte,达到查询目的,查询例子如下

 With cte1 as ( select * from category),
 cte2 as (select g.*,cte1. name  cat_name  from goods g left join cte1 on g.cat_id=cte1.id) 
 select * from cte2

7:通用表达式CET递归神器

递归cte是特殊的cte,必须以WITH RECURSIVE开头,递归子查询包括两部分,SEED查询和RECURSIVE查询,中间由union[all]分隔, SEED查询只会执行1次,以创建初始数据集, RECURSIVE查询会重复执行,直到无法满足语句条件为止。

With RECURSIVE cte as (
 select 1 n     -- SEED查询
 union all 
 Select n+1 n from cte where n<8)  -- RECURSIVE查询
 Select * from cte;

以下是2个常用场景

场景1:递归查询指定节点的所有后代

 set @customer_id=2; -- 当前节点
 WITH RECURSIVE cte AS
 (
 SELECT 
 a.customer_id, a.inviter_id,
 cast(a.inviter_id as char(255)) path
 FROM t_customer_relation a WHERE a.customer_id=@customer_id
 UNION ALL
 SELECT
 k.customer_id,k.inviter_id,
 concat(cte.path,',', k.inviter_id) path
 FROM t_customer_relation k 
 INNER JOIN cte ON cte.customer_id = k.inviter_id
 )
 SELECT cte.customer_id,cte.inviter_id,cte.path  FROM cte 
 -- left join t_customer d  on cte.customer_id=d.id
 

场景2:根据当前节点,查询自己的祖宗

set @customer_id =29 ; -- 当前节点
 WITH RECURSIVE cte AS(
     SELECT
     customer_id, 
     inviter_id, 
     convert(inviter_id , char(255)) path 
     FROM t_customer_relation WHERE customer_id = @customer_id 
     UNION ALL
     SELECT 
     c.customer_id, 
     c.inviter_id, 
     concat(cte.path,',', c.inviter_id) path 
     FROM t_customer_relation c, cte WHERE c.customer_id= cte.inviter_id
 )SELECT * FROM cte;

8:支持json类型

Mysql是关系数据库,我们如果再Mysql8前如果有此类需求,一般用Blob类型存取。但是存在以下 缺点:

1.无法保证json正确性

2.json的二操作加工需要代码处理

3.读取json的某个字段,必须从数据库读出字段所有内容

4.无法在json上的某个字段建索引

现在,可以试试在表里使用json类型字段。如下:

Create table tb(
 jsdata  json
 );
 Insert into table(’{”key”:”123”}’);

值得注意的是,key的长度不能超过2个字节(65535)。Mysql8.0提供了很多操作json的函数,包括条件查询,具体可以参考官方文档说明

9:其他新特性

a:加密函数(md5(str),sha (str),sha2 (str,hash_length))

b:GROUP BY 不再隐式排序

c:DLL的原子化(drop table table1,table2;如果table2不存在,那table1将不会删除)

d:支持降序索引

e:统计直方图

f:支持表空间加密

g:支持不可见索引(相对于enable,哪怕隐藏时依然和正常索引一样实时更新,查询不再走)

h:跳过锁等待,sql如下:

select * from table where id=1 for update nowait; --有锁就报错)
select * from table where id=1 for update skip locked; --有锁就不理)

MySql时间格式函数

以下命令为MySql的常用时间函数,可以在编写服务端时,为时间运算和分组提供便利,具体函数如下

select now() `当前时间`,
 unix_timestamp(now()) `时间戳(秒)`,
 from_unixtime(unix_timestamp(now())) `时间戳->时间`,
 concat(date_format(utc_date(),'%Y-%m-%d'),date_format(utc_time(),' %H:%i:%s')) `格林尼治时间`,
 month(now()) `月份`,
 monthname(now()) `月份`,
 dayname(now()) `星期`,
 dayofweek(now()) `对应一周中的索引(1周日 2周一 …… 7周六)`,
 weekday(now()) `日期对应的周索引(0周一 1周二 …… 6周日)`,
 week('2021-01-02',0) `计算时间是一年中的第几周`,   --  一周的第一天(日) 范围(0~53)  本年度中有一个周日
 week('2021-01-02',2) `计算时间是一年中的第几周`,   --  一周的第一天(日) 范围(1~53)  本年度中有一个周日
 week('2021-01-02',1) `计算时间是一年中的第几周`,   --  一周的第一天(一) 范围(0~53)  本年度中有3天以上
 week('2021-01-02',3) `计算时间是一年中的第几周`,   --  一周的第一天(一) 范围(1~53)  本年度中有3天以上
 week('2021-01-02',4) `计算时间是一年中的第几周`,   --  一周的第一天(日) 范围(0~53)  本年度中有3天以上
 week('2021-01-02',6) `计算时间是一年中的第几周`,   --  一周的第一天(日) 范围(1~53)  本年度中有3天以上
 week('2021-01-02',5) `计算时间是一年中的第几周`,   --  一周的第一天(一) 范围(0~53)  本年度中有一个周一
 week('2021-01-02',7) `计算时间是一年中的第几周`,   --  一周的第一天(一) 范围(1~53)  本年度中有一个周一
 dayofyear(now()) `一年当中的第几天`,
 date_Add(now(),interval 1 day) `新增一天`,
 date_format(now(),'%Y-%m-%d %H:%i:%s') `时间格式化`

创建MYSQL的定时任务

自MySQL5.1.6起,增加了一个非常有特色的功能-事件调度器(Event Scheduler),可以用做定时执行某些特定任务(例如:删除记录、对数据进行汇总、数据备份等等)。更值得一提的是MySQL的事件调度器可以精确到每秒钟执行一个任务,而操作系统的计划任务(如:Linux的cron或Windows下的任务计划)只能精确到每分钟执行一次。对于一些对数据实时性要求比较高的应用(例如:股票、赔率、比分等)就非常适合。

1、在使用这个功能之前必须确保event_scheduler已开启,可执行

 SET GLOBAL event_scheduler = 1;

2、要查看当前是否已开启事件调度器,可执行如下SQL:

 SHOW VARIABLES LIKE 'event_scheduler';

注:以下为其他常用命令

 ALTER EVENT eventName ON COMPLETION PRESERVE DISABLE;  --关闭事件任务
 ALTER EVENT eventName ON COMPLETION PRESERVE ENABLE;   --开启事件任务
 SHOW EVENTS;                                          --查看事件任务

具体创建语法如下

CREATE EVENT [IFNOT EXISTS] event_name
 ONSCHEDULE schedule
 [ONCOMPLETION [NOT] PRESERVE]
 [ENABLE | DISABLE]
 [COMMENT 'comment']
 DO sql_statement;

schedule:
AT TIMESTAMP [+ INTERVAL INTERVAL]
| EVERY INTERVAL [STARTS TIMESTAMP] [ENDS TIMESTAMP]
  
INTERVAL:
quantity {YEAR | QUARTER | MONTH | DAY | HOUR | MINUTE |
WEEK | SECOND | YEAR_MONTH | DAY_HOUR | DAY_MINUTE |
DAY_SECOND | HOUR_MINUTE | HOUR_SECOND | MINUTE_SECOND}

首先来看一个简单的例子来演示每秒插入一条记录到数据表 

CREATE TABLE aaa(timeline TIMESTAMP);
 CREATE EVENT e_test_insert
 ON SCHEDULE EVERY 1 SECOND
 DO INSERT aaa VALUE(CURRENT_TIMESTAMP);

等待3秒之后,再执行查询看看,可以看到aaa表会有3条数据
再来看看修改他的语法

ALTER EVENT event_name
 [ONSCHEDULE schedule]
 [RENAME TOnew_event_name]
 [ON COMPLETION [NOT] PRESERVE]
 [COMMENT 'comment']
 [ENABLE | DISABLE]
 [DO sql_statement]

以下是几个简单例子

ALTER EVENT e_test DISABLE;   -- 临时关闭事件
 ALTER EVENT e_test ENABLE;   -- 开启事件  
 ALTER EVENT e_test ON SCHEDULE EVERY 5 DAY;  -- 将任务改为5天执行一次:

最后是删除事件语法

DROP EVENT [IF EXISTS] event_name

CentOS 开启防火墙及指定端口

查看防火墙状态

firewall-cmd --state

开启防火墙

systemctl start firewalld.service

重启防火墙

systemctl restart firewalld.service

关闭防火墙

systemctl stop firewalld.service

开启指定端口

firewall-cmd --zone=public --add-port=80/tcp --permanent

–zone # 作用域
–add # 添加端口,格式为:端口/通讯协议
–permanent # 永久生效,没有此参数重启后失效

重新加载后生效

firewall-cmd reload

查看已开启端口

firewall-cmd --list-ports