C# WPF MVVM QQ密码管家项目(8,完结篇:自动输入QQ号、密码)

简介: 原文:C# WPF MVVM QQ密码管家项目(8,完结篇:自动输入QQ号、密码)目录: 1,界面设计 2,数据模型的建立与数据绑定 3,添加QQ数据 4,修改QQ数据 5,删除QQ数据 6,密码选择输入界面数据绑定 7,对QQ登录界面的自动输入思路分析 8,完结篇:自动输入QQ号、密码   接上篇,获取QQ登录界面句柄、窗口位置、鼠标/键盘操作等都需要用到win32api win32api是windows系统预留的接口,通过接口我们可以实现对系统更加深度地操作。
原文: C# WPF MVVM QQ密码管家项目(8,完结篇:自动输入QQ号、密码)

目录:

1,界面设计

2,数据模型的建立与数据绑定

3,添加QQ数据

4,修改QQ数据

5,删除QQ数据

6,密码选择输入界面数据绑定

7,对QQ登录界面的自动输入思路分析

8,完结篇:自动输入QQ号、密码

 

接上篇,获取QQ登录界面句柄、窗口位置、鼠标/键盘操作等都需要用到win32api

win32api是windows系统预留的接口,通过接口我们可以实现对系统更加深度地操作。

第1步:获取窗口句柄

win32api获取窗口句柄方法c#代码:

 #region 查找窗口句柄
        [DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
        #endregion

有两个参数可以填,第一个lpClassName填类名,第二个lpWindowName填窗口名。也就是说可以通过类名或者窗口名获得指定窗口的句柄(不填的参数应为:null),也可以两个参数都填上(在某些情况下使用用于精准定位窗口)。这里的话我们只填一个窗口名参数即可。

FindWindow(null, "QQ");

QQ窗口名通过spy++可以获取。

 

重温一下自动输入的步骤:

1,获取登录窗口句柄;

2,获得窗口大小;

3,获得窗口坐标;

4,计算两个输入框的位置;

5,模拟鼠标选中输入框获得输入焦点;

6,模拟键盘输入,将qq号、密码输入。

第1步已完成,QQ登录窗口句柄我们已经得到了。接下来是第2步,获得窗口大小。

因为win32api中并没有直接提供一个获取窗口大小的方法,但提供了一个获取窗口尺寸的方法。你可能有点晕,我们先看一下获取窗口尺寸的win32api方法官方说明:

BOOL GetWindowRect(HWND hWnd,LPRECT lpRect);

该函数返回指定窗口的边框矩形的尺寸。该尺寸以相对于屏幕坐标左上角的屏幕坐标给出

该函数返回指定窗口的边框矩形的尺寸,该尺寸是相对于屏幕左上角的,而不是直接给你一个宽是多高是多少的直接数据,所以我们需要稍作计算。

 #region 获得窗口位置
        [DllImport("user32.dll")]
        private static extern int GetWindowRect(IntPtr hwnd, out Rect lpRect);
        public struct Rect
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
        }
        #endregion

使用的时候填入两个参数,第一个是窗口句柄,第二个是输出的数据变量。那么输出的Rect结构数据就是窗口相对于屏幕的尺寸了。

int Left是窗口左边框相对于屏幕最左边的距离;

int Top是窗口顶部边框相对于屏幕顶部的距离;

int Right是窗口右边框相对于屏幕最左边的距离;

int Bottom是窗口下边框相对于屏幕顶部的距离;

假设B为屏幕,A是QQ登录界面,那么GetWindowRect获取到的相应数据如图所示

将RIGHT减去LEFT得到的就是窗口的真实宽度,将BOTTOM-TOP得到的就是窗口的真实高度。到这里第2个步骤“获得窗口大小”就完成了。

