Cách tạo nguồn cấp dữ liệu ưu đãi

Trong hướng dẫn sau đây, chúng ta sẽ minh hoạ cách tạo nguồn cấp dữ liệu bằng JSON bằng cách sử dụng định nghĩa Google Protocol Buffers (protobuf). Chúng ta sẽ dùng trình biên dịch protobuf để tạo mã nguồn dựa trên giản đồ protobuf. Việc tạo nguồn cấp dữ liệu bằng mã nguồn đã tạo sẽ cung cấp một giao diện dễ dàng và ngăn các thực thể nguồn cấp dữ liệu được tạo bằng tên trường hoặc loại trường không chính xác.

Thiết lập dự án

  • Tạo một thư mục dự án mới.
  • Sao chép nội dung của offer.proto money.proto dayofweek.proto timeofday.proto date.proto từ định nghĩa proto dưới dạng các tệp mới vào thư mục gốc của dự án.
  • Cài đặt trình biên dịch protoc

    Để tạo mã nguồn từ các tệp .proto, bạn sẽ cần trình biên dịch protoc. Tải tệp nhị phân được tạo sẵn mới nhất xuống từ trang phát hành của Protocol Buffers trên GitHub.

    Giải nén tệp zip và thêm đường dẫn bin vào biến môi trường PATH, ví dụ:

    unzip protoc-22.0-linux-x86_64.zip -d /usr/local/protoc
    export PATH="$PATH:/usr/local/protoc/bin"
      

Tạo mã nguồn

Python

  1. Cài đặt thư viện protobuf Python
    pip install protobuf
        
  2. Tạo mã nguồn ứng dụng.

    Tạo thư mục đầu ra proto: generated

    protoc --python_out=./generated offer.proto money.proto dayofweek.proto timeofday.proto date.proto
        

    Cập nhật biến môi trường PYTHONPATH để thêm đường dẫn đã tạo, ví dụ:

    export PYTHONPATH="$PYTHONPATH:./generated"
        

    Nếu bạn đang sử dụng Bazel, hãy thử quy tắc py_proto_library thay vì chạy protoc.

Ví dụ về cách sử dụng

Bạn có thể xem toàn bộ dự án mẫu tại đây.

import json
from google.protobuf.json_format import MessageToDict
from google.protobuf.timestamp_pb2 import Timestamp


# Replace these imports with your actual generated proto package paths
from generated import offer_pb2
from generated import money_pb2
from generated import dayofweek_pb2
from generated import timeofday_pb2

_MAX_BYTES_DATA_FILE = 200 * 1024 * 1024

def generate_offer_feed():

    # Create OfferFeed
    feed = offer_pb2.OfferFeed()

    # Build the offers
    offer = offer_pb2.Offer(
        offer_id="offer-1",
        entity_ids=["dining-1"],
        offer_source=offer_pb2.OFFER_SOURCE_AGGREGATOR,
        action_type=offer_pb2.ACTION_TYPE_DINING,
        offer_modes=[
            offer_pb2.OFFER_MODE_WALK_IN,
            offer_pb2.OFFER_MODE_FREE_RESERVATION
        ],
        offer_category=offer_pb2.OFFER_CATEGORY_BASE_OFFER,
        offer_details=offer_pb2.OfferDetails(
            offer_display_text="₹100 off on your order",
            # Note: If this is a 'oneof', you set the field name directly
            discount_value=money_pb2.Money(
                currency_code="INR",
                units=100
            )
        ),
        offer_restrictions=offer_pb2.OfferRestrictions(
            combinable_with_other_offers=True,
            combinable_offer_categories=[
                offer_pb2.OFFER_CATEGORY_ADD_ON_PAYMENT_OFFER,
                offer_pb2.OFFER_CATEGORY_ADD_ON_COUPON_OFFER
            ]
        ),
        terms=offer_pb2.Terms(
            restricted_to_certain_users=False,
            terms_and_conditions="Valid on all menu items."
        ),
        validity_periods=[
            offer_pb2.ValidityPeriod(
                valid_period=offer_pb2.ValidityRange(
                    valid_from_time=Timestamp(seconds=1687062000),
                    valid_through_time=Timestamp(seconds=1956556800)
                ),
                time_of_day=[
                    # Monday - Thursday Window
                    offer_pb2.TimeOfDayWindow(
                        time_windows=offer_pb2.TimeOfDayRange(
                            open_time=timeofday_pb2.TimeOfDay(hours=13),
                            close_time=timeofday_pb2.TimeOfDay(hours=23)
                        ),
                        day_of_week=[
                            dayofweek_pb2.DayOfWeek.MONDAY,
                            dayofweek_pb2.DayOfWeek.TUESDAY,
                            dayofweek_pb2.DayOfWeek.WEDNESDAY,
                            dayofweek_pb2.DayOfWeek.THURSDAY
                        ]
                    ),
                    # Friday - Sunday Window
                    offer_pb2.TimeOfDayWindow(
                        time_windows=offer_pb2.TimeOfDayRange(
                            open_time=timeofday_pb2.TimeOfDay(hours=13),
                            close_time=timeofday_pb2.TimeOfDay(hours=23, minutes=59, seconds=59)
                        ),
                        day_of_week=[
                            dayofweek_pb2.DayOfWeek.FRIDAY,
                            dayofweek_pb2.DayOfWeek.SATURDAY,
                            dayofweek_pb2.DayOfWeek.SUNDAY
                        ]
                    )
                ]
            )
        ],
        offer_url="https://www.example-restaurant.com/offer/base_offer_1",
        image_url="https://www.example-restaurant.com/images/offer_base.jpg"
    )

    # Example testing for menu feed size
    # Protocol buffer message must be less than 2 GiB
    # https://protobuf.dev/programming-guides/proto-limits/
    # It is recommended to not exceed 200 MB, as there is an Actions
    # Center limit of 200 MB per file after compression.
    if feed.ByteSize() + offer.ByteSize()  < _MAX_BYTES_DATA_FILE:
	    feed.data.append(offer)
    # else write current feed to file and start a new feed

    # Serialize to JSON
    # preserving_proto_field_names=True ensures camelCase becomes snake_case if defined that way in .proto
    json_output = json.dumps(
        MessageToDict(feed, preserving_proto_field_name=True)
    )

    print(json_output)

if __name__ == "__main__":
    generate_offer_feed()
    

Ví dụ về mã này minh hoạ cách tạo nguồn cấp dữ liệu ưu đãi DINING. Sau đó, ví dụ này cho thấy cách chuyển đổi nguồn cấp dữ liệu thành JSON.

API Python cho phép khởi tạo từng phần các đối tượng lồng nhau bằng cách thiết lập các thuộc tính.

Java

  1. Thêm các phần phụ thuộc protobuf-javaprotobuf-java-util vào dự án bằng maven hoặc gradle như mô tả tại đây.
  2. Tạo mã nguồn ứng dụng.
    protoc --java_out=src/main/java offer.proto money.proto dayofweek.proto timeofday.proto date.proto
        

    Bạn có thể sử dụng protobuf-maven-plugin để tạo mã nguồn trong thời gian biên dịch khi sử dụng maven.

    Nếu bạn đang sử dụng Bazel, hãy thử quy tắc java_proto_library thay vì chạy protoc.

Ví dụ về cách sử dụng

Bạn có thể xem toàn bộ dự án mẫu tại đây.

package com.example.offers;

import com.google.protobuf.util.JsonFormat;
import ext.maps.booking.feeds.offers.Offer;
import ext.maps.booking.feeds.offers.OfferFeed;
import ext.maps.booking.feeds.offers.OfferDetails;
import ext.maps.booking.feeds.offers.OfferRestrictions;
import ext.maps.booking.feeds.offers.Terms;
import ext.maps.booking.feeds.offers.ValidityPeriod;
import ext.maps.booking.feeds.offers.ValidityRange;
import ext.maps.booking.feeds.offers.OfferCategory;
import ext.maps.booking.feeds.offers.OfferMode;
import ext.maps.booking.feeds.offers.OfferSource;
import ext.maps.booking.feeds.offers.ActionType;
import ext.maps.booking.feeds.offers.TimeOfDayWindow;
import ext.maps.booking.feeds.offers.TimeOfDayRange;

import com.google.type.Money;
import com.google.protobuf.Timestamp;
import com.google.type.DayOfWeek;
import com.google.type.TimeOfDay;


public class OfferFeedGenerator {

    // 200 MB
    public static final int MAX_BYTES_DATA_FILE = 200 * 1024 * 1024;
  
    public static void main(String[] args) throws Exception {
        
        // Create OfferFeed
        OfferFeed.Builder feed = OfferFeed.newBuilder();

        // Build the offers
        Offer offer = Offer.newBuilder()
            .setOfferId("offer-1")
            .addEntityIds("dining-1")
            .setOfferSource(OfferSource.OFFER_SOURCE_AGGREGATOR)
            .setActionType(ActionType.ACTION_TYPE_DINING)
            .addOfferModes(OfferMode.OFFER_MODE_WALK_IN)
            .addOfferModes(OfferMode.OFFER_MODE_FREE_RESERVATION)
            .setOfferCategory(OfferCategory.OFFER_CATEGORY_BASE_OFFER)
            .setOfferDetails(OfferDetails.newBuilder()
                .setOfferDisplayText("₹100 off on your order")
                .setDiscountValue(Money.newBuilder()
                    .setCurrencyCode("INR")
                    .setUnits(100)))
            .setOfferRestrictions(OfferRestrictions.newBuilder()
                .setCombinableWithOtherOffers(true)
                .addCombinableOfferCategories(OfferCategory.OFFER_CATEGORY_ADD_ON_PAYMENT_OFFER)
                .addCombinableOfferCategories(OfferCategory.OFFER_CATEGORY_ADD_ON_COUPON_OFFER))
            .setTerms(Terms.newBuilder()
                .setRestrictedToCertainUsers(false)
                .setTermsAndConditions("Valid on all menu items."))
            .addValidityPeriods(ValidityPeriod.newBuilder()
                .setValidPeriod(ext.maps.booking.feeds.offers.ValidityRange.newBuilder()
                    .setValidFromTime(Timestamp.newBuilder().setSeconds(1687062000))
                    .setValidThroughTime(Timestamp.newBuilder().setSeconds(1956556800)))
                // // Monday - Thursday Window
                .addTimeOfDay(ext.maps.booking.feeds.offers.TimeOfDayWindow.newBuilder()
                    .setTimeWindows(ext.maps.booking.feeds.offers.TimeOfDayRange.newBuilder()
                        .setOpenTime(com.google.type.TimeOfDay.newBuilder().setHours(13))
                        .setCloseTime(com.google.type.TimeOfDay.newBuilder().setHours(23)))
                    .addDayOfWeek(DayOfWeek.MONDAY)
                    .addDayOfWeek(DayOfWeek.TUESDAY)
                    .addDayOfWeek(DayOfWeek.WEDNESDAY)
                    .addDayOfWeek(DayOfWeek.THURSDAY))
                // Friday - Sunday Window
                .addTimeOfDay(ext.maps.booking.feeds.offers.TimeOfDayWindow.newBuilder()
                    .setTimeWindows(ext.maps.booking.feeds.offers.TimeOfDayRange.newBuilder()
                        .setOpenTime(com.google.type.TimeOfDay.newBuilder().setHours(13))
                        .setCloseTime(com.google.type.TimeOfDay.newBuilder().setHours(23).setMinutes(59).setSeconds(59)))
                    .addDayOfWeek(DayOfWeek.FRIDAY)
                    .addDayOfWeek(DayOfWeek.SATURDAY)
                    .addDayOfWeek(DayOfWeek.SUNDAY))
            )
            .setOfferUrl("https://www.example-restaurant.com/offer/base_offer_1")
            .setImageUrl("https://www.example-restaurant.com/images/offer_base.jpg")
            .build();


        // Example testing for offer feed size
        // Protocol buffer message must be less than 2 GiB
        // https://protobuf.dev/programming-guides/proto-limits/
        // It is recommended to not exceed 200 MB, as there is an Actions
        // Center limit of 200 MB per file after compression.
        int offerSize = offer.getSerializedSize();
        int currentFeedSize = feed.build().getSerializedSize();

        if (currentFeedSize + offerSize < MAX_BYTES_DATA_FILE) {
            feed.addData(offer);
        } else {
            // 1. Serialize and save the current 'feed' to a file
            // 2. Reset 'feed' to a new empty OfferFeed
            // 3. Add the 'offer' to the new feed
        }

        // Serialize to JSON
        String jsonOutput = JsonFormat.printer()
            .preservingProtoFieldNames()
            .print(feed);

        System.out.println(jsonOutput);
    }
}

    

Ví dụ về mã này minh hoạ cách tạo nguồn cấp dữ liệu ưu đãi DINING. Sau đó, ví dụ này cho thấy cách chuyển đổi nguồn cấp dữ liệu thành JSON.

Go

  1. Cài đặt trình bổ trợ protoc cho go
    go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
        

    Cập nhật biến môi trường PATH để thêm trình bổ trợ protoc-gen-go, ví dụ:

    export PATH="$PATH:$(go env GOPATH)/bin"
        
  2. Khởi chạy ứng dụng và tạo mã nguồn ứng dụng.
    go mod init feed/app
    mkdir generated
    protoc --go_out=./generated/ offer.proto money.proto dayofweek.proto timeofday.proto date.proto
        

    Nếu bạn đang sử dụng Bazel, hãy thử quy tắc go_proto_library thay vì chạy protoc.

Ví dụ về cách sử dụng

Bạn có thể xem toàn bộ dự án mẫu tại đây.

package main

import (
	pb "feed/app/generated/ext/maps/booking/offers/proto"
	"fmt"
	"log"

	money "google.golang.org/genproto/googleapis/type/money"
	timestamppb "google.golang.org/protobuf/types/known/timestamppb"
	dayofweek "google.golang.org/genproto/googleapis/type/dayofweek"
	timeofday "google.golang.org/genproto/googleapis/type/timeofday"
	proto "google.golang.org/protobuf/proto"

	"google.golang.org/protobuf/encoding/protojson"
)

func main() {
	// 200 MB feed file size limit
	const MaxBytesDataFile = 200 * 1024 * 1024 

	// Create OfferFeed with offers
	feed := &pb.OfferFeed{}

	// Build the offers
	offer := &pb.Offer{
		OfferId:   "offer-1",
		EntityIds: []string{"dining-1"},
		OfferSource: pb.OfferSource_OFFER_SOURCE_AGGREGATOR,
		ActionType:  pb.ActionType_ACTION_TYPE_DINING,
		OfferModes: []pb.OfferMode{
			pb.OfferMode_OFFER_MODE_WALK_IN,
			pb.OfferMode_OFFER_MODE_FREE_RESERVATION,
		},
		OfferCategory: pb.OfferCategory_OFFER_CATEGORY_BASE_OFFER,
		OfferDetails: &pb.OfferDetails{
			OfferDisplayText: "₹100 off on your order",
			OfferSpecification: &pb.OfferDetails_DiscountValue{
        		DiscountValue: &money.Money{
            		CurrencyCode: "INR",
            		Units:        100,
				},
			},
		},
		OfferRestrictions: &pb.OfferRestrictions{
			CombinableWithOtherOffers: true,
			CombinableOfferCategories: []pb.OfferCategory{
				pb.OfferCategory_OFFER_CATEGORY_ADD_ON_PAYMENT_OFFER,
				pb.OfferCategory_OFFER_CATEGORY_ADD_ON_COUPON_OFFER,
			},
		},
		Terms: &pb.Terms{
			RestrictedToCertainUsers: false,
			TermsAndConditions:       "Valid on all menu items.",
		},
		ValidityPeriods: []*pb.ValidityPeriod{
			{
				ValidPeriod: &pb.ValidityRange{
					ValidFromTime:    &timestamppb.Timestamp{Seconds: 1687062000},
					ValidThroughTime: &timestamppb.Timestamp{Seconds: 1956556800},
				},
				TimeOfDay: []*pb.TimeOfDayWindow{
					// Monday - Thursday Window
					{
						TimeWindows: &pb.TimeOfDayRange{
							OpenTime:  &timeofday.TimeOfDay{Hours: 13},
							CloseTime: &timeofday.TimeOfDay{Hours: 23},
						},
						DayOfWeek: []dayofweek.DayOfWeek{
							dayofweek.DayOfWeek_MONDAY,
							dayofweek.DayOfWeek_TUESDAY,
							dayofweek.DayOfWeek_WEDNESDAY,
							dayofweek.DayOfWeek_THURSDAY,
						},
					},
					// Friday - Sunday Window
					{
						TimeWindows: &pb.TimeOfDayRange{
							OpenTime: &timeofday.TimeOfDay{Hours: 13},
							CloseTime: &timeofday.TimeOfDay{
								Hours:   23,
								Minutes: 59,
								Seconds: 59,
							},
						},
						DayOfWeek: []dayofweek.DayOfWeek{
							dayofweek.DayOfWeek_FRIDAY,
							dayofweek.DayOfWeek_SATURDAY,
							dayofweek.DayOfWeek_SUNDAY,
						},
					},
				},
			},
		},
		OfferUrl: "https://www.example-restaurant.com/offer/base_offer_1",
		ImageUrl: "https://www.example-restaurant.com/images/offer_base.jpg",
	}

	// Example testing for feed size
	// Protocol buffer message must be less than 2 GiB
	// https://protobuf.dev/programming-guides/proto-limits/
	// It is recommended to not exceed 200 MB, as there is an Actions
	// Center limit of 200 MB per file after compression.
	offerSize := proto.Size(offer)
	currentFeedSize := proto.Size(feed)

	if currentFeedSize + offerSize < MaxBytesDataFile {
		feed.Data = append(feed.Data, offer)
	} else {
		// 1. Serialize and save the current 'feed' to a file
		// 2. Reset 'feed' to a new empty OfferFeed
		// 3. Add the 'offer' to the new feed
	}

	// Serialize to JSON
	marshaler := protojson.MarshalOptions{
		UseProtoNames: true,
	}

	jsonOutput, err := marshaler.Marshal(feed)
	if err != nil {
		log.Fatalf("Failed to marshal feed: %v", err)
	}

	fmt.Println(string(jsonOutput))
}
    

Ví dụ về mã này minh hoạ cách tạo nguồn cấp dữ liệu ưu đãi DINING. Sau đó, ví dụ này cho thấy cách chuyển đổi nguồn cấp dữ liệu thành JSON.

TypeScript

  1. Cài đặt trình bổ trợ protoc TypeScript.
    npm init
    npm i -D typescript
    npm i ts-proto
          
    Xin lưu ý rằng ts-proto không phải là một dự án chính thức được Google hỗ trợ.
  2. Tạo thư mục đầu ra và tạo mã nguồn ứng dụng.

    Tạo thư mục đầu ra proto: src/generated

    protoc --plugin="./node_modules/.bin/protoc-gen-ts_proto" --ts_proto_opt=useOptionals=all --ts_proto_opt=snakeToCamel=false --ts_proto_opt=onlyTypes=true --ts_proto_out="./src/generated" offer.proto money.proto dayofweek.proto timeofday.proto date.proto
          

    Nếu bạn đang sử dụng Bazel, hãy thử quy tắc js_proto_library thay vì chạy protoc.

Ví dụ về cách sử dụng

Bạn có thể xem toàn bộ dự án mẫu tại đây.

import { 
  Offer, 
  OfferFeed, 
  OfferSource, 
  ActionType, 
  OfferMode, 
  OfferCategory 
} from "./generated/offer"; // Path to your generated types
import { Money } from "./generated/money";
import { DayOfWeek } from "./generated/dayofweek";
import { TimeOfDay } from "./generated/timeofday";
import { Timestamp } from "./generated/google/protobuf/timestamp";
import { Duration } from "./generated/google/protobuf/duration";

