Tự động tạo định nghĩa kiểu TypeScript từ gRPC trong ứng dụng Go

Để đảm bảo mã khách hàng tuân thủ kiểu mạnh và giảm thiểu lỗi khi sử dụng API, chúng ta có thể tự động tạo các định nghĩa kiểu TypeScript từ tệp .proto. Dưới đây là hướng dẫn chi tiết để thực hiện điều này.

Sử dụng thư viện protobufjs

Tài liệu tham khảo:

Cài đặt:

yarn add protobufjs

Sau khi cài đặt, bạn sẽ có hai công cụ sau trong thư mục node_modules/.bin:

  • pbjs: Công cụ chuyển đổi tệp .proto thành mã JavaScript.
  • pbts: Công cụ tạo tệp định nghĩa kiểu TypeScript từ mã JavaScript đã được chuyển đổi.

Tạo định nghĩa kiểu TypeScript từ tệp auth.proto

Chúng ta sẽ viết một script shell để tự động hóa quá trình này. Script này sẽ:

  1. Chuyển đổi tệp .proto thành mã JavaScript.
  2. Tạo tệp định nghĩa kiểu TypeScript từ mã JavaScript.

Script ví dụ:

PROTO_PATH=../microsvcs/auth/api
PBTS_BIN_DIR=./node_modules/.bin
PBTS_OUT_DIR=./miniprogram/service/proto_gen/auth
mkdir -p $PBTS_OUT_DIR

# Chuyển đổi tệp .proto sang mã JavaScript
$PBTS_BIN_DIR/pbjs -t static -w es6 $PROTO_PATH/auth.proto \
  --no-create --no-encode --no-decode --no-verify --no-delimited \
  -o $PBTS_OUT_DIR/auth_pb_tmp.js

# Tạo tệp JavaScript cuối cùng với import protobufjs
echo 'import * as $protobuf from "protobufjs";' > $PBTS_OUT_DIR/auth_pb.js
cat $PBTS_OUT_DIR/auth_pb_tmp.js >> $PBTS_OUT_DIR/auth_pb.js
rm $PBTS_OUT_DIR/auth_pb_tmp.js

# Tạo tệp định nghĩa kiểu TypeScript
$PBTS_BIN_DIR/pbts -o $PBTS_OUT_DIR/auth_pb.d.ts $PBTS_OUT_DIR/auth_pb.js

Kết quả sẽ là hai tệp:

  • auth_pb.js: Mã JavaScript từ tệp .proto.
  • auth_pb.d.ts: Định nghĩa kiểu TypeScript tương ứng.

Bạn có thể chạy script này bằng cách thực thi sh gen_ts.sh trong thư mục miniprogram.

Cập nhật tệp app.ts

Trong tệp app.ts, chúng ta cần nhập tệp định nghĩa kiểu TypeScript vừa tạo:

import { Auth } from "./service/proto_gen/auth/auth_pb";

// Ví dụ: Sử dụng định nghĩa kiểu
const authInstance: Auth = {
  token: "example-token",
  expiresIn: 3600,
};

Để đảm bảo tên thuộc tính phù hợp với quy tắc camelCase của JavaScript/TypeScript, chúng ta có thể sử dụng thư viện camelcase-keys:

yarn add camelcase-keys

Ví dụ sử dụng:

import camelcaseKeys from "camelcase-keys";

const response = {
  expires_in: 3600,
  access_token: "example-token",
};

const formattedResponse = camelcaseKeys(response);
console.log(formattedResponse); // { expiresIn: 3600, accessToken: "example-token" }

Xác thực token JWT

Mã nguồn xác thực token

Tại file token.go, chúng ta có thể triển khai lớp JWTTokenVerifier để kiểm tra token:

package auth

import (
	"crypto/rsa"
	"fmt"

	"github.com/golang-jwt/jwt/v4"
)

type TokenVerifier struct {
	PublicKey *rsa.PublicKey
}

func (v *TokenVerifier) Verify(tokenString string) (string, error) {
	token, err := jwt.ParseWithClaims(tokenString, &jwt.RegisteredClaims{}, func(token *jwt.Token) (interface{}, error) {
		return v.PublicKey, nil
	})
	if err != nil {
		return "", fmt.Errorf("lỗi phân tích token: %v", err)
	}
	if !token.Valid {
		return "", fmt.Errorf("token không hợp lệ")
	}
	claims, ok := token.Claims.(*jwt.RegisteredClaims)
	if !ok {
		return "", fmt.Errorf("không thể trích xuất claims từ token")
	}
	if err := claims.Validate(); err != nil {
		return "", fmt.Errorf("claims không hợp lệ: %v", err)
	}
	return claims.Subject, nil
}

Viết test case cho chức năng xác thực

Dưới đây là một số trường hợp cần kiểm tra:

  • Token hợp lệ.
  • Token hết hạn.
  • Token bị thao tác trái phép.
  • Token có chữ ký không đúng.

Mã test case có thể như sau:

package auth_test

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"testing"
	"time"

	"github.com/golang-jwt/jwt/v4"
	"github.com/stretchr/testify/assert"
	"your_project_path/auth"
)

func TestTokenVerification(t *testing.T) {
	privateKey, _ := rsa.GenerateKey(rand.Reader, 2048)
	publicKey := &privateKey.PublicKey

	verifier := auth.TokenVerifier{PublicKey: publicKey}

	t.Run("Valid Token", func(t *testing.T) {
		claims := jwt.RegisteredClaims{
			Subject:   "user123",
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
		}
		token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
		signedToken, _ := token.SignedString(privateKey)

		subject, err := verifier.Verify(signedToken)
		assert.NoError(t, err)
		assert.Equal(t, "user123", subject)
	})

	t.Run("Expired Token", func(t *testing.T) {
		claims := jwt.RegisteredClaims{
			Subject:   "user123",
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(-1 * time.Hour)),
		}
		token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
		signedToken, _ := token.SignedString(privateKey)

		_, err := verifier.Verify(signedToken)
		assert.Error(t, err)
	})
}

Thẻ: Go grpc typescript

Đăng vào ngày 1 tháng 6 lúc 06:34