桌面开发-学习笔记

delphi写入读取注册表,指定文件夹为用户上传打开的文件夹-delphi工资系统

// 保存最近打开的文件路径到注册表
procedure SaveLastPath(const Path: string);
var
Reg: TRegistry; // 注册表对象
begin
Reg := TRegistry.Create(KEY_WRITE); // 创建可写入的注册表对象
try
Reg.RootKey := HKEY_CURRENT_USER; // 指定根键为当前用户
if Reg.OpenKey(RegPath, True) then // 打开注册表项,如果不存在则创建
Reg.WriteString(RegValue, Path); // 写入路径信息
finally
Reg.Free; // 释放注册表对象
end;
end;

// 加载最近打开的文件路径
function LoadLastPath: string;
var
Reg: TRegistry; // 注册表对象
path: array [0..MaxChar] of char; // 存储路径的 Char 数组
UserFolderPath: string; // 存储用户文档目录
begin
Result := ''; // 初始化返回值为空
Reg := TRegistry.Create(KEY_READ); // 创建只读的注册表对象
try
//   UserFolderPath := GetHomePath;// 'C:\Users\ZBB\AppData\Roaming'
//   UserFolderPath :=TPath.GetHomePath; // 'C:\Users\ZBB\AppData\Roaming'
//   UserFolderPath :=GetEnvironmentVariable('USERNAME');// 'ZBB'
//   UserFolderPath :=GetEnvironmentVariable('USERPROFILE');    // 'C:\Users\ZBB
if Succeeded(SHGetFolderPath(0, CSIDL_PROFILE, 0, 0, @Path[0])) then // 获取用户文档目录
begin
UserFolderPath := Path; // 将 Char 数组转换为字符串
UserFolderPath := UserFolderPath + '\Documents'; // 拼接文档目录路径
end;
Reg.RootKey := HKEY_CURRENT_USER; // 指定根键为当前用户
if Reg.OpenKey(RegPath, False) then // 打开注册表项,不创建新项
Result := Reg.ReadString(RegValue); // 读取路径信息
finally
Reg.Free; // 释放注册表对象
end;
end;

// 创建打开文件对话框并设置初始目录为最近打开的目录
OpenDlg := TOpenDialog.Create(Self); // 使用 Self 构建 OpenDlg
OpenDlg.InitialDir := LoadLastPath; // 设置初始目录为最近打开的目录
OpenDlg.Options := [ofForceShowHidden, ofFileMustExist]; // 设置选项:显示隐藏文件,必须选择文件
OpenDlg.Filter := 'Word 文档 (.doc;.docx)|.doc;.docx|所有文件 (.)|.'; // 设置文件类型筛选器

if OpenDlg.Execute then... // 如果用户选择了文件并点击了“打开”按钮,则执行代码块

delphi调用c#编写好的dll

参考1
参考2

 public interface IBase64
    {
        string UnBase64String(string value);
        int ToBase64String(int value);
    }
    [ClassInterface(ClassInterfaceType.None)]
    public class Base64 : IBase64
    {   
        public  string UnBase64String(string value)
        {
            if (value == null || value == "")
            {
                return "";
            }
            byte[] bytes = Convert.FromBase64String(value);
            return Encoding.UTF8.GetString(bytes);
        }

        public  int ToBase64String(int value)
        {
            if (value == null || value == "")
            {
                return "";
            }
            byte[] bytes = Encoding.UTF8.GetBytes(value);
        }
    }

注意:使用regasm命令生成tlb:regasm路径(C:\Windows\Microsoft.NET\Framework64\v4.0.30319) dll路径 /tlb

delphi自定义控件需要将pas或dcu源文件路径添加到library

c#最小化图标

参考

    private void FrmMain_Resize(object sender, EventArgs e)
        {
            if (this.WindowState == FormWindowState.Minimized)
            {
                    Hide();
                    notifyIcon1.Visible = true;
            }
        }

        private void notifyIcon1_MouseDoubleClick(object sender, MouseEventArgs e)
        {
                if (WindowState == FormWindowState.Minimized)
                {
                    Show();
                    WindowState = FormWindowState.Normal;
                    notifyIcon1.Visible = false;
                }
        }

delphi调用外部Api

原生组件,但是需要引入libeay32.dll和ssleay32.dll

var
  I: Integer;
  aRSAData: TRSAData;
  fRSAOpenSSL: TRSAOpenSSL;
  aPathToPublickKey, aPathToPrivateKey: string;
  IdHTTP1: TIdHTTP;
  IdSSLIOHandlerSocketOpenSSL1: TIdSSLIOHandlerSocketOpenSSL;
  Response: string;
  LResponse: IHTTPResponse;
begin
  IdHTTP1 := TIdHTTP.Create(nil);
  IdSSLIOHandlerSocketOpenSSL1 := TIdSSLIOHandlerSocketOpenSSL.Create(nil);

  try
   // IdHTTP1.ProtocolVersion := pv1_1;

    // 设置SSL处理器
    IdSSLIOHandlerSocketOpenSSL1.SSLOptions.Method := sslvTLSv1;
    IdSSLIOHandlerSocketOpenSSL1.SSLOptions.Mode := sslmClient;

    IdHTTP1.HandleRedirects := True;
    IdHTTP1.Request.ContentType := 'application/json'; //设置交互方式为json格式
    IdHTTP1.IOHandler := IdSSLIOHandlerSocketOpenSSL1;
    // 发送请求
    Response := IdHTTP1.Get('https://jsonplaceholder.typicode.com/todos/1');

    // 处理响应
    ShowMessage(Response);
  finally
    IdHTTP1.Free;
    IdSSLIOHandlerSocketOpenSSL1.Free;
  end;
  end;

c#编写系统服务(安装、卸载、启动、停止)

参考

delphi打开系统文件夹方法、获取程序运行文件夹(exe所在文件夹)

delphi:

var
 path: array [0..MaxChar] of char;
    if Succeeded(SHGetFolderPath(0, CSIDL_PROFILE, 0, 0, @Path[0])) then  UserFolderPath := Path;
    //或者以下
   UserFolderPath := GetHomePath;// 'C:\Users\ZBB\AppData\Roaming'
   UserFolderPath :=TPath.GetHomePath; // 'C:\Users\ZBB\AppData\Roaming'
   UserFolderPath :=GetEnvironmentVariable('USERNAME');// 'ZBB'
   UserFolderPath :=GetEnvironmentVariable('USERPROFILE');    // 'C:\Users\ZBB
   //获取程序运行文件夹(exe所在文件夹)
   ExtractFilePath(ParamStr(0))