我们整合成一个方法

 #region 获得窗口大小
        public class Size
        {
            public int width { get; set; }
            public int height { get; set; }

        }
        public static Size GetWindowSize(IntPtr window)
        {
            Rect rect = new Rect();
            GetWindowRect(window, out rect);
            return new Size()
            {
                width = rect.Right - rect.Left,
                height = rect.Bottom - rect.Top
            };
        }
        #endregion

 

第3步获取窗口坐标其实在获取窗口尺寸的时候已经完成了,RECT int Left就是窗口所在的X坐标,int Top就是窗口的Y坐标。又省了一步。

第4步,计算两个输入框的位置:

有句话在魔术表演中经常能听到,眼睛会欺骗你。窗体也是,你所见的并不一定是真实的宽和高。试试打开QQ截图功能将鼠标移动到QQ登录窗口上试试,会有一个默认的框选区域(框选出窗口的完整区域),得到的是这样一个范围截图:

也就是说其实QQ登录窗口是多出很多透明区域的,如果你忽略了这些区域,将来计算是会出现偏差的,我是没办法负责任的哦。所以,我们在计算输入框位置时要把透明的区域计算进去。(用QQ截图的默认框选区域在某些情况下也不一定精准,请用我们刚才第2步写的GetWindowSize方法获取到宽高的数据来计算

得到QQ登录窗口真实尺寸,可以将截图放入PS中,或者用你喜欢的办法量出几个数据。如下图:

上图我画出了3条线,A、B、C。要获得QQ号码输入框的输入焦点我们要让程序模拟鼠标点击一下输入框的位置,也就是坐标(A,B),也就是点击QQ号码输入框输入区域的位置(只要坐标在输入框内即可,不一定是跟我画的线坐标一样的);那么密码输入框点击的坐标就是(A,C),只有三条线是因为密码输入框的横坐标跟QQ号码输入框的横坐标是一样的,所以直接用A就行。

QQ号码输入框应该点击的坐标是:x=(A÷窗口宽度)×窗口宽度+LEFT(窗口距离屏幕左边的距离),y=(B÷窗口高度)×窗口高度+TOP(窗口距离屏幕顶部的距离);

QQ密码输入框应该点击的坐标是:x=(A÷窗口宽度)×窗口宽度+LEFT,y=(C÷窗口高度)×窗口高度+TOP;

窗口宽高和窗口距离屏幕的距离我们第2步已经给出了~

注意,这里的A、B、C的距离都需要手动量的(可以用PS拉个线条量或者下载个屏幕直尺软件什么的用你喜欢的就行)。

代码(省略了上面的计算步骤):

 #region 获得qq号、密码输入框坐标
        public class Point
        {
            public int x { get; set; }
            public int y { get; set; }
        }
        public static Point GetQQNumberInputPoint(IntPtr window)
        {
            double A = 0.3656565656565657;
            double B = 0.5978723404255319;
            
            //获得窗口宽度
            Size window_size = GetWindowSize(window);
            //获得窗口尺寸
            Rect rect = new Rect();
            GetWindowRect(window, out rect);
            //计算
            int x = (int)(A * window_size.width + rect.Left);
            int y = (int)(B * window_size.height + rect.Top);
            return new Point()
            {
                x = x,
                y = y
            };
        }
        public static Point GetQQPassInputPoint(IntPtr window)
        {
            double A = 0.3656565656565657;
          
            double C = 0.6617021276595745;
            //获得窗口宽度
            Size window_size = GetWindowSize(window);
            //获得窗口尺寸
            Rect rect = new Rect();
            GetWindowRect(window, out rect);
            //计算
            int x = (int)(A * window_size.width + rect.Left);
            int y = (int)(C * window_size.height + rect.Top);
            return new Point()
            {
                x = x,
                y = y
            };
        }
        #endregion

接下来第5步,模拟鼠标点击输入框。win32api方法是:

 #region 设置光标位置
        [DllImport("user32.dll")]
        private static extern bool SetCursorPos(int X, int Y);
        #endregion

到第6步,非常关键的一步了。我们的qq密码中可能会存在很多种组合大小写字母+数字+符号,模拟键盘操作的时候需要注意大小写字母的判断和切换,还有某些符号是需要按住shift键才能按出来的,情况非常复杂。但是,没有什么是编程不能实现的。

首先看键盘操作的win32api方法:

 #region 键盘操作
        [DllImport("user32.dll", EntryPoint = "keybd_event")]
        public static extern void keybd_event(
    byte bVk,    //虚拟键值
    byte bScan,// 一般为0
    int dwFlags,  //这里是整数类型  0 为按下,2为释放
    int dwExtraInfo  //这里是整数类型 一般情况下设成为 0
);
        #endregion

这个虚拟键值可以通过搜索引擎查到完整的,我这就不贴出来了,但是呢,我们不需要去查,为什么呢?有win32api可以直接将字符转为虚拟键值。

 #region 将一个字符转换为虚拟键值
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        internal static extern short VkKeyScan(char ch);
        #endregion

注意哦,是字符,不是字符串,win32api没有方便到自动帮我们转换一整个字符串的键码。所以等会我们还需要稍加改进。

刚才说了有些符号需要按住shift键,还要区分大小写,所以我们需要一些win32api去判断和操作。

#region 大小写、shift等键状态获取和设置
        //大写锁定是否启用
        public static bool IsCapsLock = false;
        const uint KEYEVENTF_EXTENDEDKEY = 0x1;
        const uint KEYEVENTF_KEYUP = 0x2;

        [DllImport("user32.dll")]
        static extern short GetKeyState(int nVirtKey);
        [DllImport("user32.dll")]
        public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, uint dwExtraInfo);

        public enum VirtualKeys : byte
        {
            VK_NUMLOCK = 0x90, //数字锁定键
            VK_SCROLL = 0x91,  //滚动锁定
            VK_CAPITAL = 0x14, //大小写锁定
            VK_A = 62
        }

        public static bool GetState(VirtualKeys Key)
        {

            return (GetKeyState((int)Key) == 1);
        }
        public static void SetState(VirtualKeys Key, bool State)
        {
            if (State != GetState(Key))
            {
                keybd_event((byte)Key, 0x45, KEYEVENTF_EXTENDEDKEY | 0, 0);
                keybd_event((byte)Key, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);
            }
        }
        //设置大小写按钮是否启用(true启用,false禁用)
        public static void SetCapLock(bool islock)
        {
            if (IsCapsLock != islock)
            {
                keybd_event((byte)VirtualKeys.VK_CAPITAL, 0x45, KEYEVENTF_EXTENDEDKEY | 0, 0);
                keybd_event((byte)VirtualKeys.VK_CAPITAL, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);
               
                IsCapsLock = islock;
            }
          
        }
        #endregion

