李昊天

李昊天 查看完整档案

兰州编辑北京理工大学  |  餐饮 编辑餐饮公司  |  厨师 编辑 www.baidu.com 编辑
编辑

提升就是一个不懂到懂的过程;

个人动态

李昊天 发布了文章 · 2020-06-20

RabbitMQ入门5-消息确认模式和幂等性

1.消息确认模式

在RabbitMQ中,消息确认主要有生产者发生确认和消费者接收确认

1.1生产者发送确认

生产者发送消息到RabbitMQ服务器,如果RabbitMQ服务器收到消息,则会给生产者一个应答,用于告诉生产者该消息已经成功到达RabbitMQ服务器中

1.2消费者接收确认

用于确认消费者是否成功消费了该条消息
消息确认实现方式有两种
  1. 通过事务的方式
  2. confirm确认机制,因为事务模式比较消耗性能,在实际工作中用的也不多

2.生产者发送确认

2.1 开启confirm模式

当Channel.Confirm(noWait bool)参数设置为false时,broker会返回一个confirm.ok表示同意发送者将当前channel信道设置为confirm模式。
其他代码和transaction模式类似,只是没有Channel.TxCommit()和Channel.TxRollback()。
err = channel.Confirm(false)

2.2 以confirm模式发送消息

package main

import (
    "fmt"
    "github.com/streadway/amqp"
    "rmq/db/rmq"
)

var (
    channel *amqp.Channel
    err     error
    queue   amqp.Queue
    conn    *amqp.Connection
)

func main() {
    conn, err = rmq.GetConn()

    defer conn.Close()

    channel, err = conn.Channel()

    if err != nil {
        fmt.Printf("error: %s \n", err.Error())
        return
    }

    defer channel.Close()

    err = channel.Confirm(false)

    if err != nil {
        fmt.Printf("error: %s \n", err.Error())
        return
    }

    queue, err = channel.QueueDeclare("confirm:message", false, false, false, false, nil)
    if err != nil {
        fmt.Printf("error: %s \n", err.Error())
        return
    }

    confirms := channel.NotifyPublish(make(chan amqp.Confirmation, 1))

    defer confirmOne(confirms)
    err = channel.Publish("", queue.Name, false, false, amqp.Publishing{
        ContentType: "text/plain",
        Body:        []byte("confirm message"),
    })

    if err != nil {
        fmt.Printf("error: %s \n", err.Error())
        return
    }

    fmt.Println("消息发送成功")

}

func confirmOne(confirms <-chan amqp.Confirmation) {
    if confirmed := <-confirms; confirmed.Ack {
        fmt.Printf("confirmed delivery with delivery tag: %d", confirmed.DeliveryTag)
    } else {
        fmt.Printf("confirmed delivery of delivery tag: %d", confirmed.DeliveryTag)
    }
}

消息拒绝

_ = d.Nack(false, false) // 手动拒绝消息 可以拒绝多条消息 第二个参数设置为true 将再次放入队列中
_ = d.Reject(true) // 手动拒绝消息 只能拒绝一条消息 为true 将再次放入队列中
_ = d.Ack(false) // 手动确认

1.简介

消息幂等性其实就是保证同一个消息不被消费者重复消费两次
当消费者消费完之后,通常会发送一个ack应答确认消息给生产者
但是这中间有可能因为网络中断等原因,导致生产者未能收到确认消息,有此这条消息将被重复发送给消费者消费,实际上这条消息已经被消费过了,这就是重复消费的问题!!!

1.1 如何避免重复消费

  • 消息全局ID或者写个唯一标识 (时间戳,uuid等),每次消费消息之前根据消息id去判断该消息是否已被消费过,如果已经消费国,则不处理该消息,否则正常消费,并且进行入库操作(消息全局ID作为数据库表的主键,防止重复)
  • 利用redis的setnx命令,给消息分配一个全局ID,只要消费过该消息,将id message k:v 形式写入redis 消费者开始消费前 先去redis查询有没有消费记录

1.2 代码演示

