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": "[email protected]", // 替换为您的发件邮箱
	}

	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获取配置信息

你可能感兴趣的:(#,golang基础,golang,服务器,飞书,飞书邮件服务发送邮件,golang,open.feishu.cn)