来源

本文主要记录了 Protobuf 的基本使用。包括 编译器 protoc 、Go Plugins 安装及 .proto文件定义、编译等。

1.概述

Protocol buffers 是一种语言无关、平台无关的可扩展机制或者说是数据交换格式,用于序列化结构化数据。与 XML、JSON 相比,Protocol buffers 序列化后的码流更小、速度更快、操作更简单。

Protocol buffers are a language-neutral, platform-neutral extensible mechanism for serializing structured data.

2. Protocol Compiler

protoc 用于编译 protocolbuf (.proto文件) 和 protobuf 运行时。

protoc 是 C++ 写的,比较简单的安装方式是直接下载编译好的二进制文件。

release 版本下载地址如下

下载

下载操作系统对应版本然后解压然后配置一下环境变量即可。

比如windows就下载protoc-3.14.0-win64.zip 然后把解压后的xxx\protoc-3.14.0-win64\bin配置到环境变量。

linux则下载protoc-3.14.0-linux-x86_64.zip

解压
unzip protoc-3.14.0-linux-x86_64.zip -d protoc-3.14.0-linux-x86_64
sudo vim /etc/profile 
#记得改成自己的路径
export PATH=$PATH:/home/lixd/17x/protoc-3.14.0-linux-x86_64/bin
source /etc/profile

查看是否安装成功

protoc --version

libprotoc 3.14.0

3. Go Plugins

出了安装 protoc 之外还需要安装各个语言对应的编译插件,我用的 Go 语言,所以还需要安装一个 Go 语言的编译插件。

go get google.golang.org/protobuf/cmd/protoc-gen-go

4. Demo

创建.proto 文件

hello_world.proto

//声明 protobuf 版本 只有 proto3 才支持 gRPC
syntax = "proto3";
// .表示生成go文件输出在当前目录,proto 表示生成go文件包名为proto
option go_package = ".;proto";
// 指定当前proto文件属于helloworld包
package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}
//
// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

protoc 编译

编译命令

# Syntax: protoc [OPTION] PROTO_FILES
$ protoc --proto_path=IMPORT_PATH  --go_out=OUT_DIR  --go_opt=paths=source_relative path/to/file.proto

这里简单介绍一下 golang 的编译姿势:

–proto_path 或者-I :指定 import 路径,可以指定多个参数,编译时按顺序查找,不指定时默认查找当前目录。 .proto 文件中也可以引入其他 .proto 文件,这里主要用于指定被引入文件的位置。 –go_out:golang编译支持,指定输出文件路径 其他语言则替换即可,比如 –java_out 等等 –go_opt:指定参数,比如–go_opt=paths=source_relative就是表明生成文件输出使用相对路径。 path/to/file.proto :被编译的 .proto 文件放在最后面

protoc --go_out=. hello_word.proto

编译后会生成一个hello_word.pb.go文件。 到此为止就ok了。

5. 编译过程

可以把 protoc 的编译过程分成简单的两个步骤:

  1. 解析 .proto 文件,编译成 protobuf 的原生数据结构保存在内存中;
  2. 把 protobuf 相关的数据结构传递给相应语言的编译插件,由插件负责将接收到的 protobuf 原生结构渲染输出为特定语言的模板。

具体过程如图所示:

protobuf-process

protoc 中原生包含了部分语言(java、php、python、ruby等等)的编译插件,但是没有 Go 语言的,所以需要额外安装一个插件。

具体原生支持见源码https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/compiler/main.cc

同样的,后续讲到的 gRPC Plugins、gRPC-Gateway 也是一个个的 protoc 编译插件,将 .proto 文件编译成对应模块需要的源文件。

在做爬虫时有时候会遇到需要带已登录的 cookie 请求,这个时候最简单的方法是在浏览器登录后,在开发者面板找到 cookie 字符串,然后拷贝粘贴。这就面临一个问题需要把 cookie 字符串解析成 Go 语言 cookie 结构体。

可以通过 net/http 库转换,有两种方式可以较好实现:

方式一

使用 ReadRequest

package main

import (
    "bufio"
    "fmt"
    "net/http"
    "strings"
)

func main() {
    rawCookies := "cookie1=value1;cookie2=value2"
    rawRequest := fmt.Sprintf("GET / HTTP/1.0\r\nCookie: %s\r\n\r\n", rawCookies)

    req, err := http.ReadRequest(bufio.NewReader(strings.NewReader(rawRequest)))

    if err == nil {
        cookies := req.Cookies()
        fmt.Println(cookies)
    }
}