c#总结打开系统文件夹方法、获取程序运行文件夹获取方法

参考

一、获取当前文件的路径  

1.  System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName  获取模块的完整路径,包括文件名。

2.  System.Environment.CurrentDirectory    获取和设置当前目录(该进程从中启动的目录)的完全限定目录。   

3.  System.IO.Directory.GetCurrentDirectory()    获取应用程序的当前工作目录。这个不一定是程序从中启动的目录啊,有可能程序放在C:\www里,这个函数有可能返回C:\Documents and Settings\ZYB\,或者C:\Program Files\Adobe\,有时不一定返回什么东东,这是任何应用程序最后一次操作过的目录,比如你用Word打开了E:\doc\my.doc这个文件,此时执行这个方法就返回了E:\doc了。   

4. System.AppDomain.CurrentDomain.BaseDirectory    获取程序的基目录。

5. System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase    获取和设置包括该应用程序的目录的名称。   

6. System.Windows.Forms.Application.StartupPath    获取启动了应用程序的可执行文件的路径。效果和25一样。只是5返回的字符串后面多了一个"\"而已   

7. System.Windows.Forms.Application.ExecutablePath    获取启动了应用程序的可执行文件的路径及文件名,效果和1一样。   

二、操作环境变量    利用System.Environment.GetEnvironmentVariable()方法可以很方便地取得系统环境变量,如:    System.Environment.GetEnvironmentVariable("windir")就可以取得windows系统目录的路径。   

以下是一些常用的环境变量取值:   

System.Environment.GetEnvironmentVariable("windir");

System.Environment.GetEnvironmentVariable("INCLUDE");   

System.Environment.GetEnvironmentVariable("TMP");   

System.Environment.GetEnvironmentVariable("TEMP");   

System.Environment.GetEnvironmentVariable("Path");   

最后贴出我进行上面操作获得的变量值,事先说明,本人是编写了一个WinForm程序,项目文件存放于D:\Visual Studio Projects\MyApplication\LifeAssistant,编译后的文件位于D:\Visual Studio Projects\MyApplication\LifeAssistant\bin\Debug,最后的结果如下:

1、System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName=D:\Visual Studio Projects\MyApplication\LifeAssistant\bin\Debug\LifeAssistant.exe   

2、System.Environment.CurrentDirectory=D:\Visual Studio Projects\MyApplication\LifeAssistant\bin\Debug   

3、System.IO.Directory.GetCurrentDirectory()=D:\Visual Studio Projects\MyApplication\LifeAssistant\bin\Debug   

1 asp.net webform用"Request.PhysicalApplicationPath获取站点所在虚拟目录的物理路径,最后包含"\";   

2.c# winform用   

A:"Application.StartupPath":获取当前应用程序所在目录的路径,最后不包含"\";   

B:"Application.ExecutablePath ":获取当前应用程序文件的路径,包含文件的名称;   

C:"AppDomain.CurrentDomain.BaseDirectory":获取当前应用程序所在目录的路径,最后包含"\";   

D:"System.Threading.Thread.GetDomain().BaseDirectory":获取当前应用程序所在目录的路径,最后包含"\";   

E:"Environment.CurrentDirectory":获取当前应用程序的路径,最后不包含"\";   

F:"System.IO.Directory.GetCurrentDirectory":获取当前应用程序的路径,最后不包含"\";    3.c# windows service用"AppDomain.CurrentDomain.BaseDirectory"或"System.Threading.Thread.GetDomain().BaseDirectory";    用"Environment.CurrentDirectory"和"System.IO.Directory.GetCurrentDirectory"将得到" system32"目录的路径;   

如果要使用"Application.StartupPath""Application.ExecutablePath ",需要手动添加对"System.Windows.Forms.dll "的引用,并在程序开头用"using System.Windows.Forms"声明该引用;   

4.在卸载程序获取系统安装的目录:   

System.Reflection.Assembly curPath = System.Reflection.Assembly.GetExecutingAssembly();   

string path=curPath.Location;//得到安装程序类SetupLibrary文件的路径,获取这个文件路径所在的目录即得到安装程序的目录;   

4、System.AppDomain.CurrentDomain.BaseDirectory=D:\Visual Studio Projects\MyApplication\LifeAssistant\bin\Debug\   

5、System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase=D:\Visual Studio Projects\MyApplication\LifeAssistant\bin\Debug\   

6、System.Windows.Forms.Application.StartupPath=D:\Visual Studio Projects\MyApplication\LifeAssistant\bin\Debug   

7、System.Windows.Forms.Application.ExecutablePath=D:\Visual Studio Projects\MyApplication\LifeAssistant\bin\Debug\LifeAssistant.exe    System.Environment.GetEnvironmentVariable("windir")=C:\WINDOWS   

System.Environment.GetEnvironmentVariable("INCLUDE")=C:\Program Files\Microsoft Visual Studio .NET 2003\SDK\v1.1\include\   

System.Environment.GetEnvironmentVariable("TMP")=C:\DOCUME~1\zhoufoxcn\LOCALS~1\Temp   

System.Environment.GetEnvironmentVariable("TEMP")=C:\DOCUME~1\zhoufoxcn\LOCALS~1\Temp   

System.Environment.GetEnvironmentVariable("Path")=C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\jdk1.5.0\bin;C:\MySQLServer5.0\bin;C:\Program Files\Symantec\pcAnywhere\;C:\Program Files\Microsoft SQL Server\80\Tools\BINN   

C# 相对路径 系统路径   

//获取启动了应用程序的可执行文件的路径,不包括可执行文件的名称。 

string  str5=Application.StartupPath;    //可获得当前执行的exe的文件名。 

