基于go-micro微服务的实战-注册成功推送Rabbitmq队列,邮件服务异步发送邮件(七)
文章最后附带完整代码
这一节是在用户注册基础上,注册成果则推送用户信息到Rabbitmq,异步处理,邮件服务订阅并发送注册邮件。
go-micro v3也可以通过定制Broker,使用Rabbitmq发布和订阅,但使用过程遇到点问题,在github上也提了issues。 当然也可以自己用原生rabbitmq库或者其它封装的库,这里使用的是开源的rabbitmq连接池库gitee.com/tym_hmm/rabbitmq-pool-go",整体调用简单,也支持死信队列,重试等。
至于Rabbitmq的部署,这里就不详细讲解。可以网上搜索。
设计流程是这样
- 用户服务启用Rabbitmq的发布池
- 新增邮件服务,同时启用Rabbitmq的消费池,消费者订阅等待消费
- 用户注册成功后,用户服务发布消息到MQ队列
- 邮件服务的消费者订阅MQ队列,消费并发送注册邮件
第一步:用户服务启用Rabbitmq的发布池
在配置文件grpc_server/user/conf/service.conf
中新增配置项
##rabbitmq
rabmq_addr = "127.0.0.1"
rabmq_port = 5672
rabmq_user = "guest"
rabmq_pwd = "guest"
新增rabbitmq处理模块lib/mq/rabbitmq.go
import RabbimqPool "gitee.com/tym_hmm/rabbitmq-pool-go"
var PubMqPool *RabbimqPool.RabbitPool
//初始化发布池
func InitRabbitmq() {
PubMqPool = RabbimqPool.NewProductPool()
//设置最大连接是4个
PubMqPool.SetMaxConnection(4)
rabAddr := common.Config.String("rabmq_addr")
rabPort, _ := common.Config.Int("rabmq_port")
rabUser := common.Config.String("rabmq_user")
rabPwd := common.Config.String("rabmq_pwd")
err:= PubMqPool.ConnectVirtualHost(rabAddr, rabPort, rabUser, rabPwd, "/")
if err != nil {
log.Println("InitRabbitmq Err:", err)
}
}
//发布消息
func Publish(args ...string) *RabbimqPool.RabbitMqError{
if len(args) < 4 {
return &RabbimqPool.RabbitMqError{Message: "args error"}
}
var exChangeType string
exChangeName := args[0]
queueName := args[1]
routeKey := args[2]
body := args[3]
if len(args) == 4 {
exChangeType = RabbimqPool.EXCHANGE_TYPE_DIRECT
}else {
exChangeType = args[4]
}
data := RabbimqPool.GetRabbitMqDataFormat(
exChangeName, //交换机名
exChangeType, //交换机类型
queueName, //队列名
routeKey, //key
body, //内容
)
return PubMqPool.Push(data)
}
在服务启动处server.go
初始化Rabbitmq
import "emicro_test/emicro_7/grpc_server/user/lib/mq"
...
//初始rabbitmq连接池
mq.InitRabbitmq()
...
第二步:新增邮件服务,同时启用Rabbitmq的消费池
新增服务的目录结构和编码跟用户服务类似,具体可看项目源码
新增Rabbitmq的消费者配置信息mail/lib/conf/mq.toml
,这里由于需要多层嵌套,用toml配置格式github.com/BurntSushi/toml
[Rabbitmq]
[Rabbitmq.user_reg]
ExchangeName = "user_exchange"
ExchangeType = "direct"
Route = ""
QueueName = "user_reg"
IsTry = true
IsAutoAck = false
MaxReTry = 3
对应的toml解析结构体
type Consumer struct{
ExchangeName string
ExchangeType string
Route string
QueueName string
IsTry bool
IsAutoAck bool
MaxReTry int
}
type config struct {
Consumers map[string]Consumer `toml:"Rabbitmq"`
}
Rabbitmq的消费池初始化和消费者订阅处理
func InitRabbitmq() {
SubMqPool = RabbimqPool.NewConsumePool()
//设置最大连接数为10
SubMqPool.SetMaxConnection(10)
//设置每个消费者队列的最大消费者数为10
SubMqPool.SetMaxConsumeChannel(10)
rabAddr := common.Config.String("rabmq_addr")
rabPort, _ := common.Config.Int("rabmq_port")
rabUser := common.Config.String("rabmq_user")
rabPwd := common.Config.String("rabmq_pwd")
err:= SubMqPool.ConnectVirtualHost(rabAddr, rabPort, rabUser, rabPwd, "/")
if err != nil {
log.Println("InitRabbitmq Err:", err)
}
InitConsumer()
}
func InitConsumer() {
//获取配置和解析
var conf config
_, err := toml.DecodeFile("./lib/conf/mq.toml", &conf)
if err !=nil {
log.Println("parse mq.toml Err:", err)
return
}
//初始各个消费者队列
for _, q := range conf.Consumers{
consumeReceive := &RabbimqPool.ConsumeReceive{
ExchangeName: q.ExchangeName, //交换机名
ExchangeType: q.ExchangeType, //交换机类型
Route: q.Route, //key
QueueName: q.QueueName, //队列名
IsTry: q.IsTry, //是否重试
IsAutoAck: q.IsAutoAck, //自动消息确认
MaxReTry: int32(q.MaxReTry), //最大重试次数
EventFail: func(code int, e error, data []byte) { //消费失败的调用
log.Printf("error:%s", e)
},
EventSuccess: successHandler(q), //消费成功的调用
}
SubMqPool.RegisterConsumeReceive(consumeReceive)
}
go func() {
err := SubMqPool.RunConsume()
if err != nil {
log.Println("InitConsumer Err:", err)
}
}()
}
//根据不同队列的不同分发处理
func successHandler(consumer Consumer) func(data []byte, header map[string]interface{}, retryClient RabbimqPool.RetryClientInterface) bool{
switch consumer.QueueName {
case "user_reg":
return user_reg_handler()
}
return nil
}
//用户注册消费处理
func user_reg_handler() func(data []byte, header map[string]interface{}, retryClient RabbimqPool.RetryClientInterface) bool {
return func(data []byte, header map[string]interface{}, retryClient RabbimqPool.RetryClientInterface) bool { //如果返回true 则无需重试
log.Printf("user_reg_handler data:%s\n", string(data))
retryClient.Ack()
return true
}
}
第三步:用户注册成功后,发布消息到MQ队列
在user/handler/user_handler.go
中的UserReg
函数新增发布消息
import "emicro_test/emicro_7/grpc_server/user/lib/mq"
...
//发送到rabbitmq
jsonUser, _ := json.Marshal(user)
res := mq.Publish("user_exchange", "user_reg", "", string(jsonUser))
log.Println("pub res:",res)
resp.Status = common.RESP_SUCCESS
resp.Msg = "success"
第四步:邮件服务发送注册邮件
这里发送邮件用的是smtp,需要先开通和获取授权码,具体可参考其他人博客邮箱设置
在mail/conf/service.conf
新增邮件配置项
##邮件
smtp_addr = "smtp.qq.com"
smtp_port = 25
smtp_user = "" //发件人邮箱
smtp_password = "" //发件人授权码
新增发送邮件的Api,mail/utils/mail.go
, 使用第三方库gomail
import "gopkg.in/gomail.v2"
func SendMail(toMail string, body string) error{
smtp_addr := common.Config.String("smtp_addr")
smtp_port, _ := common.Config.Int("smtp_port")
smtp_user := common.Config.String("smtp_user")
smtp_pwd := common.Config.String("smtp_password")
m := gomail.NewMessage()
m.SetHeader("From", smtp_user)
m.SetHeader("To", toMail)
m.SetBody("text/plain", body)
d := gomail.NewPlainDialer(smtp_addr, smtp_port, smtp_user, smtp_pwd)
if err := d.DialAndSend(m); err != nil {
return err
}
return nil
}
消费发送注册邮件,调整上述第二步新增的用户注册消费处理func user_reg_handler
func user_reg_handler() func(data []byte, header map[string]interface{}, retryClient RabbimqPool.RetryClientInterface) bool {
return func(data []byte, header map[string]interface{}, retryClient RabbimqPool.RetryClientInterface) bool { //如果返回true 则无需重试
log.Printf("user_reg_handler data:%s\n", string(data))
//解析json数据
var user struct{
Id int32
Name string
Phone string
Email string
}
err := json.Unmarshal(data, &user)
if err != nil{
log.Println("user_reg_handler Unmarshal Err:", err)
return false
}
//推送邮件
Body := fmt.Sprintf("Hello. The name is %v and the phone is %v register success!!", user.Name, user.Phone)
err = utils.SendMail(user.Email, Body)
if err != nil{
log.Println("user_reg_handler Err:", err)
return false
}
retryClient.Ack()
return true
}
}
第五步:测试验证
测试验证这步跟随第4节 的注册测试,同样测试方式即可。最终有发送邮件到注册的邮箱即可。如下图效果: