Angular2+ 使用 Protocol Buffers

just do it
最近在寫一個web IM 專案,雖然本人主打golang後端,但是部分前端還是需要自己解決。因為這是一個IM系統,所以不考慮使用json來傳送資料,改用protocol buffers ,優點見 官網 。由於前端不太熟練,經常被Angular坑,包括這次,花費我一個下午時間來解決Angular2+ 使用 Protocol Buffers的問題。
靈感來源:Using protocol buffers with Node.js + Swagger + Angular進入正題,本機環境 Linux ubuntu
1. 定義一個簡單的 .proto 檔案
具體的protocol buffers 的.proto檔案定義見官網,下面展現一個簡單的小例子:
// Protocol.proto syntax="proto3"; package Protocol; message C2CSendRequest{ int64 from = 1;//傳送者 int64 to = 2;//接受者 string content = 3 ;//訊息內容 }
2.生成 .js 和 .d.ts 檔案
首先使用npm命令 安裝 protobuf.js:
npm install protobufjs
然後你會得到 pbjs 和 pbts 這兩個命令 [注意下面!有坑!]
這兩個命令都在 node_modules/protobufjs/cli/bin/ 路徑底下(看清楚這路徑有 cli ),你如果不知道在哪裡的話
執行 sudo find / -name pbjs
得到下面的輸出
pjw@O-E-M:~$ sudo find / -name pbjs find: ‘/tmp/.mount_Shadowl3Qfnt’: 許可權不夠 /usr/local/node-v10.15.0-linux-x64/bin/pbjs /usr/local/node-v10.15.0-linux-x64/lib/node_modules/pbjs /home/pjw/node_modules/.bin/pbjs /home/pjw/node_modules/protobufjs/bin/pbjs /home/pjw/node_modules/protobufjs/cli/bin/pbjs<===就是這個目錄的 pbjs,不是別的目錄。(注意這個/home/pjw是我的家目錄目錄) /home/pjw/.local/share/Trash/files/protobuf.2.js/cli/bin/pbjs /home/pjw/.local/share/Trash/files/protobuf.js/bin/pbjs /home/pjw/.local/share/Trash/files/protobuf.js/cli/bin/pbjs
這目錄底下的兩個命令還是不可以執行的,在這個目錄底下執行:
sudo chmod a+x pbjs sudo chmod a+x pbts
先說一下我在這裡遇到的坑:如果剛執行完 npm install protobufjs ,馬上執行 pbjs 命令,這條命令是不正確的,並且你執行 pbts 會顯示找不到命令。而正確的命令是在 node_modules/protobufjs/cli/bin/ 底下。
正式開始生成.js 和 .d.ts 檔案:
../node_modules/protobufjs/cli/bin/pbjs -t static-module -w commonjs -o Protocol.js Protocol.proto ../node_modules/protobufjs/cli/bin/pbts -o Protocol.d.ts Protocol.js
分別生成 Protocol.js 和 Protocol.d.ts 。
這時候 Protocol.d.ts 檔案有些錯誤, Cannot find name 'Long' , 原因是 .proto 檔案有些欄位是 int64 型別 。
解決辦法 在Protocol.d.ts 檔案第二行插入 :
import {Long} from "protobufjs";
那麼現在 js和 .d.ts 宣告檔案也有了,那如何在Angular上使用。
3. protobuf 在Angular 簡單使用
簡單的測試一下是否可以再Angular上面使用,更復雜的使用待學習。
import { Injectable } from '@angular/core'; import { Protocol } from "./Protocol";<====引入模組 @Injectable() export class TestService { request: Protocol.C2CSendRequest = new(Protocol.C2CSendRequest);<=== new 一個 message constructor() { } test(){ this.request.content="dasdasdas";<===使用它 this.request.from=1; this.request.msgid=222; this.request.timestamp=231412; console.log(this.request) } }
4.總結
其實在這篇文章 靈感來源:Using protocol buffers with Node.js + Swagger + Angular 講的很詳細,是我大部分的借鑑來源。雖然一開始搜到這篇文章,但是因為是英文沒有詳細閱讀,而是隨意瀏覽,錯過這個解決辦法。幸好師姐幫我解決問題的時候,把這篇文章再看一遍,才發現裡面的解決方案。 其中一直困擾著我的問題就是: 為什麼我的 pbjs 生成的js檔案和別人的生成js檔案不一樣,即使是.proto檔案相同。最後是發現 是node_modules/protobufjs/cli/bin目錄底下的命令才是真正需要的命令。
5.補充
Protocol.js 檔案內容:
/*eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars*/ "use strict"; var $protobuf = require("protobufjs/minimal"); // Common aliases var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; // Exported root namespace var $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {}); $root.Protocol = (function() { /** * Namespace Protocol. * @exports Protocol * @namespace */ var Protocol = {}; Protocol.C2CSendRequest = (function() { /** * Properties of a C2CSendRequest. * @memberof Protocol * @interface IC2CSendRequest * @property {number|Long|null} [from] C2CSendRequest from * @property {number|Long|null} [to] C2CSendRequest to * @property {string|null} [content] C2CSendRequest content */ /** * Constructs a new C2CSendRequest. * @memberof Protocol * @classdesc Represents a C2CSendRequest. * @implements IC2CSendRequest * @constructor * @param {Protocol.IC2CSendRequest=} [properties] Properties to set */ function C2CSendRequest(properties) { if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) if (properties[keys[i]] != null) this[keys[i]] = properties[keys[i]]; } /** * C2CSendRequest from. * @member {number|Long} from * @memberof Protocol.C2CSendRequest * @instance */ C2CSendRequest.prototype.from = $util.Long ? $util.Long.fromBits(0,0,false) : 0; /** * C2CSendRequest to. * @member {number|Long} to * @memberof Protocol.C2CSendRequest * @instance */ C2CSendRequest.prototype.to = $util.Long ? $util.Long.fromBits(0,0,false) : 0; /** * C2CSendRequest content. * @member {string} content * @memberof Protocol.C2CSendRequest * @instance */ C2CSendRequest.prototype.content = ""; /** * Creates a new C2CSendRequest instance using the specified properties. * @function create * @memberof Protocol.C2CSendRequest * @static * @param {Protocol.IC2CSendRequest=} [properties] Properties to set * @returns {Protocol.C2CSendRequest} C2CSendRequest instance */ C2CSendRequest.create = function create(properties) { return new C2CSendRequest(properties); }; /** * Encodes the specified C2CSendRequest message. Does not implicitly {@link Protocol.C2CSendRequest.verify|verify} messages. * @function encode * @memberof Protocol.C2CSendRequest * @static * @param {Protocol.IC2CSendRequest} message C2CSendRequest message or plain object to encode * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ C2CSendRequest.encode = function encode(message, writer) { if (!writer) writer = $Writer.create(); if (message.from != null && message.hasOwnProperty("from")) writer.uint32(/* id 1, wireType 0 =*/8).int64(message.from); if (message.to != null && message.hasOwnProperty("to")) writer.uint32(/* id 2, wireType 0 =*/16).int64(message.to); if (message.content != null && message.hasOwnProperty("content")) writer.uint32(/* id 3, wireType 2 =*/26).string(message.content); return writer; }; /** * Encodes the specified C2CSendRequest message, length delimited. Does not implicitly {@link Protocol.C2CSendRequest.verify|verify} messages. * @function encodeDelimited * @memberof Protocol.C2CSendRequest * @static * @param {Protocol.IC2CSendRequest} message C2CSendRequest message or plain object to encode * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ C2CSendRequest.encodeDelimited = function encodeDelimited(message, writer) { return this.encode(message, writer).ldelim(); }; /** * Decodes a C2CSendRequest message from the specified reader or buffer. * @function decode * @memberof Protocol.C2CSendRequest * @static * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from * @param {number} [length] Message length if known beforehand * @returns {Protocol.C2CSendRequest} C2CSendRequest * @throws {Error} If the payload is not a reader or valid buffer * @throws {$protobuf.util.ProtocolError} If required fields are missing */ C2CSendRequest.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length, message = new $root.Protocol.C2CSendRequest(); while (reader.pos < end) { var tag = reader.uint32(); switch (tag >>> 3) { case 1: message.from = reader.int64(); break; case 2: message.to = reader.int64(); break; case 3: message.content = reader.string(); break; default: reader.skipType(tag & 7); break; } } return message; }; /** * Decodes a C2CSendRequest message from the specified reader or buffer, length delimited. * @function decodeDelimited * @memberof Protocol.C2CSendRequest * @static * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from * @returns {Protocol.C2CSendRequest} C2CSendRequest * @throws {Error} If the payload is not a reader or valid buffer * @throws {$protobuf.util.ProtocolError} If required fields are missing */ C2CSendRequest.decodeDelimited = function decodeDelimited(reader) { if (!(reader instanceof $Reader)) reader = new $Reader(reader); return this.decode(reader, reader.uint32()); }; /** * Verifies a C2CSendRequest message. * @function verify * @memberof Protocol.C2CSendRequest * @static * @param {Object.<string,*>} message Plain object to verify * @returns {string|null} `null` if valid, otherwise the reason why it is not */ C2CSendRequest.verify = function verify(message) { if (typeof message !== "object" || message === null) return "object expected"; if (message.from != null && message.hasOwnProperty("from")) if (!$util.isInteger(message.from) && !(message.from && $util.isInteger(message.from.low) && $util.isInteger(message.from.high))) return "from: integer|Long expected"; if (message.to != null && message.hasOwnProperty("to")) if (!$util.isInteger(message.to) && !(message.to && $util.isInteger(message.to.low) && $util.isInteger(message.to.high))) return "to: integer|Long expected"; if (message.content != null && message.hasOwnProperty("content")) if (!$util.isString(message.content)) return "content: string expected"; return null; }; /** * Creates a C2CSendRequest message from a plain object. Also converts values to their respective internal types. * @function fromObject * @memberof Protocol.C2CSendRequest * @static * @param {Object.<string,*>} object Plain object * @returns {Protocol.C2CSendRequest} C2CSendRequest */ C2CSendRequest.fromObject = function fromObject(object) { if (object instanceof $root.Protocol.C2CSendRequest) return object; var message = new $root.Protocol.C2CSendRequest(); if (object.from != null) if ($util.Long) (message.from = $util.Long.fromValue(object.from)).unsigned = false; else if (typeof object.from === "string") message.from = parseInt(object.from, 10); else if (typeof object.from === "number") message.from = object.from; else if (typeof object.from === "object") message.from = new $util.LongBits(object.from.low >>> 0, object.from.high >>> 0).toNumber(); if (object.to != null) if ($util.Long) (message.to = $util.Long.fromValue(object.to)).unsigned = false; else if (typeof object.to === "string") message.to = parseInt(object.to, 10); else if (typeof object.to === "number") message.to = object.to; else if (typeof object.to === "object") message.to = new $util.LongBits(object.to.low >>> 0, object.to.high >>> 0).toNumber(); if (object.content != null) message.content = String(object.content); return message; }; /** * Creates a plain object from a C2CSendRequest message. Also converts values to other types if specified. * @function toObject * @memberof Protocol.C2CSendRequest * @static * @param {Protocol.C2CSendRequest} message C2CSendRequest * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ C2CSendRequest.toObject = function toObject(message, options) { if (!options) options = {}; var object = {}; if (options.defaults) { if ($util.Long) { var long = new $util.Long(0, 0, false); object.from = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long; } else object.from = options.longs === String ? "0" : 0; if ($util.Long) { var long = new $util.Long(0, 0, false); object.to = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long; } else object.to = options.longs === String ? "0" : 0; object.content = ""; } if (message.from != null && message.hasOwnProperty("from")) if (typeof message.from === "number") object.from = options.longs === String ? String(message.from) : message.from; else object.from = options.longs === String ? $util.Long.prototype.toString.call(message.from) : options.longs === Number ? new $util.LongBits(message.from.low >>> 0, message.from.high >>> 0).toNumber() : message.from; if (message.to != null && message.hasOwnProperty("to")) if (typeof message.to === "number") object.to = options.longs === String ? String(message.to) : message.to; else object.to = options.longs === String ? $util.Long.prototype.toString.call(message.to) : options.longs === Number ? new $util.LongBits(message.to.low >>> 0, message.to.high >>> 0).toNumber() : message.to; if (message.content != null && message.hasOwnProperty("content")) object.content = message.content; return object; }; /** * Converts this C2CSendRequest to JSON. * @function toJSON * @memberof Protocol.C2CSendRequest * @instance * @returns {Object.<string,*>} JSON object */ C2CSendRequest.prototype.toJSON = function toJSON() { return this.constructor.toObject(this, $protobuf.util.toJSONOptions); }; return C2CSendRequest; })(); return Protocol; })(); module.exports = $root;
Protocol.d.ts檔案內容:
import * as $protobuf from "protobufjs"; import {Long} from "protobufjs"; /** Namespace Protocol. */ export namespace Protocol { /** Properties of a C2CSendRequest. */ interface IC2CSendRequest { /** C2CSendRequest from */ from?: (number|Long|null); /** C2CSendRequest to */ to?: (number|Long|null); /** C2CSendRequest content */ content?: (string|null); } /** Represents a C2CSendRequest. */ class C2CSendRequest implements IC2CSendRequest { /** * Constructs a new C2CSendRequest. * @param [properties] Properties to set */ constructor(properties?: Protocol.IC2CSendRequest); /** C2CSendRequest from. */ public from: (number|Long); /** C2CSendRequest to. */ public to: (number|Long); /** C2CSendRequest content. */ public content: string; /** * Creates a new C2CSendRequest instance using the specified properties. * @param [properties] Properties to set * @returns C2CSendRequest instance */ public static create(properties?: Protocol.IC2CSendRequest): Protocol.C2CSendRequest; /** * Encodes the specified C2CSendRequest message. Does not implicitly {@link Protocol.C2CSendRequest.verify|verify} messages. * @param message C2CSendRequest message or plain object to encode * @param [writer] Writer to encode to * @returns Writer */ public static encode(message: Protocol.IC2CSendRequest, writer?: $protobuf.Writer): $protobuf.Writer; /** * Encodes the specified C2CSendRequest message, length delimited. Does not implicitly {@link Protocol.C2CSendRequest.verify|verify} messages. * @param message C2CSendRequest message or plain object to encode * @param [writer] Writer to encode to * @returns Writer */ public static encodeDelimited(message: Protocol.IC2CSendRequest, writer?: $protobuf.Writer): $protobuf.Writer; /** * Decodes a C2CSendRequest message from the specified reader or buffer. * @param reader Reader or buffer to decode from * @param [length] Message length if known beforehand * @returns C2CSendRequest * @throws {Error} If the payload is not a reader or valid buffer * @throws {$protobuf.util.ProtocolError} If required fields are missing */ public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): Protocol.C2CSendRequest; /** * Decodes a C2CSendRequest message from the specified reader or buffer, length delimited. * @param reader Reader or buffer to decode from * @returns C2CSendRequest * @throws {Error} If the payload is not a reader or valid buffer * @throws {$protobuf.util.ProtocolError} If required fields are missing */ public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): Protocol.C2CSendRequest; /** * Verifies a C2CSendRequest message. * @param message Plain object to verify * @returns `null` if valid, otherwise the reason why it is not */ public static verify(message: { [k: string]: any }): (string|null); /** * Creates a C2CSendRequest message from a plain object. Also converts values to their respective internal types. * @param object Plain object * @returns C2CSendRequest */ public static fromObject(object: { [k: string]: any }): Protocol.C2CSendRequest; /** * Creates a plain object from a C2CSendRequest message. Also converts values to other types if specified. * @param message C2CSendRequest * @param [options] Conversion options * @returns Plain object */ public static toObject(message: Protocol.C2CSendRequest, options?: $protobuf.IConversionOptions): { [k: string]: any }; /** * Converts this C2CSendRequest to JSON. * @returns JSON object */ public toJSON(): { [k: string]: any }; } }