string  str1  =Process.GetCurrentProcess().MainModule.FileName;    //获取和设置当前目录(即该进程从中启动的目录)的完全限定路径。备注  按照定义,如果该进程在本地或网络驱动器的根目录中启动,则此属性的值为驱动器名称后跟一个尾部反斜杠(如"C:\")。如果该进程在子目录中启动,则此属性的值为不带尾部反斜杠的驱动器和子目录路径(如"C:\mySubDirectory")。

  string  str2=Environment.CurrentDirectory;    //获取应用程序的当前工作目录。

  string  str3=Directory.GetCurrentDirectory();    //获取基目录,它由程序集冲突解决程序用来探测程序集。  string  str4=AppDomain.CurrentDomain.BaseDirectory;    //获取启动了应用程序的可执行文件的路径,不包括可执行文件的名称。  string  str5=Application.StartupPath;    //获取启动了应用程序的可执行文件的路径,包括可执行文件的名称。 

string  str6=Application.ExecutablePath;    //获取或设置包含该应用程序的目录的名称。    string  str7=AppDomain.CurrentDomain.SetupInformation.ApplicationBase;    //例子    Application.StartupPath;    //可以得到F:\learning\c#Training\win\win\bin\Debug    //注意自己补两个\    Application.StartupPath+"\\3.jpg";

在c#中,相对路径是用"."".."表示,   "."代表当前目录,    ".."代表上一级录。    例如 假设我用vs2005在D:\My Documents\Visual Studio 2005\Projects目录里创建了一个名叫controls的项目,即在Projects文件夹里有一个controls文件夹,controls文件夹里有三个文件:controls.sln  controls文件夹  GulfOfStLawrence文件夹。    D:\My Documents\Visual Studio 2005\Projects\Controls\Controls\bin\Debug是这个简单项目能够运行的可执行文件Controls.exe    现在我想要 D:\My Documents\Visual Studio 2005\Projects\Controls\GulfOfStLawrence文件夹下的Gulf_of_St._Lawrence.mxd(arcgis desktop)工程文件路径。    那么相对路径应该就是"..\..\..\GulfOfStLawrence\Gulf_of_St._Lawrence.mxd"string filename = @"..\..\..\GulfOfStLawrence\Gulf_of_St._Lawrence.mxd";   

心得:

1.用相对路径能增加项目的可移植性。使一个工程在移植过程中变得简单,节省了大量布置与工程相关的文件的时间。(如果设置的是绝对路径)。 

2.使用相对路径也使程序代码变得简单 

3. 但有一点必须注意:(只能在同一个驱动器里(如:都在D:里)使用相对路径)。

c#的字符串前面加@"xxx"和不加的区别

在C#中,使用@符号作为字符串文字的前缀可以创建一个所谓的“逐字字符串”,也称为“原始字符串”。逐字字符串允许你在字符串中使用特殊字符(如反斜杠),而无需对它们进行转义。这是因为逐字字符串会将其中的所有字符视为字面量。

例如,如果你想要创建一个包含反斜杠的字符串,普通字符串需要使用两个反斜杠来表示一个反斜杠:

string myString = "\\";

然而,在逐字字符串中,只需要使用一个反斜杠即可:

string myString = @"\";

需要注意的是,逐字字符串的内容必须放在一对双引号之间,否则编译器会报错。

sqlite3更改字段为非空

-- 将表 table_name 中的非空字段 column_name 修改为可空字段

ALTER TABLE employee ADD COLUMN sex_new integer NULL;
UPDATE employee SET sex_new = sex;
ALTER TABLE employee DROP COLUMN sex;
ALTER TABLE employee RENAME COLUMN sex_new TO sex;

管理windows服务(sc.exe和installutil.exe的区别)

sc.exe 是 Windows 系统中的命令行工具,它可以用于管理 Windows 服务。使用 sc.exe 命令可以创建、删除、启动、停止和修改服务等操作。

以下是 sc.exe 常用命令的示例:

创建服务:sc create ServiceName binPath= "C:\Windows\System32\myservice.exe" start= auto
删除服务:sc delete ServiceName
启动服务:sc start ServiceName
停止服务:sc stop ServiceName
修改服务启动类型为自动启动:sc config ServiceName start= auto
需要注意的是,使用 sc.exe 命令需要以管理员权限运行命令提示符或 PowerShell 终端窗口。
sc.exe 和 InstallUtil.exe 都是 Windows 系统中用于管理服务的命令行工具,但它们在使用和功能上有所不同。

sc.exe 主要用于管理 Windows 服务,它可以创建、删除、启动、停止和修改服务等操作。sc.exe 适用于本地和远程服务的操作,并且不需要安装或配置其他组件。可以通过批处理脚本或其他程序自动化调用 sc.exe 命令行,以实现对服务的管理和控制。
而 InstallUtil.exe 则主要用于安装和卸载 .NET Framework 服务应用程序。InstallUtil.exe 可以将 .NET Framework 服务应用程序将其注册为 Windows 服务,或者从系统中卸载已经存在的服务。InstallUtil.exe 可以支持 .NET Framework 应用程序的安装自定义操作和卸载自定义操作,在安装和卸载时可以执行自定义的初始化代码或清理代码等操作。InstallUtil.exe 通常用于 .NET Framework 服务应用程序的开发和部署。

总的来说,sc.exe 更加通用,支持本地和远程服务的操作,并且可以通过批处理脚本等方式进行自动化处理;而 InstallUtil.exe 则更加专注于安装和卸载 .NET Framework 服务应用程序,并可以执行自定义的操作。

dll文件一起打包到exe

参考

托管dll和非托管dll的区别

托管DLL和非托管DLL是Windows操作系统中常用的两种动态链接库文件类型,它们有以下区别:

开发语言:托管DLL使用托管语言(如C#、VB.NET等)编写,非托管DLL使用非托管语言(如C、C++、汇编等)编写。

内存管理:托管DLL由CLR(Common Language Runtime)控制内存管理,而非托管DLL需要通过显式调用内存分配和释放函数来进行内存管理。

跨平台性:托管DLL可在不同的平台上运行,因为它们使用的是面向CLR的语言,而非托管DLL则需要根据特定的CPU架构编译。

调用约定:托管DLL使用的是“公共语言规范”(Common Language Specification)中定义的调用约定,非托管DLL使用的是标准C调用约定或其他特定平台的调用约定。

安全性:托管DLL受控于CLR,其安全性更高,可以进行.NET代码级别的访问控制。而非托管DLL可以直接访问系统资源和硬件,安全性相对较低。

总之,托管DLL和非托管DLL在开发语言、内存管理、跨平台性、调用约定和安全性等方面存在着一些区别,应根据实际需求进行选择。

c#读取json文件的方法

   //第一种: 需要安装Newtonsoft.Json包
	WebClient updateClt = new WebClient()
	byte[] bJson = updateClt.DownloadData(serverUrl);
	updateJson = System.Text.Encoding.UTF8.GetString(bJson);
	// 自定义实体类对应Json的属性名称
	RemoteVersionInfo info = new RemoteVersionInfo();
	info = JsonConvert.DeserializeObject<RemoteVersionInfo>(updateJson);

    // 第二种:需要安装Microsoft.Extensions.Configuration包
    private IConfiguration _config;    // 配置对象
    // 加载配置文件
    var builder = new ConfigurationBuilder().SetBasePath(AppDomain.CurrentDomain.BaseDirectory).AddJsonFile("backup.json");
                _config = builder.Build();
     // 获取配置项
     _backupDir = _config["BackupDir"];
     _targetDir = _config["TargetDir"];
     _interval = int.Parse(_config["Interval"]);
     _maxFiles = int.Parse(_config["MaxFiles"]);
     _enableCompression = bool.Parse(_config["EnableCompression"]);

ASP和JSP的区别

ASP (Active Server Pages)是微软公司开发的一种服务器端动态网页技术,它通常运行在 Windows 服务器上。这意味着要使用 ASP 技术,需要在 Windows 操作系统上安装 IIS(Internet Information Services)Web 服务器,并使用 ASP 的相关技术和工具进行开发和部署。因此,若想使用 ASP 技术,需要选择 Windows 操作系统作为服务器系统,并将 IIS 安装和配置好,才能部署相应的 ASP 网络应用程序。

ASP 和 JSP 是两种服务器端动态网页技术,它们都可以使 HTML 和其他静态网页内容和动态网页内容结合起来,从而实现丰富、交互性更好的 Web 应用程序。它们的主要区别如下:

语言:ASP 使用 VBScript 或 JScript 进行编程,而 JSP 使用 Java 进行编程。

服务器端:ASP 通常运行在 Windows 服务器上,而 JSP 则可以在任何支持 Java 的服务器上运行。

显示逻辑:JSP 在 HTML 页面中嵌入代码块,HTML 和代码块可以混合在一起,而 ASP 则需要将 HTML 和代码分开处理。

执行过程:JSP 页面第一次被访问时,JSP 引擎将其翻译成 Servlet,并将其编译成字节码并执行。对于每个请求,该 Servlet 就会执行。而 ASP 启动时,会将所有 ASP 程序编译为 COM 组件并加载到内存中,每个请求则调用相应的组件来处理请求。

内置对象:JSP 中的内置对象包括 request、response、session、application 等,而 ASP 中的内置对象包括 request、response、session、server、application 等。

可移植性:由于 JSP 可以跨平台运行,因此具有更高的可移植性;而 ASP 只能运行在 Windows 平台上,因此可移植性相对较差。

Apache部署vue项目

// Apache\conf\httpd.conf
DocumentRoot "D:/wamp64/www"
<Directory "D:/wamp64/www/">
    Options +Indexes +FollowSymLinks +Multiviews
    AllowOverride all
    Require all granted
</Directory>
// Apache\conf\extra\httpd-vhosts.conf
<VirtualHost *:80>
    DocumentRoot "D:\wamp64/www/dist"
    ServerName localhost
    ErrorLog "logs/rsrc-error.log"
    CustomLog "logs/rsrc-access.log" common
</VirtualHost>

C#的线程携带参数执行以及回调

第一种方法

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        // ParameterizedThreadStart是一个无返回值的委托
        // 创建一个新的线程并指定要执行的方法
        Thread newThread = new Thread(new ParameterizedThreadStart(DoWork));
        // Thread newThread = new Thread(DoWork);
        // 启动线程,并传递参数
        newThread.Start("Hello, World!");

        Console.WriteLine("主线程继续执行...");

        // 等待子线程结束
        newThread.Join();

        Console.WriteLine("主线程结束.");
    }

    static void DoWork(object data)
    {
        string message = (string)data; // 将参数转换为正确的类型

        Console.WriteLine("子线程开始执行: " + message);

        // 执行一些耗时操作...

        Console.WriteLine("子线程结束执行.");
    }
}

第二种方法

using System;
using System.Threading;

public class Program
{
    static void Main(string[] args)
    {
        // 创建要传递给线程的参数
        string param = "Hello, World!";

        // 创建 Worker 类的实例
        Worker worker = new Worker();

        // 创建委托实例,用于定义回调方法
        Action callback = CallbackMethod;

        // 创建线程,并将方法和参数传递给线程的执行方法
        Thread thread = new Thread(() => worker.WorkerMethod(param, callback));

        // 启动线程
        thread.Start();

        // 主线程继续执行其他操作
        Console.WriteLine("Main thread is doing something...");

        // 等待子线程执行完毕
        thread.Join();

        // 子线程执行完毕后会回调 CallbackMethod 方法,在此处输出完成提示
        Console.WriteLine("Child thread execution completed.");

        // 等待用户按下任意键退出程序
        Console.ReadKey();
    }
}

public class Worker
{
    public void WorkerMethod(object param, Action callback)
    {
        // 在线程中执行的工作
        Console.WriteLine("Worker thread started.");

        // 模拟一段耗时操作
        Thread.Sleep(2000);

        // 打印传递的参数
        Console.WriteLine("Worker thread received parameter: " + (string)param);

        // 执行完毕后回调主线程的方法
        callback.Invoke();
    }
}

public static class CallbackClass
{
    public static void CallbackMethod()
    {
        // 回调方法
        Console.WriteLine("Callback method called.");
    }
}

C#的GDI+

