Как создать ленту предложений

В этом руководстве мы продемонстрируем, как создать JSON-ленту, используя определение протокола Google Protocol Buffers (protobuf). Мы будем использовать компилятор protobuf для генерации исходного кода на основе схемы protobuf. Создание ленты с использованием сгенерированного исходного кода обеспечивает простой интерфейс и предотвращает создание сущностей ленты с некорректными именами или типами полей.

Настройка проекта

  • Создайте новую директорию проекта.
  • Скопируйте содержимое файлов offer.proto, money.proto, dayofweek.proto, timeofday.proto, date.proto из раздела определений протоколов в качестве новых файлов в корневую директорию вашего проекта.
  • Установите компилятор protoc.

    Для генерации исходного кода из файлов .proto вам потребуется компилятор protoc. Загрузите последнюю предварительно собранную версию с страницы релизов Protocol Buffers на GitHub.

    Распакуйте архив и добавьте путь к исполняемому файлу в переменную среды PATH , например:

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

Сгенерировать исходный код

Python

  1. Установите библиотеку Python protobuf.
    pip install protobuf
        
  2. Сгенерировать исходный код клиента.

    Создайте выходной каталог для proto-файла: generated

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

    Обновите переменную среды PYTHONPATH, добавив в нее сгенерированный путь, например:

    export PYTHONPATH="$PYTHONPATH:./generated"
        

    Если вы используете Bazel , попробуйте правило py_proto_library в качестве альтернативы запуску protoc.

Пример использования

Полный пример проекта можно найти здесь .

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()
    

В приведенном примере кода показано, как создать фид с предложениями по питанию. Затем в примере демонстрируется, как сериализовать фид в формат JSON.

API Python позволяет выполнять отложенную инициализацию вложенных объектов путем установки их свойств.

Java

  1. Добавьте зависимости protobuf-java и protobuf-java-util в свой проект, используя Maven или Gradle, как описано здесь .
  2. Сгенерировать исходный код клиента.
    protoc --java_out=src/main/java offer.proto money.proto dayofweek.proto timeofday.proto date.proto
        

    Для генерации исходного кода во время компиляции с помощью Maven можно использовать плагин protobuf-maven-plugin .

    Если вы используете Bazel , попробуйте правило java_proto_library в качестве альтернативы запуску protoc.

Пример использования

Полный пример проекта можно найти здесь .

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);
    }
}

    

В приведенном примере кода показано, как создать фид с предложениями по питанию. Затем в примере демонстрируется, как сериализовать фид в формат JSON.

Идти

  1. Установите плагин protoc для Go.
    go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
        

    Обновите переменную среды PATH, добавив в нее плагин protoc-gen-go, например:

    export PATH="$PATH:$(go env GOPATH)/bin"
        
  2. Инициализируйте приложение и сгенерируйте исходный код клиента.
    go mod init feed/app
    mkdir generated
    protoc --go_out=./generated/ offer.proto money.proto dayofweek.proto timeofday.proto date.proto
        

    Если вы используете Bazel , попробуйте правило go_proto_library в качестве альтернативы запуску protoc.

Пример использования

Полный пример проекта можно найти здесь .

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))
}
    

В приведенном примере кода показано, как создать фид с предложениями по питанию. Затем в примере демонстрируется, как сериализовать фид в формат JSON.

Машинопись

  1. Установите плагин TypeScript protoc.
    npm init
    npm i -D typescript
    npm i ts-proto
          
    Обратите внимание, что ts-proto не является официально поддерживаемым проектом Google.
  2. Создайте выходной каталог и сгенерируйте исходный код клиента.

    Создайте каталог для вывода прототипа: 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
          

    Если вы используете Bazel , попробуйте правило js_proto_library в качестве альтернативы запуску protoc.

Пример использования

Полный пример проекта можно найти здесь .

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();

  

В приведенном примере кода показано, как создать фид с предложениями по питанию. Затем в примере демонстрируется, как сериализовать фид в формат JSON.

Примечание: Существуют сторонние расширения для компилятора protoc, позволяющие генерировать JSON-схему из proto-схемы. Если вы предпочитаете работать с файлами JSON-схемы, вы можете ознакомиться с одним из таких примеров здесь .