手机网站建设怎样,网络设计,西城h5网站建设,江山市城乡建设局网站介绍
Protocol Buffers and gRPC是用于定义通过网络有效通信的微服务的流行技术。许多公司在Go中构建gRPC微服务#xff0c;发布了他们开发的框架#xff0c;本文将从gRPC入门开始#xff0c;一步一步构建一个gRPC服务。
背景
之前在B站看过一个gRPC教学视频#xff0c;…介绍
Protocol Buffers and gRPC是用于定义通过网络有效通信的微服务的流行技术。许多公司在Go中构建gRPC微服务发布了他们开发的框架本文将从gRPC入门开始一步一步构建一个gRPC服务。
背景
之前在B站看过一个gRPC教学视频尝试跟着视频做但踩了不少的坑因此决定自己动手从官方教程开始完成一个gRPC项目。
开始
环境配置
首先要配置gRPC所需要的一些环境由于本人使用Go语言进行开发操作系统为Ubuntu20.04因此配置gRPC-go的环境步骤很简单。
安装Go
Ubuntu下安装Go需要先下载Go的源码本人采用的Go版本为1.18.3源码下载地址为Go语言中文网go1.18.3.linux-amd64.tar.gz。 下载完毕后首先检查机器是否存在旧版本的Go如果存在则删除然后解压源码到/usr/local。
rm -rf /usr/local/go tar -C /usr/local -xzf go1.18.3.linux-amd64.tar.gz添加/usr/local/go/bin到环境变量中可以在命令行中直接执行
export PATH$PATH:/usr/local/go/bin注意在命令行中执行上述语句只会在当前命令行环境下生效如果关闭命令行后再执行go命令会报错要解决这个问题需要将这个语句添加到$HOME/.profile或/etc/profile中并使用source命令生效 上述步骤完成后检查Go环境是否安装成功
go version输出相应版本号则代表环境配置成功。
go version go1.18.3 linux/amd64在这里配置Go的proxy为国内代理方便之后下载安装package时网速问题由于安装的Go是1.13及以上的版本因此直接执行以下命令。
go env -w GO111MODULEon
go env -w GOPROXYhttps://goproxy.cn,direct安装Protocol buffer compiler
Ubuntu上使用apt或者apt-get安装Protocol buffer compiler命令如下
sudo apt install -y protobuf-compiler检查是否安装成功
protoc --version # Ensure compiler version is 3输出相应版本号则代表环境配置成功。
libprotoc 3.6.1配置Go plugins
在配置Go plugins时遇到了很多错误。
--go_out: protoc-gen-go: plugins are not supported; use protoc --go-grpc_out... to generate gRPC或
protoc-gen-go-grpc: program not found or is not executable网上的解决方法也不一定奏效最后还是选择按照官网上的步骤安装对应版本的protoc-gen-go和protoc-gen-go-grpc。
go install google.golang.org/protobuf/cmd/protoc-gen-gov1.28
go install google.golang.org/grpc/cmd/protoc-gen-go-grpcv1.2注意这里都是从goole.golang.org下载的package 更新环境变量将下面的命令添加到$HOME/.profile或/etc/profile中source使之生效。
export PATH$PATH:$(go env GOPATH)/bin到此为之gRPC-go的环境就算配置完成了。
gRPC接口定义
.proto文件
第一步首先定义gRPC服务以及方法请求和响应类型。要定义服务请在.proto文件中指定命名服务
service NewService {rpc GetHotTopNews(Request) returns (News) {}
}然后在服务定义中定义RPC方法指定它们的请求和响应类型。gRPC允许您定义四种服务方法
一个简单的RPC其中客户端使用存根向服务端发送请求并等待响应返回就像正常的函数调用一样。
// Obtains the feature at a given position.
rpc GetFeature(Point) returns (Feature) {}服务端流式RPC客户端向服务端发送请求并获取流以读回一系列消息。客户端从返回的流中读取直到没有更多消息为止。
// Obtains the Features available within the given Rectangle. Results are
// streamed rather than returned at once (e.g. in a response message with a
// repeated field), as the rectangle may cover a large area and contain a
// huge number of features.
rpc ListFeatures(Rectangle) returns (stream Feature) {}客户端流式RPC其中客户端写入一系列消息并将它们发送到服务端再次使用提供的流。一旦客户端完成了消息的写入它会等待服务端读取所有消息并返回其响应。可以通过将stream关键字放在请求类型之前来指定客户端流式处理方法。
// Accepts a stream of Points on a route being traversed, returning a
// RouteSummary when traversal is completed.
rpc RecordRoute(stream Point) returns (RouteSummary) {}双向流式RPC双方使用读写流发送一系列消息。这两个流独立运行因此客户端和服务端可以按照他们喜欢的任何顺序读取和写入例如服务端可以在写入响应之前等待接收所有客户端消息或者它可以交替读取消息然后写入消息或其他一些读取和写入的组合保留每个流中消息的顺序。可以通过在请求和响应之前放置stream关键字来指定这种类型的方法。
// Accepts a stream of RouteNotes sent while a route is being traversed,
// while receiving other RouteNotes (e.g. from other users).
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}我们将要实现一个获取热点新闻的gRPC接口.proto文件包含服务方法中使用的所有请求和响应类型的协议缓冲区消息类型定义。例如这里是Request消息类型
message Request {string type 1;int64 page 2;int64 size 3;int64 is_filter 4;
}以及Response定义
message Response { repeated New news 1; }其中New的结构定义为
message New {string uniquekey 1;string title 2;string date 3;string category 4;string author_name 5;string url 6;string thumbnail_pic_s 7;int64 is_content 8;
}最后定义RPC接口
syntax proto3;option go_package ./;protobuf;package protobuf;service NewService {rpc GetHotTopNews(Request) returns (Response) {}
}注意这里加上了option go_package “./;protobuf”;说明生成的pb.go的package名称。
protoc命令
接下来我们需要从.proto服务定义中生成gRPC客户端和服务端接口。我们使用带有特殊gRPC Go插件的protobuf compiler来执行此操作。
protoc --go_out. --go_optpathssource_relative --go-grpc_out. --go-grpc_optpathssource_relative protobuf/*.proto会在.proto文件同级目录下生成以下go文件
news.pb.go其中包含用于填充、序列化和检索请求和响应消息类型的所有协议缓冲区代码news_grpc.pb.go包含1客户端使用服务中定义的方法调用的接口类型或存根2服务端要实现的接口类型也使用服务中定义的方法。
这里我使用VS code进行开发在编写.proto文件时推荐使用两个插件
vscode-proto3用于识别.proto文件的一些语法clang-format用于格式化.proto文件需要使用sudo apt install clang-format并且按照插件说明进行相应配置
Go服务构建
server
服务端需要实现gRPC的接口首先定义一个结构体
type Server struct {protobuf.UnimplementedNewServiceServer
}继承了生成的pb.go文件中的UnimplementedNewServiceServer接着实现接口内的方法
func (s *Server) GetHotTopNews(ctx context.Context, req *protobuf.Request) (*protobuf.Response, error) {ret : srv.RequestPublishAPI()return protobuf.Response{News: ret,}, nil
}这样最基本的gRPC服务就能启动了。
func main() {// register grpc services : grpc.NewServer()protobuf.RegisterNewServiceServer(s, Server{})// listen tcp connectionflag.Parse()lis, err : net.Listen(tcp, fmt.Sprintf(:%d, *port))if err ! nil {log.Fatalf(failed to listen: %v, err)}// start grpc serverlog.Printf(server listening at %v, lis.Addr())if err : s.Serve(lis); err ! nil {log.Fatalf(failed to serve: %v, err)}
}client
同样的我们用go编写一个客户端来请求测试gRPC服务是否能工作。
var (addr flag.String(addr, localhost:50051, the address to connect to)
)func main() {flag.Parse()// Set up a connection to the server.conn, err : grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))if err ! nil {log.Fatalf(did not connect: %v, err)}defer conn.Close()c : protobuf.NewNewServiceClient(conn)// Contact the server and print out its response.ctx, cancel : context.WithTimeout(context.Background(), time.Second)defer cancel()r, err : c.GetHotTopNews(ctx, protobuf.Request{})if err ! nil {log.Fatalf(could not greet: %v, err)}for _, v : range r.GetNews() {fmt.Println(v)}
}至此一个简单的gRPC服务就已经全部完成了但我们获取热点新闻的接口是伪造的因此我们加入免费的获取热点新闻API到项目中让客户端有实际返回API主要逻辑如下
// NewService contains services that fetch new and convert to grpc protobuf
type NewService struct {apiUri stringapiKey stringreqType stringpage intsize intisFilter int
}func (s *NewService) RequestPublishAPI() []*protobuf.New {reqUrl : fmt.Sprintf(%s?type%spage%dpage_size%dis_filter%dkey%s, s.apiUri, s.reqType, s.page, s.size, s.isFilter, s.apiKey)log.Printf(request url: %s, reqUrl)method : GETclient : http.Client{}req, err : http.NewRequest(method, reqUrl, nil)if err ! nil {panic(err)}res, err : client.Do(req)if err ! nil {panic(err)}defer res.Body.Close()body, err : ioutil.ReadAll(res.Body)if err ! nil {panic(err)}var resp ApiResponseerr json.Unmarshal(body, resp)if err ! nil {panic(err)}var ret []*protobuf.Newfor _, n : range resp.Result.Data {isContent, _ : strconv.Atoi(n.IsContent)ret append(ret, protobuf.New{Uniquekey: n.Uniquekey,Title: n.Title,Date: n.Date,Category: n.Category,AuthorName: n.AuthorName,Url: n.Url,ThumbnailPicS: n.ThumbnailPicS,IsContent: int64(isContent),})}return ret
}Test
我们来看一下目前的测试效果首先启动gRPC服务
cd cmd/server go build -o server . ./server输出结果如下则表示正常启动。
2022/07/08 22:56:19 server listening at [::]:50051然后启动客户端来发送gRPC请求
cd cmd/client go build -o client . ./client可以看到会如同客户端程序逻辑预期输出了热点新闻
uniquekey:e36249942bd61b566293a0f658a70861 title:醉酒乘客“遗失”巨额财物原来竟是…… date:2022-07-08 22:28:00 category:头条 author_name:每日新闻汇 url:https://mini.eastday.com/mobile/220708222828059733118.html thumbnail_pic_s:https://dfzximg02.dftoutiao.com/news/20220708/20220708222828_7250d5750196c6ca896094cf9e9b7910_1_mwpm_03201609.jpeg is_content:1
uniquekey:d0b9d2392e764b05be7fc3903ae8cf0e title:上海药房严守防疫阵地按防疫要求销售发烧感冒药 date:2022-07-08 22:28:00 category:头条 author_name:上观新闻供稿人民资讯 url:https://mini.eastday.com/mobile/220708222828022564952.html thumbnail_pic_s:https://dfzximg02.dftoutiao.com/news/20220708/20220708222828_59a73fae2c240c9d4dc56877af1cf021_1_mwpm_03201609.jpeg is_content:1
uniquekey:22d3605020cdcd1b3e36389812d9f57f title:新疆有个县城却迎来了第一座属于自己的机场看看吧 date:2022-07-08 22:27:00 category:头条 author_name:笑谈社会现象 url:https://mini.eastday.com/mobile/220708222748804215251.html thumbnail_pic_s:https://dfzximg02.dftoutiao.com/minimodify/20220708/640x376_62c83ee45b71b_mwpm_03201609.jpeg is_content:1
uniquekey:ee7520b15386bb24835556621135b7c7 title:长沙一辆保时捷越野车突发自燃如何避免夏季车辆“上火” date:2022-07-08 22:27:00 category:头条 author_name:长沙晚报供稿人民资讯 url:https://mini.eastday.com/mobile/220708222722680289302.html thumbnail_pic_s:https://dfzximg02.dftoutiao.com/news/20220708/20220708222722_20a12760617fdaf73ba22cbeaae5a670_1_mwpm_03201609.jpeg is_content:1
uniquekey:5b3346570ca64b911934c9c4c958150f title:知名品牌婴儿水育加盟店人去楼空 宝妈们遭遇退费难 date:2022-07-08 22:27:00 category:头条 author_name:长沙晚报供稿人民资讯 url:https://mini.eastday.com/mobile/220708222722516745726.html thumbnail_pic_s:https://dfzximg02.dftoutiao.com/news/20220708/20220708222722_476ac09f92bc5047938cbeecdef5a293_1_mwpm_03201609.jpeg is_content:1
uniquekey:4b47df2a78934af1cacaf6fac844579b title:图说│惊险面包车撞树驾驶员被困消防员“钳”来解救 date:2022-07-08 22:26:00 category:头条 author_name:文汇报供稿人民资讯 url:https://mini.eastday.com/mobile/220708222616778303564.html thumbnail_pic_s:https://dfzximg02.dftoutiao.com/news/20220708/20220708222616_07827127554548d4dd870205b517fda5_1_mwpm_03201609.jpeg is_content:1
uniquekey:9beb3c60231daa82a18c03bbad43280c title:6家经营户限期整改青岛对“雪糕刺客”出手了 date:2022-07-08 22:25:00 category:头条 author_name:半岛都市报供稿人民资讯 url:https://mini.eastday.com/mobile/220708222514489900651.html thumbnail_pic_s:https://dfzximg02.dftoutiao.com/news/20220708/20220708222514_c973a3b8b0ab7308158acf353cc32afa_1_mwpm_03201609.jpeg is_content:1
uniquekey:0849aacfb2488478bd2a9147ff6d70c2 title:大陆台企积极呼应“双碳”战略 date:2022-07-08 22:24:00 category:头条 author_name:新华网供稿人民资讯 url:https://mini.eastday.com/mobile/220708222407637690082.html is_content:1
uniquekey:d1a5bed91210467f0536fa1a77dfbf3a title:质量问题受关注!上半年四川消委组织受理相关投诉案件10277件 date:2022-07-08 22:23:00 category:头条 author_name:川观新闻供稿人民资讯 url:https://mini.eastday.com/mobile/220708222331274896200.html is_content:1
uniquekey:98161b2c5703e64a5881a3b1e778a04a title:三河7月9日将在重点区域开展免费核酸检测服务 date:2022-07-08 22:20:00 category:头条 author_name:岛民观察 url:https://mini.eastday.com/mobile/220708222048455355795.html thumbnail_pic_s:https://dfzximg02.dftoutiao.com/minimodify/20220708/1080x593_62c83d400941c_mwpm_03201609.jpeg is_content:1
可视化展示
gRPC的特性之一就是跨平台跨语言通信因此我们可以使用一个简单的react工程来做前端的可视化展示。
准备工作
在确保nodejs以及npm命令可以使用后使用create-react-app来创建react工程
npx create-react-app web现在就像我们之前为Go所做的那样我们需要为Javascript生成客户端和服务端代码。为此可以再次使用我们的news.proto 文件。在web/src目录中创建一个名为newspb/protobuf的目录来存储我们生成的文件。但是由于我们的客户端将是浏览器客户端所以我们将不得不使用grpc-web。 大多数现代浏览器尚未支持HTTP/2。由于gRPC使用HTTP/2因此需要grpc-web让浏览器客户端与gRPC服务器通信。grpc-web允许HTTP/1与Envoy等代理一起使用这有助于将HTTP/1转换为HTTP/2。 确保已安装protoc-gen-grpc-web插件 → github.com/grpc/grpc-w…运行以下命令生成相应code
protoc protobuf/*.proto --js_outimport_stylecommonjs:./web/src/newspb --grpc-web_outimport_stylecommonjs,modegrpcwebtext:./web/src/newspb在web/src/newspb/protobuf下可以找到生成的news_pb.js和news_grpc_web_pb.js
设置envoy
新建envoy.yaml配置如下
admin:access_log_path: /tmp/admin_access.logaddress:socket_address: { address: 0.0.0.0, port_value: 9000 }static_resources:listeners:- name: listener_0address:socket_address: { address: 0.0.0.0, port_value: 8000 }filter_chains:- filters:- name: envoy.http_connection_managerconfig:codec_type: autostat_prefix: ingress_httproute_config:name: local_routevirtual_hosts:- name: local_servicedomains: [*]routes:- match: { prefix: / }route:cluster: news_servicemax_grpc_timeout: 0scors:allow_origin:- *allow_methods: GET, PUT, DELETE, POST, OPTIONSallow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeoutmax_age: 1728000expose_headers: custom-header-1,grpc-status,grpc-messagehttp_filters:- name: envoy.grpc_web- name: envoy.cors- name: envoy.routerclusters:- name: news_serviceconnect_timeout: 50stype: logical_dnshttp2_protocol_options: {}lb_policy: round_robinhosts: [{ socket_address: { address: 172.17.0.1, port_value: 50051 }}]
其中在clusters的配置中hosts指向后端服务的地址因此端口号为50051envoy.yaml文件中实质上是在要求Envoy在端口8000上运行一个监听器来监听下游流量。 然后将任何到达它的流量引导到news_service这是在端口 0051上运行的 gRPC 服务器
完成配置后新建Dockerfile
FROM envoyproxy/envoy:v1.12.2COPY ./envoy/envoy.yaml /etc/envoy/envoy.yamlCMD /usr/local/bin/envoy -c /etc/envoy/envoy.yaml打包一个docker镜像
docker build -t grpc-starter-envoy:1.0 .然后运行
docker run --networkhost grpc-starter-envoy:1.0这样我们的envoy代理就设置完成了。
完善react项目
首先添加一些依赖
npm install grpc-web --save
npm install google-protobuf --save我们在web/src/App.js中实现react项目的所有逻辑
import { Request } from ./newspb/protobuf/news_pb;
import { NewServiceClient } from ./newspb/protobuf/news_grpc_web_pb;首先分别导入Request和NewServiceClient来发送请求和生成客户端。
var client new NewServiceClient(http://localhost:8000, null, null);核心请求逻辑
var request new Request();
client.getHotTopNews(request, {}, (error, reply) {if (!error) {console.log(reply.getNewsList());} else {console.log(error);}
});当请求成功时JavaScript的控制台将会打印出热点新闻列表。我们接着可以加入一些UI框架来美化展示这里选用最流行的material ui框架集成到项目中。
npm install mui/material emotion/react emotion/styled在web/src/App.js中添加以下代码
import React, { useEffect } from react;
import { Request } from ./newspb/protobuf/news_pb;
import { NewServiceClient } from ./newspb/protobuf/news_grpc_web_pb;
import Box from mui/material/Box;
import Container from mui/material/Container;
import InputLabel from mui/material/InputLabel;
import MenuItem from mui/material/MenuItem;
import FormControl from mui/material/FormControl;
import Select from mui/material/Select;
import TextField from mui/material/TextField;
import Grid from mui/material/Grid;
import Avatar from mui/material/Avatar;
import Typography from mui/material/Typography;
import Card from mui/material/Card;
import CardHeader from mui/material/CardHeader;
import CardMedia from mui/material/CardMedia;
import CardContent from mui/material/CardContent;
import CardActions from mui/material/CardActions;
import Link from mui/material/Link;
import { red } from mui/material/colors;
import NotFound from ./notfound.gif;var client new NewServiceClient(http://localhost:8000, null, null);function App() {const [newsList, setNewsList] React.useState([]);const getHotNews () {var request new Request();client.getHotTopNews(request, {}, (error, reply) {if (!error) {setNewsList(reply.getNewsList());} else {console.log(error);}});};useEffect(() {getHotNews();}, []);return (ContainerBoxFormControl sx{{ m: 1, minWidth: 120 }}InputLabel htmlFortype-selectType/InputLabelSelect defaultValuetop idtype-select labelTypeMenuItem value{top}默认/MenuItemMenuItem value{guonei}国内/MenuItemMenuItem value{guoji}国际/MenuItemMenuItem value{yule}娱乐/MenuItemMenuItem value{tiyu}体育/MenuItemMenuItem value{junshi}军事/MenuItemMenuItem value{keji}科技/MenuItemMenuItem value{caijing}财经/MenuItemMenuItem value{youxi}游戏/MenuItemMenuItem value{qiche}汽车/MenuItemMenuItem value{jiankang}健康/MenuItem/Select/FormControlFormControl sx{{ m: 1, minWidth: 120 }}TextField idpage-select labelPage variantoutlined //FormControlFormControl sx{{ m: 1, minWidth: 120 }}InputLabel htmlForsize-selectSize/InputLabelSelect defaultValue5 idsize-select labelSizeMenuItem value{5}5/MenuItemMenuItem value{10}10/MenuItemMenuItem value{20}20/MenuItemMenuItem value{30}30/MenuItem/Select/FormControl/BoxBoxGridcontainerspacing{{ xs: 2, md: 3 }}columns{{ xs: 4, sm: 8, md: 12 }}{newsList.map((value, index) (Grid item xs{2} sm{4} md{4}CardCardHeaderavatar{Avatar sx{{ bgcolor: red[500] }} aria-labelrecipe{value.array[4][0]}/Avatar}title{value.array[4] value.array[3]}subheader{value.array[2]}/{value.array[6] null ||value.array[6] undefined ||value.array[6] ? (CardMediacomponentimgheight194image{NotFound}altNews cover/) : (CardMediacomponentimgheight194image{value.array[6]}altNews cover/)}CardContentTypography variantbody2 colortext.secondary{value.array[1]}/Typography/CardContentCardActionsLinkhref{value.array[5]}underlinenonetarget_blankrelnoopener原文链接/Link/CardActions/Card/Grid))}/Grid/Box/Container);
}export default App;展示效果 最后解决request参数问题对服务端的改动如下首先修改gRPC的server实现方法。
cmd/server/main.go
func (s *Server) GetHotTopNews(ctx context.Context, req *protobuf.Request) (*protobuf.Response, error) {// 加入req参数ret : srv.RequestPublishAPI(req)return protobuf.Response{News: ret,}, nil
}修改发送请求到公共API的逻辑
service/news.go
func (s *NewService) RequestPublishAPI(request *protobuf.Request) []*protobuf.New {// check request paramif request.GetType() ! {s.reqType request.GetType()}if request.GetPage() ! 0 {s.page int(request.GetPage())}if request.GetSize() ! 0 {s.size int(request.GetSize())}...
}在web/src/App.js中加入相关事件处理函数。
import React, { useEffect } from react;
import { Request } from ./newspb/protobuf/news_pb;
import { NewServiceClient } from ./newspb/protobuf/news_grpc_web_pb;
import Box from mui/material/Box;
import Container from mui/material/Container;
import InputLabel from mui/material/InputLabel;
import MenuItem from mui/material/MenuItem;
import FormControl from mui/material/FormControl;
import Select from mui/material/Select;
import TextField from mui/material/TextField;
import Grid from mui/material/Grid;
import Avatar from mui/material/Avatar;
import Typography from mui/material/Typography;
import Card from mui/material/Card;
import CardHeader from mui/material/CardHeader;
import CardMedia from mui/material/CardMedia;
import CardContent from mui/material/CardContent;
import CardActions from mui/material/CardActions;
import Link from mui/material/Link;
import { red } from mui/material/colors;
import NotFound from ./notfound.gif;var client new NewServiceClient(http://localhost:8000, null, null);function App() {const [newsList, setNewsList] React.useState([]);const [type, setType] React.useState(top);const [page, setPage] React.useState(1);const [size, setSize] React.useState(10);const handleTypeChange (event) {setType(event.target.value);console.log(event.target.value);getHotNews(event.target.value, page, size);};const handleSizeChange (event) {setSize(event.target.value);console.log(event.target.value);getHotNews(type, page, event.target.value);};const handlePageChange (event) {setPage(event.target.value);console.log(event.target.value);getHotNews(type, event.target.value, size);};const getHotNews (type, page, size) {console.log(type, page, size);var request new Request();request.setType(type);request.setPage(page);request.setSize(size);client.getHotTopNews(request, {}, (error, reply) {if (!error) {setNewsList(reply.getNewsList());} else {console.log(error);}});};useEffect(() {getHotNews(type, page, size);}, [type, page, size]);return (ContainerBoxFormControl sx{{ m: 1, minWidth: 120 }}InputLabel htmlFortype-selectType/InputLabelSelectdefaultValuetopidtype-selectlabelTypevalue{type}onChange{handleTypeChange}MenuItem value{top}默认/MenuItemMenuItem value{guonei}国内/MenuItemMenuItem value{guoji}国际/MenuItemMenuItem value{yule}娱乐/MenuItemMenuItem value{tiyu}体育/MenuItemMenuItem value{junshi}军事/MenuItemMenuItem value{keji}科技/MenuItemMenuItem value{caijing}财经/MenuItemMenuItem value{youxi}游戏/MenuItemMenuItem value{qiche}汽车/MenuItemMenuItem value{jiankang}健康/MenuItem/Select/FormControlFormControl sx{{ m: 1, minWidth: 120 }}TextFieldidpage-selectlabelPagevariantoutlinedvalue{page}onChange{handlePageChange}//FormControlFormControl sx{{ m: 1, minWidth: 120 }}InputLabel htmlForsize-selectSize/InputLabelSelectdefaultValue5idsize-selectlabelSizevalue{size}onChange{handleSizeChange}MenuItem value{5}5/MenuItemMenuItem value{10}10/MenuItemMenuItem value{20}20/MenuItemMenuItem value{30}30/MenuItem/Select/FormControl/BoxBoxGridcontainerspacing{{ xs: 2, md: 3 }}columns{{ xs: 4, sm: 8, md: 12 }}{newsList.map((value, index) (Grid item xs{2} sm{4} md{4}CardCardHeaderavatar{Avatar sx{{ bgcolor: red[500] }} aria-labelrecipe{value.array[4][0]}/Avatar}title{value.array[4] value.array[3]}subheader{value.array[2]}/{value.array[6] null ||value.array[6] undefined ||value.array[6] ? (CardMediacomponentimgheight194image{NotFound}altNews cover/) : (CardMediacomponentimgheight194image{value.array[6]}altNews cover/)}CardContentTypography variantbody2 colortext.secondary{value.array[1]}/Typography/CardContentCardActionsLinkhref{value.array[5]}underlinenonetarget_blankrelnoopener原文链接/Link/CardActions/Card/Grid))}/Grid/Box/Container);
}export default App;Dockerlize
我们将build三个docker镜像分别提供go-grpc-server、envoy代理以及react-web服务因此在项目根目录下新建docker-compose.yaml文件
version: 3
services:proxy:build:context: ./envoydockerfile: Dockerfileports:- 8000:8000go-grpc-server:build:context: .dockerfile: Dockerfileports:- 50051:50051depends_on:- proxyweb-client:build: context: ./webdockerfile: Dockerfileports:- 3000:80depends_on:- go-grpc-server- proxytty: true
envoy的Dockerfile之前已经有过介绍这里将之前的Dockerfile移到envoy目录下路径稍微修改
envoy/Dockerfile
FROM envoyproxy/envoy:v1.12.2COPY ./envoy.yaml /etc/envoy/envoy.yamlCMD /usr/local/bin/envoy -c /etc/envoy/envoy.yaml
在web目录下新建Dockerfile提供react-web镜像。
web/Dockerfile
FROM node:16.15.1-alpine as build
WORKDIR /app
ENV PATH /app/node_modules/.bin:$PATH
COPY package.json ./
COPY package-lock.json ./
RUN npm ci --silent
RUN npm install react-scripts5.0.1 -g --silent
COPY . ./
RUN npm run buildFROM nginx:stable-alpine
COPY --frombuild /app/build /usr/share/nginx/html
EXPOSE 80
CMD [nginx, -g, daemon off;]
最后在项目根目录下新建Dockerfile提供gRPC服务。
FROM golang:1.18-alpineENV GO111MODULEon \GOPROXYhttps://goproxy.cn,directWORKDIR $GOPATH/src/github.com/surzia/grpc-starterCOPY . .
RUN go mod downloadRUN go build -o server .EXPOSE 50051CMD [ ./server ]
编译docker-compose.yaml为镜像
docker compose build运行整个项目
docker compose up -d
项目启动后打开浏览器输入http://localhost:3000 即可访问