C++程序调用开源libcurl库实现SMTP发送邮件的功能

1、本文给出封装的C++类,头文件如下:

#pragma once  

#include <string>  
#include <vector>  

#define SKIP_PEER_VERIFICATION  
#define SKIP_HOSTNAME_VERIFICATION  


class CSmtpSendMail{  
public:  
	CSmtpSendMail(const std::string & charset="gb2312"); // 也可以传入utf

	//设置stmp服务器、用户名、密码、端口(端口其实不用指定,libcurl默认25,但如果是smtps则默认是465)  
	void SetSmtpServer(const std::string &username, const std::string& password, const std::string& servername, const std::string &port="25");  
	//发送者姓名,可以不用  

	void SetSendName(const std::string& sendname);  

	//发送者邮箱   
	void SetSendMail(const std::string& sendmail);  

	//添加收件人  
	void AddRecvMail(const std::string& recvmail);

	//设置主题  
	void SetSubject(const std::string &subject);  

	//设置正文内容  
	void SetBodyContent(const std::string &content);  

	//添加附件  
	void AddAttachment(const std::string &filename);  

	//发送邮件  
	bool SendMail();  
private:  

	//回调函数,将MIME协议的拼接的字符串由libcurl发出  
	static size_t payload_source(void *ptr, size_t size, size_t nmemb, void *stream);  

	//创建邮件MIME内容  
	void CreatMessage();  

	//获取文件类型  
	int GetFileType(std::string const& stype);  

	//设置文件名  
	void SetFileName(const std::string& FileName);  

	//设置文件的contenttype  
	void SetContentType(std::string const& stype);  

	//得到文件名  
	void GetFileName(const std::string& file, std::string& filename); 

	//得到文件类型  
	void GetFileType(const std::string& file, std::string& stype);  

private:  
	std::string m_strCharset; //邮件编码  
	std::string m_strSubject; //邮件主题  
	std::string m_strContent; //邮件内容  
	std::string m_strFileName; //文件名  
	std::string m_strMessage;// 整个MIME协议字符串  
	std::string m_strUserName;//用户名  
	std::string m_strPassword;//密码  
	std::string m_strServerName;//smtp服务器  
	std::string m_strPort;//端口  
	std::string m_strSendName;//发送者姓名  
	std::string m_strSendMail;//发送者邮箱  
	std::string m_strContentType;//附件contenttype  
	std::string m_strFileContent;//附件内容  

	std::vector<std::string> m_vRecvMail; //收件人容器  
	std::vector<std::string> m_vAttachMent;//附件容器  
};  

cpp文件实现如下:

#include "stdafx.h"    
#include "smtp.h"  
#include "base64.h"  
#include ".\curl\curl.h" 
#include <iostream>  
#include <sstream>  
#include <fstream>  

CSmtpSendMail::CSmtpSendMail(const std::string & charset)  
{  
	m_strCharset = charset;  
	m_vRecvMail.clear();  
}  

void CSmtpSendMail::SetSmtpServer(const std::string & username, const std::string &password, const std::string & servername, const std::string & port)  
{  
	m_strUserName = username;  
	m_strPassword = password;  
	m_strServerName = servername;  
	m_strPort = port;  
}  

void CSmtpSendMail::SetSendName(const std::string & sendname)  
{  
	std::string strTemp = "";  
	strTemp += "=?";  
	strTemp += m_strCharset;  
	strTemp += "?B?";  
	strTemp += base64_encode((unsigned char *)sendname.c_str(), sendname.size());  
	strTemp += "?=";  
	m_strSendName = strTemp;  
}  

void CSmtpSendMail::SetSendMail(const std::string & sendmail)  
{  
	m_strSendMail = sendmail;  
}  

void CSmtpSendMail::AddRecvMail(const std::string & recvmail)  
{  
	m_vRecvMail.push_back(recvmail);  
}  