生产者
channel.Publish("", queue.Name, false, false,
            amqp.Publishing{
                MessageId:   uuid.NewV4().String(),
                Timestamp: time.Now(),
                ContentType: "text/plain",
                Body:        []byte(fmt.Sprintf("hello---%d", i)),
})
消费者
go func() {
        for d := range megs {
            err = db.GetRedis().Get(d.MessageId).Err()
            if err != redis.Nil {
                // 消息已被消费 忽略
                logger.Warn("消息已被消费 忽略 %s", d.MessageId)
                _ = d.Reject(false)
                continue
            }

            logger.Info("messageBody: %s", d.Body)
            logger.Info("messageID: %s", d.MessageId)
            logger.Info("Timestamp: %s", d.Timestamp.Format("2006-01-02 15:04:05"))

            if err := d.Ack(false); err != nil {
                logger.Error("消息确认失败")
            } else {
                db.GetRedis().SetNX(d.MessageId, d.Body, time.Hour*2)
                logger.Warn("设置消息id")
            }
        }
    }()
查看原文

赞 4 收藏 3 评论 0

李昊天 发布了文章 · 2020-06-20

RabbitMQ入门4-工作模式和交换机类型

工作模式

1. 简单队列模式(simple queue)

只包含一个生产者和一个消费者 生产者将消息发送到队列中
消费者从队列中接收消息

2. 工作队列模式(work Queues)

一个生产者对应多个消费者,一条消息只被一个消费者进行消费
工作队列有轮询分发和公平分发两种模式

2.1平均分配

公平分配,每次只给一个消费者分配一个
ch.Qos(1, 0, false)

3. 发布-订阅模式(Publish/SubScribe)

  1. 一个生产者,多个消费者
  2. 每个消费者都有自己的消息队列,分别绑定到不同的队列上
  3. 生产者没有把消息发送到队列,而是发送到交换机上
  4. 每个队列都需要绑定到交换机上
  5. 生产者生产的消息先经过交换机然后到达队列,一个消息可以被多个消费者消费
  6. 如果消息发送到没有队列绑定的交换机时,消息将会消失,因为交换机没有存储消息的能力,只有队列才有存储消息的能力;

4. 路由模式(routing)

生产者将消息发送到direct交换机,它会吧消息路由到那些binding key 与 routing key 完全匹配的queue中
这样就能实现消费者有选择的去消费消息

5. 主题模式(Tipic)

交换机通过模式匹配分配消息的路由键属性
将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上,它将路由键和绑定的字符串切分为单词,这些单词之间用点隔开(use.news)
同样也会识别两个通配符, "#" 和 "*"
# 匹配0个或者多个单词, * 匹配多个单词
binding keyrouting key
logger.#logger.error
logger.info
logger.debug
#.infologger.info

交换机类型

参数名类型解释
direct直连交换机
fanout扇型交换机
topic主题交换机
headers头交换机

直连交换机

是根据消息携带的路由键(routing key) 将消息投递到对应的队列,步骤如下:
  1. 将一个队列绑定到某个交换机上,同时赋予绑定一个路由键(routing key);
  2. 当一个携带着路由键值为R的消息被发送给直连交换机时,交换机会把它路由给绑定值同样为R的队列

扇形交换机

将消息路由给绑定到它身上的所有队列,不同于直连交换机,路由键在此类型上不启任务作用
如果N个队列绑定到某个扇形交换机上,当有消息发送给此扇形交换机时,交换机会将此消息发送给这所有的N个队列

主题交换机

队列通过路由键绑定到交换机上,然后交换机根据消息里面的路由值,将消息路由给一个或多个绑定的队列
扇形交换机和主题交换机异同
  • 对于扇形交换机 路由键是没有意义的,只要有消息,它都发送消息到它所绑定的所有队列上
  • 对于主题交换机 路由规则由路由键决定,只有满足路由键的规则,消息才可以路由到对应的队列上

头交换机

类似主题交换机,但是头交换机所有多个消息属性来替代路由键建立路由规则,通过判断消息头的值能否与指定的绑定相匹配来确立路由规则
此交换机有个重要的参数: x-match
  • x-match为any时: 消息头的任意一个值被匹配就可以满足条件
  • x-match为all的时候,就需要消息头的所有值都匹配成功
查看原文

赞 1 收藏 0 评论 0

李昊天 发布了文章 · 2020-06-20

RabbitMQ入门3-api参数

ch.QueueDeclare

    queue, err = ch.QueueDeclare(
        "hello", // name 
        false,   // durable 
        false,   // delete when unused
        false,   // exclusive
        false,   // no-wait
        nil,     // arguments
        )