整合上面的代码变成一个方法(注释比较多也不知道哪些地方需要解释,所以下面开始就不多说了直接贴代码,不懂的地方可以在评论提问吧):

  #region 通过字符串模拟键盘输入
        public static bool IsNeedShift(char c)
        {
            //需要按SHIFT的字符
            char[] needlist = { '<', '>', '?', ':', '"', '|', '{', '}', '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+' };
            if (needlist.Contains(c))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        public static void SendKeyOfString(string str)
        {
            foreach (char c in str)
            {
                bool isshiftdown = false;
                //判断字符是否是符号
                if (char.IsPunctuation(c) || char.IsSymbol(c))
                {
                    //判断符号是否需要按住shift
                    if (IsNeedShift(c))
                    {
                        //需要按shift
                        //输入键值
                        keybd_event((byte)16, 0, 0, 0);
                        isshiftdown = true;
                    }
                }
                //判断字符是否是数字
                if (char.IsNumber(c) == false)
                {
                    //不是数字,判断是否是大写字母
                    if (char.IsUpper(c))
                    {
                        //大写字母
                        //开启caps lock锁定
                        SetCapLock(true);

                    }
                    else
                    {

                        //小写字母
                        //关闭caps lock锁定

                        SetCapLock(false);


                    }
                }
               
                //模拟键盘按下
                keybd_event((byte)VkKeyScan(c), 0, 0, 0);
                //模拟键盘释放
                keybd_event((byte)VkKeyScan(c), 0, 2, 0);

                if (isshiftdown)
                {
                    //释放shift
                    keybd_event((byte)16, 0, 2, 0);
                    isshiftdown = false;
                }
               

            }
        }
        #endregion

到此自动输入的步骤代码我们都完成了,现在开始整合上面的代码完成我们的项目。

在项目根目录新建一个文件夹:Cores(核心代码),在cores文件夹新建一个类:Win32API.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace qqkeys.Cores
{
    public class Win32API
    {
        //win32api
        #region win32api
        #region 查找窗口句柄
        [DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
        #endregion

        #region 设置光标位置
        [DllImport("user32.dll")]
        public static extern bool SetCursorPos(int X, int Y);
        #endregion

        #region 鼠标操作
        //移动鼠标 
        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;
        [DllImport("user32.dll")]
        private static extern void mouse_event(int dwFlags, int dx, int dy, int dwData, int dwExtraInfo);
        #endregion

        #region 键盘操作
        [DllImport("user32.dll", EntryPoint = "keybd_event")]
        public static extern void keybd_event(
    byte bVk,    //虚拟键值
    byte bScan,// 一般为0
    int dwFlags,  //这里是整数类型  0 为按下,2为释放
    int dwExtraInfo  //这里是整数类型 一般情况下设成为 0
);
        #endregion

        #region 获得窗口位置
        [DllImport("user32.dll")]
        private static extern int GetWindowRect(IntPtr hwnd, out Rect lpRect);
        public struct Rect
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
        }
        #endregion

        #region 大小写、shift等键状态获取和设置
        //大写锁定是否启用
        public static bool IsCapsLock = false;
        const uint KEYEVENTF_EXTENDEDKEY = 0x1;
        const uint KEYEVENTF_KEYUP = 0x2;

        [DllImport("user32.dll")]
        static extern short GetKeyState(int nVirtKey);
        [DllImport("user32.dll")]
        public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, uint dwExtraInfo);

        public enum VirtualKeys : byte
        {
            VK_NUMLOCK = 0x90, //数字锁定键
            VK_SCROLL = 0x91,  //滚动锁定
            VK_CAPITAL = 0x14, //大小写锁定
            VK_A = 62
        }

        public static bool GetState(VirtualKeys Key)
        {

            return (GetKeyState((int)Key) == 1);
        }
        public static void SetState(VirtualKeys Key, bool State)
        {
            if (State != GetState(Key))
            {
                keybd_event((byte)Key, 0x45, KEYEVENTF_EXTENDEDKEY | 0, 0);
                keybd_event((byte)Key, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);
            }
        }
        //设置大小写按钮是否启用(true启用,false禁用)
        public static void SetCapLock(bool islock)
        {
            if (IsCapsLock != islock)
            {
                keybd_event((byte)VirtualKeys.VK_CAPITAL, 0x45, KEYEVENTF_EXTENDEDKEY | 0, 0);
                keybd_event((byte)VirtualKeys.VK_CAPITAL, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);
               
                IsCapsLock = islock;
            }
          
        }
        #endregion

        #region 将一个字符转换为键码
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        internal static extern short VkKeyScan(char ch);
        #endregion
        #endregion

        //扩展
        #region 扩展
        #region 鼠标单击
        public static void MouserClick()
        {
            mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0); //模拟鼠标按下操作
            mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0); //模拟鼠标放开操作
        }
        #endregion
        #region 获得窗口大小
        public class Size
        {
            public int width { get; set; }
            public int height { get; set; }

        }
        public static Size GetWindowSize(IntPtr window)
        {
            Rect rect = new Rect();
            GetWindowRect(window, out rect);
            return new Size()
            {
                width = rect.Right - rect.Left,
                height = rect.Bottom - rect.Top
            };
        }
        #endregion

        #region 获得qq号、密码输入框坐标
        public class Point
        {
            public int x { get; set; }
            public int y { get; set; }
        }
        public static Point GetQQNumberInputPoint(IntPtr window)
        {
            double A = 0.3656565656565657;
            double B = 0.5978723404255319;
            
            //获得窗口宽度
            Size window_size = GetWindowSize(window);
            //获得窗口尺寸
            Rect rect = new Rect();
            GetWindowRect(window, out rect);
            //计算
            int x = (int)(A * window_size.width + rect.Left);
            int y = (int)(B * window_size.height + rect.Top);
            return new Point()
            {
                x = x,
                y = y
            };
        }
        public static Point GetQQPassInputPoint(IntPtr window)
        {
            double A = 0.3656565656565657;
          
            double C = 0.6617021276595745;
            //获得窗口宽度
            Size window_size = GetWindowSize(window);
            //获得窗口尺寸
            Rect rect = new Rect();
            GetWindowRect(window, out rect);
            //计算
            int x = (int)(A * window_size.width + rect.Left);
            int y = (int)(C * window_size.height + rect.Top);
            return new Point()
            {
                x = x,
                y = y
            };
        }
        #endregion

        #region 通过字符串模拟键盘输入
        //判断字符是否需要按住shift
        public static bool IsNeedShift(char c)
        {
            //需要按SHIFT的字符
            char[] needlist = { '<', '>', '?', ':', '"', '|', '{', '}', '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+' };
            if (needlist.Contains(c))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        //模拟键盘操作输入指定字符串,不支持中文!!!!!!
        public static void SendKeyOfString(string str)
        {
            foreach (char c in str)
            {
                bool isshiftdown = false;
                //判断字符是否是符号
                if (char.IsPunctuation(c) || char.IsSymbol(c))
                {
                    //判断符号是否需要按住shift
                    if (IsNeedShift(c))
                    {
                        //需要按shift
                        //输入键值
                        keybd_event((byte)16, 0, 0, 0);
                        isshiftdown = true;
                    }
                }
                //判断字符是否是数字
                if (char.IsNumber(c) == false)
                {
                    //不是数字,判断是否是大写字母
                    if (char.IsUpper(c))
                    {
                        //大写字母
                        //开启caps lock锁定
                        SetCapLock(true);

                    }
                    else
                    {

                        //小写字母
                        //关闭caps lock锁定

                        SetCapLock(false);


                    }
                }
               
                //模拟键盘按下
                keybd_event((byte)VkKeyScan(c), 0, 0, 0);
                //模拟键盘释放
                keybd_event((byte)VkKeyScan(c), 0, 2, 0);

                if (isshiftdown)
                {
                    //释放shift
                    keybd_event((byte)16, 0, 2, 0);
                    isshiftdown = false;
                }
               

            }
        }
        #endregion
        #endregion


    }
}