void CSmtpSendMail::SetSubject(const std::string & subject)  
{  
	std::string strTemp = "";  
	strTemp = "Subject: ";  
	strTemp += "=?";  
	strTemp += m_strCharset;  
	strTemp += "?B?";  
	strTemp += base64_encode((unsigned char *)subject.c_str(), subject.size());  
	strTemp += "?=";  
	m_strSubject = strTemp;  
}  

void CSmtpSendMail::SetBodyContent(const std::string & content)  
{  
	m_strContent = content;  
}  

void CSmtpSendMail::AddAttachment(const std::string & filename)  
{  
	m_vAttachMent.push_back(filename);  
}  

bool CSmtpSendMail::SendMail()  
{  
	CreatMessage();  
	bool ret = true;  
	CURL *curl;  
	CURLcode res = CURLE_OK;  
	struct curl_slist *recipients = NULL;  

	curl = curl_easy_init();  
	if (curl) {  
		/* Set username and password */                                        
		curl_easy_setopt(curl, CURLOPT_USERNAME, m_strUserName.c_str());  
		curl_easy_setopt(curl, CURLOPT_PASSWORD, m_strPassword.c_str());  
		std::string tmp = "smtp://";  
		tmp += m_strServerName;  
		// 注意不能直接传入tmp,应该带上.c_str(),否则会导致下面的
		// curl_easy_perform调用返回CURLE_COULDNT_RESOLVE_HOST错误
		// 码
		curl_easy_setopt(curl, CURLOPT_URL, tmp.c_str());  
		/* If you want to connect to a site who isn't using a certificate that is 
		* signed by one of the certs in the CA bundle you have, you can skip the 
		* verification of the server's certificate. This makes the connection 
		* A LOT LESS SECURE. 
		* 
		* If you have a CA cert for the server stored someplace else than in the 
		* default bundle, then the CURLOPT_CAPATH option might come handy for 
		* you. */  
#ifdef SKIP_PEER_VERIFICATION  
		curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);  
#endif  

		/* If the site you're connecting to uses a different host name that what 
		* they have mentioned in their server certificate's commonName (or 
		* subjectAltName) fields, libcurl will refuse to connect. You can skip 
		* this check, but this will make the connection less secure. */  
#ifdef SKIP_HOSTNAME_VERIFICATION  
		curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);  
#endif  

		/* Note that this option isn't strictly required, omitting it will result 
		* in libcurl sending the MAIL FROM command with empty sender data. All 
		* autoresponses should have an empty reverse-path, and should be directed 
		* to the address in the reverse-path which triggered them. Otherwise, 
		* they could cause an endless loop. See RFC 5321 Section 4.5.5 for more 
		* details. 
		*/  
		//curl_easy_setopt(curl, CURLOPT_MAIL_FROM, FROM);   
		curl_easy_setopt(curl, CURLOPT_MAIL_FROM, m_strSendMail.c_str());  
		/* Add two recipients, in this particular case they correspond to the 
		* To: and Cc: addressees in the header, but they could be any kind of 
		* recipient. */  
		for (size_t i = 0; i < m_vRecvMail.size(); i++) {  

			recipients = curl_slist_append(recipients, m_vRecvMail[i].c_str());  
		}  
		curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients);  

		std::stringstream stream;  
		stream.str(m_strMessage.c_str());  
		stream.flush();  
		/* We're using a callback function to specify the payload (the headers and 
		* body of the message). You could just use the CURLOPT_READDATA option to 
		* specify a FILE pointer to read from. */  

		// 注意回调函数必须设置为static
		curl_easy_setopt(curl, CURLOPT_READFUNCTION, &CSmtpSendMail::payload_source);  
		curl_easy_setopt(curl, CURLOPT_READDATA, (void *)&stream);  
		curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);  

		/* Since the traffic will be encrypted, it is very useful to turn on debug 
		* information within libcurl to see what is happening during the 
		* transfer */  
		curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);  

		int nTimes = 0;
		/* Send the message */  
		res = curl_easy_perform(curl);  
		CURLINFO info = CURLINFO_NONE;  
		curl_easy_getinfo(curl, info);  
		/* Check for errors */  

		while (res != CURLE_OK) {  

			nTimes++;
			if ( nTimes > 5 )
			{
				break;
			}
			fprintf(stderr, "curl_easy_perform() failed: %s\n\n",  
				curl_easy_strerror(res));  

			char achTip[512] = {0};
			sprintf( achTip, "curl_easy_perform() failed: %s\n\n", curl_easy_strerror(res) );
			::MessageBoxA( NULL, achTip, "Tip", MB_OK);
			ret = false;  

			/*				Sleep( 100 );
			res = curl_easy_perform(curl); */ 
		}  

		/* Free the list of recipients */  
		curl_slist_free_all(recipients);  

		/* Always cleanup */  
		curl_easy_cleanup(curl);  

	}  
	return ret;  
}  

size_t CSmtpSendMail::payload_source(void *ptr, size_t size, size_t nmemb, void *stream)  
{  
	size_t num_bytes = size * nmemb;  
	char* data = (char*)ptr;  
	std::stringstream* strstream = (std::stringstream*)stream;  

	strstream->read(data, num_bytes);  

	return strstream->gcount();  
}  

void CSmtpSendMail::CreatMessage()  
{  
	m_strMessage = "From: ";  
	m_strMessage += m_strSendMail;  
	m_strMessage += "\r\nReply-To: ";  
	m_strMessage += m_strSendMail;  
	m_strMessage += "\r\nTo: ";  
	for (size_t i = 0; i < m_vRecvMail.size(); i++)  
	{  
		if (i > 0) {  
			m_strMessage += ",";  
		}  
		m_strMessage += m_vRecvMail[i];  
	}  
	m_strMessage += "\r\n";  
	m_strMessage += m_strSubject;  
	m_strMessage += "\r\nX-Mailer: The Bat! (v3.02) Professional";  
	m_strMessage += "\r\nMime-Version: 1.0";  
	m_strMessage += "\r\nContent-Type: multipart/mixed;";  
	m_strMessage += "boundary=\"simple boundary\"";  
	m_strMessage += "\r\nThis is a multi-part message in MIME format.";  
	m_strMessage += "\r\n--simple boundary";  
	//正文  
	m_strMessage += "\r\nContent-Type: text/html;";  
	m_strMessage += "charset=";  
	m_strMessage += "\"";  
	m_strMessage += m_strCharset;  
	m_strMessage += "\"";  
	m_strMessage += "\r\nContent-Transfer-Encoding: 7BIT";  
	m_strMessage += "\r\n\r\n";  
	m_strMessage += m_strContent;  

	//附件  
	std::string filename = "";  
	std::string filetype = "";  
	for (size_t i = 0; i < m_vAttachMent.size(); i++)  
	{  
		m_strMessage += "\r\n--simple boundary";  
		GetFileName(m_vAttachMent[i], filename);  
		GetFileType(m_vAttachMent[i], filetype);  
		SetContentType(filetype);  
		SetFileName(filename);  

		m_strMessage += "\r\nContent-Type: ";  
		m_strMessage += m_strContentType;  
		m_strMessage += "\tname=";  
		m_strMessage += "\"";  
		m_strMessage += m_strFileName;  
		m_strMessage += "\"";  
		m_strMessage += "\r\nContent-Disposition:attachment;filename=";  
		m_strMessage += "\"";  
		m_strMessage += m_strFileName;  
		m_strMessage += "\"";  
		m_strMessage += "\r\nContent-Transfer-Encoding:base64";   
		m_strMessage += "\r\n\r\n";  


		FILE *pt = NULL;  
		if ((pt = fopen(m_vAttachMent[i].c_str(), "rb")) == NULL) {  

			std::cerr << "打开文件失败: " << m_vAttachMent[i] <<std::endl;  
			continue;  
		}  
		fseek(pt, 0, SEEK_END);  
		int len = ftell(pt);  
		fseek(pt, 0, SEEK_SET);  
		int rlen = 0;  
		char buf[55];  
		for (size_t i = 0; i < len / 54 + 1; i++)  
		{  
			memset(buf, 0, 55);  
			rlen = fread(buf, sizeof(char), 54, pt);  
			m_strMessage += base64_encode((const unsigned char*)buf, rlen);  
			m_strMessage += "\r\n";  
		}  
		fclose(pt);  
		pt = NULL;  

	}  
	m_strMessage += "\r\n--simple boundary--\r\n";  

}  