参数名参数类型解释
namestring队列名称
durablebool是否持久化,队列的声明默认是存放到内存中的,如果rabbitmq重启会丢失,如果想重启之后还存在就要使队列持久化,保存到Erlang自带的Mnesia数据库中,当rabbitmq重启之后会读取该数据库
autoDeletebool是否自动删除队列,当最后一个消费者断开连接之后队列是否自动被删除,可以通过RabbitMQ Management,查看某个队列的消费者数量,当consumers = 0时队列就会自动删除
exclusivebool是否排外的,有两个作用,
1:当连接关闭时该队列是否会自动删除;
2:该队列是否是私有的private,如果不是排外的,可以使用两个消费者都访问同一个队列,没有任何问题,如果是排外的,会对当前队列加锁,其他通道channel是不能访问的,如果强制访问会报异常;
一般等于true的话用于一个队列只能有一个消费者来消费的场景
no-waitbool是否等待服务器返回
argumentsmap[string]interface{}设置队列的其他一些参数,如 x-rnessage-ttlx-expiresx-rnax-lengthx-rnax-length-bytesx-dead-letter-exchangex-deadletter-routing-keyx-rnax-priority 等。

ch.Publish

ch.Publish(
    "",      // exchange
    "hello", // routing key
    false,   // mandatory
    false,   // immediate
    body,    // msg
) 
参数名参数类型解释
exchangestring交换机
routing keystring路由键,#匹配0个或多个单词,*匹配一个单词,在topic exchange做消息转发用
mandatorybooltrue:如果exchange根据自身类型和消息routeKey无法找到一个符合条件的queue,那么会调用basic.return方法将消息返还给生产者。
false:出现上述情形broker会直接将消息扔掉
immediatebooltrue:如果exchange在将消息route到queue(s)时发现对应的queue上没有消费者,那么这条消息不会放入队列中。当与消息routeKey关联的所有queue(一个或多个)都没有消费者时,该消息会通过basic.return方法返还给生产者。
msg消息内容

ch.Consume

ch.Consume(
  "hello", // queue
  "",      // consumer
  true,    // auto-ack
  false,   // exclusive
  false,   // no-local
  false,   // no-wait
  nil,     // args
)
参数名参数类型解释
queuestring
consumerstring
auto-ackbool是否自动ack,如果不自动ack,需要使用channel.ack、channel.nack、channel.basicReject 进行消息应答
exclusivebool
no-localbool
no-waitbool是否等待服务器返回
args

ch.ExchangeDeclare

ch.ExchangeDeclare(
   "logs",   // name
   "fanout", // type
   true,     // durable
   false,    // auto-deleted
   false,    // internal
   false,    // no-wait
   nil,      // arguments
)
参数名参数类型解释
namestring
typestring交换机类型: directfanouttopicheaders其中一种
durablebool是否持久化,durable设置为true表示持久化,反之是非持久化,持久化的可以将交换器存盘,在服务器重启的时候不会丢失信息
auto-deletedbool是否自动删除,设置为TRUE则表是自动删除,自删除的前提是至少有一个队列或者交换器与这交换器绑定,之后所有与这个交换器绑定的队列或者交换器都与此解绑,一般都设置为fase
internalbool是否内置,如果设置 为true,则表示是内置的交换器,客户端程序无法直接发送消息到这个交换器中,只能通过交换器路由到交换器的方式
no-waitbool是否等待服务器返回
arguments其它一些结构化参数比如alternate-exchange
查看原文

赞 0 收藏 0 评论 0

李昊天 发布了文章 · 2020-06-20

RabbitMQ入门2-工作原理和基本操作

组成部分:

名称解释
Broker消息队列服务进程,该进程包含2个部分,Exchange和Queue
Exchange消息队列交换机,按一定的规则将消息路由转发到某个队列,对消息进行过滤!
Queue存储消息的队列,消息到达队列并转发给消费方
Producer消息生产者,即生产方客户端,生产方客户端将消息发送到MQ
Consumer消息消费者,消费方客户端,接收MQ转发的消息

消息发布流程:

  1. 生产者和Broker建立TCP连接
  2. 生产者和Broker建立通道.
  3. 生产者通过通道把消息发送给Broker,由Exchange将消息转发.
  4. Exchange将消息发送给指定的Queue(队列)