这个表示的意思是180度开始扫90度,270度开始扫90度,360度开始扫90度,90度(应该是y轴正方向即下方)开始扫90度,因为都是扫正数90度所以顺时针

 private GraphicsPath GetRoundedRectPath(Rectangle rect, int radius)
        {
            Rectangle rect2 = new Rectangle(rect.Location, new Size(radius, radius));
            GraphicsPath graphicsPath = new GraphicsPath();
            graphicsPath.AddArc(rect2, 180f, 90f);//左上角
            rect2.X = rect.Right - radius;
            graphicsPath.AddArc(rect2, 270f, 90f);//右上角
            rect2.Y = rect.Bottom - radius;
            rect2.Width += 1;
            rect2.Height += 1;
            graphicsPath.AddArc(rect2, 360f, 90f);//右下角           
            rect2.X = rect.Left;
            graphicsPath.AddArc(rect2, 90f, 90f);//左下角
            graphicsPath.CloseFigure();
            return graphicsPath;
        }

进程之间的通信

要实现Windows服务将数据传递到窗体程序,你可以使用以下方法之一:

  1. 使用共享文件或数据库:在Windows服务中,将需要传递的数据写入共享文件或数据库。然后,在窗体程序中定期读取该文件或数据库以获取最新的数据。

  2. 使用进程间通信(IPC)机制:Windows提供了多种IPC机制,如命名管道、邮槽、共享内存等。你可以在服务中创建一个IPC通道,然后在窗体程序中连接到该通道,并通过该通道传递数据。

  3. 使用网络通信:在Windows服务中启动一个网络服务,例如TCP/IP服务器。然后,在窗体程序中作为客户端连接到该服务,并通过网络传输数据。

IPC机制(不止以下四个)

在性能方面,命名管道、邮槽和共享内存各有其特点。下面对它们的性能进行简要比较:

  1. 命名管道(Named Pipes)—命名管道在管道服务器和一个或多个管道客户端之间提供进程间通信:

    • 特点:基于命名管道的进程间通信是一种双向通信方式,适用于客户端-服务器模式,并支持异步通信。可以跨机器通信,提供较高的可靠性和安全性。
    • 性能:命名管道的性能通常比较高,可以实现高速数据传输,尤其适用于数据量较大的情况。
  2. 邮槽(MailSlots):

    • 特点:邮槽是一种单向通信方式,适用于广播或发布-订阅模式的通信。可以跨机器通信,支持多个读取者同时读取数据。
    • 性能:邮槽的性能一般较好,但相对于命名管道来说可能会有一些额外的开销,例如广播的数据可能需要传递给多个读取者。
  3. 共享内存(Shared Memory):

    • 特点:共享内存提供在不同进程间共享数据的机制,数据直接存储在内存中,进程可以直接读取或写入共享内存区域。
    • 性能:共享内存是最快的进程间通信方式,因为数据直接存储在内存中,无需在进程间进行数据拷贝,但需要有效的同步机制来确保数据一致性和安全性。
  4. 消息队列,windows自带的MSMQ不做叙述了(简单但是需要在控制面板安装不推荐),分布式的一些中间件比如rabbitmq还是不错的。

综合考虑,命名管道通常是性能较高且灵活性较好的进程间通信方式。如果需要双向通信或跨机器通信,命名管道是一个不错的选择。邮槽适用于单向广播或发布-订阅模式的通信。共享内存在性能方面最佳,但需要额外的同步机制来处理数据的一致性和安全性。在选择合适的通信方式时,还应考虑具体应用的需求和特点。

命名管道示例

// 客户端-------------------------------------------
 private void FrmMonitoring_Load(object sender, EventArgs e)
        {
            // 在窗体加载事件中启动一个线程来监听管道
            listeningThread = new Thread(ListenToPipe);
            listeningThread.Start();
        }


        // 监听管道的方法
        private void ListenToPipe()
        {
            while (continueListening)
            {
                // 创建命名管道客户端
                using (NamedPipeClientStream clientStream = new NamedPipeClientStream(".", "MyPipe", PipeDirection.In))
                {
                    clientStream.Connect(); // 连接到服务端的管道
                    // 从管道接收消息
                    StreamReader reader = new StreamReader(clientStream);
                    string message = reader.ReadToEnd();

                    // 将消息显示在RichTextBox控件中
                    this.Invoke((MethodInvoker)delegate {
                        this.richTextBox1.AppendText(message);
                    });
                }
            }
        }
// 服务端======================================================
        protected override void OnStart(string[] args)
        {
            serverThread = new Thread(ServerTask.beginTask);
            serverThread.Start();

            // 启动定时器
            _timer = new System.Timers.Timer(TimeSpan.FromSeconds(10).TotalMilliseconds);  // 创建时间间隔为 Interval 秒的定时器对象
            _timer.Elapsed += OnTimerElapsed;      // 绑定定时器事件处理方法
            _timer.AutoReset = true;                // 设置定时器自动重置
            _timer.Enabled = true;                  // 启动定时器

        }

        // 定时器触发的事件处理方法
        private void OnTimerElapsed(object sender, ElapsedEventArgs e)
        {
            // 创建命名管道服务器
            NamedPipeServerStream serverStream = new NamedPipeServerStream("MyPipe", PipeDirection.Out);
            serverStream.WaitForConnection(); // 等待管道连接

            // 在服务中发送消息
            string message = "Hello from the service!";
            byte[] buffer = Encoding.UTF8.GetBytes(message);
            serverStream.Write(buffer, 0, buffer.Length);
            serverStream.Flush();
            serverStream.Close(); // 关闭管道连接

        }

另一种命名管道写法示例(避免频繁创建 NamedPipeClientStream和NamedPipeServerStream),使用客户端发送消息,服务端接收

// 客户端(在我这里是windows服务)=========================================

using System;
using System.Collections.Generic;
using System.IO.Pipes;
using System.IO;
using System.Linq;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;

namespace CommonLib.ipc
{
    // 客户端帮助类
    public class NamedPipeClientHelper
    {
        private NamedPipeClientStream pipeClient;
        private StreamWriter sw;

        public NamedPipeClientHelper(string pipeName)
        {
            pipeClient = new NamedPipeClientStream("localhost", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous, TokenImpersonationLevel.None);
        }

        public void SendMessage(string message)
        {
            if (!pipeClient.IsConnected)
            {
                pipeClient.Connect(5000);
            }
            try
            {
                sw = new StreamWriter(pipeClient);
                sw.AutoFlush = true;
                if (sw != null)
                {
                    sw.WriteLine(message);
                }
            }
            catch { }
        }
    }
}