在cores文件夹下新建一个类:QQAutoInput.cs

using qqkeys.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using static qqkeys.Cores.Win32API;

namespace qqkeys.Cores
{
    public class QQAutoInput
    {
        public static void Start(QQModel qm)
        {
            //需要获取大小写锁定状态
            IsCapsLock =Win32API.GetState(VirtualKeys.VK_CAPITAL);

            //获取QQ窗口句柄
            IntPtr qqptr = Win32API.FindWindow(null, "QQ");
            //获得qq号码输入框坐标
            Point qqnumberinputpoint = Win32API.GetQQNumberInputPoint(qqptr);
            //获得qq密码输入框坐标
            Point qqpassinputpoint = Win32API.GetQQPassInputPoint(qqptr);


            //将鼠标指针移动到QQ号输入框位置
            Win32API.SetCursorPos(qqnumberinputpoint.x, qqnumberinputpoint.y); 
            //双击鼠标
            MouserClick();
            MouserClick();
            //鼠标点击后需要加延迟,不然反应跟不上导致漏输入或者不输入
            Thread.Sleep(100);

            SendKeyOfString(qm.qq.ToString());



            Thread.Sleep(100);


            SetCursorPos(qqpassinputpoint.x, qqpassinputpoint.y); 
            MouserClick();
            MouserClick();
            Thread.Sleep(100);

            SendKeyOfString(qm.password);
        }
    }
}