消息接收流程:

  1. 消费者和Broker建立TCP连接
  2. 消费者和Broker建立通道
  3. 消费者监听指定的Queue
  4. 当有消息到达Queue时Broker默认将消息推给消费者
  5. 消费者接收到消息.

基本操作

后台启动rabbitmq

rabbitmq-server -detached

查看单节点状态

rabbitmqctl status

查看日志

cat $RABBITMQ/var/log/rabbitmq/rabbit@$HOSTNAME.log

查看集群状态

rabbitmqctl cluster_status

新增用户

rabbitmqctl add_user lee lee

新增授权

rabbitmqctl set_permissions -p / lee ".*" ".*" ".*"

设置管理者权限

rabbitmqctl set_user_tags lee administrator

启动web管理插件

rabbitmq-plugins enable rabbitmq_management

打开 http://ip:15672/ 即可打开web管理界面
查看原文

赞 0 收藏 0 评论 0

李昊天 发布了文章 · 2020-06-20

RabbitMQ入门1-下载安装

安装erlang

rabbitMQ是erlang语言开发的,所以安装的的时候需要erlang环境
yum -y install erlang 

测试erlang安装完毕

erl -version

下载地址

http://erlang.org/download/

下载到本地

wget https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.8.5/rabbitmq-server-generic-unix-3.8.5.tar.xz

解压

tar -xvf rabbitmq-server-generic-unix-3.8.5.tar.xz

移动目录

mv rabbitmq_server-3.8.5 /opt/rabbitmq

添加环境变量

vim ~/.bash_profile

export RABBITMQ=/opt/rabbitmq

PATH=$PATH:$HOME/bin:$RABBITMQ/sbin

使环境变量生效

source ~/.bash_profile

查看环境变量

echo $RABBITMQ

后台启动rabbitmq

rabbitmq-server -detached

错误

Crash dump was written to: erl_crash.dump
init terminating in do_boot ()

该错误是erlang和rabbitmq不符合 只能重新安装erlang
对应版本查询: https://www.rabbitmq.com/whic...

编译安装erlang

卸载之前安装的erlang

yum remove erlang

yum list installed | grep erlang-erts

yum remove erlang-erts.x86_64

下载

wget http://erlang.org/download/otp_src_21.3.tar.gz

解压

tar -zxvf otp_src_21.3.tar.gz 

进入目录

cd otp_src_21.3

生成makefile

./configure --prefix=/opt/erlang

编译安装

make 

make install

配置环境变量

vim ~/.bash_profile
# 使环境变量生效
source ~/.bash_profile

查看是否安装成功

erl -version
查看原文

赞 0 收藏 0 评论 0

李昊天 发布了文章 · 2020-06-18

golang 泛型预览版发布

可以在: https://go2goplay.golang.org/ 去尝试下go的泛型
官方表示有可能在go1.17版本中添加,最早时间可能是2021年8月份

大概代码如下:

package main

import (
    "fmt"
)

func Print(type T)(s []T) {
    for _, v := range s {
        fmt.Print(v)
    }
}

func main() {
    Print([]string{"Hello, ", "playground\n"})
    Print(int)([]int{1, 2, 3})
}

更多查看:
https://github.com/golang/pro...
https://go.googlesource.com/p...

查看原文

赞 0 收藏 0 评论 2

李昊天 发布了文章 · 2020-05-10

golang 生成个性二维码

前言

好久没发文章了,最近登录看到有朋友评论了golang生成二维码的文章,想了解下生成个性二维码的解决方案!

实现思路 ,网上搜相关资料几乎没有, 没有那就就自己想吧,就去看了下草料二维码;

然后突然就有感觉了 不就是把像素的颜色替换嘛 这还不简单

1 打开二维码图片
2 打开要填充的图片
3 将要填充的图片剪裁成和二维码一样大小
4 分析二维码的每个像素 替换成图片的像素就行了

以下是生成的例子:

例子

20200510163912.png
20200510163927.png
20200510164130.png
20200510164014.png

代码

package main

import (
    "fmt"
    "github.com/nfnt/resize"
    "image"
    "image/color"
    "image/draw"
    "log"
    "os"
    "time"

    _ "image/jpeg"
    _ "image/gif"
    "image/png"
)