// 服务端(在我这里是窗口程序)

using System;
using System.IO;
using System.IO.Pipes;
using System.Threading;

namespace CommonLib.ipc
{
    // 服务端帮助类
    public class NamedPipeServerHelper
    {
        private NamedPipeServerStream pipeServer;

        public NamedPipeServerHelper(string pipeName)
        {
            pipeServer = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Message, PipeOptions.Asynchronous);
        }

        // 定义接收消息的事件
        public event EventHandler<string> MessageReceived;

        public void StartListening()
        {
            ThreadPool.QueueUserWorkItem(delegate
            {
                pipeServer.BeginWaitForConnection((o) =>
                {
                    NamedPipeServerStream pServer = (NamedPipeServerStream)o.AsyncState;
                    pServer.EndWaitForConnection(o);
                    StreamReader sr = new StreamReader(pServer);
                    while (true)
                    {
                        string msg = sr.ReadLine();
                        Console.WriteLine(msg + DateTime.Now);
                        MessageReceived?.Invoke(this, msg);
                    }
                }, pipeServer);
            });
        }
    }
}

共享内存示例(下面这个示例展示了在 C# 中使用命名共享内存进行跨进程通信的方法:)

服务端代码(C#):



using System;
using System.IO.MemoryMappedFiles;
using System.Text;

public class MyService
{
    private MemoryMappedFile memoryMappedFile;
    private MemoryMappedViewAccessor accessor;

    public void Start()
    {
        // 创建或打开命名共享内存
        string sharedMemoryName = "MySharedMemory";
        memoryMappedFile = MemoryMappedFile.CreateOrOpen(sharedMemoryName, 1024);

        // 创建共享内存的访问器
        accessor = memoryMappedFile.CreateViewAccessor();

        // 写入数据到共享内存
        string message = "这是来自服务的消息";
        byte[] data = Encoding.UTF8.GetBytes(message);
        accessor.WriteArray(0, data, 0, data.Length);

        // 关闭共享内存访问器和共享内存
        accessor.Dispose();
        memoryMappedFile.Dispose();

        Console.WriteLine("消息发送成功");
    }
}

public static class Program
{
    public static void Main()
    {
        MyService myService = new MyService();
        myService.Start();
    }
}

客户端代码(C#):

using System;
using System.IO.MemoryMappedFiles;
using System.Text;

public class MyClient
{
    private MemoryMappedFile memoryMappedFile;
    private MemoryMappedViewAccessor accessor;

    public void ReceiveMessage()
    {
        // 打开命名共享内存
        string sharedMemoryName = "MySharedMemory";
        memoryMappedFile = MemoryMappedFile.OpenExisting(sharedMemoryName);

        // 创建共享内存的访问器
        accessor = memoryMappedFile.CreateViewAccessor();

        // 从共享内存读取数据
        byte[] data = new byte[1024];
        accessor.ReadArray(0, data, 0, data.Length);
        string message = Encoding.UTF8.GetString(data).TrimEnd('\0');

        // 输出接收到的消息
        Console.WriteLine("接收到的消息:" + message);

        // 关闭共享内存访问器和共享内存
        accessor.Dispose();
        memoryMappedFile.Dispose();
    }
}

public static class Program
{
    public static void Main()
    {
        MyClient myClient = new MyClient();
        myClient.ReceiveMessage();
    }
}

在上述示例中,服务端使用 MemoryMappedFile 创建或打开了一个命名共享内存,并使用 MemoryMappedViewAccessor 将数据写入到共享内存中。客户端使用相同的共享内存名称来打开共享内存,并使用 MemoryMappedViewAccessor 从共享内存中读取数据。

请注意,共享内存的大小需要根据实际需求进行设置,并且确保在服务端和客户端代码中使用相同的共享内存名称。

这种方式可以使服务端和客户端之间通过共享内存进行高速数据传输,但需要注意的是,在跨机器通信时,可能需要使用其他的机制(如网络通信)传递共享内存的位置信息。

延时算法

// other。。。calls++;
   while (calls > 0)
                {
                    ClsGlobal.WriteSyncResult("准备执行同步,等待中...");
                    // 在循环中进行对 calls 的判断,直到 calls 不再大于 0
                    int delay = calls * 10; // 延时时间为调用次数乘以10秒
                    Thread.Sleep(delay * 1000);
                    Interlocked.Decrement(ref calls);
                }

c#导出带格式的excel方法(epplus)

            DataTable dataTable = oa.getUser();
            // 创建Excel工作簿
            ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
            using (ExcelPackage excelPackage = new ExcelPackage())
            {
                ExcelWorksheet worksheet = excelPackage.Workbook.Worksheets.Add("Sheet1");

                // 设置字体样式
                var font = worksheet.Cells.Style.Font;
                font.Name = "微软雅黑";
                font.Size = 14;

                // 设置单元格样式
                var cellStyle = worksheet.Cells.Style;
                cellStyle.Font.Bold = true;
                cellStyle.HorizontalAlignment = ExcelHorizontalAlignment.Center;
                // 设置单元格自动换行
                cellStyle.WrapText = true;
               worksheet.Column(1).Width = 40;
                // 自动调整列宽,但是这个会忽略带换行的单元格->不适用
                //worksheet.Cells.AutoFitColumns();
                // 遍历数据集并写入Excel文件
                int row = 1;
                foreach (DataRow dataRow in dataTable.Rows)
                {
                    string[] rows = new string[]
                    {
                        "门禁考勤卡",
                        "广质院/" + dataRow["DEPT_NAME"].ToString(),
                                    dataRow["USER_NAME"].ToString(),
                                    dataRow["USER_ID"].ToString(),

                    };

                    ExcelRange cell = worksheet.Cells[row, 1];

                    // 多行数据合并到一个单元格
                    string mergedData = string.Join("\n", rows);

                    // 在单元格中写入数据
                    cell.Value = mergedData;

                    row++;
                }

                worksheet.Cells.Style.ShrinkToFit = true;
                // 保存Excel文件
                string filePath = "C:\\aa.xlsx";
                excelPackage.SaveAs(new System.IO.FileInfo(filePath));

c#使用openxml读取excel(xlsx)

private void LoadExcelData(string filePath)
        {
            dataGrid1.Rows.Clear();
            dataGrid1.Columns.Clear();

            using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Open(filePath, false))
            {
                WorkbookPart workbookPart = spreadsheetDocument.WorkbookPart;
                WorksheetPart worksheetPart = workbookPart.WorksheetParts.First();
                SheetData sheetData = worksheetPart.Worksheet.Elements<SheetData>().First();

                // 获取表头集合
                Row headerRow = sheetData.Elements<Row>().First();
                List<string> columnList = new List<string>();
                foreach (Cell cell in headerRow.Elements<Cell>())
                {
                    columnList.Add(GetCellValue(cell, workbookPart));
                }

                // 跳过表头
                IEnumerable<Row> rows = sheetData.Elements<Row>().Skip(1);

                for (int i = 0; i < columnList.Count; i++)
                {
                    dataGrid1.Columns.Add(i.ToString(), columnList[i], 120);
                }

                foreach (Row row in rows)
                {
                    dataGrid1.Rows.Add();
                    int rowIndex = (int)(row.RowIndex - 2); // DataGridView索引从0开始,需要减去表头和跳过的行数
                    foreach (Cell cell in row.Elements<Cell>())
                    {
                        int columnIndex = GetColumnIndexFromName(GetColumnName(cell.CellReference));

                        if (columnIndex >= 0)
                        {
                            string cellValue = GetCellValue(cell, workbookPart);
                            dataGrid1.Rows[rowIndex].Cells[columnIndex].Value = cellValue;
                        }
                    }
                }
            }
        }

        private string GetCellValue(Cell cell, WorkbookPart workbookPart)
        {
            if (cell.DataType == null)
            {
                return cell.InnerText;
            }
            else
            {
                // 处理XLSX文件中的共享字符串类型的单元格。
                SharedStringTablePart sharedStringTablePart = workbookPart.SharedStringTablePart;
                if (sharedStringTablePart != null)
                {
                    return sharedStringTablePart.SharedStringTable.ElementAt(int.Parse(cell.InnerText)).InnerText;
                }
                else
                {
                    return cell.InnerText;
                }
            }
        }

        private string GetColumnName(string cellReference)
        {
            return new string(cellReference.Where(c => char.IsLetter(c)).ToArray());
        }

        private int GetColumnIndexFromName(string columnName)
        {
            int columnIndex = -1;
            int factor = 1;

            foreach (char c in columnName.Reverse())
            {
                columnIndex += factor * (c - 'A' + 1);
                factor *= 26;
            }

            return columnIndex - 1;
        }

c#使用miniexcel读取xlsx文件

            IEnumerable<Dictionary<int, object>> keyValues = MiniExcelUtils.GetExcelData(filePath); // 10s
            // 获取表头集合
            columnList = keyValues.ElementAtOrDefault(0)?.Values.OfType<string>().ToList();
            // 跳过表头
            IEnumerable<Dictionary<int, object>> rowList = keyValues.Skip(1);

c#使用ExcelDataReader读取excel(xlsx、xls、csv)

// 调用处
 DataTable dataTable = ExcelHelper.ExcelToDataTable(filePath);
            // 添加列
            for (int i = 0; i < dataTable.Columns.Count; i++)
            {
                dataGrid1.Columns.Add(i.ToString(), dataTable.Columns[i].ColumnName, 120);
            }

            dataGrid1.Rows.Add(dataTable.Rows.Count);

            // 添加行
            for (int j = 0; j < dataTable.Rows.Count; j++)
            {

                for (int i = 0; i < dataTable.Columns.Count; i++)
                {
                    dataGrid1.Rows[j].Cells[i].Value = dataTable.Rows[j][i];
                }
            }  
// ===============================
  public class ExcelHelper
    {
        public static DataSet ExcelToDataSet(string relativeFilePath, bool useHeaderRow = true)
        {
            var filePath = Path.GetFullPath(relativeFilePath);
            if (!File.Exists(filePath))
            {
                throw new FileNotFoundException("找不到文件!");
            }

            var extension = Path.GetExtension(filePath).ToLower();
            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            {
                var sw = new Stopwatch();
                sw.Start();
                IExcelDataReader reader = null;
                if (extension == ".xls")
                {
                    reader = ExcelReaderFactory.CreateBinaryReader(stream);
                }
                else if (extension == ".xlsx")
                {
                    reader = ExcelReaderFactory.CreateOpenXmlReader(stream);
                }
                else if (extension == ".csv")
                {
                    reader = ExcelReaderFactory.CreateCsvReader(stream, new ExcelReaderConfiguration()
                    {
                        FallbackEncoding = Encoding.GetEncoding("GB2312")
                    });
                }

                if (reader == null)
                    return null;

                var openTiming = sw.ElapsedMilliseconds;

                DataSet ds;

                using (reader)
                {
                    ds = reader.AsDataSet(new ExcelDataSetConfiguration()
                    {
                        UseColumnDataType = false,
                        ConfigureDataTable = (tableReader) => new ExcelDataTableConfiguration()
                        {
                            UseHeaderRow = useHeaderRow // 第一行包含列名
                        }
                    });
                }

                return ds;
            }
        }

        public static DataTable ExcelToDataTable(string relativeFilePath, int sheet = 0, bool useHeaderRow = true)
        {
            var ds = ExcelToDataSet(relativeFilePath, useHeaderRow);
            if (ds == null)
                return null;
            return ds.Tables[sheet];
        }
    }

c#实现过滤规则遍历(for循环遍历/递归)

for循环遍历:

for (int rowIndex = 0; rowIndex < rowCount; rowIndex++)
            {
                // 第一种情况
                foreach (KeyValuePair<RuleEntity, int> kv in sRuleDict)
                {
                    RuleEntity rule = kv.Key;
                    int columnIndex = kv.Value;

                    string value = gqtDataGrid1.Rows[rowIndex].Cells[columnIndex].Value.ToString();

                    if (!string.IsNullOrEmpty(value))
                    {
                        switch (rule.VerifyType)
                        {
                            case 1:
                                // 执行值验证的处理逻辑
                                switch (rule.Calculation)
                                {
                                    case "等于":
                                        string[] checkEqual = rule.Checksums.Split(',');
                                        bool vEqual = false;
                                        foreach (string item in checkEqual)
                                        {
                                            if (value == item)
                                            {
                                                vEqual = true;
                                            }
                                        }
                                        if (!vEqual)
                                        {
                                            SetErrorCellStyle(rowIndex, columnIndex, "错误!该项等于规定值");
                                        }
                                        break;
                                    case "不等于":
                                        string[] checkNoEqual = rule.Checksums.Split(',');
                                        bool vNoEqual = true;
                                        foreach (string item in checkNoEqual)
                                        {
                                            if (value == item)
                                            {
                                                vNoEqual = false;
                                            }
                                        }
                                        if (vNoEqual)
                                        {
                                            SetErrorCellStyle(rowIndex, columnIndex, "错误!该项不等于规定值");
                                        }
                                        break;
                                }
                                break;
                            case 2:
                                // 执行长度验证的处理逻辑
                                switch (rule.Calculation)
                                {
                                    case "不等于":
                                        string[] checkEqual = rule.Checksums.Split(',');
                                        bool vNoEqual = true;
                                        foreach (string item in checkEqual)
                                        {
                                            if (value == item)
                                            {
                                                vNoEqual = false;
                                            }
                                        }
                                        if (vNoEqual)
                                        {
                                            SetErrorCellStyle(rowIndex, columnIndex, "错误!该项长度不等于规定值!");
                                        }
                                        break;
                                    case "大于":
                                        string[] checkGreater = rule.Checksums.Split(',');
                                        bool vGreater = false;
                                        foreach (string item in checkGreater)
                                        {
                                            if (value.Length > int.Parse(item))
                                            {
                                                vGreater = true;
                                            }
                                        }
                                        if (vGreater)
                                        {
                                            SetErrorCellStyle(rowIndex, columnIndex, "错误!该项长度大于规定值!");
                                        }
                                        break;
                                    default:
                                        break;

                                }
                                break;
                            default:
                                // 处理其他验证类型
                                break;
                        }
                    }
                }

                // 循环遍历联动规则字典
                bool allConditionsSatisfied = true; // 初始化为 true,表示所有规则都满足条件

                foreach (Dictionary<RuleEntity, int> dict in jointGroups)
                {
                    bool groupConditionsSatisfied = true; // 初始化为 true,表示当前联动规则组的条件满足

                    foreach (KeyValuePair<RuleEntity, int> kv in dict)
                    {
                        RuleEntity rule = kv.Key;
                        int columnIndex = kv.Value;

                        string value = gqtDataGrid1.Rows[rowIndex].Cells[columnIndex].Value.ToString();

                        // 调用判断函数处理单个规则
                        bool isSatisfied = processRule(rule, value);

                        // 判断当前规则是否满足条件
                        if (!isSatisfied)
                        {
                            groupConditionsSatisfied = false;
                            break; // 如果一个规则不满足条件,就跳出内层循环,无需再判断之后的规则
                        }
                    }

                    // 判断当前联动规则组的条件是否满足
                    if (!groupConditionsSatisfied)
                    {
                        allConditionsSatisfied = false;
                        break; // 如果一个联动规则组的条件不满足,就跳出外层循环,无需再判断之后的规则组
                    }else
                    {
                        Console.WriteLine("报错!!!!!!!!!");
                    }
                }

                // 所有规则条件是否满足的判断结果
                if (allConditionsSatisfied)
                {
                    // 执行满足条件时的操作
                    Console.WriteLine("所有规则条件都满足,执行相关操作");
                }
            }

递归遍历:

 foreach (Dictionary<RuleEntity, int> dict in jointGroups)
                {
                    bool groupConditionsSatisfied = recursionRule(dict, 0, rowIndex);

                    if (groupConditionsSatisfied)
                    {
                        Console.WriteLine("报错!!!!!!!!");
                    }
                }
     private bool recursionRule(Dictionary<RuleEntity, int> dict, int dictIndex, int rowIndex)
        {
            if (dictIndex >= dict.Count)
            {
                // 递归结束条件:已经遍历完所有规则
                return true;
            }

            KeyValuePair<RuleEntity, int> keyValuePair = dict.ElementAt(dictIndex);
            RuleEntity rule = keyValuePair.Key;
            int columnIndex = keyValuePair.Value;
            string value = gqtDataGrid1.Rows[rowIndex].Cells[columnIndex].Value.ToString();

            bool satisfied = false; // 标记当前规则是否满足条件
            switch (rule.VerifyType)
            {
                case 1:
                    // 执行值验证的处理逻辑
                    if (rule.Calculation == "等于")
                    {
                        string[] checkEqual = rule.Checksums.Split(',');
                        foreach (string item in checkEqual)
                        {
                            if (value == item)
                            {
                                satisfied = true;
                                break;
                            }
                        }
                    }
                    else if (rule.Calculation == "不等于")
                    {
                        string[] checkNoEqual = rule.Checksums.Split(',');
                        satisfied = true; // 默认满足条件
                        foreach (string item in checkNoEqual)
                        {
                            if (value == item)
                            {
                                satisfied = false;
                                break;
                            }
                        }
                    }
                    break;
                case 2:
                    // 执行长度验证的处理逻辑
                    if (rule.Calculation == "不等于")
                    {
                        string[] checkEqual = rule.Checksums.Split(',');
                        satisfied = true; // 默认满足条件
                        foreach (string item in checkEqual)
                        {
                            if (value == item)
                            {
                                satisfied = false;
                                break;
                            }
                        }
                    }
                    else if (rule.Calculation == "大于")
                    {
                        string checkGreater = rule.Checksums;
                        if (value.Length > int.Parse(checkGreater))
                        {
                            satisfied = true;
                        }
                    }
                    break;
                default:
                    satisfied = true; // 处理其他验证类型,默认满足条件
                    break;
            }


            if (satisfied)
            {
                // 当前规则满足条件,继续递归判断下一个规则
                return recursionRule(dict, dictIndex + 1, rowIndex);
            }
            else
            {
                // 当前规则不满足条件,返回 false
                return false;
            }

        }

C#整数索引转字母

        // 将整数索引转换为字母列名
private static string ConvertToLetter(int column)
{
    string letter = "";
    while (column >= 0)
    {
        letter = (char)('A' + (column % 26)) + letter;
        column = (column / 26) - 1;
    }
    return letter;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值