在KeysViewModel.cs中新建一个命令:Command_Input

//命令
        public Command Command_Input { get; set; }
        public KeysViewModel()
        {
            ............................

            Command_Input = new Command(new Action<object>(Command_Input_Do));


        }

        private void Command_Input_Do(object obj)
        {
            QQAutoInput.Start(SelectQQ);
        }

最后打开KeysWindow.xaml,修改设计代码的“输入”按钮:

<Button Command="{Binding Command_Input}" Width="100" Height="25" Content="输入"  Margin="0,10,0,0"></Button>

到这里这个项目已经完成

运行程序,看下效果~

项目文件下载:

点我下载

目录
相关文章
|
18天前
|
数据可视化 网络协议 C#
C#/.NET/.NET Core优秀项目和框架2024年3月简报
公众号每月定期推广和分享的C#/.NET/.NET Core优秀项目和框架(每周至少会推荐两个优秀的项目和框架当然节假日除外),公众号推文中有项目和框架的介绍、功能特点、使用方式以及部分功能截图等(打不开或者打开GitHub很慢的同学可以优先查看公众号推文,文末一定会附带项目和框架源码地址)。注意:排名不分先后,都是十分优秀的开源项目和框架,每周定期更新分享(欢迎关注公众号:追逐时光者,第一时间获取每周精选分享资讯🔔)。
|
6月前
|
C#
C#之四十六 迷你贪吃蛇项目
C#之四十六 迷你贪吃蛇项目
53 0
|
2月前
|
算法 BI API
C#/.NET/.NET Core优秀项目和框架2024年1月简报
C#/.NET/.NET Core优秀项目和框架2024年1月简报
|
3月前
|
运维 数据挖掘 C#
C#大型LIS检验信息系统项目源码
LIS系统能实现临床检验信息化,检验科信息管理自动化。其主要功能是将检验科的实验仪器传出的检验数据经数据分析后,自动生成打印报告,通过网络存储在数据库中,使医生能够通过医生工作站方便、及时地看到患者的检验结果。
31 0
|
8月前
|
开发框架 .NET C#
C#/.NET/.NET Core优秀项目和框架精选(坑已挖,欢迎大家踊跃提交PR或者Issues中留言)
C#/.NET/.NET Core优秀项目和框架精选(坑已挖,欢迎大家踊跃提交PR或者Issues中留言)
145 1
|
3月前
|
数据采集 开发框架 JavaScript
C#/.NET/.NET Core优秀项目和框架2023年12月简报
C#/.NET/.NET Core优秀项目和框架2023年12月简报
|
4月前
|
Rust 算法 C#
C#/.NET/.NET Core优秀项目和框架2023年11月简报
C#/.NET/.NET Core优秀项目和框架2023年11月简报
|
6月前
|
开发框架 API C#
C#/.NET/.NET Core优秀项目和框架精选(23年10月更新,欢迎大家踊跃提交PR一起完善让优秀的项目和框架不被埋没)
C#/.NET/.NET Core优秀项目和框架精选(23年10月更新,欢迎大家踊跃提交PR一起完善让优秀的项目和框架不被埋没)
160 0
|
6月前
|
JavaScript C# 数据安全/隐私保护
C#/.NET/.NET Core优秀项目和框架2023年9月简报
C#/.NET/.NET Core优秀项目和框架2023年9月简报
|
7月前
|
JavaScript 小程序 API
C#/.NET/.NET Core优秀项目和框架8月简报
C#/.NET/.NET Core优秀项目和框架8月简报