func main() {
    // 打开二维码图片
    qrFile, err := os.Open("img/qr.png")

    if err != nil {
        log.Fatalf("打开二维码失败 %s", err.Error())
        return
    }

    defer qrFile.Close()

    // 将二维码文件接码成图片
    img, _, err := image.Decode(qrFile)

    if err != nil {
        log.Fatalf("image decode error %s", err.Error())
        return
    }

    // 获取二维码的宽高
    width, height := img.Bounds().Max.X, img.Bounds().Max.Y

    // 打开要填充的图片
    bgFile, err := os.Open("bg/7.jpg")
    if err != nil {
        log.Fatalf("打开填充图失败 %s", err.Error())
        return
    }

    defer bgFile.Close()

    // 将填充图解码成png图片
    bgImg, _, err := image.Decode(bgFile)

    if err != nil {
        log.Fatalf("填充图解码失败  %s", err.Error())
        return
    }

    // 获取填充图的宽高
    bgWidth, bgHeight := bgImg.Bounds().Max.X, bgImg.Bounds().Max.Y

    // 检测二维码和填充图宽高是否一致
    if width != bgWidth || height != bgHeight {
        // 如果不一致将填充图剪裁
        bgImg = ImageResize(bgImg, width, height)
    }

    // 开始填充二维码
    for y := 0; y < img.Bounds().Max.X; y++ {
        for x := 0; x < img.Bounds().Max.X; x++ {

            qrImgColor := img.At(x, y)

            // 检测图片颜色 如果rgb值是 255 255 255 255 则像素点为白色 跳过
            // 如果rgba值是 0 0 0 0 则为透明色 跳过
            switch img.(type) {
            case *image.NRGBA:
                c := qrImgColor.(color.NRGBA)
                if (c.R == 0 && c.G == 0 && c.B == 0 && c.A == 0) || (c.R == 255 && c.G == 255 && c.B == 255 && c.A == 255) {
                    continue
                }

            case *image.RGBA:
                c := qrImgColor.(color.RGBA)
                if (c.R == 0 && c.G == 0 && c.B == 0 && c.A == 0) || (c.R == 255 && c.G == 255 && c.B == 255 && c.A == 255) {
                    continue
                }
            }

            // 获取要填充的图片的颜色
            bgImgColor := bgImg.At(x, y)
            
            // 填充颜色
            switch bgImg.(type) {
            case *image.RGBA64:
                c := bgImgColor.(color.RGBA64)
                img.(draw.Image).Set(x, y, color.RGBA64{R: c.R, G: c.G, B: c.B, A: c.A})

            case *image.NRGBA:
                c := bgImgColor.(color.NRGBA)
                img.(draw.Image).Set(x, y, color.NRGBA{R: c.R, G: c.G, B: c.B, A: c.A})

            case *image.RGBA:
                c := bgImgColor.(color.RGBA)
                img.(draw.Image).Set(x, y, color.RGBA{R: c.R, G: c.G, B: c.B, A: c.A})

            case *image.YCbCr:
                c := bgImgColor.(color.YCbCr)
                img.(draw.Image).Set(x, y, color.YCbCr{Y: c.Y, Cb: c.Cb, Cr: c.Cr})
            default:
                fmt.Println("error")
            }

        }
    }

    filename := fmt.Sprintf("%s.png", time.Now().Format("20060102150405"))

    // 写入文件
    outFile, err := os.Create(filename)

    if err != nil {
        log.Fatal(err)
    }

    defer outFile.Close()

    _ = png.Encode(outFile, img)
}

func ImageResize(src image.Image, w, h int) image.Image {
    return resize.Resize(uint(w), uint(h), src, resize.Lanczos3)
}

注意

1 尽量用png的二维码和填充图
2 案例中png和jpg gif图片都可以填充 其他格式的图片自己加就行了

代码地址

https://github.com/lihaotian0607/qr

其他

另外附上封装好的
https://github.com/lihaotian0607/qrcode

生成普通二维码

var qr, err = qrcode.New("https://www.xxx.com/", qrcode.Highest)

设置头像

    qr.SetAvatar(&qrcode.Avatar{
        Src:    "../static/1.jpg",
        Width:  60,
        Height: 60,
        Round:  10,
    })