// 200 MB Limit
const MAX_BYTES_DATA_FILE = 200 * 1024 * 1024;

function generateOfferFeed() {
  // Build the Offer object
  const offer: Offer = {
    offer_id: "offer-1",
    entity_ids: ["dining-1"],
    offer_source: OfferSource.OFFER_SOURCE_AGGREGATOR,
    action_type: ActionType.ACTION_TYPE_DINING,
    offer_modes: [
      OfferMode.OFFER_MODE_WALK_IN,
      OfferMode.OFFER_MODE_FREE_RESERVATION,
    ],
    offer_category: OfferCategory.OFFER_CATEGORY_BASE_OFFER,
    offer_details: {
      offer_display_text: "₹100 off on your order",
      // Set 'oneof' field: discountValue
      discount_value: {
        currency_code: "INR",
        units: 100,
      },
    },
    offer_restrictions: {
      combinable_with_other_offers: true,
      combinable_offer_categories: [
        OfferCategory.OFFER_CATEGORY_ADD_ON_PAYMENT_OFFER,
        OfferCategory.OFFER_CATEGORY_ADD_ON_COUPON_OFFER,
      ],
    },
    terms: {
      restricted_to_certain_users: false,
      terms_and_conditions: "Valid on all menu items.",
    },
    validity_periods: [
      {
        valid_period: {
          valid_from_time: new Date(1687062000000),
          valid_through_time: new Date(1956556800000),
        },
        time_of_day: [
          // Monday - Thursday Window
          {
            time_windows: {
              open_time: { hours: 13, minutes: 0, seconds: 0, nanos: 0 },
              close_time: { hours: 23, minutes: 0, seconds: 0, nanos: 0 },
            },
            day_of_week: [
              DayOfWeek.MONDAY,
              DayOfWeek.TUESDAY,
              DayOfWeek.WEDNESDAY,
              DayOfWeek.THURSDAY,
            ],
          },
          // Friday - Sunday Window
          {
            time_windows: {
              open_time: { hours: 13, minutes: 0, seconds: 0, nanos: 0 },
              close_time: { hours: 23, minutes: 59, seconds: 59, nanos: 0 },
            },
            day_of_week: [
              DayOfWeek.FRIDAY,
              DayOfWeek.SATURDAY,
              DayOfWeek.SUNDAY,
            ],
          },
        ],
      },
    ],
    offer_url: "https://www.example-restaurant.com/offer/base_offer_1",
    image_url: "https://www.example-restaurant.com/images/offer_base.jpg",
  };

  // 2. Initialize the Feed
  const feed: OfferFeed = { data: [] };

  // 3. Size checking
  // encode().finish() returns a Uint8Array (the binary representation)
  const offerSize = new Blob([JSON.stringify(offer)]).size;
  const currentFeedSize = new Blob([JSON.stringify(feed)]).size;
  
  if (currentFeedSize + offerSize < MAX_BYTES_DATA_FILE) {
    if (feed.data == undefined) {
      feed.data = [offer]; 
    } else {
      feed.data.push(offer);
    }
  } else {
    // Logic for file rotation goes here
    console.log("Feed size limit reached. Rotate file.");
  }

  // 4. Serialize to JSON
  // In JS/TS, we often just use JSON.stringify for the plain object
  // if you used proto3 and want to ensure proper enum/timestamp formatting,
  // use the library's toJSON method.
  const jsonOutput = JSON.stringify(feed);
  console.log(jsonOutput);
}

generateOfferFeed();

  

Ví dụ về mã này minh hoạ cách tạo nguồn cấp dữ liệu ưu đãi DINING. Sau đó, ví dụ này cho thấy cách chuyển đổi nguồn cấp dữ liệu thành JSON.

Lưu ý: Có các tiện ích trình biên dịch protoc của bên thứ ba để tạo Giản đồ JSON từ giản đồ proto. Nếu muốn làm việc với các tệp JSON Schema, bạn có thể xem một ví dụ như vậy tại đây.