当前位置: 首页 > news >正文

golang通过飞书邮件服务API发送邮件功能详解

一.需求

需要实现通过飞书邮件服务API发送邮件验证码功能:用户输入邮箱, 点击发送邮件,然后发送邮件验证码, 这里验证码有过期时间, 保存到redis缓存中

二.实现

实现的部分代码如下:

控制器部分代码

// 发送邮件控制器
func EmailSendController(userId uint64, m proto.Message, ctx *gin.Context) (proto.Message, error) {
	var err error
	res := CommonRes(ctx)

	// 获取请求,将请求转换为具体的类型
	req, ok := m.(*common.SendEmailCodeReq)
	if !ok {
		res.ErrCode = uint64(CodeInvalidRequestType)
		res.ErrDesc = CodeMap[CodeInvalidRequestType]
		return res, err
	}
	if req.Email == "" {
		res.ErrCode = uint64(CodeReqDataNotEmpty)
		res.ErrDesc = CodeMap[CodeReqDataNotEmpty]
		return res, err
	}
    
    // 实例化飞书结构体
	emailSender, err := mailer.NewFeishuEmailSender()
	if err != nil {
		return res, err
	}
    // 发送邮件
	err = emailSender.SendEmail(req.Email)
	if err != nil {
		return res, err
	}

	return res, nil
}

 mailer包下feishuEmail.go部分代码

package mailer

import (
	"bytes"
	"encoding/json"
	"fmt"
	"redisCore"
	"net/http"
)

var redisClient redisCore.RedisCore

const (
	appID     = "your_app_id"     // 替换为您的飞书应用 ID
	appSecret = "your_app_secret" // 替换为您的飞书应用 Secret
)

// EmailSender 用于发送邮件的结构体
type EmailSender struct {
	AccessToken string
}

// NewEmailSender 创建一个新的 EmailSender 实例
func NewFeishuEmailSender() (*EmailSender, error) {
	token, err := getAccessToken()
	if err != nil {
		return nil, NewError(uint64(CodeSendMsgAccessTokenCheckError), fmt.Errorf("%s: %s", CodeMap[CodeSendMsgAccessTokenCheckError], err.Error()))
	}
	return &EmailSender{AccessToken: token}, nil
}

// getAccessToken 获取飞书 API 访问令牌
func getAccessToken() (string, error) {
	url := "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/"
	payload := map[string]string{
		"app_id":     appID,
		"app_secret": appSecret,
	}

	data, err := json.Marshal(payload)
	if err != nil {
		return "", err
	}

	resp, err := http.Post(url, "application/json", bytes.NewBuffer(data))
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("获取访问令牌失败: %s", resp.Status)
	}

	var result map[string]interface{}
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return "", err
	}

	accessToken, ok := result["tenant_access_token"].(string)
	if !ok {
		return "", fmt.Errorf("未找到访问令牌")
	}

	return accessToken, nil
}

// SendEmail 发送邮件
func (es *EmailSender) SendEmail(email string) error {
	code := GetRandomNum()  // 验证码
	url := "https://open.feishu.cn/open-apis/email/v1/send/"
	emailData := map[string]interface{}{
		"email":      email,
		"subject":    "您的验证码",
		"content":    fmt.Sprintf("您的验证码是: %s", code),
		"from_email": "system@test.com", // 替换为您的发件邮箱
	}

	data, err := json.Marshal(emailData)
	if err != nil {
		return NewError(uint64(CodeSendMsgCheckError), fmt.Errorf("%s: json转换错误: %s", CodeMap[CodeSendMsgCheckError], err.Error()))
	}

	req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(data))
	if err != nil {
		return NewError(uint64(CodeSendMsgCheckError), fmt.Errorf("%s: 发送请求错误: %s", CodeMap[CodeSendMsgCheckError], err.Error()))
	}

	req.Header.Set("Authorization", "Bearer "+es.AccessToken)
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return NewError(uint64(CodeSendMsgCheckError), fmt.Errorf("%s: 发送响应错误: %s", CodeMap[CodeSendMsgCheckError], err.Error()))
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return NewError(uint64(CodeSendMsgCheckError), fmt.Errorf("%s: 发送邮件失败: %s", CodeMap[CodeSendMsgCheckError], resp.Status))
	}

	// 将验证码保存到 Redis,有效期 5 分钟
	err = redisClient.SaveEmailCode(email, code)
	if err != nil {
		return NewError(uint64(CodeSendMsgCheckError), fmt.Errorf("%s: 保存验证码到redis错误: %s", CodeMap[CodeSendMsgCheckError], err.Error()))
	}

	return nil
}

redis部分代码


type RedisCore struct {
}

var RedisClient *redis.Client

// 保存数据到redis
func SetEx(key string, value interface{}, expiration time.Duration) error {
	err := RedisClient.SetEx(ctxRedis, key, value, expiration).Err()
	if err != nil {
		return err
	}
	return nil
}

// 保存邮件验证码
func (RedisCore) SaveEmailCode(email, code string) error {
	// 获取邮件验证码key
	sendEmailTokenKey := pkg.SEND_EMAIL_KEY_PREFIX + ":" + email

	err := SetEx(sendEmailTokenKey, code, 60*time.Second)
	if err != nil {
		return err
	}
	return nil
}

其他公共方法代码

// 生成随机数
func GetRandomNum() string {
	var str string
	for i := 0; i < 4; i++ {
		current := rand.Intn(10)
		str += strconv.Itoa(current)
	}
	return str
}

// 自定义错误
func NewError(errCode uint64, err error) error {
	return New(int32(errCode), err.Error())
}

func New(code int32, reason string) error {
	return &MyError{Code: code, Reason: reason}
}

type MyError struct {
	Code   int32
	Reason string 
}

好了, 发送邮件操作完成了,上面还有可优化的空间: 比如: 把飞书相关的配置以及邮件模块等放到yml文件中, 通过从yml获取配置信息

相关文章:

  • echart实现动态折线图(vue3+ts)
  • react的redux总结
  • telophoto源码查看记录
  • Nextjs15 实战 - React Notes CURD 实现
  • Dockerfile中CMD命令未生效
  • MyBatis的第四天学习笔记下
  • 动态规划算法深度解析:0-1背包问题(含完整流程)
  • 【Mysql】主从复制和读写分离
  • linux 处理2个文件的差集
  • 运动规划实战案例 | 基于四叉树分解的路径规划(附ROS C++/Python仿真)
  • 7-8 超速判断
  • micro常用快捷键
  • 编译和链接(C语言)
  • 命令行工具-cmd和powershell
  • 聚类Clustering和分类Classification的区别
  • 23种设计模式-行为型模式-策略
  • ABAP,PDF,ADS,FORM,PRINT
  • Linux进程概念及理解
  • [创业之路-362]:用确定性的团队、组织、产品开发流程和方法,应对客户、市场、竞争和商业模式的不确定性。
  • CAS与sychronized优化
  • 第78届戛纳电影节开幕,罗伯特·德尼罗领取终身成就奖
  • 山西临汾哪吒主题景区回应雕塑被指抄袭:造型由第三方公司设计
  • 福建厦门市副市长、市公安局局长陈育煌出任吉林省公安厅厅长
  • 山东枣庄同一站点两名饿了么骑手先后猝死,当地热线:职能部门正调查
  • 75万采购防火墙实为299元路由器?重庆三峡学院发布终止公告:出现违法违规行为
  • 重庆大学通报本科生发14篇SCI论文:涉事学生及其父亲被处理