在.net中利用pop3收邮件

using System;
using System.Collections;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text;
using System.Diagnostics;

namespace PramodSingh.LibPOP3
{
?#region "Delegates"
?public delegate void POP3Error(string errorMsg);
?public delegate void POP3MsgRetrivalStatus(int MsgNumber,int MsgSize,int MsgRetsize);
?public delegate void POP3Complete();
?public delegate void POP3MsgComplete(int MsgNumber,string MsgHeader);
?#endregion
?
?public class POP3
?{
?
??#region "Private Class/Enum/Struct"
??// StateObject for Asynchronous Recieve event
??private class StateObject
??{
???// Client socket.
???public Socket workSocket = null;
???// Size of receive buffer.
???public const int BufferSize = 256;
???// Receive buffer.
???public byte[] buffer = new byte[BufferSize];
???// Received data string.
???public StringBuilder sb = new StringBuilder();
??}
??//POP3 commands
??private enum STATE
??{
???INIT=0,
???USER,
???PASS,
???STAT,
???LIST,
???RETR,
???ENDRETR,
???DELE,
???BYE
??}
??//email properties
??private struct MESSAGEPROP
??{
???public string text;
???public int msgSize;
???public int retrSize;
???public ArrayList attachments;
??}
??//Attachment with in the emails
??public struct Attachment
??{
???public string ContentType;
???public string name;
???public string ContentTransferEncoding;
???public string ContentDescription;
???public string ContentDisposition;
???public string filename;
???public byte[] binaryData;
??}
??#endregion

??#region "Private"
??//private variable declration
??private string lastMsg="", error="",user="", pass="",server="";
??private int numMsg=0, sizeMsg=0, retrMsg=0,delMsg=0;
??private ArrayList msgs=new ArrayList();
??private bool delAfterRead;
??private STATE state=STATE.INIT;
??private const int port = 110;
??private Socket client=null;
??private POP3Error errorDelegate=null;
??private POP3MsgRetrivalStatus msgStatusDelegate=null;
??private POP3Complete complete=null;
??private POP3MsgComplete msgcomplete=null;
??private bool usercancel=false;
??//asynchronous recieves event
??private static ManualResetEvent receiveDone =
???new ManualResetEvent(false);
??private StateObject stateObject = new StateObject();
??//reads line from index in src and appends it to dst
??private void ReadLine(int index,string src,ref string dst)
??{
???try
???{
????int where=src.IndexOf("/r",index+1);
????if(where>0)
????{
?????int length=where-index;
?????dst+=src.Substring(index,length);
????}
???}
???catch(Exception e)
???{
????error="ReadLine error:" + e.Message + "/r/n" + e.StackTrace;
????errorDelegate(error);
???}
??}
??
??//main function
??private void ParseMessage()
??{
???try
???{
????StringBuilder s=new StringBuilder();
??????
????if(lastMsg.StartsWith("-ERR")) //if there is an error
????{
?????error="Received -ERR from server :"+lastMsg;
?????Close(); //disconnect and stop
?????return;
????}
????char[] crlf={(char)13,(char)(10)};
????switch(state) //what should we do next?
????{
?????case STATE.INIT: //if we are already connected
??????msgs.Clear();
??????s.Append("USER ");
??????s.Append(user);
??????s.Append(crlf);
??????Send(client,s.ToString()); //send user id
??????state=STATE.USER;
??????break;
??
?????case STATE.USER:
??????s.Append("PASS ");
??????s.Append(pass);
??????s.Append(crlf);
??????Send(client,s.ToString()); //now send password
??????state=STATE.PASS;
??????break;
??
?????case STATE.PASS:
??????s.Append("STAT ");
??????s.Append(crlf);
??????Send(client,s.ToString()); //now send stat request
??????state=STATE.STAT;
??????break;
??
?????case STATE.STAT:
?????{
??????char[] seperator={(char)32};
??????string[] split= lastMsg.Split(seperator);
??????if (split.Length>1 )
??????{
???????numMsg=System.Convert.ToInt32( split[1].Trim().ToString());
???????sizeMsg=System.Convert.ToInt32( split[2].Trim().ToString());
??????}
?????
??????if(numMsg>0) //if there are any messages then send retr command
??????{
???????state=STATE.LIST;
???????s.Append("LIST 1" );
???????retrMsg++;
???????s.Append(crlf);
???????Send(client,s.ToString()); //send retr request
??????}
??????else //if not, disconnect
??????{
???????error="No new messages/r/n";
???????state=STATE.BYE;
??????}
?????}
??????break;
?????case STATE.LIST:
?????{
??????char[] seperator={(char)32};
??????string[] split= lastMsg.Split(seperator);
??????state=STATE.RETR;
??????s.Append("RETR "+retrMsg );
??????s.Append(crlf);
??????MESSAGEPROP prop=new MESSAGEPROP();
??????prop.retrSize=0;
??????if (split.Length>1 )
??????{
???????prop.msgSize =System.Convert.ToInt32( split[2].Trim().ToString());
??????}
??????prop.text="";
??????msgs.Add(prop);
??????Send(client,s.ToString());
?????}
??????break;
?????case STATE.RETR:
?????{
??????MESSAGEPROP tmp=(MESSAGEPROP) msgs[retrMsg-1];
??????if(lastMsg.StartsWith("+OK")) //if it's first data then skip +OK
??????{
???????lastMsg= lastMsg.Replace("+OK/r/n","");
??????}
??????tmp.text+=lastMsg; //append data to messagetext
??????tmp.retrSize+=lastMsg.Length; //add data size to retr size
??????msgStatusDelegate(retrMsg,tmp.msgSize,tmp.retrSize);
??????msgs[retrMsg-1]=tmp;
??????//if (lastMsg.EndsWith("/r/n./r/n")||(lastMsg.EndsWith("./r/n")))
??????//This is unreliable as message comes in chucks
??????//we would be able to get/r/n./r/n
??????//check the message size.
??????if(tmp.msgSize==(tmp.retrSize-3))
??????{?//check if there are any other messages
???????//Parse the message for Attachments
???????MessageBody(retrMsg);
???????msgcomplete(retrMsg,MessageHeader(retrMsg));
???????if(retrMsg ???????{
????????retrMsg++;
????????s.Append("LIST "+retrMsg);
????????s.Append(crlf);
????????Send(client,s.ToString());
????????state=STATE.LIST;
???????}
???????else
???????{
????????//we've got now all messages
????????if(delAfterRead && numMsg>0) //we want to delete them from server
????????{
?????????state=STATE.DELE;
?????????delMsg=1;
?????????s.Append("DELE "+delMsg+crlf);
?????????Send(client ,s.ToString());
????????
????????}
????????else //leave them and disconnect
????????{
?????????state=STATE.ENDRETR;
?????????error="Session ended/r/n";
?????????s.Append("QUIT " +crlf);
?????????Send(client,s.ToString());
????????}
???????}
??????}
?????}
??????break;

?????case STATE.DELE:
?????{
??????//delete another message
??????if(delMsg
??????{
???????delMsg++;
???????s.Append("DELE "+delMsg+crlf);
???????Send(client ,s.ToString());
?????
??????}
??????else //no more messages - disconnect
??????{
?????
???????state=STATE.BYE;
???????error="Deleted all messages/r/n";
???????s.Append("QUIT " +crlf);
???????Send(client,s.ToString());
??????
??????}
?????}
??????break;
?????case STATE.BYE: //default
?????default:
?????
??????break;
????}
????//call the ReceiveCallback method to get the data
????if((state!=STATE.ENDRETR)&&(state!=STATE.BYE))
????{
?????stateObject.sb=new StringBuilder();
?????//slow the pace for other app
?????Thread.Sleep(10);
?????client.BeginReceive(stateObject.buffer,0,StateObject.BufferSize,0,
??????new AsyncCallback(ReceiveCallback), stateObject);
????}
????else
????{
?????complete();
?????receiveDone.Set();
?????Close();
????}
???}
???catch(Exception e)
???{
????
????if (!usercancel)
????{
?????error="ParseMessage error:"+ "/r/n" + e.Message + "/r/n" + e.StackTrace;
????}
????else
????{
?????error ="Action canceled by user";
?????usercancel=false;
????}
????receiveDone.Set();
????Close();
????errorDelegate(error);
???}
??}
??
??//connect to server and retrieve messages
??private void Connect()
??{
???// Connect to a remote device.
???try
???{
????// Establish the remote endpoint for the socket.
????IPHostEntry ipHostInfo = Dns.Resolve(server);
????IPAddress ipAddress = ipHostInfo.AddressList[0];
????IPEndPoint remoteEP = new IPEndPoint(ipAddress, port);
????// Create the state object.
????

????// Create a TCP/IP socket.
????client = new Socket(AddressFamily.InterNetwork,
?????SocketType.Stream, ProtocolType.Tcp);

????// Connect to the remote endpoint.
????client.Connect( remoteEP);
????
????// Receive the response from the remote device.
????// Begin receiving the data from the remote device.
????state=STATE.INIT;
????
????stateObject.workSocket=client;
????client.BeginReceive( stateObject.buffer, 0, StateObject.BufferSize, 0,
?????new AsyncCallback(ReceiveCallback), stateObject);
????receiveDone.WaitOne();
??????
???}
???catch(Exception e)
???{
????char[] crlf={(char)13,(char)10};
????error="Connection error:"+ crlf + e.Message + crlf + e.StackTrace;
????errorDelegate(error);
???}
??}

??//asynchronous recieve callback
??private void ReceiveCallback( IAsyncResult ar )
??{
???try
???{
????// Retrieve the state object and the client socket
????// from the asynchronous state object.
????StateObject stateObj = (StateObject) ar.AsyncState;
????Socket client = stateObj.workSocket;
????
????// Read data from the remote device.
????int bytesRead = client.EndReceive(ar);

????if (bytesRead > 0)
????{
?????// There might be more data, so store the data received so far.
?????stateObj.sb.Append(Encoding.ASCII.GetString(stateObj.buffer,0,bytesRead));
?????lastMsg=stateObj.sb.ToString();
?????ParseMessage();
????}
???}
???catch (Exception e)
???{
????error="RecieveCallback error" + e.ToString();
????errorDelegate(error);
???}
??}
??//send the data to server
??private void Send(Socket client, String data)
??{
???try
???{
????// Convert the string data to byte data using ASCII encoding.
????byte[] byteData = Encoding.ASCII.GetBytes(data);
????// Begin sending the data to the remote device.
????client.Send(byteData);
???}
???catch(Exception e)
???{
????char[] crlf={(char)13,(char)10};
????error="Send error:"+ crlf + e.Message + crlf + e.StackTrace;
????errorDelegate(error);
???}
??}
??
??#endregion

??#region "Public"
??
??//register the Error delegates
??public POP3Error ErrorHandler
??{
???set
???{
????errorDelegate=value;
???}
??}

??//register the Status delegates
??public POP3MsgRetrivalStatus MsgStatusHandler
??{
???set
???{
????msgStatusDelegate=value;
???}
??}
??//complete handler
??public POP3Complete CompleteHandler
??{
???set
???{
????complete=value;
???}
??}
??//message complete handler
??public POP3MsgComplete MessageCompeleteHandler
??{
???set
???{
????msgcomplete=value;
???}
??}
??//save msg to a file
??public void SaveMessageToFile(int MsgNumber,string FileName)
??{
???try
???{
????MESSAGEPROP prop=(MESSAGEPROP) msgs[MsgNumber-1];
????byte[] byteData = Encoding.ASCII.GetBytes(prop.text);
????FileStream fs=new FileStream(FileName,FileMode.Create);
????fs.Write(byteData,0,byteData.Length);
????fs.Close();
???}
???catch(Exception e)
???{
????error="SaveMessageToFile error:"+ "/r/n" + e.Message + "/r/n" + e.StackTrace;
????errorDelegate(error);
???}

??}
??//gets the most important message fields: From, To, Date, Subject, Body
??//from entire message
??public string MessageHeader(int MsgNumber)
??{
???try
???{
????string ret="";
????MESSAGEPROP prop=(MESSAGEPROP) msgs[MsgNumber-1];
????int where=prop.text.IndexOf("From:");
????ReadLine(where,prop.text,ref ret);
????ret+="/r/n";
????where=prop.text.IndexOf("To:");
????ReadLine(where,prop.text,ref ret);
????ret+="/r/n";
????where=prop.text.IndexOf("Date:");
????ReadLine(where,prop.text,ref ret);?
????ret+="/r/n";
????ret+=MessageSubject(MsgNumber);
????ret+="/r/n";
????return ret;
???}
???catch(Exception e)
???{
????error="MessageHeader error:"+ "/r/n" + e.Message + "/r/n" + e.StackTrace;
????errorDelegate(error);
????return "error in getting header,see error message for details";
???}
???
??}
??//gets only message subject
??public string MessageSubject(int MsgNumber)
??{
???try
???{
????MESSAGEPROP prop=(MESSAGEPROP)msgs[MsgNumber-1];
????int where=prop.text.IndexOf("Subject:");
????string ret="";
????ReadLine(where,prop.text,ref ret);
????return ret;
???}
???catch(Exception e)
???{
????error="MessageSubject error:"+ "/r/n" + e.Message + "/r/n" + e.StackTrace;
????errorDelegate(error);
????return "error in getting subject,see error message for details";
???}
??}
??//gets only message body
??public string MessageBody(int MsgNumber)
??{
???try
???{
????string ret="";
????MESSAGEPROP prop=(MESSAGEPROP)msgs[MsgNumber-1];
????ParseAttachment(MsgNumber,ref ret);
????if (ret=="")
????{
?????int where=prop.text.IndexOf("/r/n/r/n");
?????if(where!=-1)
??????where+=4;
?????else where=0;
?????int indexStart=where;
?????int retLength=prop.text.Length-where-3;
?????ret =prop.text.Substring(indexStart,retLength);
????}
????return ret;
???}
???catch(Exception e)
???{
????error="MessageBody error:"+ "/r/n" + e.Message + "/r/n" + e.StackTrace;
????errorDelegate(error);
????return "error in getting body,see error message for details";
???}
??}
?
??//get Attachment count
??public int AttachmentCount(int MsgNumber)
??{
???try
???{
????MESSAGEPROP prop=(MESSAGEPROP)msgs[MsgNumber-1];
????return? prop.attachments.Count;
???}
???catch(Exception e)
???{
????error="MessageBody error:"+ "/r/n" + e.Message + "/r/n"+ e.StackTrace;
????errorDelegate(error);
????return 0;
???}
??}
??//get the attachments
??public POP3.Attachment[] Attachments(int MsgNumber)
??{
???try
???{
????string ret="";
????ParseAttachment(MsgNumber,ref ret);
????MESSAGEPROP prop=(MESSAGEPROP) msgs[MsgNumber-1];
????Array myTargetArray=Array.CreateInstance( typeof(Attachment), prop.attachments.Count );
????prop.attachments.CopyTo(myTargetArray);
????return (Attachment[]) myTargetArray;
???}
???catch(Exception e)
???{
????
????error="Attachments error:"+ "/r/n" + e.Message + "/r/n"+ e.StackTrace;
????errorDelegate(error);
????return null;
???}
??}
??//save attachments to file
??public void SaveAttachments(int MsgNumber,string dirPath)
??{
???try
???{
????Attachment[] attachs=Attachments(MsgNumber);
????foreach(Attachment attach in attachs)
????{
?????if(!Directory.Exists(dirPath))
?????{
??????Directory.CreateDirectory(dirPath);
?????}
?????string path=dirPath+ "//" + attach.filename;
?????FileStream fs =new FileStream(path,FileMode.Create);
?????fs.Write(attach.binaryData,0,attach.binaryData.Length);
?????fs.Close();
????}
???}
???catch(Exception e)
???{
????error="SaveAttachment error:"+ "/r/n" + e.Message + "/r/n" + e.StackTrace;
????errorDelegate(error);
???}
??}
??//gets entire message with out parsing
??public string Message(int MsgNumber)
??{
???try
???{
????MESSAGEPROP prop=(MESSAGEPROP)msgs[MsgNumber-1];
????return prop.text;
???}
???catch(Exception e)
???{
????
????error="Message error:"+ "/r/n" + e.Message + "/r/n" + e.StackTrace;
????errorDelegate(error);
????return "error in getting message,see error message for details";
???}
??}

??//cancel the request and reset
??public void Cancel()
??{
???usercancel=true;
???receiveDone.Set();
???Close();
??}
??//Open the connection with the POP3 server
??public void Open()
??{
???if(user!="" && pass!=""&& server!="")
???{
????Thread tClient= new Thread(new ThreadStart(Connect));
????tClient.Start();
????//tClient.Join();
???}
???else
???{
????
????error="Missing information for user,password and server";
????errorDelegate(error);
???}

??}

??//closes and quits from pop3 server
??public void Close()
??{
???try
???{
????receiveDone.Set();
????client.Close();
???}
???catch(Exception e)
???{
????if(!usercancel)
????{
?????error=e.Message;
????}
????else
????{
?????error="Action canceled by user";
????}
????errorDelegate(error);
???}
??}
??//sets username and password
??public string UserName
??{
???get
???{
????return user;
???}
???set
???{
????user=value;
???}
??}
??public string Password
??{
???get
???{
????return pass;
???}
???set
???{
????pass=value;
???}
??}
??public string Server
??{
???get
???{
????return server;
???}
???set
???{
????server=value;
???}
??}
??//want to delete messages after receiving?
??public bool DeleteAfterRead
??{
???get
???{
????return delAfterRead;
???}
???set
???{
????delAfterRead=value;
???}
??}
??
??//gets number of msgs that are on server
??public int NumOfMessages
??{
???get
???{
????return numMsg;
???}
??}
??//reset the counter
??public void Reset()
??{
???retrMsg=0;
???numMsg=0;
???msgs.Clear();
??}
??//individual message size
??public int SizeOfmessage(int MsgNumber)
??{
???try
???{
????MESSAGEPROP prop=(MESSAGEPROP) msgs[MsgNumber-1];
????return prop.msgSize;
???}
???catch(Exception e)
???{
????char[] crlf={(char)13,(char)10};
????error="Message error:"+ crlf + e.Message + crlf + e.StackTrace;
????errorDelegate(error);
????return 0;
???}
??}
??//gest size of all msgs
??public int SizeOfMessages
??{
???get
???{
????return sizeMsg;
???}
??}
??//gets number of received from server msgs
??public int NumOfMessagesRetieved
??{
???get
???{
????return retrMsg;
???}
??}
??//gets string error
??public string Error
??{
???get
???{
????return error;
???}
??}
??//gets last message recievied from server
??public string LastServerMessage
??{
???get
???{
????return lastMsg;
???}
??}
??
??#endregion

??#region "Constructor"??
??public POP3()
??{
???user="";
???pass="";
???server="";
???error="Not connected to server";
???delAfterRead=false;
???
??}
??public POP3(string UserName,string Password,string Server)
??{
???user=UserName;
???pass=Password;
???server=Server;
???delAfterRead=false;
??}
??public POP3(string UserName,string Password,string Server,bool DelAfterRead)
??{
???user=UserName;
???pass=Password;
???server=Server;
???delAfterRead=DelAfterRead;
??}
??
??#endregion
??
??#region "Attachment"
??//this has to be private
??//made public for testing
??private void ParseAttachment(int MsgNumber,ref string msgBody)
??{
???try
???{
????string boundary="";
???
????MESSAGEPROP prop=(MESSAGEPROP) msgs[MsgNumber-1];
????prop.attachments=new ArrayList();
????msgs[MsgNumber-1]=prop;
????int where=prop.text.IndexOf("Content-Type:");
????if (where>0)
????{
?????where=prop.text.IndexOf("boundary=");
?????if (where>1)
?????{
??????ReadLine(where,prop.text,ref boundary);
??????boundary=boundary.Replace("boundary=/"","");
??????boundary=boundary.Replace("/"","");
??????char[] crlf={(char)13,(char)10};
??????StringBuilder sb=new StringBuilder("--");
??????sb.Append(boundary);
??????sb.Append(crlf);
??????boundary=sb.ToString();
??????int startIndex=prop.text.IndexOf(boundary);
??????int endIndex=0;
??????string ret="";
??????bool bAttacEnd=false;
??????while ((endIndex=prop.text.IndexOf(boundary,startIndex+boundary.Length))>startIndex)
??????{
???????startIndex+=boundary.Length;
???????int tempIndex=endIndex;
???????endIndex-=(startIndex);
???????ret =prop.text.Substring(startIndex,endIndex);
???????//Need to Parse the data for the Attachments
???????//is attachment or data?
???????if (IsAttachment(ret))
???????{
????????ReadAttachment(ret,MsgNumber);
????????bAttacEnd=true;
???????}
???????else
???????{
????????msgBody=ret;
???????}
???????startIndex=tempIndex;
??????}
??????if(bAttacEnd)
??????{
???????sb.Append("--");
???????boundary=boundary.Replace((char)13,(char)32);
???????boundary=boundary.Replace((char)10,(char)32);
???????boundary=boundary.Trim().ToString();
???????boundary+="--";
???????startIndex+=boundary.Length;
???????endIndex=prop.text.IndexOf(boundary,startIndex+boundary.Length);
???????endIndex-=(startIndex);
???????ret =prop.text.Substring(startIndex,endIndex);
???????ReadAttachment(ret,MsgNumber);
??????}
?????}
????}
????else
????{
?????msgBody="";
????}
???}
???catch(Exception e)
???{
????error="ParseAttachment error:"+ "/r/n" + e.Message + "/r/n" + e.StackTrace;
????errorDelegate(error);
???}
??}
??
??//check if this is attachment or not
??private bool IsAttachment(string src)
??{
???int where=src.IndexOf("Content-Disposition:");
???if (where>0)
???{
????return true;
???}
???else
???{
????return false;
???}
??}

??//Get all Attachment from email
??private void ReadAttachment(string src,int MsgNumber)
??{???
???try
???{
????string ret="";
????string lastLine="";
????MESSAGEPROP prop=(MESSAGEPROP) msgs[MsgNumber-1];
????Attachment at= new Attachment();
???
????int where=src.IndexOf("Content-Type:");
????ret="";
????if (where>=0)
????{
?????ReadLine(where,src,ref ret);
?????at.ContentType=ret.Replace("Content-Type:","").Trim().ToString();
????}
????ret="";
????where=src.IndexOf("name");
????if(where>=0)
????{
?????ReadLine(where,src,ref ret);
?????at.name=ret.Replace("name","").Trim().ToString();
?????at.name=at.name.Replace("=","").Trim().ToString();
?????at.name=at.name.Replace("/"","").Trim().ToString();
????}
???
????ret="";
????where=src.IndexOf("Content-Transfer-Encoding:");
????if(where>=0)
????{
?????ReadLine(where,src,ref ret);?
?????at.ContentTransferEncoding=ret.Replace("Content-Transfer-Encoding:","").Trim().ToString();
????}
???
????ret="";
????where=src.IndexOf("Content-Description:");
????if(where>=0)
????{
?????ReadLine(where,src,ref ret);?
?????at.ContentDescription=ret.Replace("Content-Description:","").Trim().ToString();
????}

????ret="";
????where=src.IndexOf("Content-Disposition:");
????if(where>=0)
????{
?????ReadLine(where,src,ref ret);?
?????at.ContentDisposition=ret.Replace("Content-Disposition:","").Trim().ToString();
????}
????ret="";
????where=src.IndexOf("filename");
????if(where>=0)
????{
?????ReadLine(where,src,ref ret);?
?????lastLine=ret;
?????at.filename=ret.Replace("filename","").Trim().ToString();
?????at.filename=at.filename.Replace("=","").Trim().ToString();
?????at.filename=at.filename.Replace("/"","").Trim().ToString();
????}

????at.binaryData=AttachmentData(src,lastLine);
????prop.attachments.Add(at);
???}
???catch(Exception e)
???{
????error="ReadAttachment error:"+ "/r/n" + e.Message + "/r/n" + e.StackTrace;
????errorDelegate(error);
???}
??}

??private byte[] AttachmentData(string src,string lastLine)
??{
???try
???{
????string base64string="";
????int where=src.IndexOf(lastLine);
????char[] crlf={(char)13,(char)10};
????if(where>0)
????{
?????base64string=src.Substring(where+lastLine.Length);
?????base64string=base64string.Replace((char)13,(char)32);
?????base64string=base64string.Replace((char)10,(char)32);
?????base64string=base64string.Trim().ToString();
?????return Convert.FromBase64String(base64string);
????}
????return null;
???}
???catch(Exception e)
???{
????char[] crlf={(char)13,(char)10};
????error="AttachmentData error:"+ "/r/n" + e.Message + "/r/n" + e.StackTrace;
????errorDelegate(error);
????return null;
???}
???
??}
??#endregion
??
?}
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值