用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/tools-only/X-Skills --skill generate-grpc-service命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
Index of Build Systems Skills
Coordination patterns for distributed dataflow systems including barriers, epochs, and distributed snapshots
Windowing, sessionization, time-series aggregation, and late data handling for streaming systems
基于 SOC 职业分类
正在显示 SKILL.md
| name | generate-grpc-service |
| description | Generate production-ready gRPC services with Protocol Buffers |
| shortcut | grpc |
Automatically generate high-performance gRPC services with Protocol Buffer definitions, streaming support, load balancing, and comprehensive service implementations for multiple programming languages.
Use /generate-grpc-service when you need to:
DON'T use this when:
This command implements gRPC with Protocol Buffers v3 as the primary approach because:
Alternative considered: Apache Thrift
Alternative considered: GraphQL with subscriptions
Before running this command:
Create comprehensive .proto files with service definitions and message types.
Compile Protocol Buffers to target language code with gRPC plugins.
Build server-side implementations for all RPC methods.
Implement cross-cutting concerns like auth, logging, and error handling.
Set up TLS, connection pooling, and load balancing.
The command generates:
proto/service.proto - Protocol Buffer definitionsserver/ - Server implementation with all RPC methodsclient/ - Client library with connection managementinterceptors/ - Authentication, logging, metrics interceptorsconfig/ - TLS certificates and configurationdocs/api.md - Service documentation// proto/ecommerce.proto
syntax = "proto3";
package ecommerce.v1;
import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";
// Service definition with all RPC patterns
service ProductService {
// Unary RPC
rpc GetProduct(GetProductRequest) returns (Product);
// Server streaming
rpc ListProducts(ListProductsRequest) returns (stream Product);
// Client streaming
rpc ImportProducts(stream Product) returns (ImportSummary);
// Bidirectional streaming
rpc WatchInventory(stream InventoryUpdate) returns (stream InventoryChange);
// Batch operations
rpc BatchGetProducts(BatchGetProductsRequest) returns (BatchGetProductsResponse);
}
// Message definitions
message Product {
string id = 1;
string name = 2;
string description = 3;
double price = 4;
int32 inventory = 5;
repeated string categories = 6;
map<string, string> metadata = 7;
google.protobuf.Timestamp created_at = 8;
google.protobuf.Timestamp updated_at = 9;
enum Status {
STATUS_UNSPECIFIED = 0;
STATUS_ACTIVE = 1;
STATUS_DISCONTINUED = 2;
STATUS_OUT_OF_STOCK = 3;
}
Status status = 10;
}
message GetProductRequest {
string product_id = 1;
repeated string fields = 2; // Field mask for partial responses
}
message ListProductsRequest {
string category = 1;
int32 page_size = 2;
string page_token = 3;
string order_by = 4;
message Filter {
double min_price = 1;
double max_price = 2;
repeated string tags = 3;
}
Filter filter = 5;
}
message ImportSummary {
int32 total_received = 1;
int32 successful = 2;
int32 failed = 3;
repeated ImportError errors = 4;
}
message ImportError {
int32 index = 1;
string product_id = 2;
string error = 3;
}
message InventoryUpdate {
string product_id = 1;
int32 quantity_change = 2;
string warehouse_id = 3;
}
message InventoryChange {
string product_id = 1;
int32 old_quantity = 2;
int32 new_quantity = 3;
google.protobuf.Timestamp timestamp = 4;
string triggered_by = 5;
}
message BatchGetProductsRequest {
repeated string product_ids = 1;
repeated string fields = 2;
}
message BatchGetProductsResponse {
repeated Product products = 1;
repeated string not_found = 2;
}
// server/main.go - Go server implementation
package main
import (
"context"
"crypto/tls"
"fmt"
"io"
"log"
"net"
"sync"
"time"
pb "github.com/company/ecommerce/proto"
"github.com/golang/protobuf/ptypes/empty"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
)
type productServer struct {
pb.UnimplementedProductServiceServer
mu sync.RWMutex
products map[string]*pb.Product
watchers map[string]chan *pb.InventoryChange
}
// Unary RPC implementation
func (s *productServer) GetProduct(
ctx context.Context,
req *pb.GetProductRequest,
) (*pb.Product, error) {
// Extract metadata for tracing
if md, ok := metadata.FromIncomingContext(ctx); ok {
if traceID := md.Get("trace-id"); len(traceID) > 0 {
log.Printf("GetProduct request - trace: %s", traceID[0])
}
}
s.mu.RLock()
product, exists := s.products[req.ProductId]
s.mu.RUnlock()
if !exists {
return , status.Errorf(
codes.NotFound,
,
req.ProductId,
)
}
(req.Fields) > {
applyFieldMask(product, req.Fields),
}
product,
}
ListProducts(
req *pb.ListProductsRequest,
stream pb.ProductService_ListProductsServer,
) {
s.mu.RLock()
s.mu.RUnlock()
count :=
_, product := s.products {
!matchesFilter(product, req) {
}
err := stream.Send(product); err != {
status.Errorf(
codes.Internal,
,
err,
)
}
count++
req.PageSize > && count >= (req.PageSize) {
}
time.Sleep( * time.Millisecond)
}
}
ImportProducts(
stream pb.ProductService_ImportProductsServer,
) {
summary pb.ImportSummary
errors []*pb.ImportError
index :=
{
product, err := stream.Recv()
err == io.EOF {
summary.Errors = errors
stream.SendAndClose(&summary)
}
err != {
status.Errorf(
codes.Internal,
,
err,
)
}
summary.TotalReceived++
err := validateProduct(product); err != {
summary.Failed++
errors = (errors, &pb.ImportError{
Index: (index),
ProductId: product.Id,
Error: err.Error(),
})
} {
s.mu.Lock()
s.products[product.Id] = product
s.mu.Unlock()
summary.Successful++
}
index++
}
}
WatchInventory(
stream pb.ProductService_WatchInventoryServer,
) {
changeChan := ( *pb.InventoryChange, )
clientID := generateClientID()
s.mu.Lock()
s.watchers[clientID] = changeChan
s.mu.Unlock()
{
s.mu.Lock()
(s.watchers, clientID)
s.mu.Unlock()
(changeChan)
}()
errChan := ( , )
{
{
update, err := stream.Recv()
err == io.EOF {
errChan <-
}
err != {
errChan <- err
}
err := s.processInventoryUpdate(update); err != {
log.Printf(, err)
}
change := &pb.InventoryChange{
ProductId: update.ProductId,
NewQuantity: s.getInventory(update.ProductId),
Timestamp: timestamppb.Now(),
TriggeredBy: clientID,
}
s.broadcastChange(change)
}
}()
{
change := changeChan {
err := stream.Send(change); err != {
errChan <- err
}
}
}()
<-errChan
}
({}, ) {
md, ok := metadata.FromIncomingContext(ctx)
!ok {
, status.Error(codes.Unauthenticated, )
}
tokens := md.Get()
(tokens) == {
, status.Error(codes.Unauthenticated, )
}
!isValidToken(tokens[]) {
, status.Error(codes.Unauthenticated, )
}
handler(ctx, req)
}
{
cert, err := tls.LoadX509KeyPair(, )
err != {
log.Fatalf(, err)
}
config := &tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequireAndVerifyClientCert,
}
creds := credentials.NewTLS(config)
opts := []grpc.ServerOption{
grpc.Creds(creds),
grpc.UnaryInterceptor(authInterceptor),
grpc.KeepaliveParams(keepalive.ServerParameters{
MaxConnectionIdle: * time.Minute,
Time: * time.Minute,
Timeout: * time.Second,
}),
grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
MinTime: * time.Second,
PermitWithoutStream: ,
}),
grpc.MaxConcurrentStreams(),
}
server := grpc.NewServer(opts...)
pb.RegisterProductServiceServer(server, &productServer{
products: ([]*pb.Product),
watchers: ([] *pb.InventoryChange),
})
lis, err := net.Listen(, )
err != {
log.Fatalf(, err)
}
log.Println()
err := server.Serve(lis); err != {
log.Fatalf(, err)
}
}
# client/product_client.py
import grpc
import asyncio
import logging
from typing import List, Optional, AsyncIterator
from concurrent import futures
from grpc import aio
import backoff
from proto import ecommerce_pb2 as pb
from proto import ecommerce_pb2_grpc as pb_grpc
logger = logging.getLogger(__name__)
class ProductClient:
"""Enhanced gRPC client with retry, load balancing, and connection pooling."""
def __init__(
self,
servers: List[str],
api_key: Optional[str] = None,
use_tls: bool = True,
pool_size: int = 10
):
self.servers = servers
self.api_key = api_key
self.use_tls = use_tls
self.pool_size = pool_size
self.channels = []
self.stubs = []
self._round_robin_counter = 0
self._setup_channels()
def _setup_channels(self):
server .servers:
_ (.pool_size // (.servers)):
.use_tls:
(, ) f:
client_cert = f.read()
(, ) f:
client_key = f.read()
(, ) f:
ca_cert = f.read()
credentials = grpc.ssl_channel_credentials(
root_certificates=ca_cert,
private_key=client_key,
certificate_chain=client_cert
)
channel = aio.secure_channel(
server,
credentials,
options=[
(, ),
(, ),
(, ),
(, ),
]
)
:
channel = aio.insecure_channel(
server,
options=[
(, ),
(, ),
]
)
.channels.append(channel)
.stubs.append(pb_grpc.ProductServiceStub(channel))
() -> pb_grpc.ProductServiceStub:
stub = .stubs[._round_robin_counter]
._round_robin_counter = (._round_robin_counter + ) % (.stubs)
stub
() -> []:
metadata = []
.api_key:
metadata.append((, ))
metadata.append((, ._generate_trace_id()))
metadata
() -> pb.Product:
request = pb.GetProductRequest(
product_id=product_id,
fields=fields []
)
:
response = ._get_stub().GetProduct(
request,
metadata=._get_metadata(),
timeout=
)
response
grpc.RpcError e:
logger.error()
() -> AsyncIterator[pb.Product]:
request = pb.ListProductsRequest(
category=category ,
page_size=page_size
)
min_price max_price :
request..CopyFrom(pb.ListProductsRequest.Filter(
min_price=min_price ,
max_price=max_price ()
))
:
stream = ._get_stub().ListProducts(
request,
metadata=._get_metadata(),
timeout=
)
product stream:
product
grpc.RpcError e:
logger.error()
() -> pb.ImportSummary:
():
product products:
product
asyncio.sleep()
:
response = ._get_stub().ImportProducts(
generate_products(),
metadata=._get_metadata(),
timeout=
)
response.failed > :
logger.warning(
)
response
grpc.RpcError e:
logger.error()
() -> AsyncIterator[pb.InventoryChange]:
:
stream = ._get_stub().WatchInventory(
metadata=._get_metadata()
)
send_task = asyncio.create_task(._send_updates(stream, updates))
:
change stream:
change
:
send_task.cancel()
grpc.RpcError e:
logger.error()
():
:
update updates:
stream.write(update)
stream.done_writing()
asyncio.CancelledError:
():
close_tasks = [channel.close() channel .channels]
asyncio.gather(*close_tasks)
() -> :
uuid
(uuid.uuid4())
():
client = ProductClient(
servers=[
,
,
],
api_key=,
use_tls=
)
:
product = client.get_product()
()
product client.list_products(
category=,
min_price=,
max_price=
):
()
products_to_import = [
pb.Product(=, name=, price=)
i ()
]
summary = client.import_products(products_to_import)
()
():
i ():
pb.InventoryUpdate(
product_id=,
quantity_change=,
warehouse_id=
)
asyncio.sleep()
change client.watch_inventory(generate_updates()):
()
:
client.close()
__name__ == :
asyncio.run(main())
// server/index.js
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const path = require('path');
// Load proto file
const PROTO_PATH = path.join(__dirname, '../proto/ecommerce.proto');
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
const ecommerce = protoDescriptor.ecommerce.v1;
// Health check implementation
const health = require('grpc-health-check');
const healthImpl = new health.Implementation({
'': 'SERVING',
'ecommerce.v1.ProductService': 'SERVING'
});
// Service implementation
class ProductService {
constructor() {
. = ();
. = ();
}
() {
{ product_id } = call.;
product = ..(product_id);
(!product) {
({
: grpc..,
:
});
;
}
(, product);
}
() {
{ category, page_size } = call.;
count = ;
( [id, product] .) {
(category && product..(category) === -) {
;
}
call.(product);
count++;
(page_size > && count >= page_size) {
;
}
( (resolve, ));
}
call.();
}
}
() {
server = grpc.({
: ,
: * *
});
server.(
ecommerce..,
()
);
server.(health., healthImpl);
server.(
,
grpc..(),
{
(err) {
.(, err);
;
}
.();
server.();
}
);
}
();
| Error | Cause | Solution |
|---|---|---|
| "Failed to compile proto" | Invalid Protocol Buffer syntax | Validate with protoc --lint |
| "Connection refused" | Server not running or wrong port | Check server status and port |
| "Deadline exceeded" | Request timeout | Increase timeout or optimize operation |
| "Resource exhausted" | Rate limiting or quota exceeded | Implement backoff and retry |
| "Unavailable" | Server temporarily down | Implement circuit breaker pattern |
Server Options
MaxConcurrentStreams: Limit concurrent streams per connectionMaxReceiveMessageSize: Maximum message size (default 4MB)KeepaliveParams: Connection health monitoringConnectionTimeout: Maximum idle time before closingClient Options
LoadBalancingPolicy: round_robin, pick_first, grpclbWaitForReady: Block until server availableRetry: Automatic retry configurationInterceptors: Add cross-cutting concernsDO:
DON'T:
/rest-api-generator - Generate REST APIs/graphql-server-builder - Build GraphQL servers/api-gateway-builder - Create API gateways/webhook-handler-creator - Handle webhooks/websocket-server-builder - WebSocket servers