163邮箱邮件发送
链接:https://pan.wo.cn/s/1J1c4w90901 提取码:1Spc
在module.json5配置文件加上对权限的声明:
"requestPermissions" : [ { "name" : "ohos.permission.INTERNET" } ]
import socket from '@ohos.net.socket' ; import util from '@ohos.util' ; interface LocalAddress { address : string; family : number; } interface ServerAddress { address : string; port : number; family : number; } interface MessageValue { message : ArrayBuffer ; } interface SecureOptions { protocols : socket.Protocol []; cipherSuite : string; } interface ConnectOptions { address : ServerAddress ; secureOptions : SecureOptions ; } let tlsSocket : socket.TLSSocket | null = null ;@Entry @Component struct Index { bindLocal : boolean = false isServerResponse : boolean = false @State serverAddr : string = "smtp.163.com" @State serverPort : number = 465 @State userName : string = "staseaiot@163.com" @State passwd : string = "DAtTEFxcvXt76ByV" @State rcptList : string = "1970884166@qq.com" @State mailTitle : string = "意见反馈" @State mailFrom : string = "staseaiot@163.com" @State mailContent : string = "This is greeting from Harmony OS" @State canSend : boolean = false build ( ) { Row () { Column () { Text ("邮件发送客户端(163邮箱 - 465 TLS)" ) .fontSize (14 ) .fontWeight (FontWeight .Bold ) .width ('100%' ) .textAlign (TextAlign .Center ) .padding (10 ) Flex ({ justifyContent : FlexAlign .Start , alignItems : ItemAlign .Center }) { Text ("邮箱服务器地址:" ) .width (120 ) .fontSize (14 ) .flexGrow (0 ) TextInput ({ text : this .serverAddr }) .onChange ((value ) => { this .serverAddr = value }) .width (110 ) .fontSize (12 ) .flexGrow (1 ) }.width ('100%' ).padding (5 ) Flex ({ justifyContent : FlexAlign .Start , alignItems : ItemAlign .Center }) { Text ("邮箱服务器端口:" ) .width (120 ) .fontSize (14 ) .flexGrow (0 ) TextInput ({ text : this .serverPort .toString () }) .type (InputType .Number ) .onChange ((value ) => { this .serverPort = parseInt (value) }) .width (110 ) .fontSize (12 ) .flexGrow (1 ) }.width ('100%' ).padding (5 ) Flex ({ justifyContent : FlexAlign .Start , alignItems : ItemAlign .Center }) { Text ("邮箱用户名:" ) .width (90 ) .fontSize (14 ) .flexGrow (0 ) TextInput ({ text : this .userName }) .onChange ((value ) => { this .userName = value }) .width (110 ) .fontSize (12 ) .flexGrow (1 ) }.width ('100%' ).padding (5 ) Flex ({ justifyContent : FlexAlign .Start , alignItems : ItemAlign .Center }) { Text ("登录密码(授权码):" ) .width (130 ) .fontSize (14 ) .flexGrow (0 ) TextInput ({ text : this .passwd }) .onChange ((value ) => { this .passwd = value }) .width (100 ) .fontSize (12 ) .flexGrow (1 ) Button ("登录" ) .onClick (() => { this .login () }) .width (70 ) .fontSize (14 ) .flexGrow (0 ) }.width ('100%' ).padding (5 ) Flex ({ justifyContent : FlexAlign .Start , alignItems : ItemAlign .Center }) { Text ("收件人邮箱:" ) .width (90 ) .fontSize (14 ) .flexGrow (0 ) TextInput ({ placeholder : "多个使用逗号分隔" , text : this .rcptList }) .onChange ((value ) => { this .rcptList = value }) .width (110 ) .fontSize (12 ) .flexGrow (1 ) }.width ('100%' ).padding (5 ) Flex ({ justifyContent : FlexAlign .Start , alignItems : ItemAlign .Center }) { Text ("标题:" ) .width (50 ) .fontSize (14 ) .flexGrow (0 ) TextInput ({ text : this .mailTitle }) .onChange ((value ) => { this .mailTitle = value }) .width (110 ) .fontSize (12 ) .flexGrow (1 ) }.width ('100%' ).padding (5 ) Flex ({ justifyContent : FlexAlign .Start , alignItems : ItemAlign .Center }) { Text ("发件人邮箱:" ) .width (90 ) .fontSize (14 ) .flexGrow (0 ) TextInput ({ text : this .mailFrom }) .onChange ((value ) => { this .mailFrom = value }) .width (110 ) .fontSize (12 ) .flexGrow (1 ) }.width ('100%' ).padding (5 ) Flex ({ justifyContent : FlexAlign .Start , direction : FlexDirection .Column , alignItems : ItemAlign .Center }) { Text ("邮件内容:" ) .width ('100%' ) .fontSize (14 ) TextArea ({ text : this .mailContent }) .onChange ((value ) => { this .mailContent = value }) .width ('100%' ) .height (120 ) .fontSize (12 ) Button ("发送" ) .enabled (this .canSend ) .onClick (() => { this .sendMail () }) .width (80 ) .fontSize (14 ) } .flexGrow (1 ) .width ('100%' ) .padding (5 ) .height ('100%' ) } .width ('100%' ) .justifyContent (FlexAlign .Start ) .height ('100%' ) .padding (10 ) } .height ('100%' ) } createNewSocket ( ) { if (tlsSocket) { try { tlsSocket.close (); } catch (e) {} } tlsSocket = socket.constructTLSSocketInstance (); this .bindLocal = false ; } async bindSocket ( ) { this .createNewSocket (); try { let localAddress : LocalAddress = { address : "0.0.0.0" , family : 1 }; await tlsSocket!.bind (localAddress); console .log ("C: bind success" ); this .bindLocal = true ; } catch (e) { console .error (`C: bind fail - ${e.message || e} ` ); } tlsSocket!.on ("message" , (value: MessageValue ) => { this .isServerResponse = true ; let msg = buf2String (value.message ); console .log (`S: ${msg} ` ); }); tlsSocket!.on ("error" , (err ) => { console .error (`E: Socket 错误 - ${err.message || err.code || '未知 TLS 错误' } ` ); console .error ("详细错误对象:" , err); this .cleanupSocket (); }); tlsSocket!.on ("close" , () => { console .log ("C: 连接已被服务器断开(可能是空闲超时)" ); console .log ("C: 发送按钮已禁用,请重新点击'登录'或'发送'来重连" ); this .canSend = false ; this .cleanupSocket (); }); } cleanupSocket ( ) { if (tlsSocket) { try { tlsSocket.close (); } catch (e) {} tlsSocket = null ; } this .bindLocal = false ; this .isServerResponse = false ; } async login ( ) { try { this .cleanupSocket (); await this .bindSocket (); let serverAddress : ServerAddress = { address : this .serverAddr , port : this .serverPort , family : 1 }; const secureOptions : SecureOptions = { protocols : [socket.Protocol .TLSv12 , socket.Protocol .TLSv13 ], cipherSuite : 'TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256' }; const connectOptions : ConnectOptions = { address : serverAddress, secureOptions : secureOptions }; console .log (`C: 正在连接 ${this .serverAddr} :${this .serverPort} (TLS)...` ); await tlsSocket!.connect (connectOptions); console .log ("C: TLS 连接成功!" ); await this .wait4ServerResponse (10000 ); if (!this .isServerResponse ) { throw new Error ("未收到 220 欢迎消息" ); } await this .exeCmdAndWait4Response ("EHLO harmonyos.next" ); await this .exeCmdAndWait4Response ("AUTH LOGIN" ); let loginName = string2Base64 (this .userName ); await this .exeCmdAndWait4Response (loginName); let passWd = string2Base64 (this .passwd ); await this .exeCmdAndWait4Response (passWd); if (this .isServerResponse ) { this .canSend = true ; console .log ("C: 登录成功!(235 Authentication successful)" ); console .log ("C: 可以开始发送邮件" ); } else { throw new Error ("登录失败,请检查邮箱/授权码" ); } } catch (e) { console .error (`C: 登录失败 - ${e.message || e} ` ); this .cleanupSocket (); } } async sendMail ( ) { if (!this .canSend || !tlsSocket) { console .log ("C: 连接不可用,正在自动尝试重连..." ); await this .login (); if (!this .canSend ) return ; } try { console .log ("C: 开始发送邮件..." ); await this .exeCmdAndWait4Response (`MAIL FROM:<${this .mailFrom} >` ); let rcptMails = this .rcptList .split (',' ); for (let rcpt of rcptMails) { rcpt = rcpt.trim (); if (rcpt) await this .exeCmdAndWait4Response (`RCPT TO:<${rcpt} >` ); } await this .exeCmdAndWait4Response ("DATA" ); let mailBody = `From: ${this .mailFrom} \r\n` ; mailBody += `To: ${this .rcptList} \r\n` ; mailBody += `Subject: =?utf-8?B?${string2Base64(this .mailTitle)} ?=\r\n` ; mailBody += `Date: ${new Date ().toUTCString()} \r\n` ; mailBody += `Content-Type: text/plain; charset="utf-8"\r\n` ; mailBody += `Content-Transfer-Encoding: base64\r\n\r\n` ; mailBody += `${string2Base64(this .mailContent)} \r\n` ; mailBody += ".\r\n" ; const chunkSize = 2048 ; for (let i = 0 ; i < mailBody.length ; i += chunkSize) { const chunk = mailBody.substring (i, i + chunkSize); await tlsSocket!.send (chunk); console .log (`C: [chunk sent, length=${chunk.length} ]` ); } await this .wait4ServerResponse (15000 ); if (!this .isServerResponse ) { throw new Error ("服务器未确认邮件接收" ); } console .log ("C: 邮件发送成功!连接保持打开,可继续发送下一封" ); } catch (e) { console .error (`C: 发送失败 - ${e.message || e} ` ); this .cleanupSocket (); } } async exeCmdAndWait4Response (cmd: string ) { if (!tlsSocket) { throw new Error ("Socket 未初始化" ); } this .isServerResponse = false ; let fullCmd = cmd + "\r\n" ; try { await tlsSocket.send (fullCmd); console .log (`C: ${cmd} ` ); } catch (sendErr) { console .error (`C: 发送命令失败 - ${sendErr.message || sendErr} ` ); throw new Error (sendErr.message || sendErr); } await this .wait4ServerResponse (10000 ); } async wait4ServerResponse (timeoutMs: number = 10000 ) { const start = Date .now (); while (!this .isServerResponse ) { if (Date .now () - start > timeoutMs) { throw new Error (`等待响应超时 (${timeoutMs} ms)` ); } await sleep (100 ); } } } function string2Base64 (src: string ): string { try { let textEncoder = new util.TextEncoder (); let encodeValue = textEncoder.encodeInto (src); let tool = new util.Base64Helper (); return tool.encodeToStringSync (encodeValue); } catch (e) { console .error ("Base64 失败:" , e); return "" ; } } function buf2String (buf: ArrayBuffer ): string { try { let msgArray = new Uint8Array (buf); let textDecoder = util.TextDecoder .create ("utf-8" ); return textDecoder.decodeWithStream (msgArray); } catch (e) { console .error ("解码失败:" , e); return "[解码失败]" ; } } function sleep (time: number ) { return new Promise <void >((resolve ) => setTimeout (resolve, time)); }