Compare commits
16 Commits
Author | SHA1 | Date | |
---|---|---|---|
8a4b8e69bc | |||
d37d099619 | |||
e3a6e821d9 | |||
bf80400b7a | |||
4a0ed93617 | |||
afa7743e82 | |||
a6725de634 | |||
47b735fea1 | |||
078be130d7 | |||
da07118860 | |||
3e17213c8c | |||
56caa73209 | |||
55bd699475 | |||
7eae087cf4 | |||
89fc3ecf74 | |||
78990c6094 |
10
.vscode/settings.json
vendored
10
.vscode/settings.json
vendored
@ -4,5 +4,13 @@
|
||||
"CONFIG_PATH": "${workspaceFolder}/application.yaml"
|
||||
},
|
||||
"go.testEnvFile": "${workspaceFolder}/etc/unit_test.env",
|
||||
"terminal.integrated.suggest.enabled": true
|
||||
"terminal.integrated.suggest.enabled": true,
|
||||
"protoc": {
|
||||
"path": "/usr/local/bin/protoc",
|
||||
"compile_on_save": false,
|
||||
"options": [
|
||||
"--proto_path=.",
|
||||
"--proto_path=/usr/local/include",
|
||||
]
|
||||
}
|
||||
}
|
@ -28,8 +28,10 @@ func (c *consumer) Run(ctx context.Context) error {
|
||||
// 发送的数据时Json格式, 接收用的JSON, 发送也需要使用JSON
|
||||
err = e.Load(m.Value)
|
||||
if err == nil {
|
||||
// 保存日志, 保持失败的次数统计起来,披露给外部, 编写一个采集器,来采统计失败次数
|
||||
if err := event.GetService().SaveEvent(ctx, types.NewSet[*event.Event]().Add(e)); err != nil {
|
||||
c.log.Error().Msgf("save event error, %s", err)
|
||||
c.collector.Inc()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"122.51.31.227/go-course/go18/devcloud/audit/apps/event"
|
||||
"github.com/infraboard/mcube/v2/ioc"
|
||||
"github.com/infraboard/mcube/v2/ioc/config/log"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
ioc_kafka "github.com/infraboard/mcube/v2/ioc/config/kafka"
|
||||
@ -37,6 +38,9 @@ type consumer struct {
|
||||
GroupId string `toml:"group_id" json:"group_id" yaml:"group_id" env:"GROUP_ID"`
|
||||
// 当前这个消费者 配置的topic
|
||||
Topics []string `toml:"topic" json:"topic" yaml:"topic" env:"TOPIC"`
|
||||
|
||||
// 采集器
|
||||
collector *EventCollector
|
||||
}
|
||||
|
||||
// 对象名称
|
||||
@ -48,6 +52,11 @@ func (i *consumer) Name() string {
|
||||
func (i *consumer) Init() error {
|
||||
// 对象
|
||||
i.log = log.Sub(i.Name())
|
||||
|
||||
// 准备好采集器, 注册给Prometheus
|
||||
i.collector = NewEventCollector()
|
||||
prometheus.MustRegister(i.collector)
|
||||
|
||||
i.reader = ioc_kafka.ConsumerGroup(i.GroupId, i.Topics)
|
||||
|
||||
go i.Run(i.ctx)
|
||||
|
38
devcloud/audit/apps/event/consumer/metric.go
Normal file
38
devcloud/audit/apps/event/consumer/metric.go
Normal file
@ -0,0 +1,38 @@
|
||||
package consumer
|
||||
|
||||
import "github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
// # HELP save_event_error_count 事件入库失败个数统计
|
||||
// # TYPE save_event_error_count gauge
|
||||
// save_event_error_count{service="devcloud"} 0
|
||||
func NewEventCollector() *EventCollector {
|
||||
return &EventCollector{
|
||||
errCountDesc: prometheus.NewDesc(
|
||||
"save_event_error_count",
|
||||
"事件入库失败个数统计",
|
||||
[]string{},
|
||||
prometheus.Labels{"service": "devcloud"},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// 收集事件指标的采集器
|
||||
type EventCollector struct {
|
||||
errCountDesc *prometheus.Desc
|
||||
// 需要自己根据实践情况来维护这个变量
|
||||
errCount int
|
||||
}
|
||||
|
||||
func (c *EventCollector) Inc() {
|
||||
c.errCount++
|
||||
}
|
||||
|
||||
// 指标元数据注册
|
||||
func (c *EventCollector) Describe(ch chan<- *prometheus.Desc) {
|
||||
ch <- c.errCountDesc
|
||||
}
|
||||
|
||||
// 指标的值的采集
|
||||
func (c *EventCollector) Collect(ch chan<- prometheus.Metric) {
|
||||
ch <- prometheus.MustNewConstMetric(c.errCountDesc, prometheus.GaugeValue, float64(c.errCount))
|
||||
}
|
57
devcloud/mcenter/apps/label/api/api.go
Normal file
57
devcloud/mcenter/apps/label/api/api.go
Normal file
@ -0,0 +1,57 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"122.51.31.227/go-course/go18/devcloud/audit/audit"
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/label"
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/permission"
|
||||
"github.com/infraboard/mcube/v2/ioc"
|
||||
"github.com/infraboard/mcube/v2/ioc/config/gorestful"
|
||||
|
||||
restfulspec "github.com/emicklei/go-restful-openapi/v2"
|
||||
"github.com/emicklei/go-restful/v3"
|
||||
)
|
||||
|
||||
func init() {
|
||||
ioc.Api().Registry(&LabelRestfulApiHandler{})
|
||||
}
|
||||
|
||||
type LabelRestfulApiHandler struct {
|
||||
ioc.ObjectImpl
|
||||
|
||||
// 依赖控制器
|
||||
svc label.Service
|
||||
}
|
||||
|
||||
func (h *LabelRestfulApiHandler) Name() string {
|
||||
return "labels"
|
||||
}
|
||||
|
||||
func (h *LabelRestfulApiHandler) Init() error {
|
||||
h.svc = label.GetService()
|
||||
|
||||
tags := []string{"标签管理"}
|
||||
ws := gorestful.ObjectRouter(h)
|
||||
// required_auth=true/false
|
||||
ws.Route(ws.GET("").To(h.QueryLabel).
|
||||
Doc("标签列表查询").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
// 这个开关怎么生效
|
||||
// 中间件需求读取接口的描述信息,来决定是否需要认证
|
||||
Metadata(permission.Auth(true)).
|
||||
Metadata(permission.Permission(false)).
|
||||
Metadata(permission.Resource("labels")).
|
||||
Metadata(permission.Action("list")).
|
||||
Metadata(audit.Enable(true)).
|
||||
Param(restful.QueryParameter("page_size", "分页大小").DataType("integer")).
|
||||
Param(restful.QueryParameter("page_number", "页码").DataType("integer")).
|
||||
Writes(Set{}).
|
||||
Returns(200, "OK", Set{}))
|
||||
return nil
|
||||
}
|
||||
|
||||
// *types.Set[*Label]
|
||||
// 返回的泛型, API Doc这个工具 不支持泛型
|
||||
type Set struct {
|
||||
Total int64 `json:"total"`
|
||||
Items []label.Label `json:"items"`
|
||||
}
|
32
devcloud/mcenter/apps/label/api/label.go
Normal file
32
devcloud/mcenter/apps/label/api/label.go
Normal file
@ -0,0 +1,32 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/label"
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/token"
|
||||
"github.com/emicklei/go-restful/v3"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/infraboard/mcube/v2/http/restful/response"
|
||||
"github.com/infraboard/mcube/v2/ioc/config/log"
|
||||
)
|
||||
|
||||
func (h *LabelRestfulApiHandler) QueryLabel(r *restful.Request, w *restful.Response) {
|
||||
req := label.NewQueryLabelRequest()
|
||||
|
||||
// 过滤条件在认证完成后的上下文中
|
||||
tk := token.GetTokenFromCtx(r.Request.Context())
|
||||
log.L().Debug().Msgf("resource scope: %s", tk.ResourceScope)
|
||||
|
||||
// 用户的参数
|
||||
if err := binding.Query.Bind(r.Request, &req); err != nil {
|
||||
response.Failed(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
set, err := h.svc.QueryLabel(r.Request.Context(), req)
|
||||
if err != nil {
|
||||
response.Failed(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(w, set)
|
||||
}
|
@ -30,6 +30,11 @@ func (i *LabelServiceImpl) QueryLabel(ctx context.Context, in *label.QueryLabelR
|
||||
set := types.New[*label.Label]()
|
||||
|
||||
query := datasource.DBFromCtx(ctx).Model(&label.Label{})
|
||||
|
||||
if in.Key != "" {
|
||||
query = query.Where("`key` = ?", in.Key)
|
||||
}
|
||||
|
||||
err := query.Count(&set.Total).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -29,6 +29,7 @@ func TestCreateLabel(t *testing.T) {
|
||||
|
||||
func TestQueryLabel(t *testing.T) {
|
||||
req := label.NewQueryLabelRequest()
|
||||
req.Key = "team"
|
||||
set, err := svc.QueryLabel(ctx, req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -49,6 +49,7 @@ func NewQueryLabelRequest() *QueryLabelRequest {
|
||||
|
||||
type QueryLabelRequest struct {
|
||||
*request.PageRequest
|
||||
Key string `json:"key" form:"key"`
|
||||
}
|
||||
|
||||
type DescribeLabelRequest struct {
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
|
||||
// 鉴权
|
||||
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/endpoint/impl"
|
||||
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/label/api"
|
||||
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/label/impl"
|
||||
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/namespace/impl"
|
||||
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/policy/impl"
|
||||
|
@ -31,6 +31,18 @@ func (h *UserRestfulApiHandler) Init() error {
|
||||
|
||||
tags := []string{"应用管理"}
|
||||
ws := gorestful.ObjectRouter(h)
|
||||
|
||||
ws.Route(ws.POST("").To(h.CreateApplication).
|
||||
Doc("应用创建").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Metadata(permission.Auth(true)).
|
||||
Metadata(permission.Permission(true)).
|
||||
Metadata(permission.Resource("application")).
|
||||
Metadata(permission.Action("create")).
|
||||
Metadata(audit.Enable(true)).
|
||||
Writes(application.Application{}).
|
||||
Returns(200, "OK", application.Application{}))
|
||||
|
||||
// required_auth=true/false
|
||||
ws.Route(ws.GET("").To(h.QueryApplication).
|
||||
Doc("应用列表查询").
|
||||
@ -47,6 +59,17 @@ func (h *UserRestfulApiHandler) Init() error {
|
||||
Writes(Set{}).
|
||||
Returns(200, "OK", Set{}))
|
||||
|
||||
ws.Route(ws.DELETE("/{id}").To(h.DeleteApplication).
|
||||
Doc("应用删除").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Metadata(permission.Auth(true)).
|
||||
Metadata(permission.Permission(true)).
|
||||
Metadata(permission.Resource("application")).
|
||||
Metadata(permission.Action("delete")).
|
||||
Metadata(audit.Enable(true)).
|
||||
Writes().
|
||||
Returns(200, "OK", nil))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -31,3 +31,41 @@ func (h *UserRestfulApiHandler) QueryApplication(r *restful.Request, w *restful.
|
||||
|
||||
response.Success(w, set)
|
||||
}
|
||||
|
||||
func (h *UserRestfulApiHandler) CreateApplication(r *restful.Request, w *restful.Response) {
|
||||
req := application.NewCreateApplicationRequest()
|
||||
if err := r.ReadEntity(req); err != nil {
|
||||
response.Failed(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 过滤条件在认证完成后的上下文中
|
||||
tk := token.GetTokenFromCtx(r.Request.Context())
|
||||
req.SetNamespaceId(*tk.NamespaceId)
|
||||
req.CreateBy = tk.UserName
|
||||
|
||||
set, err := h.svc.CreateApplication(r.Request.Context(), req)
|
||||
if err != nil {
|
||||
response.Failed(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(w, set)
|
||||
}
|
||||
|
||||
func (h *UserRestfulApiHandler) DeleteApplication(r *restful.Request, w *restful.Response) {
|
||||
req := application.NewDeleteApplicationRequest(r.PathParameter("id"))
|
||||
|
||||
// 过滤条件在认证完成后的上下文中
|
||||
tk := token.GetTokenFromCtx(r.Request.Context())
|
||||
req.ResourceScope = tk.ResourceScope
|
||||
log.L().Debug().Msgf("resource scope: %s", tk.ResourceScope)
|
||||
|
||||
set, err := h.svc.DeleteApplication(r.Request.Context(), req)
|
||||
if err != nil {
|
||||
response.Failed(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(w, set)
|
||||
}
|
||||
|
@ -40,6 +40,9 @@ func (i *ApplicationServiceImpl) QueryApplication(ctx context.Context, in *appli
|
||||
if in.Ready != nil {
|
||||
query = query.Where("ready = ?", *in.Ready)
|
||||
}
|
||||
if in.Keywords != "" {
|
||||
query = query.Where("name LIKE ?", "%"+in.Keywords+"%")
|
||||
}
|
||||
|
||||
err := query.Count(&set.Total).Error
|
||||
if err != nil {
|
||||
|
@ -29,6 +29,7 @@ func TestQueryApplication(t *testing.T) {
|
||||
// dev01.%
|
||||
req.SetNamespaceId(1)
|
||||
req.SetScope("team", []string{"dev01%"})
|
||||
req.Keywords = "dev01"
|
||||
// req.SetScope("env", []string{"prod"})
|
||||
ins, err := svc.QueryApplication(ctx, req)
|
||||
if err != nil {
|
||||
|
@ -46,11 +46,13 @@ type QueryApplicationRequest struct {
|
||||
type QueryApplicationRequestSpec struct {
|
||||
*request.PageRequest
|
||||
// 应用ID
|
||||
Id string `json:"id" bson:"_id"`
|
||||
Id string `json:"id" form:"id"`
|
||||
// 应用名称
|
||||
Name string `json:"name" bson:"name"`
|
||||
Name string `json:"name" form:"name"`
|
||||
// 应用是否就绪
|
||||
Ready *bool `json:"ready" bson:"ready"`
|
||||
Ready *bool `json:"ready" form:"ready"`
|
||||
// 关键字
|
||||
Keywords string `json:"keywords" form:"keywords"`
|
||||
}
|
||||
|
||||
type UpdateApplicationRequest struct {
|
||||
@ -60,10 +62,22 @@ type UpdateApplicationRequest struct {
|
||||
CreateApplicationSpec
|
||||
}
|
||||
|
||||
func NewDeleteApplicationRequest(appId string) *DeleteApplicationRequest {
|
||||
return &DeleteApplicationRequest{
|
||||
DescribeApplicationRequest: *NewDescribeApplicationRequest(appId),
|
||||
}
|
||||
}
|
||||
|
||||
type DeleteApplicationRequest struct {
|
||||
DescribeApplicationRequest
|
||||
}
|
||||
|
||||
func NewDescribeApplicationRequest(appId string) *DescribeApplicationRequest {
|
||||
return &DescribeApplicationRequest{
|
||||
Id: appId,
|
||||
}
|
||||
}
|
||||
|
||||
type DescribeApplicationRequest struct {
|
||||
policy.ResourceScope
|
||||
// 应用ID
|
||||
|
16
devcloud/mpaas/apps/k8s/README.md
Normal file
16
devcloud/mpaas/apps/k8s/README.md
Normal file
@ -0,0 +1,16 @@
|
||||
# k8s 集群管理
|
||||
|
||||
https://kubernetes.io/
|
||||
|
||||
|
||||
k8s UI工具: Rancher, KubeSphere, Dashbaord k8s 集群管理工具(运维人员)
|
||||
+ Deployment,StatfulSet, DaesonSet, Pod, Service, ...
|
||||
|
||||
我们要做的系统: 是做个 k8s的集群管理工具吗? 给软件研发人员使用的(应用的发布与部署)
|
||||
|
||||
依赖于K8s提供的能力,来实现一套发布系统
|
||||
|
||||
|
||||
## 概念的定义
|
||||
|
||||
|
87
devcloud/mpaas/apps/k8s/design.drawio
Normal file
87
devcloud/mpaas/apps/k8s/design.drawio
Normal file
@ -0,0 +1,87 @@
|
||||
<mxfile host="65bd71144e">
|
||||
<diagram id="2lwm4o9OGl1Gjpf4ACLQ" name="第 1 页">
|
||||
<mxGraphModel dx="1115" dy="631" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="4" value="查询制品" style="edgeStyle=none;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" edge="1" parent="1" source="5" target="9">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="5" value="artifact<div>制品库</div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="260" y="100" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="6" style="edgeStyle=none;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="7" target="5">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="23" value="制品信息上报" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="6">
|
||||
<mxGeometry x="-0.1818" y="-1" relative="1" as="geometry">
|
||||
<mxPoint as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="7" value="CI工具" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="30" y="100" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="8" style="edgeStyle=none;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="9" target="13">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="9" value="deploy<div>应用部署</div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="260" y="250" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="10" style="edgeStyle=none;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="12" target="9">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="11" value="选择适合的制品格式<div><br></div>" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="10">
|
||||
<mxGeometry x="0.2278" y="-3" relative="1" as="geometry">
|
||||
<mxPoint x="29" y="-17" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="12" value="cluster<div>应用集群</div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="590" y="250" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="13" value="k8s 集群" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="260" y="430" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="14" style="edgeStyle=none;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="18" target="13">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="15" value="watch" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="14">
|
||||
<mxGeometry x="0.2242" y="-1" relative="1" as="geometry">
|
||||
<mxPoint as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="16" style="edgeStyle=none;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="18" target="12">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="17" value="实时更新集群状态" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="16">
|
||||
<mxGeometry x="0.0236" y="-4" relative="1" as="geometry">
|
||||
<mxPoint as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="18" value="sync<div>部署同步</div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="590" y="430" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="19" value="首次部署使用模版渲染" style="edgeStyle=none;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="20" target="12">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="20" value="temlate<div>部署模版</div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="590" y="100" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="21" style="edgeStyle=none;html=1;exitX=0;exitY=0.3333333333333333;exitDx=0;exitDy=0;exitPerimeter=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="22" target="12">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="22" value="Actor" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="770" y="260" width="30" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="24" value="应用部署 (应用发布流中最核心)" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="440" y="30" width="200" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="25" value="承载应用运行的实例" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="485" y="330" width="110" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="26" value="部署引擎" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="200" y="320" width="60" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
@ -4,7 +4,7 @@ import { Message } from '@arco-design/web-vue'
|
||||
// 封装一个axios的实例,http cient实例
|
||||
// https://axios-http.com/zh/docs/instance
|
||||
const client = axios.create({
|
||||
timeout: 3000,
|
||||
timeout: 5000,
|
||||
})
|
||||
|
||||
// 拦截API的返回结果, 如果是异常 提取异常信息,并展示
|
||||
|
@ -4,6 +4,9 @@ var MCENTER_API = {
|
||||
Login: (data) => {
|
||||
return client.post('/api/devcloud/v1/token', data)
|
||||
},
|
||||
LabelList: (params) => {
|
||||
return client.get('/api/devcloud/v1/labels', { params })
|
||||
},
|
||||
}
|
||||
|
||||
export default MCENTER_API
|
||||
|
@ -4,6 +4,15 @@ var MPAAS_API = {
|
||||
AppList: (params) => {
|
||||
return client.get('/api/devcloud/v1/applications', { params })
|
||||
},
|
||||
AppCreate: (data) => {
|
||||
return client.post('/api/devcloud/v1/applications', data)
|
||||
},
|
||||
AppUpdate: (id, data) => {
|
||||
return client.put(`/api/devcloud/v1/applications/${id}`, data)
|
||||
},
|
||||
AppDelete: (id) => {
|
||||
return client.delete(`/api/devcloud/v1/applications/${id}`)
|
||||
},
|
||||
}
|
||||
|
||||
export default MPAAS_API
|
||||
|
@ -134,7 +134,7 @@ const handleUserOption = (option) => {
|
||||
.router-view-wrapper {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
// min-height: calc(100vh - 180px);
|
||||
min-height: calc(100vh - 180px);
|
||||
/* 调整最小高度 */
|
||||
}
|
||||
|
||||
|
@ -174,7 +174,7 @@
|
||||
.router-view-wrapper {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
// min-height: calc(100vh - 180px);
|
||||
min-height: calc(100vh - 180px);
|
||||
/* 调整最小高度 */
|
||||
}
|
||||
|
||||
|
@ -63,11 +63,10 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, shallowReactive } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import HeaderNav from './components/HeaderNav.vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
import { IconApps, IconBranch, IconLock, IconSettings, IconTags } from '@arco-design/web-vue/es/icon';
|
||||
import token from '@/storage/token'
|
||||
import app from '@/storage/app'
|
||||
import { watch } from 'vue';
|
||||
@ -88,7 +87,7 @@ watch(width, (newWidth) => {
|
||||
}, { immediate: true });
|
||||
|
||||
const currentSelectMenuItem = computed(() => {
|
||||
return app.value.current_menu[app.value.current_system]?.menu_item || app.value.current_system
|
||||
return app.value.system_menu.find(item => item.key === app.value.current_system)?.current_menu_item || app.value.current_system
|
||||
})
|
||||
|
||||
const handleSystemChange = (system) => {
|
||||
@ -106,13 +105,13 @@ const handleSystemChange = (system) => {
|
||||
|
||||
// 菜单被选中
|
||||
const handleMenuClick = (key) => {
|
||||
app.value.current_menu
|
||||
app.value.system_menu.find(item => item.key === app.value.current_system).current_menu_item = key
|
||||
console.log(key)
|
||||
}
|
||||
|
||||
// 菜单项被选中
|
||||
const handleMenuItemClick = (key) => {
|
||||
app.value.current_menu[app.value.current_system].menu_item = key
|
||||
app.value.system_menu.find(item => item.key === app.value.current_system).current_menu_item = key
|
||||
router.push({ name: key })
|
||||
console.log(route)
|
||||
}
|
||||
@ -134,52 +133,9 @@ const handleUserOption = (option) => {
|
||||
};
|
||||
|
||||
const currentMenus = computed(() => {
|
||||
return systemMenus[app.value.current_system]
|
||||
return app.value.system_menu.find(item => item.key === app.value.current_system)?.menus || []
|
||||
})
|
||||
|
||||
const systemMenus = shallowReactive({
|
||||
ProjectSystem: [
|
||||
{
|
||||
key: 'ProjectList',
|
||||
icon: IconLock,
|
||||
title: '项目空间'
|
||||
},
|
||||
],
|
||||
DevelopSystem: [
|
||||
{
|
||||
key: 'AppPage',
|
||||
icon: IconApps,
|
||||
title: '应用管理'
|
||||
},
|
||||
{
|
||||
key: 'VersionIteration',
|
||||
icon: IconTags,
|
||||
title: '版本迭代'
|
||||
},
|
||||
{
|
||||
key: 'PipelineTemplate',
|
||||
icon: IconSettings,
|
||||
title: '流水线模板'
|
||||
}
|
||||
],
|
||||
ArtifactSystem: [
|
||||
{
|
||||
key: 'RegistryPage',
|
||||
icon: IconTags,
|
||||
title: '制品仓库'
|
||||
},
|
||||
{
|
||||
key: 'AssetPage',
|
||||
icon: IconBranch,
|
||||
title: '制品管理'
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@ -418,7 +374,7 @@ const systemMenus = shallowReactive({
|
||||
.router-view-wrapper {
|
||||
flex: 1;
|
||||
padding: 0px 20px 0px 40px;
|
||||
// min-height: calc(100vh - 180px);
|
||||
min-height: calc(100vh - 180px);
|
||||
/* 动态计算最小高度 */
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { ref, computed } from 'vue';
|
||||
import token from '@/storage/token'
|
||||
import app from '@/storage/app'
|
||||
import { useRouter } from 'vue-router';
|
||||
@ -37,14 +37,13 @@ import { useRouter } from 'vue-router';
|
||||
const router = useRouter()
|
||||
const emit = defineEmits(['system-change', 'user-option-click']);
|
||||
|
||||
const menuItems = ref([
|
||||
{ key: 'DashBoard', label: '工作台' },
|
||||
{ key: 'ProjectSystem', label: '项目管理' },
|
||||
{ key: 'DevelopSystem', label: '研发交付' },
|
||||
{ key: 'ArtifactSystem', label: '制品库' },
|
||||
{ key: '5', label: '测试中心' },
|
||||
{ key: '6', label: '运维中心' }
|
||||
]);
|
||||
const menuItems = computed(() => {
|
||||
const systemMenu = app.value.system_menu || []
|
||||
return systemMenu.map(item => ({
|
||||
key: item.key,
|
||||
label: item.title,
|
||||
}))
|
||||
})
|
||||
|
||||
const userOptions = ref([
|
||||
{ key: 'profile', label: '个人中心', handler: () => emit('user-option-click', 'profile') },
|
||||
|
@ -8,7 +8,7 @@ const app = createApp(App)
|
||||
import ArcoVue from '@arco-design/web-vue'
|
||||
import ArcoVueIcon from '@arco-design/web-vue/es/icon'
|
||||
import '@arco-design/web-vue/dist/arco.css'
|
||||
app.use(ArcoVue)
|
||||
app.use(ArcoVue,)
|
||||
app.use(ArcoVueIcon)
|
||||
|
||||
// 样式加载
|
||||
|
@ -1,63 +1,70 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="action-bar">
|
||||
<a-space>
|
||||
<a-button type="primary">
|
||||
<template #icon><icon-plus /></template>
|
||||
新建项目空间
|
||||
</a-button>
|
||||
</a-space>
|
||||
<a-space>
|
||||
<a-select v-model="query.ready" :style="{ width: '220px', backgroundColor: '#fff' }" @change="fetchAppList"
|
||||
placeholder="选择状态" allow-clear>
|
||||
<a-option :value="true">就绪</a-option>
|
||||
<a-option :value="false">未就绪</a-option>
|
||||
</a-select>
|
||||
<a-input-search v-model="searchKey" placeholder="搜索项目名称/描述" allow-clear
|
||||
style="width: 300px;background-color: #fff;" />
|
||||
</a-space>
|
||||
</div>
|
||||
<a-table :data="data.items" :loading="fetchAppLoading">
|
||||
<template #columns>
|
||||
<a-table-column title="名称" data-index="name"></a-table-column>
|
||||
<a-table-column title="描述" data-index="description"></a-table-column>
|
||||
<a-table-column title="团队" data-index="label.team"></a-table-column>
|
||||
<a-table-column title="创建时间" data-index="create_at"></a-table-column>
|
||||
<a-table-column title="类型" data-index="type"></a-table-column>
|
||||
<a-table-column title="仓库地址" data-index="code_repository.ssh_url"></a-table-column>
|
||||
<a-table-column title="是否就绪" data-index="ready">
|
||||
<template #cell="{ record }">
|
||||
<a-switch type="round" disabled v-model="record.ready">
|
||||
<template #checked>
|
||||
就绪
|
||||
</template>
|
||||
<template #unchecked>
|
||||
未就绪
|
||||
</template>
|
||||
</a-switch>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="操作">
|
||||
<template #cell="{ record }">
|
||||
<a-space>
|
||||
<a-button @click="editApp(record)">编辑</a-button>
|
||||
<a-button @click="deleteApp(record)" type="primary">删除</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-table-column>
|
||||
</template>
|
||||
</a-table>
|
||||
<a-card>
|
||||
<div class="action-bar">
|
||||
<a-space>
|
||||
<a-button type="outline" @click="addApp()">
|
||||
<template #icon><icon-plus /></template>
|
||||
新建应用
|
||||
</a-button>
|
||||
</a-space>
|
||||
<a-space>
|
||||
<a-select v-model="query.ready" :style="{ width: '220px' }" @change="fetchAppList" @clear="query.ready = null"
|
||||
placeholder="选择状态" allow-clear>
|
||||
<a-option :value="true">就绪</a-option>
|
||||
<a-option :value="false">未就绪</a-option>
|
||||
</a-select>
|
||||
<a-input-search v-model="query.keywords" placeholder="搜索项目名称/描述" allow-clear @press-enter="fetchAppList"
|
||||
@search="fetchAppList" style="width: 300px" />
|
||||
</a-space>
|
||||
</div>
|
||||
<a-table :data="data.items" :loading="fetchAppLoading">
|
||||
<template #columns>
|
||||
<a-table-column title="名称" data-index="name"></a-table-column>
|
||||
<a-table-column title="描述" data-index="description"></a-table-column>
|
||||
<a-table-column title="团队" data-index="label.team"></a-table-column>
|
||||
<a-table-column title="创建时间" data-index="create_at"></a-table-column>
|
||||
<a-table-column title="类型" data-index="type"></a-table-column>
|
||||
<a-table-column title="仓库地址" data-index="code_repository.ssh_url"></a-table-column>
|
||||
<a-table-column title="是否就绪" data-index="ready">
|
||||
<template #cell="{ record }">
|
||||
<a-switch type="round" disabled v-model="record.ready">
|
||||
<template #checked>
|
||||
就绪
|
||||
</template>
|
||||
<template #unchecked>
|
||||
未就绪
|
||||
</template>
|
||||
</a-switch>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="操作">
|
||||
<template #cell="{ record }">
|
||||
<a-space>
|
||||
<a-button @click="editApp(record)" type="primary">编辑</a-button>
|
||||
<a-popconfirm :content="`确定要删除${record.name}吗?`" :on-before-ok="() => doDeleteApp(record)"
|
||||
:ok-loading="deleteAppLoading" @ok="deleteApp(record)" type="warning">
|
||||
<a-button status="danger">删除</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-table-column>
|
||||
</template>
|
||||
</a-table>
|
||||
<AppFormModel v-model:visible="formVisible" :appData="currentApp" @submit="fetchAppList" />
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import API from '@/api'
|
||||
import AppFormModel from './components/AppFormModel.vue';
|
||||
|
||||
const query = reactive({
|
||||
const query = ref({
|
||||
page_size: 20,
|
||||
page_number: 1,
|
||||
|
||||
keywords: '',
|
||||
});
|
||||
|
||||
// 通过API获取应用列表
|
||||
@ -67,9 +74,13 @@ const data = ref({
|
||||
});
|
||||
const fetchAppLoading = ref(false);
|
||||
const fetchAppList = async () => {
|
||||
console.log(query.value.ready)
|
||||
if (query.value.ready === null || query.value.ready === undefined || query.value.ready === '') {
|
||||
delete query.value.ready;
|
||||
}
|
||||
try {
|
||||
fetchAppLoading.value = true;
|
||||
const resp = await API.mpaas.AppList(query);
|
||||
const resp = await API.mpaas.AppList(query.value);
|
||||
data.value = resp;
|
||||
console.log('Fetched app list:', data.value);
|
||||
} catch (error) {
|
||||
@ -82,4 +93,35 @@ const fetchAppList = async () => {
|
||||
onMounted(() => {
|
||||
fetchAppList();
|
||||
});
|
||||
|
||||
|
||||
// 编辑新增
|
||||
const formVisible = ref(false);
|
||||
const currentApp = ref(null);
|
||||
|
||||
const addApp = () => {
|
||||
formVisible.value = true;
|
||||
currentApp.value = null
|
||||
}
|
||||
|
||||
const editApp = (app) => {
|
||||
currentApp.value = app;
|
||||
formVisible.value = true;
|
||||
};
|
||||
|
||||
const deleteAppLoading = ref(false);
|
||||
const doDeleteApp = async (app) => {
|
||||
try {
|
||||
deleteAppLoading.value = true;
|
||||
await API.mpaas.AppDelete(app.id);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error deleting app:', error);
|
||||
} finally {
|
||||
deleteAppLoading.value = false;
|
||||
}
|
||||
}
|
||||
const deleteApp = () => {
|
||||
fetchAppList();
|
||||
};
|
||||
</script>
|
||||
|
186
devcloud/web/src/pages/develop/components/AppFormModel.vue
Normal file
186
devcloud/web/src/pages/develop/components/AppFormModel.vue
Normal file
@ -0,0 +1,186 @@
|
||||
<template>
|
||||
<a-modal draggable v-model:visible="modelVisible" :ok-loading="createAppLoading" :title="editMode ? '编辑应用' : '新建应用'"
|
||||
@ok="handleOk" :on-before-ok="handleBeforeOk" @cancel="handleCancel" :ok-text="editMode ? '保存' : '创建'"
|
||||
unmount-on-close>
|
||||
<a-form ref="appFormRef" :model="form" layout="vertical">
|
||||
<a-form-item label="应用名称" field="name" :rules="[{ required: true, message: '请输入应用名称' }]"
|
||||
:validate-trigger="['change', 'blur']">
|
||||
<a-input v-model="form.name" placeholder="请输入应用名称" allow-clear />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="应用描述" field="description">
|
||||
<a-textarea v-model="form.description" placeholder="请输入应用描述" :auto-size="{ minRows: 3, maxRows: 5 }"
|
||||
allow-clear />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="应用类型" field="type" :rules="[{ required: true, message: '请选择应用类型' }]">
|
||||
<a-select v-model="form.type" placeholder="应用类型" allow-clear>
|
||||
<a-option value="SOURCE_CODE">源代码</a-option>
|
||||
<a-option value="CONTAINER_IMAGE">容器镜像</a-option>
|
||||
<a-option value="OTHER">其他</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<div v-if="form.type === 'SOURCE_CODE'">
|
||||
<a-form-item label="代码仓库" field="code_repository.ssh_url" :rules="[{ required: true, message: '请输入代码仓库地址' }]">
|
||||
<a-input v-model="form.code_repository.ssh_url" placeholder="请输入代码仓库地址" allow-clear />
|
||||
</a-form-item>
|
||||
</div>
|
||||
|
||||
<a-form-item label="团队" field="label.team" :rules="[{ required: true, message: '请输入团队名称' }]">
|
||||
<a-tree-select v-model="form.label.team" :field-names="{
|
||||
key: 'value',
|
||||
title: 'label'
|
||||
}" :data="teamOptions" :loading="fetchAppTeamsLoading" placeholder="请选择团队"></a-tree-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, computed, onMounted } from 'vue';
|
||||
import API from '@/api'
|
||||
import { useTemplateRef } from 'vue'
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
appData: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:visible', 'submit']);
|
||||
|
||||
const appFormRef = useTemplateRef('appFormRef')
|
||||
|
||||
// 1. 先定义所有函数和变量
|
||||
const resetForm = () => {
|
||||
form.value = {
|
||||
name: '',
|
||||
description: '',
|
||||
code_repository: {
|
||||
ssh_url: ''
|
||||
},
|
||||
label: {
|
||||
team: ''
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// 2. 再定义其他响应式数据
|
||||
const modelVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (v) => emit('update:visible', v)
|
||||
});
|
||||
|
||||
const editMode = computed(() => !!props.appData);
|
||||
|
||||
const form = ref({
|
||||
name: '',
|
||||
description: '',
|
||||
type: 'SOURCE_CODE',
|
||||
code_repository: {}
|
||||
});
|
||||
|
||||
// 拉去团队的选型
|
||||
const fetchAppTeamsLoading = ref(false);
|
||||
const teamOptions = ref([])
|
||||
const fetchAppTeams = async () => {
|
||||
try {
|
||||
fetchAppTeamsLoading.value = true
|
||||
const resp = await API.mcenter.LabelList({
|
||||
key: 'team',
|
||||
});
|
||||
console.log(resp.items)
|
||||
if (resp.items.length > 0) {
|
||||
teamOptions.value = resp.items[0].enum_options
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching app teams:', error);
|
||||
return [];
|
||||
} finally {
|
||||
fetchAppTeamsLoading.value = false
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (props.appData) {
|
||||
form.value = { ...props.appData };
|
||||
} else {
|
||||
resetForm();
|
||||
}
|
||||
fetchAppTeams()
|
||||
});
|
||||
|
||||
// 3. 最后定义watch(此时resetForm已经定义)
|
||||
watch(() => props.visible, (newVal) => {
|
||||
if (props.appData) {
|
||||
if (newVal) {
|
||||
form.value = { ...props.appData };
|
||||
} else {
|
||||
resetForm();
|
||||
}
|
||||
}
|
||||
}, { immediate: true, });
|
||||
|
||||
|
||||
// 提交前校验, 阻止模态框关闭
|
||||
const createAppLoading = ref(false)
|
||||
const handleBeforeOk = async () => {
|
||||
// 检查
|
||||
const resp = await appFormRef.value.validate();
|
||||
if (resp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建
|
||||
createAppLoading.value = true
|
||||
try {
|
||||
createAppLoading.value = true
|
||||
|
||||
if (editMode.value) {
|
||||
await API.mpaas.AppUpdate(props.appData.id, {
|
||||
...form.value,
|
||||
name: form.value.name.trim()
|
||||
});
|
||||
return true
|
||||
} else {
|
||||
await API.mpaas.AppCreate({
|
||||
...form.value,
|
||||
name: form.value.name.trim()
|
||||
});
|
||||
}
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('Error creating app:', error);
|
||||
} finally {
|
||||
createAppLoading.value = false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const handleOk = async () => {
|
||||
emit('submit', {
|
||||
...form.value,
|
||||
name: form.value.name.trim()
|
||||
});
|
||||
resetForm(); // 提交后重置表单
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
resetForm(); // 取消时也重置表单
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 可以添加自定义样式 */
|
||||
:deep(.arco-form-item-label) {
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
@ -7,12 +7,11 @@
|
||||
<template #icon><icon-plus /></template>
|
||||
新建项目空间
|
||||
</a-button>
|
||||
|
||||
<a-input-search v-model="searchKey" placeholder="搜索项目名称/描述" @search="handleSearch" allow-clear
|
||||
style="width: 300px" />
|
||||
</a-space>
|
||||
|
||||
<a-space :size="18">
|
||||
<a-input-search v-model="searchKey" placeholder="搜索项目名称/描述" @search="handleSearch" allow-clear
|
||||
style="width: 300px" />
|
||||
<a-select v-model="filterParams.status" placeholder="项目状态" style="width: 120px" allow-clear
|
||||
@change="handleSearch">
|
||||
<a-option value="active">活跃</a-option>
|
||||
|
116
devcloud/web/src/pages/resource/AssetSync.vue
Normal file
116
devcloud/web/src/pages/resource/AssetSync.vue
Normal file
@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<a-space direction="vertical" size="large" fill>
|
||||
<div>
|
||||
<span>OnlyCurrent: </span>
|
||||
<a-switch v-model="rowSelection.onlyCurrent" />
|
||||
</div>
|
||||
<a-table row-key="name" :columns="columns" :data="data" :row-selection="rowSelection"
|
||||
v-model:selectedKeys="selectedKeys" :pagination="pagination" />
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive, ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const selectedKeys = ref(['Jane Doe', 'Alisa Ross']);
|
||||
|
||||
const rowSelection = reactive({
|
||||
type: 'checkbox',
|
||||
showCheckedAll: true,
|
||||
onlyCurrent: false,
|
||||
});
|
||||
const pagination = { pageSize: 5 }
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'Name',
|
||||
dataIndex: 'name',
|
||||
},
|
||||
{
|
||||
title: 'Salary',
|
||||
dataIndex: 'salary',
|
||||
},
|
||||
{
|
||||
title: 'Address',
|
||||
dataIndex: 'address',
|
||||
},
|
||||
{
|
||||
title: 'Email',
|
||||
dataIndex: 'email',
|
||||
},
|
||||
]
|
||||
const data = reactive([{
|
||||
key: '1',
|
||||
name: 'Jane Doe',
|
||||
salary: 23000,
|
||||
address: '32 Park Road, London',
|
||||
email: 'jane.doe@example.com'
|
||||
}, {
|
||||
key: '2',
|
||||
name: 'Alisa Ross',
|
||||
salary: 25000,
|
||||
address: '35 Park Road, London',
|
||||
email: 'alisa.ross@example.com'
|
||||
}, {
|
||||
key: '3',
|
||||
name: 'Kevin Sandra',
|
||||
salary: 22000,
|
||||
address: '31 Park Road, London',
|
||||
email: 'kevin.sandra@example.com',
|
||||
disabled: true
|
||||
}, {
|
||||
key: '4',
|
||||
name: 'Ed Hellen',
|
||||
salary: 17000,
|
||||
address: '42 Park Road, London',
|
||||
email: 'ed.hellen@example.com'
|
||||
}, {
|
||||
key: '5',
|
||||
name: 'William Smith',
|
||||
salary: 27000,
|
||||
address: '62 Park Road, London',
|
||||
email: 'william.smith@example.com'
|
||||
}, {
|
||||
key: '6',
|
||||
name: 'Jane Doe 2',
|
||||
salary: 15000,
|
||||
address: '32 Park Road, London',
|
||||
email: 'jane.doe@example.com'
|
||||
}, {
|
||||
key: '7',
|
||||
name: 'Alisa Ross 2',
|
||||
salary: 28000,
|
||||
address: '35 Park Road, London',
|
||||
email: 'alisa.ross@example.com'
|
||||
}, {
|
||||
key: '8',
|
||||
name: 'Kevin Sandra 2',
|
||||
salary: 26000,
|
||||
address: '31 Park Road, London',
|
||||
email: 'kevin.sandra@example.com',
|
||||
}, {
|
||||
key: '9',
|
||||
name: 'Ed Hellen 2',
|
||||
salary: 18000,
|
||||
address: '42 Park Road, London',
|
||||
email: 'ed.hellen@example.com'
|
||||
}, {
|
||||
key: '10',
|
||||
name: 'William Smith 2',
|
||||
salary: 12000,
|
||||
address: '62 Park Road, London',
|
||||
email: 'william.smith@example.com'
|
||||
}]);
|
||||
|
||||
return {
|
||||
rowSelection,
|
||||
columns,
|
||||
data,
|
||||
selectedKeys,
|
||||
pagination
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
116
devcloud/web/src/pages/resource/EnvManage.vue
Normal file
116
devcloud/web/src/pages/resource/EnvManage.vue
Normal file
@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<a-space direction="vertical" size="large" fill>
|
||||
<div>
|
||||
<span>OnlyCurrent: </span>
|
||||
<a-switch v-model="rowSelection.onlyCurrent" />
|
||||
</div>
|
||||
<a-table row-key="name" :columns="columns" :data="data" :row-selection="rowSelection"
|
||||
v-model:selectedKeys="selectedKeys" :pagination="pagination" />
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive, ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const selectedKeys = ref(['Jane Doe', 'Alisa Ross']);
|
||||
|
||||
const rowSelection = reactive({
|
||||
type: 'checkbox',
|
||||
showCheckedAll: true,
|
||||
onlyCurrent: false,
|
||||
});
|
||||
const pagination = { pageSize: 5 }
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'Name',
|
||||
dataIndex: 'name',
|
||||
},
|
||||
{
|
||||
title: 'Salary',
|
||||
dataIndex: 'salary',
|
||||
},
|
||||
{
|
||||
title: 'Address',
|
||||
dataIndex: 'address',
|
||||
},
|
||||
{
|
||||
title: 'Email',
|
||||
dataIndex: 'email',
|
||||
},
|
||||
]
|
||||
const data = reactive([{
|
||||
key: '1',
|
||||
name: 'Jane Doe',
|
||||
salary: 23000,
|
||||
address: '32 Park Road, London',
|
||||
email: 'jane.doe@example.com'
|
||||
}, {
|
||||
key: '2',
|
||||
name: 'Alisa Ross',
|
||||
salary: 25000,
|
||||
address: '35 Park Road, London',
|
||||
email: 'alisa.ross@example.com'
|
||||
}, {
|
||||
key: '3',
|
||||
name: 'Kevin Sandra',
|
||||
salary: 22000,
|
||||
address: '31 Park Road, London',
|
||||
email: 'kevin.sandra@example.com',
|
||||
disabled: true
|
||||
}, {
|
||||
key: '4',
|
||||
name: 'Ed Hellen',
|
||||
salary: 17000,
|
||||
address: '42 Park Road, London',
|
||||
email: 'ed.hellen@example.com'
|
||||
}, {
|
||||
key: '5',
|
||||
name: 'William Smith',
|
||||
salary: 27000,
|
||||
address: '62 Park Road, London',
|
||||
email: 'william.smith@example.com'
|
||||
}, {
|
||||
key: '6',
|
||||
name: 'Jane Doe 2',
|
||||
salary: 15000,
|
||||
address: '32 Park Road, London',
|
||||
email: 'jane.doe@example.com'
|
||||
}, {
|
||||
key: '7',
|
||||
name: 'Alisa Ross 2',
|
||||
salary: 28000,
|
||||
address: '35 Park Road, London',
|
||||
email: 'alisa.ross@example.com'
|
||||
}, {
|
||||
key: '8',
|
||||
name: 'Kevin Sandra 2',
|
||||
salary: 26000,
|
||||
address: '31 Park Road, London',
|
||||
email: 'kevin.sandra@example.com',
|
||||
}, {
|
||||
key: '9',
|
||||
name: 'Ed Hellen 2',
|
||||
salary: 18000,
|
||||
address: '42 Park Road, London',
|
||||
email: 'ed.hellen@example.com'
|
||||
}, {
|
||||
key: '10',
|
||||
name: 'William Smith 2',
|
||||
salary: 12000,
|
||||
address: '62 Park Road, London',
|
||||
email: 'william.smith@example.com'
|
||||
}]);
|
||||
|
||||
return {
|
||||
rowSelection,
|
||||
columns,
|
||||
data,
|
||||
selectedKeys,
|
||||
pagination
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
116
devcloud/web/src/pages/resource/K8sCluster.vue
Normal file
116
devcloud/web/src/pages/resource/K8sCluster.vue
Normal file
@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<a-space direction="vertical" size="large" fill>
|
||||
<div>
|
||||
<span>OnlyCurrent: </span>
|
||||
<a-switch v-model="rowSelection.onlyCurrent" />
|
||||
</div>
|
||||
<a-table row-key="name" :columns="columns" :data="data" :row-selection="rowSelection"
|
||||
v-model:selectedKeys="selectedKeys" :pagination="pagination" />
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive, ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const selectedKeys = ref(['Jane Doe', 'Alisa Ross']);
|
||||
|
||||
const rowSelection = reactive({
|
||||
type: 'checkbox',
|
||||
showCheckedAll: true,
|
||||
onlyCurrent: false,
|
||||
});
|
||||
const pagination = { pageSize: 5 }
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'Name',
|
||||
dataIndex: 'name',
|
||||
},
|
||||
{
|
||||
title: 'Salary',
|
||||
dataIndex: 'salary',
|
||||
},
|
||||
{
|
||||
title: 'Address',
|
||||
dataIndex: 'address',
|
||||
},
|
||||
{
|
||||
title: 'Email',
|
||||
dataIndex: 'email',
|
||||
},
|
||||
]
|
||||
const data = reactive([{
|
||||
key: '1',
|
||||
name: 'Jane Doe',
|
||||
salary: 23000,
|
||||
address: '32 Park Road, London',
|
||||
email: 'jane.doe@example.com'
|
||||
}, {
|
||||
key: '2',
|
||||
name: 'Alisa Ross',
|
||||
salary: 25000,
|
||||
address: '35 Park Road, London',
|
||||
email: 'alisa.ross@example.com'
|
||||
}, {
|
||||
key: '3',
|
||||
name: 'Kevin Sandra',
|
||||
salary: 22000,
|
||||
address: '31 Park Road, London',
|
||||
email: 'kevin.sandra@example.com',
|
||||
disabled: true
|
||||
}, {
|
||||
key: '4',
|
||||
name: 'Ed Hellen',
|
||||
salary: 17000,
|
||||
address: '42 Park Road, London',
|
||||
email: 'ed.hellen@example.com'
|
||||
}, {
|
||||
key: '5',
|
||||
name: 'William Smith',
|
||||
salary: 27000,
|
||||
address: '62 Park Road, London',
|
||||
email: 'william.smith@example.com'
|
||||
}, {
|
||||
key: '6',
|
||||
name: 'Jane Doe 2',
|
||||
salary: 15000,
|
||||
address: '32 Park Road, London',
|
||||
email: 'jane.doe@example.com'
|
||||
}, {
|
||||
key: '7',
|
||||
name: 'Alisa Ross 2',
|
||||
salary: 28000,
|
||||
address: '35 Park Road, London',
|
||||
email: 'alisa.ross@example.com'
|
||||
}, {
|
||||
key: '8',
|
||||
name: 'Kevin Sandra 2',
|
||||
salary: 26000,
|
||||
address: '31 Park Road, London',
|
||||
email: 'kevin.sandra@example.com',
|
||||
}, {
|
||||
key: '9',
|
||||
name: 'Ed Hellen 2',
|
||||
salary: 18000,
|
||||
address: '42 Park Road, London',
|
||||
email: 'ed.hellen@example.com'
|
||||
}, {
|
||||
key: '10',
|
||||
name: 'William Smith 2',
|
||||
salary: 12000,
|
||||
address: '62 Park Road, London',
|
||||
email: 'william.smith@example.com'
|
||||
}]);
|
||||
|
||||
return {
|
||||
rowSelection,
|
||||
columns,
|
||||
data,
|
||||
selectedKeys,
|
||||
pagination
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
116
devcloud/web/src/pages/resource/ResourceSearch.vue
Normal file
116
devcloud/web/src/pages/resource/ResourceSearch.vue
Normal file
@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<a-space direction="vertical" size="large" fill>
|
||||
<div>
|
||||
<span>OnlyCurrent: </span>
|
||||
<a-switch v-model="rowSelection.onlyCurrent" />
|
||||
</div>
|
||||
<a-table row-key="name" :columns="columns" :data="data" :row-selection="rowSelection"
|
||||
v-model:selectedKeys="selectedKeys" :pagination="pagination" />
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive, ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const selectedKeys = ref(['Jane Doe', 'Alisa Ross']);
|
||||
|
||||
const rowSelection = reactive({
|
||||
type: 'checkbox',
|
||||
showCheckedAll: true,
|
||||
onlyCurrent: false,
|
||||
});
|
||||
const pagination = { pageSize: 5 }
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'Name',
|
||||
dataIndex: 'name',
|
||||
},
|
||||
{
|
||||
title: 'Salary',
|
||||
dataIndex: 'salary',
|
||||
},
|
||||
{
|
||||
title: 'Address',
|
||||
dataIndex: 'address',
|
||||
},
|
||||
{
|
||||
title: 'Email',
|
||||
dataIndex: 'email',
|
||||
},
|
||||
]
|
||||
const data = reactive([{
|
||||
key: '1',
|
||||
name: 'Jane Doe',
|
||||
salary: 23000,
|
||||
address: '32 Park Road, London',
|
||||
email: 'jane.doe@example.com'
|
||||
}, {
|
||||
key: '2',
|
||||
name: 'Alisa Ross',
|
||||
salary: 25000,
|
||||
address: '35 Park Road, London',
|
||||
email: 'alisa.ross@example.com'
|
||||
}, {
|
||||
key: '3',
|
||||
name: 'Kevin Sandra',
|
||||
salary: 22000,
|
||||
address: '31 Park Road, London',
|
||||
email: 'kevin.sandra@example.com',
|
||||
disabled: true
|
||||
}, {
|
||||
key: '4',
|
||||
name: 'Ed Hellen',
|
||||
salary: 17000,
|
||||
address: '42 Park Road, London',
|
||||
email: 'ed.hellen@example.com'
|
||||
}, {
|
||||
key: '5',
|
||||
name: 'William Smith',
|
||||
salary: 27000,
|
||||
address: '62 Park Road, London',
|
||||
email: 'william.smith@example.com'
|
||||
}, {
|
||||
key: '6',
|
||||
name: 'Jane Doe 2',
|
||||
salary: 15000,
|
||||
address: '32 Park Road, London',
|
||||
email: 'jane.doe@example.com'
|
||||
}, {
|
||||
key: '7',
|
||||
name: 'Alisa Ross 2',
|
||||
salary: 28000,
|
||||
address: '35 Park Road, London',
|
||||
email: 'alisa.ross@example.com'
|
||||
}, {
|
||||
key: '8',
|
||||
name: 'Kevin Sandra 2',
|
||||
salary: 26000,
|
||||
address: '31 Park Road, London',
|
||||
email: 'kevin.sandra@example.com',
|
||||
}, {
|
||||
key: '9',
|
||||
name: 'Ed Hellen 2',
|
||||
salary: 18000,
|
||||
address: '42 Park Road, London',
|
||||
email: 'ed.hellen@example.com'
|
||||
}, {
|
||||
key: '10',
|
||||
name: 'William Smith 2',
|
||||
salary: 12000,
|
||||
address: '62 Park Road, London',
|
||||
email: 'william.smith@example.com'
|
||||
}]);
|
||||
|
||||
return {
|
||||
rowSelection,
|
||||
columns,
|
||||
data,
|
||||
selectedKeys,
|
||||
pagination
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
@ -59,11 +59,55 @@ const router = createRouter({
|
||||
},
|
||||
],
|
||||
},
|
||||
// 资源管理
|
||||
{
|
||||
path: '/resource',
|
||||
name: 'ResourceSystem',
|
||||
redirect: { name: 'ResourceSearch' },
|
||||
component: MenuLayout,
|
||||
meta: {
|
||||
title: '资源管理',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'search',
|
||||
name: 'ResourceSearch',
|
||||
component: () => import('@/pages/resource/ResourceSearch.vue'),
|
||||
meta: {
|
||||
title: '资源检索',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'env',
|
||||
name: 'EnvManage',
|
||||
component: () => import('@/pages/resource/EnvManage.vue'),
|
||||
meta: {
|
||||
title: '环境管理',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'asset_sync',
|
||||
name: 'AssetSync',
|
||||
component: () => import('@/pages/resource/AssetSync.vue'),
|
||||
meta: {
|
||||
title: '资产同步',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'k8s_cluster',
|
||||
name: 'K8sCluster',
|
||||
component: () => import('@/pages/resource/K8sCluster.vue'),
|
||||
meta: {
|
||||
title: 'k8s集群',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// 研发交付
|
||||
{
|
||||
path: '/develop',
|
||||
name: 'DevelopSystem',
|
||||
redirect: { name: 'SprintPage' },
|
||||
redirect: { name: 'AppPage' },
|
||||
component: MenuLayout,
|
||||
meta: {
|
||||
title: '研发交付',
|
||||
@ -99,7 +143,7 @@ const router = createRouter({
|
||||
{
|
||||
path: '/artifact',
|
||||
name: 'ArtifactSystem',
|
||||
redirect: { name: 'SprintPage' },
|
||||
redirect: { name: 'RegistryPage' },
|
||||
component: MenuLayout,
|
||||
meta: {
|
||||
title: '制品库',
|
||||
|
@ -4,24 +4,166 @@ export default useStorage(
|
||||
'app',
|
||||
{
|
||||
current_system: 'DashBoard',
|
||||
current_menu: {
|
||||
DashBoard: {
|
||||
sub_menu: '',
|
||||
menu_item: '1',
|
||||
system_menu: [
|
||||
{
|
||||
key: 'DashBoard',
|
||||
current_sub_menu: '',
|
||||
current_menu_item: 'DashboardPage',
|
||||
title: '工作台',
|
||||
},
|
||||
ProjectSystem: {
|
||||
sub_menu: '',
|
||||
menu_item: '1',
|
||||
{
|
||||
key: 'ProjectSystem',
|
||||
current_sub_menu: '',
|
||||
current_menu_item: 'ProjectList',
|
||||
title: '项目管理',
|
||||
menus: [
|
||||
{
|
||||
key: 'ProjectList',
|
||||
icon: 'IconLock',
|
||||
title: '项目空间',
|
||||
},
|
||||
],
|
||||
},
|
||||
DevelopSystem: {
|
||||
sub_menu: '',
|
||||
menu_item: '1',
|
||||
{
|
||||
key: 'ResourceSystem',
|
||||
current_sub_menu: '',
|
||||
current_menu_item: 'ResourceSearch',
|
||||
title: '资源管理',
|
||||
menus: [
|
||||
{
|
||||
key: 'ResourceSearch',
|
||||
icon: 'IconSearch',
|
||||
title: '资源检索',
|
||||
},
|
||||
{
|
||||
key: 'EnvManage',
|
||||
icon: 'IconLocation',
|
||||
title: '环境管理',
|
||||
},
|
||||
{
|
||||
key: 'AssetSync',
|
||||
icon: 'IconCloud',
|
||||
title: '资产同步',
|
||||
},
|
||||
{
|
||||
key: 'K8sCluster',
|
||||
icon: 'IconCommon',
|
||||
title: 'k8s集群',
|
||||
},
|
||||
],
|
||||
},
|
||||
ArtifactSystem: {
|
||||
sub_menu: '',
|
||||
menu_item: 'RegistryPage',
|
||||
{
|
||||
key: 'DevelopSystem',
|
||||
current_sub_menu: '',
|
||||
current_menu_item: 'AppPage',
|
||||
title: '研发交付',
|
||||
menus: [
|
||||
{
|
||||
key: 'AppPage',
|
||||
icon: 'IconApps',
|
||||
title: '应用管理',
|
||||
},
|
||||
{
|
||||
key: 'VersionIteration',
|
||||
icon: 'IconTags',
|
||||
title: '版本迭代',
|
||||
},
|
||||
{
|
||||
key: 'PipelineTemplate',
|
||||
icon: 'IconSettings',
|
||||
title: '流水线模板',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'ArtifactSystem',
|
||||
current_sub_menu: '',
|
||||
current_menu_item: 'RegistryPage',
|
||||
title: '制品库',
|
||||
menus: [
|
||||
{
|
||||
key: 'RegistryPage',
|
||||
icon: 'IconTags',
|
||||
title: '制品管理',
|
||||
},
|
||||
{
|
||||
key: 'AssetPage',
|
||||
icon: 'IconBranch',
|
||||
title: '制品管理',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'TestSystem',
|
||||
current_sub_menu: '',
|
||||
current_menu_item: 'RegistryPage',
|
||||
title: '测试中心',
|
||||
menus: [
|
||||
{
|
||||
key: 'TestApply',
|
||||
icon: 'IconStamp',
|
||||
title: '提测申请',
|
||||
},
|
||||
{
|
||||
key: 'TestCase',
|
||||
icon: 'IconExperiment',
|
||||
title: '测试用例',
|
||||
},
|
||||
{
|
||||
key: 'TestExecution',
|
||||
icon: 'IconSend',
|
||||
title: '用例执行',
|
||||
},
|
||||
{
|
||||
key: 'DefectManage',
|
||||
icon: 'IconBug',
|
||||
title: '缺陷管理',
|
||||
},
|
||||
{
|
||||
key: 'TestReport',
|
||||
icon: 'IconDashboard',
|
||||
title: '测试报告',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'SecuritySystem',
|
||||
current_sub_menu: '',
|
||||
current_menu_item: 'RegistryPage',
|
||||
title: '安全合规',
|
||||
menus: [
|
||||
{
|
||||
key: 'VulnerabilityScan',
|
||||
icon: 'IconFindReplace',
|
||||
title: '漏洞扫描',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'OpsSystem',
|
||||
current_sub_menu: '',
|
||||
current_menu_item: 'RegistryPage',
|
||||
title: '运维中心',
|
||||
menus: [
|
||||
{
|
||||
key: 'OnlineApply',
|
||||
icon: 'IconStamp',
|
||||
title: '上线申请',
|
||||
},
|
||||
{
|
||||
key: 'DeployManage',
|
||||
icon: 'IconLayers',
|
||||
title: '应用部署',
|
||||
},
|
||||
|
||||
{
|
||||
key: 'MonitorAlarm',
|
||||
icon: 'IconNotification',
|
||||
title: '监控告警',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
localStorage,
|
||||
{ mergeDefaults: true },
|
||||
|
6
go.mod
6
go.mod
@ -6,7 +6,7 @@ require (
|
||||
github.com/caarlos0/env/v6 v6.10.1
|
||||
github.com/emicklei/go-restful-openapi/v2 v2.11.0
|
||||
github.com/emicklei/go-restful/v3 v3.12.2
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/gin-gonic/gin v1.10.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/infraboard/devops v0.0.6
|
||||
github.com/infraboard/mcube/v2 v2.0.63
|
||||
@ -19,6 +19,8 @@ require (
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/lighthouse v1.0.1134
|
||||
go.mongodb.org/mongo-driver v1.17.3
|
||||
golang.org/x/crypto v0.38.0
|
||||
google.golang.org/grpc v1.72.0
|
||||
google.golang.org/protobuf v1.36.6
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/datatypes v1.2.5
|
||||
gorm.io/driver/mysql v1.5.7
|
||||
@ -112,8 +114,6 @@ require (
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect
|
||||
google.golang.org/grpc v1.72.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gorm.io/driver/postgres v1.5.11 // indirect
|
||||
modernc.org/libc v1.22.5 // indirect
|
||||
|
4
go.sum
4
go.sum
@ -36,8 +36,8 @@ github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3G
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
|
||||
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
||||
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
|
3
skills/metric/README.md
Normal file
3
skills/metric/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Metric
|
||||
|
||||
https://www.mcube.top/guide/config/metric.html
|
2
skills/rpc/README.md
Normal file
2
skills/rpc/README.md
Normal file
@ -0,0 +1,2 @@
|
||||
# rpc
|
||||
|
109
skills/rpc/flow.drawio
Normal file
109
skills/rpc/flow.drawio
Normal file
@ -0,0 +1,109 @@
|
||||
<mxfile host="65bd71144e">
|
||||
<diagram id="yflk6MuM42xHFq2jSyhu" name="第 1 页">
|
||||
<mxGraphModel dx="1115" dy="597" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="2" value="server a<div>fn</div>" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="170" y="170" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="6" style="edgeStyle=none;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="5" target="2" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="7" value="servier a: fn" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="6" vertex="1" connectable="0">
|
||||
<mxGeometry x="0.1941" y="-1" relative="1" as="geometry">
|
||||
<mxPoint as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="5" value="server b" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="630" y="170" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="8" value="rpc" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||
<mxGeometry x="400" y="100" width="60" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="9" value="fn: (my_name) {hello, my_name}" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||
<mxGeometry x="350" y="250" width="230" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="10" value="service c<div>python</div>" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="630" y="390" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="11" value="service d<div>java</div>" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="390" y="390" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="12" value="server b<div>go</div>" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="150" y="390" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="19" style="edgeStyle=none;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" parent="1" source="13" target="16" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="20" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" parent="1" source="13" target="17" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="13" value="protofuf" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="390" y="570" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="15" value="interface(struct)" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||
<mxGeometry x="160" y="480" width="100" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="16" value="interface(class)" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||
<mxGeometry x="400" y="480" width="100" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="17" value="class" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||
<mxGeometry x="670" y="480" width="60" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="18" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.43;entryY=1;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="13" target="15" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="30" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="21" target="22">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="31" value="GRPC" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="30">
|
||||
<mxGeometry x="-0.2941" relative="1" as="geometry">
|
||||
<mxPoint as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="32" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="21" target="25">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="21" value="业务网关(Restful API grpc-rest)" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="190" y="920" width="530" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="22" value="grpc service<div>app1</div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="190" y="1050" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="23" value="<span style="color: rgb(0, 0, 0);">grpc service</span><div><span style="color: rgb(0, 0, 0);">app2</span></div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="340" y="1050" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="24" value="<span style="color: rgb(0, 0, 0);">grpc service</span><div><span style="color: rgb(0, 0, 0);">app3</span></div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="480" y="1050" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="25" value="<span style="color: rgb(0, 0, 0); font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: center; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(251, 251, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; float: none; display: inline !important;">grpc service</span><div><span style="color: rgb(0, 0, 0); font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: center; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(251, 251, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; float: none; display: inline !important;">app3</span></div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="630" y="1050" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="28" style="edgeStyle=none;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" edge="1" parent="1" source="27" target="21">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="29" value="Restful API" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="28">
|
||||
<mxGeometry x="-0.15" y="-4" relative="1" as="geometry">
|
||||
<mxPoint as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="27" value="Browser" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="190" y="800" width="530" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="33" style="edgeStyle=none;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.425;entryY=1.033;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="23" target="22">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="330" y="1150"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="34" value="GRPC" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="33">
|
||||
<mxGeometry x="-0.2337" y="-2" relative="1" as="geometry">
|
||||
<mxPoint as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
47
skills/rpc/hello_world/server_a/main.go
Normal file
47
skills/rpc/hello_world/server_a/main.go
Normal file
@ -0,0 +1,47 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"net/rpc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 把我们的对象注册成一个rpc的 receiver
|
||||
// 其中rpc.Register函数调用会将对象类型中所有满足RPC规则的对象方法注册为RPC函数,
|
||||
// 所有注册的方法会放在“HelloService”服务空间之下
|
||||
// err := HelloService.FnName(req, resp)
|
||||
rpc.RegisterName("HelloService", new(HelloService))
|
||||
|
||||
// RPC服务器还没启动起来
|
||||
// 然后我们建立一个唯一的TCP链接,
|
||||
listener, err := net.Listen("tcp", ":1234")
|
||||
if err != nil {
|
||||
log.Fatal("ListenTCP error:", err)
|
||||
}
|
||||
|
||||
// 通过rpc.ServeConn函数在该TCP链接上为对方提供RPC服务。
|
||||
// 没Accept一个请求,就创建一个goroutie进行处理
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
log.Fatal("Accept error:", err)
|
||||
}
|
||||
|
||||
// 前面都是tcp的知识, 到这个RPC就接管了
|
||||
// 因此 你可以认为 rpc 帮我们封装消息到函数调用的这个逻辑,
|
||||
// 提升了工作效率, 逻辑比较简洁,可以看看他代码
|
||||
go rpc.ServeConn(conn)
|
||||
}
|
||||
}
|
||||
|
||||
// 要通过 net/rpc 把一个对象的方法 暴露为rpc,需要符合 net/rpc的方法签名: Hello(request string, reply *string) error
|
||||
// FnName(req any, resp *any) error
|
||||
|
||||
type HelloService struct{}
|
||||
|
||||
// HTTP Framwork Handler Hello(request string, reply *string) error
|
||||
func (h *HelloService) Hello(request string, reply *string) error {
|
||||
*reply = "hello " + request
|
||||
return nil
|
||||
}
|
23
skills/rpc/hello_world/server_b/main.go
Normal file
23
skills/rpc/hello_world/server_b/main.go
Normal file
@ -0,0 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"122.51.31.227/go-course/go18/skills/rpc/hello_world/service"
|
||||
)
|
||||
|
||||
func main() {
|
||||
client, err := service.NewHelloServiceClient("localhost:1234")
|
||||
if err != nil {
|
||||
log.Fatal("dialing:", err)
|
||||
}
|
||||
|
||||
// SDK 提供的方法调用
|
||||
reply, err := client.Hello(context.Background(), "bob")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(reply)
|
||||
}
|
45
skills/rpc/hello_world/service/client.go
Normal file
45
skills/rpc/hello_world/service/client.go
Normal file
@ -0,0 +1,45 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/rpc"
|
||||
)
|
||||
|
||||
// 首先是通过rpc.Dial拨号RPC服务, 建立连接
|
||||
// client, err := rpc.Dial("tcp", "localhost:1234")
|
||||
// if err != nil {
|
||||
// log.Fatal("dialing:", err)
|
||||
// }
|
||||
|
||||
// // 然后通过client.Call调用具体的RPC方法
|
||||
// // 在调用client.Call时:
|
||||
// // 第一个参数是用点号链接的RPC服务名字和方法名字,
|
||||
// // 第二个参数是 请求参数
|
||||
// // 第三个是请求响应, 必须是一个指针, 有底层rpc服务帮你赋值
|
||||
// var reply string
|
||||
// // HelloServiceClient.Hello(ctx, req) (resp, error)
|
||||
// err = client.Call("HelloService.Hello", "bob", &reply)
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
|
||||
func NewHelloServiceClient(address string) (HelloService, error) {
|
||||
client, err := rpc.Dial("tcp", address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &HelloServiceClient{client: client}, nil
|
||||
}
|
||||
|
||||
type HelloServiceClient struct {
|
||||
client *rpc.Client
|
||||
}
|
||||
|
||||
func (h *HelloServiceClient) Hello(ctx context.Context, request string) (string, error) {
|
||||
var reply string
|
||||
err := h.client.Call("HelloService.Hello", request, &reply)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return reply, nil
|
||||
}
|
8
skills/rpc/hello_world/service/interface.go
Normal file
8
skills/rpc/hello_world/service/interface.go
Normal file
@ -0,0 +1,8 @@
|
||||
package service
|
||||
|
||||
import "context"
|
||||
|
||||
// RPC 协议,约束服务端的实现,约束 客户端的调用
|
||||
type HelloService interface {
|
||||
Hello(ctx context.Context, request string) (string, error)
|
||||
}
|
166
skills/rpc/protobuf/README.md
Normal file
166
skills/rpc/protobuf/README.md
Normal file
@ -0,0 +1,166 @@
|
||||
# protobuf
|
||||
|
||||
```go
|
||||
// RPC 协议,约束服务端的实现,约束 客户端的调用
|
||||
type HelloService interface {
|
||||
Hello(ctx context.Context, request string) (string, error)
|
||||
}
|
||||
```
|
||||
|
||||
+ RPC方法的定义
|
||||
+ PRC数据结构的定义
|
||||
|
||||
安装vsocde的 proto语法高亮插件: vscode-proto3
|
||||
|
||||
## 插件安装
|
||||
|
||||
```sh
|
||||
➜ protobuf go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
||||
go: downloading google.golang.org/protobuf v1.36.8
|
||||
➜ protobuf go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
||||
go: downloading google.golang.org/grpc v1.75.0
|
||||
```
|
||||
|
||||
|
||||
## 接口声明
|
||||
|
||||
```proto
|
||||
syntax = "proto3";
|
||||
|
||||
package hello;
|
||||
option go_package="122.51.31.227/go-course/go18/skills/rpc/protobuf/hello_service";
|
||||
|
||||
// The HelloService service definition.
|
||||
service HelloService {
|
||||
// rpc 声明接口
|
||||
rpc Hello (Request) returns (Response);
|
||||
}
|
||||
|
||||
message Request {
|
||||
string value = 1;
|
||||
}
|
||||
|
||||
message Response {
|
||||
string value = 1;
|
||||
}
|
||||
```
|
||||
|
||||
## 使用protobuf 定义数据结构
|
||||
|
||||
使用protoc-gen-go 插件来生成struct
|
||||
```proto
|
||||
message Request {
|
||||
string value = 1;
|
||||
}
|
||||
|
||||
message Response {
|
||||
string value = 1;
|
||||
}
|
||||
```
|
||||
|
||||
```sh
|
||||
# -- go_out 是参数, go out: go使用go语言插件(protoc-gen-go), go语言插件的参数 out
|
||||
# out: 生成的代码放哪个目录, 默认项目根目录
|
||||
# opt: module="xxx", 用于指定文件所属go module
|
||||
# 应用使用了module="xxx"最好在工程根目录执行
|
||||
# -I: 指定protobuf include的其他包的搜索位置, 通常情况 项目根目录比较合适
|
||||
# cd go18
|
||||
protoc -I=. --go_out=. --go_opt=module="122.51.31.227/go-course/go18" skills/rpc/protobuf/hello_service/interface.proto
|
||||
```
|
||||
|
||||
|
||||
interface.pb.go
|
||||
```go
|
||||
type Request struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
```
|
||||
|
||||
## 使用protobuf 定义RPC (grpc)
|
||||
|
||||
使用 protoc-gen-go-grpc来生成 接口描述(service定义相关部分)
|
||||
```proto
|
||||
// The HelloService service definition.
|
||||
service HelloService {
|
||||
// rpc 声明接口
|
||||
rpc Hello (Request) returns (Response);
|
||||
}
|
||||
```
|
||||
|
||||
```sh
|
||||
# -- go_out 是参数, go out: go使用go语言插件(protoc-gen-go), go语言插件的参数 out
|
||||
# out: 生成的代码放哪个目录, 默认项目根目录
|
||||
# opt: module="xxx", 用于指定文件所属go module
|
||||
# 应用使用了module="xxx"最好在工程根目录执行
|
||||
# -I: 指定protobuf include的其他包的搜索位置, 通常情况 项目根目录比较合适
|
||||
# cd go18
|
||||
protoc -I=. --go-grpc_out=. --go-grpc_opt=module="122.51.31.227/go-course/go18" skills/rpc/protobuf/hello_service/interface.proto
|
||||
```
|
||||
|
||||
interface_grpc.pb.go
|
||||
```go
|
||||
// 服务端接口
|
||||
// HelloServiceServer is the server API for HelloService service.
|
||||
// All implementations must embed UnimplementedHelloServiceServer
|
||||
// for forward compatibility.
|
||||
//
|
||||
// The HelloService service definition.
|
||||
type HelloServiceServer interface {
|
||||
// rpc 声明接口
|
||||
Hello(context.Context, *Request) (*Response, error)
|
||||
mustEmbedUnimplementedHelloServiceServer()
|
||||
}
|
||||
|
||||
// 客户端接口
|
||||
// HelloServiceClient is the client API for HelloService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
//
|
||||
// The HelloService service definition.
|
||||
type HelloServiceClient interface {
|
||||
// rpc 声明接口
|
||||
Hello(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error)
|
||||
}
|
||||
|
||||
// 客户端的具体实现
|
||||
type helloServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewHelloServiceClient(cc grpc.ClientConnInterface) HelloServiceClient {
|
||||
return &helloServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *helloServiceClient) Hello(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(Response)
|
||||
err := c.cc.Invoke(ctx, HelloService_Hello_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
```
|
||||
|
||||
## 服务端实现接口,注册给grpc服务
|
||||
|
||||
|
||||
|
||||
## 其他服务 使用生成的client,调用grpc服务的方法
|
||||
|
||||
|
||||
## 包
|
||||
|
||||
```sh
|
||||
protoc -I=. --go_out=. --go_opt=module="122.51.31.227/go-course/go18" skills/rpc/protobuf/app_service/interface.proto
|
||||
```
|
165
skills/rpc/protobuf/app_service/interface.pb.go
Normal file
165
skills/rpc/protobuf/app_service/interface.pb.go
Normal file
@ -0,0 +1,165 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.8
|
||||
// protoc v6.32.0
|
||||
// source: skills/rpc/protobuf/app_service/interface.proto
|
||||
|
||||
package app_service
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
|
||||
hello_service "122.51.31.227/go-course/go18/skills/rpc/protobuf/hello_service"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type App struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"`
|
||||
// 通过包名引用其他包里面定义
|
||||
Request *hello_service.Request `protobuf:"bytes,4,opt,name=request,proto3" json:"request,omitempty"`
|
||||
Response *hello_service.Response `protobuf:"bytes,5,opt,name=response,proto3" json:"response,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *App) Reset() {
|
||||
*x = App{}
|
||||
mi := &file_skills_rpc_protobuf_app_service_interface_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *App) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*App) ProtoMessage() {}
|
||||
|
||||
func (x *App) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_skills_rpc_protobuf_app_service_interface_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use App.ProtoReflect.Descriptor instead.
|
||||
func (*App) Descriptor() ([]byte, []int) {
|
||||
return file_skills_rpc_protobuf_app_service_interface_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *App) GetId() string {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *App) GetName() string {
|
||||
if x != nil {
|
||||
return x.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *App) GetDescription() string {
|
||||
if x != nil {
|
||||
return x.Description
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *App) GetRequest() *hello_service.Request {
|
||||
if x != nil {
|
||||
return x.Request
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *App) GetResponse() *hello_service.Response {
|
||||
if x != nil {
|
||||
return x.Response
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_skills_rpc_protobuf_app_service_interface_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_skills_rpc_protobuf_app_service_interface_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"/skills/rpc/protobuf/app_service/interface.proto\x12\x03app\x1a1skills/rpc/protobuf/hello_service/interface.proto\"\xa2\x01\n" +
|
||||
"\x03App\x12\x0e\n" +
|
||||
"\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" +
|
||||
"\x04name\x18\x02 \x01(\tR\x04name\x12 \n" +
|
||||
"\vdescription\x18\x03 \x01(\tR\vdescription\x12(\n" +
|
||||
"\arequest\x18\x04 \x01(\v2\x0e.hello.RequestR\arequest\x12+\n" +
|
||||
"\bresponse\x18\x05 \x01(\v2\x0f.hello.ResponseR\bresponseB>Z<122.51.31.227/go-course/go18/skills/rpc/protobuf/app_serviceb\x06proto3"
|
||||
|
||||
var (
|
||||
file_skills_rpc_protobuf_app_service_interface_proto_rawDescOnce sync.Once
|
||||
file_skills_rpc_protobuf_app_service_interface_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_skills_rpc_protobuf_app_service_interface_proto_rawDescGZIP() []byte {
|
||||
file_skills_rpc_protobuf_app_service_interface_proto_rawDescOnce.Do(func() {
|
||||
file_skills_rpc_protobuf_app_service_interface_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_skills_rpc_protobuf_app_service_interface_proto_rawDesc), len(file_skills_rpc_protobuf_app_service_interface_proto_rawDesc)))
|
||||
})
|
||||
return file_skills_rpc_protobuf_app_service_interface_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_skills_rpc_protobuf_app_service_interface_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_skills_rpc_protobuf_app_service_interface_proto_goTypes = []any{
|
||||
(*App)(nil), // 0: app.App
|
||||
(*hello_service.Request)(nil), // 1: hello.Request
|
||||
(*hello_service.Response)(nil), // 2: hello.Response
|
||||
}
|
||||
var file_skills_rpc_protobuf_app_service_interface_proto_depIdxs = []int32{
|
||||
1, // 0: app.App.request:type_name -> hello.Request
|
||||
2, // 1: app.App.response:type_name -> hello.Response
|
||||
2, // [2:2] is the sub-list for method output_type
|
||||
2, // [2:2] is the sub-list for method input_type
|
||||
2, // [2:2] is the sub-list for extension type_name
|
||||
2, // [2:2] is the sub-list for extension extendee
|
||||
0, // [0:2] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_skills_rpc_protobuf_app_service_interface_proto_init() }
|
||||
func file_skills_rpc_protobuf_app_service_interface_proto_init() {
|
||||
if File_skills_rpc_protobuf_app_service_interface_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_skills_rpc_protobuf_app_service_interface_proto_rawDesc), len(file_skills_rpc_protobuf_app_service_interface_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 1,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_skills_rpc_protobuf_app_service_interface_proto_goTypes,
|
||||
DependencyIndexes: file_skills_rpc_protobuf_app_service_interface_proto_depIdxs,
|
||||
MessageInfos: file_skills_rpc_protobuf_app_service_interface_proto_msgTypes,
|
||||
}.Build()
|
||||
File_skills_rpc_protobuf_app_service_interface_proto = out.File
|
||||
file_skills_rpc_protobuf_app_service_interface_proto_goTypes = nil
|
||||
file_skills_rpc_protobuf_app_service_interface_proto_depIdxs = nil
|
||||
}
|
16
skills/rpc/protobuf/app_service/interface.proto
Normal file
16
skills/rpc/protobuf/app_service/interface.proto
Normal file
@ -0,0 +1,16 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package app;
|
||||
option go_package="122.51.31.227/go-course/go18/skills/rpc/protobuf/app_service";
|
||||
|
||||
// 导入包
|
||||
import "skills/rpc/protobuf/hello_service/interface.proto";
|
||||
|
||||
message App {
|
||||
string id = 1;
|
||||
string name = 2;
|
||||
string description = 3;
|
||||
// 通过包名引用其他包里面定义
|
||||
hello.Request request = 4;
|
||||
hello.Response response = 5;
|
||||
}
|
56
skills/rpc/protobuf/hello_service/client_auther.go
Normal file
56
skills/rpc/protobuf/hello_service/client_auther.go
Normal file
@ -0,0 +1,56 @@
|
||||
package hello_service
|
||||
|
||||
import context "context"
|
||||
|
||||
// PerRPCCredentials defines the common interface for the credentials which need to
|
||||
// attach security information to every RPC (e.g., oauth2).
|
||||
// type PerRPCCredentials interface {
|
||||
// // GetRequestMetadata gets the current request metadata, refreshing tokens
|
||||
// // if required. This should be called by the transport layer on each
|
||||
// // request, and the data should be populated in headers or other
|
||||
// // context. If a status code is returned, it will be used as the status for
|
||||
// // the RPC (restricted to an allowable set of codes as defined by gRFC
|
||||
// // A54). uri is the URI of the entry point for the request. When supported
|
||||
// // by the underlying implementation, ctx can be used for timeout and
|
||||
// // cancellation. Additionally, RequestInfo data will be available via ctx
|
||||
// // to this call. TODO(zhaoq): Define the set of the qualified keys instead
|
||||
// // of leaving it as an arbitrary string.
|
||||
// GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
|
||||
// // RequireTransportSecurity indicates whether the credentials requires
|
||||
// // transport security.
|
||||
// RequireTransportSecurity() bool
|
||||
// }
|
||||
|
||||
func NewClientAuthentication(clientId, clientSecret string) *Authentication {
|
||||
return &Authentication{
|
||||
clientID: clientId,
|
||||
clientSecret: clientSecret,
|
||||
}
|
||||
}
|
||||
|
||||
// Authentication todo
|
||||
type Authentication struct {
|
||||
clientID string
|
||||
clientSecret string
|
||||
}
|
||||
|
||||
// WithClientCredentials todo
|
||||
func (a *Authentication) WithClientCredentials(clientID, clientSecret string) {
|
||||
a.clientID = clientID
|
||||
a.clientSecret = clientSecret
|
||||
}
|
||||
|
||||
// GetRequestMetadata todo
|
||||
func (a *Authentication) GetRequestMetadata(context.Context, ...string) (
|
||||
map[string]string, error,
|
||||
) {
|
||||
return map[string]string{
|
||||
ClientHeaderKey: a.clientID,
|
||||
ClientSecretKey: a.clientSecret,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RequireTransportSecurity todo
|
||||
func (a *Authentication) RequireTransportSecurity() bool {
|
||||
return false
|
||||
}
|
382
skills/rpc/protobuf/hello_service/interface.pb.go
Normal file
382
skills/rpc/protobuf/hello_service/interface.pb.go
Normal file
@ -0,0 +1,382 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.8
|
||||
// protoc v6.32.0
|
||||
// source: skills/rpc/protobuf/hello_service/interface.proto
|
||||
|
||||
package hello_service
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
anypb "google.golang.org/protobuf/types/known/anypb"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type Corpus int32
|
||||
|
||||
const (
|
||||
Corpus_UNIVERSAL Corpus = 0
|
||||
Corpus_WEB Corpus = 1
|
||||
Corpus_IMAGES Corpus = 2
|
||||
Corpus_LOCAL Corpus = 3
|
||||
Corpus_NEWS Corpus = 4
|
||||
Corpus_PRODUCTS Corpus = 5
|
||||
Corpus_VIDEO Corpus = 6
|
||||
)
|
||||
|
||||
// Enum value maps for Corpus.
|
||||
var (
|
||||
Corpus_name = map[int32]string{
|
||||
0: "UNIVERSAL",
|
||||
1: "WEB",
|
||||
2: "IMAGES",
|
||||
3: "LOCAL",
|
||||
4: "NEWS",
|
||||
5: "PRODUCTS",
|
||||
6: "VIDEO",
|
||||
}
|
||||
Corpus_value = map[string]int32{
|
||||
"UNIVERSAL": 0,
|
||||
"WEB": 1,
|
||||
"IMAGES": 2,
|
||||
"LOCAL": 3,
|
||||
"NEWS": 4,
|
||||
"PRODUCTS": 5,
|
||||
"VIDEO": 6,
|
||||
}
|
||||
)
|
||||
|
||||
func (x Corpus) Enum() *Corpus {
|
||||
p := new(Corpus)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x Corpus) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (Corpus) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_skills_rpc_protobuf_hello_service_interface_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (Corpus) Type() protoreflect.EnumType {
|
||||
return &file_skills_rpc_protobuf_hello_service_interface_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x Corpus) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Corpus.Descriptor instead.
|
||||
func (Corpus) EnumDescriptor() ([]byte, []int) {
|
||||
return file_skills_rpc_protobuf_hello_service_interface_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
|
||||
// map
|
||||
Extras map[string]string `protobuf:"bytes,2,rep,name=extras,proto3" json:"extras,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Request) Reset() {
|
||||
*x = Request{}
|
||||
mi := &file_skills_rpc_protobuf_hello_service_interface_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Request) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Request) ProtoMessage() {}
|
||||
|
||||
func (x *Request) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_skills_rpc_protobuf_hello_service_interface_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Request.ProtoReflect.Descriptor instead.
|
||||
func (*Request) Descriptor() ([]byte, []int) {
|
||||
return file_skills_rpc_protobuf_hello_service_interface_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Request) GetValue() string {
|
||||
if x != nil {
|
||||
return x.Value
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Request) GetExtras() map[string]string {
|
||||
if x != nil {
|
||||
return x.Extras
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type RequestSet struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Total int64 `protobuf:"varint,1,opt,name=total,proto3" json:"total,omitempty"`
|
||||
// repeated 来描述一个数组
|
||||
Items []*Request `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *RequestSet) Reset() {
|
||||
*x = RequestSet{}
|
||||
mi := &file_skills_rpc_protobuf_hello_service_interface_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *RequestSet) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*RequestSet) ProtoMessage() {}
|
||||
|
||||
func (x *RequestSet) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_skills_rpc_protobuf_hello_service_interface_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use RequestSet.ProtoReflect.Descriptor instead.
|
||||
func (*RequestSet) Descriptor() ([]byte, []int) {
|
||||
return file_skills_rpc_protobuf_hello_service_interface_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *RequestSet) GetTotal() int64 {
|
||||
if x != nil {
|
||||
return x.Total
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *RequestSet) GetItems() []*Request {
|
||||
if x != nil {
|
||||
return x.Items
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Response) Reset() {
|
||||
*x = Response{}
|
||||
mi := &file_skills_rpc_protobuf_hello_service_interface_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Response) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Response) ProtoMessage() {}
|
||||
|
||||
func (x *Response) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_skills_rpc_protobuf_hello_service_interface_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Response.ProtoReflect.Descriptor instead.
|
||||
func (*Response) Descriptor() ([]byte, []int) {
|
||||
return file_skills_rpc_protobuf_hello_service_interface_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *Response) GetValue() string {
|
||||
if x != nil {
|
||||
return x.Value
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ErrorStatus struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
|
||||
Details []*anypb.Any `protobuf:"bytes,2,rep,name=details,proto3" json:"details,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ErrorStatus) Reset() {
|
||||
*x = ErrorStatus{}
|
||||
mi := &file_skills_rpc_protobuf_hello_service_interface_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ErrorStatus) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ErrorStatus) ProtoMessage() {}
|
||||
|
||||
func (x *ErrorStatus) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_skills_rpc_protobuf_hello_service_interface_proto_msgTypes[3]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ErrorStatus.ProtoReflect.Descriptor instead.
|
||||
func (*ErrorStatus) Descriptor() ([]byte, []int) {
|
||||
return file_skills_rpc_protobuf_hello_service_interface_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *ErrorStatus) GetMessage() string {
|
||||
if x != nil {
|
||||
return x.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ErrorStatus) GetDetails() []*anypb.Any {
|
||||
if x != nil {
|
||||
return x.Details
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_skills_rpc_protobuf_hello_service_interface_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_skills_rpc_protobuf_hello_service_interface_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"1skills/rpc/protobuf/hello_service/interface.proto\x12\x05hello\x1a\x19google/protobuf/any.proto\"\x8e\x01\n" +
|
||||
"\aRequest\x12\x14\n" +
|
||||
"\x05value\x18\x01 \x01(\tR\x05value\x122\n" +
|
||||
"\x06extras\x18\x02 \x03(\v2\x1a.hello.Request.ExtrasEntryR\x06extras\x1a9\n" +
|
||||
"\vExtrasEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"H\n" +
|
||||
"\n" +
|
||||
"RequestSet\x12\x14\n" +
|
||||
"\x05total\x18\x01 \x01(\x03R\x05total\x12$\n" +
|
||||
"\x05items\x18\x02 \x03(\v2\x0e.hello.RequestR\x05items\" \n" +
|
||||
"\bResponse\x12\x14\n" +
|
||||
"\x05value\x18\x01 \x01(\tR\x05value\"W\n" +
|
||||
"\vErrorStatus\x12\x18\n" +
|
||||
"\amessage\x18\x01 \x01(\tR\amessage\x12.\n" +
|
||||
"\adetails\x18\x02 \x03(\v2\x14.google.protobuf.AnyR\adetails*Z\n" +
|
||||
"\x06Corpus\x12\r\n" +
|
||||
"\tUNIVERSAL\x10\x00\x12\a\n" +
|
||||
"\x03WEB\x10\x01\x12\n" +
|
||||
"\n" +
|
||||
"\x06IMAGES\x10\x02\x12\t\n" +
|
||||
"\x05LOCAL\x10\x03\x12\b\n" +
|
||||
"\x04NEWS\x10\x04\x12\f\n" +
|
||||
"\bPRODUCTS\x10\x05\x12\t\n" +
|
||||
"\x05VIDEO\x10\x062j\n" +
|
||||
"\fHelloService\x12(\n" +
|
||||
"\x05Hello\x12\x0e.hello.Request\x1a\x0f.hello.Response\x120\n" +
|
||||
"\aChannel\x12\x0e.hello.Request\x1a\x0f.hello.Response\"\x00(\x010\x01B@Z>122.51.31.227/go-course/go18/skills/rpc/protobuf/hello_serviceb\x06proto3"
|
||||
|
||||
var (
|
||||
file_skills_rpc_protobuf_hello_service_interface_proto_rawDescOnce sync.Once
|
||||
file_skills_rpc_protobuf_hello_service_interface_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_skills_rpc_protobuf_hello_service_interface_proto_rawDescGZIP() []byte {
|
||||
file_skills_rpc_protobuf_hello_service_interface_proto_rawDescOnce.Do(func() {
|
||||
file_skills_rpc_protobuf_hello_service_interface_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_skills_rpc_protobuf_hello_service_interface_proto_rawDesc), len(file_skills_rpc_protobuf_hello_service_interface_proto_rawDesc)))
|
||||
})
|
||||
return file_skills_rpc_protobuf_hello_service_interface_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_skills_rpc_protobuf_hello_service_interface_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_skills_rpc_protobuf_hello_service_interface_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
||||
var file_skills_rpc_protobuf_hello_service_interface_proto_goTypes = []any{
|
||||
(Corpus)(0), // 0: hello.Corpus
|
||||
(*Request)(nil), // 1: hello.Request
|
||||
(*RequestSet)(nil), // 2: hello.RequestSet
|
||||
(*Response)(nil), // 3: hello.Response
|
||||
(*ErrorStatus)(nil), // 4: hello.ErrorStatus
|
||||
nil, // 5: hello.Request.ExtrasEntry
|
||||
(*anypb.Any)(nil), // 6: google.protobuf.Any
|
||||
}
|
||||
var file_skills_rpc_protobuf_hello_service_interface_proto_depIdxs = []int32{
|
||||
5, // 0: hello.Request.extras:type_name -> hello.Request.ExtrasEntry
|
||||
1, // 1: hello.RequestSet.items:type_name -> hello.Request
|
||||
6, // 2: hello.ErrorStatus.details:type_name -> google.protobuf.Any
|
||||
1, // 3: hello.HelloService.Hello:input_type -> hello.Request
|
||||
1, // 4: hello.HelloService.Channel:input_type -> hello.Request
|
||||
3, // 5: hello.HelloService.Hello:output_type -> hello.Response
|
||||
3, // 6: hello.HelloService.Channel:output_type -> hello.Response
|
||||
5, // [5:7] is the sub-list for method output_type
|
||||
3, // [3:5] is the sub-list for method input_type
|
||||
3, // [3:3] is the sub-list for extension type_name
|
||||
3, // [3:3] is the sub-list for extension extendee
|
||||
0, // [0:3] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_skills_rpc_protobuf_hello_service_interface_proto_init() }
|
||||
func file_skills_rpc_protobuf_hello_service_interface_proto_init() {
|
||||
if File_skills_rpc_protobuf_hello_service_interface_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_skills_rpc_protobuf_hello_service_interface_proto_rawDesc), len(file_skills_rpc_protobuf_hello_service_interface_proto_rawDesc)),
|
||||
NumEnums: 1,
|
||||
NumMessages: 5,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_skills_rpc_protobuf_hello_service_interface_proto_goTypes,
|
||||
DependencyIndexes: file_skills_rpc_protobuf_hello_service_interface_proto_depIdxs,
|
||||
EnumInfos: file_skills_rpc_protobuf_hello_service_interface_proto_enumTypes,
|
||||
MessageInfos: file_skills_rpc_protobuf_hello_service_interface_proto_msgTypes,
|
||||
}.Build()
|
||||
File_skills_rpc_protobuf_hello_service_interface_proto = out.File
|
||||
file_skills_rpc_protobuf_hello_service_interface_proto_goTypes = nil
|
||||
file_skills_rpc_protobuf_hello_service_interface_proto_depIdxs = nil
|
||||
}
|
46
skills/rpc/protobuf/hello_service/interface.proto
Normal file
46
skills/rpc/protobuf/hello_service/interface.proto
Normal file
@ -0,0 +1,46 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package hello;
|
||||
option go_package="122.51.31.227/go-course/go18/skills/rpc/protobuf/hello_service";
|
||||
|
||||
// 这里是应用其他的proto文件, 后面会讲 ipmort用法
|
||||
import "google/protobuf/any.proto";
|
||||
|
||||
// The HelloService service definition.
|
||||
service HelloService {
|
||||
// rpc 声明接口
|
||||
rpc Hello (Request) returns (Response);
|
||||
|
||||
rpc Channel (stream Request) returns (stream Response) {}
|
||||
}
|
||||
|
||||
message Request {
|
||||
string value = 1;
|
||||
// map
|
||||
map<string, string> extras = 2;
|
||||
}
|
||||
|
||||
message RequestSet {
|
||||
int64 total = 1;
|
||||
// repeated 来描述一个数组
|
||||
repeated Request items = 2;
|
||||
}
|
||||
|
||||
message Response {
|
||||
string value = 1;
|
||||
}
|
||||
|
||||
enum Corpus {
|
||||
UNIVERSAL = 0;
|
||||
WEB = 1;
|
||||
IMAGES = 2;
|
||||
LOCAL = 3;
|
||||
NEWS = 4;
|
||||
PRODUCTS = 5;
|
||||
VIDEO = 6;
|
||||
}
|
||||
|
||||
message ErrorStatus {
|
||||
string message = 1;
|
||||
repeated google.protobuf.Any details = 2;
|
||||
}
|
160
skills/rpc/protobuf/hello_service/interface_grpc.pb.go
Normal file
160
skills/rpc/protobuf/hello_service/interface_grpc.pb.go
Normal file
@ -0,0 +1,160 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v6.32.0
|
||||
// source: skills/rpc/protobuf/hello_service/interface.proto
|
||||
|
||||
package hello_service
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.64.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion9
|
||||
|
||||
const (
|
||||
HelloService_Hello_FullMethodName = "/hello.HelloService/Hello"
|
||||
HelloService_Channel_FullMethodName = "/hello.HelloService/Channel"
|
||||
)
|
||||
|
||||
// HelloServiceClient is the client API for HelloService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
//
|
||||
// The HelloService service definition.
|
||||
type HelloServiceClient interface {
|
||||
// rpc 声明接口
|
||||
Hello(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error)
|
||||
Channel(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[Request, Response], error)
|
||||
}
|
||||
|
||||
type helloServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewHelloServiceClient(cc grpc.ClientConnInterface) HelloServiceClient {
|
||||
return &helloServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *helloServiceClient) Hello(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(Response)
|
||||
err := c.cc.Invoke(ctx, HelloService_Hello_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *helloServiceClient) Channel(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[Request, Response], error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &HelloService_ServiceDesc.Streams[0], HelloService_Channel_FullMethodName, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &grpc.GenericClientStream[Request, Response]{ClientStream: stream}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type HelloService_ChannelClient = grpc.BidiStreamingClient[Request, Response]
|
||||
|
||||
// HelloServiceServer is the server API for HelloService service.
|
||||
// All implementations must embed UnimplementedHelloServiceServer
|
||||
// for forward compatibility.
|
||||
//
|
||||
// The HelloService service definition.
|
||||
type HelloServiceServer interface {
|
||||
// rpc 声明接口
|
||||
Hello(context.Context, *Request) (*Response, error)
|
||||
Channel(grpc.BidiStreamingServer[Request, Response]) error
|
||||
mustEmbedUnimplementedHelloServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedHelloServiceServer must be embedded to have
|
||||
// forward compatible implementations.
|
||||
//
|
||||
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||
// pointer dereference when methods are called.
|
||||
type UnimplementedHelloServiceServer struct{}
|
||||
|
||||
func (UnimplementedHelloServiceServer) Hello(context.Context, *Request) (*Response, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Hello not implemented")
|
||||
}
|
||||
func (UnimplementedHelloServiceServer) Channel(grpc.BidiStreamingServer[Request, Response]) error {
|
||||
return status.Errorf(codes.Unimplemented, "method Channel not implemented")
|
||||
}
|
||||
func (UnimplementedHelloServiceServer) mustEmbedUnimplementedHelloServiceServer() {}
|
||||
func (UnimplementedHelloServiceServer) testEmbeddedByValue() {}
|
||||
|
||||
// UnsafeHelloServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to HelloServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeHelloServiceServer interface {
|
||||
mustEmbedUnimplementedHelloServiceServer()
|
||||
}
|
||||
|
||||
func RegisterHelloServiceServer(s grpc.ServiceRegistrar, srv HelloServiceServer) {
|
||||
// If the following call pancis, it indicates UnimplementedHelloServiceServer was
|
||||
// embedded by pointer and is nil. This will cause panics if an
|
||||
// unimplemented method is ever invoked, so we test this at initialization
|
||||
// time to prevent it from happening at runtime later due to I/O.
|
||||
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||
t.testEmbeddedByValue()
|
||||
}
|
||||
s.RegisterService(&HelloService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _HelloService_Hello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Request)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(HelloServiceServer).Hello(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: HelloService_Hello_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(HelloServiceServer).Hello(ctx, req.(*Request))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _HelloService_Channel_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
return srv.(HelloServiceServer).Channel(&grpc.GenericServerStream[Request, Response]{ServerStream: stream})
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type HelloService_ChannelServer = grpc.BidiStreamingServer[Request, Response]
|
||||
|
||||
// HelloService_ServiceDesc is the grpc.ServiceDesc for HelloService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var HelloService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "hello.HelloService",
|
||||
HandlerType: (*HelloServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "Hello",
|
||||
Handler: _HelloService_Hello_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "Channel",
|
||||
Handler: _HelloService_Channel_Handler,
|
||||
ServerStreams: true,
|
||||
ClientStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "skills/rpc/protobuf/hello_service/interface.proto",
|
||||
}
|
35
skills/rpc/protobuf/hello_service/interface_test.go
Normal file
35
skills/rpc/protobuf/hello_service/interface_test.go
Normal file
@ -0,0 +1,35 @@
|
||||
package hello_service_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"122.51.31.227/go-course/go18/skills/rpc/protobuf/hello_service"
|
||||
"google.golang.org/protobuf/types/known/anypb"
|
||||
)
|
||||
|
||||
func TestAny(t *testing.T) {
|
||||
status := hello_service.ErrorStatus{
|
||||
Details: []*anypb.Any{},
|
||||
}
|
||||
|
||||
d1 := hello_service.Request{Value: "test"}
|
||||
any1, err := anypb.New(&d1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
d2 := hello_service.Response{Value: "test"}
|
||||
any2, err := anypb.New(&d2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
status.Details = append(status.Details, any1, any2)
|
||||
|
||||
t.Log(status)
|
||||
|
||||
target := hello_service.Request{}
|
||||
if err := status.Details[0].UnmarshalTo(&target); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(target.Value)
|
||||
}
|
90
skills/rpc/protobuf/hello_service/middleware.go
Normal file
90
skills/rpc/protobuf/hello_service/middleware.go
Normal file
@ -0,0 +1,90 @@
|
||||
package hello_service
|
||||
|
||||
import (
|
||||
context "context"
|
||||
"fmt"
|
||||
|
||||
"github.com/infraboard/mcube/v2/ioc/config/log"
|
||||
"github.com/rs/zerolog"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
const (
|
||||
ClientHeaderKey = "client-id"
|
||||
ClientSecretKey = "client-secret"
|
||||
)
|
||||
|
||||
// GrpcAuthUnaryServerInterceptor returns a new unary server interceptor for auth.
|
||||
func GrpcAuthUnaryServerInterceptor() grpc.UnaryServerInterceptor {
|
||||
return newGrpcAuther().Auth
|
||||
}
|
||||
|
||||
func newGrpcAuther() *grpcAuther {
|
||||
return &grpcAuther{
|
||||
log: log.Sub("Grpc Auther"),
|
||||
}
|
||||
}
|
||||
|
||||
// UnaryServerInterceptor provides a hook to intercept the execution of a unary RPC on the server. info
|
||||
// contains all the information of this RPC the interceptor can operate on. And handler is the wrapper
|
||||
// of the service method implementation. It is the responsibility of the interceptor to invoke handler
|
||||
// to complete the RPC.
|
||||
// type UnaryServerInterceptor func(ctx context.Context, req any, info *UnaryServerInfo, handler UnaryHandler) (resp any, err error)
|
||||
// internal todo
|
||||
type grpcAuther struct {
|
||||
log *zerolog.Logger
|
||||
}
|
||||
|
||||
func (a *grpcAuther) Auth(
|
||||
ctx context.Context, req any,
|
||||
info *grpc.UnaryServerInfo,
|
||||
handler grpc.UnaryHandler,
|
||||
) (resp any, err error) {
|
||||
// http2 header -> metadata
|
||||
// 重上下文中获取认证信息
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("ctx is not an grpc incoming context")
|
||||
}
|
||||
|
||||
fmt.Println("gprc header info: ", md)
|
||||
|
||||
clientId, clientSecret := a.GetClientCredentialsFromMeta(md)
|
||||
|
||||
// 校验调用的客户端凭证是否有效
|
||||
if err := a.validateServiceCredential(clientId, clientSecret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 把请求交给后续的Handler处理
|
||||
resp, err = handler(ctx, req)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (a *grpcAuther) GetClientCredentialsFromMeta(md metadata.MD) (
|
||||
clientId, clientSecret string) {
|
||||
cids := md.Get(ClientHeaderKey)
|
||||
sids := md.Get(ClientSecretKey)
|
||||
if len(cids) > 0 {
|
||||
clientId = cids[0]
|
||||
}
|
||||
if len(sids) > 0 {
|
||||
clientSecret = sids[0]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (a *grpcAuther) validateServiceCredential(clientId, clientSecret string) error {
|
||||
if clientId == "" && clientSecret == "" {
|
||||
return status.Errorf(codes.Unauthenticated, "client_id or client_secret is \"\"")
|
||||
}
|
||||
|
||||
if !(clientId == "admin" && clientSecret == "123456") {
|
||||
return status.Errorf(codes.Unauthenticated, "client_id or client_secret invalidate")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
63
skills/rpc/protobuf/service_a/main.go
Normal file
63
skills/rpc/protobuf/service_a/main.go
Normal file
@ -0,0 +1,63 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"122.51.31.227/go-course/go18/skills/rpc/protobuf/hello_service"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 首先是通过grpc.NewServer()构造一个gRPC服务对象
|
||||
// 补充了全局的认证中间件
|
||||
grpcServer := grpc.NewServer(grpc.ChainUnaryInterceptor(hello_service.GrpcAuthUnaryServerInterceptor()))
|
||||
|
||||
// SDK 提供 服务实现对象的注册
|
||||
hello_service.RegisterHelloServiceServer(grpcServer, &HelloService{})
|
||||
|
||||
lis, err := net.Listen("tcp", ":1234")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// 然后通过grpcServer.Serve(lis)在一个监听端口上提供gRPC服务
|
||||
grpcServer.Serve(lis)
|
||||
}
|
||||
|
||||
var _ hello_service.HelloServiceServer = (*HelloService)(nil)
|
||||
|
||||
// 实现一个GRPC的对象, 并行实现了 HelloServiceServer 接口
|
||||
// 该对象就可以注册给GRPC框架
|
||||
type HelloService struct {
|
||||
hello_service.UnimplementedHelloServiceServer
|
||||
}
|
||||
|
||||
func (h *HelloService) Hello(ctx context.Context, req *hello_service.Request) (*hello_service.Response, error) {
|
||||
return &hello_service.Response{
|
||||
Value: "Hello " + req.Value,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 接收来自客户端的流式请求,然后不断返回
|
||||
func (h *HelloService) Channel(stream grpc.BidiStreamingServer[hello_service.Request, hello_service.Response]) error {
|
||||
for {
|
||||
// 读取客户端发送过来的数据
|
||||
msg, err := stream.Recv()
|
||||
if err != nil {
|
||||
// 如果遇到io.EOF表示客户端流被关闭
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
// 处理消息并返回响应
|
||||
if err := stream.Send(&hello_service.Response{
|
||||
Value: "Hello " + msg.Value,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
66
skills/rpc/protobuf/service_b/main.go
Normal file
66
skills/rpc/protobuf/service_b/main.go
Normal file
@ -0,0 +1,66 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"122.51.31.227/go-course/go18/skills/rpc/protobuf/hello_service"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// grpc.Dial负责和gRPC服务建立链接
|
||||
conn, err := grpc.NewClient("localhost:1234",
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
grpc.WithPerRPCCredentials(hello_service.NewClientAuthentication(
|
||||
"admin",
|
||||
"123456",
|
||||
)))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// 使用SDK调用远程函数
|
||||
helloServiceClient := hello_service.NewHelloServiceClient(conn)
|
||||
resp, err := helloServiceClient.Hello(context.Background(), &hello_service.Request{
|
||||
Value: "bob",
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(resp.Value)
|
||||
|
||||
stream, err := helloServiceClient.Channel(context.Background())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 首先是向服务端发送数据
|
||||
go func() {
|
||||
count := 1
|
||||
for {
|
||||
if err := stream.Send(&hello_service.Request{Value: fmt.Sprintf("[%d] hi", count)}); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
count++
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}()
|
||||
|
||||
// 然后在循环中接收服务端返回的数据
|
||||
for {
|
||||
reply, err := stream.Recv()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(reply.GetValue())
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user