int CSmtpSendMail::GetFileType(std::string const & stype)  
{  
	if (stype == "txt")  
	{  
		return 0;  
	}  
	else if (stype == "xml")  
	{  
		return 1;  
	}  
	else if (stype == "html")  
	{  
		return 2;  
	}  
	else if (stype == "jpeg")  
	{  
		return 3;  
	}  
	else if (stype == "png")  
	{  
		return 4;  
	}  
	else if (stype == "gif")  
	{  
		return 5;  
	}  
	else if (stype == "exe")  
	{  
		return 6;  
	}  

	return -1;  
}  

void CSmtpSendMail::SetFileName(const std::string & FileName)  
{  
	std::string EncodedFileName = "=?";  
	EncodedFileName += m_strCharset;  
	EncodedFileName += "?B?";//修改  
	EncodedFileName += base64_encode((unsigned char *)FileName.c_str(), FileName.size());  
	EncodedFileName += "?=";  
	m_strFileName = EncodedFileName;  
}  

void CSmtpSendMail::SetContentType(std::string const & stype)  
{  
	int type = GetFileType(stype);  
	switch (type)  
	{//  
	case 0:  
		m_strContentType = "plain/text;";  
		break;  

	case 1:  
		m_strContentType = "text/xml;";  
		break;  

	case 2:  
		m_strContentType = "text/html;";  

	case 3:  
		m_strContentType = "image/jpeg;";  
		break;  

	case 4:  
		m_strContentType = "image/png;";  
		break;  

	case 5:  
		m_strContentType = "image/gif;";  
		break;  

	case 6:  
		m_strContentType = "application/x-msdownload;";  
		break;  

	default:  
		m_strContentType = "application/octet-stream;";  
		break;  
	}  
}  

void CSmtpSendMail::GetFileName(const std::string& file, std::string& filename)  
{  

	std::string::size_type p = file.find_last_of('/');  
	if (p == std::string::npos)  
		p = file.find_last_of('\\');  
	if (p != std::string::npos) {  
		p += 1; // get past folder delimeter  
		filename = file.substr(p, file.length() - p);  
	}  
}  

void CSmtpSendMail::GetFileType(const std::string & file, std::string & stype)  
{  
	std::string::size_type p = file.find_last_of('.');  
	if (p != std::string::npos) {  
		p += 1; // get past folder delimeter  
		stype = file.substr(p, file.length() - p);  
	}  
}  

2、上面使用到的base64编码的头文件和cpp文件如下:

#ifndef _BASE64_H_  
#define _BASE64_H_  

#include <string>

std::string base64_encode(unsigned char const* , unsigned int len);  
std::string base64_decode(std::string const& s);  

#endif
/*   
   base64.cpp and base64.h  
  
   Copyright (C) 2004-2008 Ren?Nyffenegger  
  
   This source code is provided 'as-is', without any express or implied  
   warranty. In no event will the author be held liable for any damages  
   arising from the use of this software.  
  
   Permission is granted to anyone to use this software for any purpose,  
   including commercial applications, and to alter it and redistribute it  
   freely, subject to the following restrictions:  
  
   1. The origin of this source code must not be misrepresented; you must not  
      claim that you wrote the original source code. If you use this source code  
      in a product, an acknowledgment in the product documentation would be  
      appreciated but is not required.  
  
   2. Altered source versions must be plainly marked as such, and must not be  
      misrepresented as being the original source code.  
  
   3. This notice may not be removed or altered from any source distribution.  
  
   Ren?Nyffenegger rene.nyffenegger@adp-gmbh.ch  
  
*/  
#include "stdafx.h"
#include "base64.h"  
#include <iostream>  
  