方式二

使用 header

package main

import (
    "fmt"
    "net/http"
)

func main() {
    rawCookies := "cookie1=value1;cookie2=value2"

    header := http.Header{}
    header.Add("Cookie", rawCookies)
    request := http.Request{Header: header}

    fmt.Println(request.Cookies()) // [cookie1=value1 cookie2=value2]
}

为什么写这个, 因为遇到坑了

组件需要传递 props [show] :visible.sync=”show”

弹窗关闭引起报错,并且不调起第二次弹框

解决办法

报错: 使用内部变量解决

不调起第二次弹框: 绑定 close 事件,修改父级传入的 props 【show】

组件

<el-dialog title="鸡蛋详情" :visible.sync="show" @close="updateShowDialog" width="80%">


export default {
  props: {
    showDialog: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      show: this.showDialog,



  methods: {
    updateShowDialog() {
      this.$emit("updateDialogEggDetailShow", false);
    },



父级

    <!-- 鸡蛋详情 -->
    <EggDetail
      v-if="dialogEggDetailShow"
      @updateDialogEggDetailShow="updateDialogEggDetailShow"
      :show-dialog.sync="dialogEggDetailShow"
    ></EggDetail>


为什么写这个, 因为遇到坑了

HTTP Header 规范

指定头部信息, 就可以下载了

	fileName := time.Now().Format("鸡蛋订单20060102150405") + ".csv"
	w.Header().Add("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, fileName))
	w.Header().Add("Content-Description", "File Transfer")
	w.Header().Add("Content-Type", "application/octet-stream; charset=utf-8")
	w.Write(b)

但是, 如果你前端用的是 axios 的话, 得费一番折腾了, 有几种方式, 第一种是服务端发送 blob 形式,js 接受 第二种是发送文本转 blob , 下面代码是第二种, 服务端发送了 {content: “xxsxxx”, fileName: “xxx.csv”}

const url = window.URL.createObjectURL(
  new Blob([new Uint8Array([0xef, 0xbb, 0xbf]), data.content], {
    type: "text/plain;charset=utf-8"
  })
);
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", data.fileName);
document.body.appendChild(link);
link.click();

ps

golang csv 库推荐

Download files with AJAX (axios)

瀑布流页面,滚动加载更多图片,图片加载到 80-100 张时候, 浏览器明显卡顿

优化

不在可视区域的图片直接替换成空 div,用户滚回来的时候再替换回来,like tumblr archive page

<template>
  <div>
    <div v-for="(item,key) in imgs" ref="cards" :key="key" :data-index="key" :class="item.isOptimized == false ? 'placeholders' : 'placeholdersno'" >
      <img v-if="item.isOptimized" style="width:150px;" :src="item.src" alt="">
    </div>
  </div>
</template>
<script>
import throttle from 'raf-throttle'

export default {
  data() {
    return {
      imgs: [
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        }
      ]
    }
  },
  mounted() {
    this.addOptimizeScrollListener();
  },
  beforeDestroy() {
    document.removeEventListener('scroll', this._optimizeListener, false);
  },
  methods: {
    addOptimizeScrollListener() {
      const windowHeight = window.innerHeight;
      const TOLERANCE_HEIGHT = 300;

      var that = this;

      this._optimizeListener = throttle(() => {
        if (this.$refs.cards) {
          const windowScrollY = window.scrollY;
          this.$refs.cards.forEach(card => {

            var postion = that.getPosition(card)
            var index = card.getAttribute("data-index");

            if (postion.y > windowScrollY + (windowHeight + TOLERANCE_HEIGHT) || postion.y < windowScrollY - (windowHeight + TOLERANCE_HEIGHT)) {
              that.imgs[index].isOptimized = false;
            } else {
              that.imgs[index].isOptimized = true;
            }
            // console.log(card.getAttribute("data-index"), postion.y, windowScrollY + (windowHeight + TOLERANCE_HEIGHT),postion.y > windowScrollY + (windowHeight + TOLERANCE_HEIGHT),postion.y > windowScrollY - (windowHeight + TOLERANCE_HEIGHT) )
          });
        }
      });
      document.addEventListener('scroll', this._optimizeListener, false);
    },
    getPosition(el) {
      var x = 0; var y = 0;
      while (el != null && (el.tagName || '').toLowerCase() != 'html') {
        x += el.offsetLeft || 0;
        y += el.offsetTop || 0;
        el = el.parentElement;
      }
      return { x: parseInt(x, 10), y: parseInt(y, 10) };
    }
  }
}
</script>

<style scoped>
.placeholders {
  height: 150px;
  width: 150px;
  background: linear-gradient(to left bottom, #27e3f8,#c8f4f7);
}

.placeholdersno {
  height: 150px;
  width: 150px;
}
</style>

图片父级的高度和宽度,可以在判断 isOptimized 的时候赋值给 img 当一个属性

参考

  1. 图片视频瀑布流长列表性能优化实践

Demo1 滚动条方式

<template>
  <div>
    <div v-for="(item,key) in imgs" ref="cards" :key="key" :data-index="key" :class="item.isOptimized == false ? 'placeholders' : 'placeholdersno'" >
      <img v-if="item.isOptimized" style="width:150px;" :src="item.src" alt="">
    </div>
  </div>
</template>
<script>
import throttle from 'raf-throttle'

export default {
  data() {
    return {
      imgs: [
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        }
      ]
    }
  },
  mounted() {
    this.addOptimizeScrollListener();
  },
  beforeDestroy() {
    document.removeEventListener('scroll', this._optimizeListener, false);
  },
  methods: {
    addOptimizeScrollListener() {
      const windowHeight = window.innerHeight;
      const TOLERANCE_HEIGHT = 300;

      var that = this;

      this._optimizeListener = throttle(() => {
        if (this.$refs.cards) {
          const windowScrollY = window.scrollY;
          this.$refs.cards.forEach(card => {

            var postion = that.getPosition(card)
            var index = card.getAttribute("data-index");

            if (postion.y > windowScrollY + (windowHeight + TOLERANCE_HEIGHT) || postion.y < windowScrollY - (windowHeight + TOLERANCE_HEIGHT)) {
              that.imgs[index].isOptimized = false;
            } else {
              that.imgs[index].isOptimized = true;
            }
            // console.log(card.getAttribute("data-index"), postion.y, windowScrollY + (windowHeight + TOLERANCE_HEIGHT),postion.y > windowScrollY + (windowHeight + TOLERANCE_HEIGHT),postion.y > windowScrollY - (windowHeight + TOLERANCE_HEIGHT) )
          });
        }
      });
      document.addEventListener('scroll', this._optimizeListener, false);
    },
    getPosition(el) {
      var x = 0; var y = 0;
      while (el != null && (el.tagName || '').toLowerCase() != 'html') {
        x += el.offsetLeft || 0;
        y += el.offsetTop || 0;
        el = el.parentElement;
      }
      return { x: parseInt(x, 10), y: parseInt(y, 10) };
    }
  }
}
</script>

<style scoped>
.placeholders {
  height: 150px;
  width: 150px;
  background: linear-gradient(to left bottom, #27e3f8,#c8f4f7);
}

.placeholdersno {
  height: 150px;
  width: 150px;
}
</style>

Demo2 交叉观察者

<template>
  <div>
    <div v-for="(item,key) in imgs" ref="cards" :key="key" :data-index="key" :class="item.isOptimized == false ? 'placeholders' : 'placeholdersno'" >
      <img v-if="item.isOptimized" style="width:150px;" :src="item.src" alt="">
    </div>
  </div>
</template>
<script>

export default {
  data() {
    return {
      observer: null,
      imgs: [
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        },
        {
          src: 'https://image.suning.cn/uimg/ZR/share_order/159194414707857929.jpg',
          isOptimized: false
        }
      ]
    }
  },
  mounted() {
    var that = this;
    this.makeObserver().then((observer) => {
      if (that.$refs.cards) {
        that.$refs.cards.forEach(card => {
          observer.observe(card)
        })
      }
    })
  },
  methods: {
    // 观察对象
    makeObserver() {
      var that = this;
      that.observer = new IntersectionObserver(
        function(changes) {
          changes.forEach(function(change) {
            var container = change.target;
            // show
            var index = container.getAttribute("data-index");
            if (change.isIntersecting) {
              that.imgs[index].isOptimized = true;
            } else {
              that.imgs[index].isOptimized = false;
            }
          });
        }
      );
      return new Promise(resolve => {
        resolve(that.observer)
      })
    }
  }
}
</script>

<style scoped>
.placeholders {
  height: 150px;
  width: 150px;
  background: linear-gradient(to left bottom, #27e3f8,#c8f4f7);
}

.placeholdersno {
  height: 150px;
  width: 150px;
}
</style>