设置背景图

qr.SetBackground(&qrcode.Background{
        Src:    "../static/3.png",
        X:      70,
        Y:      55,
        Width:  270,
        Height: 270,
    })

设置前景图

qr.SetForeground("../static/2.png")

写入文件

    img, err := qr.Generate(300)
    if err != nil {
        log.Fatal(err.Error())
        return
    }

    file, err := os.Create("out.png")

    if err != nil {
        log.Fatalf("创建文件失败 %s", err.Error())
        return
    }

    err = png.Encode(file, img)
    if err != nil {
        log.Fatalf("文件写入失败 %s", err.Error())
    }
查看原文

赞 0 收藏 0 评论 5

李昊天 发布了文章 · 2020-02-03

golang 生成二维码海报

生成带头像的二维码

import (  
  "errors"  
  "fmt"
  "github.com/nfnt/resize" 
  "github.com/skip2/go-qrcode" 
  "image" 
  "image/draw" 
  "image/png" 
  "os"
 )  
  
var err error  
  
func createAvatar() (image.Image, error) {  
  var (  
  bgImg      image.Image  
 offset     image.Point  
 avatarFile *os.File  
 avatarImg  image.Image  
  )  
  
  bgImg, err = createQrCode("http://www.baidu.com")  
  
  if err != nil {  
  fmt.Println("创建二维码失败:", err)  
  return nil, errors.New("创建二维码失败")  
 }  
  avatarFile, err = os.Open("./avatar.png")  
  avatarImg, err = png.Decode(avatarFile)  
  avatarImg = ImageResize(avatarImg, 40, 40)  
  b := bgImg.Bounds()  
  
  // 设置为居中  
  offset = image.Pt((b.Max.X-avatarImg.Bounds().Max.X)/2, (b.Max.Y-avatarImg.Bounds().Max.Y)/2)  
  
  m := image.NewRGBA(b)  
  
  draw.Draw(m, b, bgImg, image.Point{X: 0, Y: 0,}, draw.Src)  
  
  draw.Draw(m, avatarImg.Bounds().Add(offset), avatarImg, image.Point{X: 0, Y: 0}, draw.Over)  
  
  return m, err  
}  
  
func createQrCode(content string) (img image.Image, err error) {  
  var qrCode *qrcode.QRCode  
  
 qrCode, err = qrcode.New(content, qrcode.Highest)  
  
  if err != nil {  
  return nil, errors.New("创建二维码失败")  
 }  
  qrCode.DisableBorder = true  
  
 img = qrCode.Image(150)  
  
  return img, nil  
}  
  
func ImageResize(src image.Image, w, h int) image.Image {  
  return resize.Resize(uint(w), uint(h), src, resize.Lanczos3)  
}

预览

image.png

合成到海报

func main() {  
  var (  
  bgFile *os.File  
 bgImg     image.Image  
 qrCodeImg image.Image  
 offset    image.Point  
  )  
  
  // 01: 打开背景图片  
  bgFile, err = os.Open("./bg.png")  
  if err != nil {  
  fmt.Println("打开背景图片失败", err)  
  return  
  }  
  
  defer bgFile.Close()  
  
  // 02: 编码为图片格式  
  bgImg, err = png.Decode(bgFile)  
  if err != nil {  
  fmt.Println("背景图片编码失败:", err)  
  return  
  }  
  
  // 03: 生成二维码  
  qrCodeImg, err = createAvatar()  
  if err != nil {  
  fmt.Println("生成二维码失败:", err)  
  return  
  }  
  
  offset = image.Pt(426, 475)  
  
  b := bgImg.Bounds()  
  
  m := image.NewRGBA(b)  
  
  draw.Draw(m, b, bgImg, image.Point{X: 0, Y: 0,}, draw.Src)  
  
  draw.Draw(m, qrCodeImg.Bounds().Add(offset), qrCodeImg, image.Point{X: 0, Y: 0}, draw.Over)  
  
  // 上传至oss时这段要改 
  i, _ := os.Create(path.Base("a2.png"))  
  
  _ = png.Encode(i, m)  
  
  defer i.Close()  
  
}

预览

image.png

上传至阿里云

