当前位置: 首页 > 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获取配置信息


文章出自:
http://www.hotlads.com/news/adSfJBEE.html
http://www.hotlads.com/news/kRx3tCPW.html
http://www.hotlads.com/news/5EkamufY.html
http://www.dtcms.com/a/118735.html

相关文章:

  • 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优化
  • 10. 工具(Tools)集成:连接API、数据库与外部服务的桥梁
  • 8.方法引用综合小练习2-获取部分属性并收集到数组
  • 解读typescript中class类
  • Springboot JPA ShardingSphere 根据年分表java详细代码Demo
  • Java Stream API:现代化集合处理的艺术
  • AI比人脑更强,因为被植入思维模型【49】冰山理论思维模型
  • 鱼骨图分析法实战:5步定位系统故障
  • Linux系统学习Day2——在Linux系统中开发OpenCV
  • 【微机及接口技术】- 第九章 串行通信与串行接口(上)
  • 路由表的最终地址 root 路由跟踪,最终到哪里去