static const std::string base64_chars =   
             "ABCDEFGHIJKLMNOPQRSTUVWXYZ"  
             "abcdefghijklmnopqrstuvwxyz"  
             "0123456789+/";  
  
  
static inline bool is_base64(unsigned char c)   
{  
  return (isalnum(c) || (c == '+') || (c == '/'));  
}  
  
std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len)   
{  
  std::string ret;  
  int i = 0, j = 0;  
  unsigned char char_array_3[3], char_array_4[4];  
  
  while (in_len--)  
    {  
    char_array_3[i++] = *(bytes_to_encode++);  
    if (i == 3)   
        {  
      char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;  
      char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);  
      char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);  
      char_array_4[3] = char_array_3[2] & 0x3f;  
  
      for(i = 0; (i <4) ; i++)  
        ret += base64_chars[char_array_4[i]];  
      i = 0;  
    }  
  }  
  
  if (i)  
  {  
    for(j = i; j < 3; j++)  
      char_array_3[j] = '\0';  
  
    char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;  
    char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);  
    char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);  
    char_array_4[3] = char_array_3[2] & 0x3f;  
  
    for (j = 0; (j < i + 1); j++)  
      ret += base64_chars[char_array_4[j]];  
  
    while((i++ < 3))  
      ret += '=';  
  
  }  
  
  return ret;  
  
}  
  
std::string base64_decode(std::string const& encoded_string)   
{  
  int in_len = encoded_string.size();  
  int i = 0, j = 0, in_ = 0;  
  unsigned char char_array_4[4], char_array_3[3];  
  std::string ret;  
  
  while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_]))   
    {  
    char_array_4[i++] = encoded_string[in_]; in_++;  
    if (i ==4) {  
      for (i = 0; i <4; i++)  
        char_array_4[i] = base64_chars.find(char_array_4[i]);  
  
      char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);  
      char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);  
      char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];  
  
      for (i = 0; (i < 3); i++)  
        ret += char_array_3[i];  
      i = 0;  
    }  
  }  
  
  if (i)   
    {  
    for (j = i; j <4; j++)  
      char_array_4[j] = 0;  
  
    for (j = 0; j <4; j++)  
      char_array_4[j] = base64_chars.find(char_array_4[j]);  
  
    char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);  
    char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);  
    char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];  
  
    for (j = 0; (j < i - 1); j++)   
            ret += char_array_3[j];  
  }  
  
  return ret;  
}  


3、测试代码如下:

#include <cstdlib>  
#include <iostream>  
#include "CSendMail.h"  
  
using namespace std;  
  
#define USERNAME "xxxxxx@suhu.com"  
#define PASSWORD "xxxxxx"  
#define SMTPSERVER "smtp.suhu.com"  
#define SMTPPORT ":25"   
#define RECIPIENT "<xxxxxx@suhu.com>"  
#define MAILFROM "<xxxxxx@126.com>"  
  
int main(int argc, char** argv)  
{  
    
    CSendMail sendMail( USERNAME, PASSWORD, SMTPSERVER, 25 );      
   sendMail.SetSendName( MAILFROM );
   sendMail.SetSendMail( MAILFROM );
   sendMail.AddRecvMail( RECIPIENT );
   sendMail.SetSubject( "mail send test" );
   sendMail.SetBodyContent( "this is a test!" );
    return 0;   
}  

4、编译libcurl库

到libcurl官方网站上下载最新的libcurl代码zip包,具体编译过程参见:libcurl开源库在VS2010环境下编译配置详解_chenlycly的专栏-CSDN博客

参考:

 libcurl发送邮件C++类 :  http://blog.csdn.net/jaylong35/article/details/7210291
libcurl实现smtp发送支持附件:libcurl实现smtp发送支持附件_abqchina的博客-CSDN博客