func ossClient() (bucket *alioss.Bucket, err error) {  
  var (  
  key string = "xxx"  
  bucketName string = "xxx"  
  secret string = "xxx"  
  endpoint string = "xxx"  
  )  
  
  // 创建OSSClient实例。  
  client, err = alioss.New(endpoint, key, secret)  
  
  if err != nil {  
  fmt.Println("获取阿里云oss实例失败:", err)  
  return nil, errors.New("资源服务器配置有误")  
 }  
  // 获取存储空间。  
  bucket, err = client.Bucket(bucketName)  
  
  if err != nil {  
  logger.Error("获取阿里云oss存储空间错误:", err)  
  return nil, errors.New("获取存储服务器失败")  
 }  
  return bucket, nil  
}

开始上传

将之前注释的那段删除 修改成这段
bucket, err = ossClient()  
  
if err != nil {  
  fmt.Println("error:", err)  
  return  
}  
  
imgBuff := bytes.NewBuffer(nil) //开辟一个新的空buff  
err = png.Encode(imgBuff, m) //img写入到buff  
  
if err != nil {  
  fmt.Println("写入缓冲区失败",err)  
  return
}  
  
err = bucket.PutObject("qrcode/2-1.png", imgBuff)  
  
fmt.Println("error:", err)
查看原文

赞 2 收藏 0 评论 7

李昊天 发布了文章 · 2019-12-08

flutter 自定义 websocket 路由

flutterwebsocket 中 服务端推送数据给客户端后 很多人的处理居然都是 if / switch; 感觉这样的写法不咋好!

自己想的一个办法:
lib 目录下新建一个 socket 目录 里面创建两个文件main.dartrouter.dart;
main.dart : 主要控制websocket的连接 断开 和收到消息的处理;
router.dart 则为websocket 服务端返回的消息做路由处理;

router.dart

import 'package:lee/logic/user.dart';

typedef void RouteHandle(Map params);

var wsRouter = new WsRouter();

class WsRouter {
  static Map<String, RouteHandle> _routers = new Map();

  init() {
    routers.forEach((route) {
      route.forEach((name, value) {
        this.add(name, value);
      });
    });
  }

  // 增加路由
  void add(String name, RouteHandle handle) {
    WsRouter._routers[name] = handle;
  }

  // 路由处理
  Future<void> handle(String name, Map params) async {
    RouteHandle handle = WsRouter._routers[name];
    if (handle == null) {
      print("路由不存在");
      return;
    }
    handle(params);
  }
}

List<Map<String, RouteHandle>> routers = [
  {"login": UserLogic.login},
  {"kick": UserLogic.kick},
];

main.dart

import 'package:lee/socket/router.dart';
import 'package:web_socket_channel/io.dart';
import 'dart:convert';

var webSocket = new WebSocket();

class WebSocket {
  // webSocket连接
  IOWebSocketChannel webSocketChannel;

  factory WebSocket() => _webSocket();

  static WebSocket _instance;

  // 构造函数
  WebSocket._() {
    // 初始化webSocket路由
    wsRouter.init();
  }

  static WebSocket _webSocket() {
    if (_instance == null) {
      _instance = WebSocket._();
    }
    return _instance;
  }

  conn() {
    IOWebSocketChannel channel = new IOWebSocketChannel.connect(
        "ws://127.0.0.1:8080/ws",
        pingInterval: Duration(milliseconds: 100));

    channel.stream
        .listen((data) => onMessage(data), onError: onError, onDone: onDone);

    this.webSocketChannel = channel;
  }

  onMessage(response) async {
    // 例如服务端返回的大概是这样一个json
    // {"cmd":"kick","data":{}}
    // {"cmd":"login","data":{}}
    Map params = json.decode(response);
    wsRouter.handle(params["cmd"], params["data"]);
  }

  onError(err) async {}

  onDone() async {}
}

如果对您有帮助请点个赞/收藏
如果代码/逻辑有问题 请指正!

查看原文

赞 0 收藏 0 评论 2

李昊天 回答了问题 · 2019-11-12

electron如何实现从登陆界面跳转到主界面

关注 4 回答 3

认证与成就

  • 获得 389 次点赞
  • 获得 14 枚徽章 获得 0 枚金徽章, 获得 2 枚银徽章, 获得 12 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2018-04-27
个人主页被 2.7k 人浏览