cab-backend/examples/client.example.txt
2026-03-30 21:00:35 +03:00

155 lines
3.4 KiB
Plaintext

package main
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"log"
"sort"
"strconv"
"strings"
"time"
walletpb "_gen"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/metadata"
)
const (
sharedSecret = "super_secret_key"
serviceName = "game-service"
)
func main() {
conn, err := grpc.Dial(
"localhost:50051",
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithUnaryInterceptor(hmacUnaryClientInterceptor(sharedSecret, serviceName)),
)
if err != nil {
log.Fatalf("dial error: %v", err)
}
defer conn.Close()
client := walletpb.NewWalletServiceClient(conn)
ctx := context.Background()
1. reserve
reserveResp, err := client.Reserve(ctx, &walletpb.ReserveRequest{
RequestId: "req-1",
UserId: "user-1",
GameId: "slot-42",
RoundId: "round-1001",
Amount: 1000, 10.00
Currency: "EUR",
})
if err != nil {
log.Fatalf("reserve error: %v", err)
}
log.Printf("reserve: tx=%s status=%s available=%d reserved=%d",
reserveResp.TransactionId,
reserveResp.Status,
reserveResp.AvailableBalance,
reserveResp.ReservedBalance,
)
Тут игра “сыграла”. Допустим пользователь выиграл 25.00
confirmResp, err := client.Confirm(ctx, &walletpb.ConfirmRequest{
RequestId: "req-2",
TransactionId: reserveResp.TransactionId,
RoundId: "round-1001",
WinAmount: 2500,
})
if err != nil {
log.Fatalf("confirm error: %v", err)
}
log.Printf("confirm: tx=%s status=%s available=%d reserved=%d",
confirmResp.TransactionId,
confirmResp.Status,
confirmResp.AvailableBalance,
confirmResp.ReservedBalance,
)
txResp, err := client.GetTransaction(ctx, &walletpb.GetTransactionRequest{
TransactionId: reserveResp.TransactionId,
})
if err != nil {
log.Fatalf("get tx error: %v", err)
}
log.Printf("transaction: id=%s status=%s amount=%d win=%d",
txResp.TransactionId, txResp.Status, txResp.Amount, txResp.WinAmount)
}
---------- HMAC client interceptor ----------
func hmacUnaryClientInterceptor(secret, service string) grpc.UnaryClientInterceptor {
return func(
ctx context.Context,
method string,
req any,
reply any,
cc *grpc.ClientConn,
invoker grpc.UnaryInvoker,
opts ...grpc.CallOption,
) error {
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
md, ok := metadata.FromOutgoingContext(ctx)
if !ok {
md = metadata.New(nil)
} else {
md = md.Copy()
}
md.Set("x-service-name", service)
md.Set("x-timestamp", timestamp)
payload := buildSigningPayload(service, method, timestamp, md)
signature := computeHMAC(payload, secret)
md.Set("x-signature", signature)
ctx = metadata.NewOutgoingContext(ctx, md)
return invoker(ctx, method, req, reply, cc, opts...)
}
}
func computeHMAC(payload, secret string) string {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(payload))
return hex.EncodeToString(mac.Sum(nil))
}
func buildSigningPayload(serviceName, method, timestamp string, md metadata.MD) string {
var parts []string
parts = append(parts,
"service="+serviceName,
"method="+method,
"timestamp="+timestamp,
)
var keys []string
for k := range md {
if strings.ToLower(k) == "x-signature" {
continue
}
if strings.HasPrefix(strings.ToLower(k), ":") {
continue
}
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
vals := md.Get(k)
sort.Strings(vals)
parts = append(parts, k+"="+strings.Join(vals, ","))
}
return strings.Join(parts, "|")
}