### 回答1: libcurl是一个C语言,用于支持各种协议的数据传输。在编写代码时,需要先下载libcurl的开发,然后在代码中包含相应的头文件。接着,需要链接libcurl文件。 以下是在Linux操作系统下使用gcc编译调用libcurl的示例: 1. 安装libcurl开发 可以使用包管理器安装libcurl开发,例如在Ubuntu/Debian系统下执行以下命令: sudo apt-get install libcurl4-gnutls-dev 2. 编写代码 在代码中,需要包含curl/curl.h头文件,并调用libcurl提供的函数进行数据传输。例如: #include <curl/curl.h> int main(void) { CURL *curl; CURLcode res; curl = curl_easy_init(); if(curl) { curl_easy_setopt(curl, CURLOPT_URL, "http://www.example.com"); res = curl_easy_perform(curl); if(res != CURLE_OK) fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); curl_easy_cleanup(curl); } return 0; } 3. 编译代码 在命令行中执行以下命令进行编译: gcc -o myprogram mycode.c -lcurl 其中,myprogram为可执行文件名,mycode.c为代码文件名。参数-lcurl指定链接libcurl。 4. 运行程序 在命令行中执行以下命令运行程序: ./myprogram 以上就是使用gcc编译调用libcurl的基本步骤。在编程过程中,还需要注意设置相关选项以满足自己的需求。 ### 回答2: 在进行c编译调用libcurl前,我们需要先了解一些基础知识: 首先,libcurl是一个免费、开源的客户端 URL 传输,它支持众多协议,例如:FTP、FTPS、HTTP、HTTPS、SCP、SFTP、SMTP、POP3、IMAP 等。同时,libcurl提供了易于使用的API,方便程序员在自己的应用中调用。 其次,对于c语言编译调用libcurl,我们需要下面的步骤: 1. 下载并安装curl,使用命令行方式,如Ubuntu系统可以使用以下命令进行安装: sudo apt-get install libcurl4-openssl-dev 2. 下载或拷贝需要使用libcurl的源文件,也就是c语言文件。 3. 在c语言文件中,首先添加头文件 “curl/curl.h”,然后就可以通过调用API来使用libcurl了。 4. 具体的API函数使用方式可以查阅libcurl的官方文档进行学习和理解,例如使用curl_easy_setopt函数来设置curl句柄,然后使用curl_easy_perform函数来执行请求。 5. 可以通过编译运行来验证调用libcurl是否成功,错误信息可以看编译器的输出和libcurl的API返回值。 总之,使用c语言编译调用libcurl需要先安装curl,然后在c语言文件中添加头文件和API函数调用等内容。对于API函数的使用,需要经过学习和实践来掌握和理解。 ### 回答3: libcurl是一个用于在应用程序中进行HTTP、FTP、SMTP等通信的开源Clibcurl有许多功能,包括支持多种协议、支持不同的认证方式以及支持多线程操作等。 编译调用libcurl的步骤如下: 1. 下载libcurl并解压。 2. 打开命令行终端,转到解压后的libcurl目录下。 3. 输入命令“./configure”,进行配置,生成Makefile文件。 4. 输入命令“make”,进行编译。 5. 输入命令“make install”,将编译好的文件安装到系统目录下。 6. 在程序调用libcurl,需要包含相关的头文件,并链接对应的文件。 例如,使用gcc编译程序时,需要添加如下参数: gcc -o output main.c -lcurl 其中,main.c是程序源文件,-lcurl表示链接libcurl。 在程序调用libcurl时,需要使用相关的API函数。例如,使用curl_easy_init()函数初始化一个curl句柄,使用curl_easy_setopt()函数设置各种参数,使用curl_easy_perform()函数执行通信操作。 总的来说,编译调用libcurl并不复杂,只需要按照以上步骤进行操作即可。使用libcurl可以大大简化网络通信操作,提高程序的可移植性和可维护性。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dvlinker

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值